Source code for jsoner.registry

# -*- coding: utf-8 -*-

import pydoc
import typing as T
from collections import UserDict
from typing import Callable


[docs]class Registry(UserDict): """ The :class:`Registry` allows simple key-value mapping. Each key is only allowed once in the registry. Usage:: >>> from jsoner.registry import Registry >>> reg = Registry() >>> reg.add('foo', 42) >>> reg.get('foo') 42 >>> reg = Registry() >>> @reg.register('foo') ... def foo(): ... return 42 >>> reg.get('foo')() 42 """ @property def registry(self) -> dict: """ Returns ------- dict The registry dictionary. """ return self.data
[docs] def add(self, key: T.Any, value: T.Any) -> None: """ Adds the key, value pair to the registry. Each key is only allowed once in the registry. :param key: :param value: :return: :raise KeyError: If the key is already in the registry. """ if key in self.data: msg = 'Key `{}` is already in the registry!'.format(key) raise KeyError(msg) self.data[key] = value
[docs] def register(self, key: T.Any) -> Callable: """ :meth:`register` servers as a decorator to add functions to the registry. :param key: :return: Callable """ def inner(func): self.add(key, func) return inner return inner
def __contains__(self, item) -> bool: in_regestry = super().__contains__(item) if not in_regestry: try: self.__getitem__(item) in_regestry = True except KeyError: pass return in_regestry
[docs]class SubclassRegistry(Registry): """ The :class:`SubclassRegistry` will not only map a single key-value pair, but will also retrieve a value if the key, or the type of the key is a Subclass of any of the keys. If the key, seems to be an object, which could potentially be in the registry but is not found at once, the :class:`SubclassRegistry` will search the mro of the object and check against its entries. Usage:: >>> from jsoner.registry import SubclassRegistry >>> reg = SubclassRegistry() >>> reg.add(dict, 42) >>> reg.get(dict) 42 >>> class A: ... pass >>> class B(A): ... pass >>> reg.add(A, 'bar') >>> reg.get(B) 'bar' >>> reg.get(B()) 'bar' The registration also works with strings >>> from datetime import datetime >>> reg.add('datetime.datetime', 'foo') >>> reg.get(datetime) 'foo' >>> reg.get('dict') 42 Furthermore it can be used as decorator. >>> reg = SubclassRegistry() >>> @reg.register(A) ... def foo(): ... return 42 >>> reg.get(A)() 42 >>> reg.get(B)() 42 """ def __getitem__(self, key: T.Any) -> T.Any: """ Returns the registered function for the given object or type :param key: :return: """ msg = 'Key `{}` not found in registry.'.format(key) value = self.data.get(key) if value is not None: return value if isinstance(key, str): try: obj_type = import_object(key) except ImportError: raise KeyError(msg) # get the object type elif isinstance(key, type): obj_type = key else: obj_type = key.__class__ try: return self.data[obj_type] except KeyError: pass try: mro = obj_type.mro() except AttributeError: raise KeyError(msg) # walk mro for cls in mro: for registered_cls, value in self.data.items(): if isinstance(registered_cls, str): try: registered_cls = import_object(registered_cls) except ImportError: continue if cls is registered_cls: return value else: raise KeyError(msg)
[docs]def import_object(path: str) -> T.Any: """ Import the object or raise an :exc:`ImportError` if the object is not found. :param path: The path to the object. :return: The imported object. :raise ImportError: """ t = pydoc.locate(path) if t is None: msg = 'Object `{}` could not be found'.format(path) raise ImportError(msg) return t
encoders = SubclassRegistry() """ :attr:`encoders` contains a mapping of types and encoding functions. An encoding function takes an argument and returns a value which should be json serializable. The function which is defined in :attr:`decoders` must be able to recreate the object from the returned value. """ decoders = SubclassRegistry() """ :attr:`decoders` contains the inverse functions-type mapping for :attr:`encoders`. """