From 52f579c36c62a5be4018582044bac0940c94f2f4 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 28 Mar 2022 12:13:15 -0400 Subject: [PATCH 01/20] Initial cache engine wand redis --- diffsync/__init__.py | 129 ++++--------------- diffsync/store/__init__.py | 247 +++++++++++++++++++++++++++++++++++++ diffsync/store/redis.py | 206 +++++++++++++++++++++++++++++++ 3 files changed, 480 insertions(+), 102 deletions(-) create mode 100644 diffsync/store/__init__.py create mode 100644 diffsync/store/redis.py diff --git a/diffsync/__init__.py b/diffsync/__init__.py index b82b015b..c7c41a68 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -25,6 +25,7 @@ from .enum import DiffSyncModelFlags, DiffSyncFlags, DiffSyncStatus from .exceptions import ObjectAlreadyExists, ObjectStoreWrongType, ObjectNotFound from .helpers import DiffSyncDiffer, DiffSyncSyncer +from .store import BaseStore, LocalStore class DiffSyncModel(BaseModel): @@ -375,13 +376,19 @@ class DiffSync: `self._data[modelname][unique_id] == model_instance` """ - def __init__(self, name=None): + def __init__(self, name=None, internal_storage_engine=LocalStore): """Generic initialization function. Subclasses should be careful to call super().__init__() if they override this method. """ - self._data = defaultdict(dict) + self._log = structlog.get_logger().new(diffsync=self) + + if isinstance(internal_storage_engine, BaseStore): + self.store = internal_storage_engine + self.store.diffsync = self + else: + self.store = internal_storage_engine(diffsync=self) # If the type is not defined, use the name of the class as the default value if self.type is None: @@ -419,8 +426,8 @@ def __repr__(self): return f"<{str(self)}>" def __len__(self): - """Total number of elements stored in self._data.""" - return sum(len(entries) for entries in self._data.values()) + """Total number of elements stored.""" + return self.store.count() def load(self): """Load all desired data from whatever backend data source into this instance.""" @@ -567,6 +574,7 @@ def diff_to( def get( self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[Text, Mapping] ) -> DiffSyncModel: + """Get one object from the data store based on its unique id. Args: @@ -577,29 +585,7 @@ def get( ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - if isinstance(obj, str): - modelname = obj - if not hasattr(self, obj): - object_class = None - else: - object_class = getattr(self, obj) - else: - object_class = obj - modelname = obj.get_type() - - if isinstance(identifier, str): - uid = identifier - elif object_class: - uid = object_class.create_unique_id(**identifier) - else: - raise ValueError( - f"Invalid args: ({obj}, {identifier}): " - f"either {obj} should be a class/instance or {identifier} should be a str" - ) - - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {self.name}") - return self._data[modelname][uid] + return self.store.get(obj=obj, identifier=identifier) def get_all(self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]]) -> List[DiffSyncModel]: """Get all objects of a given type. @@ -610,12 +596,7 @@ def get_all(self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]]) -> List[ Returns: List[DiffSyncModel]: List of Object """ - if isinstance(obj, str): - modelname = obj - else: - modelname = obj.get_type() - - return list(self._data[modelname].values()) + return self.store.get_all(obj=obj) def get_by_uids( self, uids: List[Text], obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]] @@ -629,17 +610,7 @@ def get_by_uids( Raises: ObjectNotFound: if any of the requested UIDs are not found in the store """ - if isinstance(obj, str): - modelname = obj - else: - modelname = obj.get_type() - - results = [] - for uid in uids: - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {self.name}") - results.append(self._data[modelname][uid]) - return results + return self.store.get_by_uids(uids=uids, obj=obj) def add(self, obj: DiffSyncModel): """Add a DiffSyncModel object to the store. @@ -650,20 +621,18 @@ def add(self, obj: DiffSyncModel): Raises: ObjectAlreadyExists: if a different object with the same uid is already present. """ - modelname = obj.get_type() - uid = obj.get_unique_id() + return self.store.add(obj=obj) - existing_obj = self._data[modelname].get(uid) - if existing_obj: - if existing_obj is not obj: - raise ObjectAlreadyExists(f"Object {uid} already present", obj) - # Return so we don't have to change anything on the existing object and underlying data - return + def update(self, obj: DiffSyncModel): + """Update a DiffSyncModel object to the store. - if not obj.diffsync: - obj.diffsync = self + Args: + obj (DiffSyncModel): Object to store - self._data[modelname][uid] = obj + Raises: + ObjectAlreadyExists: if a different object with the same uid is already present. + """ + return self.store.update(obj=obj) def remove(self, obj: DiffSyncModel, remove_children: bool = False): """Remove a DiffSyncModel object from the store. @@ -675,26 +644,7 @@ def remove(self, obj: DiffSyncModel, remove_children: bool = False): Raises: ObjectNotFound: if the object is not present """ - modelname = obj.get_type() - uid = obj.get_unique_id() - - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {self.name}") - - if obj.diffsync is self: - obj.diffsync = None - - del self._data[modelname][uid] - - if remove_children: - for child_type, child_fieldname in obj.get_children_mapping().items(): - for child_id in getattr(obj, child_fieldname): - try: - child_obj = self.get(child_type, child_id) - self.remove(child_obj, remove_children=remove_children) - except ObjectNotFound: - # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise - self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + return self.store.remove(obj=obj, remove_children=remove_children) def get_or_instantiate( self, model: Type[DiffSyncModel], ids: Dict, attrs: Dict = None @@ -709,18 +659,7 @@ def get_or_instantiate( Returns: Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. """ - created = False - try: - obj = self.get(model, ids) - except ObjectNotFound: - if not attrs: - attrs = {} - obj = model(**ids, **attrs) - # Add the object to diffsync adapter - self.add(obj) - created = True - - return obj, created + return self.store.get_or_instantiate(modle=model, ids=ids, attrs=attrs) def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Dict) -> Tuple[DiffSyncModel, bool]: """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. @@ -733,21 +672,7 @@ def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Di Returns: Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. """ - created = False - try: - obj = self.get(model, ids) - except ObjectNotFound: - obj = model(**ids, **attrs) - # Add the object to diffsync adapter - self.add(obj) - created = True - - # Update existing obj with attrs - for attr, value in attrs.items(): - if getattr(obj, attr) != value: - setattr(obj, attr, value) - - return obj, created + return self.store.update_or_instantiate(modle=model, ids=ids, attrs=attrs) # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py new file mode 100644 index 00000000..c76c8496 --- /dev/null +++ b/diffsync/store/__init__.py @@ -0,0 +1,247 @@ +from collections import defaultdict +from typing import Callable, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Text, Tuple, Type, Union + +from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists + + +class BaseStore: + + def __init__(self, diffsync=None, name=None, *args, **kwargs) -> None: + self.diffsync = diffsync + self.name = name if name else self.__class__.__name__ + + def __str__(self): + return self.name + + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + raise NotImplementedError + + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + raise NotImplementedError + + def get_by_uids( + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: + raise NotImplementedError + + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + raise NotImplementedError + + def add(self, obj: "DiffSyncModel"): + raise NotImplementedError + + def update(self, obj: "DiffSyncModel"): + raise NotImplementedError + + def count(self, modelname): + raise NotImplementedError + + def get_or_instantiate( + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None + ) -> Tuple["DiffSyncModel", bool]: + """Attempt to get the object with provided identifiers or instantiate it with provided identifiers and attrs. + + Args: + model (DiffSyncModel): The DiffSyncModel to get or create. + ids (Mapping): Identifiers for the DiffSyncModel to get or create with. + attrs (Mapping, optional): Attributes when creating an object if it doesn't exist. Defaults to None. + + Returns: + Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. + """ + created = False + try: + obj = self.get(model, ids) + except ObjectNotFound: + if not attrs: + attrs = {} + obj = model(**ids, **attrs) + # Add the object to diffsync adapter + self.add(obj) + created = True + + return obj, created + + def update_or_instantiate( + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict + ) -> Tuple["DiffSyncModel", bool]: + """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. + + Args: + model (DiffSyncModel): The DiffSyncModel to get or create. + ids (Dict): Identifiers for the DiffSyncModel to get or create with. + attrs (Dict): Attributes when creating/updating an object if it doesn't exist. Pass in empty dict, if no specific attrs. + + Returns: + Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. + """ + created = False + try: + obj = self.get(model, ids) + except ObjectNotFound: + obj = model(**ids, **attrs) + # Add the object to diffsync adapter + self.add(obj) + created = True + + # Update existing obj with attrs + for attr, value in attrs.items(): + if getattr(obj, attr) != value: + setattr(obj, attr, value) + + return obj, created + + +class LocalStore(BaseStore): + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self._data = defaultdict(dict) + + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + """Get one object from the data store based on its unique id. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values + + Raises: + ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) + ObjectNotFound: if the requested object is not present + """ + if isinstance(obj, str): + modelname = obj + if not hasattr(self, obj): + object_class = None + else: + object_class = getattr(self, obj) + else: + object_class = obj + modelname = obj.get_type() + + if isinstance(identifier, str): + uid = identifier + elif object_class: + uid = object_class.create_unique_id(**identifier) + else: + raise ValueError( + f"Invalid args: ({obj}, {identifier}): " + f"either {obj} should be a class/instance or {identifier} should be a str" + ) + + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + return self._data[modelname][uid] + + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + """Get all objects of a given type. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Returns: + List[DiffSyncModel]: List of Object + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + return list(self._data[modelname].values()) + + def get_by_uids( + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: + """Get multiple objects from the store by their unique IDs/Keys and type. + + Args: + uids: List of unique id / key identifying object in the database. + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Raises: + ObjectNotFound: if any of the requested UIDs are not found in the store + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + results = [] + for uid in uids: + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + results.append(self._data[modelname][uid]) + return results + + def add(self, obj: "DiffSyncModel"): + """Add a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to store + + Raises: + ObjectAlreadyExists: if a different object with the same uid is already present. + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + existing_obj = self._data[modelname].get(uid) + if existing_obj: + if existing_obj is not obj: + raise ObjectAlreadyExists(f"Object {uid} already present", obj) + # Return so we don't have to change anything on the existing object and underlying data + return + + if not obj.diffsync: + obj.diffsync = self.diffsync + + self._data[modelname][uid] = obj + + def update(self, obj: "DiffSyncModel"): + modelname = obj.get_type() + uid = obj.get_unique_id() + + existing_obj = self._data[modelname].get(uid) + if existing_obj is obj: + return + + self._data[modelname][uid] = obj + + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + """Remove a DiffSyncModel object from the store. + + Args: + obj (DiffSyncModel): object to remove + remove_children (bool): If True, also recursively remove any children of this object + + Raises: + ObjectNotFound: if the object is not present + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + + if obj.diffsync: + obj.diffsync = None + + del self._data[modelname][uid] + + if remove_children: + for child_type, child_fieldname in obj.get_children_mapping().items(): + for child_id in getattr(obj, child_fieldname): + try: + child_obj = self.get(child_type, child_id) + self.remove(child_obj, remove_children=remove_children) + except ObjectNotFound: + pass + # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise + # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + + def count(self, modelname=None): + if not modelname: + return sum(len(entries) for entries in self._data.values()) + else: + return len(self._data[modelname]) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py new file mode 100644 index 00000000..7edb1aac --- /dev/null +++ b/diffsync/store/redis.py @@ -0,0 +1,206 @@ +import copy +import uuid +from pickle import loads, dumps +from typing import Callable, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Text, Tuple, Type, Union + +from redis import Redis + +from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists +from diffsync.store import BaseStore + + +class RedisStore(BaseStore): + + def __init__(self, name=None, store_id=None, host="localhost", port=6379, db=0, *args, **kwargs): + super().__init__(*args, **kwargs) + + # TODO Check connection is working + self._store = Redis(host=host, port=port, db=db) + + if store_id: + self._store_id = store_id + else: + self._store_id = str(uuid.uuid4())[:8] + + self._store_label = f"diffsync:{self._store_id}" + + def __str__(self): + return f"{self.name}({self._store_id})" + + def _get_key_for_object(self, modelname, uid): + return f"{self._store_label}:{modelname}:{uid}" + + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + """Get one object from the data store based on its unique id. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values + + Raises: + ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) + ObjectNotFound: if the requested object is not present + """ + if isinstance(obj, str): + modelname = obj + if not hasattr(self, obj): + object_class = None + else: + object_class = getattr(self, obj) + else: + object_class = obj + modelname = obj.get_type() + + if isinstance(identifier, str): + uid = identifier + elif object_class: + uid = object_class.create_unique_id(**identifier) + else: + raise ValueError( + f"Invalid args: ({obj}, {identifier}): " + f"either {obj} should be a class/instance or {identifier} should be a str" + ) + + try: + obj = loads(self._store.get(self._get_key_for_object(modelname, uid))) + obj.diffsync = self.diffsync + except TypeError: + raise ObjectNotFound(f"{modelname} {uid} not present in Cache") + + return obj + + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + """Get all objects of a given type. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Returns: + List[DiffSyncModel]: List of Object + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + results = [] + for key in self._store.scan_iter(f"{self._store_label}:{modelname}:*"): + try: + obj = loads(self._store.get(key)) + obj.diffsync = self.diffsync + results.append(obj) + except TypeError: + raise ObjectNotFound(f"{key} not present in Cache") + + return results + + def get_by_uids( + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: + """Get multiple objects from the store by their unique IDs/Keys and type. + + Args: + uids: List of unique id / key identifying object in the database. + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Raises: + ObjectNotFound: if any of the requested UIDs are not found in the store + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + results = [] + for uid in uids: + + try: + obj = loads(self._store.get(self._get_key_for_object(modelname, uid))) + obj.diffsync = self.diffsync + results.append(obj) + except TypeError: + raise ObjectNotFound(f"{modelname} {uid} not present in Cache") + + return results + + def add(self, obj: "DiffSyncModel"): + """Add a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to store + + Raises: + ObjectAlreadyExists: if a different object with the same uid is already present. + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + # Get existing Object + object_key = self._get_key_for_object(modelname, uid) + + # existing_obj_binary = self._store.get(object_key) + # if existing_obj_binary: + # existing_obj = loads(existing_obj_binary) + # existing_obj_dict = existing_obj.dict() + + # if existing_obj_dict != obj.dict(): + # raise ObjectAlreadyExists(f"Object {uid} already present", obj) + + # # Return so we don't have to change anything on the existing object and underlying data + # return + + # Remove the diffsync object before sending to Redis + obj_copy = copy.copy(obj) + obj_copy.diffsync = False + self._store.set(object_key, dumps(obj_copy)) + + def update(self, obj: "DiffSyncModel"): + modelname = obj.get_type() + uid = obj.get_unique_id() + + object_key = self._get_key_for_object(modelname, uid) + obj_copy = copy.copy(obj) + obj_copy.diffsync = False + self._store.set(object_key, dumps(obj_copy)) + + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + """Remove a DiffSyncModel object from the store. + + Args: + obj (DiffSyncModel): object to remove + remove_children (bool): If True, also recursively remove any children of this object + + Raises: + ObjectNotFound: if the object is not present + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + object_key = self._get_key_for_object(modelname, uid) + + if not self._store.exists(object_key): + raise ObjectNotFound(f"{modelname} {uid} not present in Cache") + + if obj.diffsync: + obj.diffsync = None + + self._store.delete(object_key) + + if remove_children: + for child_type, child_fieldname in obj.get_children_mapping().items(): + for child_id in getattr(obj, child_fieldname): + try: + child_obj = self.get(child_type, child_id) + self.remove(child_obj, remove_children=remove_children) + except ObjectNotFound: + pass + # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise + # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + + def count(self, modelname=None): + + search_pattern = f"{self._store_label}:*" + if modelname: + search_pattern = f"{self._store_label}:{modelname}:*" + + return sum( [ 1 for _ in self._store.scan_iter(search_pattern)]) \ No newline at end of file From a64c611fa633cdf539308aa199f66bac9a1dcee6 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 07:46:14 +0200 Subject: [PATCH 02/20] Fix and extend testing and code fixes --- diffsync/__init__.py | 43 +- diffsync/store/__init__.py | 249 +++------ diffsync/store/local.py | 181 +++++++ diffsync/store/redis.py | 67 ++- poetry.lock | 990 ++++++++++++++++++++-------------- pyproject.toml | 4 +- tests/unit/test_diffsync.py | 15 +- tests/unit/test_redisstore.py | 43 ++ 8 files changed, 976 insertions(+), 616 deletions(-) create mode 100644 diffsync/store/local.py create mode 100644 tests/unit/test_redisstore.py diff --git a/diffsync/__init__.py b/diffsync/__init__.py index c7c41a68..dee2204f 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. """ -from collections import defaultdict from inspect import isclass -from typing import Callable, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Text, Tuple, Type, Union +from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Text, Tuple, Type, Union from pydantic import BaseModel, PrivateAttr import structlog # type: ignore @@ -25,7 +24,8 @@ from .enum import DiffSyncModelFlags, DiffSyncFlags, DiffSyncStatus from .exceptions import ObjectAlreadyExists, ObjectStoreWrongType, ObjectNotFound from .helpers import DiffSyncDiffer, DiffSyncSyncer -from .store import BaseStore, LocalStore +from .store import BaseStore +from .store.local import LocalStore class DiffSyncModel(BaseModel): @@ -370,20 +370,12 @@ class DiffSync: top_level: ClassVar[List[str]] = [] """List of top-level modelnames to begin from when diffing or synchronizing.""" - _data: MutableMapping[str, MutableMapping[str, DiffSyncModel]] - """Defaultdict storing model instances. - - `self._data[modelname][unique_id] == model_instance` - """ - def __init__(self, name=None, internal_storage_engine=LocalStore): """Generic initialization function. Subclasses should be careful to call super().__init__() if they override this method. """ - self._log = structlog.get_logger().new(diffsync=self) - if isinstance(internal_storage_engine, BaseStore): self.store = internal_storage_engine self.store.diffsync = self @@ -436,10 +428,10 @@ def load(self): def dict(self, exclude_defaults: bool = True, **kwargs) -> Mapping: """Represent the DiffSync contents as a dict, as if it were a Pydantic model.""" data: Dict[str, Dict[str, Dict]] = {} - for modelname in self._data: + for modelname in self.store.get_all_model_names(): data[modelname] = {} - for unique_id, model in self._data[modelname].items(): - data[modelname][unique_id] = model.dict(exclude_defaults=exclude_defaults, **kwargs) + for obj in self.store.get_all(modelname): + data[obj.get_type()][obj.get_unique_id()] = obj.dict(exclude_defaults=exclude_defaults, **kwargs) return data def str(self, indent: int = 0) -> str: @@ -571,6 +563,14 @@ def diff_to( # Object Storage Management # ------------------------------------------------------------------------------ + def get_all_model_names(self): + """Get all model names. + + Returns: + List[str]: List of model names + """ + return self.store.get_all_model_names() + def get( self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[Text, Mapping] ) -> DiffSyncModel: @@ -659,7 +659,7 @@ def get_or_instantiate( Returns: Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. """ - return self.store.get_or_instantiate(modle=model, ids=ids, attrs=attrs) + return self.store.get_or_instantiate(model=model, ids=ids, attrs=attrs) def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Dict) -> Tuple[DiffSyncModel, bool]: """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. @@ -672,7 +672,18 @@ def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Di Returns: Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. """ - return self.store.update_or_instantiate(modle=model, ids=ids, attrs=attrs) + return self.store.update_or_instantiate(model=model, ids=ids, attrs=attrs) + + def count(self, modelname=None): + """Count how many objects of one model type exist in the backend store. + + Args: + modelname (str): The model name to check the number of elements. If not provided, default to all. + + Returns: + Int: Number of elements of the model type + """ + return self.store.count(modelname=modelname) # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index c76c8496..e510c35a 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -1,44 +1,110 @@ -from collections import defaultdict -from typing import Callable, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Text, Tuple, Type, Union +"""BaseStore module.""" +from typing import Dict, List, Mapping, Text, Tuple, Type, Union, TYPE_CHECKING +import structlog # type: ignore -from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists +from diffsync.exceptions import ObjectNotFound + +if TYPE_CHECKING: + from diffsync import DiffSyncModel class BaseStore: + """Reference store to be implemented in different backends.""" - def __init__(self, diffsync=None, name=None, *args, **kwargs) -> None: + def __init__(self, diffsync=None, name=None, *args, **kwargs) -> None: # pylint: disable=unused-argument + """Init method for BaseStore.""" self.diffsync = diffsync self.name = name if name else self.__class__.__name__ + self._log = structlog.get_logger().new(diffsync=self) def __str__(self): + """Render store name.""" return self.name + def get_all_model_names(self): + """Get all the model names stored. + + Return: + List[str]: List of all the model names. + """ + raise NotImplementedError + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + """Get one object from the data store based on its unique id. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values + + Raises: + ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) + ObjectNotFound: if the requested object is not present + """ raise NotImplementedError - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # + """Get all objects of a given type. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Returns: + List[DiffSyncModel]: List of Object + """ raise NotImplementedError def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] - ) -> List["DiffSyncModel"]: + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # + ) -> List["DiffSyncModel"]: # + """Get multiple objects from the store by their unique IDs/Keys and type. + + Args: + uids: List of unique id / key identifying object in the database. + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Raises: + ObjectNotFound: if any of the requested UIDs are not found in the store + """ raise NotImplementedError - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # + """Remove a DiffSyncModel object from the store. + + Args: + obj (DiffSyncModel): object to remove + remove_children (bool): If True, also recursively remove any children of this object + + Raises: + ObjectNotFound: if the object is not present + """ raise NotImplementedError - def add(self, obj: "DiffSyncModel"): + def add(self, obj: "DiffSyncModel"): # + """Add a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to store + + Raises: + ObjectAlreadyExists: if a different object with the same uid is already present. + """ raise NotImplementedError - def update(self, obj: "DiffSyncModel"): + def update(self, obj: "DiffSyncModel"): # + """Update a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to update + """ raise NotImplementedError def count(self, modelname): + """Returns the number of elements of an specific model name.""" raise NotImplementedError def get_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None - ) -> Tuple["DiffSyncModel", bool]: + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None # + ) -> Tuple["DiffSyncModel", bool]: # """Attempt to get the object with provided identifiers or instantiate it with provided identifiers and attrs. Args: @@ -63,8 +129,8 @@ def get_or_instantiate( return obj, created def update_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict - ) -> Tuple["DiffSyncModel", bool]: + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict # + ) -> Tuple["DiffSyncModel", bool]: # """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. Args: @@ -90,158 +156,3 @@ def update_or_instantiate( setattr(obj, attr, value) return obj, created - - -class LocalStore(BaseStore): - - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - self._data = defaultdict(dict) - - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): - """Get one object from the data store based on its unique id. - - Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve - identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values - - Raises: - ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) - ObjectNotFound: if the requested object is not present - """ - if isinstance(obj, str): - modelname = obj - if not hasattr(self, obj): - object_class = None - else: - object_class = getattr(self, obj) - else: - object_class = obj - modelname = obj.get_type() - - if isinstance(identifier, str): - uid = identifier - elif object_class: - uid = object_class.create_unique_id(**identifier) - else: - raise ValueError( - f"Invalid args: ({obj}, {identifier}): " - f"either {obj} should be a class/instance or {identifier} should be a str" - ) - - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") - return self._data[modelname][uid] - - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: - """Get all objects of a given type. - - Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve - - Returns: - List[DiffSyncModel]: List of Object - """ - if isinstance(obj, str): - modelname = obj - else: - modelname = obj.get_type() - - return list(self._data[modelname].values()) - - def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] - ) -> List["DiffSyncModel"]: - """Get multiple objects from the store by their unique IDs/Keys and type. - - Args: - uids: List of unique id / key identifying object in the database. - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve - - Raises: - ObjectNotFound: if any of the requested UIDs are not found in the store - """ - if isinstance(obj, str): - modelname = obj - else: - modelname = obj.get_type() - - results = [] - for uid in uids: - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") - results.append(self._data[modelname][uid]) - return results - - def add(self, obj: "DiffSyncModel"): - """Add a DiffSyncModel object to the store. - - Args: - obj (DiffSyncModel): Object to store - - Raises: - ObjectAlreadyExists: if a different object with the same uid is already present. - """ - modelname = obj.get_type() - uid = obj.get_unique_id() - - existing_obj = self._data[modelname].get(uid) - if existing_obj: - if existing_obj is not obj: - raise ObjectAlreadyExists(f"Object {uid} already present", obj) - # Return so we don't have to change anything on the existing object and underlying data - return - - if not obj.diffsync: - obj.diffsync = self.diffsync - - self._data[modelname][uid] = obj - - def update(self, obj: "DiffSyncModel"): - modelname = obj.get_type() - uid = obj.get_unique_id() - - existing_obj = self._data[modelname].get(uid) - if existing_obj is obj: - return - - self._data[modelname][uid] = obj - - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): - """Remove a DiffSyncModel object from the store. - - Args: - obj (DiffSyncModel): object to remove - remove_children (bool): If True, also recursively remove any children of this object - - Raises: - ObjectNotFound: if the object is not present - """ - modelname = obj.get_type() - uid = obj.get_unique_id() - - if uid not in self._data[modelname]: - raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") - - if obj.diffsync: - obj.diffsync = None - - del self._data[modelname][uid] - - if remove_children: - for child_type, child_fieldname in obj.get_children_mapping().items(): - for child_id in getattr(obj, child_fieldname): - try: - child_obj = self.get(child_type, child_id) - self.remove(child_obj, remove_children=remove_children) - except ObjectNotFound: - pass - # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise - # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - - def count(self, modelname=None): - if not modelname: - return sum(len(entries) for entries in self._data.values()) - else: - return len(self._data[modelname]) diff --git a/diffsync/store/local.py b/diffsync/store/local.py new file mode 100644 index 00000000..8c98a36a --- /dev/null +++ b/diffsync/store/local.py @@ -0,0 +1,181 @@ +"""LocalStore module.""" + +from collections import defaultdict +from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING + +from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists +from diffsync.store import BaseStore + + +if TYPE_CHECKING: + from diffsync import DiffSyncModel + + +class LocalStore(BaseStore): + """LocalStore class.""" + + def __init__(self, *args, **kwargs) -> None: + """Init method for LocalStore.""" + super().__init__(*args, **kwargs) + + self._data = defaultdict(dict) + + def get_all_model_names(self): + """Get all the model names stored. + + Return: + List[str]: List of all the model names. + """ + return self._data.keys() + + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): # + """Get one object from the data store based on its unique id. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values + + Raises: + ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) + ObjectNotFound: if the requested object is not present + """ + if isinstance(obj, str): + modelname = obj + if not hasattr(self, obj): + object_class = None + else: + object_class = getattr(self, obj) + else: + object_class = obj + modelname = obj.get_type() + + if isinstance(identifier, str): + uid = identifier + elif object_class: + uid = object_class.create_unique_id(**identifier) + else: + raise ValueError( + f"Invalid args: ({obj}, {identifier}): " + f"either {obj} should be a class/instance or {identifier} should be a str" + ) + + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + return self._data[modelname][uid] + + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # + """Get all objects of a given type. + + Args: + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Returns: + List[DiffSyncModel]: List of Object + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + return list(self._data[modelname].values()) + + def get_by_uids( + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # + ) -> List["DiffSyncModel"]: # + """Get multiple objects from the store by their unique IDs/Keys and type. + + Args: + uids: List of unique id / key identifying object in the database. + obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + + Raises: + ObjectNotFound: if any of the requested UIDs are not found in the store + """ + if isinstance(obj, str): + modelname = obj + else: + modelname = obj.get_type() + + results = [] + for uid in uids: + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + results.append(self._data[modelname][uid]) + return results + + def add(self, obj: "DiffSyncModel"): # + """Add a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to store + + Raises: + ObjectAlreadyExists: if a different object with the same uid is already present. + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + existing_obj = self._data[modelname].get(uid) + if existing_obj: + if existing_obj is not obj: + raise ObjectAlreadyExists(f"Object {uid} already present", obj) + # Return so we don't have to change anything on the existing object and underlying data + return + + if not obj.diffsync: + obj.diffsync = self.diffsync + + self._data[modelname][uid] = obj + + def update(self, obj: "DiffSyncModel"): # + """Update a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to update + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + existing_obj = self._data[modelname].get(uid) + if existing_obj is obj: + return + + self._data[modelname][uid] = obj + + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # + """Remove a DiffSyncModel object from the store. + + Args: + obj (DiffSyncModel): object to remove + remove_children (bool): If True, also recursively remove any children of this object + + Raises: + ObjectNotFound: if the object is not present + """ + modelname = obj.get_type() + uid = obj.get_unique_id() + + if uid not in self._data[modelname]: + raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") + + if obj.diffsync: + obj.diffsync = None + + del self._data[modelname][uid] + + if remove_children: + for child_type, child_fieldname in obj.get_children_mapping().items(): + for child_id in getattr(obj, child_fieldname): + try: + child_obj = self.get(child_type, child_id) + self.remove(child_obj, remove_children=remove_children) + except ObjectNotFound: + # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise + self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + + def count(self, modelname=None): + """Returns the number of elements of an specific model name.""" + if not modelname: + return sum(len(entries) for entries in self._data.values()) + + return len(self._data[modelname]) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 7edb1aac..e3aa0981 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -1,36 +1,52 @@ +"""RedisStore module.""" import copy import uuid from pickle import loads, dumps -from typing import Callable, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Text, Tuple, Type, Union +from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING from redis import Redis -from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists +from diffsync.exceptions import ObjectNotFound from diffsync.store import BaseStore +if TYPE_CHECKING: + from diffsync import DiffSyncModel + +REDIS_DIFFSYNC_ROOT_LABEL = "diffsync" + class RedisStore(BaseStore): + """RedisStore class.""" - def __init__(self, name=None, store_id=None, host="localhost", port=6379, db=0, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, store_id=None, host="localhost", port=6379, url=None, db=0, *args, **kwargs): + """Init method for RedisStore.""" - # TODO Check connection is working - self._store = Redis(host=host, port=port, db=db) + super().__init__(*args, **kwargs) - if store_id: - self._store_id = store_id + if url: + self._store = Redis.from_url(url, db=db) else: - self._store_id = str(uuid.uuid4())[:8] + self._store = Redis(host=host, port=port, db=db) - self._store_label = f"diffsync:{self._store_id}" + if not self._store.ping(): + # TODO: find the proper exception + raise Exception + + self._store_id = store_id if store_id else str(uuid.uuid4())[:8] + + self._store_label = f"{REDIS_DIFFSYNC_ROOT_LABEL}:{self._store_id}" def __str__(self): return f"{self.name}({self._store_id})" + def get_all_model_names(self): + # TODO: implement + raise NotImplementedError + def _get_key_for_object(self, modelname, uid): return f"{self._store_label}:{modelname}:{uid}" - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): # """Get one object from the data store based on its unique id. Args: @@ -66,10 +82,10 @@ def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifi obj.diffsync = self.diffsync except TypeError: raise ObjectNotFound(f"{modelname} {uid} not present in Cache") - + return obj - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # """Get all objects of a given type. Args: @@ -95,8 +111,8 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L return results def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] - ) -> List["DiffSyncModel"]: + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # + ) -> List["DiffSyncModel"]: # """Get multiple objects from the store by their unique IDs/Keys and type. Args: @@ -120,10 +136,10 @@ def get_by_uids( results.append(obj) except TypeError: raise ObjectNotFound(f"{modelname} {uid} not present in Cache") - + return results - def add(self, obj: "DiffSyncModel"): + def add(self, obj: "DiffSyncModel"): # """Add a DiffSyncModel object to the store. Args: @@ -154,7 +170,12 @@ def add(self, obj: "DiffSyncModel"): obj_copy.diffsync = False self._store.set(object_key, dumps(obj_copy)) - def update(self, obj: "DiffSyncModel"): + def update(self, obj: "DiffSyncModel"): # + """Update a DiffSyncModel object to the store. + + Args: + obj (DiffSyncModel): Object to update + """ modelname = obj.get_type() uid = obj.get_unique_id() @@ -163,7 +184,7 @@ def update(self, obj: "DiffSyncModel"): obj_copy.diffsync = False self._store.set(object_key, dumps(obj_copy)) - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # """Remove a DiffSyncModel object from the store. Args: @@ -198,9 +219,9 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") def count(self, modelname=None): - + """Returns the number of elements of an specific model name.""" search_pattern = f"{self._store_label}:*" if modelname: - search_pattern = f"{self._store_label}:{modelname}:*" - - return sum( [ 1 for _ in self._store.scan_iter(search_pattern)]) \ No newline at end of file + search_pattern = f"{self._store_label}:{modelname.lower()}:*" + + return sum([1 for _ in self._store.scan_iter(search_pattern)]) diff --git a/poetry.lock b/poetry.lock index a45745fd..ebd9ecac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,17 +8,28 @@ python-versions = "*" [[package]] name = "astroid" -version = "2.4.2" +version = "2.11.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = ">=1.11,<2" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" [package.dependencies] -lazy-object-proxy = ">=1.4.0,<1.5.0" -six = ">=1.12,<2.0" -typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} -wrapt = ">=1.11,<2.0" +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} [[package]] name = "atomicwrites" @@ -30,17 +41,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "babel" @@ -55,7 +66,7 @@ pytz = ">=2015.7" [[package]] name = "bandit" -version = "1.6.3" +version = "1.7.1" description = "Security oriented static analyser for python code." category = "dev" optional = false @@ -65,12 +76,11 @@ python-versions = ">=3.5" colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" -six = ">=1.10.0" stevedore = ">=1.20.0" [[package]] name = "black" -version = "21.10b0" +version = "21.12b0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -82,9 +92,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0,<1" platformdirs = ">=2" -regex = ">=2020.1.8" tomli = ">=0.2.6,<2.0.0" -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, @@ -99,27 +108,34 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2021.5.30" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "7.1.2" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -131,7 +147,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.3" +version = "5.5" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -151,6 +167,31 @@ category = "main" optional = false python-versions = ">=3.6, <3.7" +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "dill" +version = "0.3.4" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "docutils" version = "0.16" @@ -161,51 +202,52 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "gitdb" -version = "4.0.5" +version = "4.0.9" description = "Git Object Database" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" [package.dependencies] -smmap = ">=3.0.1,<4" +smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.11" +version = "3.1.20" description = "Python Git Library" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.6" [package.dependencies] gitdb = ">=4.0.1,<5" +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" -version = "1.2.0" +version = "1.3.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "main" optional = false @@ -213,18 +255,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "3.1.1" +version = "4.8.3" description = "Read metadata from Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -236,7 +280,7 @@ python-versions = "*" [[package]] name = "invoke" -version = "1.4.1" +version = "1.7.0" description = "Pythonic task execution" category = "dev" optional = false @@ -244,20 +288,21 @@ python-versions = "*" [[package]] name = "isort" -version = "5.6.4" +version = "5.10.1" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] [[package]] name = "jinja2" -version = "3.0.1" +version = "3.0.3" description = "A very fast and expressive template engine." category = "main" optional = false @@ -271,15 +316,15 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "lazy-object-proxy" -version = "1.4.3" +version = "1.7.1" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "m2r2" -version = "0.2.7" +version = "0.2.8" description = "Markdown and reStructuredText in a single file." category = "main" optional = false @@ -307,8 +352,8 @@ python-versions = "*" [[package]] name = "mistune" -version = "0.8.4" -description = "The fastest markdown parser in pure Python" +version = "2.0.2" +description = "A sane Markdown parser with useful plugins and renderers" category = "main" optional = false python-versions = "*" @@ -339,14 +384,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.7" +version = "21.3" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" @@ -358,7 +403,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pbr" -version = "5.5.1" +version = "5.8.1" description = "Python Build Reasonableness" category = "dev" optional = false @@ -378,29 +423,41 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psutil" +version = "5.9.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] [[package]] name = "py" -version = "1.9.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -408,19 +465,19 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" -version = "1.7.4" +version = "1.9.0" description = "Data validation and settings management using python 3.6 type hinting" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [package.dependencies] dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +typing-extensions = ">=3.7.4.3" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] -typing_extensions = ["typing-extensions (>=3.7.2)"] [[package]] name = "pydocstyle" @@ -435,7 +492,7 @@ snowballstemmer = "*" [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -443,7 +500,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.9.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -451,64 +508,89 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.6.0" +version = "2.13.5" description = "python code static checker" category = "dev" optional = false -python-versions = ">=3.5.*" +python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.4.0,<=2.5" +astroid = ">=2.11.2,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" -toml = ">=0.7.1" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +testutil = ["gitpython (>3)"] [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.7" description = "Python parsing module" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "6.1.2" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" +attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" [package.extras] -checkqa_mypy = ["mypy (==0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "2.10.1" +version = "2.12.1" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -coverage = ">=4.4" +coverage = ">=5.2.1" pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-redislite" +version = "0.1.0" +description = "Pytest plugin for testing code using Redis" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = "*" +redislite = "*" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +build = ["bump2version"] +tests = ["redis", "types-redis", "pytest-mypy"] [[package]] name = "pytest-structlog" @@ -524,7 +606,7 @@ structlog = "*" [[package]] name = "pytz" -version = "2021.1" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -532,65 +614,80 @@ python-versions = "*" [[package]] name = "pyyaml" -version = "5.3.1" +version = "5.4.1" description = "YAML parser and emitter for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "redis" +version = "4.2.2" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""} +packaging = ">=20.4" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] [[package]] -name = "regex" -version = "2020.11.13" -description = "Alternative regular expression module, to replace re." +name = "redislite" +version = "6.0.674960" +description = "Redis built into a python package" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6.0" + +[package.dependencies] +psutil = "*" +redis = "*" [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] - -[[package]] -name = "six" -version = "1.15.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "smmap" -version = "3.0.4" +version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "snowballstemmer" -version = "2.0.0" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "main" optional = false python-versions = "*" [[package]] name = "sphinx" -version = "4.0.2" +version = "4.5.0" description = "Python documentation generator" category = "main" optional = false @@ -602,6 +699,7 @@ babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -609,14 +707,14 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -707,7 +805,7 @@ test = ["pytest"] [[package]] name = "stevedore" -version = "3.3.0" +version = "3.5.0" description = "Manage dynamic plugins for Python applications" category = "dev" optional = false @@ -719,20 +817,19 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "structlog" -version = "20.1.0" +version = "20.2.0" description = "Structured Logging for Python" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -six = "*" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -azure-pipelines = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "pytest-azurepipelines", "python-rapidjson", "pytest-asyncio"] -dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "sphinx", "twisted", "pre-commit", "python-rapidjson", "pytest-asyncio"] -docs = ["sphinx", "twisted"] -tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "python-rapidjson", "pytest-asyncio"] +dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson", "furo", "sphinx", "sphinx-toolbox", "twisted", "pre-commit"] +docs = ["furo", "sphinx", "sphinx-toolbox", "twisted"] +tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson"] [[package]] name = "toml" @@ -744,7 +841,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.2" +version = "1.2.3" description = "A lil' TOML parser" category = "dev" optional = false @@ -760,48 +857,40 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "wrapt" -version = "1.12.1" +version = "1.14.0" description = "Module for decorators, wrappers and monkey patching." -category = "dev" +category = "main" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "yamllint" -version = "1.25.0" +version = "1.26.3" description = "A linter for YAML files." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.5" [package.dependencies] pathspec = ">=0.5.3" @@ -809,15 +898,15 @@ pyyaml = "*" [[package]] name = "zipp" -version = "3.4.0" +version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] docs = ["sphinx", "m2r2", "toml", "sphinx-rtd-theme", "pydantic", "structlog", "colorama", "dataclasses"] @@ -825,7 +914,7 @@ docs = ["sphinx", "m2r2", "toml", "sphinx-rtd-theme", "pydantic", "structlog", " [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "01b806daea7ea0f931916666391b6b83cf7558f75ccce9fc140dbe9a2c15073f" +content-hash = "0ebe9222aabd3c0370093003f583c62711110c09cb0b3709c5c1c4e5f13295e2" [metadata.files] alabaster = [ @@ -833,180 +922,209 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] astroid = [ - {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, - {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, + {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"}, + {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bandit = [ - {file = "bandit-1.6.3-py2.py3-none-any.whl", hash = "sha256:2ff3fe35fe3212c0be5fc9c4899bd0108e2b5239c5ff62fb174639e4660fe958"}, - {file = "bandit-1.6.3.tar.gz", hash = "sha256:d02dfe250f4aa2d166c127ad81d192579e2bfcdb8501717c0e2005e35a6bcf60"}, + {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, + {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, ] black = [ - {file = "black-21.10b0-py3-none-any.whl", hash = "sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b"}, - {file = "black-21.10b0.tar.gz", hash = "sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33"}, + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, - {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, - {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, - {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, - {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, - {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, - {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, - {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, - {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, - {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, - {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, - {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, - {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, - {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, - {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, - {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, - {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, - {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, - {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, - {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, - {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, - {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, - {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, - {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, - {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, - {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, - {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, - {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, - {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, - {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, - {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, - {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, - {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, - {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] dataclasses = [ {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"}, + {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"}, +] docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] gitdb = [ - {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, - {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.11-py3-none-any.whl", hash = "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b"}, - {file = "GitPython-3.1.11.tar.gz", hash = "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"}, + {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, + {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, + {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, + {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.1.1-py3-none-any.whl", hash = "sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013"}, - {file = "importlib_metadata-3.1.1.tar.gz", hash = "sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"}, + {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, + {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] invoke = [ - {file = "invoke-1.4.1-py2-none-any.whl", hash = "sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134"}, - {file = "invoke-1.4.1-py3-none-any.whl", hash = "sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132"}, - {file = "invoke-1.4.1.tar.gz", hash = "sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d"}, + {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"}, + {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"}, ] isort = [ - {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, - {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] jinja2 = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] m2r2 = [ - {file = "m2r2-0.2.7-py3-none-any.whl", hash = "sha256:a1b87b00f4d5163a3616253fca4c045f8351818cd803ba2e24af8897442a0eae"}, - {file = "m2r2-0.2.7.tar.gz", hash = "sha256:fbf72ade9f648d41658f97c5267687431612451f36b65761a138fbc276294df5"}, + {file = "m2r2-0.2.8-py3-none-any.whl", hash = "sha256:613934a5d02999574c0256407e6a0e71f8c436f2d2757d6e43d52d2faf8dff1c"}, + {file = "m2r2-0.2.8.tar.gz", hash = "sha256:ca39e1db74991818d667c7367e4fc2de13ecefd2a04d69d83b0ffa76d20d7e29"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -1015,27 +1133,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -1045,12 +1150,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1060,8 +1159,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] mistune = [ - {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, - {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, + {file = "mistune-2.0.2-py2.py3-none-any.whl", hash = "sha256:6bab6c6abd711c4604206c7d8cad5cd48b28f072b4bb75797d74146ba393a049"}, + {file = "mistune-2.0.2.tar.gz", hash = "sha256:6fc88c3cb49dba8b16687b41725e661cf85784c12e8974a29b9d336dd596c3a1"}, ] mypy = [ {file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"}, @@ -1084,170 +1183,201 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.7-py2.py3-none-any.whl", hash = "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"}, - {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pbr = [ - {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, - {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, + {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"}, + {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"}, ] platformdirs = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +psutil = [ + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"}, + {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"}, + {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"}, + {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, + {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, + {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, + {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, + {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, + {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, + {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, + {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"}, + {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"}, + {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"}, + {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"}, + {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"}, + {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"}, + {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"}, + {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, + {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, ] py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pydantic = [ - {file = "pydantic-1.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3c60039e84552442defbcb5d56711ef0e057028ca7bfc559374917408a88d84e"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6e7e314acb170e143c6f3912f93f2ec80a96aa2009ee681356b7ce20d57e5c62"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ef77cd17b73b5ba46788d040c0e820e49a2d80cfcd66fda3ba8be31094fd146"}, - {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:115d8aa6f257a1d469c66b6bfc7aaf04cd87c25095f24542065c68ebcb42fe63"}, - {file = "pydantic-1.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:66757d4e1eab69a3cfd3114480cc1d72b6dd847c4d30e676ae838c6740fdd146"}, - {file = "pydantic-1.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c92863263e4bd89e4f9cf1ab70d918170c51bd96305fe7b00853d80660acb26"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3b8154babf30a5e0fa3aa91f188356763749d9b30f7f211fafb247d4256d7877"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:80cc46378505f7ff202879dcffe4bfbf776c15675028f6e08d1d10bdfbb168ac"}, - {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:dda60d7878a5af2d8560c55c7c47a8908344aa78d32ec1c02d742ede09c534df"}, - {file = "pydantic-1.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4c1979d5cc3e14b35f0825caddea5a243dd6085e2a7539c006bc46997ef7a61a"}, - {file = "pydantic-1.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8857576600c32aa488f18d30833aa833b54a48e3bab3adb6de97e463af71f8f8"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f86d4da363badb39426a0ff494bf1d8510cd2f7274f460eee37bdbf2fd495ec"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3ea1256a9e782149381e8200119f3e2edea7cd6b123f1c79ab4bbefe4d9ba2c9"}, - {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:e28455b42a0465a7bf2cde5eab530389226ce7dc779de28d17b8377245982b1e"}, - {file = "pydantic-1.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:47c5b1d44934375a3311891cabd450c150a31cf5c22e84aa172967bf186718be"}, - {file = "pydantic-1.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00250e5123dd0b123ff72be0e1b69140e0b0b9e404d15be3846b77c6f1b1e387"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d24aa3f7f791a023888976b600f2f389d3713e4f23b7a4c88217d3fce61cdffc"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2c44a9afd4c4c850885436a4209376857989aaf0853c7b118bb2e628d4b78c4e"}, - {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:e87edd753da0ca1d44e308a1b1034859ffeab1f4a4492276bff9e1c3230db4fe"}, - {file = "pydantic-1.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:a3026ee105b5360855e500b4abf1a1d0b034d88e75a2d0d66a4c35e60858e15b"}, - {file = "pydantic-1.7.4-py3-none-any.whl", hash = "sha256:a82385c6d5a77e3387e94612e3e34b77e13c39ff1295c26e3ba664e7b98073e2"}, - {file = "pydantic-1.7.4.tar.gz", hash = "sha256:0a1abcbd525fbb52da58c813d54c2ec706c31a91afdb75411a73dd1dec036595"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, + {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, + {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, + {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, + {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, + {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, + {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, + {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, + {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, + {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, ] pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, ] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, - {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"}, - {file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"}, + {file = "pylint-2.13.5-py3-none-any.whl", hash = "sha256:c149694cfdeaee1aa2465e6eaab84c87a881a7d55e6e93e09466be7164764d1e"}, + {file = "pylint-2.13.5.tar.gz", hash = "sha256:dab221658368c7a05242e673c275c488670144123f4bd262b2777249c1c0de9b"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pytest = [ - {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, - {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-cov = [ - {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, - {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] +pytest-redislite = [ + {file = "pytest-redislite-0.1.0.tar.gz", hash = "sha256:087e3c33a05adebd4ced5b8f53e95375d2c57e2b235d828156c084e7e5f21a09"}, + {file = "pytest_redislite-0.1.0-py3-none-any.whl", hash = "sha256:a6c0af71f349fc7ee9cdf763bfde31165ef311ffad059e3959f64dc6e6f8cc9f"}, ] pytest-structlog = [ {file = "pytest-structlog-0.3.tar.gz", hash = "sha256:be01025a712f03e9e2ede3bfbd9bce7ce79d4b7449d426fc603bebfcb0b7e860"}, {file = "pytest_structlog-0.3-py2.py3-none-any.whl", hash = "sha256:c07ab9c89ee942ca5776ac81c746387dcb26d52c53466ee850daab67bb5b0281"}, ] pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, - {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, -] -regex = [ - {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, - {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, - {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, - {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, - {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, - {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, - {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, - {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, - {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, - {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, - {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, - {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, - {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +redis = [ + {file = "redis-4.2.2-py3-none-any.whl", hash = "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2"}, + {file = "redis-4.2.2.tar.gz", hash = "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f"}, +] +redislite = [ + {file = "redislite-6.0.674960-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:24c02a9b644d3397616989d93175a8b4c9940fbafe3a6b244c1513e0778c99d9"}, + {file = "redislite-6.0.674960-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c75a09b26ef05335e7276f416352f1af0db57e80cff25fe8aac38b92fffd0215"}, + {file = "redislite-6.0.674960-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:f27402586a9639b66aeb59189e44d027065a71c7c01e56b701be865de8406fe8"}, + {file = "redislite-6.0.674960-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ab26ec837b0c1a744e58288e2986c48b0c18773071efba5072117640e3795bd3"}, + {file = "redislite-6.0.674960-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:937e4a200402590b02aff537a6327322162f02c0b8bf359e063ed680dc4f8bd3"}, + {file = "redislite-6.0.674960-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:170eb1353e3ea56c58d58019029eb4797a24a4b9996dc8a8df2f774b66e54803"}, + {file = "redislite-6.0.674960-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:10551f3c3acbb45e6b398120f4586d02cbf0a1499ede06b7817be6111c645089"}, + {file = "redislite-6.0.674960-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9859ca5d24bb0594a61c3cba87f62ec46934e3f539bb8cab373c53bcdb6f0a97"}, + {file = "redislite-6.0.674960-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:66c927fae6df8e40d2f5ff16d47f3f4708ee62a37562394ebc18bacf8c99c034"}, + {file = "redislite-6.0.674960-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:472abdf314ca20ba72e33864fd52405a8679a15433069180fba6c5baa3566918"}, + {file = "redislite-6.0.674960-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:50e6c92dd07bc65dc64658b6bae2959d23e9e1ac85bfdb8214aab3ac8b30e807"}, + {file = "redislite-6.0.674960-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:1ea50ae4da7ca65e618b5103bde77e0d27c0458c619a6c2f57d3069c04c2c5b3"}, + {file = "redislite-6.0.674960.tar.gz", hash = "sha256:6ffc1c2233d5ca036f907e521ca8e0e0d5ce9957357e18de997332b4978357de"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] smmap = [ - {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"}, - {file = "smmap-3.0.4.tar.gz", hash = "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"}, + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ - {file = "Sphinx-4.0.2-py3-none-any.whl", hash = "sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4"}, - {file = "Sphinx-4.0.2.tar.gz", hash = "sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-0.5.2-py2.py3-none-any.whl", hash = "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f"}, @@ -1278,20 +1408,20 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] stevedore = [ - {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, - {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, + {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, + {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, ] structlog = [ - {file = "structlog-20.1.0-py2.py3-none-any.whl", hash = "sha256:8a672be150547a93d90a7d74229a29e765be05bd156a35cdcc527ebf68e9af92"}, - {file = "structlog-20.1.0.tar.gz", hash = "sha256:7a48375db6274ed1d0ae6123c486472aa1d0890b08d314d2b016f3aa7f35990b"}, + {file = "structlog-20.2.0-py2.py3-none-any.whl", hash = "sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd"}, + {file = "structlog-20.2.0.tar.gz", hash = "sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, @@ -1326,25 +1456,83 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, + {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, + {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, + {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, + {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, + {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, + {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, + {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, + {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, + {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, + {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, + {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, + {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, + {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, + {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, + {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, + {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, ] yamllint = [ - {file = "yamllint-1.25.0-py2.py3-none-any.whl", hash = "sha256:c7be4d0d2584a1b561498fa9acb77ad22eb434a109725c7781373ae496d823b3"}, - {file = "yamllint-1.25.0.tar.gz", hash = "sha256:b1549cbe5b47b6ba67bdeea31720f5c51431a4d0c076c1557952d841f7223519"}, + {file = "yamllint-1.26.3.tar.gz", hash = "sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/pyproject.toml b/pyproject.toml index e1d88aa0..37645e04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ sphinx = {version = "^4.0.2", optional = true} m2r2 = {version = "^0.2.7", optional = true} sphinx-rtd-theme = {version = "^0.5.2", optional = true} toml = {version = "^0.10.2", optional = true} +redis = "^4.2.2" [tool.poetry.dev-dependencies] @@ -47,6 +48,7 @@ coverage = {extras = ["toml"], version = "^5.3"} Sphinx = "^4.0.2" m2r2 = "^0.2.7" sphinx-rtd-theme = "^0.5.2" +pytest-redislite = { version = "^0.1.0", python = "^3.7"} [tool.poetry.extras] docs = [ @@ -101,7 +103,7 @@ notes = """, FIXME, XXX, """ - + [tool.pylint.similarities] # There's a lot of duplicate code in the examples/backend_*.py files - don't complain about it for now min-similarity-lines = 20 diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 4d32f98d..447f55a7 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -32,7 +32,7 @@ def test_diffsync_default_name_type(generic_diffsync): def test_diffsync_generic_load_is_noop(generic_diffsync): generic_diffsync.load() - assert len(generic_diffsync._data) == 0 # pylint: disable=protected-access + assert generic_diffsync.count() == 0 def test_diffsync_dict_with_no_data(generic_diffsync): @@ -89,13 +89,14 @@ def test_diffsync_add_no_raises_existing_same_object(generic_diffsync): # First attempt at adding object generic_diffsync.add(person) - assert modelname in generic_diffsync._data # pylint: disable=protected-access - assert uid in generic_diffsync._data[modelname] # pylint: disable=protected-access - assert person == generic_diffsync._data[modelname][uid] # pylint: disable=protected-access + assert modelname in generic_diffsync.get_all_model_names() + assert any(uid == obj.get_unique_id() for obj in generic_diffsync.get_all(modelname)) + + assert person == generic_diffsync.get(modelname, uid) # Attempt to add again and make sure it doesn't raise an exception generic_diffsync.add(person) - assert person is generic_diffsync._data[modelname][uid] # pylint: disable=protected-access + assert person is generic_diffsync.get(modelname, uid) assert person is generic_diffsync.get(PersonA, "Mikhail Yohman") @@ -686,7 +687,9 @@ def test_diffsync_remove_missing_child(log, backend_a): backend_a.remove(rdu_spine1_eth0) # Should log an error but continue removing other child objects backend_a.remove(rdu_spine1, remove_children=True) - assert log.has("Unable to remove child rdu-spine1__eth0 of device rdu-spine1 - not found!", diffsync=backend_a) + assert log.has( + "Unable to remove child rdu-spine1__eth0 of device rdu-spine1 - not found!", diffsync=backend_a.store + ) with pytest.raises(ObjectNotFound): backend_a.get(Interface, "rdu-spine1__eth1") diff --git a/tests/unit/test_redisstore.py b/tests/unit/test_redisstore.py new file mode 100644 index 00000000..9541bd54 --- /dev/null +++ b/tests/unit/test_redisstore.py @@ -0,0 +1,43 @@ +"""Testing of RedisStore.""" +import pytest +from diffsync.store.redis import RedisStore + + +def test_redisstore_init(redis_url): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + assert str(store) == "mystore(123)" + + +def test_redisstore_init_wrong(): + with pytest.raises(Exception): + RedisStore(name="mystore", store_id="123", url="wrong") + + +def test_redisstore_add_obj(redis_url, make_site): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + assert store.count() == 1 + + +def test_redisstore_get_all_obj(redis_url, make_site): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + assert store.get_all(site.__class__)[0] == site + + +def test_redisstore_get_obj(redis_url, make_site): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + assert store.get(site.__class__, site.name) == site + + +def test_redisstore_remove_obj(redis_url, make_site): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + assert store.count(site.__class__.__name__) == store.count() == 1 + store.remove(site) + assert store.count(site.__class__.__name__) == store.count() == 0 From 00ba58bbea97fcd11f1d7bb53e6d3ff58ad87f69 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 14:49:58 +0200 Subject: [PATCH 03/20] Fix tests --- diffsync/helpers.py | 4 +- diffsync/store/__init__.py | 2 +- diffsync/store/local.py | 4 +- diffsync/store/redis.py | 45 ++++++++++++-------- examples/01-multiple-data-sources/models.py | 4 +- examples/03-remote-system/local_adapter.py | 2 +- examples/03-remote-system/models.py | 2 +- examples/04-get-update-instantiate/models.py | 4 +- tasks.py | 2 +- tests/unit/conftest.py | 10 ++--- tests/unit/test_diff.py | 8 ++-- tests/unit/test_diff_element.py | 6 +-- tests/unit/test_diffsync.py | 10 ++--- tests/unit/test_diffsync_model.py | 2 +- 14 files changed, 58 insertions(+), 47 deletions(-) diff --git a/diffsync/helpers.py b/diffsync/helpers.py index 1960fb0c..b8b4fe2d 100644 --- a/diffsync/helpers.py +++ b/diffsync/helpers.py @@ -125,8 +125,8 @@ def diff_object_list(self, src: List["DiffSyncModel"], dst: List["DiffSyncModel" self.validate_objects_for_diff(combined_dict.values()) - for uid in combined_dict: - src_obj, dst_obj = combined_dict[uid] + for _, item in combined_dict.items(): + src_obj, dst_obj = item diff_element = self.diff_object_pair(src_obj, dst_obj) if diff_element: diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index e510c35a..1c30403f 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -11,7 +11,7 @@ class BaseStore: """Reference store to be implemented in different backends.""" - def __init__(self, diffsync=None, name=None, *args, **kwargs) -> None: # pylint: disable=unused-argument + def __init__(self, *args, diffsync=None, name: str = "", **kwargs) -> None: # pylint: disable=unused-argument """Init method for BaseStore.""" self.diffsync = diffsync self.name = name if name else self.__class__.__name__ diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 8c98a36a..40aa546b 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -1,7 +1,7 @@ """LocalStore module.""" from collections import defaultdict -from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING +from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING, Dict from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists from diffsync.store import BaseStore @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs) -> None: """Init method for LocalStore.""" super().__init__(*args, **kwargs) - self._data = defaultdict(dict) + self._data: Dict = defaultdict(dict) def get_all_model_names(self): """Get all the model names stored. diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index e3aa0981..bc541817 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -1,7 +1,7 @@ """RedisStore module.""" import copy import uuid -from pickle import loads, dumps +from pickle import loads, dumps # nosec from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING from redis import Redis @@ -18,9 +18,8 @@ class RedisStore(BaseStore): """RedisStore class.""" - def __init__(self, store_id=None, host="localhost", port=6379, url=None, db=0, *args, **kwargs): + def __init__(self, *args, store_id=None, host="localhost", port=6379, url=None, db=0, **kwargs): """Init method for RedisStore.""" - super().__init__(*args, **kwargs) if url: @@ -37,9 +36,15 @@ def __init__(self, store_id=None, host="localhost", port=6379, url=None, db=0, * self._store_label = f"{REDIS_DIFFSYNC_ROOT_LABEL}:{self._store_id}" def __str__(self): + """Render store name.""" return f"{self.name}({self._store_id})" def get_all_model_names(self): + """Get all the model names stored. + + Return: + List[str]: List of all the model names. + """ # TODO: implement raise NotImplementedError @@ -78,12 +83,12 @@ def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifi ) try: - obj = loads(self._store.get(self._get_key_for_object(modelname, uid))) - obj.diffsync = self.diffsync + obj_result = loads(self._store.get(self._get_key_for_object(modelname, uid))) # nosec + obj_result.diffsync = self.diffsync except TypeError: - raise ObjectNotFound(f"{modelname} {uid} not present in Cache") + raise ObjectNotFound(f"{modelname} {uid} not present in Cache") # pylint: disable=raise-missing-from - return obj + return obj_result def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # """Get all objects of a given type. @@ -102,11 +107,11 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L results = [] for key in self._store.scan_iter(f"{self._store_label}:{modelname}:*"): try: - obj = loads(self._store.get(key)) - obj.diffsync = self.diffsync - results.append(obj) + obj_result = loads(self._store.get(key)) # nosec + obj_result.diffsync = self.diffsync + results.append(obj_result) except TypeError: - raise ObjectNotFound(f"{key} not present in Cache") + raise ObjectNotFound(f"{key} not present in Cache") # pylint: disable=raise-missing-from return results @@ -131,11 +136,11 @@ def get_by_uids( for uid in uids: try: - obj = loads(self._store.get(self._get_key_for_object(modelname, uid))) - obj.diffsync = self.diffsync - results.append(obj) + obj_result = loads(self._store.get(self._get_key_for_object(modelname, uid))) # nosec + obj_result.diffsync = self.diffsync + results.append(obj_result) except TypeError: - raise ObjectNotFound(f"{modelname} {uid} not present in Cache") + raise ObjectNotFound(f"{modelname} {uid} not present in Cache") # pylint: disable=raise-missing-from return results @@ -167,7 +172,10 @@ def add(self, obj: "DiffSyncModel"): # # Remove the diffsync object before sending to Redis obj_copy = copy.copy(obj) - obj_copy.diffsync = False + + # obj_copy.diffsync = False + obj_copy.diffsync = None + self._store.set(object_key, dumps(obj_copy)) def update(self, obj: "DiffSyncModel"): # @@ -181,7 +189,10 @@ def update(self, obj: "DiffSyncModel"): # object_key = self._get_key_for_object(modelname, uid) obj_copy = copy.copy(obj) - obj_copy.diffsync = False + + # obj_copy.diffsync = False + obj_copy.diffsync = None + self._store.set(object_key, dumps(obj_copy)) def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # diff --git a/examples/01-multiple-data-sources/models.py b/examples/01-multiple-data-sources/models.py index ad165199..29085155 100644 --- a/examples/01-multiple-data-sources/models.py +++ b/examples/01-multiple-data-sources/models.py @@ -28,7 +28,7 @@ class Site(DiffSyncModel): _children = {"device": "devices"} name: str - devices: List = list() + devices: List = [] class Device(DiffSyncModel): @@ -42,7 +42,7 @@ class Device(DiffSyncModel): name: str site_name: Optional[str] # note that this attribute is NOT included in _attributes role: Optional[str] # note that this attribute is NOT included in _attributes - interfaces: List = list() + interfaces: List = [] class Interface(DiffSyncModel): diff --git a/examples/03-remote-system/local_adapter.py b/examples/03-remote-system/local_adapter.py index b9fa8715..55803b02 100644 --- a/examples/03-remote-system/local_adapter.py +++ b/examples/03-remote-system/local_adapter.py @@ -29,7 +29,7 @@ class LocalAdapter(DiffSync): def load(self, filename=COUNTRIES_FILE): # pylint: disable=arguments-differ """Load all regions and countries from a local JSON file.""" - with open(filename, "r") as data_file: + with open(filename, "r", encoding="utf-8") as data_file: countries = json.load(data_file) # Load all regions first diff --git a/examples/03-remote-system/models.py b/examples/03-remote-system/models.py index baa03d0e..6a1f85c1 100644 --- a/examples/03-remote-system/models.py +++ b/examples/03-remote-system/models.py @@ -16,7 +16,7 @@ class Region(DiffSyncModel): slug: str name: str - countries: List[str] = list() + countries: List[str] = [] class Country(DiffSyncModel): diff --git a/examples/04-get-update-instantiate/models.py b/examples/04-get-update-instantiate/models.py index 105dd5ce..9a105992 100644 --- a/examples/04-get-update-instantiate/models.py +++ b/examples/04-get-update-instantiate/models.py @@ -40,8 +40,8 @@ class Device(DiffSyncModel): name: str site_name: Optional[str] # note that this attribute is NOT included in _attributes role: Optional[str] # note that this attribute is NOT included in _attributes - interfaces: List = list() - sites: List = list() + interfaces: List = [] + sites: List = [] class Interface(DiffSyncModel): diff --git a/tasks.py b/tasks.py index 15e152d8..01f838a6 100644 --- a/tasks.py +++ b/tasks.py @@ -27,7 +27,7 @@ def project_ver(): """Find version from pyproject.toml to use for docker image tagging.""" - with open("pyproject.toml") as file: + with open("pyproject.toml", encoding="utf-8") as file: return toml.load(file)["tool"]["poetry"].get("version", "latest") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 64ae7e17..d340a957 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -73,7 +73,7 @@ class Site(DiffSyncModel): _children = {"device": "devices"} name: str - devices: List = list() + devices: List = [] @pytest.fixture @@ -100,7 +100,7 @@ class Device(DiffSyncModel): name: str site_name: Optional[str] # note this is not included in _attributes role: str - interfaces: List = list() + interfaces: List = [] @pytest.fixture @@ -189,7 +189,7 @@ class SiteA(Site): _children = {"device": "devices", "person": "people"} - people: List = list() + people: List = [] class DeviceA(Device): @@ -305,7 +305,7 @@ class SiteB(Site): _children = {"device": "devices", "place": "places"} - places: List = list() + places: List = [] class DeviceB(Device): @@ -313,7 +313,7 @@ class DeviceB(Device): _attributes = ("role", "vlans") - vlans: List = list() + vlans: List = [] class PlaceB(DiffSyncModel): diff --git a/tests/unit/test_diff.py b/tests/unit/test_diff.py index 2f1764d9..c74f19d5 100644 --- a/tests/unit/test_diff.py +++ b/tests/unit/test_diff.py @@ -25,10 +25,10 @@ def test_diff_empty(): """Test the basic functionality of the Diff class when initialized and empty.""" diff = Diff() - assert diff.children == {} - assert list(diff.groups()) == [] + assert not diff.children + assert not list(diff.groups()) assert not diff.has_diffs() - assert list(diff.get_children()) == [] + assert not list(diff.get_children()) def test_diff_summary_with_no_diffs(): @@ -46,7 +46,7 @@ def test_diff_str_with_no_diffs(): def test_diff_dict_with_no_diffs(): diff = Diff() - assert diff.dict() == {} + assert not diff.dict() def test_diff_len_with_no_diffs(): diff --git a/tests/unit/test_diff_element.py b/tests/unit/test_diff_element.py index 7c967330..943b0f91 100644 --- a/tests/unit/test_diff_element.py +++ b/tests/unit/test_diff_element.py @@ -29,11 +29,11 @@ def test_diff_element_empty(): assert element.dest_name == "dest" assert element.source_attrs is None assert element.dest_attrs is None - assert list(element.get_children()) == [] + assert not list(element.get_children()) assert not element.has_diffs() assert not element.has_diffs(include_children=True) assert not element.has_diffs(include_children=False) - assert element.get_attrs_keys() == [] + assert not element.get_attrs_keys() element2 = DiffElement( "interface", "eth0", {"device_name": "device1", "name": "eth0"}, source_name="S1", dest_name="D1" @@ -54,7 +54,7 @@ def test_diff_element_str_with_no_diffs(): def test_diff_element_dict_with_no_diffs(): element = DiffElement("interface", "eth0", {"device_name": "device1", "name": "eth0"}) - assert element.dict() == {} + assert not element.dict() def test_diff_element_len_with_no_diffs(): diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 447f55a7..9e49116a 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -68,8 +68,8 @@ def test_diffsync_get_with_no_data_fails(generic_diffsync): def test_diffsync_get_all_with_no_data_is_empty_list(generic_diffsync): - assert list(generic_diffsync.get_all("anything")) == [] - assert list(generic_diffsync.get_all(DiffSyncModel)) == [] + assert not list(generic_diffsync.get_all("anything")) + assert not list(generic_diffsync.get_all(DiffSyncModel)) def test_diffsync_get_by_uids_with_no_data(generic_diffsync): @@ -224,7 +224,7 @@ def test_diffsync_get_all_with_generic_model(generic_diffsync, generic_diffsync_ assert list(generic_diffsync.get_all(DiffSyncModel)) == [generic_diffsync_model] assert list(generic_diffsync.get_all(DiffSyncModel.get_type())) == [generic_diffsync_model] # Wrong object-type - no match - assert list(generic_diffsync.get_all("anything")) == [] + assert not list(generic_diffsync.get_all("anything")) def test_diffsync_get_by_uids_with_generic_model(generic_diffsync, generic_diffsync_model): @@ -247,7 +247,7 @@ def test_diffsync_remove_with_generic_model(generic_diffsync, generic_diffsync_m with pytest.raises(ObjectNotFound): generic_diffsync.get(DiffSyncModel, "") - assert list(generic_diffsync.get_all(DiffSyncModel)) == [] + assert not list(generic_diffsync.get_all(DiffSyncModel)) with pytest.raises(ObjectNotFound): generic_diffsync.get_by_uids([""], DiffSyncModel) @@ -499,7 +499,7 @@ def test_diffsync_sync_from(backend_a, backend_b): assert list(backend_a.get_all(Site)) == [site_nyc_a, site_sfo_a, site_atl_a] assert list(backend_a.get_all("site")) == [site_nyc_a, site_sfo_a, site_atl_a] - assert list(backend_a.get_all("nothing")) == [] + assert not list(backend_a.get_all("nothing")) assert backend_a.get_by_uids(["nyc", "sfo"], Site) == [site_nyc_a, site_sfo_a] assert backend_a.get_by_uids(["sfo", "nyc"], "site") == [site_sfo_a, site_nyc_a] diff --git a/tests/unit/test_diffsync_model.py b/tests/unit/test_diffsync_model.py index 7670ff2a..88cb861e 100644 --- a/tests/unit/test_diffsync_model.py +++ b/tests/unit/test_diffsync_model.py @@ -370,7 +370,7 @@ class Alpha(DiffSyncModel): name: str letter: str - numbers: List = list() + numbers: List = [] class Beta(Alpha): """A model class representing a single Greek letter in both English and Spanish.""" From d799f19382e39abc618c26cd626e61b95ee69db0 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 15:28:07 +0200 Subject: [PATCH 04/20] Exceptions --- diffsync/store/redis.py | 20 +++++++++++--------- tests/unit/test_redisstore.py | 5 +++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index bc541817..ad7bf044 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -5,8 +5,8 @@ from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING from redis import Redis - -from diffsync.exceptions import ObjectNotFound +from redis.exceptions import ConnectionError as RedisConnectionError +from diffsync.exceptions import ObjectNotFound, ObjectStoreException from diffsync.store import BaseStore if TYPE_CHECKING: @@ -22,14 +22,16 @@ def __init__(self, *args, store_id=None, host="localhost", port=6379, url=None, """Init method for RedisStore.""" super().__init__(*args, **kwargs) - if url: - self._store = Redis.from_url(url, db=db) - else: - self._store = Redis(host=host, port=port, db=db) + try: + if url: + self._store = Redis.from_url(url, db=db) + else: + self._store = Redis(host=host, port=port, db=db) - if not self._store.ping(): - # TODO: find the proper exception - raise Exception + if not self._store.ping(): + raise RedisConnectionError + except RedisConnectionError: + raise ObjectStoreException("Redis store is unavailable.") from RedisConnectionError self._store_id = store_id if store_id else str(uuid.uuid4())[:8] diff --git a/tests/unit/test_redisstore.py b/tests/unit/test_redisstore.py index 9541bd54..cab47e18 100644 --- a/tests/unit/test_redisstore.py +++ b/tests/unit/test_redisstore.py @@ -1,6 +1,7 @@ """Testing of RedisStore.""" import pytest from diffsync.store.redis import RedisStore +from diffsync.exceptions import ObjectStoreException def test_redisstore_init(redis_url): @@ -9,8 +10,8 @@ def test_redisstore_init(redis_url): def test_redisstore_init_wrong(): - with pytest.raises(Exception): - RedisStore(name="mystore", store_id="123", url="wrong") + with pytest.raises(ObjectStoreException): + RedisStore(name="mystore", store_id="123", url="redis://wrong") def test_redisstore_add_obj(redis_url, make_site): From a69d8e99bdba0361e90252e6420033da847fc3c5 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 15:31:30 +0200 Subject: [PATCH 05/20] Unittest only for >3.7 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c1dbe5f..99987134 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,8 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.6", "3.7", "3.8", "3.9"] + # pytest-redis only supported in >3.7 + python-version: ["3.7", "3.8", "3.9", "3.10"] runs-on: "ubuntu-20.04" env: PYTHON_VER: "${{ matrix.python-version }}" From fead610b129d6cb8337af40bb6ee1b6344376741 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 15:40:14 +0200 Subject: [PATCH 06/20] Clean # --- diffsync/store/__init__.py | 20 ++++++++++---------- diffsync/store/local.py | 14 +++++++------- diffsync/store/redis.py | 18 +++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 1c30403f..37815947 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -42,7 +42,7 @@ def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifi """ raise NotImplementedError - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: @@ -54,8 +54,8 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L raise NotImplementedError def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # - ) -> List["DiffSyncModel"]: # + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: @@ -67,7 +67,7 @@ def get_by_uids( """ raise NotImplementedError - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: @@ -79,7 +79,7 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # """ raise NotImplementedError - def add(self, obj: "DiffSyncModel"): # + def add(self, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -90,7 +90,7 @@ def add(self, obj: "DiffSyncModel"): # """ raise NotImplementedError - def update(self, obj: "DiffSyncModel"): # + def update(self, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -103,8 +103,8 @@ def count(self, modelname): raise NotImplementedError def get_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None # - ) -> Tuple["DiffSyncModel", bool]: # + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None + ) -> Tuple["DiffSyncModel", bool]: """Attempt to get the object with provided identifiers or instantiate it with provided identifiers and attrs. Args: @@ -129,8 +129,8 @@ def get_or_instantiate( return obj, created def update_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict # - ) -> Tuple["DiffSyncModel", bool]: # + self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict + ) -> Tuple["DiffSyncModel", bool]: """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. Args: diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 40aa546b..5643b28a 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -28,7 +28,7 @@ def get_all_model_names(self): """ return self._data.keys() - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): # + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): """Get one object from the data store based on its unique id. Args: @@ -63,7 +63,7 @@ def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifi raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") return self._data[modelname][uid] - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: @@ -80,8 +80,8 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L return list(self._data[modelname].values()) def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # - ) -> List["DiffSyncModel"]: # + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: @@ -103,7 +103,7 @@ def get_by_uids( results.append(self._data[modelname][uid]) return results - def add(self, obj: "DiffSyncModel"): # + def add(self, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -127,7 +127,7 @@ def add(self, obj: "DiffSyncModel"): # self._data[modelname][uid] = obj - def update(self, obj: "DiffSyncModel"): # + def update(self, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -142,7 +142,7 @@ def update(self, obj: "DiffSyncModel"): # self._data[modelname][uid] = obj - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index ad7bf044..a6522ece 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -53,7 +53,7 @@ def get_all_model_names(self): def _get_key_for_object(self, modelname, uid): return f"{self._store_label}:{modelname}:{uid}" - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): # + def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): """Get one object from the data store based on its unique id. Args: @@ -92,7 +92,7 @@ def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifi return obj_result - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: # + def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: @@ -118,8 +118,8 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L return results def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] # - ) -> List["DiffSyncModel"]: # + self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: @@ -146,7 +146,7 @@ def get_by_uids( return results - def add(self, obj: "DiffSyncModel"): # + def add(self, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -174,13 +174,11 @@ def add(self, obj: "DiffSyncModel"): # # Remove the diffsync object before sending to Redis obj_copy = copy.copy(obj) - - # obj_copy.diffsync = False obj_copy.diffsync = None self._store.set(object_key, dumps(obj_copy)) - def update(self, obj: "DiffSyncModel"): # + def update(self, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -191,13 +189,11 @@ def update(self, obj: "DiffSyncModel"): # object_key = self._get_key_for_object(modelname, uid) obj_copy = copy.copy(obj) - - # obj_copy.diffsync = False obj_copy.diffsync = None self._store.set(object_key, dumps(obj_copy)) - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # + def remove(self, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: From b59357a8de20c0a2c1cce2f6de8abffad9c0b136 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 16:13:28 +0200 Subject: [PATCH 07/20] extend testing --- diffsync/store/redis.py | 28 +++++++++++++++++----------- tests/unit/test_redisstore.py | 20 +++++++++++++++++++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index a6522ece..9e656b0e 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -6,7 +6,7 @@ from redis import Redis from redis.exceptions import ConnectionError as RedisConnectionError -from diffsync.exceptions import ObjectNotFound, ObjectStoreException +from diffsync.exceptions import ObjectNotFound, ObjectStoreException, ObjectAlreadyExists from diffsync.store import BaseStore if TYPE_CHECKING: @@ -47,8 +47,14 @@ def get_all_model_names(self): Return: List[str]: List of all the model names. """ - # TODO: implement - raise NotImplementedError + # TODO: optimize it + all_model_names = [] + for item in self._store.scan_iter(f"{self._store_label}:*"): + model_name = item.split(b":")[2].decode() + if model_name not in all_model_names: + all_model_names.append(model_name) + + return all_model_names def _get_key_for_object(self, modelname, uid): return f"{self._store_label}:{modelname}:{uid}" @@ -161,16 +167,16 @@ def add(self, obj: "DiffSyncModel"): # Get existing Object object_key = self._get_key_for_object(modelname, uid) - # existing_obj_binary = self._store.get(object_key) - # if existing_obj_binary: - # existing_obj = loads(existing_obj_binary) - # existing_obj_dict = existing_obj.dict() + existing_obj_binary = self._store.get(object_key) + if existing_obj_binary: + existing_obj = loads(existing_obj_binary) + existing_obj_dict = existing_obj.dict() - # if existing_obj_dict != obj.dict(): - # raise ObjectAlreadyExists(f"Object {uid} already present", obj) + if existing_obj_dict != obj.dict(): + raise ObjectAlreadyExists(f"Object {uid} already present", obj) - # # Return so we don't have to change anything on the existing object and underlying data - # return + # Return so we don't have to change anything on the existing object and underlying data + return # Remove the diffsync object before sending to Redis obj_copy = copy.copy(obj) diff --git a/tests/unit/test_redisstore.py b/tests/unit/test_redisstore.py index cab47e18..69a7c425 100644 --- a/tests/unit/test_redisstore.py +++ b/tests/unit/test_redisstore.py @@ -1,7 +1,7 @@ """Testing of RedisStore.""" import pytest from diffsync.store.redis import RedisStore -from diffsync.exceptions import ObjectStoreException +from diffsync.exceptions import ObjectStoreException, ObjectAlreadyExists def test_redisstore_init(redis_url): @@ -21,6 +21,14 @@ def test_redisstore_add_obj(redis_url, make_site): assert store.count() == 1 +def test_redisstore_add_obj_twice(redis_url, make_site): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + store.add(site) + assert store.count() == 1 + + def test_redisstore_get_all_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() @@ -42,3 +50,13 @@ def test_redisstore_remove_obj(redis_url, make_site): assert store.count(site.__class__.__name__) == store.count() == 1 store.remove(site) assert store.count(site.__class__.__name__) == store.count() == 0 + + +def test_redisstore_get_all_model_names(redis_url, make_site, make_device): + store = RedisStore(name="mystore", store_id="123", url=redis_url) + site = make_site() + store.add(site) + device = make_device() + store.add(device) + assert site.get_type() in store.get_all_model_names() + assert device.get_type() in store.get_all_model_names() From ccac2c695bc4843acd3001ac2c849d5640aa6433 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 13 Apr 2022 16:31:40 +0200 Subject: [PATCH 08/20] add types-redis for 3.9 redis support --- poetry.lock | 14 +++++++++++++- pyproject.toml | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index c0d839b6..88359a0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -856,6 +856,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "types-redis" +version = "4.1.19" +description = "Typing stubs for redis" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "types-toml" version = "0.10.4" @@ -920,7 +928,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "cbc0e16a1e8f6c2df2ced42fc4da713107f875860788a35cc0d4806bba87f0a1" +content-hash = "85311a5f39f99bf9a12cc2d900fe75d3c9b22bb73f3bb10a42aaa44b7ea32b6f" [metadata.files] alabaster = [ @@ -1492,6 +1500,10 @@ typed-ast = [ {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] +types-redis = [ + {file = "types-redis-4.1.19.tar.gz", hash = "sha256:8e3c0aa3ebbe0b414f9232aaf27c59fe9ed9eafb5cb39f74420d479dc7a32a63"}, + {file = "types_redis-4.1.19-py3-none-any.whl", hash = "sha256:f13af1ca7b115c727d552f97fd4ac056e5ba0001c29fafec3e2430574aef3651"}, +] types-toml = [ {file = "types-toml-0.10.4.tar.gz", hash = "sha256:9340e7c1587715581bb13905b3af30b79fe68afaccfca377665d5e63b694129a"}, {file = "types_toml-0.10.4-py3-none-any.whl", hash = "sha256:4a9ffd47bbcec49c6fde6351a889b2c1bd3c0ef309fa0eed60dc28e58c8b9ea6"}, diff --git a/pyproject.toml b/pyproject.toml index 8588791c..e242b386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ colorama = {version = "^0.4.3", optional = true} # For Pydantic dataclasses = {version = "^0.7", python = "~3.6"} redis = "^4.2.2" +types-redis = "^4.1.19" [tool.poetry.dev-dependencies] pytest = "*" From 6e29de558931eb50862dd214ee2f6a4286308370 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Fri, 13 May 2022 14:57:25 +0200 Subject: [PATCH 09/20] Make redis pining open --- poetry.lock | 292 ++++++++++++++++++++++++------------------------- pyproject.toml | 4 +- 2 files changed, 148 insertions(+), 148 deletions(-) diff --git a/poetry.lock b/poetry.lock index 88359a0e..a96c4532 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "astroid" -version = "2.11.2" +version = "2.11.5" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -55,11 +55,11 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "babel" -version = "2.9.1" +version = "2.10.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" @@ -276,7 +276,7 @@ python-versions = "*" [[package]] name = "invoke" -version = "1.7.0" +version = "1.7.1" description = "Pythonic task execution" category = "dev" optional = false @@ -356,7 +356,7 @@ python-versions = "*" [[package]] name = "mypy" -version = "0.942" +version = "0.950" description = "Optional static typing for Python" category = "dev" optional = false @@ -364,7 +364,7 @@ python-versions = ">=3.6" [package.dependencies] mypy-extensions = ">=0.4.3" -tomli = ">=1.1.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" @@ -402,7 +402,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pbr" -version = "5.8.1" +version = "5.9.0" description = "Python Build Reasonableness" category = "dev" optional = false @@ -502,22 +502,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.13.5" +version = "2.13.8" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.11.2,<=2.12.0-dev0" +astroid = ">=2.11.3,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} dill = ">=0.2" isort = ">=4.2.5,<6" @@ -623,7 +623,7 @@ python-versions = ">=3.6" [[package]] name = "redis" -version = "4.2.2" +version = "4.3.1" description = "Python client for Redis database and key-value store" category = "main" optional = false @@ -850,7 +850,7 @@ python-versions = ">=3.6" [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -858,7 +858,7 @@ python-versions = ">=3.6" [[package]] name = "types-redis" -version = "4.1.19" +version = "4.2.2" description = "Typing stubs for redis" category = "main" optional = false @@ -866,7 +866,7 @@ python-versions = "*" [[package]] name = "types-toml" -version = "0.10.4" +version = "0.10.7" description = "Typing stubs for toml" category = "dev" optional = false @@ -895,7 +895,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "wrapt" -version = "1.14.0" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." category = "main" optional = false @@ -928,7 +928,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "85311a5f39f99bf9a12cc2d900fe75d3c9b22bb73f3bb10a42aaa44b7ea32b6f" +content-hash = "a9546361ba2baaff8a37a734285b46c3caa244eb7ca917a067711967dcad7c93" [metadata.files] alabaster = [ @@ -936,8 +936,8 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] astroid = [ - {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"}, - {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"}, + {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, + {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -952,8 +952,8 @@ attrs = [ {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] babel = [ - {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, - {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, + {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, + {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, ] bandit = [ {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, @@ -1094,8 +1094,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] invoke = [ - {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"}, - {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"}, + {file = "invoke-1.7.1-py3-none-any.whl", hash = "sha256:2dc975b4f92be0c0a174ad2d063010c8a1fdb5e9389d69871001118b4fcac4fb"}, + {file = "invoke-1.7.1.tar.gz", hash = "sha256:7b6deaf585eee0a848205d0b8c0014b9bf6f287a8eb798818a642dff1df14b19"}, ] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, @@ -1193,29 +1193,29 @@ mistune = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] mypy = [ - {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, - {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, - {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, - {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, - {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, - {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, - {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, - {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, - {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, - {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, - {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, - {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, - {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, - {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, - {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, - {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, - {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, - {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, - {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, + {file = "mypy-0.950-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b"}, + {file = "mypy-0.950-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0"}, + {file = "mypy-0.950-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22"}, + {file = "mypy-0.950-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb"}, + {file = "mypy-0.950-cp310-cp310-win_amd64.whl", hash = "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334"}, + {file = "mypy-0.950-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f"}, + {file = "mypy-0.950-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc"}, + {file = "mypy-0.950-cp36-cp36m-win_amd64.whl", hash = "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2"}, + {file = "mypy-0.950-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed"}, + {file = "mypy-0.950-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075"}, + {file = "mypy-0.950-cp37-cp37m-win_amd64.whl", hash = "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"}, + {file = "mypy-0.950-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d"}, + {file = "mypy-0.950-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a"}, + {file = "mypy-0.950-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605"}, + {file = "mypy-0.950-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2"}, + {file = "mypy-0.950-cp38-cp38-win_amd64.whl", hash = "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff"}, + {file = "mypy-0.950-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8"}, + {file = "mypy-0.950-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038"}, + {file = "mypy-0.950-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2"}, + {file = "mypy-0.950-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519"}, + {file = "mypy-0.950-cp39-cp39-win_amd64.whl", hash = "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef"}, + {file = "mypy-0.950-py3-none-any.whl", hash = "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb"}, + {file = "mypy-0.950.tar.gz", hash = "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1230,8 +1230,8 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pbr = [ - {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"}, - {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"}, + {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, + {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, ] platformdirs = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, @@ -1329,12 +1329,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pylint = [ - {file = "pylint-2.13.5-py3-none-any.whl", hash = "sha256:c149694cfdeaee1aa2465e6eaab84c87a881a7d55e6e93e09466be7164764d1e"}, - {file = "pylint-2.13.5.tar.gz", hash = "sha256:dab221658368c7a05242e673c275c488670144123f4bd262b2777249c1c0de9b"}, + {file = "pylint-2.13.8-py3-none-any.whl", hash = "sha256:f87e863a0b08f64b5230e7e779bcb75276346995737b2c0dc2793070487b1ff6"}, + {file = "pylint-2.13.8.tar.gz", hash = "sha256:ced8968c3b699df0615e2a709554dec3ddac2f5cd06efadb69554a69eeca364a"}, ] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, @@ -1396,8 +1396,8 @@ pyyaml = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] redis = [ - {file = "redis-4.2.2-py3-none-any.whl", hash = "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2"}, - {file = "redis-4.2.2.tar.gz", hash = "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f"}, + {file = "redis-4.3.1-py3-none-any.whl", hash = "sha256:84316970995a7adb907a56754d2b92d88fc2d252963dc5ac34c88f0f1a22c25d"}, + {file = "redis-4.3.1.tar.gz", hash = "sha256:94b617b4cd296e94991146f66fc5559756fbefe9493604f0312e4d3298ac63e9"}, ] redislite = [ {file = "redislite-6.0.674960-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:24c02a9b644d3397616989d93175a8b4c9940fbafe3a6b244c1513e0778c99d9"}, @@ -1475,38 +1475,38 @@ tomli = [ {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, + {file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"}, + {file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"}, + {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"}, + {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"}, + {file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"}, + {file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"}, + {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"}, + {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"}, + {file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"}, + {file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"}, + {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"}, + {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"}, + {file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"}, + {file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"}, + {file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"}, + {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"}, + {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"}, + {file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"}, + {file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"}, + {file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"}, + {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"}, + {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"}, + {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, + {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, ] types-redis = [ - {file = "types-redis-4.1.19.tar.gz", hash = "sha256:8e3c0aa3ebbe0b414f9232aaf27c59fe9ed9eafb5cb39f74420d479dc7a32a63"}, - {file = "types_redis-4.1.19-py3-none-any.whl", hash = "sha256:f13af1ca7b115c727d552f97fd4ac056e5ba0001c29fafec3e2430574aef3651"}, + {file = "types-redis-4.2.2.tar.gz", hash = "sha256:616e5331e957832321c14a4bd206b41f967657cf360f62419b21588880af3933"}, + {file = "types_redis-4.2.2-py3-none-any.whl", hash = "sha256:8035a2631591ea2d0484f979f1424789ed757e72b56f0c5d6291a9484540e9c0"}, ] types-toml = [ - {file = "types-toml-0.10.4.tar.gz", hash = "sha256:9340e7c1587715581bb13905b3af30b79fe68afaccfca377665d5e63b694129a"}, - {file = "types_toml-0.10.4-py3-none-any.whl", hash = "sha256:4a9ffd47bbcec49c6fde6351a889b2c1bd3c0ef309fa0eed60dc28e58c8b9ea6"}, + {file = "types-toml-0.10.7.tar.gz", hash = "sha256:a567fe2614b177d537ad99a661adc9bfc8c55a46f95e66370a4ed2dd171335f9"}, + {file = "types_toml-0.10.7-py3-none-any.whl", hash = "sha256:05a8da4bfde2f1ee60e90c7071c063b461f74c63a9c3c1099470c08d6fa58615"}, ] typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, @@ -1517,70 +1517,70 @@ urllib3 = [ {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wrapt = [ - {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, - {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, - {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, - {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, - {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, - {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, - {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, - {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, - {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, - {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, - {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, - {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, - {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, - {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, - {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, - {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, - {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, - {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, - {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, - {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, - {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, - {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] yamllint = [ {file = "yamllint-1.26.3.tar.gz", hash = "sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e"}, diff --git a/pyproject.toml b/pyproject.toml index e242b386..c014aef0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,8 @@ packaging = "^21.3" colorama = {version = "^0.4.3", optional = true} # For Pydantic dataclasses = {version = "^0.7", python = "~3.6"} -redis = "^4.2.2" -types-redis = "^4.1.19" +redis = "*" +types-redis = "*" [tool.poetry.dev-dependencies] pytest = "*" From b766b0bc1d4b3dba43354dced058c171f2649293 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Mon, 16 May 2022 14:03:14 +0200 Subject: [PATCH 10/20] docs --- docs/source/core_engine/03-store.md | 47 +++++++++++++++++++ docs/source/core_engine/index.rst | 2 +- .../getting_started/01-getting-started.md | 4 +- 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 docs/source/core_engine/03-store.md diff --git a/docs/source/core_engine/03-store.md b/docs/source/core_engine/03-store.md new file mode 100644 index 00000000..3a354e05 --- /dev/null +++ b/docs/source/core_engine/03-store.md @@ -0,0 +1,47 @@ +# Store backends + +By default, `Diffsync` supports a local memory storage. All the loaded models from the adapters will be stored in memory, and become available for the diff calculation and sync process. This default behavior works well when executing all the steps in the same process, having access to the same memory space. However, if you want to scale out the execution of the tasks, running it in different processes or in totally different workers, a more distributed memory support is necessary. + +The `store` is a class attribute in the `DiffSync` class, but all the store operations in that class are abstracted in the following methods: `get_all_model_names`, `get`, `get_by_uids`, `add`, `update`, `remove`, `get_or_instantiate`, `update_or_instantiate` and `count`. + +## Use the `LocalStore` Backend + +When you initialize the `Diffsync` Adapter class, there is an optional keyed-argument, `internal_storage_engine`, defaulting to the `LocalStore` class. + +```python +>>> from diffsync import DiffSync +>>> adapter = DiffSync() +>>> type(adapter.store) + +``` + +## Use the `RedisStore` Backend + +The `RedisStore` backend, as the name suggests, connects to an external Redis service, to store data loaded by the `DiffSync` tasks. The biggest change is that it requires to initialize the Redis store class, before using it in the `DiffSync` adapter class. + +```python +>>> from diffsync import DiffSync +>>> from diffsync.store.redis import RedisStore +>>> store = RedisStore(host="redis host") +>>> adapter = DiffSync(internal_storage_engine=store) +>>> type(adapter.store) + +``` + +Notice that the `RedisStore` will validate, when initialized, that there is a reachability to the Redis host, and if not, will raise an exception: + +```python +>>> from diffsync.store.redis import RedisStore +>>> store = RedisStore(host="redis host") +redis.exceptions.ConnectionError + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "", line 1, in + File "/Users/chadell/github.ntc/diffsync/diffsync/store/redis.py", line 34, in __init__ + raise ObjectStoreException("Redis store is unavailable.") from RedisConnectionError +diffsync.exceptions.ObjectStoreException: Redis store is unavailable. +``` + +Using `RedisStore`, every adapter uses a specific Redis label, generated automatically, if not provided via the `store_id` keyed-argument. This `store_id` can be used to point an adapter to the specific memory state needed for diffsync operations. diff --git a/docs/source/core_engine/index.rst b/docs/source/core_engine/index.rst index e51744d5..3b356c5a 100644 --- a/docs/source/core_engine/index.rst +++ b/docs/source/core_engine/index.rst @@ -8,4 +8,4 @@ The core engine of DiffSync is meant to be transparent for most users but in som .. mdinclude:: 01-flags.md .. mdinclude:: 02-customize-diff-class.md - +.. mdinclude:: 03-store.md diff --git a/docs/source/getting_started/01-getting-started.md b/docs/source/getting_started/01-getting-started.md index 14d60865..61fca515 100644 --- a/docs/source/getting_started/01-getting-started.md +++ b/docs/source/getting_started/01-getting-started.md @@ -1,5 +1,3 @@ - - To be able to properly compare different datasets, DiffSync relies on a shared data model that both systems must use. Specifically, each system or dataset must provide a `DiffSync` "adapter" subclass, which in turn represents its dataset as instances of one or more `DiffSyncModel` data model classes. @@ -9,6 +7,7 @@ When comparing two systems, DiffSync detects the intersection between the two sy `DiffSyncModel` is based on [Pydantic](https://pydantic-docs.helpmanual.io/) and is using Python typing to define the format of each attribute. Each `DiffSyncModel` subclass supports the following class-level attributes: + - `_modelname` - Defines the type of the model; used to identify common models between different systems (Mandatory) - `_identifiers` - List of instance field names used as primary keys for this object (Mandatory) - `_shortname` - List of instance field names to use for a shorter name (Optional) @@ -74,6 +73,7 @@ class BackendA(DiffSync): device = self.device(name="rtr-nyc", role="router", site_name="nyc") self.add(device) site.add_child(device) + site.update() ``` # Update remote system on sync From 4641c0ba09bf094d9c48ecf4f2cc82db28fc043c Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 12:08:51 +0200 Subject: [PATCH 11/20] Apply suggestions from code review Co-authored-by: Glenn Matthews --- diffsync/store/__init__.py | 14 ++++++++------ diffsync/store/local.py | 10 ++++++---- diffsync/store/redis.py | 12 +++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 37815947..b3681427 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -11,17 +11,17 @@ class BaseStore: """Reference store to be implemented in different backends.""" - def __init__(self, *args, diffsync=None, name: str = "", **kwargs) -> None: # pylint: disable=unused-argument + def __init__(self, *args, diffsync: Optional["DiffSync"] = None, name: str = "", **kwargs) -> None: # pylint: disable=unused-argument """Init method for BaseStore.""" self.diffsync = diffsync - self.name = name if name else self.__class__.__name__ + self.name = name or self.__class__.__name__ self._log = structlog.get_logger().new(diffsync=self) def __str__(self): """Render store name.""" return self.name - def get_all_model_names(self): + def get_all_model_names(self) -> List[str]: """Get all the model names stored. Return: @@ -29,7 +29,9 @@ def get_all_model_names(self): """ raise NotImplementedError - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + def get( + self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: @@ -98,8 +100,8 @@ def update(self, obj: "DiffSyncModel"): """ raise NotImplementedError - def count(self, modelname): - """Returns the number of elements of an specific model name.""" + def count(self, modelname=None) -> int: + """Returns the number of elements of a specific model, or all elements in the store if not specified.""" raise NotImplementedError def get_or_instantiate( diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 5643b28a..d4be6324 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -20,7 +20,7 @@ def __init__(self, *args, **kwargs) -> None: self._data: Dict = defaultdict(dict) - def get_all_model_names(self): + def get_all_model_names(self) -> List[str]: """Get all the model names stored. Return: @@ -28,7 +28,9 @@ def get_all_model_names(self): """ return self._data.keys() - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + def get( + self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: @@ -173,8 +175,8 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, modelname=None): - """Returns the number of elements of an specific model name.""" + def count(self, modelname=None) -> int: + """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" if not modelname: return sum(len(entries) for entries in self._data.values()) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 9e656b0e..41650579 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -39,9 +39,9 @@ def __init__(self, *args, store_id=None, host="localhost", port=6379, url=None, def __str__(self): """Render store name.""" - return f"{self.name}({self._store_id})" + return f"{self.name} ({self._store_id})" - def get_all_model_names(self): + def get_all_model_names(self) -> List[str]: """Get all the model names stored. Return: @@ -59,7 +59,9 @@ def get_all_model_names(self): def _get_key_for_object(self, modelname, uid): return f"{self._store_label}:{modelname}:{uid}" - def get(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping]): + def get( + self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: @@ -233,10 +235,10 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, modelname=None): + def count(self, modelname=None) -> int: """Returns the number of elements of an specific model name.""" search_pattern = f"{self._store_label}:*" if modelname: search_pattern = f"{self._store_label}:{modelname.lower()}:*" - return sum([1 for _ in self._store.scan_iter(search_pattern)]) + return len(self._store.scan_iter(search_pattern)) From b44a74c4066889f1a3f1c48257ef0fae0dc9204f Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 12:37:59 +0200 Subject: [PATCH 12/20] Remove positional argument in internal store apis --- .github/workflows/ci.yml | 2 +- diffsync/__init__.py | 8 +++--- diffsync/store/__init__.py | 39 +++++++++++++------------ diffsync/store/local.py | 54 +++++++++++++++++------------------ diffsync/store/redis.py | 54 +++++++++++++++++------------------ tests/unit/test_redisstore.py | 28 +++++++++--------- 6 files changed, 94 insertions(+), 91 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99987134..3ff946fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ --- name: "CI" -on: +on: # yamllint disable-line rule:truthy - "push" - "pull_request" jobs: diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 8a6e1240..826e2b8d 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -469,7 +469,7 @@ def dict(self, exclude_defaults: bool = True, **kwargs) -> Mapping: data: Dict[str, Dict[str, Dict]] = {} for modelname in self.store.get_all_model_names(): data[modelname] = {} - for obj in self.store.get_all(modelname): + for obj in self.store.get_all(model=modelname): data[obj.get_type()][obj.get_unique_id()] = obj.dict(exclude_defaults=exclude_defaults, **kwargs) return data @@ -636,7 +636,7 @@ def get( ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - return self.store.get(obj=obj, identifier=identifier) + return self.store.get(model=obj, identifier=identifier) def get_all(self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]]) -> List[DiffSyncModel]: """Get all objects of a given type. @@ -647,7 +647,7 @@ def get_all(self, obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]]) -> List[ Returns: List[DiffSyncModel]: List of Object """ - return self.store.get_all(obj=obj) + return self.store.get_all(model=obj) def get_by_uids( self, uids: List[Text], obj: Union[Text, DiffSyncModel, Type[DiffSyncModel]] @@ -661,7 +661,7 @@ def get_by_uids( Raises: ObjectNotFound: if any of the requested UIDs are not found in the store """ - return self.store.get_by_uids(uids=uids, obj=obj) + return self.store.get_by_uids(uids=uids, model=obj) def add(self, obj: DiffSyncModel): """Add a DiffSyncModel object to the store. diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index b3681427..1c864de8 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -1,17 +1,20 @@ """BaseStore module.""" -from typing import Dict, List, Mapping, Text, Tuple, Type, Union, TYPE_CHECKING +from typing import Dict, List, Mapping, Text, Tuple, Type, Union, TYPE_CHECKING, Optional import structlog # type: ignore from diffsync.exceptions import ObjectNotFound if TYPE_CHECKING: from diffsync import DiffSyncModel + from diffsync import DiffSync class BaseStore: """Reference store to be implemented in different backends.""" - def __init__(self, *args, diffsync: Optional["DiffSync"] = None, name: str = "", **kwargs) -> None: # pylint: disable=unused-argument + def __init__( + self, *args, diffsync: Optional["DiffSync"] = None, name: str = "", **kwargs # pylint: disable=unused-argument + ) -> None: """Init method for BaseStore.""" self.diffsync = diffsync self.name = name or self.__class__.__name__ @@ -30,12 +33,12 @@ def get_all_model_names(self) -> List[str]: raise NotImplementedError def get( - self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values Raises: @@ -44,11 +47,11 @@ def get( """ raise NotImplementedError - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + def get_all(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Returns: List[DiffSyncModel]: List of Object @@ -56,20 +59,20 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L raise NotImplementedError def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + self, *, uids: List[Text], model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: uids: List of unique id / key identifying object in the database. - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Raises: ObjectNotFound: if any of the requested UIDs are not found in the store """ raise NotImplementedError - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: @@ -81,7 +84,7 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): """ raise NotImplementedError - def add(self, obj: "DiffSyncModel"): + def add(self, *, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -92,7 +95,7 @@ def add(self, obj: "DiffSyncModel"): """ raise NotImplementedError - def update(self, obj: "DiffSyncModel"): + def update(self, *, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -100,12 +103,12 @@ def update(self, obj: "DiffSyncModel"): """ raise NotImplementedError - def count(self, modelname=None) -> int: + def count(self, *, modelname=None) -> int: """Returns the number of elements of a specific model, or all elements in the store if not specified.""" raise NotImplementedError def get_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None + self, *, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict = None ) -> Tuple["DiffSyncModel", bool]: """Attempt to get the object with provided identifiers or instantiate it with provided identifiers and attrs. @@ -119,19 +122,19 @@ def get_or_instantiate( """ created = False try: - obj = self.get(model, ids) + obj = self.get(model=model, identifier=ids) except ObjectNotFound: if not attrs: attrs = {} obj = model(**ids, **attrs) # Add the object to diffsync adapter - self.add(obj) + self.add(obj=obj) created = True return obj, created def update_or_instantiate( - self, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict + self, *, model: Type["DiffSyncModel"], ids: Dict, attrs: Dict ) -> Tuple["DiffSyncModel", bool]: """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. @@ -145,11 +148,11 @@ def update_or_instantiate( """ created = False try: - obj = self.get(model, ids) + obj = self.get(model=model, identifier=ids) except ObjectNotFound: obj = model(**ids, **attrs) # Add the object to diffsync adapter - self.add(obj) + self.add(obj=obj) created = True # Update existing obj with attrs diff --git a/diffsync/store/local.py b/diffsync/store/local.py index d4be6324..f8b5d544 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -26,30 +26,30 @@ def get_all_model_names(self) -> List[str]: Return: List[str]: List of all the model names. """ - return self._data.keys() + return list(self._data.keys()) def get( - self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values Raises: ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - if isinstance(obj, str): - modelname = obj - if not hasattr(self, obj): + if isinstance(model, str): + modelname = model + if not hasattr(self, model): object_class = None else: - object_class = getattr(self, obj) + object_class = getattr(self, model) else: - object_class = obj - modelname = obj.get_type() + object_class = model + modelname = model.get_type() if isinstance(identifier, str): uid = identifier @@ -57,46 +57,46 @@ def get( uid = object_class.create_unique_id(**identifier) else: raise ValueError( - f"Invalid args: ({obj}, {identifier}): " - f"either {obj} should be a class/instance or {identifier} should be a str" + f"Invalid args: ({model}, {identifier}): " + f"either {model} should be a class/instance or {identifier} should be a str" ) if uid not in self._data[modelname]: raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") return self._data[modelname][uid] - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + def get_all(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Returns: List[DiffSyncModel]: List of Object """ - if isinstance(obj, str): - modelname = obj + if isinstance(model, str): + modelname = model else: - modelname = obj.get_type() + modelname = model.get_type() return list(self._data[modelname].values()) def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + self, *, uids: List[Text], model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: uids: List of unique id / key identifying object in the database. - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Raises: ObjectNotFound: if any of the requested UIDs are not found in the store """ - if isinstance(obj, str): - modelname = obj + if isinstance(model, str): + modelname = model else: - modelname = obj.get_type() + modelname = model.get_type() results = [] for uid in uids: @@ -105,7 +105,7 @@ def get_by_uids( results.append(self._data[modelname][uid]) return results - def add(self, obj: "DiffSyncModel"): + def add(self, *, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -129,7 +129,7 @@ def add(self, obj: "DiffSyncModel"): self._data[modelname][uid] = obj - def update(self, obj: "DiffSyncModel"): + def update(self, *, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -144,7 +144,7 @@ def update(self, obj: "DiffSyncModel"): self._data[modelname][uid] = obj - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: @@ -169,13 +169,13 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): for child_type, child_fieldname in obj.get_children_mapping().items(): for child_id in getattr(obj, child_fieldname): try: - child_obj = self.get(child_type, child_id) - self.remove(child_obj, remove_children=remove_children) + child_obj = self.get(model=child_type, identifier=child_id) + self.remove(obj=child_obj, remove_children=remove_children) except ObjectNotFound: # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, modelname=None) -> int: + def count(self, *, modelname=None) -> int: """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" if not modelname: return sum(len(entries) for entries in self._data.values()) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 41650579..50a54698 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -60,27 +60,27 @@ def _get_key_for_object(self, modelname, uid): return f"{self._store_label}:{modelname}:{uid}" def get( - self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] + self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] ) -> "DiffSyncModel": """Get one object from the data store based on its unique id. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the object to retrieve identifier: Unique ID of the object to retrieve, or dict of unique identifier keys/values Raises: ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - if isinstance(obj, str): - modelname = obj - if not hasattr(self, obj): + if isinstance(model, str): + modelname = model + if not hasattr(self, model): object_class = None else: - object_class = getattr(self, obj) + object_class = getattr(self, model) else: - object_class = obj - modelname = obj.get_type() + object_class = model + modelname = model.get_type() if isinstance(identifier, str): uid = identifier @@ -88,8 +88,8 @@ def get( uid = object_class.create_unique_id(**identifier) else: raise ValueError( - f"Invalid args: ({obj}, {identifier}): " - f"either {obj} should be a class/instance or {identifier} should be a str" + f"Invalid args: ({model}, {identifier}): " + f"either {model} should be a class/instance or {identifier} should be a str" ) try: @@ -100,19 +100,19 @@ def get( return obj_result - def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: + def get_all(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. Args: - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Returns: List[DiffSyncModel]: List of Object """ - if isinstance(obj, str): - modelname = obj + if isinstance(model, str): + modelname = model else: - modelname = obj.get_type() + modelname = model.get_type() results = [] for key in self._store.scan_iter(f"{self._store_label}:{modelname}:*"): @@ -126,21 +126,21 @@ def get_all(self, obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> L return results def get_by_uids( - self, uids: List[Text], obj: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + self, *, uids: List[Text], model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] ) -> List["DiffSyncModel"]: """Get multiple objects from the store by their unique IDs/Keys and type. Args: uids: List of unique id / key identifying object in the database. - obj: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve + model: DiffSyncModel class or instance, or modelname string, that defines the type of the objects to retrieve Raises: ObjectNotFound: if any of the requested UIDs are not found in the store """ - if isinstance(obj, str): - modelname = obj + if isinstance(model, str): + modelname = model else: - modelname = obj.get_type() + modelname = model.get_type() results = [] for uid in uids: @@ -154,7 +154,7 @@ def get_by_uids( return results - def add(self, obj: "DiffSyncModel"): + def add(self, *, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. Args: @@ -186,7 +186,7 @@ def add(self, obj: "DiffSyncModel"): self._store.set(object_key, dumps(obj_copy)) - def update(self, obj: "DiffSyncModel"): + def update(self, *, obj: "DiffSyncModel"): """Update a DiffSyncModel object to the store. Args: @@ -201,7 +201,7 @@ def update(self, obj: "DiffSyncModel"): self._store.set(object_key, dumps(obj_copy)) - def remove(self, obj: "DiffSyncModel", remove_children: bool = False): + def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. Args: @@ -228,17 +228,17 @@ def remove(self, obj: "DiffSyncModel", remove_children: bool = False): for child_type, child_fieldname in obj.get_children_mapping().items(): for child_id in getattr(obj, child_fieldname): try: - child_obj = self.get(child_type, child_id) - self.remove(child_obj, remove_children=remove_children) + child_obj = self.get(model=child_type, identifier=child_id) + self.remove(obj=child_obj, remove_children=remove_children) except ObjectNotFound: pass # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, modelname=None) -> int: + def count(self, *, modelname=None) -> int: """Returns the number of elements of an specific model name.""" search_pattern = f"{self._store_label}:*" if modelname: search_pattern = f"{self._store_label}:{modelname.lower()}:*" - return len(self._store.scan_iter(search_pattern)) + return len(list(self._store.scan_iter(search_pattern))) diff --git a/tests/unit/test_redisstore.py b/tests/unit/test_redisstore.py index 62558969..fd3c6d36 100644 --- a/tests/unit/test_redisstore.py +++ b/tests/unit/test_redisstore.py @@ -6,7 +6,7 @@ def test_redisstore_init(redis_url): store = RedisStore(name="mystore", store_id="123", url=redis_url) - assert str(store) == "mystore(123)" + assert str(store) == "mystore (123)" def test_redisstore_init_wrong(): @@ -17,46 +17,46 @@ def test_redisstore_init_wrong(): def test_redisstore_add_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) + store.add(obj=site) assert store.count() == 1 def test_redisstore_add_obj_twice(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) - store.add(site) + store.add(obj=site) + store.add(obj=site) assert store.count() == 1 def test_redisstore_get_all_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) - assert store.get_all(site.__class__)[0] == site + store.add(obj=site) + assert store.get_all(model=site.__class__)[0] == site def test_redisstore_get_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) - assert store.get(site.__class__, site.name) == site + store.add(obj=site) + assert store.get(model=site.__class__, identifier=site.name) == site def test_redisstore_remove_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) - assert store.count(site.__class__.__name__) == store.count() == 1 - store.remove(site) - assert store.count(site.__class__.__name__) == store.count() == 0 + store.add(obj=site) + assert store.count(modelname=site.__class__.__name__) == store.count() == 1 + store.remove(obj=site) + assert store.count(modelname=site.__class__.__name__) == store.count() == 0 def test_redisstore_get_all_model_names(redis_url, make_site, make_device): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() - store.add(site) + store.add(obj=site) device = make_device() - store.add(device) + store.add(obj=device) assert site.get_type() in store.get_all_model_names() assert device.get_type() in store.get_all_model_names() From 7ba011eec4f686547c6d81e5b81ff2ed32ddad89 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 14:45:31 +0200 Subject: [PATCH 13/20] Make count api similar to get ones --- .github/workflows/ci.yml | 2 +- diffsync/__init__.py | 6 +++--- diffsync/store/__init__.py | 2 +- diffsync/store/local.py | 8 ++++++-- diffsync/store/redis.py | 10 +++++++--- tests/unit/test_redisstore.py | 4 ++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ff946fe..954ac521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ --- name: "CI" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy - "push" - "pull_request" jobs: diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 826e2b8d..21919612 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -725,16 +725,16 @@ def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Di """ return self.store.update_or_instantiate(model=model, ids=ids, attrs=attrs) - def count(self, modelname=None): + def count(self, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None): """Count how many objects of one model type exist in the backend store. Args: - modelname (str): The model name to check the number of elements. If not provided, default to all. + model (DiffSyncModel): The DiffSyncModel to check the number of elements. If not provided, default to all. Returns: Int: Number of elements of the model type """ - return self.store.count(modelname=modelname) + return self.store.count(model=model) # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 1c864de8..1f16380c 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -103,7 +103,7 @@ def update(self, *, obj: "DiffSyncModel"): """ raise NotImplementedError - def count(self, *, modelname=None) -> int: + def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: """Returns the number of elements of a specific model, or all elements in the store if not specified.""" raise NotImplementedError diff --git a/diffsync/store/local.py b/diffsync/store/local.py index f8b5d544..e987ce25 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -175,9 +175,13 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, *, modelname=None) -> int: + def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" - if not modelname: + if not model: return sum(len(entries) for entries in self._data.values()) + if isinstance(model, str): + modelname = model + else: + modelname = model.get_type() return len(self._data[modelname]) diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 50a54698..2a1130d9 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -235,10 +235,14 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, *, modelname=None) -> int: - """Returns the number of elements of an specific model name.""" + def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: + """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" search_pattern = f"{self._store_label}:*" - if modelname: + if model: + if isinstance(model, str): + modelname = model + else: + modelname = model.get_type() search_pattern = f"{self._store_label}:{modelname.lower()}:*" return len(list(self._store.scan_iter(search_pattern))) diff --git a/tests/unit/test_redisstore.py b/tests/unit/test_redisstore.py index fd3c6d36..7b1f1cb3 100644 --- a/tests/unit/test_redisstore.py +++ b/tests/unit/test_redisstore.py @@ -47,9 +47,9 @@ def test_redisstore_remove_obj(redis_url, make_site): store = RedisStore(name="mystore", store_id="123", url=redis_url) site = make_site() store.add(obj=site) - assert store.count(modelname=site.__class__.__name__) == store.count() == 1 + assert store.count(model=site.__class__) == store.count() == 1 store.remove(obj=site) - assert store.count(modelname=site.__class__.__name__) == store.count() == 0 + assert store.count(model=site.__class__) == store.count() == 0 def test_redisstore_get_all_model_names(redis_url, make_site, make_device): From 542ec091537c5fe6ab116fdce519edaad9c584a1 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 15:02:09 +0200 Subject: [PATCH 14/20] Make url and host exclusive, return set for get_all_model_names --- diffsync/store/__init__.py | 6 +++--- diffsync/store/local.py | 8 ++++---- diffsync/store/redis.py | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 1f16380c..cc1b8bd0 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -1,5 +1,5 @@ """BaseStore module.""" -from typing import Dict, List, Mapping, Text, Tuple, Type, Union, TYPE_CHECKING, Optional +from typing import Dict, List, Mapping, Text, Tuple, Type, Union, TYPE_CHECKING, Optional, Set import structlog # type: ignore from diffsync.exceptions import ObjectNotFound @@ -24,11 +24,11 @@ def __str__(self): """Render store name.""" return self.name - def get_all_model_names(self) -> List[str]: + def get_all_model_names(self) -> Set[str]: """Get all the model names stored. Return: - List[str]: List of all the model names. + Set[str]: Set of all the model names. """ raise NotImplementedError diff --git a/diffsync/store/local.py b/diffsync/store/local.py index e987ce25..c8fa594a 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -1,7 +1,7 @@ """LocalStore module.""" from collections import defaultdict -from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING, Dict +from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING, Dict, Set from diffsync.exceptions import ObjectNotFound, ObjectAlreadyExists from diffsync.store import BaseStore @@ -20,13 +20,13 @@ def __init__(self, *args, **kwargs) -> None: self._data: Dict = defaultdict(dict) - def get_all_model_names(self) -> List[str]: + def get_all_model_names(self) -> Set[str]: """Get all the model names stored. Return: - List[str]: List of all the model names. + Set[str]: Set of all the model names. """ - return list(self._data.keys()) + return set(self._data.keys()) def get( self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], identifier: Union[Text, Mapping] diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 2a1130d9..8881a0d1 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -2,7 +2,7 @@ import copy import uuid from pickle import loads, dumps # nosec -from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING +from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING, Set from redis import Redis from redis.exceptions import ConnectionError as RedisConnectionError @@ -18,10 +18,13 @@ class RedisStore(BaseStore): """RedisStore class.""" - def __init__(self, *args, store_id=None, host="localhost", port=6379, url=None, db=0, **kwargs): + def __init__(self, *args, store_id=None, host=None, port=6379, url=None, db=0, **kwargs): """Init method for RedisStore.""" super().__init__(*args, **kwargs) + if url and host and port: + raise ValueError("'url' argument can't be specified together with 'host' and 'port' ones.") + try: if url: self._store = Redis.from_url(url, db=db) @@ -33,7 +36,7 @@ def __init__(self, *args, store_id=None, host="localhost", port=6379, url=None, except RedisConnectionError: raise ObjectStoreException("Redis store is unavailable.") from RedisConnectionError - self._store_id = store_id if store_id else str(uuid.uuid4())[:8] + self._store_id = store_id if store_id else str(uuid.uuid4()) self._store_label = f"{REDIS_DIFFSYNC_ROOT_LABEL}:{self._store_id}" @@ -41,18 +44,19 @@ def __str__(self): """Render store name.""" return f"{self.name} ({self._store_id})" - def get_all_model_names(self) -> List[str]: + def get_all_model_names(self) -> Set[str]: """Get all the model names stored. Return: - List[str]: List of all the model names. + Set[str]: Set of all the model names. """ # TODO: optimize it - all_model_names = [] + all_model_names = set() for item in self._store.scan_iter(f"{self._store_label}:*"): + # Model Name is the third item in the Redis key + # b'diffsync:123:device:device1' -> Model name b'device' model_name = item.split(b":")[2].decode() - if model_name not in all_model_names: - all_model_names.append(model_name) + all_model_names.add(model_name) return all_model_names From f78445651161af70c66e2aee2ba1cd8f88fcf3b7 Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 18:31:50 +0200 Subject: [PATCH 15/20] refactor --- diffsync/store/__init__.py | 33 ++++++++++++++++++++++++ diffsync/store/local.py | 20 ++------------- diffsync/store/redis.py | 52 ++++++++++---------------------------- 3 files changed, 49 insertions(+), 56 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index cc1b8bd0..036cefdc 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -161,3 +161,36 @@ def update_or_instantiate( setattr(obj, attr, value) return obj, created + + def _get_object_class_and_model( + self, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]] + ) -> Tuple[Union["DiffSyncModel", Type["DiffSyncModel"], None], str]: + """Get object class and model name for a model.""" + if isinstance(model, str): + modelname = model + if not hasattr(self, model): + return None, modelname + object_class = getattr(self, model) + else: + object_class = model + modelname = model.get_type() + + return object_class, modelname + + @staticmethod + def _get_uid( + model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]], + object_class: Union["DiffSyncModel", Type["DiffSyncModel"], None], + identifier: Union[Text, Mapping], + ) -> str: + """Get the related uid for a model and an identifier.""" + if isinstance(identifier, str): + uid = identifier + elif object_class: + uid = object_class.create_unique_id(**identifier) + else: + raise ValueError( + f"Invalid args: ({model}, {identifier}): " + f"either {model} should be a class/instance or {identifier} should be a str" + ) + return uid diff --git a/diffsync/store/local.py b/diffsync/store/local.py index c8fa594a..641c646a 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -41,25 +41,9 @@ def get( ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - if isinstance(model, str): - modelname = model - if not hasattr(self, model): - object_class = None - else: - object_class = getattr(self, model) - else: - object_class = model - modelname = model.get_type() + object_class, modelname = self._get_object_class_and_model(model) - if isinstance(identifier, str): - uid = identifier - elif object_class: - uid = object_class.create_unique_id(**identifier) - else: - raise ValueError( - f"Invalid args: ({model}, {identifier}): " - f"either {model} should be a class/instance or {identifier} should be a str" - ) + uid = self._get_uid(model, object_class, identifier) if uid not in self._data[modelname]: raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index 8881a0d1..c0993e95 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -44,6 +44,15 @@ def __str__(self): """Render store name.""" return f"{self.name} ({self._store_id})" + def _get_object_from_redis_key(self, key): + """Get the object from Redis key""" + try: + obj_result = loads(self._store.get(key)) # nosec + obj_result.diffsync = self.diffsync + return obj_result + except TypeError as exc: + raise ObjectNotFound(f"{key} not present in Cache") from exc + def get_all_model_names(self) -> Set[str]: """Get all the model names stored. @@ -76,33 +85,11 @@ def get( ValueError: if obj is a str and identifier is a dict (can't convert dict into a uid str without a model class) ObjectNotFound: if the requested object is not present """ - if isinstance(model, str): - modelname = model - if not hasattr(self, model): - object_class = None - else: - object_class = getattr(self, model) - else: - object_class = model - modelname = model.get_type() - - if isinstance(identifier, str): - uid = identifier - elif object_class: - uid = object_class.create_unique_id(**identifier) - else: - raise ValueError( - f"Invalid args: ({model}, {identifier}): " - f"either {model} should be a class/instance or {identifier} should be a str" - ) + object_class, modelname = self._get_object_class_and_model(model) - try: - obj_result = loads(self._store.get(self._get_key_for_object(modelname, uid))) # nosec - obj_result.diffsync = self.diffsync - except TypeError: - raise ObjectNotFound(f"{modelname} {uid} not present in Cache") # pylint: disable=raise-missing-from + uid = self._get_uid(model, object_class, identifier) - return obj_result + return self._get_object_from_redis_key(self._get_key_for_object(modelname, uid)) def get_all(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) -> List["DiffSyncModel"]: """Get all objects of a given type. @@ -120,12 +107,7 @@ def get_all(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"]]) results = [] for key in self._store.scan_iter(f"{self._store_label}:{modelname}:*"): - try: - obj_result = loads(self._store.get(key)) # nosec - obj_result.diffsync = self.diffsync - results.append(obj_result) - except TypeError: - raise ObjectNotFound(f"{key} not present in Cache") # pylint: disable=raise-missing-from + results.append(self._get_object_from_redis_key(key)) return results @@ -148,13 +130,7 @@ def get_by_uids( results = [] for uid in uids: - - try: - obj_result = loads(self._store.get(self._get_key_for_object(modelname, uid))) # nosec - obj_result.diffsync = self.diffsync - results.append(obj_result) - except TypeError: - raise ObjectNotFound(f"{modelname} {uid} not present in Cache") # pylint: disable=raise-missing-from + results.append(self._get_object_from_redis_key(self._get_key_for_object(modelname, uid))) return results From 825254c00204d956228268347a970220a135179c Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 19:03:27 +0200 Subject: [PATCH 16/20] A bit more of refactoring --- diffsync/store/__init__.py | 22 +++++++++++++++++++++- diffsync/store/local.py | 29 ++--------------------------- diffsync/store/redis.py | 31 +++---------------------------- 3 files changed, 26 insertions(+), 56 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 036cefdc..67132da0 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -72,6 +72,10 @@ def get_by_uids( """ raise NotImplementedError + def _remove_item(self, modelname: str, uid: str): + """Remove one item from store.""" + raise NotImplementedError + def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): """Remove a DiffSyncModel object from the store. @@ -82,7 +86,23 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): Raises: ObjectNotFound: if the object is not present """ - raise NotImplementedError + modelname = obj.get_type() + uid = obj.get_unique_id() + + self._remove_item(modelname, uid) + + if obj.diffsync: + obj.diffsync = None + + if remove_children: + for child_type, child_fieldname in obj.get_children_mapping().items(): + for child_id in getattr(obj, child_fieldname): + try: + child_obj = self.get(model=child_type, identifier=child_id) + self.remove(obj=child_obj, remove_children=remove_children) + except ObjectNotFound: + # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise + self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") def add(self, *, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 641c646a..527ef4bd 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -128,37 +128,12 @@ def update(self, *, obj: "DiffSyncModel"): self._data[modelname][uid] = obj - def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): - """Remove a DiffSyncModel object from the store. - - Args: - obj (DiffSyncModel): object to remove - remove_children (bool): If True, also recursively remove any children of this object - - Raises: - ObjectNotFound: if the object is not present - """ - modelname = obj.get_type() - uid = obj.get_unique_id() - + def _remove_item(self, modelname: str, uid: str): + """Remove one item from store.""" if uid not in self._data[modelname]: raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") - - if obj.diffsync: - obj.diffsync = None - del self._data[modelname][uid] - if remove_children: - for child_type, child_fieldname in obj.get_children_mapping().items(): - for child_id in getattr(obj, child_fieldname): - try: - child_obj = self.get(model=child_type, identifier=child_id) - self.remove(obj=child_obj, remove_children=remove_children) - except ObjectNotFound: - # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise - self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" if not model: diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index c0993e95..f2f5ed9b 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -45,7 +45,7 @@ def __str__(self): return f"{self.name} ({self._store_id})" def _get_object_from_redis_key(self, key): - """Get the object from Redis key""" + """Get the object from Redis key.""" try: obj_result = loads(self._store.get(key)) # nosec obj_result.diffsync = self.diffsync @@ -181,40 +181,15 @@ def update(self, *, obj: "DiffSyncModel"): self._store.set(object_key, dumps(obj_copy)) - def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): - """Remove a DiffSyncModel object from the store. - - Args: - obj (DiffSyncModel): object to remove - remove_children (bool): If True, also recursively remove any children of this object - - Raises: - ObjectNotFound: if the object is not present - """ - modelname = obj.get_type() - uid = obj.get_unique_id() - + def _remove_item(self, modelname: str, uid: str): + """Remove one item from store.""" object_key = self._get_key_for_object(modelname, uid) if not self._store.exists(object_key): raise ObjectNotFound(f"{modelname} {uid} not present in Cache") - if obj.diffsync: - obj.diffsync = None - self._store.delete(object_key) - if remove_children: - for child_type, child_fieldname in obj.get_children_mapping().items(): - for child_id in getattr(obj, child_fieldname): - try: - child_obj = self.get(model=child_type, identifier=child_id) - self.remove(obj=child_obj, remove_children=remove_children) - except ObjectNotFound: - pass - # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise - # self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") - def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" search_pattern = f"{self._store_label}:*" From b7016c9d16efd9b1fdeb3cc05f5ae269fac765ef Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Wed, 18 May 2022 19:15:33 +0200 Subject: [PATCH 17/20] Make redis an optional dependency --- docs/source/core_engine/03-store.md | 2 ++ poetry.lock | 21 ++++++++++++--------- pyproject.toml | 7 +++++-- tests/unit/test_diff_element.py | 2 +- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/source/core_engine/03-store.md b/docs/source/core_engine/03-store.md index 3a354e05..7a4a7bd1 100644 --- a/docs/source/core_engine/03-store.md +++ b/docs/source/core_engine/03-store.md @@ -17,6 +17,8 @@ When you initialize the `Diffsync` Adapter class, there is an optional keyed-arg ## Use the `RedisStore` Backend +To get it, you have to install diffsync package with the "redis" extra option: `pip install diffsync[redis]` + The `RedisStore` backend, as the name suggests, connects to an external Redis service, to store data loaded by the `DiffSync` tasks. The biggest change is that it requires to initialize the Redis store class, before using it in the `DiffSync` adapter class. ```python diff --git a/poetry.lock b/poetry.lock index a96c4532..96b8748f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -510,14 +510,14 @@ python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.13.8" +version = "2.13.9" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.11.3,<=2.12.0-dev0" +astroid = ">=2.11.5,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} dill = ">=0.2" isort = ">=4.2.5,<6" @@ -858,9 +858,9 @@ python-versions = ">=3.6" [[package]] name = "types-redis" -version = "4.2.2" +version = "4.2.3" description = "Typing stubs for redis" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -925,10 +925,13 @@ python-versions = ">=3.6" docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +[extras] +redis = ["redis"] + [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "a9546361ba2baaff8a37a734285b46c3caa244eb7ca917a067711967dcad7c93" +content-hash = "8a60786f23a2035981c3d3e8051ebfb77c5fcf3e0c3f9faff20e676a49f87fd6" [metadata.files] alabaster = [ @@ -1333,8 +1336,8 @@ pygments = [ {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pylint = [ - {file = "pylint-2.13.8-py3-none-any.whl", hash = "sha256:f87e863a0b08f64b5230e7e779bcb75276346995737b2c0dc2793070487b1ff6"}, - {file = "pylint-2.13.8.tar.gz", hash = "sha256:ced8968c3b699df0615e2a709554dec3ddac2f5cd06efadb69554a69eeca364a"}, + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, ] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, @@ -1501,8 +1504,8 @@ typed-ast = [ {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, ] types-redis = [ - {file = "types-redis-4.2.2.tar.gz", hash = "sha256:616e5331e957832321c14a4bd206b41f967657cf360f62419b21588880af3933"}, - {file = "types_redis-4.2.2-py3-none-any.whl", hash = "sha256:8035a2631591ea2d0484f979f1424789ed757e72b56f0c5d6291a9484540e9c0"}, + {file = "types-redis-4.2.3.tar.gz", hash = "sha256:05fe5b38b24f4adae8d9780f90d5883066246251a41f329d5b19e3861fd8ab60"}, + {file = "types_redis-4.2.3-py3-none-any.whl", hash = "sha256:cf767df39b0e36f6ae256fb251a05f3e4ca5cb3b1ba62d7a7921855f62f68070"}, ] types-toml = [ {file = "types-toml-0.10.7.tar.gz", hash = "sha256:a567fe2614b177d537ad99a661adc9bfc8c55a46f95e66370a4ed2dd171335f9"}, diff --git a/pyproject.toml b/pyproject.toml index c014aef0..af66ca5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,10 @@ packaging = "^21.3" colorama = {version = "^0.4.3", optional = true} # For Pydantic dataclasses = {version = "^0.7", python = "~3.6"} -redis = "*" -types-redis = "*" +redis = {version = "^4.3", optional = true} + +[tool.poetry.extras] +redis = ["redis"] [tool.poetry.dev-dependencies] pytest = "*" @@ -45,6 +47,7 @@ m2r2 = "*" sphinx-rtd-theme = "*" toml = "*" types-toml = "*" +types-redis = "*" pytest-redislite = { version = "^0.1.0", python = "^3.7"} [tool.black] diff --git a/tests/unit/test_diff_element.py b/tests/unit/test_diff_element.py index 943b0f91..9d44290d 100644 --- a/tests/unit/test_diff_element.py +++ b/tests/unit/test_diff_element.py @@ -33,7 +33,7 @@ def test_diff_element_empty(): assert not element.has_diffs() assert not element.has_diffs(include_children=True) assert not element.has_diffs(include_children=False) - assert not element.get_attrs_keys() + assert element.get_attrs_keys() == [] element2 = DiffElement( "interface", "eth0", {"device_name": "device1", "name": "eth0"}, source_name="S1", dest_name="D1" From 02f136ee3260e0b91bf8a1120e587af9685aab3c Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Thu, 19 May 2022 09:16:55 +0200 Subject: [PATCH 18/20] Use proper structlog key for store --- diffsync/store/__init__.py | 2 +- diffsync/store/redis.py | 2 +- tests/unit/test_diffsync.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 67132da0..6f3f514d 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -18,7 +18,7 @@ def __init__( """Init method for BaseStore.""" self.diffsync = diffsync self.name = name or self.__class__.__name__ - self._log = structlog.get_logger().new(diffsync=self) + self._log = structlog.get_logger().new(store=self) def __str__(self): """Render store name.""" diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index f2f5ed9b..a9bdcae3 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -193,7 +193,7 @@ def _remove_item(self, modelname: str, uid: str): def count(self, *, model: Union[Text, "DiffSyncModel", Type["DiffSyncModel"], None] = None) -> int: """Returns the number of elements of a specific model, or all elements in the store if unspecified.""" search_pattern = f"{self._store_label}:*" - if model: + if model is not None: if isinstance(model, str): modelname = model else: diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index f540b777..4f910aa3 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -738,9 +738,7 @@ def test_diffsync_remove_missing_child(log, backend_a): backend_a.remove(rdu_spine1_eth0) # Should log an error but continue removing other child objects backend_a.remove(rdu_spine1, remove_children=True) - assert log.has( - "Unable to remove child rdu-spine1__eth0 of device rdu-spine1 - not found!", diffsync=backend_a.store - ) + assert log.has("Unable to remove child rdu-spine1__eth0 of device rdu-spine1 - not found!", store=backend_a.store) with pytest.raises(ObjectNotFound): backend_a.get(Interface, "rdu-spine1__eth1") From 899bc23787fa7c9a22563c7b77b22d8a8a6e52ff Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Fri, 20 May 2022 09:44:26 +0200 Subject: [PATCH 19/20] PR review contributions --- diffsync/store/__init__.py | 12 +++++++++--- diffsync/store/local.py | 2 +- diffsync/store/redis.py | 13 +++++++++---- tests/unit/test_diffsync.py | 9 ++++++++- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/diffsync/store/__init__.py b/diffsync/store/__init__.py index 6f3f514d..9f234afc 100644 --- a/diffsync/store/__init__.py +++ b/diffsync/store/__init__.py @@ -72,7 +72,7 @@ def get_by_uids( """ raise NotImplementedError - def _remove_item(self, modelname: str, uid: str): + def remove_item(self, modelname: str, uid: str): """Remove one item from store.""" raise NotImplementedError @@ -89,7 +89,7 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): modelname = obj.get_type() uid = obj.get_unique_id() - self._remove_item(modelname, uid) + self.remove_item(modelname, uid) if obj.diffsync: obj.diffsync = None @@ -102,7 +102,13 @@ def remove(self, *, obj: "DiffSyncModel", remove_children: bool = False): self.remove(obj=child_obj, remove_children=remove_children) except ObjectNotFound: # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise - self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + self._log.error( + "Unable to remove child element as it was not found!", + child_type=child_type, + child_id=child_id, + parent_type=modelname, + parent_id=uid, + ) def add(self, *, obj: "DiffSyncModel"): """Add a DiffSyncModel object to the store. diff --git a/diffsync/store/local.py b/diffsync/store/local.py index 527ef4bd..eaf6fe5a 100644 --- a/diffsync/store/local.py +++ b/diffsync/store/local.py @@ -128,7 +128,7 @@ def update(self, *, obj: "DiffSyncModel"): self._data[modelname][uid] = obj - def _remove_item(self, modelname: str, uid: str): + def remove_item(self, modelname: str, uid: str): """Remove one item from store.""" if uid not in self._data[modelname]: raise ObjectNotFound(f"{modelname} {uid} not present in {str(self)}") diff --git a/diffsync/store/redis.py b/diffsync/store/redis.py index a9bdcae3..83ca68cb 100644 --- a/diffsync/store/redis.py +++ b/diffsync/store/redis.py @@ -4,8 +4,13 @@ from pickle import loads, dumps # nosec from typing import List, Mapping, Text, Type, Union, TYPE_CHECKING, Set -from redis import Redis -from redis.exceptions import ConnectionError as RedisConnectionError +try: + from redis import Redis + from redis.exceptions import ConnectionError as RedisConnectionError +except ImportError as ierr: + print("Redis is not installed. Have you installed diffsync with redis extra? `pip install diffsync[redis]`") + raise ierr + from diffsync.exceptions import ObjectNotFound, ObjectStoreException, ObjectAlreadyExists from diffsync.store import BaseStore @@ -23,7 +28,7 @@ def __init__(self, *args, store_id=None, host=None, port=6379, url=None, db=0, * super().__init__(*args, **kwargs) if url and host and port: - raise ValueError("'url' argument can't be specified together with 'host' and 'port' ones.") + raise ValueError("'url' and 'host' arguments can't be specified together.") try: if url: @@ -181,7 +186,7 @@ def update(self, *, obj: "DiffSyncModel"): self._store.set(object_key, dumps(obj_copy)) - def _remove_item(self, modelname: str, uid: str): + def remove_item(self, modelname: str, uid: str): """Remove one item from store.""" object_key = self._get_key_for_object(modelname, uid) diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 4f910aa3..9e8ccb33 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -738,7 +738,14 @@ def test_diffsync_remove_missing_child(log, backend_a): backend_a.remove(rdu_spine1_eth0) # Should log an error but continue removing other child objects backend_a.remove(rdu_spine1, remove_children=True) - assert log.has("Unable to remove child rdu-spine1__eth0 of device rdu-spine1 - not found!", store=backend_a.store) + assert log.has( + "Unable to remove child element as it was not found!", + store=backend_a.store, + parent_id=str(rdu_spine1), + parent_type=rdu_spine1.get_type(), + child_id=str(rdu_spine1_eth0), + child_type=rdu_spine1_eth0.get_type(), + ) with pytest.raises(ObjectNotFound): backend_a.get(Interface, "rdu-spine1__eth1") From f054e9e9532d689961b03b1a5b005b10657799ec Mon Sep 17 00:00:00 2001 From: Christian Adell Date: Fri, 20 May 2022 09:57:56 +0200 Subject: [PATCH 20/20] Update build-system in pyproject to support PEP 660 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index af66ca5d..58ec6e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,5 +102,5 @@ testpaths = [ ] [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry_core>=1.0.8"] +build-backend = "poetry.core.masonry.api"