ModuleDescriptor.klass raises a bare builtins.AttributeError when the class name does not exist in the module, so every register_* function in pcapkit/foundation/registry/protocols.py fails a bad class name with a stdlib exception rather than a pcapkit one.
Measured on ee51366a6 (PR #815's head) and reproducible on main:
register_apptype(port, 'pcapkit.protocols.misc.raw', 'tcp', TransportProtocol.tcp)
-> AttributeError: module 'pcapkit.protocols.misc.raw' has no attribute 'tcp'
register_apptype(AppType.http, 'pcapkit.protocols.misc.raw') # class_ omitted
-> AttributeError: module 'pcapkit.protocols.misc.raw' has no attribute '(null)'
The second is the more telling one: the NULL sentinel '(null)' reaches getattr and is reported as if the caller had asked for a class of that name.
Scope: protocols.py builds ModuleDescriptor(module, class_) in nine places, so this is the whole register_* family, not one function. The exception originates at pcapkit/corekit/module.py:37 — re-derive that line before changing it.
This is the same family as #805/#811 (struct.error escaping FieldBase.length), #828/#829 (ValueError escaping NumberField.__call__) and #831 (the second shift): a caller cannot catch these as pcapkit errors. The fix belongs in ModuleDescriptor.klass, raising ProtocolError from pcapkit.utilities.exceptions and naming both the module and the missing class, so all nine call sites gain it at once.
One coupling to know before changing it. #815 added a test asserting assertRaises(AttributeError) with assertIn("'tcp'", …) — deliberately, to prove that a str third argument is resolved as a class name and never sniffed as a transport. That assertion will need updating in the same change; the property it protects should survive as a ProtocolError whose message still contains the attempted class name.
Also worth folding in: a missing class_ should not reach getattr at all. When module is a str and class_ is still the NULL sentinel, the caller has omitted a required argument, and saying so is more useful than reporting '(null)' as an absent attribute.
Found by #815's cross-review while judging whether that AttributeError was the right failure for the residual. The conclusion there was to leave it — fixing it in one function would make register_apptype inconsistent with its eight siblings — which is what makes this a family-wide issue rather than part of that PR.
ModuleDescriptor.klassraises a barebuiltins.AttributeErrorwhen the class name does not exist in the module, so everyregister_*function inpcapkit/foundation/registry/protocols.pyfails a bad class name with a stdlib exception rather than apcapkitone.Measured on
ee51366a6(PR #815's head) and reproducible onmain:The second is the more telling one: the
NULLsentinel'(null)'reachesgetattrand is reported as if the caller had asked for a class of that name.Scope:
protocols.pybuildsModuleDescriptor(module, class_)in nine places, so this is the wholeregister_*family, not one function. The exception originates atpcapkit/corekit/module.py:37— re-derive that line before changing it.This is the same family as #805/#811 (
struct.errorescapingFieldBase.length), #828/#829 (ValueErrorescapingNumberField.__call__) and #831 (the second shift): a caller cannot catch these aspcapkiterrors. The fix belongs inModuleDescriptor.klass, raisingProtocolErrorfrompcapkit.utilities.exceptionsand naming both the module and the missing class, so all nine call sites gain it at once.One coupling to know before changing it. #815 added a test asserting
assertRaises(AttributeError)withassertIn("'tcp'", …)— deliberately, to prove that astrthird argument is resolved as a class name and never sniffed as a transport. That assertion will need updating in the same change; the property it protects should survive as aProtocolErrorwhose message still contains the attempted class name.Also worth folding in: a missing
class_should not reachgetattrat all. Whenmoduleis astrandclass_is still theNULLsentinel, the caller has omitted a required argument, and saying so is more useful than reporting'(null)'as an absent attribute.Found by #815's cross-review while judging whether that
AttributeErrorwas the right failure for the residual. The conclusion there was to leave it — fixing it in one function would makeregister_apptypeinconsistent with its eight siblings — which is what makes this a family-wide issue rather than part of that PR.