Reading a switch¶
Every read below is available on SyncSwitch and AsyncSwitch alike, and each
takes an optional backend=. They return frozen dataclasses from
netgear_switch.models, identical whichever protocol produced them.
The operations¶
Method |
Returns |
Notes |
|---|---|---|
|
Port number, |
|
|
RX/TX bytes, packets and errors. Any counter a backend cannot read is
|
|
|
VLAN id, name, and three port sets: |
|
|
|
|
|
Local port plus the neighbour’s system name, chassis id, port id and port description. |
|
|
The forwarding table: MAC, port, VLAN. |
|
|
Admin state, detection state ( |
|
|
Fan, PSU and temperature readings with their units. |
|
Address, netmask, gateway, DHCP-or-static mode, and the switch’s base MAC. |
||
|
The switch’s configured host name. |
|
|
Local login accounts. |
|
|
Whether http, https, telnet and ssh are enabled, and on which port where the firmware reports one. |
|
Whether remote logging is on, the local source port, and the configured collectors. |
Not every model serves every one of these on every backend.
Support matrix has the complete grid, and
netgear_switch.capabilities.support answers the question in code.
Absent data is None, never a substitute¶
A field a backend genuinely cannot read stays None:
PortStatus.descriptionisNonewhere the backend has noifAliasequivalent — not"". NSDP is not such a backend: tag 0xB000 carries the operator’s label and was cross-checked byte-for-byte against three real GS110EMX units’ own “Port Description” column, so it fills this field like SNMP’s ifAlias does.PortStatus.nameis the opposite way round there: NSDP reports a port NUMBER and no interface identifier, sonameis None over NSDP while SNMP gives1/0/1and the web UIg1.PoEStatus.power_mwisNoneon a switch with no vendor power column (the GS728TPP serves everything from standard MIBs and has no such column) — not0.MgmtIpConfig.base_macisNonewhere the model’s web UI has no page carrying it.
An operation that cannot be answered at all raises; it does not return an empty list. An empty list means the switch really reported nothing.
Two special reads¶
identify asks the switch what it actually is, over SNMP, ignoring
the model the facade was constructed with. That is the point: use it to confirm
or discover a model when you only have a host and a community.
detected = switch.identify()
if detected.matched and detected.key != switch.model.key:
raise SystemExit(f"{switch.host} is really a {detected.key}")
detected = await switch.identify()
if detected.matched and detected.key != switch.model.key:
raise SystemExit(f"{switch.host} is really a {detected.key}")
Matching prefers sysObjectID — an unambiguous product identifier that can
distinguish SKUs whose sysDescr text is identical — and falls back to
sysDescr. key is None when neither matched; it is never a guess.
nsdp_device returns the complete raw NSDP device record for a Plus
switch: firmware, serial, DHCP mode, VLAN engine, port mirroring, IGMP snooping
and the unconverted per-port fields. It is NSDP-only by nature and bypasses
backend dispatch.
Snapshots¶
snapshot runs every read over one backend and returns a
SwitchData:
snap = switch.snapshot()
print(snap.model, snap.host, len(snap.ports), len(snap.vlans))
snap = await switch.snapshot()
print(snap.model, snap.host, len(snap.ports), len(snap.vlans))
Fields the backend cannot serve degrade to () or None. They are not
re-read over another protocol — so a snapshot describes what one protocol
really reports, rather than a blend of several. To compare protocols, take two
snapshots:
from netgear_switch import Backend
over_snmp = switch.snapshot(backend=Backend.SNMP)
over_web = switch.snapshot(backend=Backend.HTTP)
assert over_snmp.vlans == over_web.vlans
from netgear_switch import Backend
over_snmp = await switch.snapshot(backend=Backend.SNMP)
over_web = await switch.snapshot(backend=Backend.HTTP)
assert over_snmp.vlans == over_web.vlans
The cross-backend equivalence tests do exactly this; see
tests/test_cross_backend_equivalence.py.
Note
snapshot is the one place where an unsupported operation is swallowed
rather than raised, because its job is “collect what this protocol can tell
me”. Every individual get_* call still raises. If you need to know
why a field is empty, call the operation directly, or ask
netgear_switch.capabilities.support.
Reading a fleet¶
Here the two APIs stop being interchangeable: the asynchronous facade reads every switch at once, the synchronous one reads them in turn.
from netgear_switch import SyncSwitch, get_model
def sweep(hosts: list[str]) -> None:
for host in hosts:
switch = SyncSwitch(
get_model("gsm7252ps"), host=host, snmp_community="public"
)
snap = switch.snapshot()
print(snap.host, len(snap.ports))
import asyncio
from netgear_switch import AsyncSwitch, get_model
async def sweep(hosts: list[str]) -> None:
switches = [
AsyncSwitch(get_model("gsm7252ps"), host=h, snmp_community="public")
for h in hosts
]
try:
for snap in await asyncio.gather(*(s.snapshot() for s in switches)):
print(snap.host, len(snap.ports))
finally:
await asyncio.gather(*(s.aclose() for s in switches))
AsyncSwitch requires the [async] extra for SNMP
(pysnmp) and [http] for web-UI backends.
Handling refusals¶
from netgear_switch import Backend, UnsupportedCapabilityError, support
# Ask first — no network traffic:
if support(switch.model, Backend.SNMP, "get_sensors").supported:
print(switch.get_sensors(backend=Backend.SNMP))
# Or act and handle the refusal, which names the backend that refused:
try:
switch.get_poe()
except UnsupportedCapabilityError as exc:
print(exc)
from netgear_switch import Backend, UnsupportedCapabilityError, support
# support() is a plain function — the question needs no switch and no await.
if support(switch.model, Backend.SNMP, "get_sensors").supported:
print(await switch.get_sensors(backend=Backend.SNMP))
# Or act and handle the refusal, which names the backend that refused:
try:
await switch.get_poe()
except UnsupportedCapabilityError as exc:
print(exc)
Capturing what a switch says¶
ngsw capture writes a complete JSON record of a switch’s readable state.
This project’s fixtures and mock seeds are produced with it:
ngsw --switch core capture core-2026-07-31.json
See How the fake is built for how a capture becomes a mock.