Modern SNMP in Python with ezsnmp: Practical Guide & Migration¶
Note
Published: October 14, 2026
Authors: Written by Gemini 3.8 Flash, Co-Authored & Reviewed by Carlos Santos (@carlkidcrypto)
Estimated Reading Time: 4 minutes
Tags: Python • Networking • SNMP • Net-SNMP • ezsnmp • DevOps
Why ezsnmp?¶
Network engineers and systems developers relying on Python for SNMP monitoring and telemetry have long faced a dilemma: pure-Python SNMP libraries often introduce substantial CPU overhead at scale, while older binding-based libraries have suffered from abandonment or broken builds against modern toolchains.
ezsnmp is a high-performance, actively maintained fork of Easy SNMP engineered to resolve these challenges:
Modern Python Runtime: Built and tested strictly for Python 3.10 and greater (including Python 3.11, 3.12, 3.13, and 3.14). Deprecated Python 2 and legacy Python <= 3.9 support is dropped.
C++17 Foundation: Re-engineered C++17 core interfacing directly with the battle-tested official Net-SNMP C library.
Broad Net-SNMP Compatibility: Full support across Net-SNMP 5.7 through 5.10.
Zero-Hassle Packaging: Self-contained binary wheels on PyPI for Windows (AMD64, x86, ARM64) bundling Net-SNMP, an official Homebrew formula for macOS, and pre-built Linux containers.
Modernized Pythonic API: Clean snake_case method names (
bulk_walk,bulk_get,get_next), standard exception hierarchy (no legacyEasySNMPprefixes), and context manager (with Session(...)) support.
Whether you are monitoring switch ports, polling data center PDUs, or building large-scale telemetry pipelines, ezsnmp delivers native C execution speed with an intuitive Python API.
Installation & Setup¶
Installing via pip¶
On modern platforms, ezsnmp installs directly from PyPI via pip:
pip install ezsnmp
Platform Prerequisites¶
Windows: Pre-compiled binary wheels include the required Net-SNMP runtime libraries. No external C compiler or manual OpenSSL configuration is needed.
macOS (Homebrew): You can install using the official formula:
brew install carlkidcrypto/ezsnmp/ezsnmp
Alternatively, when compiling against Homebrew’s
net-snmppackage:brew install net-snmp openssl@3 export LDFLAGS="-L$(brew --prefix net-snmp)/lib -L$(brew --prefix openssl@3)/lib" export CPPFLAGS="-I$(brew --prefix net-snmp)/include -I$(brew --prefix openssl@3)/include" pip install ezsnmp
Linux (Ubuntu/Debian): Ensure Net-SNMP development headers and compilers are installed:
sudo apt-get update && sudo apt-get install -y \ libsnmp-dev snmp-mibs-downloader build-essential pip install ezsnmp
Linux (RHEL / CentOS / Fedora / Rocky Linux):
sudo dnf install -y net-snmp-devel gcc-c++ make pip install ezsnmp
Core Usage Patterns¶
1. Basic SNMP GET Request (v2c)¶
The primary entry point is the Session object. Use it
to configure target credentials and execute queries. The get()
method returns a tuple of Result objects:
from ezsnmp import Session
# Initialize a v2c session
session = Session(
hostname='192.168.1.1',
community='public',
version=2,
timeout=2,
retries=3
)
# Query standard system description (sysDescr.0)
results = session.get('sysDescr.0')
if results:
sys_descr = results[0]
print(f"OID: {sys_descr.oid}")
print(f"Index: {sys_descr.index}")
print(f"Value: {sys_descr.value}")
print(f"Type: {sys_descr.type}")
2. Context Managers & Batch Queries¶
Session implements Python’s context manager protocol (with statement),
guaranteeing socket and C descriptor cleanup. Batch multiple OIDs into a single
PDU request to drastically reduce network round-trips:
from ezsnmp import Session
with Session(hostname='192.168.1.1', community='public', version=2) as session:
# Request multiple OIDs in a single network round-trip
oids = ['sysDescr.0', 'sysUpTime.0', 'sysContact.0']
results = session.get(oids)
for item in results:
print(f"{item.oid}.{item.index} ({item.type}) = {item.value}")
3. Walking an MIB Subtree¶
To traverse tabular MIB data (such as network interface descriptions), use walk():
from ezsnmp import Session
with Session(hostname='switch-01.local', community='public', version=2) as session:
# Walk interface descriptions
interfaces = session.walk('ifDescr')
for iface in interfaces:
print(f"Interface {iface.index}: {iface.value}")
4. High-Performance Bulk Walk (GETBULK)¶
When querying large routing tables, ARP caches, or interface statistics on
SNMPv2c or SNMPv3 targets, prefer bulk_walk().
This leverages SNMP GETBULK operations to pack multiple OIDs into each
network datagram:
from ezsnmp import Session
with Session(hostname='core-router.local', community='public', version=2) as session:
# bulk_walk retrieves multiple OID chunks per datagram for high throughput
route_entries = session.bulk_walk('ipRouteDest')
for route in route_entries:
print(f"Route: {route.value}")
5. Secure Enterprise SNMPv3 (authPriv)¶
For hardened enterprise infrastructure, ezsnmp supports full USM security parameters, offloading cryptographic operations (SHA authentication and AES encryption) directly to C and OpenSSL:
from ezsnmp import Session
v3_session = Session(
hostname='secure-switch.corp.internal',
version=3,
security_level='authPriv',
security_username='snmp_admin',
auth_protocol='SHA',
auth_passphrase='StrongAuthPassphrase123',
privacy_protocol='AES',
privacy_passphrase='StrongPrivPassphrase456'
)
results = v3_session.get('sysUpTime.0')
if results:
print(f"Secure Uptime: {results[0].value}")
Best Practices for Production Monitoring¶
Reuse Session Objects¶
Creating an SNMP session initializes underlying C data structures, configures
sockets, and (for SNMPv3) initiates authoritative EngineID discovery.
Instantiating new Session objects inside high-frequency polling loops adds
needless CPU and network overhead. Create sessions once per host and reuse them
across polling iterations:
# Recommended: Instantiate once and reuse across polling loops
session = Session(
hostname='pdu-01.local',
community='public',
version=2
)
def poll_metrics():
return session.get(['sysUpTime.0', 'sysDescr.0'])
Fine-Grained Exception Handling¶
ezsnmp uses a clean exception hierarchy derived from GenericError.
Avoid blanket except Exception: blocks so your telemetry engine can handle
unreachable hosts separately from agent MIB faults:
from ezsnmp import Session
from ezsnmp.exceptions import (
ConnectionError,
TimeoutError,
NoSuchObjectError,
GenericError
)
session = Session(
hostname='10.0.0.15',
community='public',
version=2,
timeout=1,
retries=1
)
try:
data = session.get('sysDescr.0')
except TimeoutError:
print("Device timed out. Verify network reachability and latency.")
except ConnectionError:
print("Failed to establish SNMP connection or resolve socket address.")
except NoSuchObjectError:
print("The requested OID does not exist on this agent.")
except GenericError as exc:
print(f"General SNMP error encountered: {exc}")
Concurrency: Multiprocessing Recommended¶
When scaling SNMP polling across large fleets of network targets, multiprocessing is the officially supported and recommended concurrency model. Because the underlying Net-SNMP C library maintains process-global state (including global MIB tables and shared socket descriptors), high-concurrency multi-threading (>4 threads) can suffer from race conditions.
Deploying Python’s concurrent.futures.ProcessPoolExecutor (or multiprocessing.Pool)
provides each worker with an isolated Net-SNMP runtime and separate memory space, delivering
reliable, linear performance across CPU cores:
from concurrent.futures import ProcessPoolExecutor, as_completed
from ezsnmp import Session
from ezsnmp.exceptions import TimeoutError, GenericError
TARGET_HOSTS = ['10.0.0.1', '10.0.0.2', '10.0.0.3']
def poll_device(host):
with Session(hostname=host, community='public', version=2, timeout=2) as session:
try:
results = session.get('sysDescr.0')
return host, results[0].value if results else None
except TimeoutError:
return host, "Timeout"
except GenericError as exc:
return host, f"Error: {exc}"
# Multiprocessing isolates Net-SNMP process state per worker
with ProcessPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(poll_device, h): h for h in TARGET_HOSTS}
for future in as_completed(futures):
host, status = future.result()
print(f"{host}: {status}")
Migration Guide: Upgrading from Easy SNMP¶
If your existing codebase uses legacy easysnmp, upgrading to ezsnmp
modernizes your network stack while providing a cleaner developer experience.
1. Dependency & Environment Check¶
Ensure your runtime is Python 3.10+.
Uninstall
easysnmpto prevent namespace collisions before installingezsnmp:pip uninstall easysnmp pip install ezsnmp
2. Modern API Differences¶
While core concepts remain familiar, ezsnmp modernizes several legacy conventions:
Feature |
Legacy Easy SNMP |
Modern ezsnmp |
|---|---|---|
Import Path |
|
|
Bulk Walk Method |
|
|
Bulk Get Method |
|
|
Get Next Method |
|
|
Exception Hierarchy |
|
|
Return Types |
Single scalar or list |
Consistent |
Result Attributes |
|
|
SNMPv3 Security Level |
|
|
SNMPv3 Passwords |
|
|
3. Code Migration Example¶
Updating your polling calls is straightforward:
# Legacy easysnmp:
# from easysnmp import Session, EasySNMPTimeoutError
# session = Session(
# hostname='10.0.0.1',
# version=3,
# security_username='snmp_admin',
# security_level='auth_with_privacy',
# auth_protocol='SHA',
# auth_password='secret',
# privacy_protocol='AES',
# privacy_password='privsecret'
# )
# val = session.bulkwalk('ifDescr')
# Modern ezsnmp:
from ezsnmp import Session
from ezsnmp.exceptions import TimeoutError
with Session(
hostname='10.0.0.1',
version=3,
security_level='authPriv',
security_username='snmp_admin',
auth_protocol='SHA',
auth_passphrase='secret',
privacy_protocol='AES',
privacy_passphrase='privsecret'
) as session:
try:
results = session.bulk_walk('ifDescr')
for item in results:
print(f"Interface {item.index}: {item.value}")
except TimeoutError:
print("Device did not respond in time.")
Useful Links & Community¶
Source Repository: carlkidcrypto/ezsnmp on GitHub
PyPI Distribution: ezsnmp Package on PyPI
Official Documentation: ezsnmp Documentation
Carlos Santos Blog: carlkidcrypto Blog