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[VT](dict_=None, **kwargs)[source]

Bases: ReduceAsProtocol2, 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.1.post8: Iterating an instance skips the names listed in __excluded__.

A key whose name is a dunder, such as __next_type__, is metadata. Iterating an instance, len() and to_dict() leave such a key out unless __included__ names it, and carry every other key; __excluded__ names the keys that repr, str and the dumps of pcapkit.dumpkit leave out besides. A subclass extends either list with its own names, as for __additional__.

Changed in version 1.5.0: __excluded__ names the keys left out of repr, str and the dumps only, and __included__ the dunder keys that iterating an instance, len() and to_dict() carry (#2003).

Parameters:
  • dict_ – Source data, read as by from_dict(), except that no value is rebuilt into the Info subclass its key’s annotation names.

  • **kwargs – Arbitrary keyword arguments.

__additional__: list[str]

List of additional built-in names.

__excluded__: list[str]

List of names of keys left out of repr, str and the dumps of pcapkit.dumpkit – metadata such as __next_type__, which to_dict() may still carry, see __included__. Finalisation adds the names in __builtin__, though repr, str and the dumps write a field renamed for clashing with one of them, such as a reassembled datagram’s packet, under its own name (#2733).

__included__: list[str]

List of names of dunder keys that iterating an instance, len() and to_dict() carry – metadata such as __next_type__, which a rebuild from the dict reads. Every other dunder key is dropped from all three, though indexing still reads it; a key that is no dunder is carried regardless. A subclass extends the list with its own names, as for __excluded__, so a custom Info holding dunder keys of its own must list them here for to_dict() to keep them (#2003).

__map__: dict[str, str]

Mapping of name conflicts with builtin methods (original names to transformed names).

__map_reverse__: dict[str, str]

Mapping of name conflicts with builtin methods (transformed names to original names).

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 (VT) – Arbitrary positional arguments.

  • **kwargs (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 the items method of collections.abc.Mapping returns.

Return type:

ItemsView[str, VT] | Iterator[tuple[str, 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 leave them intact rather than converting them into dict.

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). It leaves out the dunder keys __included__ does not name, carries the keys in __excluded__, and records those as its own __excluded__ for the dumpers to leave out (#2003).

Changed in version 1.6.0: The __excluded__ it records leaves out a field renamed for clashing with a builtin name, such as a reassembled datagram’s packet, so the dumpers write that field under its own name (#2733).

Return type:

OrderedMultiDict[str, 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 (ST) – Info class.

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

Return type:

ST

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[KT, VT](mapping=None)[source]

Bases: ReduceToItems, _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.

to_dict(flat=None)[source]

Return the contents as a plain mapping.

Parameters:

flat (bool | None) – If omitted, a plain MultiDict of every value, as Info.to_dict() writes this object. Otherwise MultiDict.to_dict’s dict, of the first value of each key if True, or of the list of them if False.

Return type:

Any

class pcapkit.corekit.infoclass.OrderedMultiInfo[KT, VT](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.

to_dict(flat=None)[source]

Return the contents as a plain mapping.

Parameters:

flat (bool | None) – If omitted, a plain OrderedMultiDict of every value, in order, as Info.to_dict() writes this object – the same _OrderedMultiDict it returns. Otherwise MultiDict.to_dict’s dict, of the first value of each key if True, or of the list of them if False.

Return type:

Any

Internal Definitions

class pcapkit.corekit.infoclass.InfoMeta(name, bases, attrs, **kwargs)[source]

Bases: ABCMeta

Meta class to add dynamic support to Info.

  • Info.__additional__, Info.__excluded__ and Info.__included__ list the field names treated as built-in names, left out of repr and the dumps, and carried as metadata by Info.to_dict(), respectively.

    Note

    __new__() appends each base’s names to the subclass’s own:

    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__)  # ['c', 'd', 'a', 'b']
    print(C.__additional__)  # ['e', 'f', 'c', 'd', 'a', 'b']
    
static __new__(cls, name, bases, attrs, **kwargs)[source]

Create the class, extending its Info.__additional__, Info.__excluded__ and Info.__included__ with every base’s.

Parameters:
  • name (str) – Class name.

  • bases (tuple[type, ...]) – Base classes.

  • attrs (dict[str, Any]) – Class namespace.

  • **kwargs (Any) – Arbitrary keyword arguments, passed on to type.

Return type:

Type[Info]

class pcapkit.corekit.infoclass.FinalisedState(*values)[source]

Bases: EnumLookup, IntEnum

Finalised state.

Built on EnumLookup per GitHub issue #877’s ruling that every non-registry enumeration shares that lookup contract; this class defines neither get nor _missing_ of its own.

NONE = 1

Not finalised.

BASE = 2

Base class.

FINAL = 3

Finalised. A class reaching this state is also handed to final(), so a correctly finalised class carries both markers – and they answer two different questions, which is why both are read rather than one standing in for the other. __final__ answers “may this be subclassed?”, for Info.__init_subclass__(); this state answers “has info_final() already generated the attributes?”, for info_final()’s own re-entry check. Carrying __final__ without this state is the mismatch Info.__new__() refuses: marked final by hand, never finalised, and so missing the generated __init__ that makes the class usable at all.

class pcapkit.corekit.infoclass._MultiInfo[KT, VT](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 its subclasses, MultiInfo and OrderedMultiInfo:

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.

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

Create the instance with the empty multi-mapping __update__() adds to.

Return type:

Self

__update__(dict_=None, **kwargs)[source]

Add every pair to the multi-mapping, so that a repeated key keeps every value.

Parameters:
  • dict_ (Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None) – Source pairs, as a mapping or an iterable of (key, value) pairs; a list value of a mapping counts as one pair, and a MultiDict gives every value of each key.

  • **kwargs (Any) – More pairs, added after dict_.

Return type:

None

__setattr__(name, value)[source]

Admit an attribute write only while __new__() or __update__() is writing.

Parameters:
  • name (str) – Attribute name.

  • value (Any) – Attribute value.

Raises:

UnsupportedCall – Outside those writes, as assignment to any Info does.

class pcapkit.corekit.infoclass._OrderedMultiDict[KT, VT](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 would compare != as well as == – and, since __eq__ compares the values with !=, two exports holding a nested export would compare 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.

__ne__(other)

self != other, as the negation of self == other.

The __ne__ that _OrderedMultiDict and OrderedMultiInfo add to Werkzeug’s OrderedMultiDict. A NotImplemented from __eq__ is passed on, so that Python asks other in turn, exactly as it does for ==.

Return type:

bool

__repr__()[source]

Write the export as the OrderedMultiDict it is, with every pair.

Return type:

str

pcapkit.corekit.infoclass._ne(self, other)[source]

self != other, as the negation of self == other.

The __ne__ that _OrderedMultiDict and OrderedMultiInfo add to Werkzeug’s OrderedMultiDict. A NotImplemented from __eq__ is passed on, so that Python asks other in turn, exactly as it does for ==.

Return type:

bool

Type Variables

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