MIB compiler¶
- class pysmi.compiler.MibCompiler(parser: AbstractParser, codegen: AbstractCodeGen, writer: AbstractWriter, useBundledMibs: bool = True, preferConfiguredSources: bool = False, parseCache: AbstractParseCache | None = None)¶
Top-level, user-facing, composite MIB compiler object.
MibCompiler implements high-level MIB transformation processing logic. It executes its actions by calling the following specialized objects:
readers - to acquire ASN.1 MIB data
searchers - to see if transformed MIB already exists and no processing is necessary
parser - to parse ASN.1 MIB into AST
code generator - to perform actual MIB transformation
borrowers - to fetch pre-transformed MIB if transformation is impossible
writer - to store transformed MIB data
Required components must be passed to MibCompiler on instantiation. Those components are: parser, codegenerator and writer.
Optional components could be set or modified at later phases of MibCompiler life. Unlike singular, required components, optional one can be present in sequences to address many possible sources of data. They are readers, searchers and borrowers.
Creates an instance of MibCompiler class.
- Parameters:
parser – ASN.1 MIB parser object
codegen – MIB transformation object
writer – transformed MIB storing object
- Keyword Arguments:
useBundledMibs – register pysmi’s own bundled copy of the base MIBs (
SNMPv2-SMIand friends) as a priority source, tried ahead of everything added throughadd_sources(). It is a source of its own rather than a last-resort fallback: where anadd_sources()reader has one of those modules too, the newest MODULE-IDENTITY revision supplies it and this ordering only breaks the tie –compile()states the whole rule. Set toFalseto compile fromadd_sources()alone, so a misconfigured call fails loudly instead of silently succeeding from the bundled copy.preferConfiguredSources – put
add_sources()ahead of the bundled copy where the revisions do not decide – a module with no MODULE-IDENTITY to compare, or two copies carrying the same one. The newest revision still wins when there is one on every copy; this only settles what the bundle would otherwise settle by being asked first. Nearly half the bundled modules carry no MODULE-IDENTITY at all (SNMPv2-SMIand the other SMI modules among them), so for those this is the difference between the caller’s copy being used and the bundled one.parseCache – where to keep parse trees between
compile()calls, as anAbstractParseCache. A driver compiling many source sets on one compiler – seeset_sources()– then parses the standard tree once instead of once per set. Defaults toInMemoryParseCache;FileParseCachesurvives process exit, for a build that is a shell loop rather than one process;NullParseCacheturns caching off. A provider is used as passed and never resolved by name or from configuration, so the trust boundary is the caller’s own code – which matters, because a provider that stores trees outside this process reconstructs arbitrary Python objects when it reads them back.
- addBorrowers(*borrowers: AbstractBorrower) MibCompiler¶
Deprecated alias for
add_borrowers().
- addPrioritySources(*sources: AbstractReader) MibCompiler¶
Deprecated alias for
add_priority_sources().
- addSearchers(*searchers: AbstractSearcher) MibCompiler¶
Deprecated alias for
add_searchers().
- addSources(*sources: AbstractReader) MibCompiler¶
Deprecated alias for
add_sources().
- add_borrowers(*borrowers: AbstractBorrower) MibCompiler¶
Add more transformed MIBs repositories to borrow MIBs from.
Whenever MibCompiler.compile encounters MIB module which neither of the searchers can find or fetched ASN.1 MIB module can not be parsed (due to syntax errors), these borrowers objects will be invoked in order of their addition asking each if already transformed MIB can be fetched (borrowed).
- Parameters:
borrowers – borrower object(s)
- Returns:
reference to itself (can be used for call chaining)
- add_priority_sources(*sources: AbstractReader) MibCompiler¶
Add ASN.1 MIB source repositories to be asked ahead of the rest.
Every one of these is tried, for every MIB, before any
add_sources()source, whatever order either was added in. Use it for a source that is more trustworthy than whatever the caller happens to have configured, such as pysmi’s own bundled base MIBs (seeuseBundledMibson the constructor).A distribution’s
/usr/share/snmp/mibsroutinely carries a base MIB frozen years ago, and taking that over a copy pinned to its RFC is almost never what anyone wanted. Overriding a bundled module is still possible – ship a newer MODULE-IDENTITY revision of it, passpreferConfiguredSources=Truesoadd_sources()outranks the bundle wherever revisions do not decide, or passuseBundledMibs=Falseto drop the bundle entirely.- Parameters:
sources – reader object(s)
- Returns:
reference to itself (can be used for call chaining)
- add_searchers(*searchers: AbstractSearcher) MibCompiler¶
Add more transformed MIBs repositories.
MibCompiler.compile will invoke each of configured searcher objects in order of their addition asking each if already transformed MIB module already exists and is more recent than specified.
- Parameters:
searchers – searcher object(s)
- Returns:
reference to itself (can be used for call chaining)
- add_sources(*sources: AbstractReader) MibCompiler¶
Add more ASN.1 MIB source repositories.
MibCompiler.compile will invoke each of configured source objects in order of their addition asking each to fetch MIB module specified by name. The first source that has a module supplies it, and every
add_priority_sources()source is asked first, whatever order either was added in.The one exception is a module pysmi bundles a copy of, where the newest MODULE-IDENTITY revision wins instead – when every copy found carries one – and this order only breaks the tie. Where it does fall to this order,
preferConfiguredSourceson the constructor puts these sources ahead of the bundled copy.compile()documents the whole rule.- Parameters:
sources – reader object(s)
- Returns:
reference to itself (can be used for call chaining)
- buildIndex(processedMibs: dict[str, MibStatus], **options: Any) None¶
Deprecated alias for
build_index().
- build_index(processedMibs: dict[str, MibStatus], **options: Any) None¶
Generate and store an index over the MIBs just compiled.
- Parameters:
processedMibs – MIB module names mapped to their compilation results
- Keyword Arguments:
dryRun – build the index but do not store it
ignoreErrors – log a failure to build the index instead of raising
- Raises:
PySmiError – the index could not be built or stored, unless
ignoreErrorsis set.
- bundledMibsPackage = 'pysmi.mibs.asn1'¶
Dotted package holding the bundled base MIB ASN.1 sources – read through
PackageReaderwhenuseBundledMibsis set. See pysnmp/pysmi#113.
- clearParseCache() MibCompiler¶
Deprecated alias for
clear_parse_cache().
- clear_parse_cache() MibCompiler¶
Drop every parse tree the configured cache holds.
Never needed for correctness – a key is derived from the text and its producer, so an entry is only ever reused for identical input. It is here for a caller that wants the space back at a known point.
- Returns:
reference to itself (can be used for call chaining)
- compile(*mibnames: str, **options: Any) dict[str, MibStatus]¶
Transform requested and possibly referred MIBs.
The compile method should be invoked when MibCompiler object is operational meaning at least sources are specified.
Once called with a MIB module name, compile will:
fetch ASN.1 MIB module with given name by calling sources
make sure no such transformed MIB already exists (with searchers)
parse ASN.1 MIB text with parser
perform actual MIB transformation into target format with code generator
may attempt to borrow pre-transformed MIB through borrowers
write transformed MIB through writer
The above sequence will be performed for each MIB name given in mibnames and may be performed for all MIBs referred to from MIBs being processed.
- Parameters:
mibnames – list of ASN.1 MIBs names
options – options that affect the way PySMI components work
- Keyword Arguments:
strictSources – fail a MIB that more than one configured source has a different copy of, rather than taking one and reporting the other on
MibStatus.shadowed.- Returns:
A dictionary of MIB module names processed (keys) and MibStatus class instances (values)
Note
Which copy of a module gets compiled, when more than one source has it:
For a module pysmi bundles a copy of, the newest MODULE-IDENTITY LAST-UPDATED wins – provided every copy found carries one. An undated copy cannot be placed against a dated one, so a single one of those drops the module to rule 2 whatever the others carry.
Otherwise – to break a tie between equal revisions, for a module with an undated copy, and for everything pysmi does not bundle – source order wins: every
add_priority_sources()reader, then everyadd_sources()reader, each in the order it was added.preferConfiguredSourcesmoves the bundled copy behindadd_sources()for this rule only.
Rule 1 is deliberately confined to the bundled names. Those are pinned to an RFC or to IANA and re-checked against it, so two copies of one are the same specification at two revisions and the newer is simply better. Two copies of a vendor module are not that: they are a collision, or two firmware revisions, and which one was meant is what the caller’s source order says.
Rule 2 carries more than it looks like it does: 13 of the 27 bundled modules –
SNMPv2-SMI,SNMPv2-TC,SNMPv2-CONFand the other SMI and RFC-numbered ones – have no MODULE-IDENTITY at all, so for those rule 1 can never fire and the bundled copy is used unlesspreferConfiguredSourcesoruseBundledMibs=Falsesays otherwise.MibStatus.pathnames the file a module was compiled from,MibStatus.shadowedthe copies passed over, andMibStatus.precedencewhich of these rules chose between them – so a build can record what it resolved to, why, and reproduce it later.
- listMibs(includeBundled: bool = False) list[str]¶
Deprecated alias for
list_mibs().
- list_mibs(includeBundled: bool = False) list[str]¶
Every module name the configured sources can be asked to enumerate.
This is what turns “compile this collection” into a list of modules without anyone having to write the list down. A source that cannot be listed – a web server answering a
@mib@URL template – reports nothing, so what comes back is the modules held locally.The bundled base MIBs are left out by default. They are a resolution source, supplying whatever a compiled module imports; a caller asking what to build normally means its own collection, not pysmi’s copy of the standard MIBs on top of it.
- Keyword Arguments:
includeBundled – also enumerate the bundled base MIBs.
- Returns:
Module names in source order, each appearing once.
- prune(**options: Any) dict[str, MibStatus]¶
Remove previously stored output whose source MIB no longer exists.
compile only ever acts on the MIBs it is asked for, so output for a module that has since been removed from every configured source lingers in the destination forever. prune closes that gap: it asks the writer what it currently holds (
list_data(), which reports nothing for a writer that cannot enumerate its own output, e.g.CallbackWriter), and for each name tries every configured source in turn. A name none of them have any more is removed.Only output carrying this package’s own “Produced by” marker is ever considered – a file the writer holds that this tool did not generate is left alone, whatever else is true of it.
Every source has
clear_cache()called on it first, so a reader already warmed up by an earlier compile in this same run reports what exists right now rather than what existed when it was first asked.- Keyword Arguments:
dryRun – report what would be removed without removing anything
ignoreErrors – keep going after a source or writer error instead of raising
- Returns:
A dictionary of MIB module names the writer held (keys) and MibStatus instances (values) – pruned if removed, untouched if a source still has it, failed if removal itself failed.
- Raises:
PySmiError – a source or the writer failed, unless
ignoreErrorsis set.
- resolve(mibname: str) MibResolution | None¶
Which copy of mibname the configured sources supply, without compiling it.
compile()answers this on the way past, onMibStatus.path,MibStatus.shadowedandMibStatus.precedence– but only for a module it went on to compile, and only after it has. A driver publishing the ASN.1 beside the compiled output has to answer it for every module it holds, including the ones that fail, and has to answer it the same way the compile will. Asking here is what makes the two agree by construction rather than by inspection.The rule is
compile()’s own, applied by the same code: the newest MODULE-IDENTITY revision wins, source order breaks the tie.- Parameters:
mibname – MIB module name
- Returns:
What the sources hold for that name, or
Nonewhen none of them has it.
- setSources(*sources: AbstractReader) MibCompiler¶
Deprecated alias for
set_sources().
- set_sources(*sources: AbstractReader) MibCompiler¶
Replace the ASN.1 sources, keeping everything else about this compiler.
For a driver compiling many source sets in turn – a corpus build over several hundred vendor namespaces, each with its own directory and each importing the same standard modules. Building a fresh compiler per set re-parses that standard tree once per set, because the parse cache lives in
compile()and dies with the call; swapping the sources on one compiler keeps it.Only
add_sources()readers are replaced.add_priority_sources()ones, the bundled base MIBs among them, are left in place – they are the part that does not vary between namespaces, and re-registering them per set is what this exists to avoid.Swapping sources cannot make a stale answer reachable: the parse cache is keyed by the digest of the text parsed, never by module name, so a namespace carrying a different module under a name another namespace used is different bytes and a different key. What a swap does change is which sources are asked, and that takes effect immediately – a module only the previous set had stops resolving.
- Parameters:
sources – reader object(s) to use from now on
- Returns:
reference to itself (can be used for call chaining)
Compiling many source sets¶
A corpus build compiles a few hundred vendor namespaces, each with its own source directory, and every one of them imports the same handful of standard modules.
The parse cache belongs to the compiler, so it is kept across
compile() calls and the standard tree is
parsed once. What loses it is building a new compiler per namespace – and a
shell loop, which starts a fresh process each time, can do nothing else. That
is the shape the standard tree ends up parsed once per namespace in.
set_sources() replaces the configured
sources on a compiler that is already built, so one compiler can be driven
across every namespace instead:
from pysmi.codegen import JsonCodeGen
from pysmi.compiler import MibCompiler
from pysmi.parser import SmiV1CompatParser
from pysmi.reader import FileReader
from pysmi.writer import CallbackWriter
documents = {}
def store(mibname, data, cbCtx):
documents[mibname] = data
compiler = MibCompiler(
SmiV1CompatParser(), JsonCodeGen(), CallbackWriter(store)
)
for namespace in namespaces:
compiler.set_sources(FileReader(namespace.path))
compiler.compile(*namespace.modules)
Only add_sources() readers are replaced.
The add_priority_sources() ones – pysmi’s
bundled base MIBs among them – stay registered, because they are the part that
does not vary between namespaces.
Measured over eight namespaces against the bundled sources:
wall |
parses |
distinct |
|
|---|---|---|---|
a fresh compiler each |
0.85s |
45 |
14 |
one compiler, sources swapped |
0.45s |
14 |
14 |
Every parse in the second row is of text not parsed before in that build. The saving grows with the namespace count, since the shared tree is a fixed cost paid once rather than once per namespace.
Why swapping sources cannot serve a stale answer¶
The cache is keyed by the digest of the text that was parsed, never by module name. Two namespaces carrying different modules under one name – ordinary in a vendor corpus, where a MIB name recurs across trees at different revisions – are different bytes and therefore different keys, so neither can be served the other’s tree.
What a swap does change is which sources are asked, and that takes effect at once: a module only the previous source set had stops resolving.
Choosing where the trees are kept¶
The cache is a component, passed to the constructor. The Parse caches section of the library reference lists the ones pysmi ships and the interface a provider implements.
InMemoryParseCache is the default: bounded,
least-recently-used evicted, so the shared modules stay resident while
per-namespace ones do not accumulate. It is the right answer whenever the whole
build runs in one process.
FileParseCache keeps trees in a directory, so they
outlive the process that built them. That is what a build driven as a shell loop
needs – a fresh interpreter per namespace has nothing in memory to reuse:
from pysmi.cache import FileParseCache
compiler = MibCompiler(
SmiV1CompatParser(),
JsonCodeGen(),
CallbackWriter(store), # as above
parseCache=FileParseCache("/var/cache/pysmi"),
)
Reading that cache unpickles, which reconstructs arbitrary Python objects, so point it only at a directory your own build writes. The in-memory provider has no such boundary. A provider is always used as passed and never resolved by name, from an entry point or from configuration – the trust boundary is the calling code.
NullParseCache turns caching off, which is the
behaviour of every release before it existed.
To write your own – Redis, memcached, a shared filesystem, whatever a build
already runs – implement
AbstractParseCache. No registration step exists
because none is needed. Two things a provider does not have to do:
invalidation, since the key already carries the text together with the identity
of what would parse it – the pysmi version, the parser class, its grammar
relaxations and its start symbol – so an entry written by another release, or
by a different SMI dialect, is never read by this one; and defensive copying,
since the compiler copies whatever it receives.
The dialect matters as much as the version here. parserFactory names every
specialization it builds SmiParser, so all three shipped parsers share a
class identity while accepting different grammars: a trailing comma in
IMPORTS parses under SmiV1CompatParser and raises under
SmiV2Parser.
clear_parse_cache() empties whichever
provider is configured. It is never needed for correctness.