pax_global_header00006660000000000000000000000064142451263520014516gustar00rootroot0000000000000052 comment=7eb224f52e835fb3d757b2377a93931e3eabde56 python-persisting-theory-1.0/000077500000000000000000000000001424512635200163745ustar00rootroot00000000000000python-persisting-theory-1.0/CHANGES000066400000000000000000000013541424512635200173720ustar00rootroot00000000000000Changelog ========= 1.0 *** 2022-05-06: - dropped support for python 2 - switched to pytest for tests - blackified codebase - added ci/cd pipeline 0.2.1 ***** 13/10/2014: - Added tox to testing process (persisting theory is now tested under Python 2.7 and 3.4) - Moved test directory outside of persisting-theory - Set ``force_reload`` defaut to ``False`` to in Registry.autodiscover() 0.2.0 ***** 26/07/2014: - Added prepare_data hook for manipulating data before registration - Added prepare_name hook for manipulating name before registration - Added post_register hook 0.1.2 ***** 13/07/2014: - Added handling of error in autodiscovering 0.1.1 ***** 12/07/2014: - Added Python 3 support 0.1 *** 12/07/2014 - initial releasepython-persisting-theory-1.0/COPYING000066400000000000000000000030131424512635200174240ustar00rootroot00000000000000Copyright (c) Agate Blue and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of persisting-theory nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-persisting-theory-1.0/MANIFEST.in000066400000000000000000000001141424512635200201260ustar00rootroot00000000000000include COPYING include CHANGES include README.rst recursive-include examplepython-persisting-theory-1.0/PKG-INFO000066400000000000000000000160761424512635200175030ustar00rootroot00000000000000Metadata-Version: 2.1 Name: persisting-theory Version: 1.0 Summary: Registries that can autodiscover values accross your project apps Home-page: https://code.agate.blue/agate/persisting-theory Author: Agate Blue License: BSD Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 License-File: COPYING Introduction ============ Persisting-theory is a small python utility designed to automate data discovering and access inside a list of packages. Use case: you are building an application that will have pluggable components. You want to allow these components to register data so it can be accessed by any other component of your app. If you ever used Django framework, you may remember this: .. code-block:: python from django.contrib import admin admin.autodiscover() Basically, persisting-theory will do the same, except that it let you declare what you want to autodiscover. Okay, I'm bad at explaining things, and english is not my mother tongue. Let's build a simple example. Quickstart ========== Install ******* Install the package from `PyPi `_. via pip (or any other tool):: pip install persisting-theory Persisting-theory does not require any dependency but a python installation (it has been tested on python 2.7 and python 3.4). Setup ***** A basic setup: .. code-block:: python # registries.py from persiting_theory import Registry class CallbacksRegistry(Registry): """ Allow your apps to register callbacks """ # the package where the registry will try to find callbacks in each app look_into = "callbacks_registry" callbacks_registry = CallbacksRegistry() # app1/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def dog(): print("Wouf") # app2/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def cat(): print("Meow") # dosomething.py from registries import callbacks_registry APPS = ( 'app1', 'app2', ) # Trigger autodiscovering process callbacks_registry.autodiscover(APPS) for callback in callbacks_registry.values(): callback() # Wouf # Meow API === ``Registry`` inherits from python built-in `collections.OrderedDict`, which means you can use regular dict methods to access registered data: .. code-block:: python callbacks_registry.get("dog")() # will print Wouf assert callbacks_registry.get("chicken", None) is None Registry.register() ******************* You can use this function as a decorator for registering functions and classes: .. code-block:: python from persisting_theory import Registry class AwesomeRegistry(Registry): pass r = AwesomeRegistry() # register a class @r.register class AwesomeClass: pass # register a function @r.register def awesome_function(): pass # By default, the key in the registry for a given value is obtained from the function or class name, if possible assert r.get("AwesomeClass") == AwesomeClass assert r.get("awesome_function") == awesome_function # You can override this behaviour: @r.register(name="Chuck") class AwesomeClass: pass @r.register(name="Norris") def awesome_function(): pass assert r.get("Chuck") == AwesomeClass assert r.get("Norris") == awesome_function # You can also use the register method as is awesome_var = "Chuck Norris" r.register(awesome_var, name="Who am I ?") assert r.get("Who am I ?") == awesome_var # I f you are not registering a function or a class, you MUST provide a name argument Registry.validate() ******************* By default, a registry will accept any registered value. Sometimes, it's not what you want, so you can restrict what kind of data your registry accepts: .. code-block:: python from persisting_theory import Registry class StartsWithAwesomeRegistry(Registry): def validate(self, data): if isinstance(data, str): return data.startswith("awesome") return False r = StartsWithAwesomeRegistry() # will pass registration r.register("awesome day", name="awesome_day") # will fail and raise ValueError r.register("not so awesome day", name="not_so_awesome_day") Registry.prepare_data() *********************** If you want to manipulate your data before registering it, override this method. In this example, we prefix every registered string with 'hello': .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World'), name="world") r.register(Greeting('agate'), name="agate") assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Registry.prepare_name() *********************** In a similar way, you can manipulate the name of registered data. This can help if you want to avoid repetitions. Let's improve our previous example: .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data def prepare_name(self, data, name=None): return self.data.first_name.lower() r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World')) r.register(Greeting('agate')) assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Going meta ********** If you have multiple registries, or want to allow your apps to declare their own registries, this is for you: .. code-block:: python # registries.py from persisting_theory import meta_registry, Registry class RegistryA(Registry): look_into = "a" class RegistryB(Registry): look_into = "b" registry_a = RegistryA() meta_registry.register(registry_a, name="registry_a") registry_b = RegistryB() meta_registry.register(registry_b, name="registry_b") # dosomethingelse.py from persisting_theory import meta_registry # will import registries declared in `registries` packages, and trigger autodiscover() on each of them meta_registry.autodiscover(apps=("app1", "app2")) What the hell is that name ? ============================ It's an anagram for "python registries". Contribute ========== Contributions, bug reports, and "thank you" are welcomed. License ======= The project is licensed under BSD licence. python-persisting-theory-1.0/README.rst000066400000000000000000000150251424512635200200660ustar00rootroot00000000000000Introduction ============ Persisting-theory is a small python utility designed to automate data discovering and access inside a list of packages. Use case: you are building an application that will have pluggable components. You want to allow these components to register data so it can be accessed by any other component of your app. If you ever used Django framework, you may remember this: .. code-block:: python from django.contrib import admin admin.autodiscover() Basically, persisting-theory will do the same, except that it let you declare what you want to autodiscover. Okay, I'm bad at explaining things, and english is not my mother tongue. Let's build a simple example. Quickstart ========== Install ******* Install the package from `PyPi `_. via pip (or any other tool):: pip install persisting-theory Persisting-theory does not require any dependency but a python installation (it has been tested on python 2.7 and python 3.4). Setup ***** A basic setup: .. code-block:: python # registries.py from persiting_theory import Registry class CallbacksRegistry(Registry): """ Allow your apps to register callbacks """ # the package where the registry will try to find callbacks in each app look_into = "callbacks_registry" callbacks_registry = CallbacksRegistry() # app1/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def dog(): print("Wouf") # app2/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def cat(): print("Meow") # dosomething.py from registries import callbacks_registry APPS = ( 'app1', 'app2', ) # Trigger autodiscovering process callbacks_registry.autodiscover(APPS) for callback in callbacks_registry.values(): callback() # Wouf # Meow API === ``Registry`` inherits from python built-in `collections.OrderedDict`, which means you can use regular dict methods to access registered data: .. code-block:: python callbacks_registry.get("dog")() # will print Wouf assert callbacks_registry.get("chicken", None) is None Registry.register() ******************* You can use this function as a decorator for registering functions and classes: .. code-block:: python from persisting_theory import Registry class AwesomeRegistry(Registry): pass r = AwesomeRegistry() # register a class @r.register class AwesomeClass: pass # register a function @r.register def awesome_function(): pass # By default, the key in the registry for a given value is obtained from the function or class name, if possible assert r.get("AwesomeClass") == AwesomeClass assert r.get("awesome_function") == awesome_function # You can override this behaviour: @r.register(name="Chuck") class AwesomeClass: pass @r.register(name="Norris") def awesome_function(): pass assert r.get("Chuck") == AwesomeClass assert r.get("Norris") == awesome_function # You can also use the register method as is awesome_var = "Chuck Norris" r.register(awesome_var, name="Who am I ?") assert r.get("Who am I ?") == awesome_var # I f you are not registering a function or a class, you MUST provide a name argument Registry.validate() ******************* By default, a registry will accept any registered value. Sometimes, it's not what you want, so you can restrict what kind of data your registry accepts: .. code-block:: python from persisting_theory import Registry class StartsWithAwesomeRegistry(Registry): def validate(self, data): if isinstance(data, str): return data.startswith("awesome") return False r = StartsWithAwesomeRegistry() # will pass registration r.register("awesome day", name="awesome_day") # will fail and raise ValueError r.register("not so awesome day", name="not_so_awesome_day") Registry.prepare_data() *********************** If you want to manipulate your data before registering it, override this method. In this example, we prefix every registered string with 'hello': .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World'), name="world") r.register(Greeting('agate'), name="agate") assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Registry.prepare_name() *********************** In a similar way, you can manipulate the name of registered data. This can help if you want to avoid repetitions. Let's improve our previous example: .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data def prepare_name(self, data, name=None): return self.data.first_name.lower() r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World')) r.register(Greeting('agate')) assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Going meta ********** If you have multiple registries, or want to allow your apps to declare their own registries, this is for you: .. code-block:: python # registries.py from persisting_theory import meta_registry, Registry class RegistryA(Registry): look_into = "a" class RegistryB(Registry): look_into = "b" registry_a = RegistryA() meta_registry.register(registry_a, name="registry_a") registry_b = RegistryB() meta_registry.register(registry_b, name="registry_b") # dosomethingelse.py from persisting_theory import meta_registry # will import registries declared in `registries` packages, and trigger autodiscover() on each of them meta_registry.autodiscover(apps=("app1", "app2")) What the hell is that name ? ============================ It's an anagram for "python registries". Contribute ========== Contributions, bug reports, and "thank you" are welcomed. License ======= The project is licensed under BSD licence. python-persisting-theory-1.0/example/000077500000000000000000000000001424512635200200275ustar00rootroot00000000000000python-persisting-theory-1.0/example/__init__.py000066400000000000000000000000001424512635200221260ustar00rootroot00000000000000python-persisting-theory-1.0/example/app1/000077500000000000000000000000001424512635200206705ustar00rootroot00000000000000python-persisting-theory-1.0/example/app1/__init__.py000066400000000000000000000000001424512635200227670ustar00rootroot00000000000000python-persisting-theory-1.0/example/app1/callbacks_registry.py000066400000000000000000000001461424512635200251120ustar00rootroot00000000000000from registries import callbacks_registry @callbacks_registry.register def dog(): print("Wouf") python-persisting-theory-1.0/example/app2/000077500000000000000000000000001424512635200206715ustar00rootroot00000000000000python-persisting-theory-1.0/example/app2/__init__.py000066400000000000000000000000001424512635200227700ustar00rootroot00000000000000python-persisting-theory-1.0/example/app2/callbacks_registry.py000066400000000000000000000001461424512635200251130ustar00rootroot00000000000000from registries import callbacks_registry @callbacks_registry.register def cat(): print("Meow") python-persisting-theory-1.0/example/dosomething.py000066400000000000000000000003531424512635200227220ustar00rootroot00000000000000from registries import callbacks_registry APPS = ( "app1", "app2", ) # Trigger autodiscovering process callbacks_registry.autodiscover(APPS) for callback in callbacks_registry.values(): callback() # Wouf # Meow python-persisting-theory-1.0/example/registries.py000066400000000000000000000007341424512635200225650ustar00rootroot00000000000000import os, sys sys.path.append(os.path.dirname(os.path.dirname(__file__))) from persisting_theory import Registry class CallbacksRegistry(Registry): """ Allow your apps to register callbacks """ # the package where the registry will try to find callbacks in each app look_into = "callbacks_registry" callbacks_registry = CallbacksRegistry() APPS = ( "app1", "app2", ) # Trigger autodiscovering process callbacks_registry.autodiscover(APPS) python-persisting-theory-1.0/persisting_theory.egg-info/000077500000000000000000000000001424512635200236475ustar00rootroot00000000000000python-persisting-theory-1.0/persisting_theory.egg-info/PKG-INFO000066400000000000000000000160761424512635200247560ustar00rootroot00000000000000Metadata-Version: 2.1 Name: persisting-theory Version: 1.0 Summary: Registries that can autodiscover values accross your project apps Home-page: https://code.agate.blue/agate/persisting-theory Author: Agate Blue License: BSD Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 License-File: COPYING Introduction ============ Persisting-theory is a small python utility designed to automate data discovering and access inside a list of packages. Use case: you are building an application that will have pluggable components. You want to allow these components to register data so it can be accessed by any other component of your app. If you ever used Django framework, you may remember this: .. code-block:: python from django.contrib import admin admin.autodiscover() Basically, persisting-theory will do the same, except that it let you declare what you want to autodiscover. Okay, I'm bad at explaining things, and english is not my mother tongue. Let's build a simple example. Quickstart ========== Install ******* Install the package from `PyPi `_. via pip (or any other tool):: pip install persisting-theory Persisting-theory does not require any dependency but a python installation (it has been tested on python 2.7 and python 3.4). Setup ***** A basic setup: .. code-block:: python # registries.py from persiting_theory import Registry class CallbacksRegistry(Registry): """ Allow your apps to register callbacks """ # the package where the registry will try to find callbacks in each app look_into = "callbacks_registry" callbacks_registry = CallbacksRegistry() # app1/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def dog(): print("Wouf") # app2/callbacks_registry.py from registries import callbacks_registry @callbacks_registry.register def cat(): print("Meow") # dosomething.py from registries import callbacks_registry APPS = ( 'app1', 'app2', ) # Trigger autodiscovering process callbacks_registry.autodiscover(APPS) for callback in callbacks_registry.values(): callback() # Wouf # Meow API === ``Registry`` inherits from python built-in `collections.OrderedDict`, which means you can use regular dict methods to access registered data: .. code-block:: python callbacks_registry.get("dog")() # will print Wouf assert callbacks_registry.get("chicken", None) is None Registry.register() ******************* You can use this function as a decorator for registering functions and classes: .. code-block:: python from persisting_theory import Registry class AwesomeRegistry(Registry): pass r = AwesomeRegistry() # register a class @r.register class AwesomeClass: pass # register a function @r.register def awesome_function(): pass # By default, the key in the registry for a given value is obtained from the function or class name, if possible assert r.get("AwesomeClass") == AwesomeClass assert r.get("awesome_function") == awesome_function # You can override this behaviour: @r.register(name="Chuck") class AwesomeClass: pass @r.register(name="Norris") def awesome_function(): pass assert r.get("Chuck") == AwesomeClass assert r.get("Norris") == awesome_function # You can also use the register method as is awesome_var = "Chuck Norris" r.register(awesome_var, name="Who am I ?") assert r.get("Who am I ?") == awesome_var # I f you are not registering a function or a class, you MUST provide a name argument Registry.validate() ******************* By default, a registry will accept any registered value. Sometimes, it's not what you want, so you can restrict what kind of data your registry accepts: .. code-block:: python from persisting_theory import Registry class StartsWithAwesomeRegistry(Registry): def validate(self, data): if isinstance(data, str): return data.startswith("awesome") return False r = StartsWithAwesomeRegistry() # will pass registration r.register("awesome day", name="awesome_day") # will fail and raise ValueError r.register("not so awesome day", name="not_so_awesome_day") Registry.prepare_data() *********************** If you want to manipulate your data before registering it, override this method. In this example, we prefix every registered string with 'hello': .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World'), name="world") r.register(Greeting('agate'), name="agate") assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Registry.prepare_name() *********************** In a similar way, you can manipulate the name of registered data. This can help if you want to avoid repetitions. Let's improve our previous example: .. code-block:: python from persisting_theory import Registry class HelloRegistry(Registry): def prepare_data(self, data): return 'hello ' + data def prepare_name(self, data, name=None): return self.data.first_name.lower() r = HelloRegistry() class Greeting: def __init__(self, first_name): self.first_name = first_name r.register(Greeting('World')) r.register(Greeting('agate')) assert r.register.get('world') == "hello World" assert r.register.get('agate') == "hello agate" Going meta ********** If you have multiple registries, or want to allow your apps to declare their own registries, this is for you: .. code-block:: python # registries.py from persisting_theory import meta_registry, Registry class RegistryA(Registry): look_into = "a" class RegistryB(Registry): look_into = "b" registry_a = RegistryA() meta_registry.register(registry_a, name="registry_a") registry_b = RegistryB() meta_registry.register(registry_b, name="registry_b") # dosomethingelse.py from persisting_theory import meta_registry # will import registries declared in `registries` packages, and trigger autodiscover() on each of them meta_registry.autodiscover(apps=("app1", "app2")) What the hell is that name ? ============================ It's an anagram for "python registries". Contribute ========== Contributions, bug reports, and "thank you" are welcomed. License ======= The project is licensed under BSD licence. python-persisting-theory-1.0/persisting_theory.egg-info/SOURCES.txt000066400000000000000000000014241424512635200255340ustar00rootroot00000000000000CHANGES COPYING MANIFEST.in README.rst setup.py example/__init__.py example/dosomething.py example/registries.py example/app1/__init__.py example/app1/callbacks_registry.py example/app2/__init__.py example/app2/callbacks_registry.py persisting_theory/__init__.py persisting_theory/registries.py persisting_theory.egg-info/PKG-INFO persisting_theory.egg-info/SOURCES.txt persisting_theory.egg-info/dependency_links.txt persisting_theory.egg-info/not-zip-safe persisting_theory.egg-info/top_level.txt tests/__init__.py tests/test_persiting_theory.py tests/test_registries.py tests/app1/__init__.py tests/app1/awesome_people.py tests/app1/vegetables.py tests/app2/__init__.py tests/app2/awesome_people.py tests/app2/vegetables.py tests/buggy_app/__init__.py tests/buggy_app/awesome_people.pypython-persisting-theory-1.0/persisting_theory.egg-info/dependency_links.txt000066400000000000000000000000011424512635200277150ustar00rootroot00000000000000 python-persisting-theory-1.0/persisting_theory.egg-info/not-zip-safe000066400000000000000000000000011424512635200260750ustar00rootroot00000000000000 python-persisting-theory-1.0/persisting_theory.egg-info/top_level.txt000066400000000000000000000000401424512635200263730ustar00rootroot00000000000000example persisting_theory tests python-persisting-theory-1.0/persisting_theory/000077500000000000000000000000001424512635200221555ustar00rootroot00000000000000python-persisting-theory-1.0/persisting_theory/__init__.py000066400000000000000000000002341424512635200242650ustar00rootroot00000000000000from .registries import ( Registry, meta_registry, QuerySet, Manager, MultipleObjectsReturned, DoesNotExist, ) __version__ = "1.0" python-persisting-theory-1.0/persisting_theory/registries.py000066400000000000000000000201301424512635200247030ustar00rootroot00000000000000from collections import OrderedDict import inspect import operator import importlib class DoesNotExist(ValueError): pass class MultipleObjectsReturned(ValueError): pass class QuerySet(object): def __init__(self, values): self.values = list(values) def __iter__(self): for value in self.values: yield value def __len__(self): return len(self.values) def __repr__(self): return "<{0}: {1}>".format(self.__class__.__name__, str(list(self.values))) def __getitem__(self, i): return self.values[i] def __eq__(self, other): return self.values == list(other) def _clone(self, new_values): return self.__class__(new_values) def _build_filter(self, **kwargs): """build a single filter function used to match arbitrary object""" def object_filter(obj): for key, value in kwargs.items(): # we replace dango-like lookup by dots, so attrgetter can do his job key = key.replace("__", ".") getter = operator.attrgetter(key) if not getter(obj) == value: return False return True return object_filter def exists(self): return len(self) > 0 def order_by(self, key): reverse = False if key.startswith("-"): reverse = True key = key[1:] return self._clone( sorted(self.values, key=operator.attrgetter(key), reverse=reverse) ) def all(self): return self._clone(self.values) def count(self): return len(self) def first(self): try: return self.values[0] except IndexError: return None def last(self): try: return self.values[-1] except IndexError: return None def filter(self, **kwargs): _filter = self._build_filter(**kwargs) return self._clone(filter(_filter, self.values)) def exclude(self, **kwargs): _filter = self._build_filter(**kwargs) return self._clone(filter(lambda v: not _filter(v), self.values)) def get(self, **kwargs): matches = self.filter(**kwargs) if len(matches) == 0: raise DoesNotExist() if len(matches) > 1: raise MultipleObjectsReturned() return matches[0] class Manager(object): """Used to retrieve / order / filter preferences pretty much as django's ORM managers""" def __init__(self, registry, queryset_class): self.registry = registry self.queryset_class = queryset_class def get_queryset(self): return self.queryset_class(self.registry.values()) def all(self): return self.get_queryset().all() def __getattr__(self, attr): try: return super().__getattr__(attr) except AttributeError: # Try to proxy on queryset if possible return getattr(self.get_queryset(), attr) class Registry(OrderedDict): manager_class = Manager queryset_class = QuerySet def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.objects = self.manager_class(self, self.queryset_class) def register_decorator_factory(self, **kwargs): """ Return an actual decorator for registering objects into registry """ name = kwargs.get("name") def decorator(decorated): self.register_func(data=decorated, name=name) return decorated return decorator def register(self, data=None, name=None, **kwargs): """ Use this method as a decorator on class/function you want to register: @registry.register(name="test") class Test: pass :param:data: Something to register in the registry :param:name: The unique name that will identify registered data. If None, by default, registry will try to deduce name from class name (if object is a class or an object). You can change this behaviour by overriding :py::method:`prepare_name` """ if data is None: return self.register_decorator_factory(data=data, name=name, **kwargs) else: self.register_func(data=data, name=name, **kwargs) return data def get_object_name(self, data): """ Return a name from an element (object, class, function...) """ if callable(data): return data.__name__ elif inspect.isclass(data): return data.__class__.__name__ else: raise ValueError( "Cannot deduce name from given object ({0}). Please user registry.register() with a 'name' argument.".format( data ) ) def validate(self, data): """ Called before registering a new value into the registry Override this method if you want to restrict what type of data cna be registered """ return True def prepare_name(self, data, name=None): if name is None: return self.get_object_name(data) return name def register_func(self, data, name=None, **kwargs): """ Register abritrary data into the registry """ if self.validate(data): o = self.prepare_data(data) n = self.prepare_name(data, name) self[n] = o self.post_register(data=0, name=n) else: raise ValueError( "{0} (type: {0.__class__}) is not a valid value for {1} registry".format( data, self.__class__ ) ) def post_register(self, data, name): """ Will be triggered each time a new element is successfully registered. Feel free to override this method """ pass def prepare_data(self, data): """ Override this methode if you want to manipulate data before registering it You MUST return a value to register """ return data def autodiscover(self, apps, force_reload=False): """ Iterate throught every installed apps, trying to import `look_into` package :param apps: an iterable of string, refering to python modules the registry will try to import via autodiscover """ for app in apps: app_package = __import__(app) try: package = "{0}.{1}".format( app, self.look_into ) # try to import self.package inside current app # print(package) module = __import__(package) if force_reload: importlib.reload(module) except ImportError as exc: # From django's syncdb # This is slightly hackish. We want to ignore ImportErrors # if the module itself is missing -- but we don't # want to ignore the exception if the module exists # but raises an ImportError for some reason. The only way we # can do this is to check the text of the exception. Note that # we're a bit broad in how we check the text, because different # Python implementations may not use the same text. # CPython uses the text "No module named" # PyPy uses "No module named myproject.myapp" msg = exc.args[0] if not msg.startswith("No module named") or self.look_into not in msg: raise class MetaRegistry(Registry): """ Keep a reference to all registries """ look_into = "registries" def autodiscover(self, apps, cascade=True, **kwargs): """ :param cascade: If true, will trigger autodiscover on discovered registries """ super().autodiscover(apps, **kwargs) if cascade: self.autodiscover_registries(apps) def autodiscover_registries(self, apps): for key, registry in self.items(): registry.autodiscover(apps) meta_registry = MetaRegistry() python-persisting-theory-1.0/setup.cfg000066400000000000000000000000461424512635200202150ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 python-persisting-theory-1.0/setup.py000066400000000000000000000017261424512635200201140ustar00rootroot00000000000000import os from setuptools import setup, find_packages import persisting_theory as package README = open(os.path.join(os.path.dirname(__file__), "README.rst")).read() # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) setup( name="persisting-theory", version=package.__version__, packages=find_packages(), include_package_data=True, license="BSD", # example license description="Registries that can autodiscover values accross your project apps", long_description=README, url="https://code.agate.blue/agate/persisting-theory", author="Agate Blue", zip_safe=False, classifiers=[ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", ], ) python-persisting-theory-1.0/tests/000077500000000000000000000000001424512635200175365ustar00rootroot00000000000000python-persisting-theory-1.0/tests/__init__.py000066400000000000000000000002071424512635200216460ustar00rootroot00000000000000import os, sys TEST_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(TEST_DIR) sys.path.append(BASE_DIR) python-persisting-theory-1.0/tests/app1/000077500000000000000000000000001424512635200203775ustar00rootroot00000000000000python-persisting-theory-1.0/tests/app1/__init__.py000066400000000000000000000000001424512635200224760ustar00rootroot00000000000000python-persisting-theory-1.0/tests/app1/awesome_people.py000066400000000000000000000001451424512635200237550ustar00rootroot00000000000000from ..test_registries import awesome_people @awesome_people.register class AlainDamasio: pass python-persisting-theory-1.0/tests/app1/vegetables.py000066400000000000000000000001471424512635200230740ustar00rootroot00000000000000from ..test_registries import vegetable_registry @vegetable_registry.register class Potato: pass python-persisting-theory-1.0/tests/app2/000077500000000000000000000000001424512635200204005ustar00rootroot00000000000000python-persisting-theory-1.0/tests/app2/__init__.py000066400000000000000000000000001424512635200224770ustar00rootroot00000000000000python-persisting-theory-1.0/tests/app2/awesome_people.py000066400000000000000000000001501424512635200237520ustar00rootroot00000000000000from ..test_registries import awesome_people @awesome_people.register class FrederikPeeters: pass python-persisting-theory-1.0/tests/app2/vegetables.py000066400000000000000000000002121424512635200230660ustar00rootroot00000000000000from ..test_registries import vegetable_registry @vegetable_registry.register class Ketchup: warning = "ketchup is not a vegetable" python-persisting-theory-1.0/tests/buggy_app/000077500000000000000000000000001424512635200215135ustar00rootroot00000000000000python-persisting-theory-1.0/tests/buggy_app/__init__.py000066400000000000000000000000001424512635200236120ustar00rootroot00000000000000python-persisting-theory-1.0/tests/buggy_app/awesome_people.py000066400000000000000000000001041424512635200250640ustar00rootroot00000000000000from ..test_registries import awesome_people print(inexisting_var) python-persisting-theory-1.0/tests/test_persiting_theory.py000066400000000000000000000162721424512635200245550ustar00rootroot00000000000000import pytest from . import test_registries import persisting_theory persisting_theory.meta_registry.look_into = "test_registries" TEST_APPS = ( "tests.app1", "tests.app2", ) def test_can_infer_name_from_class_function_and_instance(): registry = persisting_theory.Registry() def something(): pass class MyClass: pass assert registry.get_object_name(something) == "something" assert registry.get_object_name(MyClass) == "MyClass" with pytest.raises(ValueError): assert registry.get_object_name(MyClass()) == "MyClass" def test_can_register_data_to_registry(): data = "something" registry = persisting_theory.Registry() registry.register(data, name="key") assert len(registry) == 1 assert registry.get("key") == data def test_can_restric_registered_data(): class RestrictedRegistry(persisting_theory.Registry): def validate(self, obj): """Only accept integer values""" return isinstance(obj, int) registry = RestrictedRegistry() registry.register(12, name="twelve") with pytest.raises(ValueError): registry.register("not an int", name="not an int") def test_can_register_class_and_function_via_decorator(): registry = persisting_theory.Registry() @registry.register class ToRegister: pass assert registry.get("ToRegister") == ToRegister @registry.register def something(): pass assert registry.get("something") == something def test_can_register_via_decorator_using_custom_name(): registry = persisting_theory.Registry() @registry.register(name="custom_name") def something(): pass assert registry.get("custom_name") == something def test_meta_registry_can_autodiscovering_registries_and_trigger_their_autodiscover_method(): registry = persisting_theory.meta_registry registry.autodiscover(apps=TEST_APPS) assert len(registry) == 2 assert registry.get("awesome_people") == test_registries.awesome_people assert registry.get("vegetable_registry") == test_registries.vegetable_registry registry = test_registries.awesome_people assert len(registry) == 2 assert registry.get("AlainDamasio", None) is not None assert registry.get("FrederikPeeters", None) is not None registry = test_registries.vegetable_registry assert len(registry) == 2 assert registry.get("Potato", None) is not None assert registry.get("Ketchup", None) is not None def test_registry_can_autodiscover(): registry = test_registries.awesome_people registry.autodiscover(apps=TEST_APPS) assert len(registry) == 2 assert registry.get("AlainDamasio", None) is not None assert registry.get("FrederikPeeters", None) is not None registry.clear() def test_autodiscover_raises_an_error_if_there_is_an_error_in_imported_module(): with pytest.raises(NameError): registry = test_registries.awesome_people registry.autodiscover(apps=("tests.buggy_app",)) def test_can_manipulate_data_before_registering(): class ModifyData(persisting_theory.Registry): def prepare_data(self, data): return "hello " + data r = ModifyData() r.register("agate", name="agate") r.register("roger", name="roger") assert r.get("agate") == "hello agate" assert r.get("roger") == "hello roger" def test_can_manipulate_key_before_registering(): class ModifyKey(persisting_theory.Registry): def prepare_name(self, data, key=None): return "custom_key " + data.first_name r = ModifyKey() class N: def __init__(self, first_name): self.first_name = first_name n1 = N(first_name="agate") n2 = N(first_name="alain") r.register(n1) r.register(n2) assert r.get("custom_key agate") == n1 assert r.get("custom_key alain") == n2 def test_can_post_register_triggers_correctly(): class PostRegisterException(Exception): pass class PostRegister(persisting_theory.Registry): def post_register(self, data, name): raise PostRegisterException("Post register triggered") r = PostRegister() with pytest.raises(PostRegisterException): r.register("hello", name="world") class FakeObject(object): def __init__(self, name, **kwargs): self.name = name for key, value in kwargs.items(): setattr(self, key, value) def __repr__(self): return self.name @pytest.fixture def parents(): return [ FakeObject(name="parent_1"), FakeObject(name="parent_2"), ] @pytest.fixture def objects(parents): return [ FakeObject(name="test_1", order=2, a=1, parent=parents[0]), FakeObject(name="test_2", order=3, a=1, parent=parents[0]), FakeObject(name="test_3", order=1, a=2, parent=parents[1]), FakeObject(name="test_4", order=4, a=2, parent=parents[1]), ] @pytest.fixture def registry(objects): class R(persisting_theory.Registry): def prepare_name(self, data, key=None): return data.name registry = R() for o in objects: registry.register(o) return registry def test_default_order(registry, objects): assert list(registry.objects.all()) == objects def test_can_get_using_attribute(registry, objects): assert registry.objects.get(name="test_1") == objects[0] def test_can_filter(registry, objects): assert registry.objects.filter(a=1) == objects[:2] def test_can_combine_filters(registry, objects): assert registry.objects.filter(a=1, name="test_2") == objects[1:2] assert registry.objects.filter(a=1).filter(name="test_2") == objects[1:2] def test_related_lookups(registry, objects): assert registry.objects.filter(parent__name="parent_1") == objects[:2] assert registry.objects.exclude(parent__name="parent_1") == objects[2:] assert registry.objects.get(parent__name="parent_1", order=2) == objects[0] def test_can_exclude(registry, objects): assert registry.objects.exclude(a=1) == objects[2:] def test_can_combine_exclude(registry, objects): assert registry.objects.exclude(a=1).exclude(name="test_4") == objects[2:3] assert registry.objects.exclude(a=2, name="test_4") == objects[:3] def test_can_count(registry): assert registry.objects.filter(a=1).count() == 2 def test_first(registry): assert registry.objects.filter(a=123).first() is None assert registry.objects.filter(a=1).first() is not None def test_ordering(registry, objects): assert registry.objects.order_by("order")[:2] == [objects[2], objects[0]] assert registry.objects.order_by("-order")[:2] == [objects[3], objects[1]] def test_last(registry): assert registry.objects.filter(a=123).last() is None assert registry.objects.filter(a=1).last() is not None def test_exists(registry): assert registry.objects.filter(a=123).exists() is False assert registry.objects.filter(a=1).exists() is True def test_get_raise_exception_on_multiple_objects_returned(registry): with pytest.raises(persisting_theory.MultipleObjectsReturned): registry.objects.get(a=1) def test_get_raise_exception_on_does_not_exist(registry): with pytest.raises(persisting_theory.DoesNotExist): registry.objects.get(a=123) python-persisting-theory-1.0/tests/test_registries.py000066400000000000000000000006341424512635200233320ustar00rootroot00000000000000from persisting_theory import Registry, meta_registry class AwesomePeopleRegistry(Registry): look_into = "awesome_people" awesome_people = AwesomePeopleRegistry() meta_registry.register(awesome_people, name="awesome_people") class VegetableRegistry(Registry): look_into = "vegetables" vegetable_registry = VegetableRegistry() meta_registry.register(vegetable_registry, name="vegetable_registry")