Source code for spinn_utilities.overrides
# Copyright (c) 2017 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import inspect
[docs]class overrides(object):
"""
A decorator for indicating that a method overrides another method in
a superclass. This checks that the method does actually exist,
copies the doc-string for the method, and enforces that the method
overridden is specified, making maintenance easier.
"""
__slots__ = [
# The method in the superclass that this method overrides
"_superclass_method",
# True if the doc string is to be extended, False to set if not set
"_extend_doc",
# Any additional arguments required by the subclass method
"_additional_arguments",
# True if the subclass method may have additional defaults
"_extend_defaults",
# True if the name check is relaxed
"_relax_name_check",
# The name of the thing being overridden for error messages
"_override_name"
]
def __init__(
self, super_class_method, extend_doc=True,
additional_arguments=None, extend_defaults=False):
"""
:param super_class_method: The method to override in the superclass
:param bool extend_doc:
True the method doc string should be appended to the super-method
doc string, False if the documentation should be set to the
super-method doc string only if there isn't a doc string already
:param iterable(str) additional_arguments:
Additional arguments taken by the subclass method over the
superclass method, e.g., that are to be injected
:param bool extend_defaults:
Whether the subclass may specify extra defaults for the parameters
"""
self._superclass_method = super_class_method
self._extend_doc = bool(extend_doc)
self._extend_defaults = bool(extend_defaults)
self._relax_name_check = False
self._override_name = "super class method"
if additional_arguments is None:
self._additional_arguments = {}
else:
self._additional_arguments = frozenset(additional_arguments)
if isinstance(super_class_method, property):
self._superclass_method = super_class_method.fget
@staticmethod
def __match_defaults(default_args, super_defaults, extend_ok):
if default_args is None:
return super_defaults is None
elif super_defaults is None:
return extend_ok
if extend_ok:
return len(default_args) >= len(super_defaults)
return len(default_args) == len(super_defaults)
def __verify_method_arguments(self, method):
"""
Check that the arguments match.
"""
method_args = inspect.getfullargspec(method)
super_args = inspect.getfullargspec(self._superclass_method)
all_args = [
arg for arg in method_args.args
if arg not in self._additional_arguments]
default_args = None
if method_args.defaults is not None:
default_args = [
arg for arg in method_args.defaults
if arg not in self._additional_arguments]
if len(all_args) != len(super_args.args):
raise AttributeError(
f"Method has {len(method_args.args)} arguments but "
f"{self._override_name} has {len(super_args.args)} arguments")
for arg, super_arg in zip(all_args, super_args.args):
if arg != super_arg:
raise AttributeError(f"Missing argument {super_arg}")
if not self.__match_defaults(
default_args, super_args.defaults, self._extend_defaults):
raise AttributeError(
f"Default arguments don't match {self._override_name}")
[docs] def __call__(self, method):
"""
Apply the decorator to the given method.
"""
# Check and fail if this is a property
if isinstance(method, property):
raise AttributeError(
f"Please ensure that the {self.__class__.__name__} decorator "
"is the last decorator before the method declaration")
# Check that the name matches
if (not self._relax_name_check and
method.__name__ != self._superclass_method.__name__):
raise AttributeError(
f"{self._override_name} name "
f"{self._superclass_method.__name__} does not match "
f"{method.__name__}. Ensure {self.__class__.__name__} is the "
"last decorator before the method declaration")
# Check that the arguments match (except for __init__ as this might
# take extra arguments or pass arguments not specified)
if method.__name__ != "__init__":
self.__verify_method_arguments(method)
if (self._superclass_method.__doc__ is not None and
method.__doc__ is None):
method.__doc__ = self._superclass_method.__doc__
elif (self._extend_doc and
self._superclass_method.__doc__ is not None):
method.__doc__ = (
self._superclass_method.__doc__ + method.__doc__)
return method