diff --git a/docs/source/contributing/conventions/documentation.rst b/docs/source/contributing/conventions/documentation.rst index e78fbbea0a..36165d8755 100644 --- a/docs/source/contributing/conventions/documentation.rst +++ b/docs/source/contributing/conventions/documentation.rst @@ -295,9 +295,9 @@ Format and Mechanics adding coverage drops it from every source distribution. * **Prose names the real defining module, not a re-export.** One strand of the consistency sweep on :doc:`the roadmap `. The sentinels are the - worked example: all four are defined in :mod:`pcapkit.corekit.sentinels` and - re-exported by the modules that use them, and :ref:`sentinel-convention`'s table - names the defining module rather than any of the re-exports. + worked example: all four are defined in :mod:`pcapkit.corekit.sentinels`, and + :ref:`sentinel-convention`'s table names it rather than any of the modules that + use them, which no longer re-export them (:issue:`1744`). * **A verbatim upstream port is exempt from these conventions.** :ref:`sentinel-convention` already carries the case that settled it, the ``cached_property`` backport in :file:`pcapkit/utilities/compat.py`: the value of a diff --git a/docs/source/contributing/conventions/sentinel-convention.rst b/docs/source/contributing/conventions/sentinel-convention.rst index 534704543d..1f0f0fce52 100644 --- a/docs/source/contributing/conventions/sentinel-convention.rst +++ b/docs/source/contributing/conventions/sentinel-convention.rst @@ -37,11 +37,11 @@ derives from it mechanically -- title-case each underscore-separated word and ap - :mod:`pcapkit.corekit.sentinels` GitHub issue :issue:`911`'s housing ruling -- one module for all four -- is why the table -names a single defining module. Each of the four modules that *uses* a sentinel keeps a -re-export of it, so ``from import `` keeps working for -:mod:`pcapkit.corekit.module`, :mod:`pcapkit.corekit.fields.field`, -:mod:`pcapkit.corekit.enum` and :mod:`pcapkit.protocols.protocol` alike, including a -caller's ``if TYPE_CHECKING:``-only import of the type. +names a single defining module, and it is the only import path. The modules that *use* a +sentinel import it from there but do not re-export it: :issue:`1744` dropped the +per-module re-exports, under the ruling on :issue:`1715` that a moved name keeps no +re-export at its old path. Import the object, and the type for an annotation, from +:mod:`pcapkit.corekit.sentinels`. Where a sentinel name already exists and already follows SCREAMING_SNAKE, keep it; renaming a published sentinel again costs every caller for no further gain. @@ -56,12 +56,13 @@ private and not for public use, which is what this page and adding a sentinel, add it to the table above whether or not it is public. What reaches users is the **object only**. The owner ruled on GitHub issue :issue:`911` -that the objects alone -- ``NULL`` and its siblings -- are exported to users, so a public -sentinel names its instance in its module's ``__all__`` and leaves the type out of it. -The type stays importable by its dotted path, for an annotation or an ``is`` guard; it -is only ``import *`` that does not offer it. A private sentinel such as ``ABSENT`` is in -neither, which is what private means here -- dropping its leading underscore did not -add it to either list, and :class:`~pcapkit.corekit.sentinels.AbsentType` and +that the objects alone -- ``NULL`` and its siblings -- are exported to users, so a +public sentinel names its instance in :mod:`pcapkit.corekit.sentinels`'s ``__all__`` and +leaves the type out of it. The type stays importable by its dotted path there, for an +annotation or an ``is`` guard; it is only ``import *`` that does not offer it. A private +sentinel such as ``ABSENT`` is in neither, which is what private means here -- dropping +its leading underscore did not add it to either list, and +:class:`~pcapkit.corekit.sentinels.AbsentType` and :data:`~pcapkit.corekit.sentinels.ABSENT` are documented on :doc:`the sentinels API page ` as private and not for public use rather than left off it, since the name alone no longer says so. @@ -121,7 +122,7 @@ inconsistencies**: **reload** re-executes the class statement, so the guard does not survive one, and code holding the pre-reload instance will fail ``is``. The module that matters there is :mod:`pcapkit.corekit.sentinels`, where the class statement lives; reloading one - of the re-exporting modules has no effect on the sentinel. + of the modules that imports a sentinel from there has no effect on it. ``__bool__`` returning :obj:`False` ``NULL``, ``NO_VALUE`` and ``ABSENT`` have it, because each stands for an *absent diff --git a/docs/source/pcapkit/corekit/enum.rst b/docs/source/pcapkit/corekit/enum.rst index 20025ffcb7..6f62a69a20 100644 --- a/docs/source/pcapkit/corekit/enum.rst +++ b/docs/source/pcapkit/corekit/enum.rst @@ -20,9 +20,3 @@ adds the *mutating* half every registry under :mod:`pcapkit.const` inherits. .. automethod:: _extend .. automethod:: _unregistered_member - -Auxiliaries ------------ - -.. autoclass:: pcapkit.corekit.enum.NoDefaultType -.. autodata:: pcapkit.corekit.enum.NO_DEFAULT diff --git a/docs/source/pcapkit/corekit/fields/field.rst b/docs/source/pcapkit/corekit/fields/field.rst index d06424efb8..804785fa16 100644 --- a/docs/source/pcapkit/corekit/fields/field.rst +++ b/docs/source/pcapkit/corekit/fields/field.rst @@ -17,6 +17,12 @@ Base Fields :data:`pcapkit.utilities.config.ZERO_PAD_BUDGET_RATIO`, set with :envvar:`PCAPKIT_ZERO_PAD_BUDGET_RATIO`. +.. versionremoved:: 1.5.0 + + ``pcapkit.corekit.fields.field.NoValueType``, now + :class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`922`, + :issue:`1744`). + .. autoclass:: pcapkit.corekit.fields.field.Field :members: :show-inheritance: @@ -33,13 +39,6 @@ Base Fields .. autoattribute:: _default .. autoattribute:: _callback -Auxiliaries ------------ - -.. autoclass:: pcapkit.corekit.fields.field.NoValueType -.. autodata:: pcapkit.corekit.fields.field.NO_VALUE - :no-value: - Internal Definitions -------------------- diff --git a/docs/source/pcapkit/corekit/fields/misc.rst b/docs/source/pcapkit/corekit/fields/misc.rst index d21df60995..05f3afec06 100644 --- a/docs/source/pcapkit/corekit/fields/misc.rst +++ b/docs/source/pcapkit/corekit/fields/misc.rst @@ -46,4 +46,4 @@ Type Variables :type: pcapkit.protocols.protocol.Protocol .. data:: pcapkit.corekit.fields.misc._TN - :type: pcapkit.corekit.fields.field.NoValueType + :type: pcapkit.corekit.sentinels.NoValueType diff --git a/docs/source/pcapkit/corekit/module.rst b/docs/source/pcapkit/corekit/module.rst index f73d405f97..07dc211afb 100644 --- a/docs/source/pcapkit/corekit/module.rst +++ b/docs/source/pcapkit/corekit/module.rst @@ -19,16 +19,10 @@ with a class name. Module name. .. property:: name - :type: str | pcapkit.corekit.module.NullType + :type: str | pcapkit.corekit.sentinels.NullType - Class name, or :data:`NULL` when whatever built this descriptor never - got one -- see :attr:`klass`. - -Auxiliaries ------------ - -.. autoclass:: pcapkit.corekit.module.NullType -.. autodata:: pcapkit.corekit.module.NULL + Class name, or :data:`~pcapkit.corekit.sentinels.NULL` when whatever + built this descriptor never got one -- see :attr:`klass`. Type Variables -------------- diff --git a/docs/source/pcapkit/corekit/sentinels.rst b/docs/source/pcapkit/corekit/sentinels.rst index 5c1d75e549..c2fc2f0dcd 100644 --- a/docs/source/pcapkit/corekit/sentinels.rst +++ b/docs/source/pcapkit/corekit/sentinels.rst @@ -8,10 +8,8 @@ singleton sentinel this package defines for itself: a value recognised only by identity (``value is SENTINEL``), so it cannot be confused with a value a caller might legitimately pass. -All four are defined here. The three public ones are also re-exported by -:mod:`pcapkit.corekit.module`, :mod:`pcapkit.corekit.fields.field` and -:mod:`pcapkit.corekit.enum` respectively, so ``from import `` -works from either path. +All four are defined here, and this is their only import path: the modules +that use them do not re-export them (:issue:`1744`). .. autoclass:: pcapkit.corekit.sentinels.NullType .. autodata:: pcapkit.corekit.sentinels.NULL diff --git a/docs/source/pcapkit/foundation/reassembly/index.rst b/docs/source/pcapkit/foundation/reassembly/index.rst index 27425415db..f9ef8be5c1 100644 --- a/docs/source/pcapkit/foundation/reassembly/index.rst +++ b/docs/source/pcapkit/foundation/reassembly/index.rst @@ -64,5 +64,3 @@ Auxiliary Data .. autoclass:: pcapkit.foundation.reassembly.data.data.Deferred :members: :show-inheritance: - -``DeferredPacket`` is re-exported here from :mod:`pcapkit.corekit.packet`; see :doc:`/pcapkit/corekit/packet`. diff --git a/docs/source/pcapkit/foundation/registry.rst b/docs/source/pcapkit/foundation/registry.rst index 334b4a27e9..ffea7381a9 100644 --- a/docs/source/pcapkit/foundation/registry.rst +++ b/docs/source/pcapkit/foundation/registry.rst @@ -16,6 +16,12 @@ Foundation Registries .. module:: pcapkit.foundation.registry.foundation +.. versionchanged:: 1.5.0 + + ``NULL``, the default of every ``class_`` argument here, is now the + :data:`pcapkit.corekit.sentinels.NULL` sentinel; in 1.4.x it was the + string ``'(null)'`` (:issue:`833`). + Engine Registries ~~~~~~~~~~~~~~~~~ @@ -53,6 +59,12 @@ Protocol Registries .. module:: pcapkit.foundation.registry.protocols +.. versionchanged:: 1.5.0 + + ``NULL``, the default of every ``class_`` argument here, is now the + :data:`pcapkit.corekit.sentinels.NULL` sentinel; in 1.4.x it was the + string ``'(null)'`` (:issue:`833`). + .. autofunction:: pcapkit.foundation.registry.protocols.register_protocol .. autofunction:: pcapkit.foundation.registry.protocols.register_protocol_code diff --git a/docs/source/pcapkit/foundation/traceflow/index.rst b/docs/source/pcapkit/foundation/traceflow/index.rst index 924401911d..544d9abe2d 100644 --- a/docs/source/pcapkit/foundation/traceflow/index.rst +++ b/docs/source/pcapkit/foundation/traceflow/index.rst @@ -63,5 +63,3 @@ Auxiliary Data .. autoclass:: pcapkit.foundation.traceflow.data.data.Deferred :members: :show-inheritance: - -``DeferredPacket`` is re-exported here from :mod:`pcapkit.corekit.packet`; see :doc:`/pcapkit/corekit/packet`. diff --git a/docs/source/pcapkit/protocols/application/ngap.rst b/docs/source/pcapkit/protocols/application/ngap.rst index b0c98bd80d..9271442f23 100644 --- a/docs/source/pcapkit/protocols/application/ngap.rst +++ b/docs/source/pcapkit/protocols/application/ngap.rst @@ -10,8 +10,8 @@ which implements extractor for the NG Application Protocol data models report with: :class:`~pcapkit.protocols.application.ngap.PDUKind`, :class:`~pcapkit.protocols.application.ngap.Criticality`, -:class:`~pcapkit.protocols.application.ngap.ProcedureCode` and -:class:`~pcapkit.protocols.application.ngap.ProtocolIE`. +:class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` and +:class:`~pcapkit.const.ngap.protocol_ie.ProtocolIE`. NGAP is the control plane between a 5G RAN node (gNB or ng-eNB) and an AMF. It runs over SCTP and is named by the DATA chunk's *payload protocol @@ -84,8 +84,8 @@ Known Limitations package's.** The IE and procedure enumerations were generated from ``NGAP_Constants`` of |pycrate|_ 0.8.1 (Release-18-era: 81 procedure codes, 438 protocol IE IDs, highest 443). A |pycrate|_ that carries a newer NGAP will decode IEs that - :class:`~pcapkit.protocols.application.ngap.ProcedureCode` and - :class:`~pcapkit.protocols.application.ngap.ProtocolIE` do not name. Such a + :class:`~pcapkit.const.ngap.procedure_code.ProcedureCode` and + :class:`~pcapkit.const.ngap.protocol_ie.ProtocolIE` do not name. Such a value resolves to a non-registering member called ``Unassigned`` rather than failing or adding a member to either enumeration. * **NGAP over a fragmented SCTP association is not reassembled.** A DATA chunk @@ -124,30 +124,6 @@ Auxiliary Data :members: :show-inheritance: -.. autoclass:: pcapkit.protocols.application.ngap.ProcedureCode - :no-members: - :show-inheritance: - -.. autoclass:: pcapkit.protocols.application.ngap.ProtocolIE - :no-members: - :show-inheritance: - -.. note:: - - :class:`~pcapkit.protocols.application.ngap.ProcedureCode` and - :class:`~pcapkit.protocols.application.ngap.ProtocolIE` are rendered without - their members on purpose: between them they carry 519 of them, each named for - the 3GPP identifier it comes from, and a page listing all of them is longer - than the specification's own tables and no more useful. Read them from - ``pcapkit/protocols/application/ngap.py``, or from - ``pycrate_asn1dir.NGAP.NGAP_Constants``, which is where they were generated - from. - - Unlike the enumerations under :mod:`pcapkit.const`, these are not crawled - from an IANA registry -- 3GPP publishes them in the ASN.1 of TS 38.413 rather - than in a registry with a stable page -- so there is no matching module under - :mod:`pcapkit.vendor`. - Header Schemas -------------- diff --git a/docs/source/pcapkit/protocols/misc/pcapng.rst b/docs/source/pcapkit/protocols/misc/pcapng.rst index af992f6608..dcf0c92bc6 100644 --- a/docs/source/pcapkit/protocols/misc/pcapng.rst +++ b/docs/source/pcapkit/protocols/misc/pcapng.rst @@ -201,7 +201,13 @@ Auxiliary Data :class:`TLSKeyLabel ` is defined in :mod:`pcapkit.const.pcapng.tls_key_label`, generated like its :mod:`pcapkit.const.pcapng` siblings because :rfc:`9850#section-4.2` makes it an -IANA registry. This module re-exports it as ``TLSKeyLabel``. +IANA registry. + +.. versionremoved:: 1.5.0 + + ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, now + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` (:issue:`890`, + :issue:`1744`). .. autoclass:: pcapkit.protocols.misc.pcapng.WireGuardKeyLabel :members: diff --git a/examples/generators/options.py b/examples/generators/options.py index 975efa30ce..66955c9b3e 100644 --- a/examples/generators/options.py +++ b/examples/generators/options.py @@ -1577,11 +1577,12 @@ def _pcapng_secrets_registry() -> 'Any': def _pcapng_secrets_overrides() -> 'dict[Any, dict[str, Any]]': from pcapkit.const.pcapng.secrets_type import SecretsType as Enum_SecretsType + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as Enum_TLSKeyLabel from pcapkit.corekit.multidict import OrderedMultiDict - from pcapkit.protocols.misc.pcapng import TLSKeyLabel, WireGuardKeyLabel + from pcapkit.protocols.misc.pcapng import WireGuardKeyLabel return { Enum_SecretsType.TLS_Key_Log: { - 'entries': {TLSKeyLabel.CLIENT_RANDOM: OrderedMultiDict( + 'entries': {Enum_TLSKeyLabel.CLIENT_RANDOM: OrderedMultiDict( [(bytes(range(0x20, 0x40)), bytes(range(0x40, 0x70)))])}}, Enum_SecretsType.WireGuard_Key_Log: { 'entries': OrderedMultiDict( diff --git a/pcapkit/const/ftp/command.py b/pcapkit/const/ftp/command.py index 0ea2a09b81..97414ffd00 100644 --- a/pcapkit/const/ftp/command.py +++ b/pcapkit/const/ftp/command.py @@ -15,7 +15,8 @@ from aenum import IntEnum, IntFlag, StrEnum, auto -from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry +from pcapkit.corekit.enum import EnumLookup, EnumRegistry +from pcapkit.corekit.sentinels import NO_DEFAULT if TYPE_CHECKING: from typing import Any, Optional, Type diff --git a/pcapkit/const/reg/apptype/apptype.py b/pcapkit/const/reg/apptype/apptype.py index 9f26ebec84..93df93f727 100644 --- a/pcapkit/const/reg/apptype/apptype.py +++ b/pcapkit/const/reg/apptype/apptype.py @@ -15,7 +15,8 @@ from aenum import IntEnum, StrEnum, auto, extend_enum -from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry +from pcapkit.corekit.enum import EnumLookup, EnumRegistry +from pcapkit.corekit.sentinels import NO_DEFAULT __all__ = ['TransportProtocol', 'AppType'] diff --git a/pcapkit/corekit/enum.py b/pcapkit/corekit/enum.py index 4a88cb56eb..f310a16a33 100644 --- a/pcapkit/corekit/enum.py +++ b/pcapkit/corekit/enum.py @@ -91,7 +91,7 @@ from aenum import extend_enum -from pcapkit.corekit.sentinels import NO_DEFAULT, NoDefaultType # pylint: disable=unused-import +from pcapkit.corekit.sentinels import NO_DEFAULT from pcapkit.utilities.exceptions import BaseError, EnumKeyError, EnumValueError if TYPE_CHECKING: @@ -99,7 +99,7 @@ from typing_extensions import Self -__all__ = ['NO_DEFAULT', 'EnumLookup', 'EnumRegistry'] +__all__ = ['EnumLookup', 'EnumRegistry'] class EnumLookup: @@ -362,9 +362,10 @@ def get(cls, key: 'Any', default: 'Any' = NO_DEFAULT) -> 'Self': ``key`` does not resolve. Resolved through a plain ``_value2member_map_`` lookup, never through ``cls(default)``, so it cannot mint -- see :issue:`864`. - :data:`NO_DEFAULT` stands for *no default*; that and a - ``default`` naming no registered member both fall through to - the same lookup error ``key`` itself would have raised. + :data:`~pcapkit.corekit.sentinels.NO_DEFAULT` stands for *no + default*; that and a ``default`` naming no registered member + both fall through to the same lookup error ``key`` itself would + have raised. Returns: The canonical member for ``key``, or for ``default``. diff --git a/pcapkit/corekit/fields/field.py b/pcapkit/corekit/fields/field.py index dda005e115..855ec9c05b 100644 --- a/pcapkit/corekit/fields/field.py +++ b/pcapkit/corekit/fields/field.py @@ -20,6 +20,12 @@ :data:`pcapkit.utilities.config.ZERO_PAD_BUDGET_RATIO`, set with :envvar:`PCAPKIT_ZERO_PAD_BUDGET_RATIO`. +.. versionremoved:: 1.5.0 + + ``pcapkit.corekit.fields.field.NoValueType``, now + :class:`pcapkit.corekit.sentinels.NoValueType` (:issue:`922`, + :issue:`1744`). + """ import abc @@ -29,17 +35,18 @@ import struct from typing import TYPE_CHECKING, Generic, TypeVar, cast -from pcapkit.corekit.sentinels import NO_VALUE, NoValueType # pylint: disable=unused-import +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities import config from pcapkit.utilities.exceptions import FieldValueError, NoDefaultValue, ProtocolError -__all__ = ['NO_VALUE', 'Field'] +__all__ = ['Field'] if TYPE_CHECKING: from typing import IO, Any, Callable, Iterator, Optional from typing_extensions import Self + from pcapkit.corekit.sentinels import NoValueType from pcapkit.protocols.schema.schema import Schema _T = TypeVar('_T') diff --git a/pcapkit/corekit/fields/ipaddress.py b/pcapkit/corekit/fields/ipaddress.py index 8c1f8b6a7a..d0ff9e789c 100644 --- a/pcapkit/corekit/fields/ipaddress.py +++ b/pcapkit/corekit/fields/ipaddress.py @@ -7,7 +7,8 @@ import re from typing import TYPE_CHECKING, Generic, TypeVar, cast -from pcapkit.corekit.fields.field import NO_VALUE, Field +from pcapkit.corekit.fields.field import Field +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities.exceptions import FieldValueError __all__ = [ @@ -22,7 +23,7 @@ from typing_extensions import Literal, Self - from pcapkit.corekit.fields.field import NoValueType + from pcapkit.corekit.sentinels import NoValueType _T = TypeVar('_T', 'IPv4Address', 'IPv6Address', diff --git a/pcapkit/corekit/fields/misc.py b/pcapkit/corekit/fields/misc.py index d235c0ca52..95ff592b75 100644 --- a/pcapkit/corekit/fields/misc.py +++ b/pcapkit/corekit/fields/misc.py @@ -4,7 +4,8 @@ import io from typing import TYPE_CHECKING, TypeVar, cast -from pcapkit.corekit.fields.field import NO_VALUE, FieldBase +from pcapkit.corekit.fields.field import FieldBase +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities.compat import Mapping from pcapkit.utilities.exceptions import FieldError, NoDefaultValue from pcapkit.utilities.warnings import RegistryWarning, warn @@ -20,7 +21,7 @@ from typing_extensions import Self - from pcapkit.corekit.fields.field import NoValueType + from pcapkit.corekit.sentinels import NoValueType from pcapkit.protocols.protocol import ProtocolBase from pcapkit.protocols.schema.schema import Schema diff --git a/pcapkit/corekit/fields/numbers.py b/pcapkit/corekit/fields/numbers.py index 5781618a29..d747586725 100644 --- a/pcapkit/corekit/fields/numbers.py +++ b/pcapkit/corekit/fields/numbers.py @@ -8,7 +8,8 @@ import aenum -from pcapkit.corekit.fields.field import NO_VALUE, Field +from pcapkit.corekit.fields.field import Field +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities.exceptions import BaseError, FieldValueError, IntError, ProtocolError __all__ = [ @@ -28,7 +29,7 @@ from typing_extensions import Literal, Self from pcapkit.const.reg.apptype import AppType, TransportProtocol - from pcapkit.corekit.fields.field import NoValueType + from pcapkit.corekit.sentinels import NoValueType _T = TypeVar('_T', bound='int') diff --git a/pcapkit/corekit/fields/strings.py b/pcapkit/corekit/fields/strings.py index f118f6a5b2..1c9582000d 100644 --- a/pcapkit/corekit/fields/strings.py +++ b/pcapkit/corekit/fields/strings.py @@ -4,7 +4,8 @@ import urllib.parse as urllib_parse from typing import TYPE_CHECKING, Any, Generic, TypeVar -from pcapkit.corekit.fields.field import NO_VALUE, Field +from pcapkit.corekit.fields.field import Field +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities.chardet import detect from pcapkit.utilities.compat import Dict, unquote from pcapkit.utilities.exceptions import FieldValueError @@ -22,7 +23,7 @@ from typing_extensions import Literal, Self - from pcapkit.corekit.fields.field import NoValueType + from pcapkit.corekit.sentinels import NoValueType NamespaceEntry = Tuple[int, int] diff --git a/pcapkit/corekit/module.py b/pcapkit/corekit/module.py index 5ca59a46e9..fa853796c6 100644 --- a/pcapkit/corekit/module.py +++ b/pcapkit/corekit/module.py @@ -14,22 +14,16 @@ import sys from typing import TYPE_CHECKING, Generic, TypeVar, cast -# NOTE: ``_get_null`` is re-exported alongside the two public names for -# backward compatibility, not for use here. Every pickle written before GitHub -# issue #911 moved the definitions names ``pcapkit.corekit.module _get_null`` -# in its payload -- that is what :meth:`NullType.__reduce__` emitted -- so -# dropping the name from this module would make those payloads unloadable with -# ``AttributeError: module 'pcapkit.corekit.module' has no attribute -# '_get_null'``. Newly written pickles name the new home; both resolve to the -# same function and yield the same singleton. -from pcapkit.corekit.sentinels import NULL, NullType, _get_null # pylint: disable=unused-import +from pcapkit.corekit.sentinels import NULL from pcapkit.utilities.exceptions import ProtocolError -__all__ = ['NULL', 'ModuleDescriptor'] +__all__ = ['ModuleDescriptor'] if TYPE_CHECKING: from typing import Type + from pcapkit.corekit.sentinels import NullType + _T = TypeVar('_T') @@ -41,8 +35,8 @@ class can be imported by ``from module import name``.""" #: Module name. module: str - #: Class name, or :data:`NULL` when whatever built this descriptor never - #: got one -- see :attr:`klass`. + #: Class name, or :data:`~pcapkit.corekit.sentinels.NULL` when whatever + #: built this descriptor never got one -- see :attr:`klass`. name: 'str | NullType' @property @@ -51,8 +45,9 @@ def klass(self) -> 'Type[_T]': Raises: pcapkit.utilities.exceptions.ProtocolError: If :attr:`name` is - :data:`NULL` -- the caller building this descriptor omitted - the class name rather than naming one that turned out wrong -- + :data:`~pcapkit.corekit.sentinels.NULL` -- the caller building + this descriptor omitted the class name rather than naming one + that turned out wrong -- or if :attr:`module` has no attribute named :attr:`name`. Important: diff --git a/pcapkit/corekit/packet.py b/pcapkit/corekit/packet.py index a42a816c4a..7343170027 100644 --- a/pcapkit/corekit/packet.py +++ b/pcapkit/corekit/packet.py @@ -62,10 +62,7 @@ class DeferredPacket: :class:`pcapkit.foundation.reassembly.data.tcp.Datagram` and :class:`pcapkit.foundation.traceflow.data.tcp.Index`, which differ only in the placeholder class each names in :attr:`__deferred__` - (:issue:`1516`). It is still importable from where it lived before: - :mod:`pcapkit.foundation.reassembly.data`, - :mod:`pcapkit.foundation.reassembly.data.data` and - :mod:`pcapkit.foundation.traceflow.data.data`. + (:issue:`1516`). """ diff --git a/pcapkit/corekit/sentinels.py b/pcapkit/corekit/sentinels.py index 84fcaafbaa..28f6789a0e 100644 --- a/pcapkit/corekit/sentinels.py +++ b/pcapkit/corekit/sentinels.py @@ -13,30 +13,17 @@ house rule the four below follow. The owner's ruling on GitHub issue :issue:`911` chose one shared module over -one module per sentinel. Each sentinel is also re-exported, in a single -import line, from the module of the one class that uses it -- -:class:`NullType` from :mod:`pcapkit.corekit.module`, :class:`NoValueType` -from :mod:`pcapkit.corekit.fields.field`, :class:`NoDefaultType` from -:mod:`pcapkit.corekit.enum` and :class:`AbsentType` from -:mod:`pcapkit.protocols.protocol` -- so that no ``from import `` -breaks, including the ``if TYPE_CHECKING:``-only imports of the *types* that -:mod:`pcapkit.foundation.registry.foundation`, -:mod:`pcapkit.foundation.registry.protocols` and -:mod:`pcapkit.corekit.fields.ipaddress`, :mod:`~pcapkit.corekit.fields.misc`, -:mod:`~pcapkit.corekit.fields.numbers` and :mod:`~pcapkit.corekit.fields.strings` -carry. - -``NO_VALUE`` and ``ABSENT`` differ in how far the ruling reaches. ``NO_VALUE`` -is documented as the value of -:attr:`FieldBase.default `, -so it is a published contract and the re-export at -:mod:`pcapkit.corekit.fields.field` is load-bearing for callers outside this -package. ``ABSENT`` is private to :mod:`pcapkit.protocols.protocol` -- -nothing outside that module ever imports it, from here or from there -- so -its re-export exists only so that module's own code keeps reading -``ABSENT`` rather than a fully-qualified name. It carries no leading -underscore; :class:`AbsentType`'s docstring says what marks it private -instead. +one module per sentinel, and this module is the only import path for all +four. The re-export each one used to keep at the module of the class that +uses it was dropped under :issue:`1744`, per the ruling on :issue:`1715` that a +moved name keeps no re-export at its old path. :mod:`pcapkit.corekit.module`, +:mod:`pcapkit.corekit.fields.field`, :mod:`pcapkit.corekit.enum` and +:mod:`pcapkit.protocols.protocol` still import the object they use from here, +but none lists it in ``__all__`` or documents it, and none binds the type. + +``ABSENT`` is private to :mod:`pcapkit.protocols.protocol` -- nothing outside +that module ever reads it. It carries no leading underscore; +:class:`AbsentType`'s docstring says what marks it private instead. """ from typing import TYPE_CHECKING @@ -103,11 +90,10 @@ class NullType: reloads :mod:`pcapkit.corekit.sentinels` after import. Reloading :mod:`pcapkit.corekit.module` instead has no effect on the - singleton at all. That module only re-imports :data:`NULL` and - :class:`NullType` from here, and re-running an already-satisfied - ``from ... import`` reads the current binding in :mod:`sys.modules` - rather than re-executing anything, so it neither mints a new instance - nor loses the old one. + singleton at all. That module only imports :data:`NULL` from here, and + re-running an already-satisfied ``from ... import`` reads the current + binding in :mod:`sys.modules` rather than re-executing anything, so it + neither mints a new instance nor loses the old one. """ @@ -166,9 +152,7 @@ def __reduce__(self) -> 'tuple[Callable[[], NullType], tuple[()]]': #: NullType: Sentinel for an omitted ``class_`` argument to the ``register_*`` #: helpers in :mod:`pcapkit.foundation.registry.protocols` and #: :mod:`pcapkit.foundation.registry.foundation`. Housed here with the -#: package's other sentinels, per the module docstring above; -#: :mod:`pcapkit.corekit.module` keeps a re-export so every -#: ``from pcapkit.corekit.module import NULL`` keeps working. +#: package's other sentinels, per the module docstring above. NULL = NullType() @@ -187,8 +171,7 @@ def _get_null() -> 'NullType': class NoValueType: """Type of :data:`NO_VALUE`, the default value for :mod:`pcapkit.corekit.fields`. - Housed here per GitHub issue :issue:`911`, and re-exported from - :mod:`pcapkit.corekit.fields.field`, where + Housed here per GitHub issue :issue:`911`; :attr:`FieldBase.default ` documents it as the field-default sentinel. @@ -201,8 +184,6 @@ def __bool__(self) -> 'Literal[False]': #: NoValueType: Default value for #: :attr:`FieldBase.default `. -#: :mod:`pcapkit.corekit.fields.field` keeps a re-export, since that -#: attribute's own documentation is a published contract naming this object. NO_VALUE = NoValueType() @@ -365,9 +346,7 @@ def __repr__(self) -> 'str': #: back. See :class:`NoDefaultType` for why this is a dedicated class rather #: than a bare :class:`object`, why it is a guarded singleton, and why it #: defines neither the copy/pickle hooks nor the ``__bool__`` that its -#: :mod:`pcapkit.corekit` siblings do. :mod:`pcapkit.corekit.enum` keeps a -#: re-export, since :meth:`EnumLookup.get ` -#: names this object in its signature and docstring. +#: :mod:`pcapkit.corekit` siblings do. NO_DEFAULT = NoDefaultType() @@ -415,7 +394,7 @@ def __repr__(self) -> 'str': #: class cannot enumerate its keywords, c.f. #: :attr:`ProtocolBase.__keywords__ #: `. Never leaves -#: :mod:`pcapkit.protocols.protocol`, which keeps a private re-export of it -#: for exactly that one read. Private by documentation rather than by a +#: :mod:`pcapkit.protocols.protocol`, which imports it from here for exactly +#: that one read. Private by documentation rather than by a #: leading underscore -- see :class:`AbsentType`'s own docstring. ABSENT = AbsentType() diff --git a/pcapkit/foundation/reassembly/data/__init__.py b/pcapkit/foundation/reassembly/data/__init__.py index ea4182de67..2d81c5151e 100644 --- a/pcapkit/foundation/reassembly/data/__init__.py +++ b/pcapkit/foundation/reassembly/data/__init__.py @@ -2,8 +2,7 @@ """data models for reassembly""" # shared -from pcapkit.foundation.reassembly.data.data import (Completion, Deferred, DeferredPacket, - ReassemblyData) +from pcapkit.foundation.reassembly.data.data import Completion, Deferred, ReassemblyData # IP reassembly from pcapkit.foundation.reassembly.data.ip import Buffer as IP_Buffer @@ -22,7 +21,7 @@ from pcapkit.foundation.reassembly.data.tcp import Packet as TCP_Packet __all__ = [ - 'ReassemblyData', 'Completion', 'Deferred', 'DeferredPacket', + 'ReassemblyData', 'Completion', 'Deferred', 'IP_Packet', 'IP_DatagramID', 'IP_Datagram', 'IP_Buffer', 'IP_BufferID', diff --git a/pcapkit/foundation/reassembly/data/data.py b/pcapkit/foundation/reassembly/data/data.py index bd59205202..89380cb665 100644 --- a/pcapkit/foundation/reassembly/data/data.py +++ b/pcapkit/foundation/reassembly/data/data.py @@ -5,10 +5,9 @@ from pcapkit.corekit.enum import EnumLookup from pcapkit.corekit.infoclass import Info, info_final -from pcapkit.corekit.packet import DeferredPacket from pcapkit.utilities.compat import StrEnum, auto -__all__ = ['ReassemblyData', 'Completion', 'Deferred', 'DeferredPacket'] +__all__ = ['ReassemblyData', 'Completion', 'Deferred'] if TYPE_CHECKING: from typing import Callable, Optional diff --git a/pcapkit/foundation/registry/foundation.py b/pcapkit/foundation/registry/foundation.py index dd0b174fa9..0bb9bda787 100644 --- a/pcapkit/foundation/registry/foundation.py +++ b/pcapkit/foundation/registry/foundation.py @@ -6,10 +6,17 @@ This module provides the foundation registries for :mod:`pcapkit`. +.. versionchanged:: 1.5.0 + + ``NULL``, the default of every ``class_`` argument here, is now the + :data:`pcapkit.corekit.sentinels.NULL` sentinel; in 1.4.x it was the + string ``'(null)'`` (:issue:`833`). + """ from typing import TYPE_CHECKING, cast, overload -from pcapkit.corekit.module import NULL, ModuleDescriptor +from pcapkit.corekit.module import ModuleDescriptor +from pcapkit.corekit.sentinels import NULL from pcapkit.foundation.extraction import Extractor from pcapkit.foundation.reassembly.ipv4 import IPv4 as IPv4_Reassembly from pcapkit.foundation.reassembly.ipv6 import IPv6 as IPv6_Reassembly @@ -23,7 +30,7 @@ from dictdumper import Dumper - from pcapkit.corekit.module import NullType + from pcapkit.corekit.sentinels import NullType from pcapkit.foundation.engines import Engine from pcapkit.foundation.reassembly.reassembly import CallbackFn as Reasm_CallbackFn from pcapkit.foundation.reassembly.reassembly import Reassembly diff --git a/pcapkit/foundation/registry/protocols.py b/pcapkit/foundation/registry/protocols.py index f49aaf79f1..81054a50c4 100644 --- a/pcapkit/foundation/registry/protocols.py +++ b/pcapkit/foundation/registry/protocols.py @@ -6,6 +6,12 @@ This module provides the protocol registries for :mod:`pcapkit`. +.. versionchanged:: 1.5.0 + + ``NULL``, the default of every ``class_`` argument here, is now the + :data:`pcapkit.corekit.sentinels.NULL` sentinel; in 1.4.x it was the + string ``'(null)'`` (:issue:`833`). + """ import collections.abc import enum @@ -21,7 +27,8 @@ from pcapkit.const.reg.transtype import TransType as Enum_TransType from pcapkit.const.sctp.payload_protocol_identifier import \ PayloadProtocolIdentifier as Enum_PayloadProtocolIdentifier -from pcapkit.corekit.module import NULL, ModuleDescriptor +from pcapkit.corekit.module import ModuleDescriptor +from pcapkit.corekit.sentinels import NULL from pcapkit.protocols import __proto__ as protocol_registry from pcapkit.protocols.application.httpv2 import HTTP as HTTPv2 from pcapkit.protocols.internet.hip import HIP @@ -81,7 +88,7 @@ PayloadProtocolIdentifier as SCTP_PayloadProtocolIdentifier from pcapkit.const.tcp.mp_tcp_option import MPTCPOption as TCP_MPTCPOption from pcapkit.const.tcp.option import Option as TCP_Option - from pcapkit.corekit.module import NullType + from pcapkit.corekit.sentinels import NullType from pcapkit.protocols.application.httpv2 import FrameConstructor as HTTP_FrameConstructor from pcapkit.protocols.application.httpv2 import FrameParser as HTTP_FrameParser from pcapkit.protocols.internet.hip import ParameterConstructor as HIP_ParameterConstructor diff --git a/pcapkit/foundation/traceflow/data/data.py b/pcapkit/foundation/traceflow/data/data.py index 75dea4724d..72a8b7f0c2 100644 --- a/pcapkit/foundation/traceflow/data/data.py +++ b/pcapkit/foundation/traceflow/data/data.py @@ -4,9 +4,8 @@ from typing import TYPE_CHECKING from pcapkit.corekit.infoclass import Info, info_final -from pcapkit.corekit.packet import DeferredPacket -__all__ = ['TraceFlowData', 'Deferred', 'DeferredPacket', 'FrameRecord'] +__all__ = ['TraceFlowData', 'Deferred', 'FrameRecord'] if TYPE_CHECKING: from decimal import Decimal diff --git a/pcapkit/protocols/application/ngap.py b/pcapkit/protocols/application/ngap.py index 010aee0058..835aaa2259 100644 --- a/pcapkit/protocols/application/ngap.py +++ b/pcapkit/protocols/application/ngap.py @@ -124,7 +124,7 @@ from typing_extensions import Literal -__all__ = ['NGAP', 'PDUKind', 'Criticality', 'ProcedureCode', 'ProtocolIE'] +__all__ = ['NGAP', 'PDUKind', 'Criticality'] #: Cached ``NGAP-PDU`` object, c.f. :func:`load_pycrate`. :data:`NotImplemented` #: means the import has not been attempted yet, and :data:`None` that it was @@ -186,8 +186,9 @@ def load_pycrate() -> 'Optional[Any]': # instead (pycrate_asn1dir.NGAP's NGAP_Constants, #880), the same optional # dependency this module needs to decode NGAP at all. They therefore live under # pcapkit.const.ngap, generated by pcapkit.vendor.ngap.procedure_code and -# pcapkit.vendor.ngap.protocol_ie, and are re-exported here under their -# original names. Only the vendor crawler that generates the const module +# pcapkit.vendor.ngap.protocol_ie, and are imported here as Enum_ProcedureCode +# and Enum_ProtocolIE; this module no longer re-exports them under their +# original names (#1744). Only the vendor crawler that generates the const module # needs pycrate; neither this module nor the generated one does at import. # # The SCTP payload protocol identifiers *are* IANA's, and are used from @@ -262,22 +263,6 @@ def _missing_(cls, value: 'int') -> 'NoReturn': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) -#: Re-exported from :class:`pcapkit.const.ngap.procedure_code.ProcedureCode` -#: under its original name -- see the ``Enumerations`` comment above. A plain -#: alias, not a subclass: :mod:`aenum` refuses to extend an enumeration that -#: already has members, and this one carries 81 of them. -ProcedureCode = Enum_ProcedureCode - -#: Re-exported from :class:`pcapkit.const.ngap.protocol_ie.ProtocolIE` under -#: its original name -- see :data:`ProcedureCode` above for why -#: this is an alias rather than a subclass. -#: -#: An IE's ID name and the name of the open type its value is keyed under -#: differ in places -- IE 21 is ``id-DefaultPagingDRX`` but its value arrives -#: keyed ``PagingDRX`` -- so -#: :attr:`IE.type ` carries -#: the latter alongside this. -ProtocolIE = Enum_ProtocolIE ############################################################################## @@ -448,7 +433,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_NGAP': for ie in value.get('protocolIEs') or (): choice = ie['value'] ies.append(Data_IE( - id=ProtocolIE.get(ie['id']), + id=Enum_ProtocolIE.get(ie['id']), criticality=Criticality.get(ie['criticality']), type=choice.name, value=choice.value, @@ -456,7 +441,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_NGAP': ngap = Data_NGAP( kind=PDUKind(kind), - procedure=ProcedureCode.get(body['procedureCode']), + procedure=Enum_ProcedureCode.get(body['procedureCode']), criticality=Criticality.get(body['criticality']), message=message, ies=tuple(ies), @@ -481,7 +466,7 @@ def read(self, length: 'Optional[int]' = None, **kwargs: 'Any') -> 'Data_NGAP': def make(self, kind: 'PDUKind | str' = PDUKind.INITIATING_MESSAGE, - procedure: 'Optional[ProcedureCode | int]' = None, + procedure: 'Optional[Enum_ProcedureCode | int]' = None, criticality: 'Criticality | int | str' = Criticality.reject, message: 'Optional[str]' = None, value: 'Any' = None, @@ -528,7 +513,7 @@ def make(self, with _PDU_LOCK: try: pdu.set_val((PDUKind(kind).value, { - 'procedureCode': int(ProcedureCode.get(procedure)), + 'procedureCode': int(Enum_ProcedureCode.get(procedure)), 'criticality': Criticality.get(criticality).name, 'value': (message, _revert(value)), })) diff --git a/pcapkit/protocols/data/application/ngap.py b/pcapkit/protocols/data/application/ngap.py index 84b7c7894e..3cc3249cbc 100644 --- a/pcapkit/protocols/data/application/ngap.py +++ b/pcapkit/protocols/data/application/ngap.py @@ -10,7 +10,9 @@ if TYPE_CHECKING: from typing import Any - from pcapkit.protocols.application.ngap import Criticality, PDUKind, ProcedureCode, ProtocolIE + from pcapkit.const.ngap.procedure_code import ProcedureCode as Enum_ProcedureCode + from pcapkit.const.ngap.protocol_ie import ProtocolIE as Enum_ProtocolIE + from pcapkit.protocols.application.ngap import Criticality, PDUKind __all__ = [ 'NGAP', @@ -77,7 +79,7 @@ class IE(Data): """Data model for one NGAP protocol information element.""" #: Protocol IE ID. - id: 'ProtocolIE' + id: 'Enum_ProtocolIE' #: Criticality, i.e. what a receiver must do when it does not understand #: :attr:`id`. criticality: 'Criticality' @@ -89,7 +91,7 @@ class IE(Data): value: 'Any' if TYPE_CHECKING: - def __init__(self, id: 'ProtocolIE', criticality: 'Criticality', type: 'str', value: 'Any') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin,line-too-long + def __init__(self, id: 'Enum_ProtocolIE', criticality: 'Criticality', type: 'str', value: 'Any') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,redefined-builtin,line-too-long @info_final @@ -106,7 +108,7 @@ class NGAP(Protocol): #: Which of the three ``NGAP-PDU`` alternatives this is. kind: 'PDUKind' #: Procedure code. - procedure: 'ProcedureCode' + procedure: 'Enum_ProcedureCode' #: Criticality of the procedure. criticality: 'Criticality' #: Name of the message type, e.g. ``NGSetupRequest``. @@ -120,4 +122,4 @@ class NGAP(Protocol): value: 'Sequence' if TYPE_CHECKING: - def __init__(self, kind: 'PDUKind', procedure: 'ProcedureCode', criticality: 'Criticality', message: 'str', ies: 'tuple[IE, ...]', value: 'Sequence') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,line-too-long + def __init__(self, kind: 'PDUKind', procedure: 'Enum_ProcedureCode', criticality: 'Criticality', message: 'str', ies: 'tuple[IE, ...]', value: 'Sequence') -> 'None': ... # pylint: disable=unused-argument,super-init-not-called,multiple-statements,line-too-long diff --git a/pcapkit/protocols/data/misc/pcapng.py b/pcapkit/protocols/data/misc/pcapng.py index 5c5b588bd7..77f50f00c9 100644 --- a/pcapkit/protocols/data/misc/pcapng.py +++ b/pcapkit/protocols/data/misc/pcapng.py @@ -53,13 +53,13 @@ from pcapkit.const.pcapng.option_type import OptionType as Enum_OptionType from pcapkit.const.pcapng.record_type import RecordType as Enum_RecordType from pcapkit.const.pcapng.secrets_type import SecretsType as Enum_SecretsType + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as Enum_TLSKeyLabel from pcapkit.const.pcapng.verdict_type import VerdictType as Enum_VerdictType from pcapkit.const.reg.linktype import LinkType as Enum_LinkType from pcapkit.corekit.infoclass import MultiInfo, OrderedMultiInfo from pcapkit.corekit.multidict import OrderedMultiDict from pcapkit.corekit.version import VersionInfo - from pcapkit.protocols.misc.pcapng import (PacketDirection, PacketReception, TLSKeyLabel, - WireGuardKeyLabel) + from pcapkit.protocols.misc.pcapng import PacketDirection, PacketReception, WireGuardKeyLabel class PCAPNG(Protocol): @@ -824,14 +824,14 @@ class TLSKeyLog(DSBSecrets): """Data model for TLS key log DSB secrets.""" #: TLS key log entries. - entries: 'dict[TLSKeyLabel, OrderedMultiInfo[bytes, bytes]]' + entries: 'dict[Enum_TLSKeyLabel, OrderedMultiInfo[bytes, bytes]]' if TYPE_CHECKING: #: TLS key log text as the block carries it, comments and line order #: included. Set on parsing only, and written back verbatim on rebuild. data: 'str' - def __init__(self, entries: 'dict[TLSKeyLabel, OrderedMultiDict[bytes, bytes]]') -> 'None': ... + def __init__(self, entries: 'dict[Enum_TLSKeyLabel, OrderedMultiDict[bytes, bytes]]') -> 'None': ... @info_final diff --git a/pcapkit/protocols/internet/hopopt.py b/pcapkit/protocols/internet/hopopt.py index 27e9c86633..377975a1f7 100644 --- a/pcapkit/protocols/internet/hopopt.py +++ b/pcapkit/protocols/internet/hopopt.py @@ -35,8 +35,8 @@ from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode as Enum_SMFDPDMode from pcapkit.const.ipv6.tagger_id import TaggerID as Enum_TaggerID from pcapkit.const.reg.transtype import TransType as Enum_TransType -from pcapkit.corekit.fields.field import NO_VALUE from pcapkit.corekit.multidict import OrderedMultiDict +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.data.internet.hopopt import HOPOPT as Data_HOPOPT from pcapkit.protocols.data.internet.hopopt import CALIPSOOption as Data_CALIPSOOption from pcapkit.protocols.data.internet.hopopt import DFFFlags as Data_DFFFlags diff --git a/pcapkit/protocols/internet/ipv6_opts.py b/pcapkit/protocols/internet/ipv6_opts.py index 5376e9ed0b..c3140ccdc7 100644 --- a/pcapkit/protocols/internet/ipv6_opts.py +++ b/pcapkit/protocols/internet/ipv6_opts.py @@ -35,8 +35,8 @@ from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode as Enum_SMFDPDMode from pcapkit.const.ipv6.tagger_id import TaggerID as Enum_TaggerID from pcapkit.const.reg.transtype import TransType as Enum_TransType -from pcapkit.corekit.fields.field import NO_VALUE from pcapkit.corekit.multidict import OrderedMultiDict +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.data.internet.ipv6_opts import CALIPSOOption as Data_CALIPSOOption from pcapkit.protocols.data.internet.ipv6_opts import DFFFlags as Data_DFFFlags from pcapkit.protocols.data.internet.ipv6_opts import HomeAddressOption as Data_HomeAddressOption diff --git a/pcapkit/protocols/misc/pcapng.py b/pcapkit/protocols/misc/pcapng.py index 3696e78163..4e3c5c6b5e 100644 --- a/pcapkit/protocols/misc/pcapng.py +++ b/pcapkit/protocols/misc/pcapng.py @@ -11,6 +11,12 @@ .. [*] https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html +.. versionremoved:: 1.5.0 + + ``pcapkit.protocols.misc.pcapng.TLSKeyLabel``, now + :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel` (:issue:`890`, + :issue:`1744`). + """ import base64 import builtins @@ -390,18 +396,15 @@ def _missing_(cls, value: 'object') -> 'PacketReception': # NOTE: RFC 9850 :rfc:`9850#section-4.2` makes the TLS key labels an IANA # registry ("TLS SSLKEYLOGFILE Labels", Specification Required) rather than a -# closed, hand-picked set, so :class:`TLSKeyLabel` is the generated +# closed, hand-picked set, so the TLS key label enumeration is the generated # :class:`pcapkit.const.pcapng.tls_key_label.TLSKeyLabel`, imported above as -# ``Enum_TLSKeyLabel`` like its seven :mod:`pcapkit.const.pcapng` siblings. The -# assignment below re-exports the same object (not a copy) under the unaliased -# name, so ``from pcapkit.protocols.misc.pcapng import TLSKeyLabel`` resolves to -# the class every internal ``Enum_TLSKeyLabel`` reference uses. +# ``Enum_TLSKeyLabel`` like its seven :mod:`pcapkit.const.pcapng` siblings. This +# module no longer re-exports it under the unaliased name (:issue:`1744`). # :class:`WireGuardKeyLabel` below stays hand-written: it is a closed set # (draft-ietf-opsawg-pcapng-06 section 4.7's "is one of" four names), not a # registry, so filing it under :mod:`pcapkit.const` would misrepresent it as # one. The ``Enum_*`` alias convention covers imports of const enums, so it does # not apply to a local definition. -TLSKeyLabel = Enum_TLSKeyLabel class WireGuardKeyLabel(EnumLookup, StrEnum): diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index ff0cc1efaf..5fc80574b6 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -45,7 +45,7 @@ from pcapkit.corekit.fields.misc import ConditionalField, SchemaField, SwitchField from pcapkit.corekit.module import ModuleDescriptor from pcapkit.corekit.protochain import ProtoChain -from pcapkit.corekit.sentinels import ABSENT, AbsentType # pylint: disable=unused-import +from pcapkit.corekit.sentinels import ABSENT from pcapkit.protocols import data as data_module from pcapkit.protocols import schema as schema_module from pcapkit.protocols.data.data import Data diff --git a/pcapkit/protocols/schema/application/httpv2.py b/pcapkit/protocols/schema/application/httpv2.py index 00b5e0ca7b..754e2fa882 100644 --- a/pcapkit/protocols/schema/application/httpv2.py +++ b/pcapkit/protocols/schema/application/httpv2.py @@ -231,7 +231,7 @@ class Flags(FrameType.Flags): # ``b''`` and nothing would raise or warn. The parentheses also keep # ``pkt['pad_len']`` from being read at all when ``PADDED`` is clear, where # the :class:`~pcapkit.corekit.fields.misc.ConditionalField` above has left - # it as :data:`~pcapkit.corekit.fields.field.NO_VALUE`. + # it as :data:`~pcapkit.corekit.sentinels.NO_VALUE`. data: 'bytes' = BytesField(length=lambda pkt: pkt['__length__'] - ( pkt['pad_len'] if pkt['flags']['bit_3'] else 0 )) diff --git a/pcapkit/protocols/schema/internet/hopopt.py b/pcapkit/protocols/schema/internet/hopopt.py index 4ad3dcd94a..613e51ac9d 100644 --- a/pcapkit/protocols/schema/internet/hopopt.py +++ b/pcapkit/protocols/schema/internet/hopopt.py @@ -12,13 +12,13 @@ from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode as Enum_SMFDPDMode from pcapkit.const.reg.transtype import TransType as Enum_TransType from pcapkit.corekit.fields.collections import OptionField -from pcapkit.corekit.fields.field import NO_VALUE from pcapkit.corekit.fields.ipaddress import IPv6AddressField from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, PayloadField, SwitchField) from pcapkit.corekit.fields.numbers import (EnumField, NumberField, UInt8Field, UInt16Field, UInt32Field) from pcapkit.corekit.fields.strings import BitField, BytesField, PaddingField +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.schema.internet import ipv6_option from pcapkit.protocols.schema.internet.ipv6_option import pad_opt_data_len, rpl_opt_sub_tlv_len from pcapkit.protocols.schema.schema import EnumSchema, Schema, schema_final diff --git a/pcapkit/protocols/schema/internet/ipv6_option.py b/pcapkit/protocols/schema/internet/ipv6_option.py index 287b2c3dbd..3ddb59ca88 100644 --- a/pcapkit/protocols/schema/internet/ipv6_option.py +++ b/pcapkit/protocols/schema/internet/ipv6_option.py @@ -238,10 +238,10 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int': the option's ``len`` field is declared as a :class:`~pcapkit.corekit.fields.misc.ConditionalField` and is skipped for it. A skipped conditional field is *recorded* in the packet data as - :data:`~pcapkit.corekit.fields.field.NO_VALUE`, rather than being left + :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left out of it, so the test below has to be on the **value** and not on the presence of the key: ``pkt.get('len', 0)`` on its own hands that - :obj:`~pcapkit.corekit.fields.field.NoValueType` straight to + :obj:`~pcapkit.corekit.sentinels.NoValueType` straight to :class:`~pcapkit.corekit.fields.strings.PaddingField`, where it becomes an unusable :mod:`struct` template and surfaces much later as an opaque :exc:`struct.error`, which is how a ``Pad1`` option would fail to parse. diff --git a/pcapkit/protocols/schema/internet/ipv6_opts.py b/pcapkit/protocols/schema/internet/ipv6_opts.py index 6b53c9560c..d69caa3b4d 100644 --- a/pcapkit/protocols/schema/internet/ipv6_opts.py +++ b/pcapkit/protocols/schema/internet/ipv6_opts.py @@ -12,13 +12,13 @@ from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode as Enum_SMFDPDMode from pcapkit.const.reg.transtype import TransType as Enum_TransType from pcapkit.corekit.fields.collections import OptionField -from pcapkit.corekit.fields.field import NO_VALUE from pcapkit.corekit.fields.ipaddress import IPv6AddressField from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, PayloadField, SwitchField) from pcapkit.corekit.fields.numbers import (EnumField, NumberField, UInt8Field, UInt16Field, UInt32Field) from pcapkit.corekit.fields.strings import BitField, BytesField, PaddingField +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.schema.internet import ipv6_option from pcapkit.protocols.schema.internet.ipv6_option import pad_opt_data_len, rpl_opt_sub_tlv_len from pcapkit.protocols.schema.schema import EnumSchema, Schema, schema_final diff --git a/pcapkit/protocols/schema/internet/mh.py b/pcapkit/protocols/schema/internet/mh.py index 9040a12fcb..9791374803 100644 --- a/pcapkit/protocols/schema/internet/mh.py +++ b/pcapkit/protocols/schema/internet/mh.py @@ -412,10 +412,10 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int': :attr:`Option.length` is declared as a :class:`~pcapkit.corekit.fields.misc.ConditionalField` and is skipped for it. A skipped conditional field is *recorded* in the packet data as - :data:`~pcapkit.corekit.fields.field.NO_VALUE`, rather than being left + :data:`~pcapkit.corekit.sentinels.NO_VALUE`, rather than being left out of it, so the test below has to be on the **value** and not on the presence of the key: ``pkt.get('length', 0)`` on its own hands that - :class:`~pcapkit.corekit.fields.field.NoValueType` straight to + :class:`~pcapkit.corekit.sentinels.NoValueType` straight to :class:`~pcapkit.corekit.fields.strings.PaddingField`, where it becomes an unusable :mod:`struct` template and surfaces much later as an opaque :exc:`struct.error`, which is how a ``Pad1`` option would fail to parse. diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index 437f6e9423..b1c1d36aa3 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -66,8 +66,9 @@ from typing_extensions import Literal, Self + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as Enum_TLSKeyLabel from pcapkit.corekit.fields.field import FieldBase as Field - from pcapkit.protocols.misc.pcapng import TLSKeyLabel, WireGuardKeyLabel + from pcapkit.protocols.misc.pcapng import WireGuardKeyLabel from pcapkit.protocols.protocol import ProtocolBase if SPHINX_TYPE_CHECKING: # pragma: no cover @@ -2229,9 +2230,9 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': Revised schema. """ - from pcapkit.protocols.misc.pcapng import TLSKeyLabel + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as Enum_TLSKeyLabel - entries = collections.defaultdict(OrderedMultiDict) # type: dict[TLSKeyLabel, OrderedMultiDict[bytes, bytes]] + entries = collections.defaultdict(OrderedMultiDict) # type: dict[Enum_TLSKeyLabel, OrderedMultiDict[bytes, bytes]] lines = self.data.splitlines() for index, line in enumerate(lines): if not line or line.startswith('#'): @@ -2239,7 +2240,7 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': try: label, random, secret = line.strip().split() - label_enum = TLSKeyLabel(label.upper()) + label_enum = Enum_TLSKeyLabel(label.upper()) entries[label_enum].add(bytes.fromhex(random), bytes.fromhex(secret)) except ValueError as error: @@ -2252,7 +2253,7 @@ def post_process(self, packet: 'dict[str, Any]') -> 'Schema': if TYPE_CHECKING: #: TLS Key Log entries. - entries: 'dict[TLSKeyLabel, OrderedMultiDict[bytes, bytes]]' + entries: 'dict[Enum_TLSKeyLabel, OrderedMultiDict[bytes, bytes]]' def __init__(self, data: 'str') -> 'None': ... diff --git a/pcapkit/protocols/schema/schema.py b/pcapkit/protocols/schema/schema.py index 42b108d53a..e0db581319 100644 --- a/pcapkit/protocols/schema/schema.py +++ b/pcapkit/protocols/schema/schema.py @@ -10,11 +10,12 @@ from pcapkit.corekit.decorators import prepare from pcapkit.corekit.fields.collections import ListField, OptionField -from pcapkit.corekit.fields.field import NO_VALUE, FieldBase +from pcapkit.corekit.fields.field import FieldBase from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, PayloadField, SchemaField) from pcapkit.corekit.fields.strings import PaddingField from pcapkit.corekit.infoclass import FinalisedState +from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.utilities.compat import Mapping, final from pcapkit.utilities.exceptions import NoDefaultValue, ProtocolUnbound, SchemaError, stacklevel from pcapkit.utilities.warnings import RegistryWarning, SchemaWarning, UnknownFieldWarning, warn diff --git a/pcapkit/vendor/ftp/command.py b/pcapkit/vendor/ftp/command.py index f492cfad8c..83da90c285 100644 --- a/pcapkit/vendor/ftp/command.py +++ b/pcapkit/vendor/ftp/command.py @@ -55,7 +55,8 @@ from aenum import IntEnum, IntFlag, StrEnum, auto -from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry +from pcapkit.corekit.enum import EnumLookup, EnumRegistry +from pcapkit.corekit.sentinels import NO_DEFAULT if TYPE_CHECKING: from typing import Any, Optional, Type diff --git a/pcapkit/vendor/pcapng/tls_key_label.py b/pcapkit/vendor/pcapng/tls_key_label.py index 2917e774d9..cf0eca8de5 100644 --- a/pcapkit/vendor/pcapng/tls_key_label.py +++ b/pcapkit/vendor/pcapng/tls_key_label.py @@ -25,7 +25,7 @@ #: Hand-carried comment for the ``RSA`` member, copied verbatim from the text #: GitHub issue :issue:`882` gave it in -#: :class:`pcapkit.protocols.misc.pcapng.TLSKeyLabel` -- ``RSA`` has no row in +#: the hand-written ``pcapkit.protocols.misc.pcapng.TLSKeyLabel`` -- ``RSA`` has no row in #: :attr:`TLSKeyLabel.LINK`'s registry to generate a comment from (``grep -c #: RSA`` on the fetched CSV is 0), so it is carried across rather than #: derived, per GitHub issue :issue:`886`'s first constraint. The embedded @@ -135,7 +135,7 @@ def process(self, data: 'list[str]') -> 'tuple[list[str], list[str]]': pref = f"{renm} = '{label}'" # NOTE: mirrors the ``# nosec B105`` bandit suppression every # such member already carries in - # :class:`pcapkit.protocols.misc.pcapng.TLSKeyLabel` as of #882 -- + # the hand-written ``pcapkit.protocols.misc.pcapng.TLSKeyLabel`` as of #882 -- # bandit's B105 (hardcoded password string) flags a string # constant assigned to a name containing ``SECRET``, which eight # of these ten labels' names do. diff --git a/pcapkit/vendor/reg/apptype/apptype.py b/pcapkit/vendor/reg/apptype/apptype.py index c4300f03e1..f115ffa090 100644 --- a/pcapkit/vendor/reg/apptype/apptype.py +++ b/pcapkit/vendor/reg/apptype/apptype.py @@ -108,7 +108,8 @@ from aenum import IntEnum, StrEnum, auto, extend_enum -from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry +from pcapkit.corekit.enum import EnumLookup, EnumRegistry +from pcapkit.corekit.sentinels import NO_DEFAULT __all__ = ['TransportProtocol', '{NAME}'] diff --git a/tests/const/test_const_enum_get.py b/tests/const/test_const_enum_get.py index 8a18afcb16..1f2eea5167 100644 --- a/tests/const/test_const_enum_get.py +++ b/tests/const/test_const_enum_get.py @@ -244,10 +244,10 @@ def test_the_reported_case_returns_the_default(self) -> None: def test_omitting_the_default_still_raises_for_the_original_key(self) -> None: """GitHub issue #857: ``-1`` used to be compared with ``==`` against - :data:`~pcapkit.corekit.enum.NO_DEFAULT`, so a caller-supplied ``-1`` + :data:`~pcapkit.corekit.sentinels.NO_DEFAULT`, so a caller-supplied ``-1`` was silently read as *no default was supplied*. - :data:`~pcapkit.corekit.enum.NO_DEFAULT` is now an exported instance - of the dedicated :class:`~pcapkit.corekit.enum.NoDefaultType`, + :data:`~pcapkit.corekit.sentinels.NO_DEFAULT` is now an exported instance + of the dedicated :class:`~pcapkit.corekit.sentinels.NoDefaultType`, compared with ``is`` rather than ``==`` -- so a caller-supplied ``-1`` is never confused with the sentinel. That fact is pinned directly, independent of ``get()``, by diff --git a/tests/const/test_const_enum_no_mint.py b/tests/const/test_const_enum_no_mint.py index 2c51d0d116..449095874f 100644 --- a/tests/const/test_const_enum_no_mint.py +++ b/tests/const/test_const_enum_no_mint.py @@ -2155,7 +2155,7 @@ class BespokeGetReplacementTests(unittest.TestCase): five converted classes whose hand-written ``get()`` -- on the ``default == -1`` convention #857/#859 already retired everywhere else -- was removed in favour of the base's :meth:`~pcapkit.corekit.enum. - EnumRegistry.get`, which uses :data:`~pcapkit.corekit.enum.NO_DEFAULT` + EnumRegistry.get`, which uses :data:`~pcapkit.corekit.sentinels.NO_DEFAULT` and never mints while resolving ``default`` (#864). Verified before this replacement that no caller in :mod:`pcapkit` or diff --git a/tests/const/test_const_ftp_featcode_case_903_unit.py b/tests/const/test_const_ftp_featcode_case_903_unit.py index 72cf4492d1..2c6b67acf9 100644 --- a/tests/const/test_const_ftp_featcode_case_903_unit.py +++ b/tests/const/test_const_ftp_featcode_case_903_unit.py @@ -323,11 +323,12 @@ def test_the_generated_module_imports_what_the_override_needs(self) -> 'None': rendered = LINE('Command', 'FTP Command', '', '', 'pcapkit.vendor.ftp.command') - self.assertIn('from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry', - rendered) + self.assertIn('from pcapkit.corekit.enum import EnumLookup, EnumRegistry\n' + 'from pcapkit.corekit.sentinels import NO_DEFAULT', rendered) import pcapkit.const.ftp.command as generated - self.assertIn('from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry', + self.assertIn('from pcapkit.corekit.enum import EnumLookup, EnumRegistry\n' + 'from pcapkit.corekit.sentinels import NO_DEFAULT', inspect.getsource(generated)) diff --git a/tests/const/test_const_registry_protocol.py b/tests/const/test_const_registry_protocol.py index fa849364c9..b94f3fa8a5 100644 --- a/tests/const/test_const_registry_protocol.py +++ b/tests/const/test_const_registry_protocol.py @@ -1301,13 +1301,13 @@ def _missing_(cls, value: 'Any') -> 'Any': class NoDefaultSentinelTests(unittest.TestCase): - """GitHub issue #857: :data:`~pcapkit.corekit.enum.NO_DEFAULT` is now an - instance of the dedicated :class:`~pcapkit.corekit.enum.NoDefaultType` + """GitHub issue #857: :data:`~pcapkit.corekit.sentinels.NO_DEFAULT` is now an + instance of the dedicated :class:`~pcapkit.corekit.sentinels.NoDefaultType` compared with ``is``, rather than the magic value ``-1`` compared with ``==``. Owner ruling on #859: a bare :class:`object` -- this batch's first attempt -- is no less safe under ``is``, but a dedicated class matches the - house convention :class:`~pcapkit.corekit.module.NullType` and - :class:`~pcapkit.corekit.fields.field.NoValueType` already set, and gives + house convention :class:`~pcapkit.corekit.sentinels.NullType` and + :class:`~pcapkit.corekit.sentinels.NoValueType` already set, and gives a readable :func:`repr` in a signature, in :func:`help`, and in a traceback. @@ -1325,12 +1325,12 @@ def setUp(self) -> None: def test_no_default_is_not_equal_to_any_plausible_caller_value(self) -> None: """The sentinel's whole point: unlike ``-1``, a - :class:`~pcapkit.corekit.enum.NoDefaultType` instance -- which defines + :class:`~pcapkit.corekit.sentinels.NoDefaultType` instance -- which defines no ``__eq__`` of its own, so it inherits identity comparison from :class:`object` -- cannot compare equal to anything a caller might legitimately pass as ``default``, not even ``-1.0``, which the old ``-1`` marker could not tell apart from itself.""" - from pcapkit.corekit.enum import NO_DEFAULT + from pcapkit.corekit.sentinels import NO_DEFAULT for value in (-1, -1.0, 0, '', None, False): with self.subTest(value=value): @@ -1342,7 +1342,7 @@ def test_repr_is_the_readable_form_not_a_bare_object_address(self) -> None: ``object()``: the latter prints as ```` wherever it turns up -- a signature, :func:`help`, a traceback -- and this prints as ```` instead.""" - from pcapkit.corekit.enum import NO_DEFAULT + from pcapkit.corekit.sentinels import NO_DEFAULT rendered = repr(NO_DEFAULT) self.assertEqual(rendered, '') @@ -1361,23 +1361,30 @@ def test_type_is_the_dedicated_sentinel_class_and_only_the_object_is_exported(se #911 is the issue that ruling's implementing work belongs to. The type is out of :attr:`__all__` while staying importable by its dotted path, which is what the last assertion here pins. + + GitHub issue #1744 then dropped the re-export from + :mod:`pcapkit.corekit.enum` (#1715: a moved name keeps no re-export at + its old path), so the export rule is now asserted against + :mod:`pcapkit.corekit.sentinels`, the only import path. """ import pcapkit.corekit.enum as enum_module + import pcapkit.corekit.sentinels as sentinels - self.assertIs(type(enum_module.NO_DEFAULT), enum_module.NoDefaultType) - self.assertIn('NO_DEFAULT', enum_module.__all__) - self.assertNotIn('NoDefaultType', enum_module.__all__) - self.assertTrue(hasattr(enum_module, 'NoDefaultType')) + self.assertIs(type(sentinels.NO_DEFAULT), sentinels.NoDefaultType) + self.assertIn('NO_DEFAULT', sentinels.__all__) + self.assertNotIn('NoDefaultType', sentinels.__all__) + self.assertNotIn('NO_DEFAULT', enum_module.__all__) + self.assertFalse(hasattr(enum_module, 'NoDefaultType')) def test_constructing_the_type_again_returns_the_same_instance(self) -> None: """The ``__new__`` singleton guard: a caller who does not realise - :data:`~pcapkit.corekit.enum.NO_DEFAULT` already exists and writes + :data:`~pcapkit.corekit.sentinels.NO_DEFAULT` already exists and writes ``NoDefaultType()`` themselves still gets back the one canonical sentinel, rather than a second, non-identical object that would silently fail ``is NO_DEFAULT`` inside :meth:`~pcapkit.corekit.enum.EnumRegistry.get` and be treated as a real (if useless) default instead of *no default*.""" - from pcapkit.corekit.enum import NO_DEFAULT, NoDefaultType + from pcapkit.corekit.sentinels import NO_DEFAULT, NoDefaultType self.assertIs(NoDefaultType(), NO_DEFAULT) # And repeatedly -- not merely once by coincidence. @@ -1387,15 +1394,15 @@ def test_identity_survives_an_ordinary_second_import(self) -> None: """An already-loaded module is cached in :data:`sys.modules`, so importing it again -- by any of the usual spellings -- must not construct a second sentinel. Only a genuine :func:`importlib.reload` - would do that (see :class:`~pcapkit.corekit.enum.NoDefaultType`'s own - docstring caveat, and :class:`~pcapkit.corekit.module.NullType`'s + would do that (see :class:`~pcapkit.corekit.sentinels.NoDefaultType`'s own + docstring caveat, and :class:`~pcapkit.corekit.sentinels.NullType`'s before it), and nothing in this package reloads :mod:`pcapkit.corekit.enum` after import.""" import importlib - from pcapkit.corekit.enum import NO_DEFAULT as first_import + from pcapkit.corekit.sentinels import NO_DEFAULT as first_import - module_again = importlib.import_module('pcapkit.corekit.enum') + module_again = importlib.import_module('pcapkit.corekit.sentinels') self.assertIs(module_again.NO_DEFAULT, first_import) diff --git a/tests/corekit/test_deferred_packet_1516_unit.py b/tests/corekit/test_deferred_packet_1516_unit.py index 39d9363588..a97b805cbc 100644 --- a/tests/corekit/test_deferred_packet_1516_unit.py +++ b/tests/corekit/test_deferred_packet_1516_unit.py @@ -212,11 +212,10 @@ def test_every_model_shares_the_one_class_mixin_first(self) -> 'None': self.assertIn('packet', cls.__additional__) self.assertIs(cls.__deferred__, importlib.import_module(deferred_module).Deferred) - def test_the_old_import_paths_resolve_to_the_one_class(self) -> 'None': - """The name was public at all three before the move, and still is. - - Each path is the class object itself rather than a copy of it, so the - two old definitions really are gone and nothing can diverge again. + def test_the_old_import_paths_are_gone(self) -> 'None': + """The name was public at all three before the move; GitHub issue #1744 + dropped those re-exports (#1715: a moved name keeps no re-export at its + old path), so :mod:`pcapkit.corekit.packet` is the one home. """ from pcapkit.corekit.packet import DeferredPacket @@ -227,8 +226,8 @@ def test_the_old_import_paths_resolve_to_the_one_class(self) -> 'None': 'pcapkit.foundation.traceflow.data.data'): with self.subTest(module=module): imported = importlib.import_module(module) - self.assertIs(imported.DeferredPacket, DeferredPacket) - self.assertIn('DeferredPacket', imported.__all__) + self.assertFalse(hasattr(imported, 'DeferredPacket')) + self.assertNotIn('DeferredPacket', imported.__all__) def test_each_model_resolves_its_own_placeholder_and_not_the_other(self) -> 'None': from pcapkit.corekit.packet import DeferredPacket diff --git a/tests/corekit/test_enum_lookup_base_unit.py b/tests/corekit/test_enum_lookup_base_unit.py index 175c010e0d..d90833c565 100644 --- a/tests/corekit/test_enum_lookup_base_unit.py +++ b/tests/corekit/test_enum_lookup_base_unit.py @@ -57,7 +57,8 @@ from aenum import IntEnum, IntFlag, StrEnum -from pcapkit.corekit.enum import NO_DEFAULT, EnumLookup, EnumRegistry +from pcapkit.corekit.enum import EnumLookup, EnumRegistry +from pcapkit.corekit.sentinels import NO_DEFAULT from pcapkit.utilities.exceptions import EnumValueError if TYPE_CHECKING: diff --git a/tests/corekit/test_module.py b/tests/corekit/test_module.py index e04df41db6..bdbc5d4d82 100644 --- a/tests/corekit/test_module.py +++ b/tests/corekit/test_module.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import importlib import pickle import sys import types @@ -15,6 +16,9 @@ def setUp(self) -> None: reimport_once_per_class(self) self.module = load_module('pcapkit.corekit.module', 'pcapkit/corekit/module.py') + # GitHub issue #1744: ``NULL`` and ``NullType`` are read from their one + # home, not from :mod:`pcapkit.corekit.module`, which only uses ``NULL``. + self.sentinels = importlib.import_module('pcapkit.corekit.sentinels') def _register(self, name: str, module: types.ModuleType) -> None: """Put ``module`` in :data:`sys.modules` for the duration of a test.""" @@ -157,7 +161,7 @@ def test_klass_raises_protocolerror_when_class_name_was_never_given(self) -> Non before either is consulted. """ - descriptor = self.module.ModuleDescriptor('demo.omitted', self.module.NULL) + descriptor = self.module.ModuleDescriptor('demo.omitted', self.sentinels.NULL) with mock.patch('importlib.import_module', side_effect=RuntimeError('must not be reached')) as importer: with self.assertRaises(self.module.ProtocolError) as caught: @@ -183,7 +187,7 @@ def test_klass_treats_an_explicit_null_string_as_a_real_class_name(self) -> None self._register('demo.explicit_null', target_module) descriptor = self.module.ModuleDescriptor('demo.explicit_null', '(null)') - self.assertIsNot(descriptor.name, self.module.NULL) + self.assertIsNot(descriptor.name, self.sentinels.NULL) with self.assertRaisesRegex(self.module.ProtocolError, r"'\(null\)'"): descriptor.klass # pylint: disable=pointless-statement @@ -194,31 +198,31 @@ def test_null_sentinel_is_not_a_string(self) -> None: identity -- so an equal-but-distinct ``'(null)'`` from a caller took a different branch than the sentinel itself, purely as a function of string interning. :data:`NULL` is now a dedicated - :class:`~pcapkit.corekit.module.NullType` singleton, so no string a + :class:`~pcapkit.corekit.sentinels.NullType` singleton, so no string a caller passes can compare equal to it by ``==`` or by ``is``. "Singleton" is asserted here, not only claimed: calling - :class:`~pcapkit.corekit.module.NullType` a second time must hand + :class:`~pcapkit.corekit.sentinels.NullType` a second time must hand back :data:`NULL` itself rather than a distinct, equally-valid instance -- see :meth:`test_null_sentinel_identity_survives_copy_and_pickle` for what a second instance breaks downstream. """ - self.assertNotIsInstance(self.module.NULL, str) - self.assertIsInstance(self.module.NULL, self.module.NullType) - self.assertFalse(self.module.NULL == '(null)') # pylint: disable=unneeded-not - self.assertIsNot(self.module.NULL, '(null)') - self.assertFalse(bool(self.module.NULL)) - self.assertEqual(repr(self.module.NULL), '') - self.assertIs(self.module.NullType(), self.module.NULL) + self.assertNotIsInstance(self.sentinels.NULL, str) + self.assertIsInstance(self.sentinels.NULL, self.sentinels.NullType) + self.assertFalse(self.sentinels.NULL == '(null)') # pylint: disable=unneeded-not + self.assertIsNot(self.sentinels.NULL, '(null)') + self.assertFalse(bool(self.sentinels.NULL)) + self.assertEqual(repr(self.sentinels.NULL), '') + self.assertIs(self.sentinels.NullType(), self.sentinels.NULL) def test_null_sentinel_identity_survives_copy_and_pickle(self) -> None: """:data:`NULL` stays the same object through :mod:`copy` and :mod:`pickle`. - Before this fix, :class:`~pcapkit.corekit.module.NullType` had no - :meth:`~pcapkit.corekit.module.NullType.__copy__`, - :meth:`~pcapkit.corekit.module.NullType.__deepcopy__` or - :meth:`~pcapkit.corekit.module.NullType.__reduce__` of its own, so + Before this fix, :class:`~pcapkit.corekit.sentinels.NullType` had no + :meth:`~pcapkit.corekit.sentinels.NullType.__copy__`, + :meth:`~pcapkit.corekit.sentinels.NullType.__deepcopy__` or + :meth:`~pcapkit.corekit.sentinels.NullType.__reduce__` of its own, so :func:`copy.copy`, :func:`copy.deepcopy` and every :mod:`pickle` protocol fell back to the default behaviour for a plain object and each produced a *second*, non-identical :class:`NullType` instance. @@ -232,7 +236,7 @@ def test_null_sentinel_identity_survives_copy_and_pickle(self) -> None: default one, because :mod:`pickle` protocols 0 and 1 reconstruct through :func:`copyreg._reconstructor` -- which calls :func:`object.__new__` directly -- rather than through - :meth:`NullType.__new__ `, + :meth:`NullType.__new__ `, so a fix that only guards ``__new__`` would still fail those two. Each protocol's assertion also runs a second time, outside @@ -249,7 +253,7 @@ def test_null_sentinel_identity_survives_copy_and_pickle(self) -> None: misbehaved. """ - NULL = self.module.NULL + NULL = self.sentinels.NULL self.assertIs(copy.copy(NULL), NULL) self.assertIs(copy.deepcopy(NULL), NULL) @@ -273,7 +277,7 @@ def test_klass_raises_protocolerror_not_typeerror_after_deepcopying_the_descript :meth:`test_klass_raises_protocolerror_when_class_name_was_never_given`. Before this fix, :func:`copy.deepcopy` on the descriptor recursed into that check's operand and minted a second, non-identical - :class:`~pcapkit.corekit.module.NullType`, so the identity check + :class:`~pcapkit.corekit.sentinels.NullType`, so the identity check failed silently and ``self.name`` -- still that stray sentinel, now masquerading as an ordinary class name -- reached :func:`getattr` directly. :func:`getattr` on a non-:class:`str` name raises a bare @@ -297,7 +301,7 @@ def test_klass_raises_protocolerror_not_typeerror_after_deepcopying_the_descript target_module = types.ModuleType('demo.deepcopy_target') self._register('demo.deepcopy_target', target_module) - descriptor = self.module.ModuleDescriptor('demo.deepcopy_target', self.module.NULL) + descriptor = self.module.ModuleDescriptor('demo.deepcopy_target', self.sentinels.NULL) copied = copy.deepcopy(descriptor) with self.assertRaises(self.module.ProtocolError): diff --git a/tests/corekit/test_schema_roundtrip_unit.py b/tests/corekit/test_schema_roundtrip_unit.py index 28669632d9..9c15b18e36 100644 --- a/tests/corekit/test_schema_roundtrip_unit.py +++ b/tests/corekit/test_schema_roundtrip_unit.py @@ -275,7 +275,7 @@ def minimal(field: 'Any', cls: 'type', depth: 'int', ctx: 'dict[str, Any]') -> ' """The minimal value of ``field``, as the field type implies it.""" from pcapkit.corekit.fields import ipaddress as fip from pcapkit.corekit.fields.collections import ListField - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.corekit.fields.misc import (ConditionalField, ForwardMatchField, NoValueField, PayloadField, SchemaField, SwitchField) from pcapkit.corekit.fields.numbers import EnumField, NumberField diff --git a/tests/corekit/test_sentinel_exports_unit.py b/tests/corekit/test_sentinel_exports_unit.py index 3255df529d..b0146bd74c 100644 --- a/tests/corekit/test_sentinel_exports_unit.py +++ b/tests/corekit/test_sentinel_exports_unit.py @@ -114,6 +114,14 @@ see its own docstring below for what dropped and why the assertion strength survives the drop. +GitHub issue #1744 then dropped the per-module re-exports, under the owner's +ruling on #1715 that a moved name keeps no re-export at its old path. So the +export rule is now asserted against :data:`CANONICAL_MODULE`, the only import +path, and the three per-module tests flip: :mod:`pcapkit.corekit.module`, +:mod:`pcapkit.corekit.fields.field` and :mod:`pcapkit.corekit.enum` export +neither the object nor the type. The type is importable by its dotted path +under :data:`CANONICAL_MODULE` only. + """ from __future__ import annotations @@ -149,10 +157,11 @@ #: :data:`PUBLIC_SENTINELS` below for the (still distinct) *shim* locations. #: Built by :func:`_sentinels` from the live import, not at module load. -#: The public three of :data:`SENTINELS`, as ``(module, object name, type name)``. -#: ``ABSENT`` is absent from it deliberately: it is private, so it is exported -#: neither way and the export rule does not reach it -- GitHub issue #937 dropped -#: its leading underscore, but not its privacy. +#: The public three of :data:`SENTINELS`, as ``(consumer module, object name, type +#: name)``. The consumer is the module that used to re-export the sentinel before +#: GitHub issue #1744. ``ABSENT`` is absent from it deliberately: it is private, so +#: it is exported neither way and the export rule does not reach it -- GitHub issue +#: #937 dropped its leading underscore, but not its privacy. PUBLIC_SENTINELS = ( ('pcapkit.corekit.module', 'NULL', 'NullType'), ('pcapkit.corekit.fields.field', 'NO_VALUE', 'NoValueType'), @@ -162,15 +171,12 @@ def _sentinels() -> 'tuple[tuple[str, object, type], ...]': """:data:`SENTINELS`, read from the ``pcapkit`` import that is live now.""" - enum = importlib.import_module('pcapkit.corekit.enum') - field = importlib.import_module('pcapkit.corekit.fields.field') - module = importlib.import_module('pcapkit.corekit.module') - protocol = importlib.import_module('pcapkit.protocols.protocol') + canonical = importlib.import_module(CANONICAL_MODULE) return ( - ('NULL', module.NULL, module.NullType), - ('NO_VALUE', field.NO_VALUE, field.NoValueType), - ('NO_DEFAULT', enum.NO_DEFAULT, enum.NoDefaultType), - ('ABSENT', protocol.ABSENT, protocol.AbsentType), + ('NULL', canonical.NULL, canonical.NullType), + ('NO_VALUE', canonical.NO_VALUE, canonical.NoValueType), + ('NO_DEFAULT', canonical.NO_DEFAULT, canonical.NoDefaultType), + ('ABSENT', canonical.ABSENT, canonical.AbsentType), ) @@ -233,10 +239,10 @@ def _sentinel_section() -> 'str': class SentinelExportTests(_ReimportCase): - """``__all__`` names the object and not the type, per module.""" + """``__all__`` names the object and not the type, in the canonical module only.""" - def test_module_exports_the_object_and_not_the_type(self) -> 'None': - """``['NULL', 'NullType', 'ModuleDescriptor']`` loses the middle entry. + def test_module_exports_neither_the_object_nor_the_type(self) -> 'None': + """``['NULL', 'ModuleDescriptor']`` lost ``NULL`` under GitHub issue #1744. ``ModuleDescriptor`` is not a sentinel and has to survive the edit, which is asserted here rather than in a separate test because removing it is the @@ -245,73 +251,58 @@ def test_module_exports_the_object_and_not_the_type(self) -> 'None': """ import pcapkit.corekit.module as module - self.assertIn('NULL', module.__all__) + self.assertNotIn('NULL', module.__all__) self.assertNotIn('NullType', module.__all__) self.assertIn('ModuleDescriptor', module.__all__) - def test_enum_exports_the_object_and_not_the_type(self) -> 'None': + def test_enum_exports_neither_the_object_nor_the_type(self) -> 'None': """``EnumLookup`` and ``EnumRegistry`` are not sentinels and stay. ``EnumLookup`` in particular: GitHub pull request #906 split it out as a public base and :file:`docs/source/contributing/conventions/registry-protocol.rst` cites - its ``get``, so dropping it while removing the sentinel type next to it + its ``get``, so dropping it while removing the sentinel next to it would break that reference. """ import pcapkit.corekit.enum as enum - self.assertIn('NO_DEFAULT', enum.__all__) + self.assertNotIn('NO_DEFAULT', enum.__all__) self.assertNotIn('NoDefaultType', enum.__all__) self.assertIn('EnumLookup', enum.__all__) self.assertIn('EnumRegistry', enum.__all__) - def test_field_exports_the_object_and_not_the_type(self) -> 'None': - """The direction that is an *addition*: ``NO_VALUE`` was exported by neither. - - :attr:`FieldBase.default ` - is documented as being this object, so the ruling reaches it: a value a - caller is told to compare against is a value ``import *`` should provide. - Named ``NoValue`` in :attr:`__all__` until GitHub issue #937 renamed the - object to ``NO_VALUE``; the type stayed ``NoValueType`` throughout. - - """ + def test_field_exports_neither_the_object_nor_the_type(self) -> 'None': + """``NO_VALUE`` was added here by #911 and removed again by #1744.""" import pcapkit.corekit.fields.field as field - self.assertIn('NO_VALUE', field.__all__) + self.assertNotIn('NO_VALUE', field.__all__) self.assertNotIn('NoValueType', field.__all__) self.assertIn('Field', field.__all__) def test_star_import_binds_the_objects_and_not_the_types(self) -> 'None': - """Through ``import *`` itself, which is the surface the ruling is about.""" - for module, obj, type_ in PUBLIC_SENTINELS: - namespace = _star_import(module) - with self.subTest(module=module): - self.assertIn(obj, namespace) - self.assertNotIn(type_, namespace) - - def test_star_import_hands_back_the_canonical_object(self) -> 'None': - """Not merely *a* binding of that name -- the one sentinel instance. + """Through ``import *`` itself, which is the surface the ruling is about. - A star-import that bound a second, non-identical object would satisfy the - test above and break every ``is`` check the sentinel exists for. + The canonical module binds every public object and no type; each former + re-export site binds neither. """ canonical = importlib.import_module(CANONICAL_MODULE) - for module, obj, _ in PUBLIC_SENTINELS: - namespace = _star_import(module) - with self.subTest(module=module): - # Taken from the defining module, never from the shim being star-imported: - # both sides of the comparison would otherwise read the same shim attribute - # and the check could not fail if a shim defined its own object. + namespace = _star_import(CANONICAL_MODULE) + for module, obj, type_ in PUBLIC_SENTINELS: + with self.subTest(module=CANONICAL_MODULE, sentinel=obj): self.assertIs(namespace[obj], getattr(canonical, obj)) + self.assertNotIn(type_, namespace) + with self.subTest(module=module, sentinel=obj): + consumer = _star_import(module) + self.assertNotIn(obj, consumer) + self.assertNotIn(type_, consumer) - def test_every_sentinel_type_is_still_importable_by_name(self) -> 'None': - """The exact extent of the breaking change: ``import *`` narrows, nothing else. + def test_every_sentinel_type_is_importable_from_the_canonical_module(self) -> 'None': + """Leaving :attr:`__all__` is not being made private. - Leaving :attr:`__all__` is not being made private. An annotation or an - ``isinstance`` check that names the type keeps working, and the tree itself - relies on that -- this module's own imports are the demonstration. + An annotation or an ``isinstance`` check that names the type keeps + working, by its dotted path under :data:`CANONICAL_MODULE`. """ for name, instance, type_ in _sentinels(): @@ -550,7 +541,7 @@ class NoValueIsTheDocumentedFieldDefaultTests(_ReimportCase): def test_an_undefaulted_field_reports_the_sentinel(self) -> 'None': """``is``, not ``==`` -- the whole point of a sentinel.""" - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.corekit.fields.strings import BytesField self.assertIs(BytesField(length=4).default, NO_VALUE) @@ -563,7 +554,7 @@ def test_setting_and_deleting_a_default_round_trips_through_the_sentinel(self) - exactly the confusion the sentinel exists to prevent. """ - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.corekit.fields.strings import BytesField field = BytesField(length=4) diff --git a/tests/corekit/test_sentinels_housing_unit.py b/tests/corekit/test_sentinels_housing_unit.py index 019c24253b..7986c20398 100644 --- a/tests/corekit/test_sentinels_housing_unit.py +++ b/tests/corekit/test_sentinels_housing_unit.py @@ -11,11 +11,13 @@ four -- :class:`~pcapkit.corekit.sentinels.NullType`, :class:`~pcapkit.corekit.sentinels.NoValueType`, :class:`~pcapkit.corekit.sentinels.NoDefaultType` and -:class:`~pcapkit.corekit.sentinels.AbsentType`, and their four instances -- and -each of the four original modules (:mod:`pcapkit.corekit.module`, +:class:`~pcapkit.corekit.sentinels.AbsentType`, and their four instances. The +four original modules (:mod:`pcapkit.corekit.module`, :mod:`pcapkit.corekit.fields.field`, :mod:`pcapkit.corekit.enum` and -:mod:`pcapkit.protocols.protocol`) keeps a re-export so that no existing -``from import `` breaks. +:mod:`pcapkit.protocols.protocol`) kept a re-export of each until GitHub issue +#1744 dropped them, under the ruling on #1715 that a moved name keeps no +re-export at its old path; ``tests/project/test_no_moved_name_re_exports_1744_unit.py`` +pins that. Each still imports the *object* it uses from here. None of this module exists before that move, which is exactly what makes it a regression test rather than a description: on ``origin/main`` at ``d31c0aaf6`` @@ -37,14 +39,12 @@ ImportError while importing test module '.../test_sentinels_housing_unit.py' ModuleNotFoundError: No module named 'pcapkit.corekit.sentinels' -:class:`IdentityAcrossShimsTests` is the load-bearing class here. A sentinel's +:class:`IdentityInConsumersTests` is the load-bearing class here. A sentinel's entire reason for existing is identity comparison (``value is SENTINEL``), so the -one thing worth pinning about a re-export shim is not merely that it resolves -- -an equal-but-distinct object would make every existing ``import`` line "work" -while quietly breaking every ``is NULL`` (etc.) check downstream -- but that -``from pcapkit.corekit.module import NULL`` and -``from pcapkit.corekit.sentinels import NULL`` hand back the *same* object, for -all four pairs. :meth:`IdentityAcrossShimsTests.test_shim_and_canonical_import_are_the_same_object` +object each consumer module compares against must be the *same* object as the +one :mod:`pcapkit.corekit.sentinels` hands out -- an equal-but-distinct object +would quietly break every ``is NULL`` (etc.) check in that module. +:meth:`IdentityInConsumersTests.test_consumer_binding_and_canonical_object_are_the_same` asserts that with ``is``, never ``==``. :class:`NoImportCycleTests` pins the other thing the issue asked to be checked @@ -67,18 +67,14 @@ import pcapkit.corekit.module as module_module import pcapkit.corekit.sentinels as sentinels import pcapkit.protocols.protocol as protocol_module -from pcapkit.corekit.enum import NO_DEFAULT, NoDefaultType -from pcapkit.corekit.fields.field import NO_VALUE, NoValueType -from pcapkit.corekit.module import NULL, NullType -from pcapkit.protocols.protocol import ABSENT, AbsentType +from pcapkit.corekit.sentinels import (ABSENT, NO_DEFAULT, NO_VALUE, NULL, AbsentType, + NoDefaultType, NoValueType, NullType) from tests._support import purge_modules -#: Every sentinel, as ``(instance name, shim module, shim instance, shim type)``. -#: The shim module is the *original* location -- the one whose re-export this -#: pins -- and is deliberately not :mod:`pcapkit.corekit.sentinels` itself, which -#: is what :data:`pcapkit.corekit.sentinels`'s own attributes are compared against -#: in each test below rather than being folded into this tuple a fifth time. -SHIMMED_SENTINELS = ( +#: Every sentinel, as ``(instance name, consumer module, instance, type)``. The +#: consumer module is the one that compares against the object, and so still +#: imports it from :mod:`pcapkit.corekit.sentinels` -- without re-exporting it. +CONSUMED_SENTINELS = ( ('NULL', module_module, NULL, NullType), ('NO_VALUE', field_module, NO_VALUE, NoValueType), ('NO_DEFAULT', enum_module, NO_DEFAULT, NoDefaultType), @@ -86,81 +82,29 @@ ) -class IdentityAcrossShimsTests(unittest.TestCase): - """A re-export must hand back the *same* object, not an equal one.""" +class IdentityInConsumersTests(unittest.TestCase): + """A consumer module must compare against the *same* object, not an equal one.""" - def test_shim_and_canonical_import_are_the_same_object(self) -> 'None': + def test_consumer_binding_and_canonical_object_are_the_same(self) -> 'None': """``is``, not ``==`` -- the whole point of a sentinel. - Fails on an implementation that re-*constructs* the sentinel at each - shim location (e.g. a shim that says ``NULL = NullType()`` instead of - ``from pcapkit.corekit.sentinels import NULL``) even though such a shim - would satisfy every ``isinstance`` check and every existing import - statement. + Fails on an implementation that re-*constructs* the sentinel in a + consumer (e.g. ``NULL = NullType()`` in :mod:`pcapkit.corekit.module` + instead of ``from pcapkit.corekit.sentinels import NULL``), which would + satisfy every ``isinstance`` check while breaking every ``is`` check. """ - for name, shim_instance, canonical_name in ( - ('NULL', NULL, 'NULL'), - ('NO_VALUE', NO_VALUE, 'NO_VALUE'), - ('NO_DEFAULT', NO_DEFAULT, 'NO_DEFAULT'), - ('ABSENT', ABSENT, 'ABSENT'), - ): + for name, consumer, instance, type_ in CONSUMED_SENTINELS: with self.subTest(sentinel=name): - self.assertIs(shim_instance, getattr(sentinels, canonical_name)) - - def test_shim_and_canonical_type_are_the_same_class_object(self) -> 'None': - """The *type*, not only the instance, is one object shared everywhere. - - A shim that rebuilt the type (``class NullType(sentinels.NullType): - pass``) would let ``isinstance`` checks against either name keep working - while making ``NullType is sentinels.NullType`` false -- exactly the - gap an ``is``-only check on the instance above would miss. - - """ - for name, shim_type, canonical_name in ( - ('NullType', NullType, 'NullType'), - ('NoValueType', NoValueType, 'NoValueType'), - ('NoDefaultType', NoDefaultType, 'NoDefaultType'), - ('AbsentType', AbsentType, 'AbsentType'), - ): - with self.subTest(sentinel=name): - self.assertIs(shim_type, getattr(sentinels, canonical_name)) - - def test_shim_module_attribute_is_the_same_object_too(self) -> 'None': - """Reached through the *module*, the way every real caller does it. - - The two tests above import through this module's own ``from ... import`` - statements at the top of the file, which python's import machinery - could in principle special-case. Re-reading the attribute off each - shim module object directly rules that out. - - """ - for name, shim_module, shim_instance, shim_type in SHIMMED_SENTINELS: - with self.subTest(sentinel=name): - self.assertIs(getattr(shim_module, name), shim_instance) - self.assertIs(shim_instance, getattr(sentinels, name)) - # The type is read off the *instance* (``type(shim_instance)``) - # rather than reconstructed from the instance's name, since the - # house ``Type`` name is not a straight capitalisation - # of every instance name (``NULL`` -> ``NullType``, not - # ``NULLType``) -- exactly what a caller comparing - # ``type(value) is NullType`` would do instead of guessing. - self.assertIs(type(shim_instance), shim_type) + self.assertIs(getattr(consumer, name), instance) + self.assertIs(instance, getattr(sentinels, name)) + self.assertIs(type(instance), type_) def test_defining_module_is_the_canonical_one_for_every_type(self) -> 'None': - """:attr:`type.__module__` names where the class statement executed. - - Distinct from the identity checks above: two different classes could in - principle report the same ``__module__`` by coincidence, but the same - *class object* reporting the wrong ``__module__`` would mean something - rebuilt it under a different name, which is exactly the failure mode - :meth:`test_shim_and_canonical_type_are_the_same_class_object` also - guards against from a different angle. - - """ - for name, _, _, shim_type in SHIMMED_SENTINELS: + """:attr:`type.__module__` names where the class statement executed.""" + for name, _, _, type_ in CONSUMED_SENTINELS: with self.subTest(sentinel=name): - self.assertEqual(shim_type.__module__, 'pcapkit.corekit.sentinels') + self.assertEqual(type_.__module__, 'pcapkit.corekit.sentinels') class NoImportCycleTests(unittest.TestCase): @@ -275,20 +219,14 @@ def test_all_names_the_three_public_objects_and_no_types(self) -> 'None': self.assertNotIn(name, sentinels.__all__) -class PreMovePickleStillLoadsTests(unittest.TestCase): - """A pickle written before the move still unpickles. - - :meth:`NullType.__reduce__` names its factory by module path, so every - payload written before GitHub issue #911 relocated the definitions carries - the literal string ``pcapkit.corekit.module _get_null``. Moving the - function without leaving the name behind makes those payloads unloadable -- - measured as ``AttributeError: module 'pcapkit.corekit.module' has no - attribute '_get_null'`` -- which is a break in on-disk data rather than in - the API, and so invisible to every other test here. :data:`NULL` is public - and is what :attr:`ModuleDescriptor.name` holds, so the payloads are real. +class PicklePayloadTests(unittest.TestCase): + """A ``NULL`` pickle names :mod:`pcapkit.corekit.sentinels` and loads back. - The fix is one re-exported private name in :mod:`pcapkit.corekit.module`; - these tests are what stop it being tidied away later as an unused import. + :meth:`NullType.__reduce__` names its factory by module path. A pickle + written by 1.5.0b5 or b6, before GitHub issue #911 relocated the + definitions, named ``pcapkit.corekit.module _get_null`` instead; the + re-export that kept those loadable was dropped under GitHub issue #1744, + since nothing in the library reads that path. """ @@ -296,64 +234,21 @@ def setUp(self) -> 'None': """Resolve the modules afresh rather than using this file's own imports. :class:`NoImportCycleTests` purges ``pcapkit*`` from :data:`sys.modules` - and does not put it back, and it sorts ahead of this class. The - module-level :data:`NULL` above is therefore a stale object by the time - these tests run, whose ``_get_null`` is no longer the one a fresh import - produces -- which pickle rejects outright with ``Can't pickle …: it's not - the same object as pcapkit.corekit.sentinels._get_null``. Re-importing - here keeps these tests measuring the re-export rather than that - pre-existing ordering hazard. + and does not put it back, and it sorts ahead of this class, so the + module-level :data:`NULL` above is a stale object by now. """ self.sentinels = importlib.import_module('pcapkit.corekit.sentinels') - self.module = importlib.import_module('pcapkit.corekit.module') - self.null = self.module.NULL - - #: A ``NULL`` payload as the pre-#911 code emitted it. - #: - #: Built by pointing the factory's ``__module__`` at its old home for the - #: duration of the dump, because that attribute is exactly what pickle - #: consults to name it. Substituting the module string in the finished bytes - #: does not work: protocols 4 and 5 length-prefix it, so shortening - #: ``pcapkit.corekit.sentinels`` to ``pcapkit.corekit.module`` leaves a - #: stale prefix and the payload fails to load as truncated rather than for - #: the reason under test. - def _pre_move_payload(self, protocol: 'int') -> 'bytes': - factory = self.sentinels._get_null - original = factory.__module__ - factory.__module__ = 'pcapkit.corekit.module' - try: - blob = pickle.dumps(self.null, protocol=protocol) - finally: - factory.__module__ = original - self.assertIn(b'pcapkit.corekit.module', blob) - self.assertNotIn(b'pcapkit.corekit.sentinels', blob) - return blob - - def test_the_old_factory_name_is_still_reachable(self) -> 'None': - """The re-export, stated as the property rather than as an import line.""" - self.assertIs(self.module._get_null, self.sentinels._get_null) - - def test_a_pre_move_payload_round_trips_to_the_singleton(self) -> 'None': - """Not merely "does not raise": it has to hand back *the* ``NULL``. + self.null = self.sentinels.NULL - From protocol 0, not 2. :meth:`NullType.__reduce__` says it covers - *"every :mod:`pickle` protocol"* and singles out 0 and 1 as the ones - that would otherwise reach :func:`copyreg._reconstructor`, so those are - the protocols the mechanism most exists for and the last ones to leave - unasserted. Measured: all six name the old path and load back to the - singleton. - - """ + def test_a_freshly_written_payload_names_the_new_home(self) -> 'None': + """Every protocol names the new home, never the old one, and loads back.""" for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): with self.subTest(protocol=protocol): - self.assertIs(pickle.loads(self._pre_move_payload(protocol)), self.null) - - def test_a_freshly_written_payload_names_the_new_home(self) -> 'None': - """The re-export is for reading old data, not for writing new.""" - blob = pickle.dumps(self.null, protocol=2) - self.assertIn(b'pcapkit.corekit.sentinels', blob) - self.assertNotIn(b'pcapkit.corekit.module', blob) + blob = pickle.dumps(self.null, protocol=protocol) + self.assertIn(b'pcapkit.corekit.sentinels', blob) + self.assertNotIn(b'pcapkit.corekit.module', blob) + self.assertIs(pickle.loads(blob), self.null) if __name__ == '__main__': diff --git a/tests/project/test_no_moved_name_re_exports_1744_unit.py b/tests/project/test_no_moved_name_re_exports_1744_unit.py new file mode 100644 index 0000000000..aeb51501bd --- /dev/null +++ b/tests/project/test_no_moved_name_re_exports_1744_unit.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- +"""A moved name keeps no re-export at its old path (GitHub issue #1744). + +The owner ruled on #1715 and #1103 that a moved name or module keeps no +re-export at its old path, ``TLSKeyLabel`` included, and #1744 swept the ones +that survived. Under ruling D1 on #1744 only an *explicit* re-export goes: an +alias assignment, a name in a non-home module's ``__all__``, or a name +documented as importable there. A module that still uses a name it imports from +the new home keeps the import, so the old path resolving through it is expected. + +Six checks pin that: + +* :class:`TestNotExported` -- no removed name is in its old module's + ``__all__``; +* :class:`TestNotBound` -- a name the old module only re-exported is not bound + there at all, and a name it still uses is the new home's own object; +* :class:`TestNoReader` -- nothing under :mod:`pcapkit`, ``examples``, + ``util`` or ``tests`` imports a removed name from its old path, or reads it + as an attribute of the old module imported whole; +* :class:`TestNotDocumented` -- no ``auto*`` directive under ``docs/`` documents + a removed name at its old path; +* :class:`TestNoReExportProse` -- no docstring under :mod:`pcapkit` and no page + under ``docs/`` says a removed name is importable, re-exported or available + at its old path; +* :class:`TestVersionRemoved` -- the two paths that shipped in 1.4.x carry + ``versionremoved:: 1.5.0`` in the module docstring and on the docs page, per + #1103; the beta-only ones are deleted with no directive. + +This module is unit-tier and reads no capture. + +""" +from __future__ import annotations + +import ast +import importlib +import pathlib +import re +import unittest + +#: Repository root. +ROOT = pathlib.Path(__file__).resolve().parents[2] + +#: Removed paths the old module no longer binds at all, as ``(old module, name)``. +PURE = ( + ('pcapkit.protocols.misc.pcapng', 'TLSKeyLabel'), + ('pcapkit.corekit.fields.field', 'NoValueType'), + ('pcapkit.corekit.enum', 'NoDefaultType'), + ('pcapkit.corekit.module', 'NullType'), + ('pcapkit.corekit.module', '_get_null'), + ('pcapkit.protocols.protocol', 'AbsentType'), + ('pcapkit.foundation.reassembly.data', 'DeferredPacket'), + ('pcapkit.foundation.reassembly.data.data', 'DeferredPacket'), + ('pcapkit.foundation.traceflow.data.data', 'DeferredPacket'), + ('pcapkit.protocols.application.ngap', 'ProcedureCode'), + ('pcapkit.protocols.application.ngap', 'ProtocolIE'), +) + +#: Removed paths the old module still uses, as ``(old module, name, new home)``. +#: D1: the import stays, the export goes. +USED = ( + ('pcapkit.corekit.fields.field', 'NO_VALUE', 'pcapkit.corekit.sentinels'), + ('pcapkit.corekit.enum', 'NO_DEFAULT', 'pcapkit.corekit.sentinels'), + ('pcapkit.corekit.module', 'NULL', 'pcapkit.corekit.sentinels'), + ('pcapkit.protocols.protocol', 'ABSENT', 'pcapkit.corekit.sentinels'), +) + +#: Every removed ``(old module, name)`` pair. +REMOVED = PURE + tuple((module, name) for module, name, _ in USED) + +#: The paths that shipped in 1.4.x, as ``(old module, name, source file, docs page)``. +SHIPPED = ( + ('pcapkit.protocols.misc.pcapng', 'TLSKeyLabel', + 'pcapkit/protocols/misc/pcapng.py', 'pcapkit/protocols/misc/pcapng.rst'), + ('pcapkit.corekit.fields.field', 'NoValueType', + 'pcapkit/corekit/fields/field.py', 'pcapkit/corekit/fields/field.rst'), +) + + +def _sources() -> 'list[pathlib.Path]': + """Every Python file a reader could live in.""" + files = [] # type: list[pathlib.Path] + for top in ('pcapkit', 'examples', 'util', 'tests'): + files.extend(sorted((ROOT / top).rglob('*.py'))) + return files + + +def _docs_dir() -> 'pathlib.Path': + """The Sphinx source directory.""" + return ROOT / 'docs' / 'source' + + +def _removed_by_module() -> 'dict[str, set[str]]': + """The removed names, grouped by the old module they left.""" + removed = {} # type: dict[str, set[str]] + for module_name, name in REMOVED: + removed.setdefault(module_name, set()).add(name) + return removed + + +def _dotted(node: 'ast.expr') -> 'str | None': + """The dotted name an attribute chain spells, or :data:`None`.""" + parts = [] # type: list[str] + while isinstance(node, ast.Attribute): + parts.append(node.attr) + node = node.value + if not isinstance(node, ast.Name): + return None + parts.append(node.id) + return '.'.join(reversed(parts)) + + +#: Wording that claims a name is still reachable somewhere. +CLAIM = re.compile(r'importable from|still importable|re-exported|available as', re.IGNORECASE) + + +def _sentences(text: 'str') -> 'list[str]': + """The sentences of ``text``, each on one line.""" + sentences = [] # type: list[str] + for paragraph in re.split(r'\n\s*\n', text): + sentences.extend(re.split(r'(?<=[.!?])\s+', ' '.join(paragraph.split()))) + return sentences + + +def _claims(text: 'str', owner: 'str | None' = None) -> 'list[str]': + """Removed paths a sentence of ``text`` claims a name is reachable at. + + A sentence counts only when it carries :data:`CLAIM` wording and names a + removed ``(old module, name)`` pair: the dotted path itself, or the old + module with the name -- given in the sentence, or by ``owner``, the object + whose docstring ``text`` is. + + """ + found = [] # type: list[str] + for sentence in _sentences(text): + if CLAIM.search(sentence) is None: + continue + for module_name, name in REMOVED: + module_re, name_re = re.escape(module_name), re.escape(name) + if re.search(rf'(? 'None': + for module_name, name in REMOVED: + with self.subTest(module=module_name, name=name): + module = importlib.import_module(module_name) + self.assertNotIn(name, module.__all__) + + +class TestNotBound(unittest.TestCase): + """A pure re-export is gone; a name still used is the new home's object.""" + + def test_pure_re_exports_are_not_bound(self) -> 'None': + for module_name, name in PURE: + with self.subTest(module=module_name, name=name): + module = importlib.import_module(module_name) + self.assertFalse(hasattr(module, name)) + + def test_used_names_are_the_new_homes_object(self) -> 'None': + for module_name, name, home in USED: + with self.subTest(module=module_name, name=name): + module = importlib.import_module(module_name) + self.assertIs(getattr(module, name), getattr(importlib.import_module(home), name)) + + +class TestNoReader(unittest.TestCase): + """Every other reader imports the name from its new home.""" + + def test_no_import_from_an_old_path(self) -> 'None': + removed = _removed_by_module() + found = [] # type: list[str] + for path in _sources(): + tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path)) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module in removed: + for alias in node.names: + if alias.name in removed[node.module]: + found.append(f'{path.relative_to(ROOT)}:{node.lineno}: ' + f'{node.module}.{alias.name}') + self.assertEqual(found, []) + + def test_no_attribute_read_through_an_old_module(self) -> 'None': + removed = _removed_by_module() + found = [] # type: list[str] + for path in _sources(): + tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path)) + # An old module bound under a local name, and the top-level names a + # plain ``import a.b.c`` binds, through which ``a.b.c.NAME`` reads. + aliases = {} # type: dict[str, str] + roots = set() # type: set[str] + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + if alias.asname is None: + roots.add(alias.name.partition('.')[0]) + elif alias.name in removed: + aliases[alias.asname] = alias.name + elif isinstance(node, ast.ImportFrom) and node.module and not node.level: + for alias in node.names: + if f'{node.module}.{alias.name}' in removed: + aliases[alias.asname or alias.name] = f'{node.module}.{alias.name}' + for node in ast.walk(tree): + if not isinstance(node, ast.Attribute) or not isinstance(node.ctx, ast.Load): + continue + base = _dotted(node.value) + if base in aliases: + module_name = aliases[base] + elif base in removed and base.partition('.')[0] in roots: + module_name = base + else: + continue + if node.attr in removed[module_name]: + found.append(f'{path.relative_to(ROOT)}:{node.lineno}: ' + f'{module_name}.{node.attr}') + self.assertEqual(found, []) + + +class TestNotDocumented(unittest.TestCase): + """No ``auto*`` directive documents a removed name at its old path.""" + + def test_no_directive_names_an_old_path(self) -> 'None': + found = [] # type: list[str] + for page in sorted(_docs_dir().rglob('*.rst')): + text = page.read_text(encoding='utf-8') + for module_name, name in REMOVED: + pattern = rf'(?m)^\s*\.\. auto\w+:: {re.escape(module_name)}\.{re.escape(name)}\s*$' + if re.search(pattern, text): + found.append(f'{page.relative_to(ROOT)}: {module_name}.{name}') + self.assertEqual(found, []) + + +class TestNoReExportProse(unittest.TestCase): + """No prose says a removed name is still reachable at its old path.""" + + def test_no_docstring_claims_an_old_path(self) -> 'None': + found = [] # type: list[str] + for path in sorted((ROOT / 'pcapkit').rglob('*.py')): + tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path)) + for node in ast.walk(tree): + if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + owner = None if isinstance(node, ast.Module) else node.name + for claim in _claims(ast.get_docstring(node) or '', owner): + found.append(f'{path.relative_to(ROOT)}: {claim}') + self.assertEqual(found, []) + + def test_no_docs_page_claims_an_old_path(self) -> 'None': + found = [] # type: list[str] + for page in sorted(_docs_dir().rglob('*.rst')): + for claim in _claims(page.read_text(encoding='utf-8')): + found.append(f'{page.relative_to(ROOT)}: {claim}') + self.assertEqual(found, []) + + +class TestVersionRemoved(unittest.TestCase): + """A path that shipped in 1.4.x is recorded as removed in 1.5.0, twice.""" + + def test_docstring_and_docs_page_carry_the_directive(self) -> 'None': + for module_name, name, source, page in SHIPPED: + old_path = f'``{module_name}.{name}``' + module = importlib.import_module(module_name) + for label, text in (('docstring', module.__doc__ or ''), + ('docs page', (_docs_dir() / page).read_text(encoding='utf-8'))): + with self.subTest(path=f'{module_name}.{name}', where=label): + blocks = re.findall(r'(?ms)^\.\. versionremoved:: 1\.5\.0\n\n(.*?)(?=^\S|\Z)', text) + self.assertTrue(any(old_path in block for block in blocks), + f'no versionremoved:: 1.5.0 block names {old_path} in {label}') + self.assertTrue((ROOT / source).is_file()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/protocols/application/test_httpv2_payload_length_unit.py b/tests/protocols/application/test_httpv2_payload_length_unit.py index a4a039f3fc..93ce90499b 100644 --- a/tests/protocols/application/test_httpv2_payload_length_unit.py +++ b/tests/protocols/application/test_httpv2_payload_length_unit.py @@ -565,7 +565,7 @@ def fields(self) -> 'list[tuple[str, Any, str]]': def packet(self, *, padded: 'bool') -> 'dict[str, Any]': """A synthetic ``packet`` mapping for a length callback. - ``pad_len`` is :data:`~pcapkit.corekit.fields.field.NO_VALUE` in the + ``pad_len`` is :data:`~pcapkit.corekit.sentinels.NO_VALUE` in the unpadded mapping because that is what the :class:`~pcapkit.corekit.fields.misc.ConditionalField` ahead of the payload actually leaves behind when ``PADDED`` is clear -- see @@ -578,7 +578,7 @@ def packet(self, *, padded: 'bool') -> 'dict[str, Any]': The mapping to hand the callback. """ - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE flags = {f'bit_{bit}': 0 for bit in range(8)} if padded: @@ -609,7 +609,7 @@ def test_the_unpadded_arm_returns_the_remaining_length(self) -> None: def test_the_unpadded_arm_does_not_consult_pad_len(self) -> None: """The unpadded arm must not evaluate ``pad_len``. - ``pad_len`` is :data:`~pcapkit.corekit.fields.field.NO_VALUE` when + ``pad_len`` is :data:`~pcapkit.corekit.sentinels.NO_VALUE` when ``PADDED`` is clear, so the conditional has to short-circuit around it. It does so in the fixed form because the conditional *is* the right operand of the subtraction. This does not discriminate the fix from the @@ -619,7 +619,7 @@ def test_the_unpadded_arm_does_not_consult_pad_len(self) -> None: which raises on the value the field machinery actually supplies. """ - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE packet = self.packet(padded=False) self.assertIs(packet['pad_len'], NO_VALUE) @@ -658,7 +658,7 @@ def test_the_padded_arm_reaches_zero_and_is_not_clamped(self) -> None: shows up here and at that boundary. """ - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE packet = { '__length__': self.PAD_LEN, diff --git a/tests/protocols/application/test_ngap_unit.py b/tests/protocols/application/test_ngap_unit.py index bd23415655..be13982ac4 100644 --- a/tests/protocols/application/test_ngap_unit.py +++ b/tests/protocols/application/test_ngap_unit.py @@ -76,8 +76,9 @@ def _sctp_frame(payload: bytes, ppid: int = 60) -> bytes: ########################################################################## def test_enumerations_are_defined_without_pycrate(self) -> None: - from pcapkit.protocols.application.ngap import (Criticality, PDUKind, ProcedureCode, - ProtocolIE) + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.const.ngap.protocol_ie import ProtocolIE + from pcapkit.protocols.application.ngap import Criticality, PDUKind # Counts as measured against pycrate 0.8.1's NGAP_Constants. A drift # here means the pasted-in block was regenerated against a different @@ -133,7 +134,8 @@ def test_unknown_procedure_and_ie_resolve_without_raising_or_aliasing(self) -> N value-suffixed member sharing one mutable default with every other unrecognised key. """ - from pcapkit.protocols.application.ngap import ProcedureCode, ProtocolIE + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.const.ngap.protocol_ie import ProtocolIE # A pycrate carrying a newer NGAP than this release names decodes IEs # this package does not know; they must report, not explode. @@ -178,7 +180,8 @@ def test_get_of_an_unresolvable_string_key_raises_rather_than_minting(self) -> N never take, which is exactly the path GitHub issue #880 measured the aliasing defect on. """ - from pcapkit.protocols.application.ngap import ProcedureCode, ProtocolIE + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.const.ngap.protocol_ie import ProtocolIE for enum in (ProcedureCode, ProtocolIE): with self.subTest(enum=enum.__name__): @@ -334,8 +337,9 @@ def test_sctp_payload_degrades_to_raw_without_pycrate(self) -> None: @unittest.skipUnless(HAS_PYCRATE, 'pycrate not installed') def test_fixture_decodes_and_surfaces_the_promised_fields(self) -> None: - from pcapkit.protocols.application.ngap import (NGAP, Criticality, PDUKind, - ProcedureCode, ProtocolIE) + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.const.ngap.protocol_ie import ProtocolIE + from pcapkit.protocols.application.ngap import NGAP, Criticality, PDUKind info = NGAP(NGSETUP_REQUEST).info @@ -415,7 +419,8 @@ def test_make_data_reports_every_field_make_consumes(self) -> None: @unittest.skipUnless(HAS_PYCRATE, 'pycrate not installed') def test_make_accepts_plain_python_values(self) -> None: - from pcapkit.protocols.application.ngap import NGAP, Criticality, ProcedureCode + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.protocols.application.ngap import NGAP, Criticality ngap = self._protocol() schema = ngap.make( @@ -487,7 +492,8 @@ def test_end_to_end_through_sctp(self) -> None: """PPID 60 dispatches an SCTP DATA chunk to :class:`NGAP`.""" import io - from pcapkit.protocols.application.ngap import NGAP, ProcedureCode + from pcapkit.const.ngap.procedure_code import ProcedureCode + from pcapkit.protocols.application.ngap import NGAP from pcapkit.protocols.transport.sctp import SCTP raw = self._sctp_frame(NGSETUP_REQUEST) diff --git a/tests/protocols/internet/test_ipv6_extension_unit.py b/tests/protocols/internet/test_ipv6_extension_unit.py index 78b7abf59a..6b467ebf65 100644 --- a/tests/protocols/internet/test_ipv6_extension_unit.py +++ b/tests/protocols/internet/test_ipv6_extension_unit.py @@ -1140,7 +1140,7 @@ def _assert_option_readers_cover_branchy_values(self, protocol_cls: type) -> Non from pcapkit.const.ipv6.seed_id import SeedID from pcapkit.const.ipv6.smf_dpd_mode import SMFDPDMode from pcapkit.const.ipv6.tagger_id import TaggerID - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.corekit.multidict import OrderedMultiDict from pcapkit.protocols.schema.internet import hopopt as hopopt_schema from pcapkit.protocols.schema.internet import ipv6_opts as opts_schema @@ -2239,13 +2239,13 @@ def _assert_padding_option_schema_sizes_itself(self, protocol_cls: type) -> None :func:`~pcapkit.protocols.schema.internet.hopopt.pad_opt_data_len` is the whole of the read-side fix, so it is tested directly as well as through a parse: it has to read a skipped conditional field -- which is - recorded as :data:`~pcapkit.corekit.fields.field.NO_VALUE`, not omitted -- + recorded as :data:`~pcapkit.corekit.sentinels.NO_VALUE`, not omitted -- as zero padding octets rather than passing it on to :class:`~pcapkit.corekit.fields.strings.PaddingField`. """ from pcapkit.const.ipv6.option import Option - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.schema.internet import hopopt as hopopt_schema from pcapkit.protocols.schema.internet import ipv6_opts as opts_schema diff --git a/tests/protocols/internet/test_ipv6_option_helpers_1519_unit.py b/tests/protocols/internet/test_ipv6_option_helpers_1519_unit.py index 1dd22be25f..472b998cd9 100644 --- a/tests/protocols/internet/test_ipv6_option_helpers_1519_unit.py +++ b/tests/protocols/internet/test_ipv6_option_helpers_1519_unit.py @@ -108,7 +108,7 @@ def test_shared_dpd_selector_reports_an_unregistered_mode(self) -> None: f'{prefix}: invalid SMF DPD mode: {SMFDPDMode.H_DPD}') def test_shared_length_helpers(self) -> None: - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE shared = self._shared() self.assertEqual(shared.rpl_opt_sub_tlv_len({'len': 9}), 5) diff --git a/tests/protocols/internet/test_mh_unit.py b/tests/protocols/internet/test_mh_unit.py index 34d0f812e1..852926a7fb 100644 --- a/tests/protocols/internet/test_mh_unit.py +++ b/tests/protocols/internet/test_mh_unit.py @@ -1984,13 +1984,13 @@ def test_mh_padding_option_schema_sizes_itself(self) -> None: :func:`~pcapkit.protocols.schema.internet.mh.pad_opt_data_len` has to read a skipped conditional field -- which is recorded as - :data:`~pcapkit.corekit.fields.field.NO_VALUE`, not omitted -- as zero + :data:`~pcapkit.corekit.sentinels.NO_VALUE`, not omitted -- as zero padding octets, rather than handing that singleton to :class:`~pcapkit.corekit.fields.strings.PaddingField` where it becomes an unusable :mod:`struct` template. """ from pcapkit.const.mh.option import Option - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.protocols.schema.internet import mh as schema self.assertEqual(schema.pad_opt_data_len({}), 0) diff --git a/tests/protocols/internet/test_smf_dpd_truncated_len_unit.py b/tests/protocols/internet/test_smf_dpd_truncated_len_unit.py index d2604f4637..71f7816c19 100644 --- a/tests/protocols/internet/test_smf_dpd_truncated_len_unit.py +++ b/tests/protocols/internet/test_smf_dpd_truncated_len_unit.py @@ -10,7 +10,7 @@ :meth:`Schema.unpack ` rewound the full three. The option was therefore re-read from the preceding ``PadN`` padding octet, as a ``Pad1`` option, so ``len`` was skipped and left as -:data:`~pcapkit.corekit.fields.field.NO_VALUE`. +:data:`~pcapkit.corekit.sentinels.NO_VALUE`. Every case builds its own octets in memory and reads no capture. The classes are imported inside each test, after diff --git a/tests/protocols/misc/test_pcapng_roundtrip_unit.py b/tests/protocols/misc/test_pcapng_roundtrip_unit.py index 08675f86bc..131f5ee31b 100644 --- a/tests/protocols/misc/test_pcapng_roundtrip_unit.py +++ b/tests/protocols/misc/test_pcapng_roundtrip_unit.py @@ -182,8 +182,9 @@ def test_key_logs_rebuild_with_comments_and_order(self) -> None: def test_key_log_writers_end_lines_with_newlines(self) -> None: """#1271: entries built from keywords parse back, with no wall-clock time.""" from pcapkit.const.pcapng.secrets_type import SecretsType + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel from pcapkit.corekit.multidict import OrderedMultiDict - from pcapkit.protocols.misc.pcapng import PCAPNG, TLSKeyLabel, WireGuardKeyLabel + from pcapkit.protocols.misc.pcapng import PCAPNG, WireGuardKeyLabel from pcapkit.protocols.schema.misc.pcapng import TLSKeyLog, WireGuardKeyLog proto = PCAPNG.__new__(PCAPNG) diff --git a/tests/protocols/misc/test_pcapng_unit.py b/tests/protocols/misc/test_pcapng_unit.py index 958d236241..49957bf896 100644 --- a/tests/protocols/misc/test_pcapng_unit.py +++ b/tests/protocols/misc/test_pcapng_unit.py @@ -1313,8 +1313,9 @@ def with_length(schema: object, length: int) -> object: def test_pcapng_record_and_secrets_readers_cover_dispatch(self) -> None: from pcapkit.const.pcapng.record_type import RecordType from pcapkit.const.pcapng.secrets_type import SecretsType + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel from pcapkit.corekit.multidict import OrderedMultiDict - from pcapkit.protocols.misc.pcapng import PCAPNG, TLSKeyLabel, WireGuardKeyLabel + from pcapkit.protocols.misc.pcapng import PCAPNG, WireGuardKeyLabel from pcapkit.protocols.schema.misc.pcapng import (EndRecord, IPv4Record, IPv6Record, TLSKeyLog, UnknownRecord, UnknownSecrets, WireGuardKeyLog, ZigBeeAPSKey, @@ -1387,14 +1388,10 @@ def test_pcapng_record_and_secrets_readers_cover_dispatch(self) -> None: def test_tls_key_label_tracks_rfc_9850_plus_documented_rsa_exception(self) -> None: # GitHub issue #886: the canonical definition moved from a hand-written # class in this module to a generated one under - # :mod:`pcapkit.const.pcapng.tls_key_label`, with a re-export left here - # so ``from pcapkit.protocols.misc.pcapng import TLSKeyLabel`` keeps - # working. Both names have to resolve to the *same* object, not merely - # to two classes that happen to agree. - from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel as CanonicalTLSKeyLabel - from pcapkit.protocols.misc.pcapng import TLSKeyLabel - - self.assertIs(TLSKeyLabel, CanonicalTLSKeyLabel) + # :mod:`pcapkit.const.pcapng.tls_key_label`. The re-export once left at + # ``pcapkit.protocols.misc.pcapng.TLSKeyLabel`` was dropped under #1744; + # tests/project/test_no_moved_name_re_exports_1744_unit.py pins that. + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel # RFC 9850 :rfc:`9850#section-4.2`'s "TLS SSLKEYLOGFILE Labels" registry # (10 rows as of 2026-09-28), plus the NSS-historical ``RSA`` label this @@ -1442,8 +1439,9 @@ def test_pcapng_schema_helpers_and_post_process_branches(self) -> None: from pcapkit.const.pcapng.option_type import OptionType from pcapkit.const.pcapng.record_type import RecordType from pcapkit.const.pcapng.secrets_type import SecretsType + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel from pcapkit.corekit.fields.numbers import UInt16Field, UInt32Field - from pcapkit.protocols.misc.pcapng import TLSKeyLabel, WireGuardKeyLabel + from pcapkit.protocols.misc.pcapng import WireGuardKeyLabel from pcapkit.protocols.schema.misc import pcapng as schema_pcapng from pcapkit.utilities.exceptions import FieldValueError, ProtocolError @@ -1768,8 +1766,9 @@ def test_pcapng_make_helpers_cover_options_records_secrets_and_blocks(self) -> N TLSKeyLog as DataTLSKeyLog, UnknownBlock as DataUnknownBlock, UnknownOption as DataUnknownOption) + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel from pcapkit.protocols.misc.pcapng import (PCAPNG, PacketDirection, PacketReception, - TLSKeyLabel, WireGuardKeyLabel) + WireGuardKeyLabel) from pcapkit.protocols.schema.misc.pcapng import CommentOption, EndOfOption from pcapkit.utilities.exceptions import ProtocolError @@ -2078,8 +2077,9 @@ def test_pcapng_remaining_constructor_branches_and_custom_dispatch(self) -> None from pcapkit.const.pcapng.secrets_type import SecretsType from pcapkit.const.pcapng.verdict_type import VerdictType from pcapkit.corekit.multidict import OrderedMultiDict + from pcapkit.const.pcapng.tls_key_label import TLSKeyLabel from pcapkit.protocols.misc.pcapng import (PCAPNG, PacketDirection, PacketReception, - TLSKeyLabel, WireGuardKeyLabel) + WireGuardKeyLabel) from pcapkit.protocols.schema.misc.pcapng import (CommentOption, DecryptionSecretsBlock, EndRecord, IPv4Record as SchemaIPv4Record, IPv6Record as SchemaIPv6Record, diff --git a/tests/protocols/schema/test_schema_unit.py b/tests/protocols/schema/test_schema_unit.py index b5f2afa82f..8863c599e9 100644 --- a/tests/protocols/schema/test_schema_unit.py +++ b/tests/protocols/schema/test_schema_unit.py @@ -390,7 +390,7 @@ class OptionalSchema(Schema): def test_schema_mapping_payload_list_and_default_edge_branches(self) -> None: NestedSchema, FeatureSchema, PayloadOnlySchema, _, _ = self._make_schema_classes() - from pcapkit.corekit.fields.field import NO_VALUE + from pcapkit.corekit.sentinels import NO_VALUE from pcapkit.corekit.fields.numbers import UInt8Field from pcapkit.protocols.schema.schema import Schema, schema_final from pcapkit.utilities.exceptions import ProtocolUnbound diff --git a/tests/protocols/test_construction_keyword_check_unit.py b/tests/protocols/test_construction_keyword_check_unit.py index 1c1477276d..cab5bc32c3 100644 --- a/tests/protocols/test_construction_keyword_check_unit.py +++ b/tests/protocols/test_construction_keyword_check_unit.py @@ -676,9 +676,9 @@ class SentinelTests(unittest.TestCase): ``Optional[frozenset[str]]`` and :class:`~pcapkit.protocols.application.http.HTTP` sets it -- so the third needs a marker of its own. - That marker is :data:`~pcapkit.protocols.protocol.ABSENT`, an instance of - :class:`~pcapkit.protocols.protocol.AbsentType` following - :class:`~pcapkit.corekit.fields.field.NoValueType`, which is how this library + That marker is :data:`~pcapkit.corekit.sentinels.ABSENT`, an instance of + :class:`~pcapkit.corekit.sentinels.AbsentType` following + :class:`~pcapkit.corekit.sentinels.NoValueType`, which is how this library already spells a singleton marker. It was a bare ``object()`` when #640 was first raised; a bare ``object()`` has no name in a traceback, no informative :func:`repr`, and no type a checker can hold anyone to. @@ -712,8 +712,7 @@ class SentinelTests(unittest.TestCase): def test_the_absent_marker_is_an_instance_of_its_own_type(self) -> None: """It has a type of its own, and the falsiness the convention carries.""" - from pcapkit.corekit.fields.field import NO_VALUE, NoValueType - from pcapkit.protocols.protocol import ABSENT, AbsentType + from pcapkit.corekit.sentinels import ABSENT, NO_VALUE, AbsentType, NoValueType self.assertIsInstance(ABSENT, AbsentType) @@ -743,7 +742,7 @@ def test_the_absent_marker_is_an_instance_of_its_own_type(self) -> None: def test_the_absent_marker_cannot_be_confused_with_another_singleton(self) -> None: """No other singleton in the library answers an ``is`` against it. - :data:`~pcapkit.corekit.fields.field.NO_VALUE` is the near neighbour and + :data:`~pcapkit.corekit.sentinels.NO_VALUE` is the near neighbour and the one deliberately *not* reused here: it is documented as the default value of :attr:`FieldBase.default ` and means "no value was @@ -751,8 +750,7 @@ def test_the_absent_marker_cannot_be_confused_with_another_singleton(self) -> No between the two would make either site's marker satisfy the other's test. """ - from pcapkit.corekit.fields.field import NO_VALUE, NoValueType - from pcapkit.protocols.protocol import ABSENT, AbsentType + from pcapkit.corekit.sentinels import ABSENT, NO_VALUE, AbsentType, NoValueType self.assertIsNot(ABSENT, NO_VALUE) self.assertNotIsInstance(ABSENT, NoValueType)