Passed
Push — main ( cee75c...37036d )
by Douglas
02:08
created

mandos.model.utils.reflection_utils   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 61
dl 0
loc 123
rs 10
c 0
b 0
f 0
wmc 15

8 Methods

Rating   Name   Duplication   Size   Complexity  
A ReflectionUtils.injection() 0 24 2
A ReflectionUtils.optional_args() 0 13 1
A ReflectionUtils._args() 0 10 1
A ReflectionUtils.required_args() 0 13 1
A ReflectionUtils.subclasses() 0 10 5
A ReflectionUtils.default_arg_values() 0 3 1
A ReflectionUtils.get_generic_arg() 0 24 3
A ReflectionUtils.subclass_dict() 0 3 1
1
import inspect
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import sys
3
import typing
4
from typing import Type, Optional, Mapping, Any
5
6
7
T = typing.TypeVar("T")
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
8
9
10
class InjectionError(LookupError):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
11
    """ """
12
13
14
class ReflectionUtils:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
15
    @classmethod
16
    def get_generic_arg(cls, clazz: Type[T], bound: Optional[Type[T]] = None) -> Type:
17
        """
18
        Finds the generic argument (specific TypeVar) of a :py:class:`~typing.Generic` class.
19
        **Assumes that ``clazz`` only has one type parameter. Always returns the first.**
20
21
        Args:
22
            clazz: The Generic class
23
            bound: If non-None, requires the returned type to be a subclass of ``bound`` (or equal to it)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
24
25
        Returns:
26
            The class
27
28
        Raises:
29
            AssertionError: For most errors
30
        """
31
        bases = clazz.__orig_bases__
32
        try:
33
            param = typing.get_args(bases[0])[0]
0 ignored issues
show
Bug introduced by
The Module typing does not seem to have a member named get_args.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
34
        except KeyError:
35
            raise AssertionError(f"Failed to get generic type on {cls}")
36
        if not issubclass(param, bound):
37
            raise AssertionError(f"{param} is not a {bound}")
38
        return param
39
40
    @classmethod
41
    def subclass_dict(cls, clazz: Type[T], concrete: bool = False) -> Mapping[str, Type[T]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
42
        return {c.__name__: c for c in cls.subclasses(clazz, concrete=concrete)}
43
44
    @classmethod
45
    def subclasses(cls, clazz, concrete: bool = False):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
46
        for subclass in clazz.__subclasses__():
47
            yield from cls.subclasses(subclass, concrete=concrete)
48
            if (
49
                not concrete
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
50
                or not inspect.isabstract(subclass)
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
51
                and not subclass.__name__.startswith("_")
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
52
            ):
53
                yield subclass
54
55
    @classmethod
56
    def default_arg_values(cls, func) -> Mapping[str, Optional[Any]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
57
        return {k: v.default for k, v in cls.optional_args(func).items()}
58
59
    @classmethod
60
    def required_args(cls, func):
61
        """
62
        Finds parameters that lack default values.
63
64
        Args:
65
            func: A function or method
66
67
        Returns:
68
            A dict mapping parameter names to instances of ``MappingProxyType``,
69
            just as ``inspect.signature(func).parameters`` does.
70
        """
71
        return cls._args(func, True)
72
73
    @classmethod
74
    def optional_args(cls, func):
75
        """
76
        Finds parameters that have default values.
77
78
        Args:
79
            func: A function or method
80
81
        Returns:
82
            A dict mapping parameter names to instances of ``MappingProxyType``,
83
            just as ``inspect.signature(func).parameters`` does.
84
        """
85
        return cls._args(func, False)
86
87
    @classmethod
88
    def _args(cls, func, req):
89
        signature = inspect.signature(func)
90
        return {
91
            k: v
92
            for k, v in signature.parameters.items()
93
            if req
94
            and v.default is inspect.Parameter.empty
95
            or not req
96
            and v.default is not inspect.Parameter.empty
97
        }
98
99
    @classmethod
100
    def injection(cls, fully_qualified: str, clazz: Type[T]) -> Type[T]:
101
        """
102
        Gets a **class** by its fully-resolved class name.
103
104
        Args:
105
            fully_qualified:
106
            clazz:
107
108
        Returns:
109
            The Type
110
111
        Raises:
112
            InjectionError: If the class was not found
113
        """
114
        s = fully_qualified
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
115
        mod = s[: s.rfind(".")]
116
        clz = s[s.rfind(".") :]
117
        try:
118
            return getattr(sys.modules[mod], clz)
119
        except AttributeError:
120
            raise InjectionError(
121
                f"Did not find {clazz} by fully-qualified class name {fully_qualified}"
122
            ) from None
123