Importing a transponder model first (e.g. iso15693.type5, or iso14443a via the fuzzer) triggered pm3py.sim's __init__, which eagerly re-imported that same transponder before it finished initialising — ImportError on a partially-initialised module. Masked in the full suite by import order; failed whenever a transponder was the first import. Move sim/__init__'s ~90 transponder re-exports into sim/_models.py and load them lazily via a PEP 562 __getattr__ (public API unchanged: attribute access, from-import, __all__, dir(), and 'import *' all still work). Defer fuzzer.py's iso14443a.base import into the one method that uses it. Add tests/test_import_hygiene.py — subprocess fresh-import guards per transponder family. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
4.0 KiB
Python
93 lines
4.0 KiB
Python
"""pm3py.sim — Software-defined transponder/reader simulation framework."""
|
|
|
|
# --- Sim infrastructure (stays here) ---
|
|
from .frame import RFFrame
|
|
from .memory import DirtyByteArray, MemoryRegion, BlockAccess
|
|
from .medium import Medium, SoftwareMedium
|
|
from .transponder import Transponder
|
|
from .reader import Reader, ScriptedReader, InteractiveReader, ReaderStep, StepResult
|
|
from .fuzzer import MutationFuzzer, GrammarFuzzer
|
|
from .relay import RelayTransponder, MitMProxy
|
|
from .replay import TraceRecorder, TraceReplayer, TraceEntry
|
|
from .pm3medium import PM3ReaderMedium
|
|
from .table_compiler import TableEntry, ResponseTable, TableCompiler
|
|
from .sim_session import SimSession
|
|
from .mcu_bridge import McuBridge
|
|
from .dual_session import DualInterfaceSession
|
|
|
|
# --- Transponder models (canonical home: pm3py.transponders) ---
|
|
# Loaded lazily (PEP 562) from ._models. Those models import back into pm3py.sim for the
|
|
# Transponder/Reader/RFFrame/Medium primitives, so importing them at package-init time would
|
|
# create a transponders<->sim circular import (breaking e.g. `from pm3py.transponders...` as a
|
|
# first import). On first access they load from ._models and cache into this module's globals.
|
|
def __getattr__(name):
|
|
if name.startswith("__") and name.endswith("__"):
|
|
raise AttributeError(name) # don't trigger a model import for dunder probes
|
|
import importlib
|
|
|
|
models = importlib.import_module(f"{__name__}._models")
|
|
try:
|
|
value = getattr(models, name)
|
|
except AttributeError:
|
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
|
globals()[name] = value # cache: subsequent access is a direct lookup
|
|
return value
|
|
|
|
|
|
def __dir__():
|
|
return sorted(set(globals()) | set(__all__))
|
|
|
|
|
|
__all__ = [
|
|
"RFFrame",
|
|
"DirtyByteArray", "MemoryRegion", "BlockAccess",
|
|
"Medium", "SoftwareMedium",
|
|
"Transponder",
|
|
"Reader", "ScriptedReader", "InteractiveReader", "ReaderStep", "StepResult",
|
|
"Tag14443A", "Tag14443A_3", "Tag14443A_4", "Reader14443A", "State14443A",
|
|
"Crypto1",
|
|
"MifareClassicTag", "MifareClassicReader", "MifareAccessConditions",
|
|
"Tag15693", "Reader15693", "State15693",
|
|
"TagLF", "ReaderLF", "Modulation",
|
|
"EM4100Tag", "EM4100Reader", "EM4100Code",
|
|
"HIDProxTag", "HIDReader",
|
|
"T5577Tag", "T5577Reader", "T5577Config",
|
|
"Hitag2Tag", "Hitag2Reader",
|
|
"BitField", "Register",
|
|
"NfcType2Tag", "NfcType4Tag", "NfcType5Tag",
|
|
"NxpType2Tag", "Type2Config",
|
|
"Ntag21x", "NTAG210", "NTAG212", "NTAG213", "NTAG215", "NTAG216", "Ntag21xConfig",
|
|
"MifareUltralight", "MifareUltralightC", "MifareUltralightEV1",
|
|
"MF0UL11", "MF0UL21", "UL_C_DEFAULT_KEY",
|
|
"UltralightEV1Config", "UltralightCConfig",
|
|
"NtagI2C", "NT3H2111", "NT3H2211",
|
|
"NtagI2CConfig", "NtagI2CSession", "NtagI2CPtI2C",
|
|
"ST25TN", "ST25TN512", "ST25TN01K",
|
|
"ST25TA", "ST25TA512B", "ST25TA02KB", "ST25TA16K", "ST25TA64K",
|
|
"OptigaAuthenticateNBT",
|
|
"ST25TV", "ST25TV512C", "ST25TV02KC",
|
|
"ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K", "St25dvRfAreaSS",
|
|
"MydVicinity", "SRF55V02P", "SRF55V10P",
|
|
"Tag14443B", "StateB", "Tag14443B_4", "StateB4",
|
|
"ST25TB", "ST25TB512AC", "ST25TB512AT", "ST25TB02K", "ST25TB04K",
|
|
"RF430CL330H",
|
|
"TagItHFIPlus",
|
|
"RF430FRL15xH", "RF430FRL152H", "RF430FRL153H", "RF430FRL154H",
|
|
"ndef_text", "ndef_uri", "ndef_mime",
|
|
"NxpIcodeTag", "IcodeSlixTag", "IcodeSlix2Tag",
|
|
"Icode3Tag", "IcodeDnaTag",
|
|
"Ntag5PlatformTag", "Ntag5SwitchTag", "Ntag5LinkTag", "Ntag5BoostTag",
|
|
"Ntag5StatusReg", "Ntag5ConfigReg",
|
|
"NxpKeyPrivileges", "IcodeFeatureFlags",
|
|
"DesfireTag", "DesfireReader",
|
|
"xEM", "xNT", "xM1", "FlexDF", "NExT", "MagicMifareClassicTag",
|
|
"MutationFuzzer", "GrammarFuzzer",
|
|
"RelayTransponder", "MitMProxy",
|
|
"TraceRecorder", "TraceReplayer", "TraceEntry",
|
|
"PM3ReaderMedium",
|
|
"TableEntry", "ResponseTable", "TableCompiler",
|
|
"SimSession",
|
|
"McuBridge",
|
|
"DualInterfaceSession",
|
|
]
|