Date: 2026-05-06
Status: revised planning baseline
Repo state: greenfield (README.md and LICENSE only at the time of writing)
tsnmp Istsnmp is an importable Python SNMP manager runtime with a thin companion CLI.
The primary product is the Python package:
get, getnext, getbulk, walk, and bulkwalkThe CLI exists for smoke testing, demos, debugging, and parity checks. It is not the primary v0.1 product surface.
tsnmp must work with no MIB tooling installed. A user must be able to perform live SNMP manager operations with numeric OIDs and raw typed values even when no MIB bundle is loaded.
tsnmp is not trying to replace existing SNMP command-line tools as a primary objective. Those tools already cover operator workflows over raw MIB inputs well. The differentiated goal here is a clean Python runtime.
tsmiThe ownership boundary should stay explicit.
| Concern | Owner |
|---|---|
| ASN.1 / SMI parsing | tsmi |
| import resolution across MIB modules | tsmi |
| producing compiled JSON artifacts | tsmi |
| defining and versioning the JSON IR | tsmi |
| loading compiled JSON bundles | tsnmp |
| OID and symbol translation at runtime | tsnmp |
| BER / SNMP encode-decode | tsnmp |
| UDP transport and request handling | tsnmp |
| manager API | tsnmp |
| optional CLI | tsnmp |
| runtime value formatting and enrichment | tsnmp |
The important rule is: tsnmp consumes tsmi output artifacts, but it does not become a compiler frontend in v0.1.
tsmi JSON artifacts for symbolic lookup and better output.pysnmp compatibility layers.pysnmp API paritytsnmpset) unless the scope is explicitly expanded laterThe point of v0.1 is to prove a clean runtime architecture, not to clone the entire legacy ecosystem.
The main user journey should be:
tsmi if symbolic enrichment is desiredtrishul_snmp in PythonThe main design artifact is therefore the Python API, not the CLI syntax.
The CLI should remain intentionally small:
Inside the manager, transport, and wire layers, requests and responses should be numeric only:
This keeps core SNMP behavior independent of any MIB source.
tsnmp should load compiled JSON artifacts without importing trishul-smi as a Python package.
That keeps deployment simple:
The public package surface should be async-first.
That fits the transport model better than a blocking-first surface:
A sync facade may be added later, but it is not part of the v0.1 critical path.
The current tsmi JSON is usable but not yet fully versioned as a downstream contract.
tsnmp therefore should:
#6 and #7 landInside tsnmp, bundle data should be normalized into an internal registry model.
v0.1 should ship only one external adapter:
tsmi 0.3.x JSON bundle directoriesThis preserves a future extension point without publishing a generic ingest schema too early.
Recommended layering:
wire/
Handles BER TLV parsing, ASN.1 value types, SNMP message parsing, and PDU encode-decode.transport/
Handles UDP sockets, request IDs, timeout behavior, retry behavior, and response matching.manager/
Exposes async manager operations over numeric OIDs and raw SNMP values.mib/
Loads compiled JSON bundle directories, builds indexes, resolves names, and formats values.cli/
Thin wrappers over the same manager and bundle APIs.Dependency direction should remain one-way:
wire imports nothing from transport, manager, or mibtransport depends on wiremanager depends on transport and wiremib depends on shared runtime types, but not on transportcli is the composition layerThis is the key feasibility constraint. If manager starts importing MIB-specific logic directly, tsmi stops being optional in practice.
Use a src/ layout from the start.
src/
trishul_snmp/
__init__.py
errors.py
types.py
wire/
__init__.py
ber.py
asn1.py
message.py
pdu.py
transport/
__init__.py
udp.py
dispatcher.py
manager/
__init__.py
client.py
operations.py
walk.py
mib/
__init__.py
bundle.py
loader.py
registry.py
render.py
models.py
cli/
__init__.py
main.py
common.py
output.py
tests/
docs/
Suggested responsibilities:
types.py
Public dataclasses and enums such as OID, VarBind, Response, error-status enums, and manager result types.wire/
Pure protocol logic only.transport/dispatcher.py
Request ID allocation, outstanding request tracking, timeout handling, and retry orchestration.manager/client.py
Async client lifecycle and top-level operations.mib/models.py
Internal normalized registry records derived from bundle JSON.mib/loader.py
Reads a module JSON file or bundle directory and validates module JSON.mib/registry.py
Builds symbol indexes, type indexes, and longest-prefix OID lookup structures.mib/render.py
Converts raw varbinds into richer output using object metadata, TCs, enums, display hints, and best-effort index rendering.The public Python API is the primary v0.1 design surface.
Example shape:
from trishul_snmp import V2cManager, load_bundle
bundle = load_bundle("./mibs-json") # optional
async with V2cManager(
host="10.0.0.1",
community="public",
timeout=2.0,
retries=1,
bundle=bundle,
) as manager:
response = await manager.get(
"IF-MIB::ifDescr.1",
"1.3.6.1.2.1.1.3.0",
)
for vb in response.varbinds:
print(vb.oid, vb.display_name, vb.display_value)
Recommended public entry points:
load_bundle(path) -> MibBundleV2cManager(...)await manager.get(*targets)await manager.get_next(*targets)await manager.get_bulk(*targets, non_repeaters=0, max_repetitions=10)await manager.walk(root, *, bulk=True, max_repetitions=10)bundle.translate(target)Recommended behavior:
load_bundle(path) accepts either a single compiled module JSON file or a directory of compiled module JSON filesRecommended result model:
Response
Carries request metadata, SNMP error status, and varbinds.VarBind
Always carries numeric OID and raw typed value.
When enrichment is available, it also carries resolved name and display text.MibBundle
Exposes translation and reverse-lookup services used by both API and CLI.Sync API guidance:
tsmi Enrichment Should FitThe runtime-facing enrichment abstraction should stay small, even if v0.1 only ships one adapter.
Required capabilities:
This boundary may stay internal at first, but it should be real in the code.
v0.1 should accept:
v0.1 should not accept:
tsmi runtime importstsmpSidecars are optional:
manifest.json is optional bundle metadataoid_index.json is optional performance metadataThat keeps the runtime/compiler split explicit, avoids input ambiguity, and prevents sidecars from becoming correctness requirements.
tsmi JSON RealityToday, tsmi emits one JSON file per module and includes enough information for runtime use:
oid and oid_pathobject_type, class, and nodetypesyntax, constraints, max_access, statusindex and augmentsnotifications.*.memberstypes.*.base_type, display_hint, and constraintsmodule_metadataThat is already enough for:
Recommended load behavior:
*.json files in the directory and ignore obvious sidecars.generated_by.(module, symbol) -> object indexoid_path -> object exact indexoid_index.json exists, use it as a reverse-lookup accelerator only.The important rule is progressive enrichment, not all-or-nothing bundle validity. Missing sidecars must not prevent basic use, and missing dependency module JSON files may reduce enrichment fidelity without blocking core manager operations.
tsmi Issues #5, #6, and #7 LandThe upstream issues matter directly to tsnmp:
#5 oid_index.json
Useful for faster reverse lookup, but not a blocker because tsnmp can build its own in-memory indexes.#6 bundle manifest
Strongly preferred, because deterministic bundle loading is cleaner than directory heuristics.#7 JSON IR versioning
The most important contract issue. Without it, downstream compatibility is only “tested against known tsmi versions”.Recommended v0.1 position:
tsmi 0.3.x JSON shape explicitlyThe CLI is a secondary interface over the same Python API and bundle loader.
Recommended initial CLI:
| Command | Purpose |
|---|---|
tsnmp translate TARGET |
Translate numeric OID to symbol, or symbol to numeric OID |
tsnmp get TARGET [TARGET ...] |
Single request for one or more objects |
tsnmp getnext TARGET [TARGET ...] |
Next-object retrieval |
tsnmp getbulk TARGET [TARGET ...] |
Raw bulk retrieval |
tsnmp walk ROOT |
Sequential walk, defaulting to bulk when available |
tsnmp bulkwalk ROOT |
Explicit bulk walk |
tsnmp version |
Print version |
Suggested common options for live commands:
--host--port default 161--community--timeout--retries--bundle PATH--numeric to suppress symbolic rendering even when a bundle is present--json for machine-readable outputSpecific command notes:
translate
Works offline with only --bundle, where the path may be a single module JSON file or a directory.get and getnext
Accept either numeric or symbolic input. If symbolic input is used without a bundle, fail clearly.walk and bulkwalk
Print one varbind per line by default and support --json.getbulk
Needs --non-repeaters and --max-repetitions.Deliberate exclusions from the initial CLI:
settrap-listenmib compiletsmiload_bundle("./mibs-json"), load_bundle("./IF-MIB.json"), or tsnmp translate --bundle ...mib.loader loads either a single module JSON file or scans a directory and validates module JSONmib.registry builds symbol and OID indexesMODULE::symbol -> numeric OIDThere is no SNMP traffic in this flow. It validates the enrichment boundary before runtime code is involved.
get with No BundleV2cManager.get() accepts the numeric target directlymanager builds request varbindstransport.dispatcher allocates a request ID and timeout trackingwire.pdu and wire.message encode an SNMPv2c requesttransport.udp sends the datagramResponse and VarBind objects are returned with no enrichment fields populatedThe numeric-only runtime starts before encode and stays numeric until return.
get with Bundlemib.render reverse-resolves OIDs and applies formatting where metadata existsResponse and VarBind objects are returned with both raw and enriched fieldsThe runtime stays numeric internally. Enrichment happens only after receive.
walk and bulkwalkmanager.walk() repeatedly issues getbulk or getnextendOfMibViewmib.loader reads *.json and ignores sidecarsmib.registry builds:
(module, symbol) -> objectoid_path -> objectoid_index.json exists, it may be used as an accelerator onlyMibBundle exposes the resulting registry to translation and rendering logicToday:
tsmi 0.3.x module JSON shape from either a single file or a directoryLater:
tsmi JSON file or directory outputgetgetnextgetbulkwalkbulkwalkTimeTicksIpAddressnoSuchObject, noSuchInstance, endOfMibView)These are acceptable only if they do not put the main package and runtime milestones at risk.
setgetbulktsmi invocation from tsnmpDeferring raw MIB ingestion is deliberate. Existing SNMP CLI tools already cover that user journey, and adding it here would blur the runtime/compiler split without improving the primary Python API.
Recommended baseline:
>=3.10
Chosen to align with trishul-smi and avoid unnecessary environment fragmentation.hatchling
Keeps packaging consistent with tsmi.src/ layoutpytest, pytest-asyncio, mypy, ruffargparse for the initial thin CLIRecommended runtime dependency posture:
pysnmptrishul-smiorjsonCodec recommendation:
wire/This is a pragmatic middle ground:
pysnmppysnmptsmi issue #7 is the biggest architectural risk. Until IR versioning exists, tsnmp can only support a tested schema window, not a formally stable contract.oid_index.json and manifest support, first load may be slower and more heuristic than desired.getbulk behavior. The walk implementation needs clear stop rules.set because this is a manager package; the scope cut must be documented clearlytsmi bug issues #1 through #4 are not architectural blockers, but they do affect trust in the artifacts users hand to tsnmpDeliverables:
tsmi bundle contract for v0.1Exit criteria:
Deliverables:
tsmi JSON module files and bundle directoriesload_bundle() and bundle.translate()Exit criteria:
This is the first real proof point because it validates the tsmi boundary before wire work begins.
Deliverables:
get, getnext, and getbulk over numeric OIDsExit criteria:
Deliverables:
walk and bulkwalkV2cManager package surfaceExit criteria:
Deliverables:
load_bundle() and V2cManagerExit criteria:
tsnmp v0.1 is a Python package first and a CLI second.tsnmp v0.1 is manager-only.tsnmp v0.1 supports SNMPv2c only.tsmi is optional and is consumed only as compiled JSON artifacts, not as a required runtime import.translate, get, getnext, getbulk, walk, and bulkwalk.set, trap receiver support, SNMPv3, and agent functionality are deferred.pysnmp API parity in v0.1.src/ layout and an async-first Python API.tsmi compatibility target is the current 0.3.x JSON shape, with a plan to adopt manifest and IR-version validation when upstream lands them.tsnmp may normalize external artifacts internally, but it does not publish a generic third-party JSON ingest contract in v0.1.tsnmp is feasible as a clean, package-first manager runtime with optional JSON-backed MIB enrichment.
The strongest v0.1 architecture is:
pysnmpThat delivers a useful foundation without pulling tsnmp into compiler responsibilities or raw-MIB frontend behavior.