From f5d4bda7ddc6a4f13b497cdf2222dd2c2e1e7d2a Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Sat, 26 Sep 2026 13:58:19 -0400 Subject: [PATCH] fix(vendor): stop get() minting; _missing_ fixed for 21 of 105 (#775) Tier 1 of #775: get()'s string-key miss no longer permanently registers a member, across all 105 edited pcapkit.const registries. _missing_'s bounded-range fix only changes behaviour for the 21 of those 105 whose vendor crawler leaves process() unmodified -- of the remaining 84, 83 keep their own bespoke process()-driven _missing_ and still mint via extend_enum, and the 84th has no unassigned range to mint from at all. - pcapkit/vendor/default.py: get() raises (or falls back to a value lookup) instead of extend_enum()-ing a name; the 21-registry _missing_ range branch mints via a fresh _unregistered_member() instead. Both gain register(value, name), the explicit caller-named path that still grows the registry -- value-first, matching _unregistered_member's own order, so the two no longer disagree. - pcapkit/toolkit/scapy.py, pyshark.py: let an unresolvable link-layer name raise. On base these sites minted: Enum_LinkType.get() defaulted to -1, so get('ETH') ran extend_enum(LinkType, 'ETH', -1) and returned a freshly minted LinkType.ETH = -1, and a second unresolvable name aliased onto the same -1 member. Asked to pick a real DLT as the fallback instead, the ruling was to raise: NULL and RAW are genuine DLTs with their own handler protocol classes, not stand-ins for "unknown link type". The bare KeyError is caught and re-raised as MissingKeyError, this package's own house exception for a lookup miss. An IP-rooted scapy packet now raises rather than getting a minted placeholder. - pcapkit/toolkit/pyshark.py: PyShark reports Wireshark's PDML filter name, not a DLT name -- `eth`, not `ethernet` -- so every Ethernet capture would otherwise raise, and engines/pyshark.py calls tcp_traceflow for each TCP packet under trace=True. A curated FILTER_NAME_TO_LINKTYPE table translates the two names that are unambiguous against Wireshark's dissector registrations -- `eth` -> ETHERNET and `tr` -> IEEE802_5, each bound to exactly one WTAP_ENCAP_* -- and is consulted before LinkType.get, which still resolves a filter name that already spells a member (`ppp`, `fddi`). A genuinely unknown name still raises. Measured with tshark 4.6.9 and editcap -T across the 158 of its 226 encapsulations an Ethernet source can be rewritten into: `sll` serves both LINUX_SLL (113) and LINUX_SLL2 (276) under one filter name, so it gets no entry and raises. `raw` serves RAW/IPV4/IPV6 (101/228/229) and `null` serves NULL/LOOP (0/108); neither raises, because 'RAW' and 'NULL' are member names, so the fallback answers 101 and 0 -- silently wrong for 228, 229 and 108, which is #843 rather than anything this table introduces. `ip` and `ipv6` never arrive as the root layer in any of those 158; a raw IPv6 capture roots at `raw`. The other 68 refuse the rewrite and are untested. `fr` turns out to be single-DLT (both its encapsulations write 107) so it is mappable, left out as unneeded scope; `wlan` was not investigated. Refs #840, which also asks for `ip` -> IPV4 and `loop` -> NULL. Only `ip` is refused; `loop` already resolves to LinkType.LOOP (108) through the fallback, and is anyway the wrong filter name -- tshark registers `loop` as Configuration Test Protocol (loopback), an Ethernet payload, while a DLT_NULL or DLT_LOOP capture presents `null`. `null` resolves to LinkType.NULL (0), so DLT_LOOP silently reads as DLT_NULL; that ambiguity is #843. The issue stays open. - pcapkit/protocols/internet/hopopt.py, ipv6_opts.py: the SeedID/TaggerID get() misses in _read_opt_mpl/_read_opt_smf_dpd now raise this reader's own ProtocolError instead of a bare KeyError, matching the neighbouring "unknown QS function"-style wording. The guard is for an unresolvable string key from a hand-built schema, not a wire value -- an unassigned wire value still mints via _missing_, deferred to tier 2. The key is pre-bound to None so the except handler can report it even when the namespace dict itself lacks the key, instead of re-subscripting and letting a second, bare KeyError escape. - The non-minting pseudo-member now carries the bare registry word -- Unassigned, not Unassigned_39 -- per the maintainer's ruling. The numeric suffix existed to keep a *minted* member unique in __members__; nothing is minted here, and a pseudo-member is absent from __members__, _member_map_ and _value2member_map_ alike, so two same-named ones coexist and repr still disambiguates by value. The 49 call sites across those 21 registries drop the suffix; every extend_enum name is left numbered, because a minted Unassigned would collide. - 105 generated pcapkit/const/ modules hand-edited to match, then proved equivalent by regenerating all 21 changed registries from live IANA: the resulting diff is byte-identical to the hand-edit, and seven further registries across both template kinds regenerate with no change at all, so no upstream drift is riding along. - tests/const/test_const_enum_no_mint.py, tests/toolkit/test_scapy_unit.py, tests/toolkit/test_pyshark_unit.py and tests/protocols/internet/ test_ipv6_extension_unit.py: new/updated coverage for all of the above. Out of scope, counted exactly by AST census rather than estimated: 1026 minting call sites remain -- 1007 in _missing_ and 7 in get() across pcapkit/const/, plus 12 hand-written ones in protocols/internet/mh.py and protocols/application/ngap.py that no regeneration can reach. AppType alone holds 767 of them. register()/register_alias account for a further 106 sites which are the by-design caller-named mint path and stay. Deferred to tier 2. Build: py_compile clean on all 117 touched files. Tests: coverage run -m pytest tests/const/, 98 passed, 39458 subtests, 57.77% branch (57.79% baseline, steady); combined with the new/updated test paths above, 61.76%. --- docs/source/pcapkit/toolkit/3rdparty.rst | 2 + pcapkit/const/arp/hardware.py | 53 +- pcapkit/const/arp/operation.py | 51 +- pcapkit/const/esp/cipher.py | 49 +- pcapkit/const/esp/integrity.py | 49 +- pcapkit/const/hip/certificate.py | 51 +- pcapkit/const/hip/cipher.py | 51 +- pcapkit/const/hip/di.py | 51 +- pcapkit/const/hip/ecdsa_curve.py | 51 +- pcapkit/const/hip/ecdsa_low_curve.py | 51 +- pcapkit/const/hip/eddsa_curve.py | 49 +- pcapkit/const/hip/esp_transform_suite.py | 51 +- pcapkit/const/hip/group.py | 49 +- pcapkit/const/hip/hi_algorithm.py | 53 +- pcapkit/const/hip/hit_suite.py | 51 +- pcapkit/const/hip/nat_traversal.py | 51 +- pcapkit/const/hip/notify_message.py | 75 ++- pcapkit/const/hip/packet.py | 49 +- pcapkit/const/hip/parameter.py | 49 +- pcapkit/const/hip/registration.py | 53 +- pcapkit/const/hip/registration_failure.py | 53 +- pcapkit/const/hip/suite.py | 51 +- pcapkit/const/hip/transport.py | 49 +- pcapkit/const/http/error_code.py | 49 +- pcapkit/const/http/frame.py | 49 +- pcapkit/const/http/setting.py | 49 +- pcapkit/const/ipv4/classification_level.py | 49 +- pcapkit/const/ipv4/option_class.py | 49 +- pcapkit/const/ipv4/option_number.py | 49 +- pcapkit/const/ipv4/protection_authority.py | 49 +- pcapkit/const/ipv4/qs_function.py | 49 +- pcapkit/const/ipv4/router_alert.py | 49 +- pcapkit/const/ipv4/tos_del.py | 49 +- pcapkit/const/ipv4/tos_ecn.py | 49 +- pcapkit/const/ipv4/tos_pre.py | 49 +- pcapkit/const/ipv4/tos_rel.py | 49 +- pcapkit/const/ipv4/tos_thr.py | 49 +- pcapkit/const/ipv4/ts_flag.py | 49 +- pcapkit/const/ipv6/option.py | 49 +- pcapkit/const/ipv6/option_action.py | 49 +- pcapkit/const/ipv6/qs_function.py | 49 +- pcapkit/const/ipv6/router_alert.py | 49 +- pcapkit/const/ipv6/routing.py | 49 +- pcapkit/const/ipv6/seed_id.py | 49 +- pcapkit/const/ipv6/smf_dpd_mode.py | 49 +- pcapkit/const/ipv6/tagger_id.py | 49 +- pcapkit/const/ipx/packet.py | 49 +- pcapkit/const/ipx/socket.py | 49 +- pcapkit/const/l2tp/type.py | 49 +- pcapkit/const/mh/access_type.py | 49 +- pcapkit/const/mh/ack_status_code.py | 49 +- pcapkit/const/mh/ani_suboption.py | 49 +- pcapkit/const/mh/auth_subtype.py | 49 +- pcapkit/const/mh/binding_error.py | 49 +- pcapkit/const/mh/binding_revocation.py | 49 +- pcapkit/const/mh/cga_extension.py | 49 +- pcapkit/const/mh/cga_sec.py | 49 +- pcapkit/const/mh/cga_type.py | 49 +- pcapkit/const/mh/dhcp_support_mode.py | 49 +- pcapkit/const/mh/dns_status_code.py | 49 +- pcapkit/const/mh/dsmip6_tls_packet.py | 49 +- pcapkit/const/mh/dsmipv6_home_address.py | 49 +- pcapkit/const/mh/enumerating_algorithm.py | 49 +- pcapkit/const/mh/fb_ack_status.py | 49 +- pcapkit/const/mh/fb_action.py | 49 +- pcapkit/const/mh/fb_indication_trigger.py | 49 +- pcapkit/const/mh/fb_type.py | 49 +- pcapkit/const/mh/flow_id_status.py | 49 +- pcapkit/const/mh/flow_id_suboption.py | 49 +- pcapkit/const/mh/handoff_type.py | 49 +- pcapkit/const/mh/handover_ack_status.py | 49 +- pcapkit/const/mh/handover_initiate_status.py | 49 +- pcapkit/const/mh/home_address_reply.py | 49 +- pcapkit/const/mh/lla_code.py | 49 +- pcapkit/const/mh/lma_mag_suboption.py | 49 +- pcapkit/const/mh/mn_group_id.py | 49 +- pcapkit/const/mh/mn_id_subtype.py | 49 +- pcapkit/const/mh/operator_id.py | 49 +- pcapkit/const/mh/option.py | 49 +- pcapkit/const/mh/packet.py | 49 +- pcapkit/const/mh/qos_attribute.py | 49 +- pcapkit/const/mh/revocation_status_code.py | 49 +- pcapkit/const/mh/revocation_trigger.py | 49 +- pcapkit/const/mh/status_code.py | 49 +- pcapkit/const/mh/traffic_selector.py | 49 +- pcapkit/const/mh/upa_status.py | 49 +- pcapkit/const/mh/upn_reason.py | 49 +- pcapkit/const/ospf/authentication.py | 53 +- pcapkit/const/ospf/packet.py | 53 +- pcapkit/const/pcapng/block_type.py | 49 +- pcapkit/const/pcapng/filter_type.py | 49 +- pcapkit/const/pcapng/hash_algorithm.py | 49 +- pcapkit/const/pcapng/record_type.py | 49 +- pcapkit/const/pcapng/secrets_type.py | 49 +- pcapkit/const/pcapng/verdict_type.py | 49 +- pcapkit/const/reg/ethertype.py | 49 +- pcapkit/const/reg/linktype.py | 49 +- pcapkit/const/reg/transtype.py | 49 +- pcapkit/const/sctp/cause_code.py | 57 +- pcapkit/const/sctp/chunk.py | 57 +- pcapkit/const/sctp/parameter.py | 57 +- .../const/sctp/payload_protocol_identifier.py | 53 +- pcapkit/const/tcp/checksum.py | 49 +- pcapkit/const/tcp/mp_tcp_option.py | 49 +- pcapkit/const/tcp/option.py | 49 +- pcapkit/const/vlan/priority_level.py | 49 +- pcapkit/protocols/internet/hopopt.py | 24 +- pcapkit/protocols/internet/ipv6_opts.py | 24 +- pcapkit/toolkit/pyshark.py | 92 +++- pcapkit/toolkit/scapy.py | 26 +- pcapkit/utilities/decorators.py | 17 +- pcapkit/vendor/default.py | 51 +- tests/const/test_const_enum_get.py | 6 +- tests/const/test_const_enum_no_mint.py | 500 ++++++++++++++++++ .../internet/test_ipv6_extension_unit.py | 11 + tests/protocols/transport/test_sctp_unit.py | 6 +- tests/toolkit/test_pyshark_unit.py | 189 ++++++- tests/toolkit/test_scapy_unit.py | 47 ++ 118 files changed, 5824 insertions(+), 414 deletions(-) create mode 100644 tests/const/test_const_enum_no_mint.py diff --git a/docs/source/pcapkit/toolkit/3rdparty.rst b/docs/source/pcapkit/toolkit/3rdparty.rst index 11070ec49c..714d7b0dab 100644 --- a/docs/source/pcapkit/toolkit/3rdparty.rst +++ b/docs/source/pcapkit/toolkit/3rdparty.rst @@ -82,6 +82,8 @@ usable for its caller. .. autofunction:: pcapkit.toolkit.pyshark.tcp_traceflow +.. autodata:: pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE + Auxiliary Functions ------------------- diff --git a/pcapkit/const/arp/hardware.py b/pcapkit/const/arp/hardware.py index eb65bcdc9c..4f8b4450aa 100644 --- a/pcapkit/const/arp/hardware.py +++ b/pcapkit/const/arp/hardware.py @@ -165,9 +165,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Hardware': if default == -1: raise return Hardware(default) - if key not in Hardware._member_map_: # pylint: disable=no-member - return extend_enum(Hardware, key, default) - return Hardware[key] # type: ignore[misc] + try: + return Hardware[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Hardware(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Hardware': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Hardware': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Hardware': @@ -181,8 +224,8 @@ def _missing_(cls, value: 'int') -> 'Hardware': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 39 <= value <= 255: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 258 <= value <= 65534: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/arp/operation.py b/pcapkit/const/arp/operation.py index be4b7caffe..9200c86a78 100644 --- a/pcapkit/const/arp/operation.py +++ b/pcapkit/const/arp/operation.py @@ -118,9 +118,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Operation': if default == -1: raise return Operation(default) - if key not in Operation._member_map_: # pylint: disable=no-member - return extend_enum(Operation, key, default) - return Operation[key] # type: ignore[misc] + try: + return Operation[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Operation(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Operation': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Operation': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Operation': @@ -134,5 +177,5 @@ def _missing_(cls, value: 'int') -> 'Operation': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 26 <= value <= 65534: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/esp/cipher.py b/pcapkit/const/esp/cipher.py index 9c5ea0315c..49d0b8c940 100644 --- a/pcapkit/const/esp/cipher.py +++ b/pcapkit/const/esp/cipher.py @@ -235,9 +235,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Cipher': if default == -1: raise return Cipher(default) - if key not in Cipher._member_map_: # pylint: disable=no-member - return extend_enum(Cipher, key, default) - return Cipher[key] # type: ignore[misc] + try: + return Cipher[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Cipher(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Cipher': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Cipher': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Cipher': diff --git a/pcapkit/const/esp/integrity.py b/pcapkit/const/esp/integrity.py index 7e2a4cee76..52abb09c66 100644 --- a/pcapkit/const/esp/integrity.py +++ b/pcapkit/const/esp/integrity.py @@ -124,9 +124,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Integrity': if default == -1: raise return Integrity(default) - if key not in Integrity._member_map_: # pylint: disable=no-member - return extend_enum(Integrity, key, default) - return Integrity[key] # type: ignore[misc] + try: + return Integrity[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Integrity(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Integrity': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Integrity': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Integrity': diff --git a/pcapkit/const/hip/certificate.py b/pcapkit/const/hip/certificate.py index e1df1f2fb5..c7f09e75e0 100644 --- a/pcapkit/const/hip/certificate.py +++ b/pcapkit/const/hip/certificate.py @@ -64,9 +64,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Certificate': if default == -1: raise return Certificate(default) - if key not in Certificate._member_map_: # pylint: disable=no-member - return extend_enum(Certificate, key, default) - return Certificate[key] # type: ignore[misc] + try: + return Certificate[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Certificate(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Certificate': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Certificate': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Certificate': @@ -80,5 +123,5 @@ def _missing_(cls, value: 'int') -> 'Certificate': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 9 <= value <= 255: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/cipher.py b/pcapkit/const/hip/cipher.py index 018e7a7079..8817e90593 100644 --- a/pcapkit/const/hip/cipher.py +++ b/pcapkit/const/hip/cipher.py @@ -52,9 +52,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Cipher': if default == -1: raise return Cipher(default) - if key not in Cipher._member_map_: # pylint: disable=no-member - return extend_enum(Cipher, key, default) - return Cipher[key] # type: ignore[misc] + try: + return Cipher[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Cipher(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Cipher': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Cipher': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Cipher': @@ -68,5 +111,5 @@ def _missing_(cls, value: 'int') -> 'Cipher': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 5 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/di.py b/pcapkit/const/hip/di.py index 875441e5e1..d4c87f3198 100644 --- a/pcapkit/const/hip/di.py +++ b/pcapkit/const/hip/di.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'DITypes': if default == -1: raise return DITypes(default) - if key not in DITypes._member_map_: # pylint: disable=no-member - return extend_enum(DITypes, key, default) - return DITypes[key] # type: ignore[misc] + try: + return DITypes[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return DITypes(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'DITypes': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'DITypes': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'DITypes': @@ -62,5 +105,5 @@ def _missing_(cls, value: 'int') -> 'DITypes': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 3 <= value <= 15: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/ecdsa_curve.py b/pcapkit/const/hip/ecdsa_curve.py index 90bf93b480..fb248f8acf 100644 --- a/pcapkit/const/hip/ecdsa_curve.py +++ b/pcapkit/const/hip/ecdsa_curve.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ECDSACurve': if default == -1: raise return ECDSACurve(default) - if key not in ECDSACurve._member_map_: # pylint: disable=no-member - return extend_enum(ECDSACurve, key, default) - return ECDSACurve[key] # type: ignore[misc] + try: + return ECDSACurve[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ECDSACurve(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ECDSACurve': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ECDSACurve': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ECDSACurve': @@ -62,5 +105,5 @@ def _missing_(cls, value: 'int') -> 'ECDSACurve': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 3 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/ecdsa_low_curve.py b/pcapkit/const/hip/ecdsa_low_curve.py index 55183d806c..47f90053f0 100644 --- a/pcapkit/const/hip/ecdsa_low_curve.py +++ b/pcapkit/const/hip/ecdsa_low_curve.py @@ -43,9 +43,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ECDSALowCurve': if default == -1: raise return ECDSALowCurve(default) - if key not in ECDSALowCurve._member_map_: # pylint: disable=no-member - return extend_enum(ECDSALowCurve, key, default) - return ECDSALowCurve[key] # type: ignore[misc] + try: + return ECDSALowCurve[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ECDSALowCurve(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ECDSALowCurve': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ECDSALowCurve': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ECDSALowCurve': @@ -59,5 +102,5 @@ def _missing_(cls, value: 'int') -> 'ECDSALowCurve': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 2 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/eddsa_curve.py b/pcapkit/const/hip/eddsa_curve.py index 569af9503a..3dbd2d76a2 100644 --- a/pcapkit/const/hip/eddsa_curve.py +++ b/pcapkit/const/hip/eddsa_curve.py @@ -52,9 +52,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'EdDSACurve': if default == -1: raise return EdDSACurve(default) - if key not in EdDSACurve._member_map_: # pylint: disable=no-member - return extend_enum(EdDSACurve, key, default) - return EdDSACurve[key] # type: ignore[misc] + try: + return EdDSACurve[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return EdDSACurve(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'EdDSACurve': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'EdDSACurve': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'EdDSACurve': diff --git a/pcapkit/const/hip/esp_transform_suite.py b/pcapkit/const/hip/esp_transform_suite.py index 040108b7ff..3bc26e9adf 100644 --- a/pcapkit/const/hip/esp_transform_suite.py +++ b/pcapkit/const/hip/esp_transform_suite.py @@ -85,9 +85,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ESPTransformSuite': if default == -1: raise return ESPTransformSuite(default) - if key not in ESPTransformSuite._member_map_: # pylint: disable=no-member - return extend_enum(ESPTransformSuite, key, default) - return ESPTransformSuite[key] # type: ignore[misc] + try: + return ESPTransformSuite[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ESPTransformSuite(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ESPTransformSuite': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ESPTransformSuite': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ESPTransformSuite': @@ -101,5 +144,5 @@ def _missing_(cls, value: 'int') -> 'ESPTransformSuite': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 16 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/group.py b/pcapkit/const/hip/group.py index e769b044d8..1c7a9d5c3f 100644 --- a/pcapkit/const/hip/group.py +++ b/pcapkit/const/hip/group.py @@ -73,9 +73,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Group': if default == -1: raise return Group(default) - if key not in Group._member_map_: # pylint: disable=no-member - return extend_enum(Group, key, default) - return Group[key] # type: ignore[misc] + try: + return Group[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Group(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Group': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Group': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Group': diff --git a/pcapkit/const/hip/hi_algorithm.py b/pcapkit/const/hip/hi_algorithm.py index dc7f342cbb..822d023abf 100644 --- a/pcapkit/const/hip/hi_algorithm.py +++ b/pcapkit/const/hip/hi_algorithm.py @@ -70,9 +70,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HIAlgorithm': if default == -1: raise return HIAlgorithm(default) - if key not in HIAlgorithm._member_map_: # pylint: disable=no-member - return extend_enum(HIAlgorithm, key, default) - return HIAlgorithm[key] # type: ignore[misc] + try: + return HIAlgorithm[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HIAlgorithm(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HIAlgorithm': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HIAlgorithm': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HIAlgorithm': @@ -86,8 +129,8 @@ def _missing_(cls, value: 'int') -> 'HIAlgorithm': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 10 <= value <= 12: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 14 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/hit_suite.py b/pcapkit/const/hip/hit_suite.py index eac9ca362b..2fff029676 100644 --- a/pcapkit/const/hip/hit_suite.py +++ b/pcapkit/const/hip/hit_suite.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HITSuite': if default == -1: raise return HITSuite(default) - if key not in HITSuite._member_map_: # pylint: disable=no-member - return extend_enum(HITSuite, key, default) - return HITSuite[key] # type: ignore[misc] + try: + return HITSuite[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HITSuite(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HITSuite': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HITSuite': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HITSuite': @@ -71,5 +114,5 @@ def _missing_(cls, value: 'int') -> 'HITSuite': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 6 <= value <= 15: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/nat_traversal.py b/pcapkit/const/hip/nat_traversal.py index 2edb6e7e2c..8f84e9905b 100644 --- a/pcapkit/const/hip/nat_traversal.py +++ b/pcapkit/const/hip/nat_traversal.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'NATTraversal': if default == -1: raise return NATTraversal(default) - if key not in NATTraversal._member_map_: # pylint: disable=no-member - return extend_enum(NATTraversal, key, default) - return NATTraversal[key] # type: ignore[misc] + try: + return NATTraversal[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return NATTraversal(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'NATTraversal': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'NATTraversal': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'NATTraversal': @@ -65,5 +108,5 @@ def _missing_(cls, value: 'int') -> 'NATTraversal': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 4 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/notify_message.py b/pcapkit/const/hip/notify_message.py index 884cafaf57..bae8602132 100644 --- a/pcapkit/const/hip/notify_message.py +++ b/pcapkit/const/hip/notify_message.py @@ -151,9 +151,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'NotifyMessage': if default == -1: raise return NotifyMessage(default) - if key not in NotifyMessage._member_map_: # pylint: disable=no-member - return extend_enum(NotifyMessage, key, default) - return NotifyMessage[key] # type: ignore[misc] + try: + return NotifyMessage[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return NotifyMessage(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'NotifyMessage': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'NotifyMessage': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'NotifyMessage': @@ -167,41 +210,41 @@ def _missing_(cls, value: 'int') -> 'NotifyMessage': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 2 <= value <= 6: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 8 <= value <= 13: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 21 <= value <= 23: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 29 <= value <= 31: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 33 <= value <= 39: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 52 <= value <= 59: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 65 <= value <= 69: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 71 <= value <= 89: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 91 <= value <= 99: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 101 <= value <= 8191: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 8192 <= value <= 16383: #: Reserved for Private Use [:rfc:`7401`] - return extend_enum(cls, 'Reserved_for_Private_Use_%d' % value, value) + return cls._unregistered_member(value, 'Reserved_for_Private_Use') if 16386 <= value <= 40959: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 40960 <= value <= 65535: #: Reserved for Private Use [:rfc:`7401`] - return extend_enum(cls, 'Reserved_for_Private_Use_%d' % value, value) + return cls._unregistered_member(value, 'Reserved_for_Private_Use') return super()._missing_(value) diff --git a/pcapkit/const/hip/packet.py b/pcapkit/const/hip/packet.py index 518a72dd11..b0a544cb33 100644 --- a/pcapkit/const/hip/packet.py +++ b/pcapkit/const/hip/packet.py @@ -70,9 +70,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Packet': if default == -1: raise return Packet(default) - if key not in Packet._member_map_: # pylint: disable=no-member - return extend_enum(Packet, key, default) - return Packet[key] # type: ignore[misc] + try: + return Packet[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Packet(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Packet': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Packet': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Packet': diff --git a/pcapkit/const/hip/parameter.py b/pcapkit/const/hip/parameter.py index 0e52d098c8..96f966ef23 100644 --- a/pcapkit/const/hip/parameter.py +++ b/pcapkit/const/hip/parameter.py @@ -223,9 +223,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Parameter': if default == -1: raise return Parameter(default) - if key not in Parameter._member_map_: # pylint: disable=no-member - return extend_enum(Parameter, key, default) - return Parameter[key] # type: ignore[misc] + try: + return Parameter[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Parameter(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Parameter': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Parameter': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Parameter': diff --git a/pcapkit/const/hip/registration.py b/pcapkit/const/hip/registration.py index 0f6246b14a..b6a17cfa35 100644 --- a/pcapkit/const/hip/registration.py +++ b/pcapkit/const/hip/registration.py @@ -52,9 +52,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Registration': if default == -1: raise return Registration(default) - if key not in Registration._member_map_: # pylint: disable=no-member - return extend_enum(Registration, key, default) - return Registration[key] # type: ignore[misc] + try: + return Registration[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Registration(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Registration': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Registration': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Registration': @@ -68,8 +111,8 @@ def _missing_(cls, value: 'int') -> 'Registration': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 5 <= value <= 200: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 201 <= value <= 255: #: Reserved for Private Use [:rfc:`8003`] - return extend_enum(cls, 'Reserved_for_Private_Use_%d' % value, value) + return cls._unregistered_member(value, 'Reserved_for_Private_Use') return super()._missing_(value) diff --git a/pcapkit/const/hip/registration_failure.py b/pcapkit/const/hip/registration_failure.py index cc3c8058e6..dd0b402955 100644 --- a/pcapkit/const/hip/registration_failure.py +++ b/pcapkit/const/hip/registration_failure.py @@ -68,9 +68,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RegistrationFailure': if default == -1: raise return RegistrationFailure(default) - if key not in RegistrationFailure._member_map_: # pylint: disable=no-member - return extend_enum(RegistrationFailure, key, default) - return RegistrationFailure[key] # type: ignore[misc] + try: + return RegistrationFailure[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RegistrationFailure(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RegistrationFailure': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RegistrationFailure': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RegistrationFailure': @@ -84,8 +127,8 @@ def _missing_(cls, value: 'int') -> 'RegistrationFailure': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 10 <= value <= 200: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 201 <= value <= 255: #: Reserved for Private Use [:rfc:`8003`] - return extend_enum(cls, 'Reserved_for_Private_Use_%d' % value, value) + return cls._unregistered_member(value, 'Reserved_for_Private_Use') return super()._missing_(value) diff --git a/pcapkit/const/hip/suite.py b/pcapkit/const/hip/suite.py index 1cd457f0c8..d1a3c0711b 100644 --- a/pcapkit/const/hip/suite.py +++ b/pcapkit/const/hip/suite.py @@ -58,9 +58,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Suite': if default == -1: raise return Suite(default) - if key not in Suite._member_map_: # pylint: disable=no-member - return extend_enum(Suite, key, default) - return Suite[key] # type: ignore[misc] + try: + return Suite[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Suite(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Suite': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Suite': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Suite': @@ -74,5 +117,5 @@ def _missing_(cls, value: 'int') -> 'Suite': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 7 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/hip/transport.py b/pcapkit/const/hip/transport.py index 751bf24e27..4465d235f6 100644 --- a/pcapkit/const/hip/transport.py +++ b/pcapkit/const/hip/transport.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Transport': if default == -1: raise return Transport(default) - if key not in Transport._member_map_: # pylint: disable=no-member - return extend_enum(Transport, key, default) - return Transport[key] # type: ignore[misc] + try: + return Transport[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Transport(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Transport': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Transport': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Transport': diff --git a/pcapkit/const/http/error_code.py b/pcapkit/const/http/error_code.py index c05fd19e51..fcce61c00e 100644 --- a/pcapkit/const/http/error_code.py +++ b/pcapkit/const/http/error_code.py @@ -81,9 +81,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ErrorCode': if default == -1: raise return ErrorCode(default) - if key not in ErrorCode._member_map_: # pylint: disable=no-member - return extend_enum(ErrorCode, key, default) - return ErrorCode[key] # type: ignore[misc] + try: + return ErrorCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ErrorCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ErrorCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ErrorCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ErrorCode': diff --git a/pcapkit/const/http/frame.py b/pcapkit/const/http/frame.py index 73453d1d58..1b269de63c 100644 --- a/pcapkit/const/http/frame.py +++ b/pcapkit/const/http/frame.py @@ -79,9 +79,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Frame': if default == -1: raise return Frame(default) - if key not in Frame._member_map_: # pylint: disable=no-member - return extend_enum(Frame, key, default) - return Frame[key] # type: ignore[misc] + try: + return Frame[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Frame(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Frame': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Frame': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Frame': diff --git a/pcapkit/const/http/setting.py b/pcapkit/const/http/setting.py index 96344fe936..08a755f088 100644 --- a/pcapkit/const/http/setting.py +++ b/pcapkit/const/http/setting.py @@ -77,9 +77,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Setting': if default == -1: raise return Setting(default) - if key not in Setting._member_map_: # pylint: disable=no-member - return extend_enum(Setting, key, default) - return Setting[key] # type: ignore[misc] + try: + return Setting[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Setting(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Setting': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Setting': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Setting': diff --git a/pcapkit/const/ipv4/classification_level.py b/pcapkit/const/ipv4/classification_level.py index 07965a36cb..2603b3374c 100644 --- a/pcapkit/const/ipv4/classification_level.py +++ b/pcapkit/const/ipv4/classification_level.py @@ -53,9 +53,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ClassificationLevel': if default == -1: raise return ClassificationLevel(default) - if key not in ClassificationLevel._member_map_: # pylint: disable=no-member - return extend_enum(ClassificationLevel, key, default) - return ClassificationLevel[key] # type: ignore[misc] + try: + return ClassificationLevel[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ClassificationLevel(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ClassificationLevel': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ClassificationLevel': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ClassificationLevel': diff --git a/pcapkit/const/ipv4/option_class.py b/pcapkit/const/ipv4/option_class.py index 02be2b06c4..cef626cb31 100644 --- a/pcapkit/const/ipv4/option_class.py +++ b/pcapkit/const/ipv4/option_class.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'OptionClass': if default == -1: raise return OptionClass(default) - if key not in OptionClass._member_map_: # pylint: disable=no-member - return extend_enum(OptionClass, key, default) - return OptionClass[key] # type: ignore[misc] + try: + return OptionClass[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return OptionClass(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'OptionClass': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'OptionClass': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'OptionClass': diff --git a/pcapkit/const/ipv4/option_number.py b/pcapkit/const/ipv4/option_number.py index f5130a161f..8f03a90266 100644 --- a/pcapkit/const/ipv4/option_number.py +++ b/pcapkit/const/ipv4/option_number.py @@ -127,9 +127,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'OptionNumber': if default == -1: raise return OptionNumber(default) - if key not in OptionNumber._member_map_: # pylint: disable=no-member - return extend_enum(OptionNumber, key, default) - return OptionNumber[key] # type: ignore[misc] + try: + return OptionNumber[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return OptionNumber(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'OptionNumber': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'OptionNumber': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'OptionNumber': diff --git a/pcapkit/const/ipv4/protection_authority.py b/pcapkit/const/ipv4/protection_authority.py index 31bf47da3e..7593af5b8d 100644 --- a/pcapkit/const/ipv4/protection_authority.py +++ b/pcapkit/const/ipv4/protection_authority.py @@ -53,9 +53,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ProtectionAuthority': if default == -1: raise return ProtectionAuthority(default) - if key not in ProtectionAuthority._member_map_: # pylint: disable=no-member - return extend_enum(ProtectionAuthority, key, default) - return ProtectionAuthority[key] # type: ignore[misc] + try: + return ProtectionAuthority[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ProtectionAuthority(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ProtectionAuthority': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ProtectionAuthority': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ProtectionAuthority': diff --git a/pcapkit/const/ipv4/qs_function.py b/pcapkit/const/ipv4/qs_function.py index 66ddbc4cc4..8f5fcd8587 100644 --- a/pcapkit/const/ipv4/qs_function.py +++ b/pcapkit/const/ipv4/qs_function.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'QSFunction': if default == -1: raise return QSFunction(default) - if key not in QSFunction._member_map_: # pylint: disable=no-member - return extend_enum(QSFunction, key, default) - return QSFunction[key] # type: ignore[misc] + try: + return QSFunction[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return QSFunction(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'QSFunction': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'QSFunction': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'QSFunction': diff --git a/pcapkit/const/ipv4/router_alert.py b/pcapkit/const/ipv4/router_alert.py index 78af61a5f4..551d2145e3 100644 --- a/pcapkit/const/ipv4/router_alert.py +++ b/pcapkit/const/ipv4/router_alert.py @@ -238,9 +238,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RouterAlert': if default == -1: raise return RouterAlert(default) - if key not in RouterAlert._member_map_: # pylint: disable=no-member - return extend_enum(RouterAlert, key, default) - return RouterAlert[key] # type: ignore[misc] + try: + return RouterAlert[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RouterAlert(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RouterAlert': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RouterAlert': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RouterAlert': diff --git a/pcapkit/const/ipv4/tos_del.py b/pcapkit/const/ipv4/tos_del.py index e2ad4ed47d..e252286484 100644 --- a/pcapkit/const/ipv4/tos_del.py +++ b/pcapkit/const/ipv4/tos_del.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ToSDelay': if default == -1: raise return ToSDelay(default) - if key not in ToSDelay._member_map_: # pylint: disable=no-member - return extend_enum(ToSDelay, key, default) - return ToSDelay[key] # type: ignore[misc] + try: + return ToSDelay[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ToSDelay(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ToSDelay': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ToSDelay': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ToSDelay': diff --git a/pcapkit/const/ipv4/tos_ecn.py b/pcapkit/const/ipv4/tos_ecn.py index 78ad77181c..aeaaad5655 100644 --- a/pcapkit/const/ipv4/tos_ecn.py +++ b/pcapkit/const/ipv4/tos_ecn.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ToSECN': if default == -1: raise return ToSECN(default) - if key not in ToSECN._member_map_: # pylint: disable=no-member - return extend_enum(ToSECN, key, default) - return ToSECN[key] # type: ignore[misc] + try: + return ToSECN[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ToSECN(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ToSECN': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ToSECN': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ToSECN': diff --git a/pcapkit/const/ipv4/tos_pre.py b/pcapkit/const/ipv4/tos_pre.py index dc60da648c..6fc7f1762a 100644 --- a/pcapkit/const/ipv4/tos_pre.py +++ b/pcapkit/const/ipv4/tos_pre.py @@ -53,9 +53,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ToSPrecedence': if default == -1: raise return ToSPrecedence(default) - if key not in ToSPrecedence._member_map_: # pylint: disable=no-member - return extend_enum(ToSPrecedence, key, default) - return ToSPrecedence[key] # type: ignore[misc] + try: + return ToSPrecedence[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ToSPrecedence(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ToSPrecedence': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ToSPrecedence': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ToSPrecedence': diff --git a/pcapkit/const/ipv4/tos_rel.py b/pcapkit/const/ipv4/tos_rel.py index 2340f14756..1b69affb18 100644 --- a/pcapkit/const/ipv4/tos_rel.py +++ b/pcapkit/const/ipv4/tos_rel.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ToSReliability': if default == -1: raise return ToSReliability(default) - if key not in ToSReliability._member_map_: # pylint: disable=no-member - return extend_enum(ToSReliability, key, default) - return ToSReliability[key] # type: ignore[misc] + try: + return ToSReliability[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ToSReliability(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ToSReliability': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ToSReliability': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ToSReliability': diff --git a/pcapkit/const/ipv4/tos_thr.py b/pcapkit/const/ipv4/tos_thr.py index 127d3e8b68..15ba5ada34 100644 --- a/pcapkit/const/ipv4/tos_thr.py +++ b/pcapkit/const/ipv4/tos_thr.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ToSThroughput': if default == -1: raise return ToSThroughput(default) - if key not in ToSThroughput._member_map_: # pylint: disable=no-member - return extend_enum(ToSThroughput, key, default) - return ToSThroughput[key] # type: ignore[misc] + try: + return ToSThroughput[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ToSThroughput(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ToSThroughput': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ToSThroughput': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ToSThroughput': diff --git a/pcapkit/const/ipv4/ts_flag.py b/pcapkit/const/ipv4/ts_flag.py index 2fe1a2a101..ff6c3a013d 100644 --- a/pcapkit/const/ipv4/ts_flag.py +++ b/pcapkit/const/ipv4/ts_flag.py @@ -43,9 +43,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'TSFlag': if default == -1: raise return TSFlag(default) - if key not in TSFlag._member_map_: # pylint: disable=no-member - return extend_enum(TSFlag, key, default) - return TSFlag[key] # type: ignore[misc] + try: + return TSFlag[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return TSFlag(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'TSFlag': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'TSFlag': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'TSFlag': diff --git a/pcapkit/const/ipv6/option.py b/pcapkit/const/ipv6/option.py index 839533166c..d58d1b8a6e 100644 --- a/pcapkit/const/ipv6/option.py +++ b/pcapkit/const/ipv6/option.py @@ -127,9 +127,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Option': if default == -1: raise return Option(default) - if key not in Option._member_map_: # pylint: disable=no-member - return extend_enum(Option, key, default) - return Option[key] # type: ignore[misc] + try: + return Option[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Option(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Option': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Option': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Option': diff --git a/pcapkit/const/ipv6/option_action.py b/pcapkit/const/ipv6/option_action.py index 13a169111c..14c798a56a 100644 --- a/pcapkit/const/ipv6/option_action.py +++ b/pcapkit/const/ipv6/option_action.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'OptionAction': if default == -1: raise return OptionAction(default) - if key not in OptionAction._member_map_: # pylint: disable=no-member - return extend_enum(OptionAction, key, default) - return OptionAction[key] # type: ignore[misc] + try: + return OptionAction[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return OptionAction(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'OptionAction': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'OptionAction': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'OptionAction': diff --git a/pcapkit/const/ipv6/qs_function.py b/pcapkit/const/ipv6/qs_function.py index faa1057e13..10e05beb87 100644 --- a/pcapkit/const/ipv6/qs_function.py +++ b/pcapkit/const/ipv6/qs_function.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'QSFunction': if default == -1: raise return QSFunction(default) - if key not in QSFunction._member_map_: # pylint: disable=no-member - return extend_enum(QSFunction, key, default) - return QSFunction[key] # type: ignore[misc] + try: + return QSFunction[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return QSFunction(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'QSFunction': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'QSFunction': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'QSFunction': diff --git a/pcapkit/const/ipv6/router_alert.py b/pcapkit/const/ipv6/router_alert.py index cbbfa0403e..0881ed5f4e 100644 --- a/pcapkit/const/ipv6/router_alert.py +++ b/pcapkit/const/ipv6/router_alert.py @@ -250,9 +250,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RouterAlert': if default == -1: raise return RouterAlert(default) - if key not in RouterAlert._member_map_: # pylint: disable=no-member - return extend_enum(RouterAlert, key, default) - return RouterAlert[key] # type: ignore[misc] + try: + return RouterAlert[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RouterAlert(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RouterAlert': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RouterAlert': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RouterAlert': diff --git a/pcapkit/const/ipv6/routing.py b/pcapkit/const/ipv6/routing.py index 8b25e740bc..a292c1fe48 100644 --- a/pcapkit/const/ipv6/routing.py +++ b/pcapkit/const/ipv6/routing.py @@ -67,9 +67,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Routing': if default == -1: raise return Routing(default) - if key not in Routing._member_map_: # pylint: disable=no-member - return extend_enum(Routing, key, default) - return Routing[key] # type: ignore[misc] + try: + return Routing[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Routing(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Routing': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Routing': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Routing': diff --git a/pcapkit/const/ipv6/seed_id.py b/pcapkit/const/ipv6/seed_id.py index 678d620380..4649ff93c9 100644 --- a/pcapkit/const/ipv6/seed_id.py +++ b/pcapkit/const/ipv6/seed_id.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'SeedID': if default == -1: raise return SeedID(default) - if key not in SeedID._member_map_: # pylint: disable=no-member - return extend_enum(SeedID, key, default) - return SeedID[key] # type: ignore[misc] + try: + return SeedID[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return SeedID(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'SeedID': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'SeedID': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'SeedID': diff --git a/pcapkit/const/ipv6/smf_dpd_mode.py b/pcapkit/const/ipv6/smf_dpd_mode.py index cfc7544151..f5f525e59f 100644 --- a/pcapkit/const/ipv6/smf_dpd_mode.py +++ b/pcapkit/const/ipv6/smf_dpd_mode.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'SMFDPDMode': if default == -1: raise return SMFDPDMode(default) - if key not in SMFDPDMode._member_map_: # pylint: disable=no-member - return extend_enum(SMFDPDMode, key, default) - return SMFDPDMode[key] # type: ignore[misc] + try: + return SMFDPDMode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return SMFDPDMode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'SMFDPDMode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'SMFDPDMode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'SMFDPDMode': diff --git a/pcapkit/const/ipv6/tagger_id.py b/pcapkit/const/ipv6/tagger_id.py index 3ab9033bb5..dd227c5fe0 100644 --- a/pcapkit/const/ipv6/tagger_id.py +++ b/pcapkit/const/ipv6/tagger_id.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'TaggerID': if default == -1: raise return TaggerID(default) - if key not in TaggerID._member_map_: # pylint: disable=no-member - return extend_enum(TaggerID, key, default) - return TaggerID[key] # type: ignore[misc] + try: + return TaggerID[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return TaggerID(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'TaggerID': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'TaggerID': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'TaggerID': diff --git a/pcapkit/const/ipx/packet.py b/pcapkit/const/ipx/packet.py index 3241f033a1..c8a7c9b81a 100644 --- a/pcapkit/const/ipx/packet.py +++ b/pcapkit/const/ipx/packet.py @@ -62,9 +62,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Packet': if default == -1: raise return Packet(default) - if key not in Packet._member_map_: # pylint: disable=no-member - return extend_enum(Packet, key, default) - return Packet[key] # type: ignore[misc] + try: + return Packet[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Packet(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Packet': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Packet': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Packet': diff --git a/pcapkit/const/ipx/socket.py b/pcapkit/const/ipx/socket.py index 0f9c4a0b42..2e5b077bd2 100644 --- a/pcapkit/const/ipx/socket.py +++ b/pcapkit/const/ipx/socket.py @@ -82,9 +82,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Socket': if default == -1: raise return Socket(default) - if key not in Socket._member_map_: # pylint: disable=no-member - return extend_enum(Socket, key, default) - return Socket[key] # type: ignore[misc] + try: + return Socket[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Socket(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Socket': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Socket': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Socket': diff --git a/pcapkit/const/l2tp/type.py b/pcapkit/const/l2tp/type.py index 9711a69729..4448bcfb8b 100644 --- a/pcapkit/const/l2tp/type.py +++ b/pcapkit/const/l2tp/type.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Type': if default == -1: raise return Type(default) - if key not in Type._member_map_: # pylint: disable=no-member - return extend_enum(Type, key, default) - return Type[key] # type: ignore[misc] + try: + return Type[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Type(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Type': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Type': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Type': diff --git a/pcapkit/const/mh/access_type.py b/pcapkit/const/mh/access_type.py index 2a5eb279aa..b213bd4d96 100644 --- a/pcapkit/const/mh/access_type.py +++ b/pcapkit/const/mh/access_type.py @@ -79,9 +79,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'AccessType': if default == -1: raise return AccessType(default) - if key not in AccessType._member_map_: # pylint: disable=no-member - return extend_enum(AccessType, key, default) - return AccessType[key] # type: ignore[misc] + try: + return AccessType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return AccessType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'AccessType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'AccessType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'AccessType': diff --git a/pcapkit/const/mh/ack_status_code.py b/pcapkit/const/mh/ack_status_code.py index bbacbbfb43..53b39454e8 100644 --- a/pcapkit/const/mh/ack_status_code.py +++ b/pcapkit/const/mh/ack_status_code.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ACKStatusCode': if default == -1: raise return ACKStatusCode(default) - if key not in ACKStatusCode._member_map_: # pylint: disable=no-member - return extend_enum(ACKStatusCode, key, default) - return ACKStatusCode[key] # type: ignore[misc] + try: + return ACKStatusCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ACKStatusCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ACKStatusCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ACKStatusCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ACKStatusCode': diff --git a/pcapkit/const/mh/ani_suboption.py b/pcapkit/const/mh/ani_suboption.py index 56c3f2aba1..94616d684c 100644 --- a/pcapkit/const/mh/ani_suboption.py +++ b/pcapkit/const/mh/ani_suboption.py @@ -61,9 +61,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'ANISuboption': if default == -1: raise return ANISuboption(default) - if key not in ANISuboption._member_map_: # pylint: disable=no-member - return extend_enum(ANISuboption, key, default) - return ANISuboption[key] # type: ignore[misc] + try: + return ANISuboption[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return ANISuboption(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'ANISuboption': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'ANISuboption': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'ANISuboption': diff --git a/pcapkit/const/mh/auth_subtype.py b/pcapkit/const/mh/auth_subtype.py index 139fc72e3b..c7b55859cd 100644 --- a/pcapkit/const/mh/auth_subtype.py +++ b/pcapkit/const/mh/auth_subtype.py @@ -43,9 +43,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'AuthSubtype': if default == -1: raise return AuthSubtype(default) - if key not in AuthSubtype._member_map_: # pylint: disable=no-member - return extend_enum(AuthSubtype, key, default) - return AuthSubtype[key] # type: ignore[misc] + try: + return AuthSubtype[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return AuthSubtype(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'AuthSubtype': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'AuthSubtype': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'AuthSubtype': diff --git a/pcapkit/const/mh/binding_error.py b/pcapkit/const/mh/binding_error.py index 62d3c18b7e..e97daa391a 100644 --- a/pcapkit/const/mh/binding_error.py +++ b/pcapkit/const/mh/binding_error.py @@ -41,9 +41,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'BindingError': if default == -1: raise return BindingError(default) - if key not in BindingError._member_map_: # pylint: disable=no-member - return extend_enum(BindingError, key, default) - return BindingError[key] # type: ignore[misc] + try: + return BindingError[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return BindingError(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'BindingError': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'BindingError': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'BindingError': diff --git a/pcapkit/const/mh/binding_revocation.py b/pcapkit/const/mh/binding_revocation.py index aebe3daf86..a43f47c75c 100644 --- a/pcapkit/const/mh/binding_revocation.py +++ b/pcapkit/const/mh/binding_revocation.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'BindingRevocation': if default == -1: raise return BindingRevocation(default) - if key not in BindingRevocation._member_map_: # pylint: disable=no-member - return extend_enum(BindingRevocation, key, default) - return BindingRevocation[key] # type: ignore[misc] + try: + return BindingRevocation[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return BindingRevocation(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'BindingRevocation': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'BindingRevocation': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'BindingRevocation': diff --git a/pcapkit/const/mh/cga_extension.py b/pcapkit/const/mh/cga_extension.py index 3c65a86ddd..89d8ad80ae 100644 --- a/pcapkit/const/mh/cga_extension.py +++ b/pcapkit/const/mh/cga_extension.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'CGAExtension': if default == -1: raise return CGAExtension(default) - if key not in CGAExtension._member_map_: # pylint: disable=no-member - return extend_enum(CGAExtension, key, default) - return CGAExtension[key] # type: ignore[misc] + try: + return CGAExtension[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return CGAExtension(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'CGAExtension': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'CGAExtension': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'CGAExtension': diff --git a/pcapkit/const/mh/cga_sec.py b/pcapkit/const/mh/cga_sec.py index 49da223ede..f30bb933d0 100644 --- a/pcapkit/const/mh/cga_sec.py +++ b/pcapkit/const/mh/cga_sec.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'CGASec': if default == -1: raise return CGASec(default) - if key not in CGASec._member_map_: # pylint: disable=no-member - return extend_enum(CGASec, key, default) - return CGASec[key] # type: ignore[misc] + try: + return CGASec[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return CGASec(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'CGASec': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'CGASec': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'CGASec': diff --git a/pcapkit/const/mh/cga_type.py b/pcapkit/const/mh/cga_type.py index 325d3ecbef..144beedce4 100644 --- a/pcapkit/const/mh/cga_type.py +++ b/pcapkit/const/mh/cga_type.py @@ -58,9 +58,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'CGAType': if default == -1: raise return CGAType(default) - if key not in CGAType._member_map_: # pylint: disable=no-member - return extend_enum(CGAType, key, default) - return CGAType[key] # type: ignore[misc] + try: + return CGAType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return CGAType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'CGAType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'CGAType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'CGAType': diff --git a/pcapkit/const/mh/dhcp_support_mode.py b/pcapkit/const/mh/dhcp_support_mode.py index 25830e90e4..b93f736251 100644 --- a/pcapkit/const/mh/dhcp_support_mode.py +++ b/pcapkit/const/mh/dhcp_support_mode.py @@ -43,9 +43,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'DHCPSupportMode': if default == -1: raise return DHCPSupportMode(default) - if key not in DHCPSupportMode._member_map_: # pylint: disable=no-member - return extend_enum(DHCPSupportMode, key, default) - return DHCPSupportMode[key] # type: ignore[misc] + try: + return DHCPSupportMode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return DHCPSupportMode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'DHCPSupportMode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'DHCPSupportMode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'DHCPSupportMode': diff --git a/pcapkit/const/mh/dns_status_code.py b/pcapkit/const/mh/dns_status_code.py index 3fa81aaa7d..07bf21901f 100644 --- a/pcapkit/const/mh/dns_status_code.py +++ b/pcapkit/const/mh/dns_status_code.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'DNSStatusCode': if default == -1: raise return DNSStatusCode(default) - if key not in DNSStatusCode._member_map_: # pylint: disable=no-member - return extend_enum(DNSStatusCode, key, default) - return DNSStatusCode[key] # type: ignore[misc] + try: + return DNSStatusCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return DNSStatusCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'DNSStatusCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'DNSStatusCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'DNSStatusCode': diff --git a/pcapkit/const/mh/dsmip6_tls_packet.py b/pcapkit/const/mh/dsmip6_tls_packet.py index ebd18058fd..52217f58b1 100644 --- a/pcapkit/const/mh/dsmip6_tls_packet.py +++ b/pcapkit/const/mh/dsmip6_tls_packet.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'DSMIP6TLSPacket': if default == -1: raise return DSMIP6TLSPacket(default) - if key not in DSMIP6TLSPacket._member_map_: # pylint: disable=no-member - return extend_enum(DSMIP6TLSPacket, key, default) - return DSMIP6TLSPacket[key] # type: ignore[misc] + try: + return DSMIP6TLSPacket[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return DSMIP6TLSPacket(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'DSMIP6TLSPacket': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'DSMIP6TLSPacket': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'DSMIP6TLSPacket': diff --git a/pcapkit/const/mh/dsmipv6_home_address.py b/pcapkit/const/mh/dsmipv6_home_address.py index 3ed901c058..1446a529c8 100644 --- a/pcapkit/const/mh/dsmipv6_home_address.py +++ b/pcapkit/const/mh/dsmipv6_home_address.py @@ -58,9 +58,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'DSMIPv6HomeAddress': if default == -1: raise return DSMIPv6HomeAddress(default) - if key not in DSMIPv6HomeAddress._member_map_: # pylint: disable=no-member - return extend_enum(DSMIPv6HomeAddress, key, default) - return DSMIPv6HomeAddress[key] # type: ignore[misc] + try: + return DSMIPv6HomeAddress[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return DSMIPv6HomeAddress(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'DSMIPv6HomeAddress': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'DSMIPv6HomeAddress': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'DSMIPv6HomeAddress': diff --git a/pcapkit/const/mh/enumerating_algorithm.py b/pcapkit/const/mh/enumerating_algorithm.py index a5996ad85b..34aa8a32c7 100644 --- a/pcapkit/const/mh/enumerating_algorithm.py +++ b/pcapkit/const/mh/enumerating_algorithm.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'EnumeratingAlgorithm': if default == -1: raise return EnumeratingAlgorithm(default) - if key not in EnumeratingAlgorithm._member_map_: # pylint: disable=no-member - return extend_enum(EnumeratingAlgorithm, key, default) - return EnumeratingAlgorithm[key] # type: ignore[misc] + try: + return EnumeratingAlgorithm[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return EnumeratingAlgorithm(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'EnumeratingAlgorithm': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'EnumeratingAlgorithm': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'EnumeratingAlgorithm': diff --git a/pcapkit/const/mh/fb_ack_status.py b/pcapkit/const/mh/fb_ack_status.py index 98d3d04df3..478fa71bad 100644 --- a/pcapkit/const/mh/fb_ack_status.py +++ b/pcapkit/const/mh/fb_ack_status.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowBindingACKStatus': if default == -1: raise return FlowBindingACKStatus(default) - if key not in FlowBindingACKStatus._member_map_: # pylint: disable=no-member - return extend_enum(FlowBindingACKStatus, key, default) - return FlowBindingACKStatus[key] # type: ignore[misc] + try: + return FlowBindingACKStatus[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowBindingACKStatus(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowBindingACKStatus': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowBindingACKStatus': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowBindingACKStatus': diff --git a/pcapkit/const/mh/fb_action.py b/pcapkit/const/mh/fb_action.py index c2776be2f4..a1d0d32bf6 100644 --- a/pcapkit/const/mh/fb_action.py +++ b/pcapkit/const/mh/fb_action.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowBindingAction': if default == -1: raise return FlowBindingAction(default) - if key not in FlowBindingAction._member_map_: # pylint: disable=no-member - return extend_enum(FlowBindingAction, key, default) - return FlowBindingAction[key] # type: ignore[misc] + try: + return FlowBindingAction[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowBindingAction(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowBindingAction': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowBindingAction': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowBindingAction': diff --git a/pcapkit/const/mh/fb_indication_trigger.py b/pcapkit/const/mh/fb_indication_trigger.py index 6472ea6c1c..d44f585283 100644 --- a/pcapkit/const/mh/fb_indication_trigger.py +++ b/pcapkit/const/mh/fb_indication_trigger.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowBindingIndicationTrigger' if default == -1: raise return FlowBindingIndicationTrigger(default) - if key not in FlowBindingIndicationTrigger._member_map_: # pylint: disable=no-member - return extend_enum(FlowBindingIndicationTrigger, key, default) - return FlowBindingIndicationTrigger[key] # type: ignore[misc] + try: + return FlowBindingIndicationTrigger[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowBindingIndicationTrigger(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowBindingIndicationTrigger': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowBindingIndicationTrigger': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowBindingIndicationTrigger': diff --git a/pcapkit/const/mh/fb_type.py b/pcapkit/const/mh/fb_type.py index d21b937e19..9c688377ad 100644 --- a/pcapkit/const/mh/fb_type.py +++ b/pcapkit/const/mh/fb_type.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowBindingType': if default == -1: raise return FlowBindingType(default) - if key not in FlowBindingType._member_map_: # pylint: disable=no-member - return extend_enum(FlowBindingType, key, default) - return FlowBindingType[key] # type: ignore[misc] + try: + return FlowBindingType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowBindingType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowBindingType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowBindingType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowBindingType': diff --git a/pcapkit/const/mh/flow_id_status.py b/pcapkit/const/mh/flow_id_status.py index 38f4999f87..492e812af4 100644 --- a/pcapkit/const/mh/flow_id_status.py +++ b/pcapkit/const/mh/flow_id_status.py @@ -58,9 +58,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowIDStatus': if default == -1: raise return FlowIDStatus(default) - if key not in FlowIDStatus._member_map_: # pylint: disable=no-member - return extend_enum(FlowIDStatus, key, default) - return FlowIDStatus[key] # type: ignore[misc] + try: + return FlowIDStatus[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowIDStatus(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowIDStatus': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowIDStatus': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowIDStatus': diff --git a/pcapkit/const/mh/flow_id_suboption.py b/pcapkit/const/mh/flow_id_suboption.py index c9298677d0..54b3cc975e 100644 --- a/pcapkit/const/mh/flow_id_suboption.py +++ b/pcapkit/const/mh/flow_id_suboption.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FlowIDSuboption': if default == -1: raise return FlowIDSuboption(default) - if key not in FlowIDSuboption._member_map_: # pylint: disable=no-member - return extend_enum(FlowIDSuboption, key, default) - return FlowIDSuboption[key] # type: ignore[misc] + try: + return FlowIDSuboption[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FlowIDSuboption(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FlowIDSuboption': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FlowIDSuboption': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FlowIDSuboption': diff --git a/pcapkit/const/mh/handoff_type.py b/pcapkit/const/mh/handoff_type.py index 32345c073e..58467ce06c 100644 --- a/pcapkit/const/mh/handoff_type.py +++ b/pcapkit/const/mh/handoff_type.py @@ -58,9 +58,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HandoffType': if default == -1: raise return HandoffType(default) - if key not in HandoffType._member_map_: # pylint: disable=no-member - return extend_enum(HandoffType, key, default) - return HandoffType[key] # type: ignore[misc] + try: + return HandoffType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HandoffType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HandoffType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HandoffType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HandoffType': diff --git a/pcapkit/const/mh/handover_ack_status.py b/pcapkit/const/mh/handover_ack_status.py index 5540e8043b..db852148b8 100644 --- a/pcapkit/const/mh/handover_ack_status.py +++ b/pcapkit/const/mh/handover_ack_status.py @@ -76,9 +76,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HandoverACKStatus': if default == -1: raise return HandoverACKStatus(default) - if key not in HandoverACKStatus._member_map_: # pylint: disable=no-member - return extend_enum(HandoverACKStatus, key, default) - return HandoverACKStatus[key] # type: ignore[misc] + try: + return HandoverACKStatus[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HandoverACKStatus(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HandoverACKStatus': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HandoverACKStatus': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HandoverACKStatus': diff --git a/pcapkit/const/mh/handover_initiate_status.py b/pcapkit/const/mh/handover_initiate_status.py index fc08579fd3..49e7ffb68b 100644 --- a/pcapkit/const/mh/handover_initiate_status.py +++ b/pcapkit/const/mh/handover_initiate_status.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HandoverInitiateStatus': if default == -1: raise return HandoverInitiateStatus(default) - if key not in HandoverInitiateStatus._member_map_: # pylint: disable=no-member - return extend_enum(HandoverInitiateStatus, key, default) - return HandoverInitiateStatus[key] # type: ignore[misc] + try: + return HandoverInitiateStatus[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HandoverInitiateStatus(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HandoverInitiateStatus': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HandoverInitiateStatus': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HandoverInitiateStatus': diff --git a/pcapkit/const/mh/home_address_reply.py b/pcapkit/const/mh/home_address_reply.py index c21b6a29d7..90df233259 100644 --- a/pcapkit/const/mh/home_address_reply.py +++ b/pcapkit/const/mh/home_address_reply.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HomeAddressReply': if default == -1: raise return HomeAddressReply(default) - if key not in HomeAddressReply._member_map_: # pylint: disable=no-member - return extend_enum(HomeAddressReply, key, default) - return HomeAddressReply[key] # type: ignore[misc] + try: + return HomeAddressReply[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HomeAddressReply(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HomeAddressReply': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HomeAddressReply': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HomeAddressReply': diff --git a/pcapkit/const/mh/lla_code.py b/pcapkit/const/mh/lla_code.py index 1b74b8b366..b6b938c3e2 100644 --- a/pcapkit/const/mh/lla_code.py +++ b/pcapkit/const/mh/lla_code.py @@ -53,9 +53,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'LLACode': if default == -1: raise return LLACode(default) - if key not in LLACode._member_map_: # pylint: disable=no-member - return extend_enum(LLACode, key, default) - return LLACode[key] # type: ignore[misc] + try: + return LLACode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return LLACode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'LLACode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'LLACode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'LLACode': diff --git a/pcapkit/const/mh/lma_mag_suboption.py b/pcapkit/const/mh/lma_mag_suboption.py index 45c0c46ab3..c21825f342 100644 --- a/pcapkit/const/mh/lma_mag_suboption.py +++ b/pcapkit/const/mh/lma_mag_suboption.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'LMAControlledMAGSuboption': if default == -1: raise return LMAControlledMAGSuboption(default) - if key not in LMAControlledMAGSuboption._member_map_: # pylint: disable=no-member - return extend_enum(LMAControlledMAGSuboption, key, default) - return LMAControlledMAGSuboption[key] # type: ignore[misc] + try: + return LMAControlledMAGSuboption[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return LMAControlledMAGSuboption(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'LMAControlledMAGSuboption': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'LMAControlledMAGSuboption': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'LMAControlledMAGSuboption': diff --git a/pcapkit/const/mh/mn_group_id.py b/pcapkit/const/mh/mn_group_id.py index e44c57482f..471692a80f 100644 --- a/pcapkit/const/mh/mn_group_id.py +++ b/pcapkit/const/mh/mn_group_id.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'MNGroupID': if default == -1: raise return MNGroupID(default) - if key not in MNGroupID._member_map_: # pylint: disable=no-member - return extend_enum(MNGroupID, key, default) - return MNGroupID[key] # type: ignore[misc] + try: + return MNGroupID[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return MNGroupID(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'MNGroupID': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'MNGroupID': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'MNGroupID': diff --git a/pcapkit/const/mh/mn_id_subtype.py b/pcapkit/const/mh/mn_id_subtype.py index a41eeeea98..50f09d06ff 100644 --- a/pcapkit/const/mh/mn_id_subtype.py +++ b/pcapkit/const/mh/mn_id_subtype.py @@ -61,9 +61,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'MNIDSubtype': if default == -1: raise return MNIDSubtype(default) - if key not in MNIDSubtype._member_map_: # pylint: disable=no-member - return extend_enum(MNIDSubtype, key, default) - return MNIDSubtype[key] # type: ignore[misc] + try: + return MNIDSubtype[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return MNIDSubtype(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'MNIDSubtype': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'MNIDSubtype': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'MNIDSubtype': diff --git a/pcapkit/const/mh/operator_id.py b/pcapkit/const/mh/operator_id.py index bbc17ee336..f8d81169f6 100644 --- a/pcapkit/const/mh/operator_id.py +++ b/pcapkit/const/mh/operator_id.py @@ -50,9 +50,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'OperatorID': if default == -1: raise return OperatorID(default) - if key not in OperatorID._member_map_: # pylint: disable=no-member - return extend_enum(OperatorID, key, default) - return OperatorID[key] # type: ignore[misc] + try: + return OperatorID[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return OperatorID(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'OperatorID': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'OperatorID': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'OperatorID': diff --git a/pcapkit/const/mh/option.py b/pcapkit/const/mh/option.py index e5aeada3d8..7603389689 100644 --- a/pcapkit/const/mh/option.py +++ b/pcapkit/const/mh/option.py @@ -250,9 +250,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Option': if default == -1: raise return Option(default) - if key not in Option._member_map_: # pylint: disable=no-member - return extend_enum(Option, key, default) - return Option[key] # type: ignore[misc] + try: + return Option[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Option(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Option': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Option': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Option': diff --git a/pcapkit/const/mh/packet.py b/pcapkit/const/mh/packet.py index d6841f06fd..05b3b1e3f9 100644 --- a/pcapkit/const/mh/packet.py +++ b/pcapkit/const/mh/packet.py @@ -109,9 +109,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Packet': if default == -1: raise return Packet(default) - if key not in Packet._member_map_: # pylint: disable=no-member - return extend_enum(Packet, key, default) - return Packet[key] # type: ignore[misc] + try: + return Packet[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Packet(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Packet': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Packet': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Packet': diff --git a/pcapkit/const/mh/qos_attribute.py b/pcapkit/const/mh/qos_attribute.py index 20ec5e4917..8fdea57b75 100644 --- a/pcapkit/const/mh/qos_attribute.py +++ b/pcapkit/const/mh/qos_attribute.py @@ -76,9 +76,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'QoSAttribute': if default == -1: raise return QoSAttribute(default) - if key not in QoSAttribute._member_map_: # pylint: disable=no-member - return extend_enum(QoSAttribute, key, default) - return QoSAttribute[key] # type: ignore[misc] + try: + return QoSAttribute[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return QoSAttribute(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'QoSAttribute': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'QoSAttribute': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'QoSAttribute': diff --git a/pcapkit/const/mh/revocation_status_code.py b/pcapkit/const/mh/revocation_status_code.py index 920b1251dd..5d38760943 100644 --- a/pcapkit/const/mh/revocation_status_code.py +++ b/pcapkit/const/mh/revocation_status_code.py @@ -67,9 +67,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RevocationStatusCode': if default == -1: raise return RevocationStatusCode(default) - if key not in RevocationStatusCode._member_map_: # pylint: disable=no-member - return extend_enum(RevocationStatusCode, key, default) - return RevocationStatusCode[key] # type: ignore[misc] + try: + return RevocationStatusCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RevocationStatusCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RevocationStatusCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RevocationStatusCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RevocationStatusCode': diff --git a/pcapkit/const/mh/revocation_trigger.py b/pcapkit/const/mh/revocation_trigger.py index 82d06bd93b..f308753c54 100644 --- a/pcapkit/const/mh/revocation_trigger.py +++ b/pcapkit/const/mh/revocation_trigger.py @@ -67,9 +67,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RevocationTrigger': if default == -1: raise return RevocationTrigger(default) - if key not in RevocationTrigger._member_map_: # pylint: disable=no-member - return extend_enum(RevocationTrigger, key, default) - return RevocationTrigger[key] # type: ignore[misc] + try: + return RevocationTrigger[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RevocationTrigger(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RevocationTrigger': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RevocationTrigger': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RevocationTrigger': diff --git a/pcapkit/const/mh/status_code.py b/pcapkit/const/mh/status_code.py index b95f7a9fa3..c9971a84da 100644 --- a/pcapkit/const/mh/status_code.py +++ b/pcapkit/const/mh/status_code.py @@ -218,9 +218,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'StatusCode': if default == -1: raise return StatusCode(default) - if key not in StatusCode._member_map_: # pylint: disable=no-member - return extend_enum(StatusCode, key, default) - return StatusCode[key] # type: ignore[misc] + try: + return StatusCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return StatusCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'StatusCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'StatusCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'StatusCode': diff --git a/pcapkit/const/mh/traffic_selector.py b/pcapkit/const/mh/traffic_selector.py index 4a1fb1c995..400e352c10 100644 --- a/pcapkit/const/mh/traffic_selector.py +++ b/pcapkit/const/mh/traffic_selector.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'TrafficSelector': if default == -1: raise return TrafficSelector(default) - if key not in TrafficSelector._member_map_: # pylint: disable=no-member - return extend_enum(TrafficSelector, key, default) - return TrafficSelector[key] # type: ignore[misc] + try: + return TrafficSelector[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return TrafficSelector(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'TrafficSelector': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'TrafficSelector': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'TrafficSelector': diff --git a/pcapkit/const/mh/upa_status.py b/pcapkit/const/mh/upa_status.py index 915a2aac6d..d273c209af 100644 --- a/pcapkit/const/mh/upa_status.py +++ b/pcapkit/const/mh/upa_status.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'UpdateNotificationACKStatus': if default == -1: raise return UpdateNotificationACKStatus(default) - if key not in UpdateNotificationACKStatus._member_map_: # pylint: disable=no-member - return extend_enum(UpdateNotificationACKStatus, key, default) - return UpdateNotificationACKStatus[key] # type: ignore[misc] + try: + return UpdateNotificationACKStatus[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return UpdateNotificationACKStatus(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'UpdateNotificationACKStatus': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'UpdateNotificationACKStatus': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'UpdateNotificationACKStatus': diff --git a/pcapkit/const/mh/upn_reason.py b/pcapkit/const/mh/upn_reason.py index 83009e133d..9e3879fc81 100644 --- a/pcapkit/const/mh/upn_reason.py +++ b/pcapkit/const/mh/upn_reason.py @@ -67,9 +67,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'UpdateNotificationReason': if default == -1: raise return UpdateNotificationReason(default) - if key not in UpdateNotificationReason._member_map_: # pylint: disable=no-member - return extend_enum(UpdateNotificationReason, key, default) - return UpdateNotificationReason[key] # type: ignore[misc] + try: + return UpdateNotificationReason[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return UpdateNotificationReason(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'UpdateNotificationReason': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'UpdateNotificationReason': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'UpdateNotificationReason': diff --git a/pcapkit/const/ospf/authentication.py b/pcapkit/const/ospf/authentication.py index 000e012cf6..bf27856436 100644 --- a/pcapkit/const/ospf/authentication.py +++ b/pcapkit/const/ospf/authentication.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Authentication': if default == -1: raise return Authentication(default) - if key not in Authentication._member_map_: # pylint: disable=no-member - return extend_enum(Authentication, key, default) - return Authentication[key] # type: ignore[misc] + try: + return Authentication[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Authentication(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Authentication': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Authentication': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Authentication': @@ -65,8 +108,8 @@ def _missing_(cls, value: 'int') -> 'Authentication': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 4 <= value <= 255: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 256 <= value <= 65535: #: Deprecated [:rfc:`6549`] - return extend_enum(cls, 'Deprecated_%d' % value, value) + return cls._unregistered_member(value, 'Deprecated') return super()._missing_(value) diff --git a/pcapkit/const/ospf/packet.py b/pcapkit/const/ospf/packet.py index 9c6321ea33..fc4cac226f 100644 --- a/pcapkit/const/ospf/packet.py +++ b/pcapkit/const/ospf/packet.py @@ -55,9 +55,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Packet': if default == -1: raise return Packet(default) - if key not in Packet._member_map_: # pylint: disable=no-member - return extend_enum(Packet, key, default) - return Packet[key] # type: ignore[misc] + try: + return Packet[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Packet(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Packet': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Packet': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Packet': @@ -71,8 +114,8 @@ def _missing_(cls, value: 'int') -> 'Packet': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 6 <= value <= 127: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 128 <= value <= 255: #: Reserved - return extend_enum(cls, 'Reserved_%d' % value, value) + return cls._unregistered_member(value, 'Reserved') return super()._missing_(value) diff --git a/pcapkit/const/pcapng/block_type.py b/pcapkit/const/pcapng/block_type.py index 982f8bb059..ae4b542de2 100644 --- a/pcapkit/const/pcapng/block_type.py +++ b/pcapkit/const/pcapng/block_type.py @@ -127,9 +127,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'BlockType': if default == -1: raise return BlockType(default) - if key not in BlockType._member_map_: # pylint: disable=no-member - return extend_enum(BlockType, key, default) - return BlockType[key] # type: ignore[misc] + try: + return BlockType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return BlockType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'BlockType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'BlockType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'BlockType': diff --git a/pcapkit/const/pcapng/filter_type.py b/pcapkit/const/pcapng/filter_type.py index 466fcfa3b8..47aaa29bf4 100644 --- a/pcapkit/const/pcapng/filter_type.py +++ b/pcapkit/const/pcapng/filter_type.py @@ -38,9 +38,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'FilterType': if default == -1: raise return FilterType(default) - if key not in FilterType._member_map_: # pylint: disable=no-member - return extend_enum(FilterType, key, default) - return FilterType[key] # type: ignore[misc] + try: + return FilterType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return FilterType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'FilterType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'FilterType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'FilterType': diff --git a/pcapkit/const/pcapng/hash_algorithm.py b/pcapkit/const/pcapng/hash_algorithm.py index a0b47bfa08..2f45e2d0e5 100644 --- a/pcapkit/const/pcapng/hash_algorithm.py +++ b/pcapkit/const/pcapng/hash_algorithm.py @@ -49,9 +49,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'HashAlgorithm': if default == -1: raise return HashAlgorithm(default) - if key not in HashAlgorithm._member_map_: # pylint: disable=no-member - return extend_enum(HashAlgorithm, key, default) - return HashAlgorithm[key] # type: ignore[misc] + try: + return HashAlgorithm[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return HashAlgorithm(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'HashAlgorithm': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'HashAlgorithm': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'HashAlgorithm': diff --git a/pcapkit/const/pcapng/record_type.py b/pcapkit/const/pcapng/record_type.py index 1774f22139..bb49c7091f 100644 --- a/pcapkit/const/pcapng/record_type.py +++ b/pcapkit/const/pcapng/record_type.py @@ -46,9 +46,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'RecordType': if default == -1: raise return RecordType(default) - if key not in RecordType._member_map_: # pylint: disable=no-member - return extend_enum(RecordType, key, default) - return RecordType[key] # type: ignore[misc] + try: + return RecordType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return RecordType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'RecordType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'RecordType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'RecordType': diff --git a/pcapkit/const/pcapng/secrets_type.py b/pcapkit/const/pcapng/secrets_type.py index 229519d509..70d72a674f 100644 --- a/pcapkit/const/pcapng/secrets_type.py +++ b/pcapkit/const/pcapng/secrets_type.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'SecretsType': if default == -1: raise return SecretsType(default) - if key not in SecretsType._member_map_: # pylint: disable=no-member - return extend_enum(SecretsType, key, default) - return SecretsType[key] # type: ignore[misc] + try: + return SecretsType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return SecretsType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'SecretsType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'SecretsType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'SecretsType': diff --git a/pcapkit/const/pcapng/verdict_type.py b/pcapkit/const/pcapng/verdict_type.py index 9b4823e955..1bb9464f78 100644 --- a/pcapkit/const/pcapng/verdict_type.py +++ b/pcapkit/const/pcapng/verdict_type.py @@ -43,9 +43,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'VerdictType': if default == -1: raise return VerdictType(default) - if key not in VerdictType._member_map_: # pylint: disable=no-member - return extend_enum(VerdictType, key, default) - return VerdictType[key] # type: ignore[misc] + try: + return VerdictType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return VerdictType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'VerdictType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'VerdictType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'VerdictType': diff --git a/pcapkit/const/reg/ethertype.py b/pcapkit/const/reg/ethertype.py index a0f5f55608..f78630427a 100644 --- a/pcapkit/const/reg/ethertype.py +++ b/pcapkit/const/reg/ethertype.py @@ -535,9 +535,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'EtherType': if default == -1: raise return EtherType(default) - if key not in EtherType._member_map_: # pylint: disable=no-member - return extend_enum(EtherType, key, default) - return EtherType[key] # type: ignore[misc] + try: + return EtherType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return EtherType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'EtherType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'EtherType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'EtherType': diff --git a/pcapkit/const/reg/linktype.py b/pcapkit/const/reg/linktype.py index 6a38455c15..ea11ba211d 100644 --- a/pcapkit/const/reg/linktype.py +++ b/pcapkit/const/reg/linktype.py @@ -765,9 +765,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'LinkType': if default == -1: raise return LinkType(default) - if key not in LinkType._member_map_: # pylint: disable=no-member - return extend_enum(LinkType, key, default) - return LinkType[key] # type: ignore[misc] + try: + return LinkType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return LinkType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'LinkType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'LinkType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'LinkType': diff --git a/pcapkit/const/reg/transtype.py b/pcapkit/const/reg/transtype.py index 091e17754d..cb8af75827 100644 --- a/pcapkit/const/reg/transtype.py +++ b/pcapkit/const/reg/transtype.py @@ -513,9 +513,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'TransType': if default == -1: raise return TransType(default) - if key not in TransType._member_map_: # pylint: disable=no-member - return extend_enum(TransType, key, default) - return TransType[key] # type: ignore[misc] + try: + return TransType[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return TransType(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'TransType': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'TransType': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'TransType': diff --git a/pcapkit/const/sctp/cause_code.py b/pcapkit/const/sctp/cause_code.py index 72bd4a0d55..458de847c8 100644 --- a/pcapkit/const/sctp/cause_code.py +++ b/pcapkit/const/sctp/cause_code.py @@ -110,9 +110,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'CauseCode': if default == -1: raise return CauseCode(default) - if key not in CauseCode._member_map_: # pylint: disable=no-member - return extend_enum(CauseCode, key, default) - return CauseCode[key] # type: ignore[misc] + try: + return CauseCode[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return CauseCode(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'CauseCode': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'CauseCode': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'CauseCode': @@ -126,14 +169,14 @@ def _missing_(cls, value: 'int') -> 'CauseCode': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 14 <= value <= 99: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 104 <= value <= 159: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 165 <= value <= 260: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 262 <= value <= 65535: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/sctp/chunk.py b/pcapkit/const/sctp/chunk.py index 50e5f579d6..4ca60d2226 100644 --- a/pcapkit/const/sctp/chunk.py +++ b/pcapkit/const/sctp/chunk.py @@ -128,9 +128,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Chunk': if default == -1: raise return Chunk(default) - if key not in Chunk._member_map_: # pylint: disable=no-member - return extend_enum(Chunk, key, default) - return Chunk[key] # type: ignore[misc] + try: + return Chunk[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Chunk(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Chunk': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Chunk': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Chunk': @@ -144,14 +187,14 @@ def _missing_(cls, value: 'int') -> 'Chunk': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 16 <= value <= 62: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 66 <= value <= 126: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 133 <= value <= 190: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 195 <= value <= 254: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/sctp/parameter.py b/pcapkit/const/sctp/parameter.py index be1276c072..5a6b2bfcc2 100644 --- a/pcapkit/const/sctp/parameter.py +++ b/pcapkit/const/sctp/parameter.py @@ -134,9 +134,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Parameter': if default == -1: raise return Parameter(default) - if key not in Parameter._member_map_: # pylint: disable=no-member - return extend_enum(Parameter, key, default) - return Parameter[key] # type: ignore[misc] + try: + return Parameter[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Parameter(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Parameter': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Parameter': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Parameter': @@ -150,14 +193,14 @@ def _missing_(cls, value: 'int') -> 'Parameter': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 2 <= value <= 4: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 19 <= value <= 32767: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 32777 <= value <= 49151: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 49159 <= value <= 65534: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/sctp/payload_protocol_identifier.py b/pcapkit/const/sctp/payload_protocol_identifier.py index 8c0bb36054..69a7853025 100644 --- a/pcapkit/const/sctp/payload_protocol_identifier.py +++ b/pcapkit/const/sctp/payload_protocol_identifier.py @@ -283,9 +283,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'PayloadProtocolIdentifier': if default == -1: raise return PayloadProtocolIdentifier(default) - if key not in PayloadProtocolIdentifier._member_map_: # pylint: disable=no-member - return extend_enum(PayloadProtocolIdentifier, key, default) - return PayloadProtocolIdentifier[key] # type: ignore[misc] + try: + return PayloadProtocolIdentifier[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return PayloadProtocolIdentifier(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'PayloadProtocolIdentifier': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'PayloadProtocolIdentifier': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'PayloadProtocolIdentifier': @@ -299,8 +342,8 @@ def _missing_(cls, value: 'int') -> 'PayloadProtocolIdentifier': raise ValueError('%r is not a valid %s' % (value, cls.__name__)) if 74 <= value <= 4241: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') if 4243 <= value <= 4294967295: #: Unassigned - return extend_enum(cls, 'Unassigned_%d' % value, value) + return cls._unregistered_member(value, 'Unassigned') return super()._missing_(value) diff --git a/pcapkit/const/tcp/checksum.py b/pcapkit/const/tcp/checksum.py index 57d9e75758..d3ae2c3400 100644 --- a/pcapkit/const/tcp/checksum.py +++ b/pcapkit/const/tcp/checksum.py @@ -45,9 +45,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Checksum': if default == -1: raise return Checksum(default) - if key not in Checksum._member_map_: # pylint: disable=no-member - return extend_enum(Checksum, key, default) - return Checksum[key] # type: ignore[misc] + try: + return Checksum[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Checksum(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Checksum': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Checksum': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Checksum': diff --git a/pcapkit/const/tcp/mp_tcp_option.py b/pcapkit/const/tcp/mp_tcp_option.py index a3cb7c1f0f..43748e7a4d 100644 --- a/pcapkit/const/tcp/mp_tcp_option.py +++ b/pcapkit/const/tcp/mp_tcp_option.py @@ -67,9 +67,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'MPTCPOption': if default == -1: raise return MPTCPOption(default) - if key not in MPTCPOption._member_map_: # pylint: disable=no-member - return extend_enum(MPTCPOption, key, default) - return MPTCPOption[key] # type: ignore[misc] + try: + return MPTCPOption[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return MPTCPOption(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'MPTCPOption': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'MPTCPOption': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'MPTCPOption': diff --git a/pcapkit/const/tcp/option.py b/pcapkit/const/tcp/option.py index 38c17c9cd0..14927a7672 100644 --- a/pcapkit/const/tcp/option.py +++ b/pcapkit/const/tcp/option.py @@ -174,9 +174,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'Option': if default == -1: raise return Option(default) - if key not in Option._member_map_: # pylint: disable=no-member - return extend_enum(Option, key, default) - return Option[key] # type: ignore[misc] + try: + return Option[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return Option(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'Option': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'Option': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'Option': diff --git a/pcapkit/const/vlan/priority_level.py b/pcapkit/const/vlan/priority_level.py index 5d7358b2e3..a13ada4025 100644 --- a/pcapkit/const/vlan/priority_level.py +++ b/pcapkit/const/vlan/priority_level.py @@ -61,9 +61,52 @@ def get(key: 'int | str', default: 'int' = -1) -> 'PriorityLevel': if default == -1: raise return PriorityLevel(default) - if key not in PriorityLevel._member_map_: # pylint: disable=no-member - return extend_enum(PriorityLevel, key, default) - return PriorityLevel[key] # type: ignore[misc] + try: + return PriorityLevel[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return PriorityLevel(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> 'PriorityLevel': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> 'PriorityLevel': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> 'PriorityLevel': diff --git a/pcapkit/protocols/internet/hopopt.py b/pcapkit/protocols/internet/hopopt.py index 751063142f..8aa2debcb2 100644 --- a/pcapkit/protocols/internet/hopopt.py +++ b/pcapkit/protocols/internet/hopopt.py @@ -795,7 +795,17 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') if TYPE_CHECKING: schema = cast('Schema_SMFIdentificationBasedDPDOption', schema) - tid_type = Enum_TaggerID.get(schema.info['type']) + # NOTE: #775 tier 1 made an unresolvable *string* key raise KeyError + # instead of minting a member; only a hand-constructed schema hits + # this guard. An unassigned *wire* value still mints via _missing_ + # (tier 2, deferred), so it never reaches here. + tid_key = None + try: + tid_key = schema.info['type'] + tid_type = Enum_TaggerID.get(tid_key) + except KeyError: + raise ProtocolError(f'{self.alias}: [OptNo {schema.type}] ' + f'unknown tagger-id type: {tid_key!r}') from None tid_len = schema.info['len'] opt = Data_SMFIdentificationBasedDPDOption( @@ -1019,7 +1029,17 @@ def _read_opt_mpl(self, schema: 'Schema_MPLOption', *, options: 'Option') -> 'Da ProtocolError: If the option is malformed. """ - kind = Enum_SeedID.get(schema.flags['type']) + # NOTE: #775 tier 1 made an unresolvable *string* key raise KeyError + # instead of minting a member; only a hand-constructed schema hits + # this guard. An unassigned *wire* value still mints via _missing_ + # (tier 2, deferred), so it never reaches here. + seed_key = None + try: + seed_key = schema.flags['type'] + kind = Enum_SeedID.get(seed_key) + except KeyError: + raise ProtocolError(f'{self.alias}: [OptNo {schema.type}] ' + f'unknown seed-id type: {seed_key!r}') from None clen = schema.len if schema.len < 2: diff --git a/pcapkit/protocols/internet/ipv6_opts.py b/pcapkit/protocols/internet/ipv6_opts.py index 6d3db23859..dbf8bba64d 100644 --- a/pcapkit/protocols/internet/ipv6_opts.py +++ b/pcapkit/protocols/internet/ipv6_opts.py @@ -798,7 +798,17 @@ def _read_opt_smf_dpd(self, schema: 'Schema_SMFDPDOption', *, options: 'Option') if TYPE_CHECKING: schema = cast('Schema_SMFIdentificationBasedDPDOption', schema) - tid_type = Enum_TaggerID.get(schema.info['type']) + # NOTE: #775 tier 1 made an unresolvable *string* key raise KeyError + # instead of minting a member; only a hand-constructed schema hits + # this guard. An unassigned *wire* value still mints via _missing_ + # (tier 2, deferred), so it never reaches here. + tid_key = None + try: + tid_key = schema.info['type'] + tid_type = Enum_TaggerID.get(tid_key) + except KeyError: + raise ProtocolError(f'{self.alias}: [OptNo {schema.type}] ' + f'unknown tagger-id type: {tid_key!r}') from None tid_len = schema.info['len'] opt = Data_SMFIdentificationBasedDPDOption( @@ -1022,7 +1032,17 @@ def _read_opt_mpl(self, schema: 'Schema_MPLOption', *, options: 'Option') -> 'Da ProtocolError: If the option is malformed. """ - kind = Enum_SeedID.get(schema.flags['type']) + # NOTE: #775 tier 1 made an unresolvable *string* key raise KeyError + # instead of minting a member; only a hand-constructed schema hits + # this guard. An unassigned *wire* value still mints via _missing_ + # (tier 2, deferred), so it never reaches here. + seed_key = None + try: + seed_key = schema.flags['type'] + kind = Enum_SeedID.get(seed_key) + except KeyError: + raise ProtocolError(f'{self.alias}: [OptNo {schema.type}] ' + f'unknown seed-id type: {seed_key!r}') from None clen = schema.len if schema.len < 2: diff --git a/pcapkit/toolkit/pyshark.py b/pcapkit/toolkit/pyshark.py index 27b5fa1940..cf38237f13 100644 --- a/pcapkit/toolkit/pyshark.py +++ b/pcapkit/toolkit/pyshark.py @@ -21,13 +21,68 @@ from pcapkit.const.reg.linktype import LinkType as Enum_LinkType from pcapkit.foundation.traceflow.data.tcp import Packet as TF_TCP_Packet +from pcapkit.utilities.exceptions import MissingKeyError if TYPE_CHECKING: from typing import Any from pyshark.packet.packet import Packet -__all__ = ['packet2dict', 'tcp_traceflow'] +__all__ = ['packet2dict', 'tcp_traceflow', 'FILTER_NAME_TO_LINKTYPE'] + +#: Wireshark display-filter name -> :class:`~pcapkit.const.reg.linktype.LinkType` member, for the +#: link-layer protocols :mod:`pyshark` can hand :func:`tcp_traceflow` as +#: ``packet.layers[0].layer_name``. PyShark takes that name verbatim from the PDML ```` attribute, which is Wireshark's own dissector *filter* name -- the third argument to +#: ``proto_register_protocol()`` in the relevant ``epan/dissectors/packet-*.c`` -- and that +#: vocabulary is not :class:`LinkType`'s: Ethernet's filter name is ``eth``, never ``ETHERNET``. +#: This table exists so :mod:`pcapkit` can bridge the two without minting a DLT for a name it +#: cannot actually place (see the NOTE in :func:`tcp_traceflow`). +#: +#: Both entries were checked two ways: against Wireshark's dissector registrations, and live with +#: ``tshark`` 4.6.9 plus ``editcap -T `` to confirm the PDML root each encapsulation +#: actually produces. Note the upper-casing is done by the call site in :func:`tcp_traceflow`, +#: not by :meth:`LinkType.get`, which is a plain subscript. +#: +#: * ``eth`` -> :attr:`~LinkType.ETHERNET` -- ``packet-eth.c`` registers it against +#: ``WTAP_ENCAP_ETHERNET``, and ``ether`` is the only one of the 158 swept encapsulations that +#: roots at ``eth``. ``ether-nettl`` is among the 68 unswept, so "exactly one" is unproven. +#: * ``tr`` -> :attr:`~LinkType.IEEE802_5` -- ``packet-tr.c``, ``WTAP_ENCAP_TOKEN_RING``; ``tr`` +#: is the only swept encapsulation rooting at ``tr``, with ``tr-nettl`` likewise unswept. +#: :class:`LinkType`'s own ``#:`` comment records that ``DLT_IEEE802`` *is* Token Ring, the +#: missing ``_5`` being historical. +#: +#: Everything else, measured rather than reasoned. A filter name serving several DLTs cannot be +#: mapped, because the PDML node does not say which one arrived: +#: +#: * ``sll`` -- serves :attr:`LinkType.LINUX_SLL` (113) and :attr:`LinkType.LINUX_SLL2` (276). +#: ``tshark -G protocols`` registers a single ``sll`` name, and ``editcap -T linux-sll`` and +#: ``-T linux-sll2`` both root at ``sll``. No entry; raises. +#: * ``raw`` -- serves :attr:`LinkType.RAW` (101), :attr:`LinkType.IPV4` (228) and +#: :attr:`LinkType.IPV6` (229); ``editcap -T rawip``, ``-T rawip4`` and ``-T rawip6`` all root at +#: ``raw``. It does **not** raise: ``'RAW'`` is a member name, so the fallback answers 101 for all +#: three -- silently wrong for 228 and 229. Tracked in #843. +#: * ``null`` -- serves :attr:`LinkType.NULL` (0) and :attr:`LinkType.LOOP` (108); ``editcap -T +#: null`` and ``-T loop`` both root at ``null``. Also does not raise: answers 0 for both, silently +#: wrong for 108. Tracked in #843. (``loop`` is a different protocol entirely -- ``tshark -G +#: protocols`` gives it as Configuration Test Protocol, an Ethernet payload on ethertype 0x9000.) +#: * ``ip`` and ``ipv6`` -- never arrive as the root layer. ``editcap -T`` accepts 226 +#: encapsulations; of the **158** an Ethernet source can be rewritten into, neither is ever +#: ``layers[0]`` -- a raw IPv6 capture roots at ``raw`` and carries ``ipv6`` as the *next* layer. +#: The other 68 refuse that rewrite (``can't be written as``, not an unknown type) and are +#: untested. No entry needed for either, and none would be reached. +#: * ``ppp``, ``fddi``, ``lapd`` -- no entry needed; each upper-cases onto a real member name, so +#: the fallback resolves them. ``ppp`` and ``lapd`` each serve several DLTs and so answer with the +#: wrong one, which is the same #843 defect rather than anything this table introduces. +#: * ``fr`` -- measured single-DLT after all: ``frelay`` and ``frelay-with-direction`` both write +#: DLT 107, so it is mappable. Left out only because adding it is scope this change does not need. +#: * ``wlan`` -- not investigated. Left out rather than guessed. +#: * every other :class:`LinkType` member -- no evidence was gathered either way; they are simply +#: untried, not ruled out. +FILTER_NAME_TO_LINKTYPE = { + 'eth': Enum_LinkType.ETHERNET, + 'tr': Enum_LinkType.IEEE802_5, +} # type: dict[str, Enum_LinkType] def packet2dict(packet: 'Packet') -> 'dict[str, Any]': @@ -83,8 +138,41 @@ def tcp_traceflow(packet: 'Packet') -> 'TF_TCP_Packet | None': if 'TCP' in packet: tcp = cast('Packet', packet.tcp) + # NOTE: no default here, deliberately. Since #775 tier 1, ``get()`` + # with no default raises on an unresolvable name instead of minting + # one. NULL and RAW are genuine DLTs -- BSD loopback and raw IP + # framing, respectively -- each meant to go with its own handler + # protocol class, so neither is an honest stand-in for "unknown link + # type" and this must not paper over the miss with either. + # + # PyShark's own layer name is Wireshark's PDML *filter* name (e.g. + # ``eth``, not ``ethernet``), which is generally not a LinkType member + # name -- so ``FILTER_NAME_TO_LINKTYPE`` (module level, above) is + # tried first, on the name as PyShark reports it. Only when that + # table has nothing for this name does this fall back to + # :meth:`LinkType.get` on the upper-cased name, which is what lets a + # filter name that happens to already spell a LinkType member (e.g. + # ``ppp``, ``fddi``) resolve without needing an entry of its own. The + # table is consulted first rather than second so that a curated, + # source-verified entry always wins over an incidental upper-case + # match, should a future LinkType member ever collide with one of + # this module's filter names by coincidence. The bare ``KeyError`` + # from either lookup is re-raised as + # :exc:`~pcapkit.utilities.exceptions.MissingKeyError` -- this + # package's own house exception for a lookup miss -- rather than + # letting it escape this public function. + name = packet.layers[0].layer_name + try: + protocol = FILTER_NAME_TO_LINKTYPE[name.lower()] + except KeyError: + name = name.upper() + try: + protocol = Enum_LinkType.get(name) + except KeyError: + raise MissingKeyError(name) from None + data = TF_TCP_Packet( # type: ignore[type-var] - protocol=Enum_LinkType.get(packet.layers[0].layer_name.upper()), # data link type from global header + protocol=protocol, # data link type index=int(packet.number), # frame number frame=packet2dict(packet), # extracted packet syn=bool(int(tcp.flags_syn)), # TCP synchronise (SYN) flag diff --git a/pcapkit/toolkit/scapy.py b/pcapkit/toolkit/scapy.py index 1878d753c1..09df946fc2 100644 --- a/pcapkit/toolkit/scapy.py +++ b/pcapkit/toolkit/scapy.py @@ -43,7 +43,7 @@ from pcapkit.foundation.reassembly.data.tcp import Packet as TCP_Packet from pcapkit.foundation.traceflow.data.tcp import Packet as TF_TCP_Packet from pcapkit.utilities.compat import ModuleNotFoundError # pylint: disable=redefined-builtin -from pcapkit.utilities.exceptions import ModuleNotFound, stacklevel +from pcapkit.utilities.exceptions import MissingKeyError, ModuleNotFound, stacklevel from pcapkit.utilities.warnings import ScapyWarning, warn try: @@ -320,8 +320,30 @@ def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | No ip = cast('IP', packet['IP']) if 'IP' in packet else cast('IPv6', packet['IPv6']) tcp = cast('TCP', packet['TCP']) + # NOTE: no default here, deliberately. Since #775 tier 1, ``get()`` + # with no default raises on an unresolvable name instead of minting + # one. NULL and RAW are genuine DLTs -- BSD loopback and raw IP + # framing, respectively -- each meant to go with its own handler + # protocol class, so neither is an honest stand-in for "unknown link + # type" and this must not paper over the miss with either. An + # IP-rooted Scapy packet's ``(IP()/TCP()).name`` is ``'IP'``, which is + # not a LinkType member name, and now raises. Note the asymmetry, which + # is not a choice made here: an IPv6-rooted packet's name uppercases to + # ``'IPV6'``, which *is* a member (``LinkType.IPV6``, 229), so it + # resolves silently -- to a DLT the caller never chose. Only the v4 name + # happens to miss. The bare ``KeyError`` + # from :meth:`LinkType.get` is caught and re-raised as + # :exc:`~pcapkit.utilities.exceptions.MissingKeyError` -- this + # package's own house exception for a lookup miss -- rather than + # letting it escape this public function. + name = packet.name.upper() + try: + protocol = Enum_LinkType.get(name) + except KeyError: + raise MissingKeyError(name) from None + data = TF_TCP_Packet( # type: ignore[type-var] - protocol=Enum_LinkType.get(packet.name.upper()), # data link type from global header + protocol=protocol, # data link type index=count, # frame number frame=packet2dict(packet), # extracted packet syn=bool(tcp.flags.S), # TCP synchronise (SYN) flag diff --git a/pcapkit/utilities/decorators.py b/pcapkit/utilities/decorators.py index b68ed030b6..1b59496dec 100644 --- a/pcapkit/utilities/decorators.py +++ b/pcapkit/utilities/decorators.py @@ -161,16 +161,19 @@ def behold(*args: 'P.args', **kwargs: 'P.kwargs') -> 'R_beholder': # # Measured, because the layers differ and it is easy to state this too # broadly: SCTP's unregistered path keeps its enumeration -- an unknown - # PPID gives ``SCTP:Unassigned_4243`` and ``protocol=4243`` -- so + # PPID gives ``SCTP:Unassigned`` and ``protocol=4243`` -- so # without this line, *registering* NGAP on PPID 60 would have made a # failed parse report a bare ``SCTP:Raw`` and ``protocol=None``, less - # than the same bytes gave while unregistered. TCP's unregistered path - # does not: an unknown port yields ``protocol=None`` already, because + # than the same bytes gave while unregistered. TCP's unregistered + # path keeps its code too, but as a plain integer: an unknown port + # yields ``TCP:Raw`` with ``protocol=49998``, because # ``Transport._decode_next_layer`` resolves ports through - # ``__proto__`` and never reaches here. So this makes the *failure* - # path uniform while the *unknown* paths stay inconsistent with each - # other, which is #418 rather than something to fix from inside a - # decorator. + # ``__proto__`` and never reaches here, and forwards the port + # whether or not it is registered. So both *unknown* paths preserve + # the code that arrived; what differs is only that SCTP's is an + # enumeration the protochain can name, while TCP's is an ``int`` and + # renders as ``Raw``. That was #418, closed by #426; this line is + # about the *failure* path, which it makes uniform. next_ = protocol(file_, length, error=str(exc), alias=proto) return cast('R_beholder', next_) return behold diff --git a/pcapkit/vendor/default.py b/pcapkit/vendor/default.py index 90812082e7..b7eecaa9b1 100644 --- a/pcapkit/vendor/default.py +++ b/pcapkit/vendor/default.py @@ -92,9 +92,52 @@ def get(key: 'int | str', default: 'int' = -1) -> '{NAME}': if default == -1: raise return {NAME}(default) - if key not in {NAME}._member_map_: # pylint: disable=no-member - return extend_enum({NAME}, key, default) - return {NAME}[key] # type: ignore[misc] + try: + return {NAME}[key] # type: ignore[misc] + except KeyError: + if default == -1: + raise + return {NAME}(default) + + @classmethod + def register(cls, value: 'int', name: 'str') -> '{NAME}': + """Explicitly register a new member. + + Unlike :meth:`get` and :meth:`_missing_`, which resolve a key or a + value without minting anything new, this is the caller-named entry + point that still grows the registry, via :func:`aenum.extend_enum`. + + Args: + value: Value of the new member. + name: Name of the new member. + + Returns: + The newly registered member. + + """ + return extend_enum(cls, name, value) + + @classmethod + def _unregistered_member(cls, value: 'int', name: 'str') -> '{NAME}': + """Build a member absent from this registry's own lookup tables. + + Used by :meth:`_missing_` for a bounded-but-unassigned value it + resolves without anyone asking for a name, so that such a lookup no + longer grows the registry -- contrast :meth:`register`, the explicit + path that still does. + + Args: + value: The member's value. + name: The member's name. + + Returns: + The unregistered member. + + """ + obj = int.__new__(cls, value) + obj._name_ = name # pylint: disable=protected-access + obj._value_ = value # pylint: disable=protected-access + return obj @classmethod def _missing_(cls, value: 'int') -> '{NAME}': @@ -361,7 +404,7 @@ def process(self, data: 'list[str]') -> 'tuple[list[str], list[str]]': miss.append(f'if {start} <= value <= {stop}:') miss.append(f' #: {desc}') - miss.append(f" return extend_enum(cls, '{self.safe_name(name)}_%d' % value, value)") + miss.append(f" return cls._unregistered_member(value, '{self.safe_name(name)}')") return enum, miss def count(self, data: 'list[str]') -> 'Counter[str]': diff --git a/tests/const/test_const_enum_get.py b/tests/const/test_const_enum_get.py index f0e26ed57c..e3be99c403 100644 --- a/tests/const/test_const_enum_get.py +++ b/tests/const/test_const_enum_get.py @@ -180,7 +180,11 @@ def test_the_reported_case_returns_the_default(self) -> None: # A resolvable key is untouched. self.assertIs(Hardware.get(1), Hardware.Ethernet) - self.assertIs(Hardware.get(40), Hardware(40)) + # 40 falls in Hardware's unassigned 39-255 range: since #775 tier 1, + # that no longer mints a permanent member, so each lookup returns its + # own unregistered pseudo-member -- equal, but no longer the same + # object, which is why this is assertEqual rather than assertIs. + self.assertEqual(Hardware.get(40), Hardware(40)) self.assertIs(Hardware.get('Ethernet'), Hardware.Ethernet) def test_the_placeholder_still_raises(self) -> None: diff --git a/tests/const/test_const_enum_no_mint.py b/tests/const/test_const_enum_no_mint.py new file mode 100644 index 0000000000..6d4c219221 --- /dev/null +++ b/tests/const/test_const_enum_no_mint.py @@ -0,0 +1,500 @@ +# -*- coding: utf-8 -*- +"""Regression tests for GitHub issue #775's tier 1: the miss path must not mint. + +The maintainer's ruling on #775, verbatim: *"so that we dont create registered +enums out of unrecognised/unregistered values, unless user/caller explicitly +created them"* -- and lookup never counts as asking for a name, only the new +:meth:`register` classmethod does. Before this change, both ``get()``'s string +path and ``_missing_``'s bounded-but-unassigned range branch called +:func:`aenum.extend_enum`, permanently growing the registry for a value nobody +asked to be named. Measured on this tree before the fix: +:class:`~pcapkit.const.arp.hardware.Hardware`'s ``__members__`` moved from 41 +entries to 42 after a single ``Hardware(40)`` (40 sits in the documented +39-255 "Unassigned" range) and stayed at 42 on a second call -- growth that +never reverses for the life of the process, and that a long-running capture +walking many such values would accumulate without bound. + +Tier 1 touches exactly two sites in :mod:`pcapkit.vendor.default`: the shared +``get()`` template and the ``_missing_`` range-branch :meth:`~pcapkit.vendor. +default.Vendor.process` assembles -- see that module for the two hunks. It is +scoped to the registries that inherit both unmodified, which this module's +:data:`REGISTRIES_WITH_UNASSIGNED_RANGES` and :data:`ALL_REGISTRIES` enumerate; +the 12 vendor files that replace the shared template wholesale (and whatever +they generate) are out of scope and untouched. + +""" +from __future__ import annotations + +import ast +import importlib +import inspect +import pathlib +import re +import unittest +from typing import TYPE_CHECKING + +from tests._support import ISOLATED_PREFIXES, purge_modules, restore_modules, snapshot_modules + +if TYPE_CHECKING: + from typing import Optional + +#: (module, class name) for every one of tier 1's 22 registries whose vendor +#: crawler inherits :mod:`pcapkit.vendor.default`'s ``process()`` unmodified, +#: so their ``_missing_`` range branches are in scope for the mint fix too -- +#: not just their ``get()``. Derived by AST from pcapkit/vendor/*.py: these are +#: exactly the in-scope vendor files with no ``def process`` override of their +#: own. +REGISTRIES_WITH_UNASSIGNED_RANGES = ( + ('pcapkit.const.arp.hardware', 'Hardware'), + ('pcapkit.const.arp.operation', 'Operation'), + ('pcapkit.const.hip.certificate', 'Certificate'), + ('pcapkit.const.hip.cipher', 'Cipher'), + ('pcapkit.const.hip.di', 'DITypes'), + ('pcapkit.const.hip.ecdsa_curve', 'ECDSACurve'), + ('pcapkit.const.hip.ecdsa_low_curve', 'ECDSALowCurve'), + ('pcapkit.const.hip.esp_transform_suite', 'ESPTransformSuite'), + ('pcapkit.const.hip.hi_algorithm', 'HIAlgorithm'), + ('pcapkit.const.hip.hit_suite', 'HITSuite'), + ('pcapkit.const.hip.nat_traversal', 'NATTraversal'), + ('pcapkit.const.hip.notify_message', 'NotifyMessage'), + ('pcapkit.const.hip.registration', 'Registration'), + ('pcapkit.const.hip.registration_failure', 'RegistrationFailure'), + ('pcapkit.const.hip.suite', 'Suite'), + # NOTE: hip.transport.Transport is deliberately absent here: its FLAG + # bound (0-3) covers every defined code with no unassigned gap, so its + # process() emits no range branch at all -- nothing for this sweep to + # exercise. It still carries get()/register()/_unregistered_member, + # which ALL_REGISTRIES below covers. + ('pcapkit.const.ospf.authentication', 'Authentication'), + ('pcapkit.const.ospf.packet', 'Packet'), + ('pcapkit.const.sctp.cause_code', 'CauseCode'), + ('pcapkit.const.sctp.chunk', 'Chunk'), + ('pcapkit.const.sctp.parameter', 'Parameter'), + ('pcapkit.const.sctp.payload_protocol_identifier', 'PayloadProtocolIdentifier'), +) + +#: Adds the one range-less registry back in, for the tests that only need +#: get()/register()/_unregistered_member rather than an unassigned value. +ALL_REGISTRIES = REGISTRIES_WITH_UNASSIGNED_RANGES + ( + ('pcapkit.const.hip.transport', 'Transport'), +) + +#: Every one of tier 1's 105 registries -- every generated ``const/`` module +#: whose vendor crawler inherits the shared ``get()``/``register()``/ +#: ``_unregistered_member()`` template from :mod:`pcapkit.vendor.default` +#: unmodified. Derived the same way as :data:`ALL_REGISTRIES` above, just +#: without narrowing to the 22 with an unassigned range: every vendor file +#: under :mod:`pcapkit.vendor` that is not one of the 12 which replace the +#: shared template wholesale (and not one of the four ``AppType`` transport +#: subclasses that inherit *that* bespoke template instead). +ALL_105_REGISTRIES = ( + ('pcapkit.const.arp.hardware', 'Hardware'), + ('pcapkit.const.arp.operation', 'Operation'), + ('pcapkit.const.esp.cipher', 'Cipher'), + ('pcapkit.const.esp.integrity', 'Integrity'), + ('pcapkit.const.hip.certificate', 'Certificate'), + ('pcapkit.const.hip.cipher', 'Cipher'), + ('pcapkit.const.hip.di', 'DITypes'), + ('pcapkit.const.hip.ecdsa_curve', 'ECDSACurve'), + ('pcapkit.const.hip.ecdsa_low_curve', 'ECDSALowCurve'), + ('pcapkit.const.hip.eddsa_curve', 'EdDSACurve'), + ('pcapkit.const.hip.esp_transform_suite', 'ESPTransformSuite'), + ('pcapkit.const.hip.group', 'Group'), + ('pcapkit.const.hip.hi_algorithm', 'HIAlgorithm'), + ('pcapkit.const.hip.hit_suite', 'HITSuite'), + ('pcapkit.const.hip.nat_traversal', 'NATTraversal'), + ('pcapkit.const.hip.notify_message', 'NotifyMessage'), + ('pcapkit.const.hip.packet', 'Packet'), + ('pcapkit.const.hip.parameter', 'Parameter'), + ('pcapkit.const.hip.registration', 'Registration'), + ('pcapkit.const.hip.registration_failure', 'RegistrationFailure'), + ('pcapkit.const.hip.suite', 'Suite'), + ('pcapkit.const.hip.transport', 'Transport'), + ('pcapkit.const.http.error_code', 'ErrorCode'), + ('pcapkit.const.http.frame', 'Frame'), + ('pcapkit.const.http.setting', 'Setting'), + ('pcapkit.const.ipv4.classification_level', 'ClassificationLevel'), + ('pcapkit.const.ipv4.option_class', 'OptionClass'), + ('pcapkit.const.ipv4.option_number', 'OptionNumber'), + ('pcapkit.const.ipv4.protection_authority', 'ProtectionAuthority'), + ('pcapkit.const.ipv4.qs_function', 'QSFunction'), + ('pcapkit.const.ipv4.router_alert', 'RouterAlert'), + ('pcapkit.const.ipv4.tos_del', 'ToSDelay'), + ('pcapkit.const.ipv4.tos_ecn', 'ToSECN'), + ('pcapkit.const.ipv4.tos_pre', 'ToSPrecedence'), + ('pcapkit.const.ipv4.tos_rel', 'ToSReliability'), + ('pcapkit.const.ipv4.tos_thr', 'ToSThroughput'), + ('pcapkit.const.ipv4.ts_flag', 'TSFlag'), + ('pcapkit.const.ipv6.option', 'Option'), + ('pcapkit.const.ipv6.option_action', 'OptionAction'), + ('pcapkit.const.ipv6.qs_function', 'QSFunction'), + ('pcapkit.const.ipv6.router_alert', 'RouterAlert'), + ('pcapkit.const.ipv6.routing', 'Routing'), + ('pcapkit.const.ipv6.seed_id', 'SeedID'), + ('pcapkit.const.ipv6.smf_dpd_mode', 'SMFDPDMode'), + ('pcapkit.const.ipv6.tagger_id', 'TaggerID'), + ('pcapkit.const.ipx.packet', 'Packet'), + ('pcapkit.const.ipx.socket', 'Socket'), + ('pcapkit.const.l2tp.type', 'Type'), + ('pcapkit.const.mh.access_type', 'AccessType'), + ('pcapkit.const.mh.ack_status_code', 'ACKStatusCode'), + ('pcapkit.const.mh.ani_suboption', 'ANISuboption'), + ('pcapkit.const.mh.auth_subtype', 'AuthSubtype'), + ('pcapkit.const.mh.binding_error', 'BindingError'), + ('pcapkit.const.mh.binding_revocation', 'BindingRevocation'), + ('pcapkit.const.mh.cga_extension', 'CGAExtension'), + ('pcapkit.const.mh.cga_sec', 'CGASec'), + ('pcapkit.const.mh.cga_type', 'CGAType'), + ('pcapkit.const.mh.dhcp_support_mode', 'DHCPSupportMode'), + ('pcapkit.const.mh.dns_status_code', 'DNSStatusCode'), + ('pcapkit.const.mh.dsmip6_tls_packet', 'DSMIP6TLSPacket'), + ('pcapkit.const.mh.dsmipv6_home_address', 'DSMIPv6HomeAddress'), + ('pcapkit.const.mh.enumerating_algorithm', 'EnumeratingAlgorithm'), + ('pcapkit.const.mh.fb_ack_status', 'FlowBindingACKStatus'), + ('pcapkit.const.mh.fb_action', 'FlowBindingAction'), + ('pcapkit.const.mh.fb_indication_trigger', 'FlowBindingIndicationTrigger'), + ('pcapkit.const.mh.fb_type', 'FlowBindingType'), + ('pcapkit.const.mh.flow_id_status', 'FlowIDStatus'), + ('pcapkit.const.mh.flow_id_suboption', 'FlowIDSuboption'), + ('pcapkit.const.mh.handoff_type', 'HandoffType'), + ('pcapkit.const.mh.handover_ack_status', 'HandoverACKStatus'), + ('pcapkit.const.mh.handover_initiate_status', 'HandoverInitiateStatus'), + ('pcapkit.const.mh.home_address_reply', 'HomeAddressReply'), + ('pcapkit.const.mh.lla_code', 'LLACode'), + ('pcapkit.const.mh.lma_mag_suboption', 'LMAControlledMAGSuboption'), + ('pcapkit.const.mh.mn_group_id', 'MNGroupID'), + ('pcapkit.const.mh.mn_id_subtype', 'MNIDSubtype'), + ('pcapkit.const.mh.operator_id', 'OperatorID'), + ('pcapkit.const.mh.option', 'Option'), + ('pcapkit.const.mh.packet', 'Packet'), + ('pcapkit.const.mh.qos_attribute', 'QoSAttribute'), + ('pcapkit.const.mh.revocation_status_code', 'RevocationStatusCode'), + ('pcapkit.const.mh.revocation_trigger', 'RevocationTrigger'), + ('pcapkit.const.mh.status_code', 'StatusCode'), + ('pcapkit.const.mh.traffic_selector', 'TrafficSelector'), + ('pcapkit.const.mh.upa_status', 'UpdateNotificationACKStatus'), + ('pcapkit.const.mh.upn_reason', 'UpdateNotificationReason'), + ('pcapkit.const.ospf.authentication', 'Authentication'), + ('pcapkit.const.ospf.packet', 'Packet'), + ('pcapkit.const.pcapng.block_type', 'BlockType'), + ('pcapkit.const.pcapng.filter_type', 'FilterType'), + ('pcapkit.const.pcapng.hash_algorithm', 'HashAlgorithm'), + ('pcapkit.const.pcapng.record_type', 'RecordType'), + ('pcapkit.const.pcapng.secrets_type', 'SecretsType'), + ('pcapkit.const.pcapng.verdict_type', 'VerdictType'), + ('pcapkit.const.reg.ethertype', 'EtherType'), + ('pcapkit.const.reg.linktype', 'LinkType'), + ('pcapkit.const.reg.transtype', 'TransType'), + ('pcapkit.const.sctp.cause_code', 'CauseCode'), + ('pcapkit.const.sctp.chunk', 'Chunk'), + ('pcapkit.const.sctp.parameter', 'Parameter'), + ('pcapkit.const.sctp.payload_protocol_identifier', 'PayloadProtocolIdentifier'), + ('pcapkit.const.tcp.checksum', 'Checksum'), + ('pcapkit.const.tcp.mp_tcp_option', 'MPTCPOption'), + ('pcapkit.const.tcp.option', 'Option'), + ('pcapkit.const.vlan.priority_level', 'PriorityLevel'), +) + +_RANGE_RE = re.compile(r'if (\d+) <= value <= (\d+):') + + +def _first_unassigned_value(cls: 'type') -> 'int': + """The first value :meth:`cls._missing_ `'s own source + documents as a bounded-but-unassigned range, read from the class's own + compiled ``_missing_`` rather than hardcoded, so this sweep tracks + whatever the generated module actually says. + + """ + source = inspect.getsource(cls._missing_) # type: ignore[attr-defined] + match = _RANGE_RE.search(source) + assert match is not None, f'{cls.__name__}._missing_ has no range branch' + return int(match.group(1)) + + +def _purge_member(cls: 'type', name: 'str', value: 'int') -> 'None': + """Undo an :func:`~aenum.extend_enum` so a test's explicit + :meth:`register` call does not leak into the rest of the suite. + + Mirrors ``tests.const.test_const_apptype_split_unit._purge_member``, minus + the :class:`~pcapkit.const.reg.apptype.AppType`-only ``__registry__`` + bookkeeping: the base template these registries share carries no such + attribute. + + """ + member = cls.__members__.get(name) # type: ignore[attr-defined] + if member is None: + return + cls._member_map_.pop(name, None) # type: ignore[attr-defined] + if name in cls._member_names_: # type: ignore[attr-defined] + cls._member_names_.remove(name) # type: ignore[attr-defined] + cls._value2member_map_.pop(value, None) # type: ignore[attr-defined] + + +class UnassignedRangeDoesNotMintTests(unittest.TestCase): + """A bounded-but-unassigned value must not grow the registry.""" + + if TYPE_CHECKING: + registries: 'list[type]' + + @classmethod + def setUpClass(cls) -> None: + snapshot = snapshot_modules(ISOLATED_PREFIXES) + purge_modules(['pcapkit']) + cls.registries = [ + getattr(importlib.import_module(module_name), class_name) + for module_name, class_name in REGISTRIES_WITH_UNASSIGNED_RANGES + ] + cls.addClassCleanup(restore_modules, snapshot, ISOLATED_PREFIXES) + + def test_the_reported_case(self) -> None: + """The exact figures from this module's own docstring, pinned.""" + from pcapkit.const.arp.hardware import Hardware + + before = len(Hardware.__members__) + first = Hardware(40) + after_one = len(Hardware.__members__) + second = Hardware(40) + after_two = len(Hardware.__members__) + + self.assertEqual(before, after_one) + self.assertEqual(before, after_two) + self.assertEqual(first, second) + self.assertIsNot(first, second) + self.assertNotIn(40, Hardware._value2member_map_) # type: ignore[attr-defined] + self.assertNotIn('Unassigned', Hardware.__members__) + + def test_unassigned_value_absent_from_lookup_tables(self) -> None: + """Swept across every registry with a bounded-unassigned range.""" + for cls in self.registries: + with self.subTest(registry=cls.__qualname__): + value = _first_unassigned_value(cls) + self.assertNotIn(value, cls._value2member_map_) # type: ignore[attr-defined] + + member = cls(value) + + self.assertEqual(member.value, value) + self.assertNotIn(value, cls._value2member_map_) # type: ignore[attr-defined] + self.assertNotIn(member.name, cls.__members__) + + def test_repeated_lookup_does_not_grow_members(self) -> None: + """Calling twice must not mint a second permanent member either.""" + for cls in self.registries: + with self.subTest(registry=cls.__qualname__): + value = _first_unassigned_value(cls) + before = len(cls.__members__) + + first = cls(value) + after_one = len(cls.__members__) + second = cls(value) + after_two = len(cls.__members__) + + self.assertEqual(before, after_one) + self.assertEqual(before, after_two) + self.assertEqual(first, second) + self.assertIsNot(first, second) + + def test_out_of_bound_value_still_fails(self) -> None: + """#775 accepts that a value outside every declared range stays + failing -- this fix only stops the *mint*, not the eventual raise.""" + from pcapkit.const.arp.hardware import Hardware + + with self.assertRaises(ValueError): + Hardware(1 << 32) + + +class GetNoLongerMintsTests(unittest.TestCase): + """``get()``'s string path must resolve without minting, matching the + same ruling applied to the int path above.""" + + def setUp(self) -> None: + snapshot = snapshot_modules(ISOLATED_PREFIXES) + purge_modules(['pcapkit']) + self.addCleanup(restore_modules, snapshot, ISOLATED_PREFIXES) + + def test_unresolvable_string_key_without_default_raises(self) -> None: + from pcapkit.const.arp.hardware import Hardware + + before = len(Hardware.__members__) + with self.assertRaises(KeyError): + Hardware.get('Definitely-Not-A-Member') + self.assertEqual(before, len(Hardware.__members__)) + + def test_unresolvable_string_key_with_default_falls_back_by_value(self) -> None: + from pcapkit.const.arp.hardware import Hardware + + before = len(Hardware.__members__) + result = Hardware.get('Definitely-Not-A-Member', 1) + self.assertIs(result, Hardware.Ethernet) + self.assertEqual(before, len(Hardware.__members__)) + + def test_unresolvable_string_key_with_default_in_unassigned_range(self) -> None: + """The fallback is itself a value lookup, so a default landing in a + bounded-unassigned range returns the same kind of pseudo-member the + int path does -- rather than minting a member literally named after + the caller's unresolved key, which is what this used to do.""" + from pcapkit.const.arp.hardware import Hardware + + before = len(Hardware.__members__) + result = Hardware.get('Definitely-Not-A-Member', 40) + self.assertEqual(result.value, 40) + self.assertEqual(before, len(Hardware.__members__)) + self.assertNotIn('Definitely-Not-A-Member', Hardware.__members__) + + +class RegisterStillMintsTests(unittest.TestCase): + """The one caller-named, explicit path must still grow the registry.""" + + def setUp(self) -> None: + snapshot = snapshot_modules(ISOLATED_PREFIXES) + purge_modules(['pcapkit']) + self.addCleanup(restore_modules, snapshot, ISOLATED_PREFIXES) + + def test_register_mints_a_real_permanent_member(self) -> None: + from pcapkit.const.arp.hardware import Hardware + + self.addCleanup(_purge_member, Hardware, 'PyPCAPKit_775_test', 60000) + + before = len(Hardware.__members__) + new = Hardware.register(60000, 'PyPCAPKit_775_test') + after = len(Hardware.__members__) + + self.assertEqual(after, before + 1) + self.assertIs(new, Hardware.PyPCAPKit_775_test) # type: ignore[attr-defined] + self.assertIs(Hardware(60000), new) + self.assertIn(60000, Hardware._value2member_map_) # type: ignore[attr-defined] + + def test_register_is_present_on_every_tier_1_registry(self) -> None: + for module_name, class_name in ALL_REGISTRIES: + with self.subTest(registry=class_name): + cls = getattr(importlib.import_module(module_name), class_name) + self.assertTrue(callable(getattr(cls, 'register', None))) + self.assertTrue(callable(getattr(cls, '_unregistered_member', None))) + + def test_register_actually_mints_on_every_one_of_the_105_registries(self) -> None: + """Exercise :meth:`register` for real -- not just ``callable()`` -- + on every one of tier 1's 105 registries, the full set the shared + template now reaches (:data:`ALL_105_REGISTRIES`), not only the 22 + with a bounded-unassigned range. A high, fixed value is deliberately + outside any registry's assigned codes -- most bound themselves to 16 + bits or fewer -- so this cannot collide with anything either the CSV + seeded or a sibling test minted. + + """ + value = 0x6E7A0001 # arbitrary, outside every registry's own domain + name = 'PyPCAPKit_775_sweep' + + for module_name, class_name in ALL_105_REGISTRIES: + with self.subTest(registry=class_name): + cls = getattr(importlib.import_module(module_name), class_name) + self.addCleanup(_purge_member, cls, name, value) + + before = len(cls.__members__) + new = cls.register(value, name) + after = len(cls.__members__) + + self.assertEqual(after, before + 1) + self.assertIs(cls(value), new) + self.assertIn(value, cls._value2member_map_) # type: ignore[attr-defined] + + +class UnregisteredMemberHelperTests(unittest.TestCase): + """:meth:`_unregistered_member` in isolation, on every one of the 105 + registries -- not only the 22 whose own ``_missing_`` happens to call it. + It is part of the shared template regardless, so every registry carries + it whether or not its own ``_missing_`` uses it yet (the other 83 keep + their own bespoke ``process()``-driven ``_missing_``, out of tier 1's + scope), and this pins that the helper itself behaves identically on all + of them. + + """ + + def setUp(self) -> None: + snapshot = snapshot_modules(ISOLATED_PREFIXES) + purge_modules(['pcapkit']) + self.addCleanup(restore_modules, snapshot, ISOLATED_PREFIXES) + + def test_builds_an_absent_member_on_every_registry(self) -> None: + value = 0x6E7A0002 # arbitrary, distinct from the register() sweep's + name = 'PyPCAPKit_775_unregistered' + + for module_name, class_name in ALL_105_REGISTRIES: + with self.subTest(registry=class_name): + cls = getattr(importlib.import_module(module_name), class_name) + + before = len(cls.__members__) + member = cls._unregistered_member(value, name) # type: ignore[attr-defined] + after = len(cls.__members__) + + self.assertIsInstance(member, cls) + self.assertEqual(member.value, value) + self.assertEqual(member.name, name) + self.assertEqual(before, after) + self.assertNotIn(value, cls._value2member_map_) # type: ignore[attr-defined] + self.assertNotIn(name, cls.__members__) + + +class UnregisteredMemberNameIsBareTests(unittest.TestCase): + """#775's Q1 follow-up, the maintainer's ruling verbatim: *"Q1 - bare it + is."* Asked whether the non-minting path should honour the registry's + own ``unassigned``/``reserved`` name directly or keep appending the + numeric value, he chose the bare name -- safe precisely because a + pseudo-member built by :meth:`_unregistered_member` never enters + ``__members__``/``_member_map_``/``_value2member_map_``, so two + same-named pseudo-members (e.g. ``Chunk._unregistered_member(20, + 'Unassigned')`` and ``(70, 'Unassigned')``) cannot collide the way two + *minted* ``extend_enum`` members with the same name would. + + This walks every generated :mod:`pcapkit.const` module by AST -- rather + than pinning one example -- and asserts that every + ``cls._unregistered_member(...)`` call site passes a plain string + literal with no ``%`` formatting. It therefore covers all 49 call sites + tier 1's follow-up touched, and any added by a later regeneration, + without caring which registry they belong to. A call site still using + ``extend_enum(...)`` -- the ~92 registries that still mint, such as + :mod:`pcapkit.const.mh` or :class:`~pcapkit.const.pcapng.block_type. + BlockType` -- is out of scope and untouched by this sweep, since a + minted name still needs its numeric suffix to avoid a genuine + ``__members__`` collision. + + """ + + def test_every_unregistered_member_call_passes_a_bare_name(self) -> None: + repo_root = pathlib.Path(__file__).resolve().parents[2] + const_root = repo_root / 'pcapkit' / 'const' + self.assertTrue(const_root.is_dir(), f'{const_root} is not a directory') + + offenders = [] # type: list[str] + call_count = 0 + + for path in sorted(const_root.rglob('*.py')): + source = path.read_text() + tree = ast.parse(source, filename=str(path)) + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + func = node.func + if not (isinstance(func, ast.Attribute) and func.attr == '_unregistered_member'): + continue + + call_count += 1 + args = node.args + name_arg = args[1] if len(args) > 1 else None + is_bare_literal = ( + isinstance(name_arg, ast.Constant) + and isinstance(name_arg.value, str) + and '%' not in name_arg.value + ) + if not is_bare_literal: + segment = ast.get_source_segment(source, node) + offenders.append(f'{path.relative_to(repo_root)}:{node.lineno}: {segment}') + + # Sanity: the sweep itself must actually be exercising something -- + # tier 1's follow-up touched exactly 49 call sites across 21 files. + self.assertGreaterEqual(call_count, 49, + f'expected at least 49 _unregistered_member call sites, found {call_count}') + self.assertEqual(offenders, [], + 'found _unregistered_member call(s) with a non-bare name:\n' + '\n'.join(offenders)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/protocols/internet/test_ipv6_extension_unit.py b/tests/protocols/internet/test_ipv6_extension_unit.py index c9fa6a421e..bc9aad3f0b 100644 --- a/tests/protocols/internet/test_ipv6_extension_unit.py +++ b/tests/protocols/internet/test_ipv6_extension_unit.py @@ -1307,6 +1307,17 @@ def assert_bad(reader, option_schema) -> None: bad_smf = schema.SMFHashBasedDPDOption(type=Option.SMF_DPD, len=3, hav=b'\x81\x02\x03') object.__setattr__(bad_smf, 'mode', 2) assert_bad(proto._read_opt_smf_dpd, bad_smf) + # #775 tier 1: an unresolvable TaggerID name now raises KeyError out + # of Enum_TaggerID.get() instead of minting a member -- this reader + # must turn that into its own ProtocolError, not let the KeyError + # escape. Mirrors the SeedID 'Bogus_Seed_For_Test' case below. + bad_tagger_id = schema.SMFIdentificationBasedDPDOption( + type=Option.SMF_DPD, len=3, + info={'mode': 0, 'type': 'Bogus_Tagger_For_Test', 'len': 0}, + tid=None, id=b'', + ) + object.__setattr__(bad_tagger_id, 'mode', SMFDPDMode.I_DPD) + assert_bad(proto._read_opt_smf_dpd, bad_tagger_id) assert_bad(proto._read_opt_pdm, schema.PDMOption( type=Option.PDM, len=9, scaledtlr=1, scaledtls=2, psntp=3, psnlr=4, deltatlr=5, deltatls=6, diff --git a/tests/protocols/transport/test_sctp_unit.py b/tests/protocols/transport/test_sctp_unit.py index ef7b7bfc43..9150416dbe 100644 --- a/tests/protocols/transport/test_sctp_unit.py +++ b/tests/protocols/transport/test_sctp_unit.py @@ -973,7 +973,7 @@ def test_unregistered_ppid_does_not_mutate_the_class_registry(self) -> None: # 4243 is the first code in the trailing "Unassigned" block of the IANA # registry, i.e. a PPID that is well-formed but cannot be registered. UNREGISTERED = 4243 - self.assertEqual(PayloadProtocolIdentifier(UNREGISTERED).name, 'Unassigned_4243') + self.assertEqual(PayloadProtocolIdentifier(UNREGISTERED).name, 'Unassigned') raw = self._build([(Chunk.Payload_Data, dict(I=True, U=True, B=True, E=True, tsn=1, @@ -993,7 +993,7 @@ def test_unregistered_ppid_does_not_mutate_the_class_registry(self) -> None: self.assertEqual(bytes(first.payload), b'unknown-pdu') self.assertEqual(int(first.ppid), UNREGISTERED) self.assertEqual(first.payload.info.protocol, UNREGISTERED) - self.assertEqual(str(first.protochain), 'SCTP:Unassigned_4243') + self.assertEqual(str(first.protochain), 'SCTP:Unassigned') # (b) The class-level registry gained nothing: no ``None`` key, and # no key for the unregistered PPID either. @@ -1006,7 +1006,7 @@ def test_unregistered_ppid_does_not_mutate_the_class_registry(self) -> None: second = self._packet(raw) self.assertIsInstance(second.payload, Raw) self.assertEqual(bytes(second.payload), b'unknown-pdu') - self.assertEqual(str(second.protochain), 'SCTP:Unassigned_4243') + self.assertEqual(str(second.protochain), 'SCTP:Unassigned') self.assertEqual(set(SCTP.__proto__), before) # And the PPID is still registrable without a bogus overwrite diff --git a/tests/toolkit/test_pyshark_unit.py b/tests/toolkit/test_pyshark_unit.py index 5338768aa3..9ce7f03043 100644 --- a/tests/toolkit/test_pyshark_unit.py +++ b/tests/toolkit/test_pyshark_unit.py @@ -1,7 +1,6 @@ from __future__ import annotations import importlib.util -from ipaddress import ip_address import unittest from tests._support import purge_modules @@ -20,11 +19,24 @@ def __init__(self, layer_name: str, **fields: object) -> None: class FakePySharkPacket: def __init__(self, *, ipv6: bool = False, tcp: bool = True, - ip: bool = True) -> None: + ip: bool = True, ether: bool = True, + link_layer: str = 'eth') -> None: self.number = '12' self.frame_info = FakeLayer('frame', time_epoch=70.5, cap_len='54') self.layers = [ - FakeLayer('ethernet', src='aa:aa:aa:aa:aa:aa'), + # Real PyShark names a layer from the PDML ```` + # attribute, which is Wireshark's protocol *filter* name -- + # ``eth``, not ``ethernet`` -- so the fake has to match that or it + # is not exercising what the real engine hands ``tcp_traceflow``. + # That name is not a LinkType *member* name either, but + # ``pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE`` now + # translates the handful of filter names it can justify (``eth``, + # ``tr``) before falling back to ``LinkType.get()`` -- see that + # table's own comment for the evidence and for what was + # deliberately left out. ``link_layer`` lets a test pick any + # root-layer name, including one outside that table, without + # duplicating this whole fake. + FakeLayer(link_layer, src='aa:aa:aa:aa:aa:aa'), FakeLayer('ip', src='192.0.2.1', dst='198.51.100.1'), # ``flags_reset`` is PyShark's spelling of Wireshark's # ``tcp.flags.reset``, the field the RST flag comes from. ``seq`` and @@ -47,6 +59,16 @@ def __init__(self, *, ipv6: bool = False, tcp: bool = True, self.tcp = self.layers[2] else: self.layers = self.layers[:2] + if not ether: + # #775 tier 1 regression coverage: drop the Ethernet layer so + # ``layers[0]`` -- what ``tcp_traceflow`` feeds the lookup -- is the + # IP layer, whose ``layer_name`` is ``'ip'``, a name that is neither + # in ``FILTER_NAME_TO_LINKTYPE`` nor a LinkType member. Note this is + # a synthetic shape rather than a real capture's: a genuine raw-IP + # capture roots at ``raw``, not ``ip`` (see that table's comment). + # What is under test is the unknown-name path, which ``'ip'`` + # exercises regardless of how a capture would reach it. + self.layers = self.layers[1:] def __contains__(self, name: str) -> bool: return name in self._contains @@ -63,35 +85,148 @@ def test_packet2dict_nests_frame_and_layers(self) -> None: packet = FakePySharkPacket() converted = toolkit.packet2dict(packet) self.assertEqual(converted['time_epoch'], 70.5) - self.assertEqual(converted['ETHERNET']['src'], 'aa:aa:aa:aa:aa:aa') - self.assertEqual(converted['ETHERNET']['IP']['src'], '192.0.2.1') - self.assertEqual(converted['ETHERNET']['IP']['TCP']['srcport'], '1234') + self.assertEqual(converted['ETH']['src'], 'aa:aa:aa:aa:aa:aa') + self.assertEqual(converted['ETH']['IP']['src'], '192.0.2.1') + self.assertEqual(converted['ETH']['IP']['TCP']['srcport'], '1234') + + def test_tcp_traceflow_negative_paths(self) -> None: + """The ``ip=False``/``tcp=False`` short-circuits return ``None`` + before ``tcp_traceflow`` ever resolves a link type, so they are + unaffected by #775 tier 1 dropping the ``LinkType.NULL`` default + (see ``test_tcp_traceflow_raises_for_an_ip_rooted_packet`` and the + ``FILTER_NAME_TO_LINKTYPE``-resolving tests below for that). + + """ + from pcapkit.toolkit import pyshark as toolkit + + self.assertIsNone(toolkit.tcp_traceflow(FakePySharkPacket(ip=False))) + self.assertIsNone(toolkit.tcp_traceflow(FakePySharkPacket(tcp=False))) + + def test_tcp_traceflow_resolves_ethernet_via_filter_name_mapping(self) -> None: + """PyShark's PDML filter name for the outermost layer of an ordinary + Ethernet capture is ``eth``, not ``ETHERNET`` -- not a LinkType + member name on its own. Per the ruling on #838, + ``pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE`` now translates + that filter name to :attr:`LinkType.ETHERNET` before + :func:`tcp_traceflow` ever reaches ``Enum_LinkType.get()``, so this + no longer raises the way it did prior to that table landing. + + """ + from pcapkit.const.reg.linktype import LinkType + from pcapkit.toolkit import pyshark as toolkit - def test_tcp_traceflow_ipv4_ipv6_and_negative_paths(self) -> None: + packet = FakePySharkPacket() + self.assertEqual(packet.layers[0].layer_name, 'eth') + + data = toolkit.tcp_traceflow(packet) + self.assertIsNotNone(data) + self.assertEqual(data.protocol, LinkType.ETHERNET) + + # the IPv6-rooted capture hits the very same lookup, since + # ``layers[0]`` is still the Ethernet layer either way + data_v6 = toolkit.tcp_traceflow(FakePySharkPacket(ipv6=True)) + self.assertIsNotNone(data_v6) + self.assertEqual(data_v6.protocol, LinkType.ETHERNET) + + def test_tcp_traceflow_resolves_token_ring_via_filter_name_mapping(self) -> None: + """Wireshark's Token Ring dissector -- the ``DLT_IEEE802`` root layer + -- files its PDML node under the filter name ``tr``, per + ``packet-tr.c``'s ``proto_register_protocol("Token-Ring", + "Token-Ring", "tr")`` (verified against the Wireshark source, no + ``tshark``/PyShark binary being available on this host). That name + is also not a LinkType member on its own, so it needs the same + ``FILTER_NAME_TO_LINKTYPE`` entry as ``eth`` to resolve to + :attr:`LinkType.IEEE802_5`. + + """ from pcapkit.const.reg.linktype import LinkType from pcapkit.toolkit import pyshark as toolkit - flow = toolkit.tcp_traceflow(FakePySharkPacket()) - self.assertIsNotNone(flow) - assert flow is not None - self.assertEqual(flow.protocol, LinkType.ETHERNET) - self.assertEqual(flow.index, 12) - self.assertEqual(flow.src, ip_address('192.0.2.1')) - self.assertEqual(flow.dst, ip_address('198.51.100.1')) - self.assertEqual(flow.srcport, 1234) - self.assertEqual(flow.dstport, 80) - self.assertTrue(flow.syn) - self.assertFalse(flow.fin) - self.assertEqual(flow.timestamp, 70.5) - - v6_flow = toolkit.tcp_traceflow(FakePySharkPacket(ipv6=True)) - self.assertIsNotNone(v6_flow) - assert v6_flow is not None - self.assertEqual(v6_flow.src, ip_address('2001:db8::1')) - self.assertEqual(v6_flow.dst, ip_address('2001:db8::2')) + packet = FakePySharkPacket(link_layer='tr') + self.assertEqual(packet.layers[0].layer_name, 'tr') - self.assertIsNone(toolkit.tcp_traceflow(FakePySharkPacket(ip=False))) - self.assertIsNone(toolkit.tcp_traceflow(FakePySharkPacket(tcp=False))) + data = toolkit.tcp_traceflow(packet) + self.assertIsNotNone(data) + self.assertEqual(data.protocol, LinkType.IEEE802_5) + + def test_tcp_traceflow_raises_for_an_ip_rooted_packet(self) -> None: + """``'ip'`` is absent from ``FILTER_NAME_TO_LINKTYPE`` and is not a + LinkType member name either, so ``Enum_LinkType.get()`` misses too and + this raises, exactly as it did before the mapping table landed. + + The double feeds ``'ip'`` as ``layers[0]`` to exercise that path. A real + raw-IP capture would root at ``raw`` instead -- measured, and recorded in + the table's own comment -- so this is the unknown-name guard rather than + a reproduction of any particular capture shape. + + """ + from pcapkit.const.reg.linktype import LinkType + from pcapkit.toolkit import pyshark as toolkit + from pcapkit.utilities.exceptions import MissingKeyError + + packet = FakePySharkPacket(ether=False) + self.assertEqual(packet.layers[0].layer_name, 'ip') + + before = len(LinkType.__members__) + with self.assertRaises(MissingKeyError) as ctx: + toolkit.tcp_traceflow(packet) + after = len(LinkType.__members__) + + self.assertEqual(ctx.exception.args[0], 'IP') + self.assertEqual(before, after) + + def test_tcp_traceflow_raises_for_a_genuinely_unknown_name(self) -> None: + """A link-layer filter name that is neither in + ``FILTER_NAME_TO_LINKTYPE`` nor a LinkType member name under any + case must still raise :exc:`MissingKeyError` rather than be papered + over with a stand-in DLT -- the maintainer's ruling from #775 tier 1 + that this mapping does not relax. + + """ + from pcapkit.const.reg.linktype import LinkType + from pcapkit.toolkit import pyshark as toolkit + from pcapkit.utilities.exceptions import MissingKeyError + + packet = FakePySharkPacket(link_layer='wobegon') + self.assertEqual(packet.layers[0].layer_name, 'wobegon') + + before = len(LinkType.__members__) + with self.assertRaises(MissingKeyError) as ctx: + toolkit.tcp_traceflow(packet) + after = len(LinkType.__members__) + + self.assertEqual(ctx.exception.args[0], 'WOBEGON') + self.assertEqual(before, after) # the miss must not grow the registry + + def test_tcp_traceflow_end_to_end_for_an_ethernet_capture(self) -> None: + """With the link-layer name resolved, the rest of + :func:`tcp_traceflow`'s ``TF_TCP_Packet`` construction is unaffected + by this change -- check every field it fills in from the fake, not + just ``protocol``, so a regression there is not masked by the two + tests above only checking the new mapping. + + """ + from pcapkit.const.reg.linktype import LinkType + from pcapkit.toolkit import pyshark as toolkit + + packet = FakePySharkPacket() + data = toolkit.tcp_traceflow(packet) + + self.assertIsNotNone(data) + self.assertEqual(data.protocol, LinkType.ETHERNET) + self.assertEqual(data.index, 12) + self.assertEqual(str(data.src), '192.0.2.1') + self.assertEqual(str(data.dst), '198.51.100.1') + self.assertEqual(data.srcport, 1234) + self.assertEqual(data.dstport, 80) + self.assertTrue(data.syn) + self.assertFalse(data.fin) + self.assertFalse(data.rst) + self.assertEqual(data.seq, 101) + self.assertEqual(data.ack, 202) + self.assertEqual(data.timestamp, 70.5) + self.assertEqual(data.header, b'') + self.assertEqual(data.payload, bytearray()) if __name__ == '__main__': diff --git a/tests/toolkit/test_scapy_unit.py b/tests/toolkit/test_scapy_unit.py index 4cc68af6b2..a82a35db7d 100644 --- a/tests/toolkit/test_scapy_unit.py +++ b/tests/toolkit/test_scapy_unit.py @@ -87,6 +87,21 @@ def _make_ether_raw(self): return Ether(bytes(Ether(**self._ether_kwargs()) / Raw(b'raw'))) + def _make_ip_rooted_tcp_packet(self): + # No Ether layer, so ``packet.name`` (what #775's tier 1 fix at + # scapy.py:324 feeds ``Enum_LinkType.get()``) is ``'IP'`` -- not a + # LinkType member name, unlike the Ether-rooted packets above whose + # name is ``'Ethernet'``. + from scapy.layers.inet import IP, TCP + from scapy.packet import Raw + + packet = ( + IP(src='192.0.2.1', dst='198.51.100.1', id=123) / + TCP(sport=1234, dport=80, seq=10, ack=5, flags='SA') / + Raw(b'data') + ) + return IP(bytes(packet)) + def test_import_without_scapy_sets_none_and_warns(self) -> None: purge_modules(['pcapkit.toolkit.scapy']) real_import = builtins.__import__ @@ -280,6 +295,38 @@ def test_tcp_reassembly_and_traceflow(self) -> None: self.assertIsNone(toolkit.tcp_reassembly(self._make_ether_raw(), count=1)) self.assertIsNone(toolkit.tcp_traceflow(self._make_ipv4_fragment(), count=1)) + def test_tcp_traceflow_raises_for_an_ip_rooted_packet(self) -> None: + """An IP-rooted (Ether-less) packet's ``.name`` is ``'IP'``, which is + not a LinkType member name. Since #775 tier 1, ``Enum_LinkType.get()`` + with no default raises on an unresolvable name instead of minting + one -- and per the ruling on #838, NULL and RAW are genuine DLTs with + their own handler protocol classes, not stand-ins for "unknown link + type", so this call site no longer papers over the miss with a + ``LinkType.NULL`` default. This pins that the miss now raises + :exc:`~pcapkit.utilities.exceptions.MissingKeyError` naming the + offending value, and that the registry is still not grown by it. + + Contrast an Ether-rooted packet, exercised in + ``test_tcp_reassembly_and_traceflow`` above: ``(Ether()/IP()/TCP()). + name`` is ``'Ethernet'``, which *does* resolve to + ``LinkType.ETHERNET``, so only the IP-rooted path raises here. + + """ + from pcapkit.const.reg.linktype import LinkType + from pcapkit.toolkit import scapy as toolkit + from pcapkit.utilities.exceptions import MissingKeyError + + packet = self._make_ip_rooted_tcp_packet() + self.assertEqual(packet.name, 'IP') + + before = len(LinkType.__members__) + with self.assertRaises(MissingKeyError) as ctx: + toolkit.tcp_traceflow(packet, count=1) + after = len(LinkType.__members__) + + self.assertEqual(ctx.exception.args[0], 'IP') + self.assertEqual(before, after) + if __name__ == '__main__': unittest.main()