Passed
Push — master ( af9889...f446f0 )
by Max
01:14
created

structured_data._adt.constructor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 13
eloc 77
dl 0
loc 128
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A ADTConstructor.__dir__() 0 5 1
A SumMember.__get__() 0 4 3
A SumMember.__init__() 0 2 1

3 Functions

Rating   Name   Duplication   Size   Complexity  
A make_constructor() 0 40 2
A _should_include() 0 6 4
A make_constructors() 0 8 2
1
"""Base class for Sum subclasses and Product.
2
3
Blocks access to many built-in methods.
4
"""
5
6
import inspect
7
import typing
8
import weakref
9
10
from . import annotations
11
12
_T = typing.TypeVar("_T")
13
14
15
def _should_include(name, static):
16
    if name in SHADOWED_ATTRIBUTES and static is None:
17
        return False
18
    if isinstance(static, SumMember):
19
        return False
20
    return True
21
22
23
class ADTConstructor:
24
    """Base class for ADT Constructor classes."""
25
26
    __slots__ = ()
27
28
    def __dir__(self):
29
        return [
30
            attribute
31
            for attribute in super().__dir__()
32
            if _should_include(attribute, inspect.getattr_static(self, attribute))
33
        ]
34
35
    __eq__ = object.__eq__
36
    __ne__ = object.__ne__
37
    __hash__ = object.__hash__
38
39
40
SHADOWED_ATTRIBUTES = {
41
    "__add__",
42
    "__contains__",
43
    "__getitem__",
44
    "__iter__",
45
    "__len__",
46
    "__mul__",
47
    "__rmul__",
48
    "count",
49
    "index",
50
    "__lt__",
51
    "__le__",
52
    "__gt__",
53
    "__ge__",
54
}
55
56
57
for _attribute in SHADOWED_ATTRIBUTES:
58
    setattr(ADTConstructor, _attribute, None)
59
60
61
class SumMember:
62
    """Accessor for Sum subclass constructor, only accessible from the base."""
63
    def __init__(self, subcls: type):
64
        self.subcls = subcls
65
66
    def __get__(self, obj, cls):
67
        if cls is ADT_BASES[self.subcls] and obj is None:
68
            return self.subcls
69
        raise AttributeError("Can only access adt members through base class.")
70
71
72
ADT_BASES: typing.MutableMapping[type, type] = weakref.WeakKeyDictionary()
73
74
75
def make_constructor(_cls, name: str, args: typing.Tuple, subclass_order):
76
    """Create a subclass of _cls with a constructor generated from args."""
77
    length = len(args)
78
79
    # pylint: disable=missing-docstring
80
    class Constructor(_cls, ADTConstructor, tuple):  # type: ignore
81
        __doc__ = f"""Auto-generated subclass {name} of ADT {_cls.__qualname__}.
82
83
        Takes {length} argument{'' if length == 1 else 's'}.
84
        """
85
86
        __slots__ = ()
87
88
        def __new__(cls, *args):
89
            if len(args) != length:
90
                raise ValueError
91
            return super().__new__(cls, args)
92
93
    ADT_BASES[Constructor] = _cls
94
95
    Constructor.__name__ = name
96
    Constructor.__qualname__ = "{qualname}.{name}".format(
97
        qualname=_cls.__qualname__, name=name
98
    )
99
100
    setattr(_cls, name, SumMember(Constructor))
101
    subclass_order.append(Constructor)
102
103
    annotations_ = {f"_{index}": arg for (index, arg) in enumerate(args)}
104
    parameters = [
105
        inspect.Parameter(name, inspect.Parameter.POSITIONAL_ONLY, annotation=arg)
106
        for (name, arg) in annotations_.items()
107
    ]
108
    annotations_["return"] = _cls.__qualname__
109
110
    Constructor.__new__.__signature__ = inspect.Signature(  # type: ignore
111
        [inspect.Parameter("cls", inspect.Parameter.POSITIONAL_ONLY)] + parameters,
112
        return_annotation=_cls.__qualname__,
113
    )
114
    Constructor.__new__.__annotations__ = annotations_
115
116
117
def make_constructors(cls: typing.Type[_T]):
118
    """Return all of the constructors of the given class in definition order."""
119
    subclass_order: typing.List[typing.Type[_T]] = []
120
121
    for name, args in annotations.sum_args_from_annotations(cls).items():
122
        make_constructor(cls, name, args, subclass_order)
123
124
    return tuple(subclass_order)
125
126
127
__all__ = ["ADTConstructor", "make_constructor"]
128