Merge pull request #3956 from ostcar/use_typing_protocol

Use Protocol instead of ABC in cache_provicer
This commit is contained in:
Oskar Hahn 2018-10-29 12:33:01 +01:00 committed by GitHub
commit 6ed5650262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 75 deletions

View File

@ -17,7 +17,7 @@ matrix:
script: script:
- flake8 openslides tests - flake8 openslides tests
- isort --check-only --diff --recursive openslides tests - isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/ - python -m mypy openslides/ tests/
- python -W ignore -m pytest --cov --cov-fail-under=70 - python -W ignore -m pytest --cov --cov-fail-under=70
- language: python - language: python
@ -34,7 +34,7 @@ matrix:
script: script:
- flake8 openslides tests - flake8 openslides tests
- isort --check-only --diff --recursive openslides tests - isort --check-only --diff --recursive openslides tests
- python -m mypy openslides/ - python -m mypy openslides/ tests/
- python -W ignore -m pytest --cov --cov-fail-under=70 - python -W ignore -m pytest --cov --cov-fail-under=70
- language: node_js - language: node_js

View File

@ -18,8 +18,8 @@ from asgiref.sync import async_to_sync, sync_to_async
from django.conf import settings from django.conf import settings
from .cache_providers import ( from .cache_providers import (
BaseCacheProvider,
Cachable, Cachable,
ElementCacheProvider,
MemmoryCacheProvider, MemmoryCacheProvider,
RedisCacheProvider, RedisCacheProvider,
get_all_cachables, get_all_cachables,
@ -62,7 +62,7 @@ class ElementCache:
self, self,
redis: str, redis: str,
use_restricted_data_cache: bool = False, use_restricted_data_cache: bool = False,
cache_provider_class: Type[BaseCacheProvider] = RedisCacheProvider, cache_provider_class: Type[ElementCacheProvider] = RedisCacheProvider,
cachable_provider: Callable[[], List[Cachable]] = get_all_cachables, cachable_provider: Callable[[], List[Cachable]] = get_all_cachables,
start_time: int = None) -> None: start_time: int = None) -> None:
""" """

View File

@ -11,6 +11,7 @@ from typing import (
) )
from django.apps import apps from django.apps import apps
from typing_extensions import Protocol
from .utils import split_element_id, str_dict_to_bytes from .utils import split_element_id, str_dict_to_bytes
@ -27,83 +28,52 @@ else:
no_redis_dependency = False no_redis_dependency = False
class BaseCacheProvider: class ElementCacheProvider(Protocol):
""" """
Base class for cache provider. Base class for cache provider.
See RedisCacheProvider as reverence implementation. See RedisCacheProvider as reverence implementation.
""" """
full_data_cache_key = 'full_data'
restricted_user_cache_key = 'restricted_data:{user_id}'
change_id_cache_key = 'change_id'
prefix = 'element_cache_'
def __init__(self, *args: Any) -> None: def __init__(self, *args: Any) -> None: ...
pass
def get_full_data_cache_key(self) -> str: async def clear_cache(self) -> None: ...
return "".join((self.prefix, self.full_data_cache_key))
def get_restricted_data_cache_key(self, user_id: int) -> str: async def reset_full_cache(self, data: Dict[str, str]) -> None: ...
return "".join((self.prefix, self.restricted_user_cache_key.format(user_id=user_id)))
def get_change_id_cache_key(self) -> str: async def data_exists(self, user_id: Optional[int] = None) -> bool: ...
return "".join((self.prefix, self.change_id_cache_key))
async def clear_cache(self) -> None: async def add_elements(self, elements: List[str]) -> None: ...
raise NotImplementedError("CacheProvider has to implement the method clear_cache().")
async def reset_full_cache(self, data: Dict[str, str]) -> None: async def del_elements(self, elements: List[str], user_id: Optional[int] = None) -> None: ...
raise NotImplementedError("CacheProvider has to implement the method reset_full_cache().")
async def data_exists(self, user_id: Optional[int] = None) -> bool: async def add_changed_elements(self, default_change_id: int, element_ids: Iterable[str]) -> int: ...
raise NotImplementedError("CacheProvider has to implement the method exists_full_data().")
async def add_elements(self, elements: List[str]) -> None: async def get_all_data(self, user_id: Optional[int] = None) -> Dict[bytes, bytes]: ...
raise NotImplementedError("CacheProvider has to implement the method add_elements().")
async def del_elements(self, elements: List[str], user_id: Optional[int] = None) -> None:
raise NotImplementedError("CacheProvider has to implement the method del_elements().")
async def add_changed_elements(self, default_change_id: int, element_ids: Iterable[str]) -> int:
raise NotImplementedError("CacheProvider has to implement the method add_changed_elements().")
async def get_all_data(self, user_id: Optional[int] = None) -> Dict[bytes, bytes]:
raise NotImplementedError("CacheProvider has to implement the method get_all_data().")
async def get_data_since( async def get_data_since(
self, self,
change_id: int, change_id: int,
user_id: Optional[int] = None, user_id: Optional[int] = None,
max_change_id: int = -1) -> Tuple[Dict[str, List[bytes]], List[str]]: max_change_id: int = -1) -> Tuple[Dict[str, List[bytes]], List[str]]: ...
raise NotImplementedError("CacheProvider has to implement the method get_data_since().")
async def get_element(self, element_id: str) -> Optional[bytes]: async def get_element(self, element_id: str) -> Optional[bytes]: ...
raise NotImplementedError("CacheProvider has to implement the method get_element().")
async def del_restricted_data(self, user_id: int) -> None: async def del_restricted_data(self, user_id: int) -> None: ...
raise NotImplementedError("CacheProvider has to implement the method del_restricted_data().")
async def set_lock(self, lock_name: str) -> bool: async def set_lock(self, lock_name: str) -> bool: ...
raise NotImplementedError("CacheProvider has to implement the method set_lock().")
async def get_lock(self, lock_name: str) -> bool: async def get_lock(self, lock_name: str) -> bool: ...
raise NotImplementedError("CacheProvider has to implement the method get_lock().")
async def del_lock(self, lock_name: str) -> None: async def del_lock(self, lock_name: str) -> None: ...
raise NotImplementedError("CacheProvider has to implement the method del_lock().")
async def get_change_id_user(self, user_id: int) -> Optional[int]: async def get_change_id_user(self, user_id: int) -> Optional[int]: ...
raise NotImplementedError("CacheProvider has to implement the method get_change_id_user().")
async def update_restricted_data(self, user_id: int, data: Dict[str, str]) -> None: async def update_restricted_data(self, user_id: int, data: Dict[str, str]) -> None: ...
raise NotImplementedError("CacheProvider has to implement the method update_restricted_data().")
async def get_current_change_id(self) -> List[Tuple[str, int]]: async def get_current_change_id(self) -> List[Tuple[str, int]]: ...
raise NotImplementedError("CacheProvider has to implement the method get_current_change_id().")
async def get_lowest_change_id(self) -> Optional[int]: async def get_lowest_change_id(self) -> Optional[int]: ...
raise NotImplementedError("CacheProvider has to implement the method get_lowest_change_id().")
class RedisConnectionContextManager: class RedisConnectionContextManager:
@ -123,11 +93,15 @@ class RedisConnectionContextManager:
self.conn.close() self.conn.close()
class RedisCacheProvider(BaseCacheProvider): class RedisCacheProvider:
""" """
Cache provider that loads and saves the data to redis. Cache provider that loads and saves the data to redis.
""" """
redis_pool: Optional[aioredis.RedisConnection] = None redis_pool: Optional[aioredis.RedisConnection] = None
full_data_cache_key: str = 'full_data'
restricted_user_cache_key: str = 'restricted_data:{user_id}'
change_id_cache_key: str = 'change_id'
prefix: str = 'element_cache_'
def __init__(self, redis: str) -> None: def __init__(self, redis: str) -> None:
self.redis_address = redis self.redis_address = redis
@ -138,6 +112,15 @@ class RedisCacheProvider(BaseCacheProvider):
""" """
return RedisConnectionContextManager(self.redis_address) return RedisConnectionContextManager(self.redis_address)
def get_full_data_cache_key(self) -> str:
return "".join((self.prefix, self.full_data_cache_key))
def get_restricted_data_cache_key(self, user_id: int) -> str:
return "".join((self.prefix, self.restricted_user_cache_key.format(user_id=user_id)))
def get_change_id_cache_key(self) -> str:
return "".join((self.prefix, self.change_id_cache_key))
async def clear_cache(self) -> None: async def clear_cache(self) -> None:
""" """
Deleted all cache entries created with this element cache. Deleted all cache entries created with this element cache.
@ -371,7 +354,7 @@ class RedisCacheProvider(BaseCacheProvider):
'_config:lowest_change_id') '_config:lowest_change_id')
class MemmoryCacheProvider(BaseCacheProvider): class MemmoryCacheProvider:
""" """
CacheProvider for the ElementCache that uses only the memory. CacheProvider for the ElementCache that uses only the memory.
@ -516,7 +499,7 @@ class MemmoryCacheProvider(BaseCacheProvider):
return None return None
class Cachable: class Cachable(Protocol):
""" """
A Cachable is an object that returns elements that can be cached. A Cachable is an object that returns elements that can be cached.
@ -527,13 +510,11 @@ class Cachable:
""" """
Returns the string representing the name of the cachable. Returns the string representing the name of the cachable.
""" """
raise NotImplementedError("Cachable has to implement the method get_collection_string().")
def get_elements(self) -> List[Dict[str, Any]]: def get_elements(self) -> List[Dict[str, Any]]:
""" """
Returns all elements of the cachable. Returns all elements of the cachable.
""" """
raise NotImplementedError("Cachable has to implement the method get_collection_string().")
def restrict_elements( def restrict_elements(
self, self,
@ -544,10 +525,7 @@ class Cachable:
elements can be an empty list, a list with some elements of the cachable or with all elements can be an empty list, a list with some elements of the cachable or with all
elements of the cachable. elements of the cachable.
The default implementation returns the full_data.
""" """
return elements
def get_all_cachables() -> List[Cachable]: def get_all_cachables() -> List[Cachable]:
@ -558,7 +536,7 @@ def get_all_cachables() -> List[Cachable]:
for app in apps.get_app_configs(): for app in apps.get_app_configs():
try: try:
# Get the method get_startup_elements() from an app. # Get the method get_startup_elements() from an app.
# This method has to return an iterable of Collection objects. # This method has to return an iterable of Cachable objects.
get_startup_elements = app.get_startup_elements get_startup_elements = app.get_startup_elements
except AttributeError: except AttributeError:
# Skip apps that do not implement get_startup_elements. # Skip apps that do not implement get_startup_elements.

View File

@ -16,7 +16,6 @@ from django.db.models import Model
from mypy_extensions import TypedDict from mypy_extensions import TypedDict
from .cache import element_cache from .cache import element_cache
from .cache_providers import Cachable
if TYPE_CHECKING: if TYPE_CHECKING:
@ -211,7 +210,7 @@ class CollectionElement:
return self.deleted return self.deleted
class Collection(Cachable): class Collection:
""" """
Represents all elements of one collection. Represents all elements of one collection.
""" """

View File

@ -10,3 +10,4 @@ mypy_extensions>=0.4,<0.5
PyPDF2>=1.26,<1.27 PyPDF2>=1.26,<1.27
roman>=2.0,<3.1 roman>=2.0,<3.1
setuptools>=29.0,<41.0 setuptools>=29.0,<41.0
typing_extensions>=3.6.6,<3.7

View File

@ -1,4 +1,5 @@
from textwrap import dedent from textwrap import dedent
from typing import Optional
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@ -183,7 +184,7 @@ class Command(BaseCommand):
@transaction.atomic @transaction.atomic
def create_staff_users(self, options): def create_staff_users(self, options):
if options['users'] is None and not options['only']: if options['users'] is None and not options['only']:
staff_users = DEFAULT_NUMBER staff_users: Optional[int] = DEFAULT_NUMBER
elif options['users'] is None: elif options['users'] is None:
staff_users = None staff_users = None
else: else:
@ -214,7 +215,7 @@ class Command(BaseCommand):
@transaction.atomic @transaction.atomic
def create_default_users(self, options): def create_default_users(self, options):
if options['users'] is None and not options['only']: if options['users'] is None and not options['only']:
default_users = DEFAULT_NUMBER default_users: Optional[int] = DEFAULT_NUMBER
elif options['users'] is None: elif options['users'] is None:
default_users = None default_users = None
else: else:

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, List from typing import Any, Dict, List, Optional
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.db import DEFAULT_DB_ALIAS, connections from django.db import DEFAULT_DB_ALIAS, connections
@ -8,11 +8,10 @@ from openslides.core.config import config
from openslides.users.models import User from openslides.users.models import User
from openslides.utils.autoupdate import inform_data_collection_element_list from openslides.utils.autoupdate import inform_data_collection_element_list
from openslides.utils.cache import element_cache, get_element_id from openslides.utils.cache import element_cache, get_element_id
from openslides.utils.cache_providers import Cachable
from openslides.utils.collection import CollectionElement from openslides.utils.collection import CollectionElement
class TConfig(Cachable): class TConfig:
""" """
Cachable, that fills the cache with the default values of the config variables. Cachable, that fills the cache with the default values of the config variables.
""" """
@ -28,8 +27,14 @@ class TConfig(Cachable):
config.key_to_id[item.name] = id+1 config.key_to_id[item.name] = id+1
return elements return elements
def restrict_elements(
self,
user: Optional['CollectionElement'],
elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return elements
class TUser(Cachable):
class TUser:
""" """
Cachable, that fills the cache with the default values of the config variables. Cachable, that fills the cache with the default values of the config variables.
""" """
@ -45,6 +50,12 @@ class TUser(Cachable):
'last_email_send': None, 'comment': '', 'is_active': True, 'default_password': 'admin', 'last_email_send': None, 'comment': '', 'is_active': True, 'default_password': 'admin',
'session_auth_hash': '362d4f2de1463293cb3aaba7727c967c35de43ee'}] 'session_auth_hash': '362d4f2de1463293cb3aaba7727c967c35de43ee'}]
def restrict_elements(
self,
user: Optional['CollectionElement'],
elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return elements
async def set_config(key, value): async def set_config(key, value):
""" """

View File

@ -57,6 +57,6 @@ class TestPersonalNoteAccessPermissions(TestCase):
[CollectionElement.from_values( [CollectionElement.from_values(
'users/personal_note', 'users/personal_note',
1, 1,
full_data={'user_id': 1})], full_data={'user_id': 1}).get_full_data()],
None) None)
self.assertEqual(rd, []) self.assertEqual(rd, [])

View File

@ -11,7 +11,7 @@ class UserCreateUpdateSerializerTest(TestCase):
Tests, that the validator raises a ValidationError, if not data is given. Tests, that the validator raises a ValidationError, if not data is given.
""" """
serializer = UserFullSerializer() serializer = UserFullSerializer()
data = {} data: object = {}
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
serializer.validate(data) serializer.validate(data)

View File

@ -23,7 +23,7 @@ def restrict_elements(
return out return out
class Collection1(Cachable): class Collection1:
def get_collection_string(self) -> str: def get_collection_string(self) -> str:
return 'app/collection1' return 'app/collection1'
@ -36,7 +36,7 @@ class Collection1(Cachable):
return restrict_elements(user, elements) return restrict_elements(user, elements)
class Collection2(Cachable): class Collection2:
def get_collection_string(self) -> str: def get_collection_string(self) -> str:
return 'app/collection2' return 'app/collection2'