Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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