Completed
Pull Request — master (#49)
by Max
03:39
created

structured_data._adt.constructor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 77
dl 0
loc 129
rs 10
c 0
b 0
f 0
wmc 13
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: str, static: typing.Any) -> bool:
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 DummyS:
24
    """Dummy class to help keep Sum and Product separate."""
25
26
    __slots__ = ()
27
28
29
class DummyP:
30
    """Dummy class to help keep Sum and Product separate."""
31
32
    __slots__ = ()
33
34
35
class SumBase(DummyS, DummyP):
36
    """Dummy class to help keep Sum and Product separate."""
37
38
    __slots__ = ()
39
40
41
class ProductBase(DummyP, DummyS):
42
    """Dummy class to help keep Sum and Product separate."""
43
44
    __slots__ = ()
45
46
47
class ADTConstructor:
48
    """Base class for ADT Constructor classes."""
49
50
    __slots__ = ()
51
52
    def __dir__(self) -> typing.List[str]:
53
        return [
54
            attribute
55
            for attribute in super().__dir__()
56
            if _should_include(attribute, inspect.getattr_static(self, attribute))
57
        ]
58
59
    __eq__ = object.__eq__
60
    __ne__ = object.__ne__
61
    __hash__ = object.__hash__
62
63
64
SHADOWED_ATTRIBUTES = {
65
    "__add__",
66
    "__contains__",
67
    "__getitem__",
68
    "__iter__",
69
    "__len__",
70
    "__mul__",
71
    "__rmul__",
72
    "count",
73
    "index",
74
    "__lt__",
75
    "__le__",
76
    "__gt__",
77
    "__ge__",
78
}
79
80
81
for _attribute in SHADOWED_ATTRIBUTES:
82
    setattr(ADTConstructor, _attribute, None)
83
84
85
class SumMember:
86
    """Accessor for Sum subclass constructor, only accessible from the base."""
87
88
    def __init__(self, subcls: type):
89
        self.subcls = subcls
90
91
    def __get__(self, obj: typing.Optional[_T], cls: typing.Type[_T]) -> type:
92
        if cls is ADT_BASES[self.subcls] and obj is None:
93
            return self.subcls
94
        raise AttributeError("Can only access adt members through base class.")
95
96
97
ADT_BASES: typing.MutableMapping[type, type] = weakref.WeakKeyDictionary()
98
99
100
def make_constructor(
101
    _cls: typing.Type[_T],
102
    name: str,
103
    args: typing.Tuple,
104
    subclass_order: typing.List[typing.Type[_T]],
105
) -> None:
106
    """Create a subclass of _cls with a constructor generated from args."""
107
    length = len(args)
108
109
    # pylint: disable=missing-docstring
110
    class Constructor(_cls, ADTConstructor, tuple):  # type: ignore
111
        __doc__ = f"""Auto-generated subclass {name} of ADT {_cls.__qualname__}.
112
113
        Takes {length} argument{'' if length == 1 else 's'}.
114
        """
115
116
        __slots__ = ()
117
118
        def __new__(cls, /, *args: typing.Any):  # noqa: E225
119
            if len(args) != length:
120
                raise ValueError
121
            return super().__new__(cls, args)
122
123
    ADT_BASES[Constructor] = _cls
124
125
    Constructor.__name__ = name
126
    Constructor.__qualname__ = "{qualname}.{name}".format(
127
        qualname=_cls.__qualname__, name=name
128
    )
129
130
    setattr(_cls, name, SumMember(Constructor))
131
    subclass_order.append(Constructor)
132
133
    annotations_ = {f"_{index}": arg for (index, arg) in enumerate(args)}
134
    parameters = [
135
        inspect.Parameter(name, inspect.Parameter.POSITIONAL_ONLY, annotation=arg)
136
        for (name, arg) in annotations_.items()
137
    ]
138
    annotations_["return"] = _cls.__qualname__
139
140
    Constructor.__new__.__signature__ = inspect.Signature(  # type: ignore
141
        [inspect.Parameter("cls", inspect.Parameter.POSITIONAL_ONLY)] + parameters,
142
        return_annotation=_cls.__qualname__,
143
    )
144
    Constructor.__new__.__annotations__ = annotations_
145
146
147
def make_constructors(cls: typing.Type[_T]) -> typing.Tuple[typing.Type[_T], ...]:
148
    """Return all of the constructors of the given class in definition order."""
149
    subclass_order: typing.List[typing.Type[_T]] = []
150
151
    for name, args in annotations.sum_args_from_annotations(cls).items():
152
        make_constructor(cls, name, args, subclass_order)
153
154
    return tuple(subclass_order)
155
156
157
__all__ = ["ADTConstructor", "make_constructor"]
158