Skip to content
Merged
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2018 Split Software, Co.
Copyright © 2020 Split Software, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
69 changes: 35 additions & 34 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
"""Setup module."""
#!/usr/bin/env python

from setuptools import setup, find_packages
from os import path
from setuptools import setup, find_packages

tests_require = [
TESTS_REQUIRES = [
'flake8',
'pytest',
'pytest<=4.6', # for deprecated python versions: https://docs.pytest.org/en/latest/py27-py34-deprecation.html
'pytest-mock',
'coverage',
'pytest-cov',
'mock;python_version<"3"'
]
install_requires = [

INSTALL_REQUIRES = [
'requests>=2.9.1',
'pyyaml>=5.1',
'future>=0.15.2',
'docopt>=0.6.2',
'six>=1.10.0',
'enum34;python_version<"3.4"',
'six>=1.10.0;python_version<"3"',
'futures>=3.0.5;python_version<"3"'
]

with open(path.join(path.abspath(path.dirname(__file__)),
'splitio', 'version.py')) as f:
exec(f.read())
exec(f.read()) # pylint: disable=exec-used

setup(name='splitio_client',
version=__version__, # noqa
description='Split.io Python Client',
author='Patricio Echague, Sebastian Arrubia',
author_email='pato@split.io, sebastian@split.io',
url='https://github.com/splitio/python-client',
download_url=('https://github.com/splitio/python-client/tarball/' +
__version__),
license='Apache License 2.0',
install_requires=install_requires,
tests_require=tests_require,
extras_require={
'test': tests_require,
'redis': ['redis>=2.10.5'],
'uwsgi': ['uwsgi>=2.0.0'],
'cpphash': ['mmh3cffi>=0.1.4']
},
setup_requires=['pytest-runner'],
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries'
],
packages=find_packages())
setup(
name='splitio_client',
version=__version__, # pylint: disable=undefined-variable
description='Split.io Python Client',
author='Patricio Echague, Sebastian Arrubia',
author_email='pato@split.io, sebastian@split.io',
url='https://github.com/splitio/python-client',
download_url=('https://github.com/splitio/python-client/tarball/' + __version__), # pylint: disable=undefined-variable
license='Apache License 2.0',
install_requires=INSTALL_REQUIRES,
tests_require=TESTS_REQUIRES,
extras_require={
'test': TESTS_REQUIRES,
'redis': ['redis>=2.10.5'],
'uwsgi': ['uwsgi>=2.0.0'],
'cpphash': ['mmh3cffi>=0.1.4']
},
setup_requires=['pytest-runner'],
classifiers=[
'Environment :: Console',
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries'
],
packages=find_packages()
)
6 changes: 3 additions & 3 deletions splitio/client/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def _build_in_memory_factory(api_key, config, sdk_url=None, events_url=None): #
timeout=cfg.get('connectionTimeout')
)

sdk_metadata = util.get_metadata(config)
sdk_metadata = util.get_metadata(cfg)
apis = {
'splits': SplitsAPI(http_client, api_key),
'segments': SegmentsAPI(http_client, api_key),
Expand Down Expand Up @@ -348,8 +348,8 @@ def _build_redis_factory(api_key, config):
"""Build and return a split factory with redis-based storage."""
cfg = DEFAULT_CONFIG.copy()
cfg.update(config)
sdk_metadata = util.get_metadata(config)
redis_adapter = redis.build(config)
sdk_metadata = util.get_metadata(cfg)
redis_adapter = redis.build(cfg)
storages = {
'splits': RedisSplitStorage(redis_adapter),
'segments': RedisSegmentStorage(redis_adapter),
Expand Down
36 changes: 21 additions & 15 deletions splitio/storage/adapters/cache_trait.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
):
"""Class constructor."""
self._data = {}
self._lock = threading.RLock()
self._lock = threading.Lock()
self._max_age_seconds = max_age_seconds
self._max_size = max_size
self._lru = None
Expand Down Expand Up @@ -109,26 +109,31 @@ def _bubble_up(self, node):
if node is None:
return None

# First item, just set lru & mru
if not self._data:
self._lru = node
self._mru = node
return node

# MRU, just return it
if node is self._mru:
return node

# LRU, update pointer and end-of-list
if node is self._lru:
self._lru = node.next
self._lru.previous = None

if node.previous is not None:
node.previous.next = node.next

if node.next is not None:
node.next.previous = node.previous

if self._lru == node:
if node.next is not None: #only update lru pointer if there are more than 1 elements.
self._lru = node.next

if not self._data:
# if there are no items, set the LRU to this node
self._lru = node
else:
# if there is at least one item, update the MRU chain
self._mru.next = node

node.next = None
node.previous = self._mru
node.previous.next = node
node.next = None
self._mru = node

return node

def _rollover(self):
Expand All @@ -137,13 +142,14 @@ def _rollover(self):
next_item = self._lru.next
del self._data[self._lru.key]
self._lru = next_item
self._lru.previous = None

def __str__(self):
"""User friendly representation of cache."""
nodes = []
node = self._mru
while node is not None:
nodes.append('<%s: %s> -->' % (node.key, node.value))
nodes.append('\t<%s: %s> -->' % (node.key, node.value))
node = node.previous
return '<MRU>\n' + '\n'.join(nodes) + '\n<LRU>'

Expand Down
2 changes: 1 addition & 1 deletion splitio/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '8.1.7'
__version__ = '8.1.8'
19 changes: 19 additions & 0 deletions tests/storage/adapters/test_cache_trait.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Cache testing module."""
#pylint: disable=protected-access,no-self-use,line-too-long
import time
from random import choice

import pytest

Expand Down Expand Up @@ -55,6 +56,9 @@ def test_lru_behaviour(self, mocker):
assert 'some' not in cache._data
assert len(cache._data) == 5
assert cache._lru.key == 'some_other'
for node in cache._data.values():
if node != cache._mru:
assert node.next is not None

# `some_other` should be the next key to be evicted.
# if we issue a get call for it, it should be marked as the MRU,
Expand All @@ -64,6 +68,21 @@ def test_lru_behaviour(self, mocker):
assert cache._mru.key == 'some_other'
assert cache._lru.key == 'another'

def test_intensive_usage_behavior(self, mocker):
"""Test fetches with random repeated strings."""
user_func = mocker.Mock()
user_func.side_effect = lambda *p, **kw: len(p[0])
key_func = mocker.Mock()
key_func.side_effect = lambda *p, **kw: p[0]
cache = cache_trait.LocalMemoryCache(key_func, user_func, 1, 5)

strings = ['a', 'bb', 'ccc', 'dddd', 'eeeee', 'ffffff', 'ggggggg', 'hhhhhhhh',
'jjjjjjjjj', 'kkkkkkkkkk']
for _ in range(0, 100000):
chosen = choice(strings)
assert cache.get(chosen) == len(chosen)
assert cache._lru is not None

def test_expiration_behaviour(self, mocker):
"""Test time expiration works as expected."""
user_func = mocker.Mock()
Expand Down