Upgrading from v1.x to v2.x

AsyncIO first

The “sync” implementation has been removed in favour of code-deduplication. All exposed methods are now asyncio coroutines.

Reasons for Change

puresnmp v1.x had a lot of code duplication by providing both a “sync” and “async” implementation. This made it error-prone to add new features and applying bugfixes.

Providing a single code-base made the maintenance easier at the cost of losing the “sync” implementation.

Mitigation: calling a “sync” corouting can be done fairly easily by wrapping the call in either asyncio.run() (Python 3.7+) or asyncio.loop.run_until_complete() (Python <3.7).

Note

Care has to be taken when running this multi-threaded. The asyncio “event-loop” is global in Python. This should not be an issue in most cases. If strange things happen, read up on asyncio with multi-threading.

Example

Running in “async” context
import asyncio
import puresnmp

async def hello_world():
    client = puresnmp.PyWrapper(
        puresnmp.Client("192.0.2.1", puresnmp.V2C("private))
    )
    result = await client.get("1.3.6.1.2.1.1.2.0)
    return result
Running in “sync” context, Python 3.7+
import asyncio
import puresnmp

client = puresnmp.PyWrapper(
    puresnmp.Client("192.0.2.1", puresnmp.V2C("private))
)
coro = client.get("1.3.6.1.2.1.1.2.0)
asyncio.run(coro)
Running in “sync” context, Python <3.7
import asyncio
import puresnmp

client = puresnmp.PyWrapper(
    puresnmp.Client("192.0.2.1", puresnmp.V2C("private))
)
loop = asyncio.get_event_loop()
coro = client.get("1.3.6.1.2.1.1.2.0)
loop.run_until_complete(coro)

Module-Level functions moved to client-classes

puresnmp v1.x has four core modules which provided simple functions to execute SNMP requests: puresnmp.api.raw and puresnmp.api.pythonic. And asyncio equivalents of both.

puresnmp now has one core code-base in puresnmp.api.raw.Client. And a wrapper dealing with data-type conversion in puresnmp.api.pythonic.PyWrapper.

Reasons for Change

  • Provides the ability to deduplicate certain values (IP-address, credentials, UDP timeout, …) in client code.

  • No longer polluting the global (builtin) namespace.

Example

puresnmp v1.x
from puresnmp import get

result = get("192.0.2.1", "private", "1.3.6.1.2.1.1.2.0")
result = get("192.0.2.1", "private", "1.3.6.1.2.1.1.1.0")
puresnmp v2.x
from puresnmp import Client, PyWrapper, V2C

client = PyWrapper(Client("192.0.2.1", V2C("private")))
result = await client.get("1.3.6.1.2.1.1.2.0")
result = await client.get("1.3.6.1.2.1.1.1.0")

Strict Data-Type Decoupling

The old “pythonic” interface has been replaced with a “wrapper” class. This class wraps a normal “raw” client and takes care of data-type conversions. The goal of the wrapper is to shield any client-code from changes to internal data-types.

Reasons for Change

puresnmp v1.x had a leaky abstraction through inconsistent internal handling of SNMP “VarBinds”. They sometimes contained instances of ObjectIdentifier and x690.types.Type classes and sometimes pure-Python data-types. It also caused an overall inconsistency between the “raw” and “pythonic” interfaces.

This made the usage of puresnmp potentially brittle as internal changes could break client-code. Extra care was taken to avoid this throught the history of puresnmp. This blocked some internal changes.

The data-types are now fully decoupled via the puresnmp.api.pythonic.PyWrapper class. This decoupling is “opt-in” from the client code and it is possible to use puresnmp.api.raw.Client without wrapper. By exposing internal data-types Client instances offer more flexibility at the expense of additional risk that internal changes break client code.

Example

“raw” api
from puresnmp import Client, ObjectIdentifier, V2C

client = Client("192.0.2.1", V2C("private"))
result = await client.get(ObjectIdentifier("1.3.6.1.2.1.1.2.0"))
print(repr(result))
# output: OctetString(b"...")
Using “PyWrapper”
from puresnmp import Client, ObjectIdentifier, V2C, PyWrapper

client = PyWrapper(Client("192.0.2.1", V2C("private")))
result = await client.get("1.3.6.1.2.1.1.2.0")
print(repr(result))
# output: b"..."

ObjectIdentifier Data-Type

The data-type of x690.types.ObjectIdentifier is now consistently exposing “str” instances.

Reasons for Change

  • Simplification of client code (no longer necessary to use .from_string(...))

  • More user-friendly “repr” output

Example

puresnmp v1.x
from x690.types import ObjectIdentifier as OID

oid = OID.from_string("1.3.6.1.2.1.1.2.0")
print(repr(oid))
# output: ObjectIdentifier((1, 3, 6, 1, 2, 1, 1, 2, 0))
puresnmp v2.x
from x690.types import ObjectIdentifier as OID

oid = OID("1.3.6.1.2.1.1.2.0")
print(repr(oid))
# output: ObjectIdentifier("1.3.6.1.2.1.1.2.0")