|
10 | 10 | in :pep:`557`. |
11 | 11 |
|
12 | 12 | """ |
| 13 | +import abc |
13 | 14 | import collections.abc |
14 | 15 | import enum |
15 | 16 | import itertools |
@@ -140,7 +141,59 @@ def info_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST': |
140 | 141 | return final(cls) |
141 | 142 |
|
142 | 143 |
|
143 | | -class Info(Mapping[str, VT], Generic[VT]): |
| 144 | +class InfoMeta(abc.ABCMeta): |
| 145 | + """Meta class to add dynamic support to :class:`Info`. |
| 146 | +
|
| 147 | + This meta class is used to generate necessary attributes for the |
| 148 | + :class:`Info` class. It can be useful to reduce runtime generation |
| 149 | + cost as well as caching already generated attributes. |
| 150 | +
|
| 151 | + * :attr:`Info.__additional__` and :attr:`Info.__excluded__` are |
| 152 | + lists of additional and excluded field names, which are used to |
| 153 | + determine certain names to be included or excluded from the field |
| 154 | + dictionary. They will be automatically populated from the class |
| 155 | + attributes of the :class:`Info` class and its base classes. |
| 156 | +
|
| 157 | + .. note:: |
| 158 | +
|
| 159 | + This is implemented thru the :meth:`__new__` method, which will |
| 160 | + inherit the additional and excluded field names from the base |
| 161 | + classes, as well as populating the additional and excluded field |
| 162 | + from the subclass attributes. |
| 163 | +
|
| 164 | + .. code-block:: python |
| 165 | +
|
| 166 | + class A(Info): |
| 167 | + __additional__ = ['a', 'b'] |
| 168 | +
|
| 169 | + class B(A): |
| 170 | + __additional__ = ['c', 'd'] |
| 171 | +
|
| 172 | + class C(B): |
| 173 | + __additional__ = ['e', 'f'] |
| 174 | +
|
| 175 | + print(A.__additional__) # ['a', 'b'] |
| 176 | + print(B.__additional__) # ['a', 'b', 'c', 'd'] |
| 177 | + print(C.__additional__) # ['a', 'b', 'c', 'd', 'e', 'f'] |
| 178 | +
|
| 179 | + """ |
| 180 | + |
| 181 | + def __new__(cls, name: 'str', bases: 'tuple[type, ...]', attrs: 'dict[str, Any]', **kwargs: 'Any') -> 'Type[Info]': |
| 182 | + if '__additional__' not in attrs: |
| 183 | + attrs['__additional__'] = [] |
| 184 | + if '__excluded__' not in attrs: |
| 185 | + attrs['__excluded__'] = [] |
| 186 | + |
| 187 | + for base in bases: |
| 188 | + if hasattr(base, '__additional__'): |
| 189 | + attrs['__additional__'].extend( |
| 190 | + name for name in base.__additional__ if name not in attrs['__additional__']) |
| 191 | + if hasattr(base, '__excluded__'): |
| 192 | + attrs['__excluded__'].extend(name for name in base.__excluded__ if name not in attrs['__excluded__']) |
| 193 | + return super().__new__(cls, name, bases, attrs, **kwargs) # type: ignore[return-value] |
| 194 | + |
| 195 | + |
| 196 | +class Info(Mapping[str, VT], Generic[VT], metaclass=InfoMeta): |
144 | 197 | """Turn dictionaries into :obj:`object` like instances. |
145 | 198 |
|
146 | 199 | * :class:`Info` objects inherit from :obj:`dict` type |
|
0 commit comments