diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 39485b6786..2ed088366b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,19 +5,20 @@ name: "Lint" # that rewrites the generated constants, not as a check. # # Every step but actionlint is advisory (`continue-on-error: true`), because none -# is clean and a job red from day one teaches everyone to ignore red. Promote a -# step by deleting its `continue-on-error` once it is clean, and move it below -# the advisory steps, as a failed step skips every step after it. Counts are in -# the run summary, not here: written-down counts drift (#753). +# was clean when added, and a job red from day one teaches everyone to ignore +# red. Promote a step by deleting its `continue-on-error` once it is clean, and +# move it below the advisory steps, as a failed step skips every step after it. +# Counts are in the run summary, not here: written-down counts drift (#753). # # pylint's `R0401` cyclic-import depends on module-processing order, so runs on # one tree disagree on it; a change in the R class alone is not evidence. # # Promotion order, cheapest first: -# * bandit -- mostly B104 on `'0.0.0.0'` default arguments in MH/MIP option -# builders, which bind no socket; triage the rest with `# nosec`. -# * mypy -- part of its output is `unused-ignore`, risk-free to delete. -# * vermin -- blocked on a decision, not on cleanup: see its step. +# * bandit -- clean since #2145: each finding was a false positive and carries +# a justified `# nosec`. Promotable. +# * vermin -- clean since #2145: meets vermin.ini's 3.8 target. Promotable. +# * mypy -- #2145 cleared the `unused-ignore`s and the pyshark link-type +# tables; the rest is real typing work. # * pylint -- not realistically promotable: `unused-argument`, # `redefined-builtin` and `super-init-not-called` are inherent to the # schema DSL, and its metaprogramming yields `unsubscriptable-object` and @@ -127,11 +128,10 @@ jobs: # `vermin-ci`, not `vermin`: that target redirects the report into temp/, # out of this log. Same flags, via $(VERMIN_FLAGS). # - # Blocking needs a decision, not a cleanup. vermin.ini's `targets = 3.8` - # matches `requires-python` (#2051), but vermin reports 3.11: - # `--no-parse-comments` ignores the `# novermin` markers on compat.py's - # version-gated imports, and evaluated annotations put some - # `pcapkit.foundation` modules at 3.9. + # vermin.ini's `targets = 3.8` matches `requires-python` (#2051). Comments + # are parsed (#2145), so the `# novermin` markers on compat.py's + # version-gated imports count, and builtin generics in evaluated + # annotations are spelt with `typing` aliases. - name: vermin id: vermin continue-on-error: true diff --git a/Makefile b/Makefile index 8be2c038a5..d24132fe5c 100644 --- a/Makefile +++ b/Makefile @@ -149,8 +149,11 @@ RUN ?= pipenv run # flags in each. Note the `pcapkit` before the flags as well as after: that is how this # recipe has always read, and vermin de-duplicates the paths (it reports the # same number of files analyzed either way), so it is preserved verbatim rather -# than tidied. -VERMIN_FLAGS = --backport argparse --backport enum --backport importlib --backport ipaddress --backport typing --backport typing_extensions --no-parse-comments --eval-annotations -vv +# than tidied. `--no-parse-comments` is gone (#2145): it came with the first +# vermin recipe, with no recorded reason, and made vermin ignore the +# `# novermin` markers on compat.py's version-gated imports. The cost of parsing +# them is that a marker silences its whole line, so keep them on guarded lines. +VERMIN_FLAGS = --backport argparse --backport enum --backport importlib --backport ipaddress --backport typing --backport typing_extensions --eval-annotations -vv # `pylint.extensions.emptystring` no longer exists (#767): the check it provided, # `compare-to-empty-string`, was folded into the core `refactoring` checker at # pylint 3.0 as `use-implicit-booleaness-not-comparison-to-string` (C1804), with @@ -180,7 +183,7 @@ vermin: # that would have opened it never reached -- useless as a log. # This one writes to stdout and lets vermin's failure propagate, which is what # `targets = 3.8` in vermin.ini is for: vermin exits non-zero when the target is -# not met, and it detects 3.11. +# not met. vermin-ci: $(RUN) vermin pcapkit $(VERMIN_FLAGS) pcapkit diff --git a/pcapkit/const/ftp/command.py b/pcapkit/const/ftp/command.py index 14bb049fd6..19627efa94 100644 --- a/pcapkit/const/ftp/command.py +++ b/pcapkit/const/ftp/command.py @@ -668,5 +668,5 @@ def _missing_(cls, value: 'str') -> 'Command': raise ValueError(f'{value!r} is not a valid {cls.__name__}') name = value.upper() if name in cls._member_map_: - return cls._member_map_[name] # type: ignore[return-value] + return cls._member_map_[name] return cls._unregistered_member(value, name) diff --git a/pcapkit/const/http/method.py b/pcapkit/const/http/method.py index 56a07fadb4..ed392ff116 100644 --- a/pcapkit/const/http/method.py +++ b/pcapkit/const/http/method.py @@ -329,5 +329,5 @@ def _missing_(cls, value: 'str') -> 'Method': raise ValueError(f'{value!r} is not a valid {cls.__name__}') name = value.upper() if name in cls._member_map_: - return cls._member_map_[name] # type: ignore[return-value] + return cls._member_map_[name] return cls._unregistered_member(value, name) diff --git a/pcapkit/corekit/fields/misc.py b/pcapkit/corekit/fields/misc.py index 84c85c13ca..b92a5c0e31 100644 --- a/pcapkit/corekit/fields/misc.py +++ b/pcapkit/corekit/fields/misc.py @@ -255,7 +255,7 @@ def optional(self) -> 'bool': def protocol(self) -> 'Type[_TP]': """Payload protocol.""" if self._protocol is None: - from pcapkit.protocols.misc.raw import Raw # type: ignore[unreachable] # pylint: disable=import-outside-toplevel # isort:skip + from pcapkit.protocols.misc.raw import Raw # pylint: disable=import-outside-toplevel # isort:skip return Raw return self._protocol @@ -391,7 +391,7 @@ def unpack(self, buffer: 'bytes | IO[bytes]', packet: 'dict[str, Any]') -> '_TP' """ if self._protocol is None: - if isinstance(buffer, bytes): # type: ignore[unreachable] + if isinstance(buffer, bytes): return cast('_TP', buffer) return cast('_TP', buffer.read()) @@ -458,7 +458,7 @@ def field(self) -> 'FieldBase[_TC]': """Field instance.""" return self._field - def __init__(self, selector: 'Callable[[dict[str, Any]], FieldBase[_TC]]' = lambda _: NoValueField()) -> 'None': # type: ignore[assignment,return-value] + def __init__(self, selector: 'Callable[[dict[str, Any]], FieldBase[_TC]]' = lambda _: NoValueField()) -> 'None': #self._name = '' self._field = cast('FieldBase[_TC]', NoValueField()) self._selector = selector diff --git a/pcapkit/corekit/infoclass.py b/pcapkit/corekit/infoclass.py index 8b36ba90d3..8fdf3ed89c 100644 --- a/pcapkit/corekit/infoclass.py +++ b/pcapkit/corekit/infoclass.py @@ -1010,7 +1010,7 @@ class MultiInfo(_MultiInfo[KT, VT], MultiDict[KT, VT]): __hash__ = None # type: ignore[assignment] __repr__ = MultiDict.__repr__ __str__ = MultiDict.__str__ - __copy__ = MultiDict.__copy__ # type: ignore[assignment] + __copy__ = MultiDict.__copy__ keys = MultiDict.keys values = MultiDict.values items = MultiDict.items # type: ignore[assignment] @@ -1104,7 +1104,7 @@ class OrderedMultiInfo(_MultiInfo[KT, VT], OrderedMultiDict[KT, VT]): __hash__ = None __repr__ = OrderedMultiDict.__repr__ __str__ = OrderedMultiDict.__str__ - __copy__ = OrderedMultiDict.__copy__ # type: ignore[assignment] + __copy__ = OrderedMultiDict.__copy__ keys = OrderedMultiDict.keys values = OrderedMultiDict.values items = OrderedMultiDict.items # type: ignore[assignment] @@ -1301,7 +1301,7 @@ def _nested_types(cls: 'Type[Info]') -> 'dict[str, Type[Info]]': try: hint = typing.get_type_hints(types.SimpleNamespace(__annotations__={key: ann}), globalns, localns)[key] - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # nosec: B112 # unresolvable: not nested continue info_cls = _as_info_type(hint) diff --git a/pcapkit/corekit/multidict.py b/pcapkit/corekit/multidict.py index 7d27f8f6dc..8269043933 100644 --- a/pcapkit/corekit/multidict.py +++ b/pcapkit/corekit/multidict.py @@ -192,7 +192,7 @@ def __getitem__(self, key: '_KT') -> '_VT': if key in self: lst = super().__getitem__(key) if len(lst) > 0: - return lst[0] # type: ignore[no-any-return] + return lst[0] raise MissingKeyError(key, quiet=True) def __setitem__(self, key: '_KT', value: '_VT') -> 'None': @@ -216,7 +216,7 @@ def get(self, key: '_KT') -> '_VT | _T': ... @overload def get(self, key: '_KT', default: '_VT | _T' = ...) -> '_VT | _T': ... - def get(self, key: '_KT', default: '_VT | _T' = None) -> '_VT | _T': # type: ignore[assignment,misc] + def get(self, key: '_KT', default: '_VT | _T' = None) -> '_VT | _T': # type: ignore[assignment] try: return self[key] except MissingKeyError: @@ -291,7 +291,7 @@ def setlistdefault(self, key: '_KT', default_list: 'Optional[Iterable[_VT]]' = N if key not in self: super().__setitem__(key, list(default_list or ())) - return super().__getitem__(key) # type: ignore[no-any-return] + return super().__getitem__(key) def items(self, multi: 'bool' = False) -> 'Iterable[tuple[_KT, _VT]]': # type: ignore[override] """Return an iterator of ``(key, value)`` pairs. @@ -390,7 +390,7 @@ def __or__(self, other: 'Mapping[_KT, _VT | list[_VT] | tuple[_VT, ...] | set[_V rv.update(other) return rv - def __ior__(self, other: 'Mapping[_KT, _VT | list[_VT] | tuple[_VT, ...] | set[_VT]] | Iterable[tuple[_KT, _VT]]') -> 'Self': # type: ignore[override,misc] # pylint: disable=line-too-long + def __ior__(self, other: 'Mapping[_KT, _VT | list[_VT] | tuple[_VT, ...] | set[_VT]] | Iterable[tuple[_KT, _VT]]') -> 'Self': # type: ignore[override] # pylint: disable=line-too-long if not isinstance(other, (cabc.Mapping, cabc.Iterable)): return NotImplemented @@ -404,7 +404,7 @@ def pop(self, key: '_KT', default: '_VT') -> '_VT': ... # pylint: disable=argum @overload def pop(self, key: '_KT', default: '_T') -> '_VT | _T': ... # pylint: disable=arguments-differ - def pop(self, key: '_KT', default: '_VT | _T' = _missing) -> '_VT | _T': # type: ignore[assignment,misc] + def pop(self, key: '_KT', default: '_VT | _T' = _missing) -> '_VT | _T': # type: ignore[assignment] """Pop the first item for a list on the dict. Afterwards the key is removed from the dict, so additional values are discarded: @@ -451,7 +451,7 @@ def poplist(self, key: '_KT') -> 'list[_VT]': """Pop the list for a key from the dict. If the key is not in the dict an empty list is returned. """ - return super().pop(key, []) # type: ignore[no-any-return] + return super().pop(key, []) def popitemlist(self) -> 'tuple[_KT, list[_VT]]': """Pop a ``(key, list)`` tuple from the dict.""" @@ -551,7 +551,7 @@ def __setstate__(self, values: 'Any') -> 'None': def __getitem__(self, key: '_KT') -> '_VT': if key in self: - return dict.__getitem__(self, key)[0].value # type: ignore[no-any-return] + return dict.__getitem__(self, key)[0].value raise MissingKeyError(key, quiet=True) def __setitem__(self, key: '_KT', value: '_VT') -> 'None': @@ -598,7 +598,7 @@ def listvalues(self) -> 'Iterable[list[_VT]]': yield values def add(self, key: '_KT', value: '_VT') -> 'None': - dict.setdefault(self, key, []).append(_omd_bucket(self, key, value)) # type: ignore[misc] + dict.setdefault(self, key, []).append(_omd_bucket(self, key, value)) def getlist(self, key: '_KT') -> 'list[_VT]': rv: 'list[_omd_bucket[_KT, _VT]]' @@ -634,7 +634,7 @@ def pop(self, key: '_KT', default: '_VT') -> '_VT': ... # pylint: disable=argum @overload def pop(self, key: '_KT', default: '_T') -> '_VT | _T': ... # pylint: disable=arguments-differ - def pop(self, key: '_KT', default: '_VT | _T' = _missing) -> '_VT | _T': # type: ignore[assignment,misc] + def pop(self, key: '_KT', default: '_VT | _T' = _missing) -> '_VT | _T': # type: ignore[assignment] buckets: 'list[_omd_bucket[_KT, _VT]]' try: diff --git a/pcapkit/dumpkit/common.py b/pcapkit/dumpkit/common.py index adbf92a705..414bb5ff75 100644 --- a/pcapkit/dumpkit/common.py +++ b/pcapkit/dumpkit/common.py @@ -20,7 +20,7 @@ import re import string import types -import xml.sax.saxutils +import xml.sax.saxutils # nosec: B406 # escape() only, nothing parsed from typing import TYPE_CHECKING import aenum diff --git a/pcapkit/foundation/engines/_pcap_backend.py b/pcapkit/foundation/engines/_pcap_backend.py index 7b170370f7..7c6db14686 100644 --- a/pcapkit/foundation/engines/_pcap_backend.py +++ b/pcapkit/foundation/engines/_pcap_backend.py @@ -150,7 +150,7 @@ def installed_distributions() -> 'tuple[str, ...]': try: if next(iter(importlib.metadata.distributions(name=name)), None) is None: continue - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # nosec: B112 # see below # A corrupt or unreadable ``dist-info`` is not a reason to fail an # extraction. Detection is a courtesy; treat "cannot tell" as "not # installed" and let the import attempt be the authority. diff --git a/pcapkit/foundation/engines/pcap_ct.py b/pcapkit/foundation/engines/pcap_ct.py index 4e6e4f464b..39318efac8 100644 --- a/pcapkit/foundation/engines/pcap_ct.py +++ b/pcapkit/foundation/engines/pcap_ct.py @@ -22,7 +22,7 @@ __all__ = ['PCAP_CT'] if TYPE_CHECKING: - from typing import Iterator, Optional, Protocol, Type + from typing import Iterator, Optional, Protocol, Tuple, Type from pcap import pcap as Handle @@ -39,7 +39,7 @@ def pcap(self) -> 'Type[Handle]': ... #: so that it is not mistaken for #: :class:`pcapkit.protocols.misc.pcap.frame.Frame`, which is what the #: built-in engines return. - RawFrame = tuple[float, bytes] + RawFrame = Tuple[float, bytes] class PCAP_CT(EngineBase['RawFrame']): diff --git a/pcapkit/foundation/engines/pypcap.py b/pcapkit/foundation/engines/pypcap.py index 924079bcb7..ba766d6de0 100644 --- a/pcapkit/foundation/engines/pypcap.py +++ b/pcapkit/foundation/engines/pypcap.py @@ -28,7 +28,7 @@ __all__ = ['PyPCAP'] if TYPE_CHECKING: - from typing import Iterator, Optional, Protocol, Type + from typing import Iterator, Optional, Protocol, Tuple, Type from pcap import pcap as Handle @@ -44,7 +44,7 @@ def pcap(self) -> 'Type[Handle]': ... #: iteration yields. Deliberately *not* named ``Frame``, so that it is not #: mistaken for :class:`pcapkit.protocols.misc.pcap.frame.Frame`, which is #: what the built-in engines return. - RawFrame = tuple[float, bytes] + RawFrame = Tuple[float, bytes] class PyPCAP(EngineBase['RawFrame']): diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index e1d973077d..7d3be41272 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -49,7 +49,8 @@ if TYPE_CHECKING: from io import BufferedReader from types import ModuleType, TracebackType - from typing import IO, Any, Callable, DefaultDict, Iterable, Mapping, Optional, Type, Union + from typing import (IO, Any, Callable, DefaultDict, Iterable, Mapping, Optional, Tuple, Type, + Union) from dpkt.dpkt import Packet as DPKTPacket from pcapfile.structs import pcap_packet as PCAPFilePacket @@ -84,7 +85,7 @@ # One member covers both: they read the same interface, from two independent # distributions of it. Packet = Union[Frame, PCAPNG, ScapyPacket, DPKTPacket, PySharkPacket, - PCAPFilePacket, tuple[float, bytes]] + PCAPFilePacket, Tuple[float, bytes]] Protocols = Union[str, ProtocolBase, Type[ProtocolBase]] # NOTE: every caller discards the callback's result, so it is typed ``Any``: diff --git a/pcapkit/foundation/reassembly/reassembly.py b/pcapkit/foundation/reassembly/reassembly.py index 7848c019ef..24f497166a 100644 --- a/pcapkit/foundation/reassembly/reassembly.py +++ b/pcapkit/foundation/reassembly/reassembly.py @@ -35,7 +35,7 @@ _BT = TypeVar('_BT', bound='Info') if TYPE_CHECKING: - from typing import Any, Callable, Optional, Type + from typing import Any, Callable, List, Optional, Type from typing_extensions import Self @@ -43,7 +43,7 @@ from pcapkit.corekit.module import ModuleDescriptor from pcapkit.protocols.protocol import ProtocolBase - CallbackFn = Callable[[list[_DT]], None] + CallbackFn = Callable[[List[_DT]], None] __all__ = ['Reassembly'] diff --git a/pcapkit/protocols/internet/internet.py b/pcapkit/protocols/internet/internet.py index dfc7dee394..100204055f 100644 --- a/pcapkit/protocols/internet/internet.py +++ b/pcapkit/protocols/internet/internet.py @@ -434,7 +434,7 @@ def _decode_next_layer(self, dict_: '_PT', proto: 'Optional[int]' = None, # pyl """ next_ = cast('ProtocolBase', # type: ignore[redundant-cast] self._import_next_layer(proto, length, packet=packet, version=version, - payload=payload)) # type: ignore[arg-type,misc,call-arg] + payload=payload)) # type: ignore[misc,call-arg] info, chain = next_.info, next_.protochain # make next layer protocol name diff --git a/pcapkit/protocols/internet/ipv6.py b/pcapkit/protocols/internet/ipv6.py index 38dabf7d69..60ee9a05c4 100644 --- a/pcapkit/protocols/internet/ipv6.py +++ b/pcapkit/protocols/internet/ipv6.py @@ -563,7 +563,7 @@ def _decode_next_layer(self, ipv6: 'Data_IPv6', proto: 'Optional[int]' = None, token = _frame_depth.set(depth + len(_protos)) try: next_ = self._import_next_layer(proto, packet=packet, version=6, extension=True, - payload=payload) # type: ignore[misc,call-arg,arg-type] + payload=payload) # type: ignore[misc,call-arg] finally: _frame_depth.reset(token) info = next_.info diff --git a/pcapkit/protocols/internet/ipv6_ext.py b/pcapkit/protocols/internet/ipv6_ext.py index c8f7b630d8..d32b4af410 100644 --- a/pcapkit/protocols/internet/ipv6_ext.py +++ b/pcapkit/protocols/internet/ipv6_ext.py @@ -597,7 +597,7 @@ def __post_init__(self, file: 'Optional[IO[bytes] | bytes]' = None, length: 'Opt self._extf = extension # call super __post_init__ - super().__post_init__(file, length, version=version, extension=extension, **kwargs) # type: ignore[arg-type] + super().__post_init__(file, length, version=version, extension=extension, **kwargs) def __length_hint__(self) -> 'int': """Return an estimated length for the object. diff --git a/pcapkit/protocols/internet/mh.py b/pcapkit/protocols/internet/mh.py index 16d3a95d97..74bc54eff7 100644 --- a/pcapkit/protocols/internet/mh.py +++ b/pcapkit/protocols/internet/mh.py @@ -9002,7 +9002,7 @@ def _make_opt_restart(self, type: 'Enum_Option', option: 'Optional[Data_RestartC def _make_opt_ipv4_hoa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressOption]' = None, *, prefix_length: 'int' = 32, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind request_prefix: 'bool' = False, reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressOption': @@ -9048,7 +9048,7 @@ def _make_opt_ipv4_ack(self, type: 'Enum_Option', status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long status_reversed: 'bool' = False, prefix_length: 'int' = 32, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4AddressAcknowledgementOption': """Make MH IPv4 address acknowledgement option. @@ -9124,7 +9124,7 @@ def _make_opt_nat(self, type: 'Enum_Option', option: 'Optional[Data_NATDetection ) def _make_opt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_IPv4CareofAddressOption]' = None, *, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_IPv4CareofAddressOption': """Make MH IPv4 care-of address option. @@ -9260,7 +9260,7 @@ def _make_opt_bid(self, type: 'Enum_Option', option: 'Optional[Data_BindingIdent def _make_opt_ipv4_hoa_req(self, type: 'Enum_Option', option: 'Optional[Data_IPv4HomeAddressRequestOption]' = None, *, prefix_length: 'int' = 32, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressRequestOption': """Make MH IPv4 home address request option. @@ -9298,7 +9298,7 @@ def _make_opt_ipv4_hoa_rep(self, type: 'Enum_Option', option: 'Optional[Data_IPv status_namespace: 'Optional[dict[str, int] | dict[int, str] | Type[StdlibEnum] | Type[AenumEnum]]' = None, # pylint: disable=line-too-long status_reversed: 'bool' = False, prefix_length: 'int' = 32, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'int' = 0, **kwargs: 'Any') -> 'Schema_IPv4HomeAddressReplyOption': """Make MH IPv4 home address reply option. @@ -9341,7 +9341,7 @@ def _make_opt_ipv4_hoa_rep(self, type: 'Enum_Option', option: 'Optional[Data_IPv def _make_opt_ipv4_router(self, type: 'Enum_Option', option: 'Optional[Data_IPv4DefaultRouterAddressOption]' = None, *, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind reserved: 'bytes' = b'\x00\x00', **kwargs: 'Any') -> 'Schema_IPv4DefaultRouterAddressOption': """Make MH IPv4 default-router address option. @@ -9909,7 +9909,7 @@ def _make_opt_load(self, type: 'Enum_Option', option: 'Optional[Data_LoadInforma def _make_opt_alt_ipv4_coa(self, type: 'Enum_Option', option: 'Optional[Data_AlternateIPv4CareofAddressOption]' = None, *, - address: 'bytes | str | int | IPv4Address' = '0.0.0.0', + address: 'bytes | str | int | IPv4Address' = '0.0.0.0', # nosec: B104 # not a bind **kwargs: 'Any') -> 'Schema_AlternateIPv4CareofAddressOption': """Make MH alternate IPv4 care-of address option. diff --git a/pcapkit/protocols/schema/misc/pcapng.py b/pcapkit/protocols/schema/misc/pcapng.py index b639eb4005..fd56a00da6 100644 --- a/pcapkit/protocols/schema/misc/pcapng.py +++ b/pcapkit/protocols/schema/misc/pcapng.py @@ -1068,7 +1068,7 @@ class Option(EnumSchema[Enum_OptionType]): __excluded__ = ['__enum__', '__namespace__'] #: Namespace of PCAP-NG option type numbers. - __namespace__: 'str' = None # type: ignore[assignment] + __namespace__: 'str' = None #: Mapping of PCAP-NG option type numbers to schemas. __enum__: 'DefaultDict[str, DefaultDict[Enum_OptionType, Type[Option]]]' = collections.defaultdict( lambda: Option.registry['opt'], { diff --git a/pcapkit/protocols/transport/sctp.py b/pcapkit/protocols/transport/sctp.py index 38cb3b320f..16e0952c58 100644 --- a/pcapkit/protocols/transport/sctp.py +++ b/pcapkit/protocols/transport/sctp.py @@ -704,7 +704,7 @@ def make(self, return schema @classmethod - def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]') -> 'None': # type: ignore[override] # pylint: disable=line-too-long + def register(cls, code: 'Enum_PayloadProtocolIdentifier | int', protocol: 'ModuleDescriptor[ProtocolBase] | Type[ProtocolBase]') -> 'None': # pylint: disable=line-too-long r"""Register a new protocol class for a payload protocol identifier. Notes: @@ -982,7 +982,7 @@ def _decode_next_layer(self, dict_: 'Data_SCTP', proto: 'Optional[int]' = None, """ return ProtocolBase._decode_next_layer( # pylint: disable=protected-access - self, dict_, proto, length, packet=packet) # type: ignore[arg-type,return-value] + self, dict_, proto, length, packet=packet) # type: ignore[arg-type] def _read_sctp_chunks(self) -> 'Chunks': """Read SCTP chunk list. diff --git a/pcapkit/toolkit/pypcapfile.py b/pcapkit/toolkit/pypcapfile.py index 42cab3defa..e4ca7e15e0 100644 --- a/pcapkit/toolkit/pypcapfile.py +++ b/pcapkit/toolkit/pypcapfile.py @@ -866,7 +866,7 @@ def tcp_traceflow(packet: 'Packet', *, data_link: 'Enum_LinkType', tcp = TCP(segment) hdr_len = tcp.data_offset # at least ``TCP_MIN_HEADER_LEN``, see ``_transport`` - return TF_TCP_Packet( # type: ignore[type-var] + return TF_TCP_Packet( protocol=data_link, # data link type from savefile header index=count, # frame number frame=packet2dict(packet, data_link=data_link), # extracted packet diff --git a/pcapkit/toolkit/pyshark.py b/pcapkit/toolkit/pyshark.py index 1ff73108a2..623b994b07 100644 --- a/pcapkit/toolkit/pyshark.py +++ b/pcapkit/toolkit/pyshark.py @@ -16,7 +16,7 @@ """ import decimal import ipaddress -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast from pcapkit.const.reg.linktype import LinkType as Enum_LinkType from pcapkit.foundation.traceflow.data.tcp import Packet as TF_TCP_Packet @@ -59,7 +59,7 @@ #: Each is below except ``frame.encap_type`` 32 (``editcap -T hhdlc``, DLT 121), left out because #: :class:`~pcapkit.const.reg.linktype.LinkType` has no member for 121. An encapsulation absent #: from this table raises rather than resolving to a near-miss DLT. -ENCAP_TYPE_TO_LINKTYPE = { +ENCAP_TYPE_TO_LINKTYPE = cast('dict[int, Enum_LinkType]', { 1: Enum_LinkType.ETHERNET, # ether 2: Enum_LinkType.IEEE802_5, # tr 3: Enum_LinkType.SLIP, # slip @@ -212,7 +212,7 @@ 222: Enum_LinkType.SILABS_DEBUG_CHANNEL, # silabs-dch 223: Enum_LinkType.MDB, # mdb 225: Enum_LinkType.DECT_NR, # dect_nr -} # type: dict[int, Enum_LinkType] +}) # a cast, as mypy reads aenum members as int (#2145) #: Wireshark display-filter name -> :class:`~pcapkit.const.reg.linktype.LinkType` member, used by #: :func:`tcp_traceflow` **only** when the frame layer carries no ``frame.encap_type`` field for @@ -256,7 +256,7 @@ #: ``frame.encap_type`` is the primary key; ``eth`` and ``tr`` additionally check out against #: Wireshark's own registrations (``packet-eth.c`` on ``WTAP_ENCAP_ETHERNET``, ``packet-tr.c`` on #: ``WTAP_ENCAP_TOKEN_RING``). -FILTER_NAME_TO_LINKTYPE = { +FILTER_NAME_TO_LINKTYPE = cast('dict[str, Enum_LinkType]', { 'alp': Enum_LinkType.ATSC_ALP, # alp 'ap1394': Enum_LinkType.APPLE_IP_OVER_IEEE1394, # ap1394 'atm': Enum_LinkType.SUNATM, # atm-pdus @@ -315,7 +315,7 @@ 'vsock': Enum_LinkType.VSOCK, # vsock 'wpan-nonask-phy': Enum_LinkType.IEEE802_15_4_NONASK_PHY, # wpan-nonask-phy 'xra': Enum_LinkType.DOCSIS31_XRA31, # docsis31_xra31 -} # type: dict[str, Enum_LinkType] +}) # a cast, as mypy reads aenum members as int (#2145) #: PyShark's text for a boolean field -> its value. tshark 4.x spells one ``'True'``/``'False'`` #: (measured for ``tcp.flags_syn``, ``tcp.flags_fin`` and ``tcp.flags_reset`` with PyShark 0.6 diff --git a/pcapkit/utilities/compat.py b/pcapkit/utilities/compat.py index bae7f1046f..454af876f3 100644 --- a/pcapkit/utilities/compat.py +++ b/pcapkit/utilities/compat.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Iterator, Optional + from typing import Any, Iterator, Optional, Type __all__ = [ # functions @@ -127,7 +127,7 @@ def unquote(string: 'str | bytes', encoding: 'str' = 'utf-8', errors: 'str' = 'r class _ContextManager: def __init__(self, new_context: Context) -> None: ... def __enter__(self) -> Context: ... - def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ... + def __exit__(self, t: Type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ... @contextmanager def localcontext(ctx: 'Optional[Context]' = None, **kwargs: 'Any') -> '_ContextManager': @@ -163,7 +163,7 @@ def _iter_bits_lsb(num: 'int') -> 'Iterator[int]': def show_flag_values(value: 'IntFlag') -> 'list[int]': return list(_iter_bits_lsb(value)) else: - from enum import show_flag_values # type: ignore[attr-defined] # novermin + from enum import show_flag_values # novermin if sys.version_info < (3, 10): from typing_extensions import TypeAlias diff --git a/pcapkit/utilities/config.py b/pcapkit/utilities/config.py index e9c8dcd241..b81d3376cf 100644 --- a/pcapkit/utilities/config.py +++ b/pcapkit/utilities/config.py @@ -99,7 +99,7 @@ def read_limit(name: 'str', default: 'int', *, minimum: 'int' = 0, try: value = int(text) except ValueError: - value = None # type: ignore[assignment] + value = None if value is None or (value < minimum and not (risk is not None and value == -1)): warn(f'{variable}={text!r} is not {accepted}; using the default, {default}', ConfigWarning, stacklevel=_importer_stacklevel()) diff --git a/pcapkit/vendor/ftp/command.py b/pcapkit/vendor/ftp/command.py index af084c129c..f385a4cd48 100644 --- a/pcapkit/vendor/ftp/command.py +++ b/pcapkit/vendor/ftp/command.py @@ -502,7 +502,7 @@ def _missing_(cls, value: 'str') -> '{NAME}': raise ValueError(f'{{value!r}} is not a valid {{cls.__name__}}') name = value.upper() if name in cls._member_map_: - return cls._member_map_[name] # type: ignore[return-value] + return cls._member_map_[name] return cls._unregistered_member(value, name) '''.strip() # type: Callable[[str, str, str, str, str], str] diff --git a/pcapkit/vendor/http/method.py b/pcapkit/vendor/http/method.py index b2207fb696..e126123254 100644 --- a/pcapkit/vendor/http/method.py +++ b/pcapkit/vendor/http/method.py @@ -234,7 +234,7 @@ def _missing_(cls, value: 'str') -> '{NAME}': raise ValueError(f'{{value!r}} is not a valid {{cls.__name__}}') name = value.upper() if name in cls._member_map_: - return cls._member_map_[name] # type: ignore[return-value] + return cls._member_map_[name] return cls._unregistered_member(value, name) '''.strip() # type: Callable[[str, str, str, str], str] diff --git a/pcapkit/vendor/pcapng/tls_key_label.py b/pcapkit/vendor/pcapng/tls_key_label.py index 916194cd99..e6813e513b 100644 --- a/pcapkit/vendor/pcapng/tls_key_label.py +++ b/pcapkit/vendor/pcapng/tls_key_label.py @@ -163,7 +163,7 @@ def process(self, data: 'list[str]') -> 'tuple[list[str], list[str]]': sufs = self.wrap_comment(f'{desc}, c.f., :rfc:`{rfc}{section}`.') pref = f"{renm} = '{label}'" - # NOTE: mirrors the ``# nosec B105`` bandit suppression every + # NOTE: mirrors the ``nosec B105`` bandit suppression every # such member carried in the hand-written # ``pcapkit.protocols.misc.pcapng.TLSKeyLabel`` (#882) -- # bandit's B105 (hardcoded password string) flags a string diff --git a/tests/project/test_pyshark_encap_map.py b/tests/project/test_pyshark_encap_map.py index 9960999f1e..ff2f56e557 100644 --- a/tests/project/test_pyshark_encap_map.py +++ b/tests/project/test_pyshark_encap_map.py @@ -425,12 +425,13 @@ def test_string_keys_are_quoted_like_the_committed_table(self) -> None: block = encap_map.render_dict('FILTER_NAME_TO_LINKTYPE', entries, 'str') self.assertIn(" 'eth': Enum_LinkType.ETHERNET,", block) - self.assertTrue(block.rstrip('\n').endswith('} # type: dict[str, Enum_LinkType]')) + self.assertTrue(block.startswith("FILTER_NAME_TO_LINKTYPE = cast('dict[str, Enum_LinkType]', {\n")) + self.assertTrue(block.rstrip('\n').endswith('}) # a cast, as mypy reads aenum members as int (#2145)')) def test_empty_table_still_closes_correctly(self) -> None: block = encap_map.render_dict('EMPTY', {}, 'int') - self.assertEqual(block, 'EMPTY = {\n} # type: dict[int, Enum_LinkType]\n') + self.assertEqual(block, "EMPTY = cast('dict[int, Enum_LinkType]', {\n}) # a cast, as mypy reads aenum members as int (#2145)\n") class ReplaceBlockTests(unittest.TestCase): @@ -439,19 +440,19 @@ class ReplaceBlockTests(unittest.TestCase): TEXT = ( 'BEFORE = 1\n' '\n' - 'ENCAP_TYPE_TO_LINKTYPE = {\n' + "ENCAP_TYPE_TO_LINKTYPE = cast('dict[int, Enum_LinkType]', {\n" ' 1: Enum_LinkType.ETHERNET, # ether\n' - '} # type: dict[int, Enum_LinkType]\n' + '}) # a cast, as mypy reads aenum members as int (#2145)\n' '\n' - 'FILTER_NAME_TO_LINKTYPE = {\n' + "FILTER_NAME_TO_LINKTYPE = cast('dict[str, Enum_LinkType]', {\n" " 'eth': Enum_LinkType.ETHERNET, # ether\n" - '} # type: dict[str, Enum_LinkType]\n' + '}) # a cast, as mypy reads aenum members as int (#2145)\n' '\n' 'AFTER = 2\n' ) def test_surrounding_text_survives_the_swap(self) -> None: - replacement = 'ENCAP_TYPE_TO_LINKTYPE = {\n 2: Enum_LinkType.SLIP, # slip\n} # type: dict[int, Enum_LinkType]\n' + replacement = encap_map.render_dict('ENCAP_TYPE_TO_LINKTYPE', {2: (Enum_LinkType.SLIP, ['slip'])}, 'int') updated = encap_map._replace_block(self.TEXT, 'ENCAP_TYPE_TO_LINKTYPE', replacement) diff --git a/util/pyshark_encap_map.py b/util/pyshark_encap_map.py index 03643bb1bf..79cdae9261 100644 --- a/util/pyshark_encap_map.py +++ b/util/pyshark_encap_map.py @@ -471,13 +471,17 @@ def _key_repr(key: 'int | str') -> str: def render_dict(name: str, entries: 'dict[Any, tuple[Any, list[str]]]', value_type: str) -> str: - """Render one ``NAME = { ... }`` block, byte-for-byte as it is committed. + """Render one ``NAME = cast(..., { ... })`` block, byte-for-byte as it is committed. + + The literal is wrapped in :func:`typing.cast` rather than annotated, + because mypy reads an :mod:`aenum` member as its ``int`` value, so a + ``dict[..., Enum_LinkType]`` annotation fails on every entry (#2145). Args: name: The dict's variable name, e.g. ``ENCAP_TYPE_TO_LINKTYPE``. entries: Key -> (enum member, [source names]), key-ascending. - value_type: The key type's spelling in the trailing - ``# type: dict[...]`` comment (``int`` or ``str``). + value_type: The key type's spelling in the ``cast`` target, + ``dict[...]`` (``int`` or ``str``). Returns: The complete block, ending in a single newline. @@ -490,17 +494,17 @@ def render_dict(name: str, entries: 'dict[Any, tuple[Any, list[str]]]', width = max((len(code) for code, _ in rows), default=0) + 2 - lines = [f'{name} = {{'] + lines = [f"{name} = cast('dict[{value_type}, Enum_LinkType]', {{"] for code, comment in rows: lines.append(f'{code.ljust(width)}# {comment}') - lines.append(f'}} # type: dict[{value_type}, Enum_LinkType]') + lines.append('}) # a cast, as mypy reads aenum members as int (#2145)') return '\n'.join(lines) + '\n' def _replace_block(text: str, name: str, replacement: str) -> str: - """Swap ``NAME = { ... }`` for *replacement* in *text*. + """Swap ``NAME = cast(..., { ... })`` for *replacement* in *text*. - The block is found by its opening ``NAME = {`` line and its first + The block is found by its opening ``NAME = ...{`` line and its first closing ``}`` line after that -- sufficient here because no value in either table is itself a mapping, so no line before the close can start with ``}``. @@ -520,7 +524,7 @@ def _replace_block(text: str, name: str, replacement: str) -> str: """ lines = text.split('\n') start = next((index for index, line in enumerate(lines) - if line == f'{name} = {{'), None) + if line.startswith(f'{name} = ') and line.endswith('{')), None) if start is None: raise EncapSweepError(f'{TARGET} has no {name!r} block to replace') end = next((index for index in range(start + 1, len(lines))