Skip to content

Commit 010444b

Browse files
committed
added persistent additional/excluded support to Info class
1 parent c1f4444 commit 010444b

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

docs/source/pcapkit/corekit/infoclass.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ in :pep:`557`.
1515
:param \*args: Arbitrary positional arguments.
1616
:param \*\*kwargs: Arbitrary keyword arguments.
1717

18+
.. automethod:: from_dict
19+
.. automethod:: to_dict
20+
1821
.. automethod:: __post_init__
1922

2023
.. autoattribute:: __additional__
@@ -23,3 +26,13 @@ in :pep:`557`.
2326
:no-value:
2427

2528
.. autodecorator:: pcapkit.corekit.infoclass.info_final
29+
30+
Meta Classes
31+
------------
32+
33+
.. autoclass:: pcapkit.corekit.infoclass.InfoMeta
34+
:members:
35+
:show-inheritance:
36+
37+
:param \*args: Arbitrary positional arguments.
38+
:param \*\*kwargs: Arbitrary keyword arguments.

pcapkit/corekit/infoclass.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
in :pep:`557`.
1111
1212
"""
13+
import abc
1314
import collections.abc
1415
import enum
1516
import itertools
@@ -140,7 +141,59 @@ def info_final(cls: 'ST', *, _finalised: 'bool' = True) -> 'ST':
140141
return final(cls)
141142

142143

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):
144197
"""Turn dictionaries into :obj:`object` like instances.
145198
146199
* :class:`Info` objects inherit from :obj:`dict` type

0 commit comments

Comments
 (0)