Skip to content

refactor(protocols)!: replace the pcapkit.protocols.__proto__ dict with an auto-registering Registry class #2763

Closed

Description

@JarryShaw

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.

  • Registry holds a class-level map with class methods to register a protocol and to look one up by name, following the class-registry convention (feat(link): decode radiotap's optional fields (#2576) #2657).
  • ProtocolBase.__init_subclass__ registers each concrete subclass under its upper-cased class name.
  • The first read still imports every shipped protocol package, so the registry is complete under the lazy imports of perf(protocols): import the packages above a protocol's on first use (#2730) #2757.
  • pcapkit.foundation.registry.register_protocol and the existing callers (ProtocolBase lookups, corekit.fields.misc, the sentinels) go through Registry.
  • It is reachable as pcapkit.protocols.registry.Registry, and lazily as pcapkit.protocols.Registry, not in __all__.
  • Class-level __proto__ dispatch maps on protocol classes are unchanged.

Activity

  1. added
    breakingBreaks public-facing behaviour or API (apply alongside the type label)
    refactorRestructuring for its own sake — neither a fix nor a new capability (refactor: prefix)
    wipWork in flight - a covering PR is open or an agent is actively on it
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    on Nov 4, 2026
  2. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    Decision needed: three details of the Registry design. A worker starts now on the recommendations, so a ruling here only adjusts that work.

    1. 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.

    2. 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.

    3. 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.

  3. added this to the PyPCAPKit v1.6 milestone on Nov 4, 2026
  4. moved this to Needs decision in PyPCAPKiton Nov 4, 2026
  5. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    Take all recommendations

  6. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.

  7. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.

  8. removed
    needs: decisionWaiting on the maintainer to decide — not blocked by other work
    on Nov 4, 2026
  9. moved this from Needs decision to WIP in PyPCAPKiton Nov 4, 2026
  10. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.

  11. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.
  12. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.

  13. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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__.
  14. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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.

  15. JarryShaw commented on Nov 4, 2026

    @JarryShaw
    OwnerAuthor

    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__.

  16. removed
    wipWork in flight - a covering PR is open or an agent is actively on it
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingBreaks public-facing behaviour or API (apply alongside the type label)refactorRestructuring for its own sake — neither a fix nor a new capability (refactor: prefix)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions