diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda69b39..3e8cd74c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: env: COLOR: 'yes' run: | - pytest tests + pytest tests --cov-report xml --cov-report html python -m coverage xml - name: Run functional tests run: bash examples/run_all.sh diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 00000000..ceba7e6a --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,30 @@ +[mypy] +files = aiocache, examples, tests +#check_untyped_defs = True +follow_imports_for_stubs = True +#disallow_any_decorated = True +disallow_any_generics = True +disallow_incomplete_defs = True +disallow_subclassing_any = True +#disallow_untyped_calls = True +disallow_untyped_decorators = True +#disallow_untyped_defs = True +implicit_reexport = False +no_implicit_optional = True +show_error_codes = True +strict_equality = True +warn_incomplete_stub = True +warn_redundant_casts = True +warn_unreachable = True +warn_unused_ignores = True +disallow_any_unimported = True +#warn_return_any = True + +[mypy-tests.*] +disallow_any_decorated = False +disallow_untyped_calls = False +disallow_untyped_defs = False + + +[mypy-msgpack.*] +ignore_missing_imports = True diff --git a/aiocache/__init__.py b/aiocache/__init__.py index 4af9751f..13a4fbd1 100644 --- a/aiocache/__init__.py +++ b/aiocache/__init__.py @@ -1,11 +1,13 @@ import logging +from typing import Dict, Type from ._version import __version__ from .backends.memory import SimpleMemoryCache +from .base import BaseCache logger = logging.getLogger(__name__) -AIOCACHE_CACHES = {SimpleMemoryCache.NAME: SimpleMemoryCache} +AIOCACHE_CACHES: Dict[str, Type[BaseCache]] = {SimpleMemoryCache.NAME: SimpleMemoryCache} try: import aioredis diff --git a/aiocache/backends/memory.py b/aiocache/backends/memory.py index 2e718145..e2ddf03a 100644 --- a/aiocache/backends/memory.py +++ b/aiocache/backends/memory.py @@ -1,4 +1,5 @@ import asyncio +from typing import Dict from aiocache.base import BaseCache from aiocache.serializers import NullSerializer @@ -9,8 +10,8 @@ class SimpleMemoryBackend: Wrapper around dict operations to use it as a cache backend """ - _cache = {} - _handlers = {} + _cache: Dict[str, object] = {} + _handlers: Dict[str, asyncio.TimerHandle] = {} def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/aiocache/backends/redis.py b/aiocache/backends/redis.py index 43ff9c76..1b08c0ce 100644 --- a/aiocache/backends/redis.py +++ b/aiocache/backends/redis.py @@ -46,8 +46,6 @@ class RedisBackend: " end" ) - pools = {} - def __init__( self, endpoint="127.0.0.1", diff --git a/aiocache/base.py b/aiocache/base.py index 95e2548e..d8481092 100644 --- a/aiocache/base.py +++ b/aiocache/base.py @@ -3,6 +3,7 @@ import logging import os import time +from typing import Callable, Set from aiocache import serializers @@ -14,7 +15,7 @@ class API: - CMDS = set() + CMDS: Set[Callable[..., object]] = set() @classmethod def register(cls, func): @@ -103,6 +104,8 @@ class BaseCache: the backend. It can be overriden in the specific calls. """ + NAME: str + def __init__( self, serializer=None, plugins=None, namespace=None, key_builder=None, timeout=5, ttl=None ): diff --git a/aiocache/factory.py b/aiocache/factory.py index 30b27cbb..416b0a21 100644 --- a/aiocache/factory.py +++ b/aiocache/factory.py @@ -2,6 +2,7 @@ import urllib import warnings from copy import deepcopy +from typing import Dict from aiocache import AIOCACHE_CACHES from aiocache.base import BaseCache @@ -126,7 +127,7 @@ def from_url(cls, url): class CacheHandler: - _config = { + _config: Dict[str, Dict[str, object]] = { "default": { "cache": "aiocache.SimpleMemoryCache", "serializer": {"class": "aiocache.serializers.StringSerializer"}, @@ -136,7 +137,7 @@ class CacheHandler: def __init__(self): self._caches = {} - def add(self, alias: str, config: dict) -> None: + def add(self, alias: str, config: Dict[str, object]) -> None: """ Add a cache to the current config. If the key already exists, it will overwrite it:: @@ -153,7 +154,7 @@ def add(self, alias: str, config: dict) -> None: """ self._config[alias] = config - def get(self, alias: str): + def get(self, alias: str) -> object: """ Retrieve cache identified by alias. Will return always the same instance diff --git a/aiocache/lock.py b/aiocache/lock.py index 9ae6afd3..222cc728 100644 --- a/aiocache/lock.py +++ b/aiocache/lock.py @@ -1,6 +1,6 @@ import asyncio import uuid -from typing import Any, Union +from typing import Any, Dict, Union from aiocache.base import BaseCache @@ -60,7 +60,7 @@ class RedLock: result of ``super_expensive_function``. """ - _EVENTS = {} + _EVENTS: Dict[str, asyncio.Event] = {} def __init__(self, client: BaseCache, key: str, lease: Union[int, float]): self.client = client @@ -149,7 +149,7 @@ async def _acquire(self): async def __aexit__(self, exc_type, exc_value, traceback): pass - async def cas(self, value: Any, **kwargs) -> bool: + async def cas(self, value: Any, **kwargs: Any) -> bool: """ Checks and sets the specified value for the locked key. If the value has changed since the lock was created, it will raise an :class:`aiocache.lock.OptimisticLockError` diff --git a/aiocache/py.typed b/aiocache/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/aiocache/serializers/serializers.py b/aiocache/serializers/serializers.py index 047758f9..3df48f8a 100644 --- a/aiocache/serializers/serializers.py +++ b/aiocache/serializers/serializers.py @@ -1,5 +1,6 @@ import logging import pickle # noqa: S403 +from typing import Any, Optional logger = logging.getLogger(__name__) @@ -7,7 +8,7 @@ import ujson as json except ImportError: logger.debug("ujson module not found, using json") - import json + import json # type: ignore[no-redef] try: import msgpack @@ -21,16 +22,18 @@ class BaseSerializer: - DEFAULT_ENCODING = "utf-8" + DEFAULT_ENCODING: Optional[str] = "utf-8" def __init__(self, *args, encoding=_NOT_SET, **kwargs): self.encoding = self.DEFAULT_ENCODING if encoding is _NOT_SET else encoding super().__init__(*args, **kwargs) - def dumps(self, value): + # TODO(PY38): Positional-only + def dumps(self, value: Any) -> str: raise NotImplementedError("dumps method must be implemented") - def loads(self, value): + # TODO(PY38): Positional-only + def loads(self, value: str) -> Any: raise NotImplementedError("loads method must be implemented") diff --git a/examples/marshmallow_serializer_class.py b/examples/marshmallow_serializer_class.py index 5c8cfe6b..42ec01f5 100644 --- a/examples/marshmallow_serializer_class.py +++ b/examples/marshmallow_serializer_class.py @@ -21,7 +21,7 @@ def __eq__(self, obj): return self.__dict__ == obj.__dict__ -class MarshmallowSerializer(Schema, BaseSerializer): +class MarshmallowSerializer(Schema, BaseSerializer): # type: ignore[misc] int_type = fields.Integer() str_type = fields.String() dict_type = fields.Dict() diff --git a/requirements-dev.txt b/requirements-dev.txt index 2ea49fe7..591677bf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,3 +11,4 @@ pytest==7.0.1 pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7 +types-ujson==4.2.1 diff --git a/setup.cfg b/setup.cfg index 53857ffe..e979d2fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ universal=1 max-line-length=100 [tool:pytest] -addopts = --cov=aiocache --cov-report xml --cov-report html --cov-report term +addopts = --cov=aiocache --cov-report term asyncio_mode = auto junit_suite_name = aiohttp_test_suite #filterwarnings= diff --git a/setup.py b/setup.py index 2f0d9407..889e5434 100644 --- a/setup.py +++ b/setup.py @@ -39,4 +39,5 @@ "memcached": ["aiomcache>=0.5.2"], "msgpack": ["msgpack>=0.5.5"], }, + include_package_data=True, ) diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index 37b5190e..1965cbbc 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -1,7 +1,6 @@ import pytest from aiocache import Cache, caches -from aiocache.backends.redis import RedisBackend def pytest_configure(): @@ -27,11 +26,6 @@ def reset_caches(): ) -@pytest.fixture(autouse=True) -def reset_redis_pools(): - RedisBackend.pools = {} - - @pytest.fixture def redis_cache(event_loop): cache = Cache(Cache.REDIS, namespace="test") diff --git a/tests/acceptance/test_base.py b/tests/acceptance/test_base.py index a0e65111..45a22c96 100644 --- a/tests/acceptance/test_base.py +++ b/tests/acceptance/test_base.py @@ -2,7 +2,9 @@ import pytest -from aiocache import MemcachedCache, RedisCache, SimpleMemoryCache +from aiocache.backends.memcached import MemcachedCache +from aiocache.backends.memory import SimpleMemoryCache +from aiocache.backends.redis import RedisCache from aiocache.base import _Conn diff --git a/tests/acceptance/test_factory.py b/tests/acceptance/test_factory.py index c6f829ca..3c5516b7 100644 --- a/tests/acceptance/test_factory.py +++ b/tests/acceptance/test_factory.py @@ -1,6 +1,9 @@ import pytest -from aiocache import Cache, MemcachedCache, RedisCache, SimpleMemoryCache +from aiocache import Cache +from aiocache.backends.memcached import MemcachedCache +from aiocache.backends.memory import SimpleMemoryCache +from aiocache.backends.redis import RedisCache class TestCache: diff --git a/tests/acceptance/test_serializers.py b/tests/acceptance/test_serializers.py index 91597b5f..c0669124 100644 --- a/tests/acceptance/test_serializers.py +++ b/tests/acceptance/test_serializers.py @@ -1,3 +1,4 @@ +import pickle import random import pytest @@ -6,11 +7,7 @@ try: import ujson as json except ImportError: - import json -try: - import cPickle as pickle -except ImportError: - import pickle + import json # type: ignore[no-redef] from aiocache.serializers import ( BaseSerializer, diff --git a/tests/ut/backends/test_memcached.py b/tests/ut/backends/test_memcached.py index d34564b7..e7d93fda 100644 --- a/tests/ut/backends/test_memcached.py +++ b/tests/ut/backends/test_memcached.py @@ -3,8 +3,7 @@ import aiomcache import pytest -from aiocache import MemcachedCache -from aiocache.backends.memcached import MemcachedBackend +from aiocache.backends.memcached import MemcachedBackend, MemcachedCache from aiocache.base import BaseCache from aiocache.serializers import JsonSerializer @@ -272,7 +271,7 @@ def test_parse_uri_path(self): @pytest.mark.parametrize( "namespace, expected", - ([None, "test" + pytest.KEY], ["", pytest.KEY], ["my_ns", "my_ns" + pytest.KEY]), + ([None, "test" + pytest.KEY], ["", pytest.KEY], ["my_ns", "my_ns" + pytest.KEY]), # type: ignore[attr-defined] # noqa: B950 ) def test_build_key_bytes(self, set_test_namespace, memcached_cache, namespace, expected): assert memcached_cache.build_key(pytest.KEY, namespace=namespace) == expected.encode() diff --git a/tests/ut/backends/test_memory.py b/tests/ut/backends/test_memory.py index f8556599..537ca72d 100644 --- a/tests/ut/backends/test_memory.py +++ b/tests/ut/backends/test_memory.py @@ -3,8 +3,7 @@ import pytest -from aiocache import SimpleMemoryCache -from aiocache.backends.memory import SimpleMemoryBackend +from aiocache.backends.memory import SimpleMemoryBackend, SimpleMemoryCache from aiocache.base import BaseCache from aiocache.serializers import NullSerializer diff --git a/tests/ut/backends/test_redis.py b/tests/ut/backends/test_redis.py index 2f0be891..e36fdd73 100644 --- a/tests/ut/backends/test_redis.py +++ b/tests/ut/backends/test_redis.py @@ -3,15 +3,14 @@ import aioredis import pytest -from aiocache import RedisCache -from aiocache.backends.redis import RedisBackend, conn +from aiocache.backends.redis import RedisBackend, RedisCache, conn from aiocache.base import BaseCache from aiocache.serializers import JsonSerializer pytest.skip("aioredis code is broken", allow_module_level=True) -@pytest.fixture +@pytest.fixture # type: ignore[unreachable] def redis_connection(): return create_autospec(aioredis.RedisConnection) diff --git a/tests/ut/conftest.py b/tests/ut/conftest.py index 4fc5f62f..8c12c810 100644 --- a/tests/ut/conftest.py +++ b/tests/ut/conftest.py @@ -11,7 +11,9 @@ import pytest -from aiocache import MemcachedCache, RedisCache, caches +from aiocache import caches +from aiocache.backends.memcached import MemcachedCache +from aiocache.backends.redis import RedisCache from aiocache.base import API, BaseCache from aiocache.plugins import BasePlugin from aiocache.serializers import BaseSerializer diff --git a/tests/ut/test_base.py b/tests/ut/test_base.py index aad128e1..78249134 100644 --- a/tests/ut/test_base.py +++ b/tests/ut/test_base.py @@ -227,7 +227,7 @@ def set_test_namespace(self, base_cache): @pytest.mark.parametrize( "namespace, expected", - ([None, "test" + pytest.KEY], ["", pytest.KEY], ["my_ns", "my_ns" + pytest.KEY]), + ([None, "test" + pytest.KEY], ["", pytest.KEY], ["my_ns", "my_ns" + pytest.KEY]), # type: ignore[attr-defined] # noqa: B950 ) def test_build_key(self, set_test_namespace, base_cache, namespace, expected): assert base_cache.build_key(pytest.KEY, namespace=namespace) == expected diff --git a/tests/ut/test_decorators.py b/tests/ut/test_decorators.py index e4c0cb1b..002581d5 100644 --- a/tests/ut/test_decorators.py +++ b/tests/ut/test_decorators.py @@ -6,7 +6,8 @@ import pytest -from aiocache import SimpleMemoryCache, cached, cached_stampede, multi_cached +from aiocache import cached, cached_stampede, multi_cached +from aiocache.backends.memory import SimpleMemoryCache from aiocache.base import BaseCache, SENTINEL from aiocache.decorators import _get_args_dict from aiocache.lock import RedLock diff --git a/tests/ut/test_factory.py b/tests/ut/test_factory.py index b35da4ea..6af458ea 100644 --- a/tests/ut/test_factory.py +++ b/tests/ut/test_factory.py @@ -2,12 +2,19 @@ import pytest -from aiocache import AIOCACHE_CACHES, Cache, MemcachedCache, RedisCache, SimpleMemoryCache, caches +from aiocache import AIOCACHE_CACHES, Cache, caches +from aiocache.backends.memcached import MemcachedCache +from aiocache.backends.memory import SimpleMemoryCache +from aiocache.backends.redis import RedisCache from aiocache.exceptions import InvalidCacheType from aiocache.factory import _class_from_string, _create_cache from aiocache.plugins import HitMissRatioPlugin, TimingPlugin from aiocache.serializers import JsonSerializer, PickleSerializer +assert Cache.REDIS is not None +assert Cache.MEMCACHED is not None +CACHE_NAMES = (Cache.MEMORY.NAME, Cache.REDIS.NAME, Cache.MEMCACHED.NAME) + def test_class_from_string(): assert _class_from_string("aiocache.RedisCache") == RedisCache @@ -39,9 +46,7 @@ def test_cache_types(self): assert Cache.REDIS == RedisCache assert Cache.MEMCACHED == MemcachedCache - @pytest.mark.parametrize( - "cache_type", [Cache.MEMORY.NAME, Cache.REDIS.NAME, Cache.MEMCACHED.NAME] - ) + @pytest.mark.parametrize("cache_type", CACHE_NAMES) def test_new(self, cache_type): kwargs = {"a": 1, "b": 2} cache_class = Cache.get_scheme_class(cache_type) @@ -61,7 +66,7 @@ def test_new_invalid_cache_raises(self): list(AIOCACHE_CACHES.keys()) ) - @pytest.mark.parametrize("scheme", [Cache.MEMORY.NAME, Cache.REDIS.NAME, Cache.MEMCACHED.NAME]) + @pytest.mark.parametrize("scheme", CACHE_NAMES) def test_get_scheme_class(self, scheme): assert Cache.get_scheme_class(scheme) == AIOCACHE_CACHES[scheme] @@ -69,7 +74,7 @@ def test_get_scheme_class_invalid(self): with pytest.raises(InvalidCacheType): Cache.get_scheme_class("http") - @pytest.mark.parametrize("scheme", [Cache.MEMORY.NAME, Cache.REDIS.NAME, Cache.MEMCACHED.NAME]) + @pytest.mark.parametrize("scheme", CACHE_NAMES) def test_from_url_returns_cache_from_scheme(self, scheme): assert isinstance(Cache.from_url("{}://".format(scheme)), Cache.get_scheme_class(scheme)) diff --git a/tests/ut/test_serializers.py b/tests/ut/test_serializers.py index f0ca7f80..dd30f256 100644 --- a/tests/ut/test_serializers.py +++ b/tests/ut/test_serializers.py @@ -76,7 +76,7 @@ def serializer(self): yield PickleSerializer(protocol=4) def test_init(self, serializer): - assert isinstance(serializer, BaseSerializer) + assert isinstance(serializer, PickleSerializer) assert serializer.DEFAULT_ENCODING is None assert serializer.encoding is None assert serializer.protocol == 4