From d99a9b36ef13a6520312cac1e6f59b48cb1d6117 Mon Sep 17 00:00:00 2001 From: Lucidiot Date: Mon, 7 Oct 2019 06:18:45 +0200 Subject: [PATCH] Use objtools.registry --- requirements.txt | 1 + stdqs/parsing/lookups.py | 74 ++++++++++--------------------------- stdqs/parsing/transforms.py | 50 +++++++++++++------------ stdqs/queryset.py | 5 ++- stdqs/utils.py | 33 ----------------- 5 files changed, 50 insertions(+), 113 deletions(-) diff --git a/requirements.txt b/requirements.txt index e69de29..a6f46e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +objtools>=0.1 diff --git a/stdqs/parsing/lookups.py b/stdqs/parsing/lookups.py index e634e9b..a3f970d 100644 --- a/stdqs/parsing/lookups.py +++ b/stdqs/parsing/lookups.py @@ -1,24 +1,32 @@ from abc import ABCMeta, abstractmethod -from stdqs.utils import Registry +from objtools.registry import ClassRegistry import operator -class LookupMetaclass(ABCMeta): +class LookupRegistry(ClassRegistry): - def __new__(cls, name, *args, **kwargs): - newclass = ABCMeta.__new__(cls, name, *args, **kwargs) - if name != "Lookup": - register(newclass) - return newclass + def check_key(self, key): + assert isinstance(key, str), 'Lookup name must be a string.' + + def check_value(self, value): + assert issubclass(value, Lookup), \ + 'Lookup should inherit from the Lookup abstract class.' -class Lookup(metaclass=LookupMetaclass): +registry = LookupRegistry() +register = registry.register +unregister = registry.unregister + + +class LookupMetaclass(registry.metaclass, ABCMeta): + pass + + +class Lookup(metaclass=LookupMetaclass, register=False): """ A filtering operation: The "lt" in `field__lt=4`. """ - lookup_name = None - def __init__(self, lhs, rhs): self.lhs, self.rhs = lhs, rhs @@ -55,54 +63,12 @@ def func_lookup(func, name=None): { '__module__': func_lookup.__module__, '__qualname__': class_name, - 'lookup_name': name, 'filter_obj': lambda self, obj: func(obj, self.rhs) - } + }, + key=name, ) -class LookupRegistry(Registry): - - def check_key(self, key): - assert isinstance(key, str), 'Lookup name must be a string.' - - def check_value(self, value): - assert callable(getattr(value, 'filter_obj', None)), \ - '{} should implement the filter_obj method.'.format(repr(value)) - - def register(self, key, value=None): - """ - Register a lookup. - May be used as register(lookup_class) if the lookup has a defined name, - or as register(name, lookup_class) or register(lookup_class, name) - to set a custom name. - Can be used as a decorator on a Lookup if it has a defined name. - """ - if value: - try: - return super().register(key, value) - except (KeyError, ValueError): - # Try the other way around - try: - return super().register(value, key) - except (KeyError, ValueError): - pass - raise - - if not getattr(key, 'lookup_name', None): - raise KeyError( - 'Lookup is missing a lookup name.' - 'Set the lookup_name attribute or use register(name, lookup).' - ) - - super().register(key.lookup_name, key) - - -registry = LookupRegistry() -register = registry.register -unregister = registry.unregister - - LessThanLookup = func_lookup(operator.lt) GreaterThanLookup = func_lookup(operator.gt) LessOrEqualLookup = func_lookup(operator.le, name='lte') diff --git a/stdqs/parsing/transforms.py b/stdqs/parsing/transforms.py index eba39b3..ddba6e1 100644 --- a/stdqs/parsing/transforms.py +++ b/stdqs/parsing/transforms.py @@ -1,18 +1,25 @@ -from stdqs.parsing.lookups import Lookup, LookupMetaclass, LookupRegistry +from stdqs.parsing.lookups import Lookup, LookupRegistry from abc import ABCMeta, abstractmethod from collections.abc import Iterator -class TransformMetaclass(LookupMetaclass): +class TransformRegistry(LookupRegistry): - def __new__(cls, name, *args, **kwargs): - newclass = ABCMeta.__new__(cls, name, *args, **kwargs) - if name not in ("Transform", "GetterTransform"): - register(newclass) - return newclass + def check_value(self, value): + assert issubclass(value, Transform), \ + 'Transform must inherit from the Transform base class' -class Transform(Lookup, metaclass=TransformMetaclass): +registry = TransformRegistry() +register = registry.register +unregister = registry.unregister + + +class TransformMetaclass(registry.metaclass, ABCMeta): + pass + + +class Transform(metaclass=TransformMetaclass, register=False): """ A special kind of lookup that also allows lookups on itself. For example, the "abs" in `field__abs__lt=2`. @@ -20,7 +27,7 @@ class Transform(Lookup, metaclass=TransformMetaclass): """ def __init__(self, lhs, rhs=None): - super().__init__(lhs, rhs) + self.lhs, self.rhs = lhs, rhs def filter_obj(self, obj): transformed = self.transform_obj(obj) @@ -36,6 +43,13 @@ class Transform(Lookup, metaclass=TransformMetaclass): return NotImplemented. """ + def __repr__(self): + return '{}({!r},{!r})'.format( + self.__class__.__name__, + self.lhs, + self.rhs, + ) + def func_transform(func, name=None): """ @@ -54,13 +68,13 @@ def func_transform(func, name=None): { '__module__': func_transform.__module__, '__qualname__': class_name, - 'lookup_name': name, 'transform_obj': lambda self, obj: func(obj) - } + }, + key=name, ) -class GetterTransform(Transform): +class GetterTransform(Transform, register=False): """ A special Transform that fetches values from attributes, keys or methods. Should not be registered as a normal Transform. @@ -84,18 +98,6 @@ class GetterTransform(Transform): return None -class TransformRegistry(LookupRegistry): - - def check_value(self, value): - super().check_value(value) - assert callable(getattr(value, 'transform_obj', None)), \ - '{} should implement the transform_obj method.'.format(repr(value)) - - -registry = TransformRegistry() -register = registry.register -unregister = registry.unregister - AbsoluteTransform = func_transform(abs) AsciiTransform = func_transform(ascii) BinaryTransform = func_transform(bin) diff --git a/stdqs/queryset.py b/stdqs/queryset.py index 5e5b411..7f07dcd 100644 --- a/stdqs/queryset.py +++ b/stdqs/queryset.py @@ -1,6 +1,7 @@ from collections.abc import Sequence, Collection, Iterator +from objtools.registry import ClassRegistry from stdqs.exceptions import ObjectDoesNotExist, MultipleObjectsReturned -from stdqs.utils import Registry, flatten +from stdqs.utils import flatten from stdqs.parsing import QueryParser REPR_OUTPUT_SIZE = 20 @@ -165,7 +166,7 @@ class DictQuerySet(QuerySet): return flatten(self.collection) -class QuerySetRegistry(Registry): +class QuerySetRegistry(ClassRegistry): def check_key(self, key): assert isinstance(key, type), \ diff --git a/stdqs/utils.py b/stdqs/utils.py index c1abecd..7b547fe 100644 --- a/stdqs/utils.py +++ b/stdqs/utils.py @@ -1,39 +1,6 @@ from collections.abc import Mapping, Iterable -class Registry(dict): - - unregister = dict.__delitem__ - - def check_key(self, key): - pass - - def check_value(self, value): - pass - - def check(self, key, value): - try: - self.check_key(key) - except KeyError: - raise - except Exception as e: - raise KeyError(e.message) - - try: - self.check_value(value) - except ValueError: - raise - except Exception as e: - raise ValueError(e.message) - - def register(self, key, value): - self.__setitem__(key, value) - - def __setitem__(self, key, value): - self.check(key, value) - super().__setitem__(key, value) - - def flatten(data): """ Turns data structured in nested iterables into data suitable for filtering