Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/source/contributing/conventions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ House Conventions
* :ref:`extension-header-subclassing` -- a protocol class hierarchy;
* :ref:`protocol-family-grouping` -- how a protocol family's base and members
spell :meth:`~pcapkit.protocols.protocol.Protocol.id`, how a protocol's
package, modules and classes are named, and how quietly a next-layer fallback keeps a
refused layer as :class:`~pcapkit.protocols.misc.raw.protocol.Raw`;
package, modules and classes are named and imported, and how quietly a
next-layer fallback keeps a refused layer as
:class:`~pcapkit.protocols.misc.raw.protocol.Raw`;
* :ref:`protocol-member-order` -- the order of members in a protocol class body;
* :ref:`protocol-layer-placement` -- which subpackage a dissector belongs in;
* :ref:`process` -- the repository rather than any of its code;
Expand Down
12 changes: 12 additions & 0 deletions docs/source/contributing/conventions/protocol-family-grouping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,18 @@ these files with ``filter_files`` set, so one named on the command line is skipp
too, and ``make isort`` skips every ``__init__.py`` besides. Ruled in review of
:pr:`2753` (:issue:`2534`).

**The packages above a protocol's package import it on first use**, ruled by the
owner in session and recorded on :issue:`2730`. :mod:`pcapkit.protocols`, its
layer packages, :mod:`~pcapkit.protocols.misc.pcap` and
:mod:`~pcapkit.protocols.misc.checksum` name each protocol class under
``if TYPE_CHECKING:`` and bind the ``__getattr__`` and ``__dir__`` that
:func:`~pcapkit.utilities.importlib.lazy_exports` returns, as a :mod:`pcapkit.const`
package does (:ref:`const-vendor-pairing`), and :mod:`pcapkit` re-exports
:mod:`pcapkit.protocols` the same way. A protocol's own package keeps the imports
above, since importing its ``protocol`` module imports its ``schema`` and ``data``
anyway. :data:`pcapkit.protocols.__proto__` names every protocol class, so
:mod:`pcapkit.protocols.registry` builds it when it is first reached.

**A family's shared pieces live in the family base's package**, and its members
import them (:issue:`2534`): :class:`~pcapkit.protocols.link.c_tag.protocol.C_Tag`
and :class:`~pcapkit.protocols.link.s_tag.protocol.S_Tag` read the schema and data
Expand Down
6 changes: 6 additions & 0 deletions docs/source/pcapkit/protocols/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,15 @@ from given information. The class hierarchy of :mod:`pcapkit.protocols`:
Protocol Registry
-----------------

.. module:: pcapkit.protocols.registry

.. autodata:: pcapkit.protocols.__proto__
:no-value:

:mod:`pcapkit.protocols.registry` builds it the first time it is reached, and
imports every protocol class :mod:`pcapkit.protocols` exports to do so
(:issue:`2730`).

.. seealso::

Please refer to :func:`pcapkit.foundation.registry.protocols.protocol.register_protocol`
Expand Down
9 changes: 8 additions & 1 deletion pcapkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@
###############################################################################

import warnings
from typing import TYPE_CHECKING

import tbtrim

from pcapkit.utilities.exceptions import BaseError
from pcapkit.utilities.importlib import lazy_exports
from pcapkit.utilities.logging import DEVMODE
from pcapkit.utilities.warnings import DevModeWarning

Expand All @@ -93,7 +95,9 @@
exception=BaseError, strict=False)

from pcapkit.interface import *
from pcapkit.protocols import *

if TYPE_CHECKING:
from pcapkit.protocols import *

__all__ = [
'extract', 'reassemble', 'trace', # Interface Functions
Expand Down Expand Up @@ -134,3 +138,6 @@

#: version number
__version__ = '1.5.1'

# Protocols are imported the first time they are reached (#2730).
__getattr__, __dir__ = lazy_exports(__name__, globals(), packages=('protocols',))
4 changes: 3 additions & 1 deletion pcapkit/foundation/reassembly/reassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import math
from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast

from pcapkit.protocols import __proto__ as protocol_registry
from pcapkit.protocols.misc.raw.protocol import Raw
from pcapkit.utilities.exceptions import FieldValueError, UnsupportedCall
from pcapkit.utilities.logging import get_logger
Expand Down Expand Up @@ -79,6 +78,9 @@ def protocol(cls) -> 'Type[ProtocolBase]':
"""Protocol of current reassembly object."""
if hasattr(cls, '__protocol_type__'):
return cls.__protocol_type__
# NOTE: read here, not on import, which would build it and import every protocol (#2730).
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel
return protocol_registry.get(cls.name.upper(), Raw)

@property
Expand Down
11 changes: 10 additions & 1 deletion pcapkit/foundation/registry/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import aenum

from pcapkit.corekit.sentinels import MISSING
from pcapkit.protocols import __proto__ as protocol_registry
from pcapkit.protocols.protocol import ProtocolBase
from pcapkit.utilities.exceptions import RegistryError
from pcapkit.utilities.logging import get_logger
Expand Down Expand Up @@ -120,6 +119,10 @@ class rather than supplied by a caller, and this function is the funnel
(:issue:`2529`).

"""
# NOTE: read here, not on import, which would build it and import every protocol (#2730).
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel

if not isinstance(protocol, type):
raise RegistryError(f'protocol must be a class, not {protocol!r}')
if not issubclass(protocol, ProtocolBase):
Expand Down Expand Up @@ -230,6 +233,9 @@ def _release_name(target: 'tuple[type, Any]') -> 'None':
target: The ``(destination, code)`` being registered over.

"""
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel

lease = _CODE_LEASES.pop(target, None)
if lease is None:
return
Expand Down Expand Up @@ -274,6 +280,9 @@ def _register_name(protocol: 'Type[ProtocolBase]', *targets: 'tuple[type, Any]')
*targets: The ``(destination, code)`` pairs it was registered at.

"""
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel

name = protocol.__name__.upper()
with _LEASE_LOCK:
stack = _NAME_LEASES.get(name)
Expand Down
4 changes: 3 additions & 1 deletion pcapkit/foundation/traceflow/traceflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

from pcapkit.corekit.module import ModuleDescriptor
from pcapkit.dumpkit.common import make_dumper
from pcapkit.protocols import __proto__ as protocol_registry
from pcapkit.protocols.misc.raw.protocol import Raw
from pcapkit.utilities.exceptions import FileExists, RegistryError, UnsupportedCall, stacklevel
from pcapkit.utilities.logging import get_logger
Expand Down Expand Up @@ -80,6 +79,9 @@ def protocol(cls) -> 'Type[ProtocolBase]':
"""Protocol of current object."""
if hasattr(cls, '__protocol_type__'):
return cls.__protocol_type__
# NOTE: read here, not on import, which would build it and import every protocol (#2730).
from pcapkit.protocols import \
__proto__ as protocol_registry # pylint: disable=import-outside-toplevel
return protocol_registry.get(cls.name.upper(), Raw)

@property
Expand Down
44 changes: 27 additions & 17 deletions pcapkit/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@
:mod:`pcapkit.protocols` is a collection of all protocol families,
with detailed implementation and methods.

Each protocol class, and :data:`__proto__`, is imported the first time it is
reached rather than with this package, through
:func:`~pcapkit.utilities.importlib.lazy_exports` (:issue:`2730`).

"""
from typing import TYPE_CHECKING

# NOTE: ahead of any protocol module: defining ``Protocol`` imports Raw and
# NoPayload, which import it back, so either imported first is met half-built.
# Base Class for Protocols
from pcapkit.protocols.protocol import Protocol

# Utility Classes for Protocols
from pcapkit.protocols.misc import *

# Protocols & Macros
from pcapkit.protocols.link import *
from pcapkit.protocols.internet import *
from pcapkit.protocols.transport import *
from pcapkit.protocols.application import *

# Protocol Data
from pcapkit.protocols import data
from pcapkit.protocols.data import Data
Expand All @@ -31,11 +28,25 @@
from pcapkit.protocols import schema
from pcapkit.protocols.schema import Schema

from pcapkit.utilities.importlib import lazy_exports

if TYPE_CHECKING:
from typing import Type

from pcapkit.protocols.protocol import ProtocolBase

# Utility Classes for Protocols
from pcapkit.protocols.misc import *

# Protocols & Macros
from pcapkit.protocols.link import *
from pcapkit.protocols.internet import *
from pcapkit.protocols.transport import *
from pcapkit.protocols.application import *

#: dict[str, ~typing.Type[~pcapkit.protocols.protocol.ProtocolBase]]: Protocol registry.
__proto__ = {} # type: dict[str, Type[ProtocolBase]]

__all__ = [
# PCAP Headers
'Header', 'Frame',
Expand Down Expand Up @@ -74,14 +85,7 @@
'OSPF',
'RARP', 'DRARP',
'SSL', 'SSLv2', 'SSLv3', 'TLS', 'DTLS',
]

#: dict[str, ~typing.Type[~pcapkit.protocols.protocol.ProtocolBase]]: Protocol registry.
__proto__ = {} # type: dict[str, Type[ProtocolBase]]
for name in __all__:
__proto__[name.upper()] = globals()[name]

__all__.extend((
# Protocol Numbers
'LINKTYPE', 'ETHERTYPE', 'TRANSTYPE', 'APPTYPE',

Expand All @@ -90,4 +94,10 @@

# Protocol Schema
'Schema', 'schema',
))
]

# NOTE: ``Protocol`` is bound above, and named here so that ``dir()`` lists it.
__getattr__, __dir__ = lazy_exports(__name__, globals(), {
'Protocol': ('protocol', 'Protocol'),
'__proto__': ('registry', '__proto__'),
}, packages=('misc', 'link', 'internet', 'transport', 'application'))
67 changes: 47 additions & 20 deletions pcapkit/protocols/application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
:mod:`pcapkit.protocols.application` is a collection of all protocols in
application layer.

Each protocol class, and the base class
:class:`~pcapkit.protocols.application.application.Application`, is imported the
first time it is reached rather than with this package, through
:func:`~pcapkit.utilities.importlib.lazy_exports` (:issue:`2730`).

"""
# TODO: Implements BGP, DHCP, DHCPv6, DNS, IMAP, LDAP, MQTT,
# NNTP, NTP, ONC:RPC, POP, RIP, RTP, SIP, SMTP, SNMP,
Expand All @@ -17,29 +22,33 @@
# dependency; see pcapkit.protocols.application.ngap.protocol. Importing the module
# is free -- ``pycrate`` is imported inside the parse path, not here.

# Base Class for Application Layer
from pcapkit.protocols.application.application import Application

# Utility Classes for Protocols
from pcapkit.protocols.application.ftp.protocol import FTP
from pcapkit.protocols.application.ftp_data.protocol import FTP_DATA
from pcapkit.protocols.application.httpv1.protocol import HTTPv1
from pcapkit.protocols.application.httpv2.protocol import HTTPv2
from pcapkit.protocols.application.ngap.protocol import NGAP
from pcapkit.protocols.application.ospf.protocol import OSPF
from pcapkit.protocols.application.rarp.protocol import RARP
from pcapkit.protocols.application.drarp.protocol import DRARP
from pcapkit.protocols.application.sslv2.protocol import SSLv2
from pcapkit.protocols.application.tls.protocol import TLS
from pcapkit.protocols.application.sslv3.protocol import SSLv3
from pcapkit.protocols.application.dtls.protocol import DTLS

# Base Classes
from pcapkit.protocols.application.http.protocol import HTTP
from pcapkit.protocols.application.ssl.protocol import SSL
from typing import TYPE_CHECKING

# Application Layer Protocol Numbers
from pcapkit.const.reg.apptype import AppType as APPTYPE
from pcapkit.utilities.importlib import lazy_exports

if TYPE_CHECKING:
# Base Class for Application Layer
from pcapkit.protocols.application.application import Application

# Utility Classes for Protocols
from pcapkit.protocols.application.ftp import FTP
from pcapkit.protocols.application.ftp_data import FTP_DATA
from pcapkit.protocols.application.httpv1 import HTTPv1
from pcapkit.protocols.application.httpv2 import HTTPv2
from pcapkit.protocols.application.ngap import NGAP
from pcapkit.protocols.application.ospf import OSPF
from pcapkit.protocols.application.rarp import RARP
from pcapkit.protocols.application.drarp import DRARP
from pcapkit.protocols.application.sslv2 import SSLv2
from pcapkit.protocols.application.tls import TLS
from pcapkit.protocols.application.sslv3 import SSLv3
from pcapkit.protocols.application.dtls import DTLS

# Base Classes
from pcapkit.protocols.application.http import HTTP
from pcapkit.protocols.application.ssl import SSL

__all__ = [
'APPTYPE',
Expand All @@ -50,3 +59,21 @@
'RARP', 'DRARP',
'SSL', 'SSLv2', 'SSLv3', 'TLS', 'DTLS',
]

__getattr__, __dir__ = lazy_exports(__name__, globals(), {
'Application': ('application', 'Application'),
'FTP': ('ftp', 'FTP'),
'FTP_DATA': ('ftp_data', 'FTP_DATA'),
'HTTP': ('http', 'HTTP'),
'HTTPv1': ('httpv1', 'HTTPv1'),
'HTTPv2': ('httpv2', 'HTTPv2'),
'NGAP': ('ngap', 'NGAP'),
'OSPF': ('ospf', 'OSPF'),
'RARP': ('rarp', 'RARP'),
'DRARP': ('drarp', 'DRARP'),
'SSL': ('ssl', 'SSL'),
'SSLv2': ('sslv2', 'SSLv2'),
'SSLv3': ('sslv3', 'SSLv3'),
'TLS': ('tls', 'TLS'),
'DTLS': ('dtls', 'DTLS'),
})
Loading
Loading