You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
refactor(protocols)!: replace the pcapkit.protocols.__proto__ dict with an auto-registering Registry class #2763
What
Replace the module-level dict pcapkit.protocols.__proto__ with a Registry class in pcapkit.protocols.registry that distributes protocol classes, and have ProtocolBase register new protocol classes into it automatically (owner, in session, 2026-11-04). pcapkit.protocols.__proto__ shipped and was autodoc'd in 1.5.1, so this is breaking: the old name keeps no alias and gets a versionremoved note.
Proposed shape: see the decision comment below for the open details.
Decision needed: three details of the Registry design. A worker starts now on the recommendations, so a ruling here only adjusts that work.
Which subclasses auto-register?
(a) Concrete subclasses only, skipping abstract bases such as Link, Internet and family bases.
(b) Every subclass.
Recommend (a). Abstract bases are not parseable layers, and the 1.5.1 registry never held them.
An auto-registered name that clashes with an existing entry (e.g. a test or user class IPv4(...)):
(a) Keep the existing entry and log the clash, as register_protocol does today; replacing stays explicit through Registry.register / register_protocol.
(b) The last definition wins.
Recommend (a). Defining a subclass should never silently re-point IPv4 lookups.
Lookup surface:
(a) Class methods (register, get, names, membership).
(b) A metaclass that makes Registry itself a read-only Mapping, so Registry['IPV4'] works.
Recommend (a). It is plainer, and matches the class-registry convention; (b) adds a metaclass for subscript syntax.
And for built-in protocols, we still do the opt-in method through ModuleDescriptor to hardcode directly in the registry mapping so that things are populated upfront and loaded lazily.
Reconciled. This combines your in-session revision with the two comments here. It replaces the Registry class proposal above.
Map: a class-level dunder ProtocolBase.__registry__ (name → protocol). The pcapkit.protocols.registry module is removed, along with the Registry class and the pcapkit.protocols.Registry export.
Built-ins: hardcoded in __registry__ as ModuleDescriptor entries, the same opt-in way the class __proto__ maps name their targets. The map is complete from the start, and each class loads only when its entry is resolved. That replaces my "getter imports the shipped packages first" idea.
New subclasses: concrete ones auto-register from ProtocolBase.__init_subclass__ (rec. 1). A clash with an existing name keeps the existing entry and logs (rec. 2). A built-in replacing its own descriptor is not a clash.
Interface: class methods, per registry-protocol.rst (rec. 3). There is an owning register_protocol and a getter that resolves descriptors. The pcapkit.foundation.registry.protocols.register_protocol wrapper stays, retagged # NOTE: __registry__.
Removal:pcapkit.protocols.__proto__ is removed with no alias and a versionremoved note, so this is breaking.
Nothing is left open; needs: decision is off. A worker starts now. The first one stopped before writing any code.
Actually the auto registry goes on Protocol (the public facing base class) not ProtocolBase (the internal one), following the convention of other Base classes in the library.
Reconciled: the registry moves to Protocol, the public-facing base class, instead of ProtocolBase, the internal one, to match the library's other Base classes.
What moves:Protocol.__registry__, the register_protocol classmethod, the getters, and auto-registration from Protocol.__init_subclass__.
Unchanged: everything else in the reconciliation above. That covers the ModuleDescriptor built-ins, concrete-only registration, keep-and-log on a clash, and removing pcapkit.protocols.__proto__ with no alias.
Follow-up for the worker: it is building on ProtocolBase right now. Moving the members is a small change, so it goes in as soon as the worker reports, before the PR opens.
I think the dunder registry mapping stays on ProtocolBase with its classmethod and property? Only the init_subclass hook for auto registering goes to Protocol class.
Yes, reconciled. This supersedes my previous comment.
Stays on ProtocolBase: the __registry__ map, its owning register_protocol classmethod, and the getters.
Moves to Protocol: only the auto-registration hook, in Protocol.__init_subclass__. So a class gets auto-registered only when it subclasses the public base.
Read surface: classmethod getters, as you ruled in session. "Property" isn't settled yet. If you also want a property-style accessor, for example AnyProtocol.registry through ProtocolMeta, say so and it goes in the same PR.
For the worker: it is already building the map and API on ProtocolBase. The follow-up is to move only the hook into Protocol.__init_subclass__.
Yes so a property or class property returns the entire entry mapping plus a get_protocol or similar (name not settles classmethod does the lookup and returns the class.
Reconciled. The read surface becomes two accessors.
A class property returning the whole entry mapping. I recommend AnyProtocol.registry. It would be a property on ProtocolMeta, because a classmethod wrapping a property was deprecated in 3.11 and removed in 3.13, and our KTLO floor runs from 3.8 to 3.15. It returns a read-only view of __registry__. Built-ins show there as their ModuleDescriptor entries, so reading the map imports nothing.
A classmethod that looks up a name and returns the class. I recommend get_protocol(name, default=...). It resolves a descriptor on first use.
The names aren't settled. I'll go with registry and get_protocol unless you rule otherwise; renaming them is a one-line change before merge. Everything else stands: the map and API live on ProtocolBase, and the auto-registration hook is in Protocol.__init_subclass__.
Activity
JarryShaw commentedon Nov 4, 2026
Decision needed: three details of the
Registrydesign. A worker starts now on the recommendations, so a ruling here only adjusts that work.Which subclasses auto-register?
Link,Internetand family bases.Recommend (a). Abstract bases are not parseable layers, and the 1.5.1 registry never held them.
An auto-registered name that clashes with an existing entry (e.g. a test or user
class IPv4(...)):register_protocoldoes today; replacing stays explicit throughRegistry.register/register_protocol.Recommend (a). Defining a subclass should never silently re-point
IPv4lookups.Lookup surface:
register,get,names, membership).Registryitself a read-onlyMapping, soRegistry['IPV4']works.Recommend (a). It is plainer, and matches the class-registry convention; (b) adds a metaclass for subscript syntax.
JarryShaw commentedon Nov 4, 2026
Take all recommendations
JarryShaw commentedon Nov 4, 2026
And for built-in protocols, we still do the opt-in method through ModuleDescriptor to hardcode directly in the registry mapping so that things are populated upfront and loaded lazily.
JarryShaw commentedon Nov 4, 2026
Reconciled. This combines your in-session revision with the two comments here. It replaces the
Registryclass proposal above.ProtocolBase.__registry__(name → protocol). Thepcapkit.protocols.registrymodule is removed, along with theRegistryclass and thepcapkit.protocols.Registryexport.__registry__asModuleDescriptorentries, the same opt-in way the class__proto__maps name their targets. The map is complete from the start, and each class loads only when its entry is resolved. That replaces my "getter imports the shipped packages first" idea.ProtocolBase.__init_subclass__(rec. 1). A clash with an existing name keeps the existing entry and logs (rec. 2). A built-in replacing its own descriptor is not a clash.registry-protocol.rst(rec. 3). There is an owningregister_protocoland a getter that resolves descriptors. Thepcapkit.foundation.registry.protocols.register_protocolwrapper stays, retagged# NOTE: __registry__.pcapkit.protocols.__proto__is removed with no alias and aversionremovednote, so this isbreaking.Nothing is left open;
needs: decisionis off. A worker starts now. The first one stopped before writing any code.JarryShaw commentedon Nov 4, 2026
Actually the auto registry goes on Protocol (the public facing base class) not ProtocolBase (the internal one), following the convention of other Base classes in the library.
JarryShaw commentedon Nov 4, 2026
Reconciled: the registry moves to
Protocol, the public-facing base class, instead ofProtocolBase, the internal one, to match the library's other Base classes.Protocol.__registry__, theregister_protocolclassmethod, the getters, and auto-registration fromProtocol.__init_subclass__.ModuleDescriptorbuilt-ins, concrete-only registration, keep-and-log on a clash, and removingpcapkit.protocols.__proto__with no alias.ProtocolBaseright now. Moving the members is a small change, so it goes in as soon as the worker reports, before the PR opens.JarryShaw commentedon Nov 4, 2026
I think the dunder registry mapping stays on ProtocolBase with its classmethod and property? Only the init_subclass hook for auto registering goes to Protocol class.
JarryShaw commentedon Nov 4, 2026
Yes, reconciled. This supersedes my previous comment.
ProtocolBase: the__registry__map, its owningregister_protocolclassmethod, and the getters.Protocol: only the auto-registration hook, inProtocol.__init_subclass__. So a class gets auto-registered only when it subclasses the public base.AnyProtocol.registrythroughProtocolMeta, say so and it goes in the same PR.ProtocolBase. The follow-up is to move only the hook intoProtocol.__init_subclass__.JarryShaw commentedon Nov 4, 2026
Yes so a property or class property returns the entire entry mapping plus a get_protocol or similar (name not settles classmethod does the lookup and returns the class.
JarryShaw commentedon Nov 4, 2026
Reconciled. The read surface becomes two accessors.
AnyProtocol.registry. It would be a property onProtocolMeta, because aclassmethodwrapping apropertywas deprecated in 3.11 and removed in 3.13, and our KTLO floor runs from 3.8 to 3.15. It returns a read-only view of__registry__. Built-ins show there as theirModuleDescriptorentries, so reading the map imports nothing.get_protocol(name, default=...). It resolves a descriptor on first use.The names aren't settled. I'll go with
registryandget_protocolunless you rule otherwise; renaming them is a one-line change before merge. Everything else stands: the map and API live onProtocolBase, and the auto-registration hook is inProtocol.__init_subclass__.refactor(protocols)!: replace the pcapkit.protocols.__proto__ dict wi…