diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..60fe20e --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,45 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + env: + COVERAGE_OPTIONS: "-a" + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Tox and any other packages + run: pip install tox tox-gh-actions + - name: Test with tox + run: tox + + code_quality: + name: Code Quality + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.12 + uses: actions/setup-python@v2 + with: + python-version: '3.12' + - name: Install Tox + run: pip install tox + - name: isort + run: tox -e isort + - name: readme + run: tox -e readme + - name: flake8 + run: tox -e flake8 + - name: check-manifest + run: tox -e check-manifest diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ff8b126..0000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -sudo: false -language: python -python: - - 3.6 - - 3.7 - - 3.8 -cache: pip -env: - - DJANGO=3.2 - - DJANGO=dev - -matrix: - include: - - python: 3.8 - env: TOXENV=flake8 - - python: 3.8 - env: TOXENV=isort - - python: 3.8 - env: TOXENV=readme - - python: 3.8 - env: TOXENV=check-manifest - allow_failures: - - env: DJANGO=dev -install: -- travis_retry python3 -m pip install tox-travis -script: -- tox -deploy: - provider: pypi - username: __token__ - password: - secure: "HBbDdcrrzRrSXbQGz3cz24/xNIW77tVlHQ+D6DS5raOSV7en5CPaiWZ50YkMQa08nfBOR1RBT8JivrC05qz1+TqMwOz0+rinFeGPmu9gLY8f+1kx0UZnmXG7GHsm6dBGOP6nIHA40+rtK/YLuhskdd2hEBIOAuAxXuCSd1UQ/3bj/nuspDwcot9OdJiyn+Xl+cjI39Gt8WlF7ZBjgFRw1hNSIJ2f0or38zf+SSDY6QgKiJZCn+4sooy0assCms84LSKumSl7Ya50IWwBGJgJaNPxikqgTf3rDP9bT25OLtJw78zJnO4QC2PJ9Y7j2tzeYkelHJM794uAtKNaW1SanlYdBXQoE7m1CE8DszZxCtN/siuz+30whFJ885pPaSN3m5uJct/v9GxBEAHtoCrwwdfrTvzVCU43PGwPZvO768k465rRGUnBInLybO5hTq6myajecZB2WhVXMh7/KMcXlVtnD5PvIhxLkLPVIGBK+1wFfvH/4ijZl8Dhz5PXP53AcX/Ohdli7+kd9DQEvieE2bhDpBaJq2r7B4etW/bjz76GQ704uzuF5JXXEibaT1fox8nqJzghuWfcXJvoTa20iR1quuHg0Z4qBIYh76CUXafi4iIZ0fCcIDVPWM7KSp1rvt4BgCdi1csQdTasw0l0WGoKdOSu+LlNSrqzPLoXQlE=" - distributions: sdist bdist_wheel - skip_upload_docs: true - on: - repo: harikitech/django-elastipymemcache - tags: true - python: 3.8 - condition: "$DJANGO = 3.2" -notifications: - email: false diff --git a/README.rst b/README.rst index 587d806..d1cd44d 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Requirements ------------ * pymemcache -* Django>=2.2 +* Django>=3.2 * django-pymemcache>=1.0 Installation diff --git a/django_elastipymemcache/__init__.py b/django_elastipymemcache/__init__.py index f40c6d0..25e6d8b 100644 --- a/django_elastipymemcache/__init__.py +++ b/django_elastipymemcache/__init__.py @@ -1,2 +1,2 @@ -VERSION = (2, 0, 4) +VERSION = (2, 0, 5) __version__ = '.'.join(map(str, VERSION)) diff --git a/django_elastipymemcache/backend.py b/django_elastipymemcache/backend.py index fadc172..aef6b50 100644 --- a/django_elastipymemcache/backend.py +++ b/django_elastipymemcache/backend.py @@ -84,7 +84,7 @@ def client_servers(self): socket.gaierror, socket.timeout, ) as e: - logger.warn( + logger.warning( 'Cannot connect to cluster %s, err: %s', self.configuration_endpoint_client.server, e, diff --git a/django_elastipymemcache/client.py b/django_elastipymemcache/client.py index cf6847e..7a86683 100644 --- a/django_elastipymemcache/client.py +++ b/django_elastipymemcache/client.py @@ -1,7 +1,7 @@ import logging -from distutils.version import StrictVersion from django.utils.encoding import smart_str +from packaging.version import parse from pymemcache.client.base import Client, _readline from pymemcache.exceptions import MemcacheUnknownError @@ -17,7 +17,7 @@ def __init__(self, *args, ignore_cluster_errors=False, **kwargs): return client def _get_cluster_info_cmd(self): - if StrictVersion(smart_str(self.version())) < StrictVersion('1.4.14'): + if parse(smart_str(self.version())) < parse('1.4.14'): return b'get AmazonElastiCache:cluster\r\n' return b'config get cluster\r\n' @@ -26,7 +26,10 @@ def _extract_cluster_info(self, line): nodes = [] for raw_node in raw_nodes.split(b' '): host, ip, port = raw_node.split(b'|') - nodes.append((smart_str(ip or host), int(port))) + nodes.append('{host}:{port}'.format( + host=smart_str(ip or host), + port=int(port) + )) return { 'version': int(raw_version), 'nodes': nodes, @@ -63,7 +66,7 @@ def get_cluster_info(self): return self._fetch_cluster_info_cmd(cmd, 'config cluster') except Exception as e: if self.ignore_cluster_errors: - logger.warn('Failed to get cluster: %s', e) + logger.warning('Failed to get cluster: %s', e) return { 'version': None, 'nodes': [( diff --git a/requirements.txt b/requirements.txt index 5292a93..82e1394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -check-manifest==0.42 -coverage==5.2.1 -flake8==3.8.3 -isort==5.5.0 -mock==4.0.2 -nose==1.3.7 -pymemcache==3.4.3 -readme-renderer==26.0 +check-manifest==0.48 +coverage==6.4.4 +flake8==5.0.4 +isort==5.10.1 +mock==4.0.3 +pymemcache==3.5.2 +pytest==7.1.2 +readme-renderer==37.0 diff --git a/setup.cfg b/setup.cfg index ca864d7..363fcea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,3 +15,13 @@ ignore = [coverage:run] branch = True omit = tests/* + +[flake8] +exclude = + .git, + .tox, + .venv, + .eggs, + migrations, + venv, + __pycache__ diff --git a/setup.py b/setup.py index a1844ec..d26c3e8 100644 --- a/setup.py +++ b/setup.py @@ -24,12 +24,16 @@ 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 3.2', + 'Framework :: Django :: 4.2', + 'Framework :: Django :: 5.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=find_packages(exclude=('tests',)), diff --git a/tests/test_backend.py b/tests/test_backend.py index 650162d..0a526ab 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -1,242 +1,229 @@ +from unittest import TestCase from unittest.mock import Mock, patch import django from django.core.cache import InvalidCacheBackendError -from nose.tools import eq_, raises from django_elastipymemcache.client import ConfigurationEndpointClient -@raises(InvalidCacheBackendError) -def test_multiple_servers(): - from django_elastipymemcache.backend import ElastiPymemcache - ElastiPymemcache('h1:0,h2:0', {}) - - -@raises(InvalidCacheBackendError) -def test_wrong_server_format(): - from django_elastipymemcache.backend import ElastiPymemcache - ElastiPymemcache('h', {}) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_split_servers(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - backend = ElastiPymemcache('h:0', {}) - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - backend._class = Mock() - assert backend._cache - get_cluster_info.assert_called() - backend._class.assert_called_once() - eq_(backend._class.call_args[0], (servers,)) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_node_info_cache(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - - backend = ElastiPymemcache('h:0', {}) - backend._class = Mock() - backend.set('key1', 'val') - backend.get('key1') - backend.set('key2', 'val') - backend.get('key2') - backend._class.assert_called_once() - eq_(backend._class.call_args[0], (servers,)) - eq_(backend._cache.get.call_count, 2) - eq_(backend._cache.set.call_count, 2) - - get_cluster_info.assert_called_once() - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_failed_to_connect_servers(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - backend = ElastiPymemcache('h:0', {}) - get_cluster_info.side_effect = OSError() - eq_(backend.client_servers, []) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_invalidate_cache(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - - backend = ElastiPymemcache('h:0', {}) - # backend._class = Mock() - assert backend._cache - backend._cache.get = Mock() - backend._cache.get.side_effect = Exception() - try: - backend.get('key1', 'val') - except Exception: - pass - # This should have removed the _cache instance - assert '_cache' not in backend.__dict__ - # Again - backend._cache.get = Mock() - backend._cache.get.side_effect = Exception() - try: - backend.get('key1', 'val') - except Exception: - pass - assert '_cache' not in backend.__dict__ - assert backend._cache - eq_(get_cluster_info.call_count, 3) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_add(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - - backend = ElastiPymemcache('h:0', {}) - ret = backend.add('key1', 'value1') - eq_(ret, False) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_delete(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - - backend = ElastiPymemcache('h:0', {}) - ret = backend.delete('key1') - if django.get_version() >= '3.1': - eq_(ret, False) - else: - eq_(ret, None) - - -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_get_many(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache - - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } - - backend = ElastiPymemcache('h:0', {}) - ret = backend.get_many(['key1']) - eq_(ret, {}) - - # When server does not found... - with patch('pymemcache.client.hash.HashClient._get_client') as p: - p.return_value = None - ret = backend.get_many(['key2']) - eq_(ret, {}) - - with patch('pymemcache.client.hash.HashClient._safely_run_func') as p2: - p2.return_value = { - ':1:key3': 1509111630.048594 +class ErrorTestCase(TestCase): + def test_multiple_servers(self): + with self.assertRaises(InvalidCacheBackendError): + from django_elastipymemcache.backend import ElastiPymemcache + ElastiPymemcache('h1:0,h2:0', {}) + + def test_wrong_server_format(self): + with self.assertRaises(InvalidCacheBackendError): + from django_elastipymemcache.backend import ElastiPymemcache + ElastiPymemcache('h', {}) + + +class BackendTestCase(TestCase): + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_split_servers(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + backend = ElastiPymemcache('h:0', {}) + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } + backend._class = Mock() + assert backend._cache + get_cluster_info.assert_called() + backend._class.assert_called_once() + assert backend._class.call_args[0] == (servers,) + + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_node_info_cache(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers } - ret = backend.get_many(['key3']) - eq_(ret, {'key3': 1509111630.048594}) + backend = ElastiPymemcache('h:0', {}) + backend._class = Mock() + backend.set('key1', 'val') + backend.get('key1') + backend.set('key2', 'val') + backend.get('key2') + backend._class.assert_called_once() + assert backend._class.call_args[0] == (servers,) + assert backend._cache.get.call_count == 2 + assert backend._cache.set.call_count == 2 + + get_cluster_info.assert_called_once() + + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_failed_to_connect_servers(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + backend = ElastiPymemcache('h:0', {}) + get_cluster_info.side_effect = OSError() + assert backend.client_servers == [] + + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_invalidate_cache(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } - # If False value is included, include it. - with patch('pymemcache.client.hash.HashClient.get_multi') as p: - p.return_value = { - ':1:key1': 1509111630.048594, - ':1:key2': False, - ':1:key3': 1509111630.058594, + backend = ElastiPymemcache('h:0', {}) + # backend._class = Mock() + assert backend._cache + backend._cache.get = Mock() + backend._cache.get.side_effect = Exception() + try: + backend.get('key1', 'val') + except Exception: + pass + # This should have removed the _cache instance + assert '_cache' not in backend.__dict__ + # Again + backend._cache.get = Mock() + backend._cache.get.side_effect = Exception() + try: + backend.get('key1', 'val') + except Exception: + pass + assert '_cache' not in backend.__dict__ + assert backend._cache + assert get_cluster_info.call_count == 3 + + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_add(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers } - ret = backend.get_many(['key1', 'key2', 'key3']) - eq_( - ret, - { - 'key1': 1509111630.048594, - 'key2': False, - 'key3': 1509111630.058594 - }, - ) - - # Even None is valid. Only key not found is not returned. - with patch('pymemcache.client.hash.HashClient.get_multi') as p: - p.return_value = { - ':1:key1': None, - ':1:key2': 1509111630.048594, + + backend = ElastiPymemcache('h:0', {}) + ret = backend.add('key1', 'value1') + assert ret is False + + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_delete(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers } - ret = backend.get_many(['key1', 'key2', 'key3']) - eq_( - ret, - { - 'key1': None, - 'key2': 1509111630.048594, - }, - ) + backend = ElastiPymemcache('h:0', {}) + ret = backend.delete('key1') + if django.get_version() >= '3.1': + assert ret is False + else: + assert ret is None -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_set_many(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_get_many(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } - backend = ElastiPymemcache('h:0', {}) - ret = backend.set_many({'key1': 'value1', 'key2': 'value2'}) - eq_(ret, ['key1', 'key2']) + backend = ElastiPymemcache('h:0', {}) + ret = backend.get_many(['key1']) + assert ret == {} + + # When server does not found... + with patch('pymemcache.client.hash.HashClient._get_client') as p: + p.return_value = None + ret = backend.get_many(['key2']) + assert ret == {} + + with patch('pymemcache.client.hash.HashClient._safely_run_func') as p2: + p2.return_value = { + ':1:key3': 1509111630.048594 + } + + ret = backend.get_many(['key3']) + assert ret == {'key3': 1509111630.048594} + + # If False value is included, include it. + with patch('pymemcache.client.hash.HashClient.get_multi') as p: + p.return_value = { + ':1:key1': 1509111630.048594, + ':1:key2': False, + ':1:key3': 1509111630.058594, + } + ret = backend.get_many(['key1', 'key2', 'key3']) + assert ret == { + 'key1': 1509111630.048594, + 'key2': False, + 'key3': 1509111630.058594 + } + + # Even None is valid. Only key not found is not returned. + with patch('pymemcache.client.hash.HashClient.get_multi') as p: + p.return_value = { + ':1:key1': None, + ':1:key2': 1509111630.048594, + } + ret = backend.get_many(['key1', 'key2', 'key3']) + assert ret == { + 'key1': None, + 'key2': 1509111630.048594, + } + @patch('pymemcache.client.base.Client.set_many') + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_set_many(self, get_cluster_info, set_many): + from django_elastipymemcache.backend import ElastiPymemcache -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_delete_many(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } + set_many.side_effect = [[':1:key1'], [':1:key2']] - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } + backend = ElastiPymemcache('h:0', {}) + ret = backend.set_many({'key1': 'value1', 'key2': 'value2'}) + assert ret == ['key1', 'key2'] - backend = ElastiPymemcache('h:0', {}) - ret = backend.delete_many(['key1', 'key2']) - eq_(ret, None) + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_delete_many(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_incr(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache + backend = ElastiPymemcache('h:0', {}) + ret = backend.delete_many(['key1', 'key2']) + assert ret is None - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_incr(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache - backend = ElastiPymemcache('h:0', {}) - ret = backend.incr('key1', 1) - eq_(ret, False) + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } + backend = ElastiPymemcache('h:0', {}) + ret = backend.incr('key1', 1) + assert ret is False -@patch.object(ConfigurationEndpointClient, 'get_cluster_info') -def test_client_decr(get_cluster_info): - from django_elastipymemcache.backend import ElastiPymemcache + @patch.object(ConfigurationEndpointClient, 'get_cluster_info') + def test_client_decr(self, get_cluster_info): + from django_elastipymemcache.backend import ElastiPymemcache - servers = [('h1', 0), ('h2', 0)] - get_cluster_info.return_value = { - 'nodes': servers - } + servers = ['h1:0', 'h2:0'] + get_cluster_info.return_value = { + 'nodes': servers + } - backend = ElastiPymemcache('h:0', {}) - ret = backend.decr('key1', 1) - eq_(ret, False) + backend = ElastiPymemcache('h:0', {}) + ret = backend.decr('key1', 1) + assert ret is False diff --git a/tests/test_client.py b/tests/test_client.py index 57a4f9c..714df5f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,11 +1,11 @@ import collections from unittest.mock import call, patch -from nose.tools import eq_, raises from pymemcache.exceptions import ( MemcacheUnknownCommandError, MemcacheUnknownError, ) +from pytest import raises from django_elastipymemcache.client import ConfigurationEndpointClient @@ -22,16 +22,16 @@ @patch('socket.socket') def test_get_cluster_info(socket, _): recv_bufs = collections.deque([ - b'VERSION 1.4.14\r\n', - ] + EXAMPLE_RESPONSE) + b'VERSION 1.4.14\r\n', + ] + EXAMPLE_RESPONSE) client = socket.return_value client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() cluster_info = ConfigurationEndpointClient(('h', 0)).get_cluster_info() - eq_(cluster_info['nodes'], [ - ('10.82.235.120', 11211), - ('10.80.249.27', 11211), - ]) + assert cluster_info['nodes'] == [ + '10.82.235.120:11211', + '10.80.249.27:11211', + ] client.sendall.assert_has_calls([ call(b'version\r\n'), call(b'config get cluster\r\n'), @@ -42,65 +42,66 @@ def test_get_cluster_info(socket, _): @patch('socket.socket') def test_get_cluster_info_before_1_4_13(socket, _): recv_bufs = collections.deque([ - b'VERSION 1.4.13\r\n', - ] + EXAMPLE_RESPONSE) + b'VERSION 1.4.13\r\n', + ] + EXAMPLE_RESPONSE) client = socket.return_value client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() cluster_info = ConfigurationEndpointClient(('h', 0)).get_cluster_info() - eq_(cluster_info['nodes'], [ - ('10.82.235.120', 11211), - ('10.80.249.27', 11211), - ]) + assert cluster_info['nodes'] == [ + '10.82.235.120:11211', + '10.80.249.27:11211', + ] client.sendall.assert_has_calls([ call(b'version\r\n'), call(b'get AmazonElastiCache:cluster\r\n'), ]) -@raises(MemcacheUnknownCommandError) @patch('socket.getaddrinfo', return_value=[range(5)]) @patch('socket.socket') def test_no_configuration_protocol_support_with_errors(socket, _): - recv_bufs = collections.deque([ - b'VERSION 1.4.13\r\n', - b'ERROR\r\n', - ]) - client = socket.return_value - client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() - ConfigurationEndpointClient(('h', 0)).get_cluster_info() + with raises(MemcacheUnknownCommandError): + recv_bufs = collections.deque([ + b'VERSION 1.4.13\r\n', + b'ERROR\r\n', + ]) + + client = socket.return_value + client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() + ConfigurationEndpointClient(('h', 0)).get_cluster_info() -@raises(MemcacheUnknownError) @patch('socket.getaddrinfo', return_value=[range(5)]) @patch('socket.socket') def test_cannot_parse_version(socket, _): - recv_bufs = collections.deque([ - b'VERSION 1.4.34\r\n', - b'CONFIG cluster 0 147\r\n', - b'fail\nhost|ip|11211 host|ip|11211\n\r\n', - b'END\r\n', - ]) + with raises(MemcacheUnknownError): + recv_bufs = collections.deque([ + b'VERSION 1.4.34\r\n', + b'CONFIG cluster 0 147\r\n', + b'fail\nhost|ip|11211 host|ip|11211\n\r\n', + b'END\r\n', + ]) - client = socket.return_value - client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() - ConfigurationEndpointClient(('h', 0)).get_cluster_info() + client = socket.return_value + client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() + ConfigurationEndpointClient(('h', 0)).get_cluster_info() -@raises(MemcacheUnknownError) @patch('socket.getaddrinfo', return_value=[range(5)]) @patch('socket.socket') def test_cannot_parse_nodes(socket, _): - recv_bufs = collections.deque([ - b'VERSION 1.4.34\r\n', - b'CONFIG cluster 0 147\r\n', - b'1\nfail\n\r\n', - b'END\r\n', - ]) + with raises(MemcacheUnknownError): + recv_bufs = collections.deque([ + b'VERSION 1.4.34\r\n', + b'CONFIG cluster 0 147\r\n', + b'1\nfail\n\r\n', + b'END\r\n', + ]) - client = socket.return_value - client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() - ConfigurationEndpointClient(('h', 0)).get_cluster_info() + client = socket.return_value + client.recv.side_effect = lambda *args, **kwargs: recv_bufs.popleft() + ConfigurationEndpointClient(('h', 0)).get_cluster_info() @patch('socket.getaddrinfo', return_value=[range(5)]) @@ -118,4 +119,4 @@ def test_ignore_erros(socket, _): ('h', 0), ignore_cluster_errors=True, ).get_cluster_info() - eq_(cluster_info['nodes'], [('h', 0)]) + assert cluster_info['nodes'] == [('h', 0)] diff --git a/tox.ini b/tox.ini index 758bce1..431d6ad 100644 --- a/tox.ini +++ b/tox.ini @@ -1,48 +1,65 @@ [tox] envlist = - py{36,37,38}-dj32, - py{36,37,38}-djdev, + py{39,310,311,312}-dj32, + py{39,310,311,312}-dj40, + py{39,310,311,312}-dj41, + py{39,310,311,312,313}-dj42, + py{312,313}-dj52, flake8, isort, - readme + readme, check-manifest -[travis:env] -DJANGO = - 3.2: dj32 - dev: djdev +[gh-actions] +python = + 3.9: py39 + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 [testenv] -passenv = TOXENV CI TRAVIS TRAVIS_* CODECOV_* +passenv = TOXENV, CI, TRAVIS, TRAVIS_*, CODECOV_* deps = - dj32: Django<3.3 + dj32: Django>=3.2,<4.0 + dj40: Django>=4.0,<4.1 + dj41: Django>=4.1,<4.2 + dj52: Django>=5.2,<5.3 + + django-pymemcache<2.0 djdev: https://github.com/django/django/archive/master.tar.gz -r{toxinidir}/requirements.txt - py37-dj32: codecov + py312-dj52: codecov setenv = PYTHONPATH = {toxinidir} commands = - coverage run --source=django_elastipymemcache -m nose --verbose - py37-dj32: coverage report - py37-dj32: coverage xml - py37-dj32: codecov + coverage run --source=django_elastipymemcache -m pytest --verbose + py312-dj52: coverage report + py312-dj52: coverage xml + py312-dj52: codecov [testenv:flake8] -basepython = python3.7 +skip_install = true +basepython = python3.12 commands = flake8 deps = flake8 [testenv:isort] -basepython = python3.7 +skip_install = true +basepython = python3.12 commands = isort --verbose --check-only --diff django_elastipymemcache tests setup.py deps = isort [testenv:readme] -basepython = python3.7 +skip_install = true +basepython = python3.12 commands = python setup.py check -r -s -deps = readme_renderer +deps = + readme_renderer + setuptools [testenv:check-manifest] -basepython = python3.7 +skip_install = true +basepython = python3.12 commands = check-manifest {toxinidir} deps = check-manifest