Facades

SyncSwitch and AsyncSwitch are the public entry points. They expose the same operations with the same arguments, and both resolve exactly one backend per call — see Concepts.

Synchronous

Public synchronous read/write facade: SyncSwitch.

netgear_switch.sync_api.detect_model(host, *, community=None, client=None)[source]

Identify a switch’s model over SNMP, WITHOUT already knowing/hardcoding it – the discovery entry point a caller (e.g. gdoc2netcfg) uses BEFORE it can construct a SyncSwitch at all: call this first, then registry.get_model(detected.key) + SyncSwitch(...) once detected.key is not None. See models.DetectedModel / protocols.snmp.parse.detect_model_from_sysdescr for exactly how (and why) an unmatched sysDescr honestly yields key=None rather than a guess.

Builds the default net-snmp CLI client from host/community unless client is injected (tests, or an already-open connection).

Return type:

DetectedModel

class netgear_switch.sync_api.SyncSwitch(
model,
host,
*,
snmp_community=None,
snmp_client=None,
snmp_write_community=None,
snmp_write_client=None,
snmp_write_community_resolver=None,
nsdp_interface=None,
nsdp_client=None,
nsdp_write_client=None,
nsdp_password=None,
nsdp_password_resolver=None,
http_client=None,
http_password=None,
http_password_resolver=None,
cli_client=None,
protected_ports=frozenset(),
backend=None,
)[source]

Bases: object

Synchronous, model-driven read/write facade over one switch.

close()[source]

Release the HTTP client THIS facade built (never one injected by the caller). Safe to call even when no HTTP op was ever dispatched.

classmethod from_config(cfg, *, env=None)[source]
Return type:

SyncSwitch

resolve_backend(backend=None)[source]

The ONE backend an op with this backend argument will run on.

Resolution order: the per-call backend, else this facade’s default (the backend= constructor argument), else the first backend in _BACKEND_PREFERENCE this model declares (SNMP > NSDP > HTTP > SSH > TELNET > CONSOLE). A named backend the model does not have raises rather than falling back to one it does. Public so a caller can ask “what would this op talk to?” without performing it.

Return type:

Backend

get_ports(*, backend=None)[source]
Return type:

list[PortStatus]

get_stats(*, backend=None)[source]
Return type:

list[PortStats]

get_vlans(*, backend=None)[source]
Return type:

list[VLANInfo]

get_pvids(*, backend=None)[source]
Return type:

list[tuple[int, int]]

get_lldp(*, backend=None)[source]
Return type:

list[LLDPNeighbor]

get_macs(*, backend=None)[source]
Return type:

list[MacEntry]

get_poe(*, backend=None)[source]
Return type:

list[PoEStatus]

get_sensors(*, backend=None)[source]
Return type:

list[Sensor]

get_mgmt_ip(*, backend=None)[source]
Return type:

MgmtIpConfig

get_syslog(*, backend=None)[source]

Remote-logging configuration: whether it is on, and its collectors.

Served over SNMP and the FASTPATH CLI, which agree field-for-field on live hardware. gs728tpp is refused over SNMP by name: it registers no Netgear vendor subtree, and the logging columns are vendor-only, so an empty result would be indistinguishable from a switch with no collectors configured.

Return type:

SyslogConfig

get_users(*, backend=None)[source]

The switch’s local login accounts.

Served over the FASTPATH CLI. SwitchUser.access_mode keeps the firmware’s own wording, which differs between images, and privileged carries the normalised reading.

Return type:

list[SwitchUser]

get_services(*, backend=None)[source]

Which management services are enabled, and on which ports.

Served over the FASTPATH CLI. A port of None means the firmware reports no port for that service, not that a default applies.

Return type:

list[ServiceStatus]

get_hostname(*, backend=None)[source]

The switch’s host name.

Every backend can answer, but from a different place: SNMP reads the standard sysName scalar, NSDP the HOSTNAME tag, the CLI show hosts, and the web UI the device-identity page – on the two dialects whose page carries the field; the rest refuse by name.

The CLI deliberately parses show hosts rather than show running-config: the two report different values on real hardware, and only show hosts agrees with sysName.

Return type:

str

nsdp_device()[source]

Return the COMPLETE raw NsdpDevice for this switch: model, MAC, hostname, mgmt IP, firmware, DHCP mode, port count, serial number, VLAN engine, raw per-port status (speed byte NOT pre-converted to Mbps – see protocols.nsdp.types.NsdpPortStatus.speed) and statistics, VLAN membership, PVIDs, plus QoS engine/mirroring/IGMP snooping/broadcast filtering/loop detection.

Unlike every other read op, this deliberately bypasses the SNMP/NSDP/HTTP backend-preference dispatch (_read): NSDP is the ONLY backend that can serve it, so a model without an NSDP backend raises UnsupportedCapabilityError directly (mirroring identify()’s bypass of that dispatch below, and NsdpReader.__init__’s own _require_nsdp guard).

Return type:

NsdpDevice

identify()[source]

Detect this switch’s ACTUAL model via SNMP sysDescr, independent of self.model.

Unlike every other read/write op, this deliberately bypasses the per-op SNMP/NSDP/HTTP backend-preference dispatch (_read) AND the self.model SNMP-backend gate entirely: it exists precisely to confirm/discover a switch’s real model when the caller does not yet trust the model this facade happens to have been constructed with (e.g. a placeholder used only to carry host/credentials). Reuses an injected snmp_client/snmp_community exactly like _reader_for(Backend.SNMP) would, but never requires self.model.backends to include SNMP.

Return type:

DetectedModel

snapshot(*, backend=None)[source]

Aggregate every read op over ONE backend.

backend names it; None uses this model’s default backend (see resolve_backend). A field that backend cannot serve degrades to ()/None – it is NOT re-read over a different protocol, so a snapshot describes what one protocol really reports rather than a blend of several. Take two snapshots (one per backend) to compare protocols.

Return type:

SwitchData

set_poe(port, on, *, force=False, backend=None)[source]
set_port_enabled(port, enabled, *, force=False, backend=None)[source]
set_port_description(port, description, *, force=False, backend=None)[source]

Label a port. Pass "" to clear the label.

Cosmetic: it moves no traffic and cannot strand a switch, so it is not force-gated. force only overrides the protected-port guard, for callers who have marked their uplinks protected.

set_port_speed(port, speed, *, force=False, backend=None)[source]

Force a port’s speed/duplex, or return it to auto-negotiation.

Disruptive – applying either bounces the link – so force is what overrides the protected-port guard, as for set_pvid.

Served over the FASTPATH CLI. The read that pairs with it is PortStatus.speed_config, NOT speed_mbps: the first is what the port is configured to, the second what its link negotiated, and on a down port only the first has an answer.

A forced 1000 Mbit/s is refused by name: 1000BASE-T requires auto-negotiation, and the firmware’s speed grammar omits it accordingly (measured – the switch answers “% Invalid input”).

set_flow_control(port, enabled, *, force=False, backend=None)[source]

Turn IEEE 802.3x flow control on or off for a port.

Served over the FASTPATH CLI. The read that pairs with it is PortStatus.flow_control, which on that backend is the CONFIGURED setting – show port’s Flow Mode column moves on a link-down port.

set_pvid(port, vlan, *, force=False, backend=None)[source]
set_vlan_membership(vlan, port, mode, *, force=False, backend=None)[source]
create_vlan(vlan, name, *, force=False, backend=None)[source]
delete_vlan(vlan, *, force=False, backend=None)[source]
cycle_poe(port, *, force=False, timeouts=_DEFAULT_POE_TIMEOUTS, backend=None)[source]
clear_poe_fault(
port,
*,
force=False,
timeouts=_DEFAULT_POE_TIMEOUTS,
backend=None,
)[source]
set_syslog_enabled(enabled, *, force=False, backend=None)[source]

Turn remote syslog on or off.

Served over SNMP. Adding or removing a COLLECTOR is a separate, unbuilt operation – it needs a row-status write that has not been driven against hardware.

add_syslog_collector(
host,
*,
port=514,
severity=6,
force=False,
backend=None,
)[source]

Add a remote syslog collector, and read it back to confirm.

Served over the FASTPATH CLI. severity is the standard syslog number (0 emergency .. 7 debug); the switch forwards messages at or above it. Refuses if a collector for host already exists, because the firmware would otherwise add a second row for the same address and silently duplicate delivery.

remove_syslog_collector(host, *, force=False, backend=None)[source]

Remove a remote syslog collector, and read back to confirm it is gone.

Served over the FASTPATH CLI. Refuses if no collector for host is configured, rather than removing a row that is not there.

set_hostname(name, *, force=False, backend=None)[source]

Set the switch’s host name, and read it back to confirm.

Written over SNMP (sysName) or the FASTPATH CLI (hostname), whichever backend resolves. Not force-gated: renaming cannot strand a switch and is reversible by writing the old name back, unlike set_mgmt_ip, which drops the session that issues it.

The Plus models cannot be renamed by this library yet – they have neither backend, and the NSDP and web-UI routes are unbuilt.

set_mgmt_ip(address, netmask, gateway, *, force=False, backend=None)[source]
upload_certificate(cert_pem, key_pem, *, force=False)[source]

Upload an HTTPS SSL server certificate + private key to the switch.

Implemented for gsm7228ps/S3300 (grounded multipart web-UI upload). Disruptive (replaces the running certificate), so force=True is required. A model whose real mechanism is known but not yet implemented (m4300 SCP, gs728tpp XML-API) raises NotImplementedError naming the mechanism – NOT UnsupportedCapabilityError, since the hardware can do it; a model with no HTTP backend and no known mechanism raises UnsupportedCapabilityError.

upload_certificate_scp(*, scp_source, scp_password, remote_dir, chain=False)[source]

Deploy an HTTPS SSL server certificate to a FASTPATH switch over SCP.

For the Fully Managed FASTPATH line (M4300 / GSM7252PS) only, whose firmware pulls the certificate with copy scp://<src> nvram:sslpem-server rather than an HTTP form. Runs the disable-HTTPS -> copy(server) -> optional copy(root chain) -> re-enable-HTTPS -> save-config sequence over the library’s existing CLI transport (SSH by default). Re-enabling HTTPS loads the new certificate in place; the switch is NOT rebooted.

The CALLER must have STAGED the PEM(s) on the SCP source first: the switch pulls <host-with-dots-as-dashes>-server.pem (and, when chain is set, <...>-root.pem) from remote_dir on scp_source (a user@host[:port] string). This library only SENDS the copy commands (per the design decision) – it does not run the staging SCP server.

Dispatched ONLY for FASTPATH models with a known copy-scp profile (m4300-24x/-16x, gsm7252ps); every other model – including FASTPATH gsm7228ps, whose cert upload is HTTP multipart – raises UnsupportedCapabilityError.

HONESTY: GROUNDED in the certbot-hook FastpathScpUpdater prior art and MOCK-TESTED end-to-end, but NOT live-verified (a real run is a production write needing a staging SCP server, which CI lacks) – see cli_write.deploy_certificate_scp.

Asynchronous

Public asynchronous read/write facade: AsyncSwitch (mirror of SyncSwitch).

async netgear_switch.aio_api.async_detect_model(host, *, community=None, client=None)[source]

Async twin of sync_api.detect_model – see there.

Return type:

DetectedModel

class netgear_switch.aio_api.AsyncSwitch(
model,
host,
*,
snmp_community=None,
snmp_client=None,
snmp_write_community=None,
snmp_write_client=None,
snmp_write_community_resolver=None,
nsdp_interface=None,
nsdp_client=None,
nsdp_write_client=None,
nsdp_password=None,
nsdp_password_resolver=None,
http_client=None,
http_password=None,
http_password_resolver=None,
protected_ports=frozenset(),
backend=None,
)[source]

Bases: object

Asynchronous, model-driven read/write facade over one switch.

async aclose()[source]

Release the HTTP client THIS facade built (never one injected by the caller). Safe to call even when no HTTP op was ever dispatched.

classmethod from_config(cfg, *, env=None)[source]
Return type:

AsyncSwitch

resolve_backend(backend=None)[source]

Async twin of SyncSwitch.resolve_backend – see there.

Return type:

Backend

async get_ports(*, backend=None)[source]
Return type:

list[PortStatus]

async get_stats(*, backend=None)[source]
Return type:

list[PortStats]

async get_vlans(*, backend=None)[source]
Return type:

list[VLANInfo]

async get_pvids(*, backend=None)[source]
Return type:

list[tuple[int, int]]

async get_lldp(*, backend=None)[source]
Return type:

list[LLDPNeighbor]

async get_macs(*, backend=None)[source]
Return type:

list[MacEntry]

async get_poe(*, backend=None)[source]
Return type:

list[PoEStatus]

async get_sensors(*, backend=None)[source]
Return type:

list[Sensor]

async get_mgmt_ip(*, backend=None)[source]
Return type:

MgmtIpConfig

async get_syslog(*, backend=None)[source]

Async twin of SyncSwitch.get_syslog – see there.

Return type:

SyslogConfig

async get_users(*, backend=None)[source]

Async twin of SyncSwitch.get_users.

AsyncSwitch has no CLI backend, so this refuses on every model – the same honest refusal every async CLI read gives.

Return type:

list[SwitchUser]

async get_services(*, backend=None)[source]

Async twin of SyncSwitch.get_services – CLI only, see there.

Return type:

list[ServiceStatus]

async get_hostname(*, backend=None)[source]

Async twin of SyncSwitch.get_hostname – see there.

Return type:

str

async nsdp_device()[source]

Async twin of SyncSwitch.nsdp_device – see there.

Return type:

NsdpDevice

async identify()[source]

Async twin of SyncSwitch.identify – see there.

Return type:

DetectedModel

async snapshot(*, backend=None)[source]

Async twin of SyncSwitch.snapshot: every read op over ONE backend (named, or this model’s default), with a field that backend cannot serve degrading to ()/None rather than being re-read over another protocol.

Return type:

SwitchData

async set_poe(port, on, *, force=False, backend=None)[source]
async set_port_enabled(port, enabled, *, force=False, backend=None)[source]
async set_port_description(port, description, *, force=False, backend=None)[source]

Async twin of SyncSwitch.set_port_description – see it.

async set_port_speed(port, speed, *, force=False, backend=None)[source]

Async twin of SyncSwitch.set_port_speed – see it.

Every async backend refuses this: the operation is served over the FASTPATH CLI, and all three CLI transports (paramiko SSH, telnet, pyserial console) are synchronous, so AsyncSwitch has no CLI backend at all. Kept present so the refusal names the backend rather than surfacing as AttributeError.

async set_flow_control(port, enabled, *, force=False, backend=None)[source]

Async twin of SyncSwitch.set_flow_control – see it.

Like set_port_speed, every async backend refuses: this op is served over the CLI, whose three transports are all synchronous.

async set_pvid(port, vlan, *, force=False, backend=None)[source]
async set_vlan_membership(vlan, port, mode, *, force=False, backend=None)[source]
async create_vlan(vlan, name, *, force=False, backend=None)[source]
async delete_vlan(vlan, *, force=False, backend=None)[source]
async cycle_poe(
port,
*,
force=False,
timeouts=_DEFAULT_POE_TIMEOUTS,
backend=None,
)[source]
async clear_poe_fault(
port,
*,
force=False,
timeouts=_DEFAULT_POE_TIMEOUTS,
backend=None,
)[source]
async set_syslog_enabled(enabled, *, force=False, backend=None)[source]

Turn remote syslog on or off.

Served over SNMP here. The CLI serves it too, but not asynchronously – all three CLI transports are synchronous, so AsyncSwitch has no CLI backend.

async add_syslog_collector(
host,
*,
port=514,
severity=6,
force=False,
backend=None,
)[source]

Async twin of SyncSwitch.add_syslog_collector – see it.

Every async backend refuses: this op is served over the CLI, whose transports are all synchronous.

async remove_syslog_collector(host, *, force=False, backend=None)[source]

Async twin of SyncSwitch.remove_syslog_collector – see it.

async set_hostname(name, *, force=False, backend=None)[source]

Async twin of SyncSwitch.set_hostname – see there.

Note AsyncSwitch has no CLI backend at all, so on this facade the write resolves over SNMP or not at all.

async set_mgmt_ip(address, netmask, gateway, *, force=False, backend=None)[source]
async upload_certificate(cert_pem, key_pem, *, force=False)[source]

Async twin of SyncSwitch.upload_certificate – see there.

async upload_certificate_scp(*, scp_source, scp_password, remote_dir, chain=False)[source]

Async twin of SyncSwitch.upload_certificate_scp (FASTPATH SCP cert deploy). The op is CLI/SCP-based, and CLI transports are SYNCHRONOUS – the async facade has no CLI backend (the same reason async CLI reads and writes are unavailable). The method EXISTS for API-surface parity but honestly raises UnsupportedCapabilityError rather than silently lacking it: use SyncSwitch.upload_certificate_scp for this op.

Backend resolution

The seam both facades share: which backend an operation runs on, and how each backend’s client is built. Internal, but documented because its behaviour is the library’s central guarantee.

Internal backend-resolution seam shared by SyncSwitch and AsyncSwitch.

Only SNMP is wired in this slice. Model-driven dispatch lives here so the two facades stay identical and Slices 5/6 can add NSDP/HTTP backends without touching the public facade surface. Transport imports are function-local so import netgear_switch never requires net-snmp binaries or pysnmp.

netgear_switch._dispatch.resolve_backend(model, requested, preference)[source]

Pick THE ONE backend an op will run on – no silent substitution.

Shared by SyncSwitch/AsyncSwitch so both facades resolve identically.

  • requested given: that exact backend, or UnsupportedCapabilityError if the model does not have it. The caller asked for a protocol; getting a different one back would make any claim about that protocol worthless (the concrete bug this replaced: HttpReader.get_vlans returning empty untagged_ports went unnoticed for months because the facade quietly answered from SNMP instead).

  • requested None: the FIRST backend in preference the model declares. Deterministic and independent of the op – the facade never probes one backend, catches its refusal and tries the next.

Note this resolves a backend the MODEL has, not one that necessarily implements the op: an op the resolved backend cannot serve raises rather than being re-routed. That is the point.

Return type:

Backend

netgear_switch._dispatch.require_snmp_backend(model)[source]

Raise unless the model exposes an SNMP read backend.

netgear_switch._dispatch.require_mac_table(model)[source]

Raise unless the model has a readable MAC/FDB table.

netgear_switch._dispatch.require_nsdp_backend(model)[source]

Raise unless the model exposes an NSDP backend.

netgear_switch._dispatch.build_sync_snmp_client(host, community)[source]

Default sync SNMP client (net-snmp CLI). Imported lazily.

Return type:

SnmpClient

netgear_switch._dispatch.build_async_snmp_client(host, community)[source]

Default async SNMP client (pysnmp). Imported lazily.

Return type:

AsyncSnmpClient

netgear_switch._dispatch.build_sync_snmp_write_client(host, write_community)[source]

Default sync SNMP write client (net-snmp CLI). Imported lazily.

Return type:

SnmpWriteClient

netgear_switch._dispatch.build_async_snmp_write_client(host, write_community)[source]

Default async SNMP write client (pysnmp). Imported lazily.

Return type:

AsyncSnmpWriteClient

netgear_switch._dispatch.build_sync_nsdp_client(host, interface)[source]

Default sync NSDP client (UDP). One client does read AND write, so the return is annotated with the richer NsdpWriteClient protocol (the concrete UdpNsdpClient satisfies it; NsdpWriteClient extends NsdpClient so a read-only caller can still use it). Lazy import.

Return type:

NsdpWriteClient

netgear_switch._dispatch.build_async_nsdp_client(host, interface)[source]

Default async NSDP client (asyncio UDP). Read AND write; annotated with the richer AsyncNsdpWriteClient protocol (AsyncUdpNsdpClient satisfies it and it extends AsyncNsdpClient, so _reader() can still accept it). Lazy import.

Return type:

AsyncNsdpWriteClient

netgear_switch._dispatch.require_http_backend(model)[source]

Raise unless the model exposes an HTTP web-UI backend.

netgear_switch._dispatch.http_reads_supported(model)[source]

True only if the model’s web reads/writes are grounded (reads_verified).

The facade uses this to decide whether HTTP may join the per-op backend fallback. An UNVERIFIED-pending-capture model (gsm7228ps cheetah) returns False: its HTTP path is never used for read/write dispatch (SNMP stays authoritative; HTTP is reserved for firmware/reboot). gs110emx’s Gambit login + sysInfo/interface_stats reads ARE grounded (see protocols/http/endpoints.py), so this returns True for it – but NSDP is still authoritative for every op it serves (ports/VLANs/PVIDs/stats/ mgmt-IP). In practice this means gs110emx’s HTTP get_stats/get_mgmt_ip are NEVER actually reached through the SyncSwitch/AsyncSwitch facade: NSDP’s own get_stats/get_mgmt_ip always return a result rather than raising UnsupportedCapabilityError (unlike get_macs/get_lldp/ get_sensors/get_poe, which NSDP genuinely doesn’t have and does raise for), so the facade’s per-op backend loop never falls through to HTTP for those two ops – only a directly-constructed HttpReader/AsyncHttpReader exercises them. The lazy import keeps import netgear_switch clear of the endpoints module on the hot path (endpoints is pure, but this mirrors the other lazy builders).

Return type:

bool

netgear_switch._dispatch.require_cli_backend(model)[source]

Raise unless the model exposes a CLI (SSH/telnet/console) backend.

netgear_switch._dispatch.cli_reads_supported(model)[source]

True only once a model’s CLI reads are cross-verified (reads_verified).

Mirrors http_reads_supported. A CLI spec with reads_verified=False (CLI reader output not yet cross-verified against SNMP on live hardware) gates OFF. The FASTPATH models (m4300-24x/-16x, gsm7252ps) are now verified and return True; other models return False and the facade never dispatches a live read to their CLI backend. The parsers and the in-process mock CLI face are exercised independently of this gate.

Return type:

bool

netgear_switch._dispatch.cli_writes_supported(model)[source]

True once a model’s CLI WRITE path is live-verified (writes_verified).

Requires reads_verified too, and not incidentally: every CLI write verifies itself by reading back through CliReader, so a model whose CLI reads are not trusted cannot honestly verify a CLI write either. All four FASTPATH CLI models are verified (see CliModelSpec.writes_verified for the per-model live-run evidence).

Return type:

bool

netgear_switch._dispatch.build_sync_cli_client(host, username, password, model)[source]

Build the sync CLI session for model.

SSH (paramiko) is the default network CLI transport. A model that exposes TELNET but NOT SSH (the S3300-52X / gsm7228ps, whose FASTPATH CLI listens on telnet port 60000 with no ssh listener anywhere) instead gets the telnet transport dialled at cli_spec(model).telnet_port. Both transports are imported lazily. The console transport is never auto-selected here (it needs a serial device path, not a host).

Return type:

CliSession

netgear_switch._dispatch.build_sync_http_client(host, password, model)[source]

Default sync web-UI client (httpx). Imported lazily.

Return type:

HttpClient

netgear_switch._dispatch.build_async_http_client(host, password, model)[source]

Default async web-UI client (httpx). Imported lazily.

Return type:

AsyncHttpClient