Source code for spinn_utilities.require_subclass
# Copyright (c) 2021 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.
from typing import Any, Callable, Dict, Type
class _RequiresSubclassTypeError(TypeError):
"""
A special exception to handle just the case where the decorator is applied
so that we can easily figure out when to pass on the extra meta-arguments.
Do not use outside this file. Outsiders should just see ``TypeError``.
"""
[docs]
def require_subclass(required_class: Type) -> Callable[[Type], Type]:
"""
Decorator that arranges for subclasses of the decorated class to
require that they are also subclasses of the given class.
Usage example::
@require_subclass(AbstractVertex)
class AbstractVirtual(object):
...
:param type required_class:
The class that the subclass of the decorated class must be an instance
of (if that subclass is concrete).
"""
# Beware! This is all deep shenanigans!
#
# The __init_subclass__ stuff is from
# https://stackoverflow.com/a/45400374/301832
# The setattr() call is from:
# https://stackoverflow.com/a/533583/301832
# The classmethod() call is from:
# https://stackoverflow.com/a/17930262/301832
# The use of __class__ to enable super() to work is from:
# https://stackoverflow.com/a/43779009/301832
# The need to do this as a functional decorator is my own discovery;
# without it, some very weird interactions with meta classes happen and I
# really don't want to debug that stuff.
def decorate(target_class: Type) -> Type:
# pylint: disable=unused-variable
__class__ = target_class # @ReservedAssignment # noqa: F841
def __init_subclass__(
cls: Type, allow_derivation: bool = False,
**kwargs: Dict[str, Any]) -> None:
if not issubclass(cls, required_class) and not allow_derivation:
raise _RequiresSubclassTypeError(
f"{cls.__name__} must be a subclass "
f"of {required_class.__name__} and the derivation was not "
"explicitly allowed with allow_derivation=True")
try:
super().__init_subclass__(**kwargs) # type: ignore[misc]
except _RequiresSubclassTypeError:
super().__init_subclass__( # type: ignore[misc]
allow_derivation=allow_derivation, **kwargs)
setattr(target_class, '__init_subclass__',
classmethod(__init_subclass__))
return target_class
return decorate