python-netgear-switch-library 0.0.post154__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netgear_switch/__init__.py +132 -0
- netgear_switch/_dispatch.py +178 -0
- netgear_switch/_version.py +24 -0
- netgear_switch/aio_api.py +529 -0
- netgear_switch/cli/__init__.py +1 -0
- netgear_switch/cli/capture.py +131 -0
- netgear_switch/cli/context.py +39 -0
- netgear_switch/cli/format.py +201 -0
- netgear_switch/cli/main.py +484 -0
- netgear_switch/cli/resolve.py +108 -0
- netgear_switch/cli/safety.py +71 -0
- netgear_switch/config.py +184 -0
- netgear_switch/errors.py +52 -0
- netgear_switch/http_read.py +174 -0
- netgear_switch/http_write.py +420 -0
- netgear_switch/models.py +156 -0
- netgear_switch/nsdp_read.py +221 -0
- netgear_switch/nsdp_write.py +315 -0
- netgear_switch/protocols/__init__.py +1 -0
- netgear_switch/protocols/http/__init__.py +1 -0
- netgear_switch/protocols/http/crypt.py +29 -0
- netgear_switch/protocols/http/endpoints.py +165 -0
- netgear_switch/protocols/http/forms.py +77 -0
- netgear_switch/protocols/http/parse.py +238 -0
- netgear_switch/protocols/http/session.py +29 -0
- netgear_switch/protocols/nsdp/__init__.py +7 -0
- netgear_switch/protocols/nsdp/auth.py +33 -0
- netgear_switch/protocols/nsdp/client.py +67 -0
- netgear_switch/protocols/nsdp/parsers.py +209 -0
- netgear_switch/protocols/nsdp/protocol.py +201 -0
- netgear_switch/protocols/nsdp/types.py +137 -0
- netgear_switch/protocols/nsdp/write.py +98 -0
- netgear_switch/protocols/snmp/__init__.py +1 -0
- netgear_switch/protocols/snmp/client.py +88 -0
- netgear_switch/protocols/snmp/oids.py +125 -0
- netgear_switch/protocols/snmp/parse.py +777 -0
- netgear_switch/protocols/snmp/write.py +112 -0
- netgear_switch/py.typed +0 -0
- netgear_switch/registry.py +227 -0
- netgear_switch/snmp_read.py +226 -0
- netgear_switch/snmp_write.py +625 -0
- netgear_switch/sync_api.py +557 -0
- netgear_switch/transport/__init__.py +1 -0
- netgear_switch/transport/aio/__init__.py +1 -0
- netgear_switch/transport/aio/nsdp_udp.py +152 -0
- netgear_switch/transport/aio/snmp_pysnmp.py +247 -0
- netgear_switch/transport/http/__init__.py +1 -0
- netgear_switch/transport/http/client.py +217 -0
- netgear_switch/transport/sync/__init__.py +1 -0
- netgear_switch/transport/sync/nsdp_udp.py +109 -0
- netgear_switch/transport/sync/snmp_netsnmp_cli.py +257 -0
- netgear_switch/virtual/__init__.py +8 -0
- netgear_switch/virtual/faces/__init__.py +2 -0
- netgear_switch/virtual/faces/http.py +164 -0
- netgear_switch/virtual/faces/mibview.py +92 -0
- netgear_switch/virtual/faces/nsdp.py +124 -0
- netgear_switch/virtual/faces/snmp.py +412 -0
- netgear_switch/virtual/seed.py +220 -0
- netgear_switch/virtual/server.py +106 -0
- netgear_switch/virtual/state.py +615 -0
- netgear_switch/virtual/web.py +210 -0
- python_netgear_switch_library-0.0.post154.dist-info/METADATA +85 -0
- python_netgear_switch_library-0.0.post154.dist-info/RECORD +66 -0
- python_netgear_switch_library-0.0.post154.dist-info/WHEEL +4 -0
- python_netgear_switch_library-0.0.post154.dist-info/entry_points.txt +2 -0
- python_netgear_switch_library-0.0.post154.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Python Netgear Switch Interface Library.
|
|
2
|
+
|
|
3
|
+
Query and control Netgear switches over SNMP, NSDP and the HTTP web UI
|
|
4
|
+
behind one model-driven API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import PackageNotFoundError
|
|
8
|
+
from importlib.metadata import version as _pkg_version
|
|
9
|
+
|
|
10
|
+
from .aio_api import AsyncSwitch, async_detect_model
|
|
11
|
+
from .config import (
|
|
12
|
+
SwitchConfig,
|
|
13
|
+
ensure_secure_file,
|
|
14
|
+
load_inventory,
|
|
15
|
+
resolve_secret,
|
|
16
|
+
)
|
|
17
|
+
from .errors import (
|
|
18
|
+
ConfigError,
|
|
19
|
+
CredentialError,
|
|
20
|
+
HttpAuthError,
|
|
21
|
+
HttpError,
|
|
22
|
+
HttpUnexpectedPageError,
|
|
23
|
+
NetgearSwitchError,
|
|
24
|
+
ProtectedPortError,
|
|
25
|
+
UnknownModelError,
|
|
26
|
+
UnsupportedCapabilityError,
|
|
27
|
+
WriteVerificationError,
|
|
28
|
+
)
|
|
29
|
+
from .models import (
|
|
30
|
+
DetectedModel,
|
|
31
|
+
IpMode,
|
|
32
|
+
LLDPNeighbor,
|
|
33
|
+
MacEntry,
|
|
34
|
+
MgmtIpConfig,
|
|
35
|
+
PoEDetect,
|
|
36
|
+
PoEStatus,
|
|
37
|
+
PortStats,
|
|
38
|
+
PortStatus,
|
|
39
|
+
Sensor,
|
|
40
|
+
SwitchData,
|
|
41
|
+
VLANInfo,
|
|
42
|
+
VlanMode,
|
|
43
|
+
)
|
|
44
|
+
from .protocols.nsdp.types import (
|
|
45
|
+
LinkSpeed as NsdpLinkSpeed,
|
|
46
|
+
)
|
|
47
|
+
from .protocols.nsdp.types import (
|
|
48
|
+
NsdpDevice,
|
|
49
|
+
NsdpIgmpSnooping,
|
|
50
|
+
NsdpPortMirroring,
|
|
51
|
+
NsdpPortPvid,
|
|
52
|
+
NsdpPortStatistics,
|
|
53
|
+
NsdpPortStatus,
|
|
54
|
+
NsdpVlanMembership,
|
|
55
|
+
)
|
|
56
|
+
from .protocols.nsdp.types import (
|
|
57
|
+
VLANEngine as NsdpVlanEngine,
|
|
58
|
+
)
|
|
59
|
+
from .registry import MODELS, Backend, SwitchClass, SwitchModel, get_model
|
|
60
|
+
from .sync_api import SyncSwitch, detect_model
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Normal case: the package is installed (editable or wheel) and its
|
|
64
|
+
# dist-info carries the version hatch-vcs derived from git at build time.
|
|
65
|
+
__version__: str = _pkg_version("python-netgear-switch-library")
|
|
66
|
+
except PackageNotFoundError: # pragma: no cover - only hit outside an installed env
|
|
67
|
+
# Fallback for running with no install metadata available at all (e.g. a
|
|
68
|
+
# bare source checkout added straight to sys.path without an install).
|
|
69
|
+
# Deliberately does NOT import the hatch-vcs-generated `_version.py`: that
|
|
70
|
+
# file is gitignored and absent on a fresh checkout, and importing it here
|
|
71
|
+
# would make `mypy --strict` fail in that state (import-not-found) while
|
|
72
|
+
# a `# type: ignore` on the import would itself be flagged unused
|
|
73
|
+
# (no-redef) once the file exists after a build. Every installed/editable
|
|
74
|
+
# environment (dev, CI, wheel) already resolves the metadata version
|
|
75
|
+
# above, so this branch is unreachable there.
|
|
76
|
+
__version__ = "0.0.dev0+unknown"
|
|
77
|
+
|
|
78
|
+
__all__ = [ # noqa: RUF022 -- grouped by source module below, not alphabetical
|
|
79
|
+
"__version__",
|
|
80
|
+
# errors
|
|
81
|
+
"NetgearSwitchError",
|
|
82
|
+
"ConfigError",
|
|
83
|
+
"CredentialError",
|
|
84
|
+
"UnknownModelError",
|
|
85
|
+
"UnsupportedCapabilityError",
|
|
86
|
+
"WriteVerificationError",
|
|
87
|
+
"ProtectedPortError",
|
|
88
|
+
"HttpError",
|
|
89
|
+
"HttpAuthError",
|
|
90
|
+
"HttpUnexpectedPageError",
|
|
91
|
+
# models
|
|
92
|
+
"PortStatus",
|
|
93
|
+
"PoEStatus",
|
|
94
|
+
"PoEDetect",
|
|
95
|
+
"VLANInfo",
|
|
96
|
+
"VlanMode",
|
|
97
|
+
"IpMode",
|
|
98
|
+
"LLDPNeighbor",
|
|
99
|
+
"MacEntry",
|
|
100
|
+
"PortStats",
|
|
101
|
+
"MgmtIpConfig",
|
|
102
|
+
"Sensor",
|
|
103
|
+
"SwitchData",
|
|
104
|
+
"DetectedModel",
|
|
105
|
+
# NSDP full-device types (SyncSwitch.nsdp_device()/AsyncSwitch.nsdp_device())
|
|
106
|
+
"NsdpDevice",
|
|
107
|
+
"NsdpPortStatus",
|
|
108
|
+
"NsdpPortStatistics",
|
|
109
|
+
"NsdpVlanMembership",
|
|
110
|
+
"NsdpPortPvid",
|
|
111
|
+
"NsdpPortMirroring",
|
|
112
|
+
"NsdpIgmpSnooping",
|
|
113
|
+
"NsdpLinkSpeed",
|
|
114
|
+
"NsdpVlanEngine",
|
|
115
|
+
# registry
|
|
116
|
+
"Backend",
|
|
117
|
+
"SwitchClass",
|
|
118
|
+
"SwitchModel",
|
|
119
|
+
"MODELS",
|
|
120
|
+
"get_model",
|
|
121
|
+
# config
|
|
122
|
+
"SwitchConfig",
|
|
123
|
+
"resolve_secret",
|
|
124
|
+
"load_inventory",
|
|
125
|
+
"ensure_secure_file",
|
|
126
|
+
# facades
|
|
127
|
+
"SyncSwitch",
|
|
128
|
+
"AsyncSwitch",
|
|
129
|
+
# model detection (Task 2)
|
|
130
|
+
"detect_model",
|
|
131
|
+
"async_detect_model",
|
|
132
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Internal backend-resolution seam shared by SyncSwitch and AsyncSwitch.
|
|
2
|
+
|
|
3
|
+
Only SNMP is wired in this slice. Model-driven dispatch lives here so the two
|
|
4
|
+
facades stay identical and Slices 5/6 can add NSDP/HTTP backends without
|
|
5
|
+
touching the public facade surface. Transport imports are function-local so
|
|
6
|
+
``import netgear_switch`` never requires net-snmp binaries or pysnmp.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from .errors import CredentialError, UnsupportedCapabilityError
|
|
13
|
+
from .registry import Backend
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .protocols.nsdp.client import AsyncNsdpWriteClient, NsdpWriteClient
|
|
17
|
+
from .protocols.snmp.client import (
|
|
18
|
+
AsyncSnmpClient,
|
|
19
|
+
AsyncSnmpWriteClient,
|
|
20
|
+
SnmpClient,
|
|
21
|
+
SnmpWriteClient,
|
|
22
|
+
)
|
|
23
|
+
from .registry import SwitchModel
|
|
24
|
+
from .transport.http.client import AsyncHttpClient, HttpClient
|
|
25
|
+
|
|
26
|
+
BACKEND_NOT_IMPLEMENTED = (
|
|
27
|
+
"model {key!r} has no SNMP backend; its NSDP backend is used instead. "
|
|
28
|
+
"(An HTTP-only capability is not implemented until Slice 6.)"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def require_snmp_backend(model: SwitchModel) -> None:
|
|
33
|
+
"""Raise unless the model exposes an SNMP read backend."""
|
|
34
|
+
if Backend.SNMP not in model.backends:
|
|
35
|
+
raise UnsupportedCapabilityError(
|
|
36
|
+
BACKEND_NOT_IMPLEMENTED.format(key=model.key)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_mac_table(model: SwitchModel) -> None:
|
|
41
|
+
"""Raise unless the model has a readable MAC/FDB table."""
|
|
42
|
+
if not model.has_mac_table:
|
|
43
|
+
raise UnsupportedCapabilityError(
|
|
44
|
+
f"model {model.key!r} has no MAC/FDB table"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def require_nsdp_backend(model: SwitchModel) -> None:
|
|
49
|
+
"""Raise unless the model exposes an NSDP backend."""
|
|
50
|
+
if Backend.NSDP not in model.backends:
|
|
51
|
+
raise UnsupportedCapabilityError(
|
|
52
|
+
f"model {model.key!r} has no NSDP backend"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _require_community(host: str, community: str | None) -> str:
|
|
57
|
+
if community is None:
|
|
58
|
+
raise CredentialError(
|
|
59
|
+
f"no SNMP read community configured for {host!r}"
|
|
60
|
+
)
|
|
61
|
+
return community
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_sync_snmp_client(host: str, community: str | None) -> SnmpClient:
|
|
65
|
+
"""Default sync SNMP client (net-snmp CLI). Imported lazily."""
|
|
66
|
+
from .transport.sync.snmp_netsnmp_cli import NetsnmpCliClient
|
|
67
|
+
|
|
68
|
+
return NetsnmpCliClient(host, _require_community(host, community))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_async_snmp_client(host: str, community: str | None) -> AsyncSnmpClient:
|
|
72
|
+
"""Default async SNMP client (pysnmp). Imported lazily."""
|
|
73
|
+
from .transport.aio.snmp_pysnmp import PysnmpClient
|
|
74
|
+
|
|
75
|
+
return PysnmpClient(host, _require_community(host, community))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _require_write_community(host: str, community: str | None) -> str:
|
|
79
|
+
# An empty string must be rejected too, not just None -- otherwise an
|
|
80
|
+
# unresolved/blank write-community spec could silently flow through to
|
|
81
|
+
# `snmpset -c ""` instead of raising (carry-forward review fix).
|
|
82
|
+
if not community:
|
|
83
|
+
raise CredentialError(
|
|
84
|
+
f"no SNMP write community configured for {host!r}"
|
|
85
|
+
)
|
|
86
|
+
return community
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def build_sync_snmp_write_client(
|
|
90
|
+
host: str, write_community: str | None
|
|
91
|
+
) -> SnmpWriteClient:
|
|
92
|
+
"""Default sync SNMP write client (net-snmp CLI). Imported lazily."""
|
|
93
|
+
from .transport.sync.snmp_netsnmp_cli import NetsnmpCliClient
|
|
94
|
+
|
|
95
|
+
return NetsnmpCliClient(host, _require_write_community(host, write_community))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_async_snmp_write_client(
|
|
99
|
+
host: str, write_community: str | None
|
|
100
|
+
) -> AsyncSnmpWriteClient:
|
|
101
|
+
"""Default async SNMP write client (pysnmp). Imported lazily."""
|
|
102
|
+
from .transport.aio.snmp_pysnmp import PysnmpClient
|
|
103
|
+
|
|
104
|
+
return PysnmpClient(host, _require_write_community(host, write_community))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_sync_nsdp_client(host: str, interface: str | None) -> NsdpWriteClient:
|
|
108
|
+
"""Default sync NSDP client (UDP). One client does read AND write, so the
|
|
109
|
+
return is annotated with the richer ``NsdpWriteClient`` protocol (the
|
|
110
|
+
concrete ``UdpNsdpClient`` satisfies it; ``NsdpWriteClient`` extends
|
|
111
|
+
``NsdpClient`` so a read-only caller can still use it). Lazy import."""
|
|
112
|
+
from .transport.sync.nsdp_udp import UdpNsdpClient
|
|
113
|
+
|
|
114
|
+
return UdpNsdpClient(host, interface=interface)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build_async_nsdp_client(host: str, interface: str | None) -> AsyncNsdpWriteClient:
|
|
118
|
+
"""Default async NSDP client (asyncio UDP). Read AND write; annotated with
|
|
119
|
+
the richer ``AsyncNsdpWriteClient`` protocol (``AsyncUdpNsdpClient`` satisfies
|
|
120
|
+
it and it extends ``AsyncNsdpClient``, so ``_reader()`` can still accept it).
|
|
121
|
+
Lazy import."""
|
|
122
|
+
from .transport.aio.nsdp_udp import AsyncUdpNsdpClient
|
|
123
|
+
|
|
124
|
+
return AsyncUdpNsdpClient(host, interface=interface)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def require_http_backend(model: SwitchModel) -> None:
|
|
128
|
+
"""Raise unless the model exposes an HTTP web-UI backend."""
|
|
129
|
+
if Backend.HTTP not in model.backends:
|
|
130
|
+
raise UnsupportedCapabilityError(
|
|
131
|
+
f"model {model.key!r} has no HTTP backend"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def http_reads_supported(model: SwitchModel) -> bool:
|
|
136
|
+
"""True only if the model's web reads/writes are grounded (reads_verified).
|
|
137
|
+
|
|
138
|
+
The facade uses this to decide whether HTTP may join the per-op backend
|
|
139
|
+
fallback. UNVERIFIED-pending-capture models (gs110emx Gambit, gsm7228ps
|
|
140
|
+
cheetah) return False: their HTTP path is never used for read/write dispatch
|
|
141
|
+
(gsm7228ps stays SNMP-authoritative; HTTP is reserved for firmware/reboot).
|
|
142
|
+
The lazy import keeps ``import netgear_switch`` clear of the endpoints module
|
|
143
|
+
on the hot path (endpoints is pure, but this mirrors the other lazy builders).
|
|
144
|
+
"""
|
|
145
|
+
if Backend.HTTP not in model.backends:
|
|
146
|
+
return False
|
|
147
|
+
from .protocols.http.endpoints import HTTP_SPECS
|
|
148
|
+
|
|
149
|
+
spec = HTTP_SPECS.get(model.key)
|
|
150
|
+
return spec is not None and spec.reads_verified
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _require_http_password(host: str, password: str | None) -> str:
|
|
154
|
+
if not password:
|
|
155
|
+
raise CredentialError(f"no HTTP password configured for {host!r}")
|
|
156
|
+
return password
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def build_sync_http_client(
|
|
160
|
+
host: str, password: str | None, model: SwitchModel
|
|
161
|
+
) -> HttpClient:
|
|
162
|
+
"""Default sync web-UI client (httpx). Imported lazily."""
|
|
163
|
+
from .protocols.http.endpoints import http_spec
|
|
164
|
+
from .transport.http.client import HttpClient
|
|
165
|
+
|
|
166
|
+
return HttpClient(host, _require_http_password(host, password), http_spec(model))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def build_async_http_client(
|
|
170
|
+
host: str, password: str | None, model: SwitchModel
|
|
171
|
+
) -> AsyncHttpClient:
|
|
172
|
+
"""Default async web-UI client (httpx). Imported lazily."""
|
|
173
|
+
from .protocols.http.endpoints import http_spec
|
|
174
|
+
from .transport.http.client import AsyncHttpClient
|
|
175
|
+
|
|
176
|
+
return AsyncHttpClient(
|
|
177
|
+
host, _require_http_password(host, password), http_spec(model)
|
|
178
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.post154'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 'post154')
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|