From 777f000a72916cc71e2bec9eee1a913d7b9ef13c Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Thu, 15 Oct 2026 13:26:10 -0400 Subject: [PATCH] fix(protocols): export Protocol from pcapkit.protocols.protocol (#1770) - protocol.py: __all__ listed only ProtocolBase, so a star-import did not bind Protocol, the module's user-facing class. It now lists both. ProtocolMeta stays out, as protocol.rst files it under Internal Definitions. - The registry error still names Protocol while the guard tests ProtocolBase. That gap is deliberate: the message's reader is a third party, who should subclass the public class (#1016). - New test_protocol_module_exports_1770_unit.py pins __all__ and the star-import, and that a non-Protocol built-in passes the guard. Narrow runs and tests/project (930 passed, 1 skipped) pass. Closes #1770 --- pcapkit/protocols/protocol.py | 2 +- .../test_protocol_module_exports_1770_unit.py | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/protocols/test_protocol_module_exports_1770_unit.py diff --git a/pcapkit/protocols/protocol.py b/pcapkit/protocols/protocol.py index 5fc80574b..be61e2833 100644 --- a/pcapkit/protocols/protocol.py +++ b/pcapkit/protocols/protocol.py @@ -72,7 +72,7 @@ from pcapkit.corekit.context import ProtocolContext -__all__ = ['ProtocolBase'] +__all__ = ['Protocol', 'ProtocolBase'] _PT = TypeVar('_PT', bound='Data') _ST = TypeVar('_ST', bound='Schema') diff --git a/tests/protocols/test_protocol_module_exports_1770_unit.py b/tests/protocols/test_protocol_module_exports_1770_unit.py new file mode 100644 index 000000000..5fbc05a86 --- /dev/null +++ b/tests/protocols/test_protocol_module_exports_1770_unit.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""GitHub issue #1770: :mod:`pcapkit.protocols.protocol` exports ``Protocol``. + +The module's :attr:`__all__` was ``['ProtocolBase']``, so ``from +pcapkit.protocols.protocol import *`` did not bind +:class:`~pcapkit.protocols.protocol.Protocol`, the class its docstring and +:file:`protocol.rst` present as the module's user-facing one. It is listed now. + +""" + +import importlib.util +import unittest +from unittest import mock + +RUNTIME_DEPS = ('tbtrim', 'aenum', 'chardet', 'dictdumper') +HAS_RUNTIME = all(importlib.util.find_spec(name) is not None for name in RUNTIME_DEPS) + + +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') +class RegistryGuardWidthTests(unittest.TestCase): + """The registry guard is wider than its error message, on purpose.""" + + def test_the_guard_accepts_a_protocol_base_that_is_not_a_protocol(self) -> None: + # The guard tests ProtocolBase because every built-in derives from it + # and none from Protocol. The message still names Protocol, since its + # reader is a third party who should subclass the public class (#1016). + from pcapkit.protocols.misc.raw import Raw + from pcapkit.protocols.protocol import Protocol, ProtocolBase + + self.assertFalse(issubclass(Raw, Protocol)) + with mock.patch.dict(ProtocolBase.__proto__): + ProtocolBase.register(1, Raw) + self.assertIs(ProtocolBase.__proto__[1], Raw) + + +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') +class ProtocolModuleAllTests(unittest.TestCase): + """``import *`` of :mod:`pcapkit.protocols.protocol` binds ``Protocol``.""" + + def test_protocol_is_in_all(self) -> None: + from pcapkit.protocols import protocol + + self.assertIn('Protocol', protocol.__all__) + self.assertIn('ProtocolBase', protocol.__all__) + + def test_star_import_binds_protocol(self) -> None: + from pcapkit.protocols import protocol + + namespace = {} # type: dict[str, object] + exec('from pcapkit.protocols.protocol import *', namespace) # pylint: disable=exec-used + self.assertIs(namespace.get('Protocol'), protocol.Protocol) + self.assertIs(namespace.get('ProtocolBase'), protocol.ProtocolBase) + + +if __name__ == '__main__': + unittest.main()