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.
Files changed (66) hide show
  1. netgear_switch/__init__.py +132 -0
  2. netgear_switch/_dispatch.py +178 -0
  3. netgear_switch/_version.py +24 -0
  4. netgear_switch/aio_api.py +529 -0
  5. netgear_switch/cli/__init__.py +1 -0
  6. netgear_switch/cli/capture.py +131 -0
  7. netgear_switch/cli/context.py +39 -0
  8. netgear_switch/cli/format.py +201 -0
  9. netgear_switch/cli/main.py +484 -0
  10. netgear_switch/cli/resolve.py +108 -0
  11. netgear_switch/cli/safety.py +71 -0
  12. netgear_switch/config.py +184 -0
  13. netgear_switch/errors.py +52 -0
  14. netgear_switch/http_read.py +174 -0
  15. netgear_switch/http_write.py +420 -0
  16. netgear_switch/models.py +156 -0
  17. netgear_switch/nsdp_read.py +221 -0
  18. netgear_switch/nsdp_write.py +315 -0
  19. netgear_switch/protocols/__init__.py +1 -0
  20. netgear_switch/protocols/http/__init__.py +1 -0
  21. netgear_switch/protocols/http/crypt.py +29 -0
  22. netgear_switch/protocols/http/endpoints.py +165 -0
  23. netgear_switch/protocols/http/forms.py +77 -0
  24. netgear_switch/protocols/http/parse.py +238 -0
  25. netgear_switch/protocols/http/session.py +29 -0
  26. netgear_switch/protocols/nsdp/__init__.py +7 -0
  27. netgear_switch/protocols/nsdp/auth.py +33 -0
  28. netgear_switch/protocols/nsdp/client.py +67 -0
  29. netgear_switch/protocols/nsdp/parsers.py +209 -0
  30. netgear_switch/protocols/nsdp/protocol.py +201 -0
  31. netgear_switch/protocols/nsdp/types.py +137 -0
  32. netgear_switch/protocols/nsdp/write.py +98 -0
  33. netgear_switch/protocols/snmp/__init__.py +1 -0
  34. netgear_switch/protocols/snmp/client.py +88 -0
  35. netgear_switch/protocols/snmp/oids.py +125 -0
  36. netgear_switch/protocols/snmp/parse.py +777 -0
  37. netgear_switch/protocols/snmp/write.py +112 -0
  38. netgear_switch/py.typed +0 -0
  39. netgear_switch/registry.py +227 -0
  40. netgear_switch/snmp_read.py +226 -0
  41. netgear_switch/snmp_write.py +625 -0
  42. netgear_switch/sync_api.py +557 -0
  43. netgear_switch/transport/__init__.py +1 -0
  44. netgear_switch/transport/aio/__init__.py +1 -0
  45. netgear_switch/transport/aio/nsdp_udp.py +152 -0
  46. netgear_switch/transport/aio/snmp_pysnmp.py +247 -0
  47. netgear_switch/transport/http/__init__.py +1 -0
  48. netgear_switch/transport/http/client.py +217 -0
  49. netgear_switch/transport/sync/__init__.py +1 -0
  50. netgear_switch/transport/sync/nsdp_udp.py +109 -0
  51. netgear_switch/transport/sync/snmp_netsnmp_cli.py +257 -0
  52. netgear_switch/virtual/__init__.py +8 -0
  53. netgear_switch/virtual/faces/__init__.py +2 -0
  54. netgear_switch/virtual/faces/http.py +164 -0
  55. netgear_switch/virtual/faces/mibview.py +92 -0
  56. netgear_switch/virtual/faces/nsdp.py +124 -0
  57. netgear_switch/virtual/faces/snmp.py +412 -0
  58. netgear_switch/virtual/seed.py +220 -0
  59. netgear_switch/virtual/server.py +106 -0
  60. netgear_switch/virtual/state.py +615 -0
  61. netgear_switch/virtual/web.py +210 -0
  62. python_netgear_switch_library-0.0.post154.dist-info/METADATA +85 -0
  63. python_netgear_switch_library-0.0.post154.dist-info/RECORD +66 -0
  64. python_netgear_switch_library-0.0.post154.dist-info/WHEEL +4 -0
  65. python_netgear_switch_library-0.0.post154.dist-info/entry_points.txt +2 -0
  66. python_netgear_switch_library-0.0.post154.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,88 @@
1
+ """Shared SNMP transport seam: row type, error, and client protocols.
2
+
3
+ Pure and I/O-free, and transport-agnostic. The net-snmp CLI (sync) and pysnmp
4
+ (async) transports both implement these protocols and return SnmpRow instances
5
+ the parsers consume. No transport-specific types appear here.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING, Protocol
11
+
12
+ from ...errors import NetgearSwitchError
13
+
14
+ if TYPE_CHECKING:
15
+ from .write import SetVarbind
16
+
17
+ # snmp_type tokens meaning "no value present here".
18
+ ABSENT_TYPES: frozenset[str] = frozenset(
19
+ {"NOSUCHOBJECT", "NOSUCHINSTANCE", "ENDOFMIBVIEW"}
20
+ )
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class SnmpRow:
25
+ """One SNMP varbind: full numeric OID, normalized value, type token.
26
+
27
+ ``value`` is a normalized Python value so the sync (net-snmp CLI) and async
28
+ (pysnmp) clients are interchangeable: ``int`` for integer-family types
29
+ (INTEGER/Gauge32/Counter32/Counter64/Timeticks-numeric), ``str`` for text,
30
+ OID and IP-address values, ``bytes`` for raw octet strings (Hex-STRING).
31
+ Both clients MUST yield equal values for the same OID (Task 16 enforces it).
32
+ """
33
+
34
+ oid: str
35
+ value: int | str | bytes
36
+ snmp_type: str
37
+
38
+
39
+ class SnmpError(NetgearSwitchError):
40
+ """An SNMP transport operation failed (timeout, connection, agent error)."""
41
+
42
+
43
+ def full_oid(oid: str, oid_index: str) -> str:
44
+ """Join an optional instance index onto a base OID as a full numeric OID.
45
+
46
+ A transport may hand back the whole numeric OID in ``oid`` with an empty
47
+ ``oid_index``, or split the instance into ``oid_index``. Joining both and
48
+ stripping any leading dot is correct either way.
49
+ """
50
+ oid = oid.lstrip(".")
51
+ return f"{oid}.{oid_index}" if oid_index else oid
52
+
53
+
54
+ class SnmpClient(Protocol):
55
+ """Synchronous SNMP v2c read client for a single switch."""
56
+
57
+ def get(self, oids: list[str]) -> list[SnmpRow]: ...
58
+
59
+ def walk(self, base_oid: str) -> list[SnmpRow]: ...
60
+
61
+
62
+ class AsyncSnmpClient(Protocol):
63
+ """Asynchronous SNMP v2c read client for a single switch."""
64
+
65
+ async def get(self, oids: list[str]) -> list[SnmpRow]: ...
66
+
67
+ async def walk(self, base_oid: str) -> list[SnmpRow]: ...
68
+
69
+
70
+ class SnmpWriteClient(SnmpClient, Protocol):
71
+ """Synchronous SNMP v2c read+write client for a single switch.
72
+
73
+ Extends the read client with SET. ``set_many`` is one PDU (atomic). A write
74
+ RW community can also read, so a single write client verifies its own
75
+ writes via the inherited ``get``/``walk``.
76
+ """
77
+
78
+ def set(self, varbind: SetVarbind) -> None: ...
79
+
80
+ def set_many(self, varbinds: list[SetVarbind]) -> None: ...
81
+
82
+
83
+ class AsyncSnmpWriteClient(AsyncSnmpClient, Protocol):
84
+ """Asynchronous SNMP v2c read+write client for a single switch."""
85
+
86
+ async def set(self, varbind: SetVarbind) -> None: ...
87
+
88
+ async def set_many(self, varbinds: list[SetVarbind]) -> None: ...
@@ -0,0 +1,125 @@
1
+ """SNMP OID constants and per-model vendor OID tables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING
7
+
8
+ from ...errors import UnsupportedCapabilityError
9
+
10
+ if TYPE_CHECKING:
11
+ # Imported only for the vendor_oids() type annotation; keep it behind
12
+ # TYPE_CHECKING so ruff's TC rules stay clean and the pure layer stays light.
13
+ from ...registry import SwitchModel
14
+
15
+
16
+ # Standard-MIB OID constants
17
+ # MIB-II System group scalars (Task 2 model detection): both are full,
18
+ # instance-qualified (".0") leaf OIDs, fetched with a plain exact-OID GET
19
+ # (unlike the walk-based base-OIDs below) -- see snmp_read.read_system_info.
20
+ SYS_DESCR = "1.3.6.1.2.1.1.1.0" # sysDescr: text incl. the model name
21
+ SYS_OBJECT_ID = "1.3.6.1.2.1.1.2.0" # sysObjectID: read-only signal, unused
22
+ # for matching (no known OID->model table exists -- see parse.py's
23
+ # detect_model_from_sysdescr docstring).
24
+ IF_ADMIN_STATUS = "1.3.6.1.2.1.2.2.1.7" # ifAdminStatus (1=up,2=down)
25
+ IF_OPER_STATUS = "1.3.6.1.2.1.2.2.1.8" # ifOperStatus (1=up,2=down)
26
+ IF_IN_ERRORS = "1.3.6.1.2.1.2.2.1.14"
27
+ IF_OUT_ERRORS = "1.3.6.1.2.1.2.2.1.20"
28
+ IF_NAME = "1.3.6.1.2.1.31.1.1.1.1"
29
+ IF_HC_IN_OCTETS = "1.3.6.1.2.1.31.1.1.1.6"
30
+ IF_HC_IN_UCAST = "1.3.6.1.2.1.31.1.1.1.7"
31
+ IF_HC_OUT_OCTETS = "1.3.6.1.2.1.31.1.1.1.10"
32
+ IF_HC_OUT_UCAST = "1.3.6.1.2.1.31.1.1.1.11"
33
+ IF_HIGH_SPEED = "1.3.6.1.2.1.31.1.1.1.15" # Mbps
34
+ IF_ALIAS = "1.3.6.1.2.1.31.1.1.1.18"
35
+ DOT1D_BASE_BRIDGE_ADDRESS = "1.3.6.1.2.1.17.1.1" # scalar (.0); BRIDGE-MIB base MAC
36
+ DOT1D_BASE_PORT_IF_INDEX = "1.3.6.1.2.1.17.1.4.1.2"
37
+ DOT1Q_TP_FDB_PORT = "1.3.6.1.2.1.17.7.1.2.2.1.2" # MAC table, port column ONLY
38
+ DOT1Q_VLAN_STATIC_NAME = "1.3.6.1.2.1.17.7.1.4.3.1.1"
39
+ DOT1Q_VLAN_STATIC_EGRESS = "1.3.6.1.2.1.17.7.1.4.3.1.2"
40
+ DOT1Q_VLAN_STATIC_UNTAGGED = "1.3.6.1.2.1.17.7.1.4.3.1.4"
41
+ DOT1Q_PVID = "1.3.6.1.2.1.17.7.1.4.5.1.1"
42
+ DOT1Q_VLAN_STATIC_ROW_STATUS = "1.3.6.1.2.1.17.7.1.4.3.1.5" # dot1qVlanStaticRowStatus
43
+ ROW_STATUS_CREATE_AND_GO = 4 # RowStatus createAndGo
44
+ ROW_STATUS_DESTROY = 6 # RowStatus destroy
45
+ # columns 5=chassis,7=portId,8=portDesc,9=sysName
46
+ LLDP_REM_TABLE = "1.0.8802.1.1.2.1.4.1"
47
+ # RFC3621; col3=admin, col6=detect
48
+ PETH_PSE_PORT_TABLE = "1.3.6.1.2.1.105.1.1.1"
49
+ # ipAddrTable (snmp_common.py:36 base .4.20)
50
+ IP_ADENT_ADDR = "1.3.6.1.2.1.4.20.1.1"
51
+ IP_ADENT_IFINDEX = "1.3.6.1.2.1.4.20.1.2"
52
+ IP_ADENT_NETMASK = "1.3.6.1.2.1.4.20.1.3"
53
+ IP_ROUTE_DEST = "1.3.6.1.2.1.4.21.1.1" # ipRouteDest
54
+ # ipRouteNextHop (gateway where dest=0.0.0.0)
55
+ IP_ROUTE_NEXTHOP = "1.3.6.1.2.1.4.21.1.7"
56
+
57
+
58
+ # (kind, unit, column suffix under {base}.43.1)
59
+ BOX_SENSOR_COLUMNS: tuple[tuple[str, str, str], ...] = (
60
+ ("fan", "RPM", "6.1.4"),
61
+ ("power", "W", "8.1.5"),
62
+ ("temperature", "C", "15.1.3"),
63
+ )
64
+
65
+ DHCP_MODE_OID_SUFFIX = "99.1"
66
+ """UNVERIFIED — this Netgear private OID for DHCP-vs-static management-IP mode.
67
+
68
+ This is an unconfirmed guess used only so the mock and reader agree under
69
+ test; it MUST be confirmed against real hardware via the capture utility
70
+ (Slice 7) before it is trusted. Until then get_mgmt_ip returns IpMode.UNKNOWN
71
+ when this OID is absent.
72
+ """
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class VendorOids:
77
+ """Per-model Netgear vendor-specific OID table."""
78
+
79
+ base: str
80
+ poe_power_mw: str
81
+ box_fan: str
82
+ box_psu_power: str
83
+ box_temp: str
84
+ dhcp_mode_unverified: str
85
+ """The ONE symbol every call site uses for the DHCP-mode OID. See
86
+ DHCP_MODE_OID_SUFFIX above — UNVERIFIED, best-effort read only. No call site
87
+ may hard-code a ``.99.1`` literal; they all reference this field."""
88
+ mgmt_write_addr_unverified: str
89
+ mgmt_write_netmask_unverified: str
90
+ mgmt_write_gateway_unverified: str
91
+ """UNVERIFIED writable management-IP OIDs — placeholders pending Slice 7
92
+ hardware capture. They are NEVER trusted on real hardware (set_mgmt_ip is
93
+ force-gated and documented UNVERIFIED); they exist so the mutable mock and
94
+ the writer agree under test, mirroring the ``dhcp_mode_unverified``
95
+ precedent above. No call site may hard-code these literals."""
96
+
97
+
98
+ def vendor_oids(model: SwitchModel) -> VendorOids:
99
+ """Resolve vendor OIDs for a switch model.
100
+
101
+ Args:
102
+ model: A SwitchModel instance with snmp_vendor_base set.
103
+
104
+ Returns:
105
+ VendorOids dataclass populated with vendor-specific OID strings.
106
+
107
+ Raises:
108
+ UnsupportedCapabilityError: If the model has no SNMP vendor base.
109
+ """
110
+ base = model.snmp_vendor_base
111
+ if base is None:
112
+ raise UnsupportedCapabilityError(
113
+ f"model {model.key!r} has no SNMP vendor OID subtree"
114
+ )
115
+ return VendorOids(
116
+ base=base,
117
+ poe_power_mw=f"{base}.15.1.1.1.2",
118
+ box_fan=f"{base}.43.1.6.1.4",
119
+ box_psu_power=f"{base}.43.1.8.1.5",
120
+ box_temp=f"{base}.43.1.15.1.3",
121
+ dhcp_mode_unverified=f"{base}.{DHCP_MODE_OID_SUFFIX}",
122
+ mgmt_write_addr_unverified=f"{base}.98.1",
123
+ mgmt_write_netmask_unverified=f"{base}.98.2",
124
+ mgmt_write_gateway_unverified=f"{base}.98.3",
125
+ )