Info Class

pcapkit.corekit.infoclass contains the dict-like class Info, modelled on dataclasses.dataclass() (PEP 557), and the immutable multi-mapping classes MultiInfo and OrderedMultiInfo that a finalised Info holds its option lists in.

class pcapkit.corekit.infoclass.Info(dict_=None, **kwargs)[source]

Bases: Mapping[str, VT], Generic[VT]

Turn dictionaries into object like instances.

  • Info objects are Mapping instances, not dict subclasses

  • Info objects are iterable, and support the read-only dict interface

  • Info objects are immutable, thus cannot set or delete attributes after initialisation

Important

Info will attempt to rename keys with the same names as the class’s builtin methods, and store the mapping information in the __map__ and __map_reverse__ attributes. However, when accessing such renamed keys, the original key name should always be used, i.e., such renaming is totally transparent to the user.

Changed in version 1.3.3: Iterating an instance skips the names listed in __excluded__.

Parameters:
  • *args – Arbitrary positional arguments.

  • **kwargs – Arbitrary keyword arguments.

__additional__: list[str]

List of additional built-in names.

__excluded__: list[str]

List of names to be excluded from dict conversion.

classmethod __init_subclass__(*args, **kwargs)[source]

Refuse to derive from a finalised info class.

final() is a promise to the type checker and nothing more: it records __final__ on the class and leaves the interpreter free to subclass it anyway. Every class info_final() finalises carries a generated __init__ built from the annotations that were visible at that moment, so a subclass added afterwards inherits a constructor that does not know about its own fields – silently, and with the __builtin__ and __excluded__ sets of its parent. This turns that promise into a rule the interpreter keeps.

Parameters:
  • *args (Any) – Arbitrary positional arguments.

  • **kwargs (Any) – Arbitrary keyword arguments in class definition.

Raises:

InfoError – If any class in cls’s ancestry carries the __final__ marker in its own __dict__.

static __new__(cls, *args, **kwargs)[source]

Create a new instance.

The class will try to automatically generate __init__ method with the same signature as specified in class variables’ type annotations, which is inspired by PEP 557 (dataclasses).

Note

Whether cls still needs finalising is read from its own __dict__, never inherited (GitHub issue #1490). Each class is finalised on its first construction, whatever its ancestors’ state, so a subclass of a FinalisedState.BASE or FinalisedState.FINAL ancestor still gets its own __builtin__, __excluded__ and generated __init__, and a @final class under a BASE ancestor is refused like any other.

Parameters:
  • *args (TypeVar(VT)) – Arbitrary positional arguments.

  • **kwargs (TypeVar(VT)) – Arbitrary keyword arguments.

Raises:

InfoError – If cls was marked with final() but never finalised by info_final(), i.e. it carries __final__ in its own __dict__ without FinalisedState.FINAL.

Return type:

Self

__post_init__()[source]

Customisation method to be called after initialisation.

items(multi=False)[source]

Return the (key, value) pairs.

Parameters:

multi (bool) – If set to True, an iterator with a pair for each value of each key, as to_dict() writes them. Otherwise a view of the pairs of the first value of each key, as Mapping.items returns.

Return type:

ItemsView[str, TypeVar(VT)] | Iterator[tuple[str, TypeVar(VT)]]

classmethod from_dict(dict_=None, **kwargs)[source]

Create a new instance.

  • If dict_ is present and has a .keys() method, then does: for k in dict_: self[k] = dict_[k].

  • If dict_ is present and has no .keys() method, then does: for k, v in dict_: self[k] = v.

  • If dict_ is not present, then does: for k, v in kwargs.items(): self[k] = v.

  • If dict_ is a MultiDict, a key it holds more than once keeps every value, as to_dict() returns them: the first is the value of the key, and items(multi=True) yields all of them.

A mapping given for a key whose type annotation resolves at runtime to an Info subclass (or to Optional of one) is rebuilt into that class, so cls.from_dict(info.to_dict()) restores such nested values. A key without such an annotation keeps the mapping as is – see to_dict().

Every MultiDict or OrderedMultiDict value is stored as a MultiInfo or OrderedMultiInfo respectively, or as the subclass of either that the key’s annotation names, such as Settings.

The rebuild is as unchecked as the rest of this method: a mapping whose keys are not the nested class’s own is accepted and becomes an instance carrying those keys, exactly as a direct call to the nested class’s own from_dict() would. Validating it here would reject data that Info(**kwargs) has always accepted, so the shape of the mapping stays the caller’s responsibility.

Parameters:
Return type:

Self

to_dict()[source]

Convert Info into dict.

The dict is an OrderedMultiDict of the fields, in order – a _OrderedMultiDict, i.e. one whose != is the negation of its ==, so two equal exports compare != False. A key held more than once – as given by a MultiDict to from_dict(), e.g. a repeated IPv6 extension header – keeps every value, in place, for items(multi=True) and getlist(), while indexing sees the first. A nested Info becomes its own to_dict(): an OrderedMultiDict for a field model or an OrderedMultiInfo, and a MultiDict for a MultiInfo; the values those two hold are not converted.

Note

dict(result), {**result} and json.dumps() see the first value of each key only, and silently drop the rest of a repeated key; read every value with items(multi=True) or getlist(). The result compares equal to another MultiDict only, never to a plain dict.

Important

We only convert nested Info objects into dict if they are the direct value of the Info object’s attribute. Should such Info objects be nested within other data, types, such as list, tuple, set, etc., we shall not convert them into dict and remain them intact.

Note

from_dict() reverses the flattening only for keys whose type annotation resolves at runtime to an Info subclass. For any other key – every key of a bare Info, and annotations naming a type imported only under TYPE_CHECKING – the conversion is one-way, and the nested value comes back as the OrderedMultiInfo that from_dict() stores the OrderedMultiDict it was written as in.

Changed in version 1.5.0: Returns an OrderedMultiDict rather than a dict, so a key held more than once keeps every value (#1484).

Return type:

OrderedMultiDict[str, TypeVar(VT)]

@pcapkit.corekit.infoclass.info_final(cls, *, _finalised=True)[source]

Finalise info class.

This decorator function is used to generate necessary attributes and methods for the decorated Info class. It can be useful to reduce runtime generation time as well as caching already generated attributes.

Notes

The decorator should only be used on the final class. Applying it with _finalised=True seals the class against subclassing, which Info.__init_subclass__() enforces, and marks it with final() – so @info_final implies @final and there is never a reason to write both. Writing both is harmless in either order, though; what is not is @final without this decorator, which Info.__new__() refuses – provided @final is final() (or, on Python 3.11 and up, typing.final() directly). Below 3.11, plain typing.final() does not set __final__ at all (gh-90500), so a class marked with a user’s own from typing import final on 3.10 is not refused: there is nothing on the class for Info.__new__() to read.

Applying this decorator to the same class a second time only warns: the first application already did the work, so the duplicate is redundant rather than wrong, and the class comes back finalised and usable.

Parameters:
  • cls (TypeVar(ST, bound= Type[Info])) – Info class.

  • _finalised (bool) – Whether to make the info class as finalised.

Return type:

TypeVar(ST, bound= Type[Info])

Returns:

Finalised info class.

Warns:

pcapkit.utilities.warnings.InfoWarning – If cls has already been finalised by this function, i.e. it carries FinalisedState.FINAL in its own __dict__. The class is returned untouched.

Changed in version 1.5.0: @final is enforced at runtime: a class marked with it but never passed through this decorator is refused at first construction (#778).

class pcapkit.corekit.infoclass.MultiInfo(mapping=None)[source]

Bases: _MultiInfo[KT, VT], MultiDict[KT, VT]

An immutable MultiDict that is an Info.

It holds every value of a key, as a MultiDict does, but refuses every mutation, as Info does, with UnsupportedCall (see _MultiInfo for how it is both).

A finalised Info holds every MultiDict value as one of these, and to_dict() – its own and its holder’s – turns it back into a MultiDict.

Parameters:

mapping (Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None) – The initial value, as for MultiDict.

Added in version 1.5.0.

class pcapkit.corekit.infoclass.OrderedMultiInfo(mapping=None)[source]

Bases: _MultiInfo[KT, VT], OrderedMultiDict[KT, VT]

An immutable OrderedMultiDict that is an Info.

As MultiInfo, but keeping the order of every value across keys, as an OrderedMultiDict does – i.e. wire order for an option list.

A finalised Info holds every OrderedMultiDict value as one of these, and to_dict() – its own and its holder’s – turns it back into an OrderedMultiDict.

Parameters:

mapping (Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None) – The initial value, as for OrderedMultiDict.

Added in version 1.5.0.

Internal Definitions

class pcapkit.corekit.infoclass.InfoMeta(name: str, bases: tuple[type, ...], attrs: dict[str, Any], **kwargs: Any)[source]

Bases: ABCMeta

Meta class to add dynamic support to Info.

This meta class is used to generate necessary attributes for the Info class. It can be useful to reduce runtime generation cost as well as caching already generated attributes.

  • Info.__additional__ and Info.__excluded__ are lists of additional and excluded field names, which are used to determine certain names to be included or excluded from the field dictionary. They will be automatically populated from the class attributes of the Info class and its base classes.

    Note

    This is implemented thru the __new__() method, which will inherit the additional and excluded field names from the base classes, as well as populating the additional and excluded field from the subclass attributes.

    class A(Info):
        __additional__ = ['a', 'b']
    
    class B(A):
        __additional__ = ['c', 'd']
    
    class C(B):
        __additional__ = ['e', 'f']
    
    print(A.__additional__)  # ['a', 'b']
    print(B.__additional__)  # ['a', 'b', 'c', 'd']
    print(C.__additional__)  # ['a', 'b', 'c', 'd', 'e', 'f']
    
class pcapkit.corekit.infoclass._MultiInfo(mapping=None)[source]

Bases: Info[VT], Generic[KT, VT]

An Info that holds its values as a multi-mapping.

The common base of MultiInfo and OrderedMultiInfo. Each of those also derives from the multidict class it is named after, after Info, so it is an Info – isinstance() holds, and it inherits Info.__new__()’s finalisation, Info.from_dict(), Info.__init_subclass__() and Info.__additional__/Info.__excluded__ – while its values live in the multi-mapping rather than in __dict__.

That is the whole carve-out from Info, and it is confined to this class and the two below it:

  • __new__() gives the multi-mapping its empty storage, and __update__() adds every pair to it, so that the inherited Info.from_dict() builds one; info_final() generates no __init__ for such a class (it has no declared fields);

  • __setattr__() admits the multi-mapping’s own bookkeeping, and only while one of those two is writing;

  • every mutator of the multi-mapping raises UnsupportedCall, as assignment to an Info does;

  • to_dict() exports the plain multi-mapping, so that cls.from_dict(info.to_dict()) == info;

  • MultiInfo and OrderedMultiInfo bind the mapping protocol back to their multi-mapping class, which Info and Mapping precede in the MRO.

A key also reads as an attribute, giving its first value: a str key by its name, an Enum key by its member name. A name the class itself defines, such as items, is a method and not a key.

class pcapkit.corekit.infoclass._OrderedMultiDict(mapping=None)[source]

Bases: OrderedMultiDict[KT, VT]

The OrderedMultiDict that Info.to_dict() returns.

It adds one method, __ne__() (_ne()). The multidict classes are Werkzeug’s, kept verbatim, and Werkzeug’s OrderedMultiDict defines __eq__ but no __ne__: != falls back to dict’s, which compares the internal buckets, so two equal exports compared != as well as == – and, since __eq__ compares the values with !=, two exports holding a nested export compared unequal (#1484). Its copies, deep copies and pickles stay of this class, as Werkzeug’s build them from type(self), and __repr__() writes it as the OrderedMultiDict it is.

Type Variables

pcapkit.corekit.infoclass.KT: Any
pcapkit.corekit.infoclass.VT: Any
pcapkit.corekit.infoclass.ST: Type[pcapkit.corekit.infoclass.Info]