Source code for plasmapy.utils.datatype_factory_base

"""Functionality implementing the plasma class factory."""

# SunPy is released under a BSD-style open source license:

# Copyright (c) 2013-2018 The SunPy developers
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:

# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.

# * 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.

# 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
# HOLDER 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.

# Project     : https://github.com/sunpy/sunpy
# File        : sunpy/util/datatype_factory_base.py
# Commit hash : f6330eea602ea796b5b004dee283b8877b24da23
__all__ = [
    "BasicRegistrationFactory",
    "MultipleMatchError",
    "NoMatchError",
    "ValidationFunctionError",
]

import inspect


[docs] class BasicRegistrationFactory: """ Generalized registrable 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. .. attention:: |expect-api-changes| Attributes ---------- registry : `dict` Dictionary mapping classes (key) to function (value) which validates input. default_widget_type : `type` Class of the default widget. Defaults to `None`. validation_functions : `list` of `str` List of function names that are valid validation functions. Parameters ---------- default_widget_type : `type`, optional additional_validation_functions : `list` of `str`, optional List of strings corresponding to additional validation function names. Notes ----- A valid validation function must be a `classmethod` of the registered widget and must return `True` or `False`. """ def __init__( self, default_widget_type=None, additional_validation_functions=None, registry=None, ) -> None: self.registry = {} if registry is None else registry if additional_validation_functions is None: additional_validation_functions = [] 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): candidate_widget_types = [ key for key in self.registry if self.registry[key](*args, **kwargs) ] 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: # We'll need to switch from print() to using logging library print(candidate_widget_types) # noqa: T201 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: bool = False): """Register a widget with the factory. If ``validation_function`` is not specified, tests ``WidgetType`` for existence of any function in the ``validation_functions`` attribute, which is a list of strings which must be callable class attributes. Parameters ---------- WidgetType : `type` Widget to register. validation_function : function, optional Function to validate against. Defaults to `None`, which indicates that a `classmethod` in the ``validation_functions`` attribute is used. is_default : `bool`, optional Sets ``WidgetType`` to be the default widget. """ 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) is_not_classmethod = not ( inspect.ismethod(vfunc) and vfunc.__self__ is WidgetType ) if is_not_classmethod: raise ValidationFunctionError( f"{WidgetType.__name__}.{vfunc_str} must be a classmethod." ) self.registry[WidgetType] = vfunc found = True break if not found: raise ValidationFunctionError( "No proper validation function for class " f"{WidgetType.__name__} found." )
[docs] def unregister(self, WidgetType) -> None: """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."""