Source code for netgear_switch.protocols.snmp.client

"""Shared SNMP transport seam: row type, error, and client protocols.

Pure and I/O-free, and transport-agnostic. The net-snmp CLI (sync) and pysnmp
(async) transports both implement these protocols and return SnmpRow instances
the parsers consume. No transport-specific types appear here.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Protocol

from ...errors import NetgearSwitchError

if TYPE_CHECKING:
    from .write import SetVarbind

# snmp_type tokens meaning "no value present here".
ABSENT_TYPES: frozenset[str] = frozenset(
    {"NOSUCHOBJECT", "NOSUCHINSTANCE", "ENDOFMIBVIEW"}
)


[docs] @dataclass(frozen=True) class SnmpRow: """One SNMP varbind: full numeric OID, normalized value, type token. ``value`` is a normalized Python value so the sync (net-snmp CLI) and async (pysnmp) clients are interchangeable: ``int`` for integer-family types (INTEGER/Gauge32/Counter32/Counter64/Timeticks-numeric), ``str`` for text, OID and IP-address values, ``bytes`` for raw octet strings (Hex-STRING). Both clients MUST yield equal values for the same OID (Task 16 enforces it). """ oid: str value: int | str | bytes snmp_type: str
[docs] class SnmpError(NetgearSwitchError): """An SNMP transport operation failed (timeout, connection, agent error)."""
[docs] def full_oid(oid: str, oid_index: str) -> str: """Join an optional instance index onto a base OID as a full numeric OID. A transport may hand back the whole numeric OID in ``oid`` with an empty ``oid_index``, or split the instance into ``oid_index``. Joining both and stripping any leading dot is correct either way. """ oid = oid.lstrip(".") return f"{oid}.{oid_index}" if oid_index else oid
[docs] class SnmpClient(Protocol): """Synchronous SNMP v2c read client for a single switch."""
[docs] def get(self, oids: list[str]) -> list[SnmpRow]: ...
[docs] def walk(self, base_oid: str) -> list[SnmpRow]: ...
[docs] class AsyncSnmpClient(Protocol): """Asynchronous SNMP v2c read client for a single switch."""
[docs] async def get(self, oids: list[str]) -> list[SnmpRow]: ...
[docs] async def walk(self, base_oid: str) -> list[SnmpRow]: ...
[docs] class SnmpWriteClient(SnmpClient, Protocol): """Synchronous SNMP v2c read+write client for a single switch. Extends the read client with SET. ``set_many`` is one PDU (atomic). A write RW community can also read, so a single write client verifies its own writes via the inherited ``get``/``walk``. """
[docs] def set(self, varbind: SetVarbind) -> None: ...
[docs] def set_many(self, varbinds: list[SetVarbind]) -> None: ...
[docs] class AsyncSnmpWriteClient(AsyncSnmpClient, Protocol): """Asynchronous SNMP v2c read+write client for a single switch."""
[docs] async def set(self, varbind: SetVarbind) -> None: ...
[docs] async def set_many(self, varbinds: list[SetVarbind]) -> None: ...