Source code for binx.registry

""" A private registry for the collection objects. It is mainly used to register
adaption classes on each collection object for data cleaning/processing. The classes
are created by the user and registered at runtime.
"""

from .exceptions import RegistryError
from .utils import bfs_shortest_path
import warnings

_collection_registry = {}

from pprint import pprint

[docs]def register_collection(cls): """ registers a new collection class. """ fullpath = cls.__module__ + '.' + cls.__name__ if fullpath in _collection_registry: # if the fullpath is already # if registered_adapters or adaptable_from already have values we should raise here # this should be enough to ensure the adapter chain won't be compromised by accident adapters = _collection_registry[fullpath][1]['registered_adapters'] from_colls = _collection_registry[fullpath][1]['adaptable_from'] if len(adapters) > 0 or len(from_colls) > 0: raise RegistryError('The name {} has already been registered in the adapter chain and cannot be overridden'.format(fullpath)) _collection_registry[fullpath][0] = cls warnings.warn("The reference to {} has been overridden in the registry.".format(fullpath)) else: # you must do the lookup by fullpath _collection_registry[fullpath] = [cls, { 'serializer_class': cls.serializer_class, 'internal_class': cls.internal_class, 'registered_adapters': set(), #NOTE these are the classes registered adapters 'adaptable_from': set() #NOTE these are other collection objects a coll can be adapted from }]
[docs]def get_class_from_collection_registry(classname): """ returns the full tuple given the fully qualified classname """ try: klass_tuple = _collection_registry[classname] except KeyError: raise RegistryError('The classname {} was not found in the registry'.format(classname)) return klass_tuple
[docs]def register_adapter_to_collection(classname, adapter): """ appends an adapter to the klass object """ _collection_registry[classname][1]['registered_adapters'].add(adapter)
[docs]def register_adaptable_collection(classname, coll): """ appends an adaptable collection to a classes list of adaptable collections """ _collection_registry[classname][1]['adaptable_from'].add(coll)
def _make_cc_graph(): """ returns a graph of connected collections. This is given as a flat dictionary of sets using the 'adaptable_from' sets for each graph. This is used by adapter path to return a path of classes connecting two nodes """ graph = {} #graph = {v[0]:[] for k,v in _collection_registry.items()} for name, entry in _collection_registry.items(): graph[entry[0]] = set() targets = set([e.target_collection_class for e in entry[1]['registered_adapters']]) graph[entry[0]].update(entry[1]['adaptable_from']) graph[entry[0]].update(targets) #graph[entry].add(entry[1]['adaptable_from']) graph = {k:v for k,v in graph.items()if len(v) > 0} return graph
[docs]def adapter_path(from_class, end_class): """ traverses the registry and builds a class path of adapters to a target using by looking at each nodes 'adaptable_from' set. It will traverse the graph until all possibilities are exhausted. If it finds a matching adaptable, it returns the path of adapter objects that are needed to adapt the schema. If no path is found it returns an empty list """ current_graph = _make_cc_graph() # create snapshot of current path if len(current_graph) == 0: return [] colls = bfs_shortest_path(current_graph, from_class, end_class) # return adaptable collection path if len(colls) == 0: return colls # empty list # loop through list of classes going forward and find the appropriate adapter for the next coll class # append these to a list and return adapters = [None] * (len(colls) - 1) for i in range(1, len(colls)): target = colls[i] # get refs current = colls[i-1] current_registry_entry = current.get_registry_entry() # return the complete registry entry for adapter_class in current_registry_entry[1]['registered_adapters']: if adapter_class.target_collection_class is target: adapters[i-1] = adapter_class return adapters