Source code for sunpy.util.datatype_factory_base

"""
This module provides the base registration factory used for all SunPy data/net
factories.
"""
import inspect

__all__ = ["BasicRegistrationFactory", "NoMatchError",
           "MultipleMatchError", "ValidationFunctionError"]


[docs] class BasicRegistrationFactory: """ Generalized registerable factory type. Widgets (classes) can be registered with an instance of this class. Arguments to the factory's ``__call__`` method are then passed to a function specified by the registered factory, which validates the input and returns a instance of the class that best matches the inputs. Attributes ---------- registry : `dict` Dictionary mapping classes (key) to function (value) which validates input. default_widget_type : `type` Class of the default widget. validation_functions : `list` of `str` List of function names that are valid validation functions. Parameters ---------- default_widget_type : `type`, optional Class of the default widget. Defaults to `None`. additional_validation_functions : `list` of `str`, optional List of strings corresponding to additional validation function names. Defaults to `list`. registry : `dict`, optional Dictionary mapping classes (key) to function (value) which validates input. Defaults to `None`. Notes ----- * A valid validation function must be a classmethod of the registered widget and it must return a `bool`. """ def __init__(self, default_widget_type=None, additional_validation_functions=[], registry=None): if registry is None: self.registry = dict() else: self.registry = registry self.default_widget_type = default_widget_type self.validation_functions = (['_factory_validation_function'] + additional_validation_functions)
[docs] def __call__(self, *args, **kwargs): """ Method for running the factory. Arguments args and kwargs are passed through to the validation function and to the constructor for the final type. """ # Any preprocessing and massaging of inputs can happen here return self._check_registered_widget(*args, **kwargs)
def _check_registered_widget(self, *args, **kwargs): """ Implementation of a basic check to see if arguments match a widget. """ candidate_widget_types = list() for key in self.registry: # Call the registered validation function for each registered class if self.registry[key](*args, **kwargs): candidate_widget_types.append(key) n_matches = len(candidate_widget_types) if n_matches == 0: if self.default_widget_type is None: raise NoMatchError("No types match specified arguments and no default is set.") else: candidate_widget_types = [self.default_widget_type] elif n_matches > 1: raise MultipleMatchError(f"Too many candidate types identified ({n_matches})." "Specify enough keywords to guarantee unique type " "identification.") # Only one is found WidgetType = candidate_widget_types[0] return WidgetType(*args, **kwargs)
[docs] def register(self, WidgetType, validation_function=None, is_default=False): """ Register a widget with the factory. If ``validation_function`` is not specified, tests ``WidgetType`` for existence of any function in in the list ``self.validation_functions``, which is a list of strings which must be callable class attribute. Parameters ---------- WidgetType : `type` Widget to register. validation_function : `function`, optional Function to validate against. Defaults to None, which indicates that a classmethod in validation_functions is used. Defaults to `None`. is_default : `bool`, optional Sets WidgetType to be the default widget. Defaults to `False`. """ if is_default: self.default_widget_type = WidgetType elif validation_function is not None: if not callable(validation_function): raise AttributeError("Keyword argument 'validation_function' must be callable.") self.registry[WidgetType] = validation_function else: found = False for vfunc_str in self.validation_functions: if hasattr(WidgetType, vfunc_str): vfunc = getattr(WidgetType, vfunc_str) # check if classmethod: stackoverflow #19227724 _classmethod = inspect.ismethod(vfunc) and vfunc.__self__ is WidgetType if _classmethod: self.registry[WidgetType] = vfunc found = True break else: raise ValidationFunctionError("{}.{} must be a classmethod." .format(WidgetType.__name__, vfunc_str)) if not found: raise ValidationFunctionError("No proper validation function for class {} " "found.".format(WidgetType.__name__))
[docs] def unregister(self, WidgetType): """ Remove a widget from the factory's registry. """ self.registry.pop(WidgetType)
[docs] class NoMatchError(Exception): """ Exception for when no candidate class is found. """
[docs] class MultipleMatchError(Exception): """ Exception for when too many candidate classes are found. """
[docs] class ValidationFunctionError(AttributeError): """ Exception for when no candidate class is found. """