Files
pm3py/pm3py/sim/__init__.py
michael 0586e2a41a fix(sim): break the transponders<->sim circular import via lazy re-exports
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>
2026-07-15 21:23:12 -07:00

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",
]