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(f"{WidgetType.__name__}.{vfunc_str} must be a classmethod.")
if not found:
raise ValidationFunctionError(f"No proper validation function for class {WidgetType.__name__} "
"found.")
[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.
"""