python-netgear-switch-library 0.0.post154__tar.gz → 0.0.post290__tar.gz
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.
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/ci.yml +2 -2
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/deb.yml +8 -8
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/publish-pypi.yml +4 -4
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.gitignore +3 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/PKG-INFO +57 -1
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/README.md +49 -0
- python_netgear_switch_library-0.0.post290/docs/superpowers/plans/2026-07-23-gsm7252ps-http-xe-dialect.md +130 -0
- python_netgear_switch_library-0.0.post290/docs/superpowers/specs/2026-07-23-gsm7252ps-http-xe-dialect-design.md +141 -0
- python_netgear_switch_library-0.0.post290/pyproject.toml +115 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/_dispatch.py +101 -25
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/_version.py +2 -2
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/aio_api.py +95 -12
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/capture.py +2 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/format.py +40 -1
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/main.py +323 -4
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/resolve.py +73 -5
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/safety.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/cli_read.py +107 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/cli_write.py +89 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/http_read.py +572 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/http_write.py +263 -1
- python_netgear_switch_library-0.0.post290/src/netgear_switch/mcp/__init__.py +14 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/mcp/server.py +519 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/models.py +23 -15
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/nsdp_read.py +22 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/nsdp_write.py +29 -10
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/__init__.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/commands.py +237 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/parse.py +671 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/crypt.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/endpoints.py +662 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/forms.py +2 -3
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/parse.py +2131 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/session.py +30 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/types.py +48 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/__init__.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/auth.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/client.py +2 -2
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/parsers.py +11 -5
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/protocol.py +12 -5
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/types.py +8 -1
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/write.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/client.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/oids.py +78 -8
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/parse.py +240 -29
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/write.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/registry.py +334 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/snmp_read.py +132 -33
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/snmp_write.py +139 -68
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/sync_api.py +237 -20
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/nsdp_udp.py +16 -9
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/snmp_pysnmp.py +36 -13
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/__init__.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/console.py +103 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/session.py +241 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/ssh.py +147 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/telnet.py +104 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/http/client.py +563 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/sync/nsdp_udp.py +17 -8
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/sync/snmp_netsnmp_cli.py +34 -17
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/__init__.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/cli_fastpath.py +375 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/__init__.py +1 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/faces/cli.py +114 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/faces/http.py +560 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/mibview.py +22 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/nsdp.py +8 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/snmp.py +40 -7
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/seed.py +2580 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/server.py +247 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/state.py +242 -62
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/web.py +4 -3
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs105pe.py +179 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs110emx.py +173 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs110emx_templates.py +35 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs728tpp.py +246 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gsm7228ps.py +145 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gsm7252ps.py +347 -0
- python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_m4300.py +202 -0
- python_netgear_switch_library-0.0.post290/tests/capture_parity.py +156 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_cli_integration.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_control_vlan_ip.py +2 -6
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_format.py +11 -5
- python_netgear_switch_library-0.0.post290/tests/cli/test_new_commands.py +279 -0
- python_netgear_switch_library-0.0.post290/tests/cli/test_op_coverage.py +90 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_prompt_wiring.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_resolve.py +108 -10
- python_netgear_switch_library-0.0.post290/tests/cli/test_serve.py +57 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/conftest.py +37 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/equivalence.py +187 -14
- python_netgear_switch_library-0.0.post290/tests/fixtures/captures/gsm7228ps.json +1763 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/captures/gsm7252ps.json +4533 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/captures/m4300-16x.json +4009 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/captures/m4300-24x.json +4085 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_environment.txt +20 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_lldp.txt +57 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_mac_table.txt +21 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_network.txt +17 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_poe.txt +52 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_port_all.txt +81 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_show_version.txt +10 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan.txt +10 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan_brief.txt +2 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan_port_all.txt +82 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_environment.txt +26 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_interface_ethernet_1_0_1.txt +81 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_lldp_remote_device_all.txt +21 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_mac_addr_table.txt +16 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_network.txt +18 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_poe_port_info_all.txt +20 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_port_all.txt +59 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_version.txt +12 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_90.txt +62 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_brief.txt +17 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_port_all.txt +60 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_environment.txt +19 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_interface_ethernet_1_0_1.txt +89 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_ip_management.txt +22 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_lldp_remote_device_all.txt +41 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_mac_addr_table.txt +485 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_poe_port_info_all.txt +20 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_port_all.txt +148 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_version.txt +11 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan.txt +19 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_1.txt +150 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_4.txt +150 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_5.txt +150 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_port_all.txt +148 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_environment.txt +18 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_interface_ethernet_1_0_1.txt +89 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_ip_management.txt +14 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_lldp_remote_device_all.txt +29 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_mac_addr_table.txt +649 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_port_all.txt +157 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_version.txt +11 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan.txt +19 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_5.txt +158 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_90.txt +158 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_port_all.txt +156 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_membership.html +60 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_portstats.html +113 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_pvid.html +64 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_status.html +162 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_switch_info.html +142 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_vlancfg.html +73 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_cf8021q.html +173 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_interface_stats.html +157 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_login.html +126 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_port_settings.html +370 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_pvid.html +147 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_redirect.html +15 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_sysinfo.html +150 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_vlanmembership.html +101 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_device_info_and_sensors.xml +258 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_lldp.xml +153 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_macs.xml +1166 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_mgmt_ip.xml +115 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_poe.xml +557 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_ports.xml +1458 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_pvids_membership.xml +2046 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_vlan_membership_per_vlan.xml +460 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_vlans.xml +153 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_basicAddressTable.html +325 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_lldpRemoteInventory.html +184 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_poeInterfaceConfiguration.html +1590 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portPvidConfiguration.html +1437 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portStatistics.html +1340 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portsConfiguration.html +1830 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_sysInfo.html +498 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_vlanStatus.html +179 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_basicAddressTable.html +2082 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_lldpRemoteInventory.html +498 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_login.html +257 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_poeInterfaceConfiguration.html +1555 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portPvidConfiguration.html +1075 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portStatistics.html +1201 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portsConfiguration.html +1284 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_sysInfo.html +500 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_vlanStatus.html +233 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_addresstable.html +432 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_ports.html +1569 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_portstats.html +1162 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_pvid.html +1043 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_sysinfo.html +579 -0
- python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_vlanstatus.html +311 -0
- python_netgear_switch_library-0.0.post290/tests/http_specs.py +40 -0
- python_netgear_switch_library-0.0.post290/tests/protocols/cli/test_cli_parse.py +593 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/http/test_crypt.py +13 -0
- python_netgear_switch_library-0.0.post290/tests/protocols/http/test_endpoints.py +233 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/http/test_forms.py +2 -2
- python_netgear_switch_library-0.0.post290/tests/protocols/http/test_parse.py +785 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_parsers.py +32 -8
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_protocol.py +27 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_write_frame.py +1 -1
- python_netgear_switch_library-0.0.post290/tests/protocols/snmp/test_oids.py +94 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_mgmt_ip.py +54 -1
- python_netgear_switch_library-0.0.post290/tests/protocols/snmp/test_parse_poe_sensors.py +147 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_ports.py +92 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_system_info.py +85 -36
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_vlans.py +3 -9
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_write_encode.py +4 -1
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_aio_api.py +150 -94
- python_netgear_switch_library-0.0.post290/tests/test_cli_read.py +324 -0
- python_netgear_switch_library-0.0.post290/tests/test_cli_write.py +162 -0
- python_netgear_switch_library-0.0.post290/tests/test_cross_backend_equivalence.py +669 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_deb_version.py +25 -7
- python_netgear_switch_library-0.0.post290/tests/test_facade_equivalence.py +85 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_http_dispatch.py +16 -7
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_http_equivalence.py +1 -0
- python_netgear_switch_library-0.0.post290/tests/test_http_read.py +625 -0
- python_netgear_switch_library-0.0.post290/tests/test_http_write.py +803 -0
- python_netgear_switch_library-0.0.post290/tests/test_mcp_server.py +396 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_integration.py +4 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_read.py +61 -12
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_write.py +18 -10
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_quality_gates.py +1 -0
- python_netgear_switch_library-0.0.post290/tests/test_registry.py +166 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_integration.py +8 -6
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_read.py +52 -6
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_write.py +95 -60
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_sync_api.py +192 -88
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_version.py +1 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_write_equivalence.py +4 -2
- python_netgear_switch_library-0.0.post290/tests/transport/_fastpath_fake.py +323 -0
- python_netgear_switch_library-0.0.post290/tests/transport/test_cli_console.py +86 -0
- python_netgear_switch_library-0.0.post290/tests/transport/test_cli_scp_cert.py +234 -0
- python_netgear_switch_library-0.0.post290/tests/transport/test_cli_telnet.py +81 -0
- python_netgear_switch_library-0.0.post290/tests/transport/test_http_client.py +523 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_nsdp_udp_sync.py +151 -24
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_set_transport.py +20 -10
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_snmp_netsnmp_cli.py +54 -9
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_snmp_pysnmp.py +78 -23
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_value_parity.py +1 -0
- python_netgear_switch_library-0.0.post290/tests/virtual/test_cli_cross_backend.py +145 -0
- python_netgear_switch_library-0.0.post290/tests/virtual/test_cli_gsm7228ps_parity.py +140 -0
- python_netgear_switch_library-0.0.post290/tests/virtual/test_gsm7228ps_seed.py +111 -0
- python_netgear_switch_library-0.0.post290/tests/virtual/test_m4300_seeds.py +126 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_mibview.py +39 -6
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_mutable_state.py +3 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_nsdp_state.py +29 -10
- python_netgear_switch_library-0.0.post290/tests/virtual/test_serve_daemon.py +146 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_snmp_write_face.py +86 -56
- python_netgear_switch_library-0.0.post290/tests/virtual/test_state_seed.py +232 -0
- python_netgear_switch_library-0.0.post290/tests/virtual/test_virtual_http_face.py +633 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_virtual_nsdp_face.py +6 -3
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_virtual_snmp_face.py +147 -2
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_web_projection.py +21 -7
- python_netgear_switch_library-0.0.post290/uv.lock +1357 -0
- python_netgear_switch_library-0.0.post154/pyproject.toml +0 -72
- python_netgear_switch_library-0.0.post154/src/netgear_switch/http_read.py +0 -174
- python_netgear_switch_library-0.0.post154/src/netgear_switch/protocols/http/endpoints.py +0 -165
- python_netgear_switch_library-0.0.post154/src/netgear_switch/protocols/http/parse.py +0 -238
- python_netgear_switch_library-0.0.post154/src/netgear_switch/registry.py +0 -227
- python_netgear_switch_library-0.0.post154/src/netgear_switch/transport/http/client.py +0 -217
- python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/faces/http.py +0 -164
- python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/seed.py +0 -220
- python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/server.py +0 -106
- python_netgear_switch_library-0.0.post154/tests/protocols/http/test_endpoints.py +0 -59
- python_netgear_switch_library-0.0.post154/tests/protocols/http/test_parse.py +0 -132
- python_netgear_switch_library-0.0.post154/tests/protocols/snmp/test_oids.py +0 -40
- python_netgear_switch_library-0.0.post154/tests/protocols/snmp/test_parse_poe_sensors.py +0 -72
- python_netgear_switch_library-0.0.post154/tests/test_facade_equivalence.py +0 -13
- python_netgear_switch_library-0.0.post154/tests/test_http_read.py +0 -124
- python_netgear_switch_library-0.0.post154/tests/test_http_write.py +0 -409
- python_netgear_switch_library-0.0.post154/tests/test_registry.py +0 -95
- python_netgear_switch_library-0.0.post154/tests/transport/test_http_client.py +0 -283
- python_netgear_switch_library-0.0.post154/tests/virtual/test_state_seed.py +0 -137
- python_netgear_switch_library-0.0.post154/tests/virtual/test_virtual_http_face.py +0 -130
- python_netgear_switch_library-0.0.post154/uv.lock +0 -573
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/LICENSE +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/RELEASING.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/changelog +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/control +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/copyright +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/rules +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/source/format +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-15-foundation.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-17-slice-02-snmp-read-core.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-17-slice-03-read-apis.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-18-slice-04-snmp-write.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-18-slice-05-nsdp.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-18-slice-06-http.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-18-slice-07-cli.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-18-slice-08-packaging-ci.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/specs/2026-07-15-netgear-control-library-design.md +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/apt-index.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/ci-build.sh +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/deb-version.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/context.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/config.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/errors.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/py.typed +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/http/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/sync/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_capture.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_control_poe_port.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_main_framework.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_read_commands.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_safety.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_dashboard.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_login.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_membership.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_poestatus.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_portstats.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_pvid.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_vlancfg.html +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_fans.txt +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_ifhighspeed.txt +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_ifoperstatus.txt +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_psu.txt +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_auth.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_client.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_lldp_macs.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_config.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_dispatch.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_errors.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_import.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_models.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_models_snmp_read.py +0 -0
- {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_public_api.py +0 -0
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
matrix:
|
|
22
22
|
python-version: ["3.11", "3.12", "3.13"]
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@v5
|
|
25
25
|
with:
|
|
26
26
|
fetch-depth: 0 # full history so hatch-vcs can derive the version
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
sudo apt-get update
|
|
31
31
|
sudo apt-get install -y --no-install-recommends snmp
|
|
32
32
|
|
|
33
|
-
- uses: astral-sh/setup-uv@
|
|
33
|
+
- uses: astral-sh/setup-uv@v7
|
|
34
34
|
|
|
35
35
|
- name: Install the matrix Python
|
|
36
36
|
run: uv python install ${{ matrix.python-version }}
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
matrix:
|
|
20
20
|
suite: [trixie, sid]
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v5
|
|
23
23
|
with:
|
|
24
24
|
fetch-depth: 0 # full history so deb-version.py can count commits
|
|
25
25
|
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
docker.io/library/debian:${{ matrix.suite }} \
|
|
31
31
|
sh packaging/ci-build.sh
|
|
32
32
|
|
|
33
|
-
- uses: actions/upload-artifact@
|
|
33
|
+
- uses: actions/upload-artifact@v7
|
|
34
34
|
with:
|
|
35
35
|
name: debs-${{ matrix.suite }}
|
|
36
36
|
path: built-debs/
|
|
@@ -45,16 +45,16 @@ jobs:
|
|
|
45
45
|
name: github-pages
|
|
46
46
|
url: ${{ steps.deploy.outputs.page_url }}
|
|
47
47
|
steps:
|
|
48
|
-
- uses: actions/checkout@
|
|
48
|
+
- uses: actions/checkout@v5
|
|
49
49
|
|
|
50
50
|
- name: Download trixie debs
|
|
51
|
-
uses: actions/download-artifact@
|
|
51
|
+
uses: actions/download-artifact@v8
|
|
52
52
|
with:
|
|
53
53
|
name: debs-trixie
|
|
54
54
|
path: apt-repo/trixie/
|
|
55
55
|
|
|
56
56
|
- name: Download sid debs
|
|
57
|
-
uses: actions/download-artifact@
|
|
57
|
+
uses: actions/download-artifact@v8
|
|
58
58
|
with:
|
|
59
59
|
name: debs-sid
|
|
60
60
|
path: apt-repo/sid/
|
|
@@ -97,11 +97,11 @@ jobs:
|
|
|
97
97
|
cd ../..
|
|
98
98
|
done
|
|
99
99
|
|
|
100
|
-
- uses: actions/configure-pages@
|
|
100
|
+
- uses: actions/configure-pages@v6
|
|
101
101
|
|
|
102
|
-
- uses: actions/upload-pages-artifact@
|
|
102
|
+
- uses: actions/upload-pages-artifact@v5
|
|
103
103
|
with:
|
|
104
104
|
path: apt-repo/
|
|
105
105
|
|
|
106
106
|
- id: deploy
|
|
107
|
-
uses: actions/deploy-pages@
|
|
107
|
+
uses: actions/deploy-pages@v5
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
build:
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
20
|
with:
|
|
21
21
|
fetch-depth: 0 # full history for hatch-vcs version derivation
|
|
22
22
|
|
|
23
|
-
- uses: astral-sh/setup-uv@
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
24
|
|
|
25
25
|
- name: Build sdist + wheel
|
|
26
26
|
run: uv build
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
- name: Show derived version
|
|
29
29
|
run: ls -1 dist/
|
|
30
30
|
|
|
31
|
-
- uses: actions/upload-artifact@
|
|
31
|
+
- uses: actions/upload-artifact@v7
|
|
32
32
|
with:
|
|
33
33
|
name: dist
|
|
34
34
|
path: dist/
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
permissions:
|
|
42
42
|
id-token: write
|
|
43
43
|
steps:
|
|
44
|
-
- uses: actions/download-artifact@
|
|
44
|
+
- uses: actions/download-artifact@v8
|
|
45
45
|
with:
|
|
46
46
|
name: dist
|
|
47
47
|
path: dist/
|
{python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/PKG-INFO
RENAMED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-netgear-switch-library
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.post290
|
|
4
4
|
Summary: Python library and CLI to query and control Netgear switches over SNMP, NSDP and HTTP.
|
|
5
5
|
Author-email: Tim Ansell <me@mith.ro>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: cryptography>=41
|
|
9
10
|
Provides-Extra: async
|
|
10
11
|
Requires-Dist: pysnmp>=7.0; extra == 'async'
|
|
11
12
|
Provides-Extra: http
|
|
12
13
|
Requires-Dist: httpx>=0.27; extra == 'http'
|
|
14
|
+
Provides-Extra: mcp
|
|
15
|
+
Requires-Dist: mcp>=1.2; extra == 'mcp'
|
|
16
|
+
Provides-Extra: ssh
|
|
17
|
+
Requires-Dist: paramiko<3,>=2.12; extra == 'ssh'
|
|
18
|
+
Requires-Dist: pyserial>=3.5; extra == 'ssh'
|
|
13
19
|
Provides-Extra: sync
|
|
14
20
|
Provides-Extra: testing
|
|
21
|
+
Requires-Dist: mcp>=1.2; extra == 'testing'
|
|
15
22
|
Requires-Dist: pysnmp>=7.0; extra == 'testing'
|
|
16
23
|
Description-Content-Type: text/markdown
|
|
17
24
|
|
|
@@ -72,6 +79,55 @@ This project is a **rolling release**: the version is derived from git
|
|
|
72
79
|
(`0.0.postN` / `X.Y.postN`), and every merge to `main` publishes a new version
|
|
73
80
|
to PyPI and the apt repo. There are no tags or manual version bumps.
|
|
74
81
|
|
|
82
|
+
## Mock switch daemons (`ngsw serve`)
|
|
83
|
+
|
|
84
|
+
The library ships in-repo virtual/mock switches. `ngsw serve` runs them as
|
|
85
|
+
standalone, long-lived daemons on real sockets, so an external tool or library
|
|
86
|
+
can point at a mock when real hardware is unavailable (CI, local development,
|
|
87
|
+
demos).
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
# Serve one model (ephemeral ports), or several, or every registered model:
|
|
91
|
+
ngsw serve --model gsm7228ps
|
|
92
|
+
ngsw serve --model gsm7228ps --model gs305ep
|
|
93
|
+
ngsw serve --all
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For each switch that comes up it prints the model, bind host, the actual bound
|
|
97
|
+
port(s) (SNMP/NSDP over UDP, HTTP over TCP), and the SNMP community and HTTP
|
|
98
|
+
password it accepts, then blocks until interrupted (`Ctrl-C` / `SIGTERM`), at
|
|
99
|
+
which point every switch is stopped cleanly:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
[gsm7228ps] host=127.0.0.1
|
|
103
|
+
SNMP udp/36540
|
|
104
|
+
HTTP tcp/42629
|
|
105
|
+
community='public' http_password='password'
|
|
106
|
+
serving 1 mock switch(es); press Ctrl-C to stop
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Point any tool at the printed port. For example, with the mock above serving
|
|
110
|
+
SNMP on UDP `36540`:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
snmpwalk -v2c -c public 127.0.0.1:36540 1.3.6.1.2.1.1
|
|
114
|
+
# ...or the library's own CLI against the same mock:
|
|
115
|
+
ngsw --host 127.0.0.1 --model gsm7228ps --community public ports # + your transport wiring
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Options:
|
|
119
|
+
|
|
120
|
+
- `--model KEY` — model to serve; repeatable. `--all` serves every registered
|
|
121
|
+
model (see `ngsw models`).
|
|
122
|
+
- `--host IP` — bind address (default `127.0.0.1`). Pass `0.0.0.0` to expose the
|
|
123
|
+
mock to other hosts on the network.
|
|
124
|
+
- `--community STR` / `--http-password STR` — credentials the mock accepts
|
|
125
|
+
(defaults `public` / `password`).
|
|
126
|
+
- `--port N` / `--http-port N` — pin the SNMP/NSDP UDP port and the HTTP TCP
|
|
127
|
+
port instead of using ephemeral ones. Because a pinned port is a single
|
|
128
|
+
listener, these are only allowed when serving exactly one model; otherwise
|
|
129
|
+
leave them unset and read the printed ephemeral ports.
|
|
130
|
+
|
|
75
131
|
## Development
|
|
76
132
|
|
|
77
133
|
```sh
|
{python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/README.md
RENAMED
|
@@ -55,6 +55,55 @@ This project is a **rolling release**: the version is derived from git
|
|
|
55
55
|
(`0.0.postN` / `X.Y.postN`), and every merge to `main` publishes a new version
|
|
56
56
|
to PyPI and the apt repo. There are no tags or manual version bumps.
|
|
57
57
|
|
|
58
|
+
## Mock switch daemons (`ngsw serve`)
|
|
59
|
+
|
|
60
|
+
The library ships in-repo virtual/mock switches. `ngsw serve` runs them as
|
|
61
|
+
standalone, long-lived daemons on real sockets, so an external tool or library
|
|
62
|
+
can point at a mock when real hardware is unavailable (CI, local development,
|
|
63
|
+
demos).
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
# Serve one model (ephemeral ports), or several, or every registered model:
|
|
67
|
+
ngsw serve --model gsm7228ps
|
|
68
|
+
ngsw serve --model gsm7228ps --model gs305ep
|
|
69
|
+
ngsw serve --all
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For each switch that comes up it prints the model, bind host, the actual bound
|
|
73
|
+
port(s) (SNMP/NSDP over UDP, HTTP over TCP), and the SNMP community and HTTP
|
|
74
|
+
password it accepts, then blocks until interrupted (`Ctrl-C` / `SIGTERM`), at
|
|
75
|
+
which point every switch is stopped cleanly:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
[gsm7228ps] host=127.0.0.1
|
|
79
|
+
SNMP udp/36540
|
|
80
|
+
HTTP tcp/42629
|
|
81
|
+
community='public' http_password='password'
|
|
82
|
+
serving 1 mock switch(es); press Ctrl-C to stop
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Point any tool at the printed port. For example, with the mock above serving
|
|
86
|
+
SNMP on UDP `36540`:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
snmpwalk -v2c -c public 127.0.0.1:36540 1.3.6.1.2.1.1
|
|
90
|
+
# ...or the library's own CLI against the same mock:
|
|
91
|
+
ngsw --host 127.0.0.1 --model gsm7228ps --community public ports # + your transport wiring
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
|
|
96
|
+
- `--model KEY` — model to serve; repeatable. `--all` serves every registered
|
|
97
|
+
model (see `ngsw models`).
|
|
98
|
+
- `--host IP` — bind address (default `127.0.0.1`). Pass `0.0.0.0` to expose the
|
|
99
|
+
mock to other hosts on the network.
|
|
100
|
+
- `--community STR` / `--http-password STR` — credentials the mock accepts
|
|
101
|
+
(defaults `public` / `password`).
|
|
102
|
+
- `--port N` / `--http-port N` — pin the SNMP/NSDP UDP port and the HTTP TCP
|
|
103
|
+
port instead of using ephemeral ones. Because a pinned port is a single
|
|
104
|
+
listener, these are only allowed when serving exactly one model; otherwise
|
|
105
|
+
leave them unset and read the printed ephemeral ports.
|
|
106
|
+
|
|
58
107
|
## Development
|
|
59
108
|
|
|
60
109
|
```sh
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# gsm7252ps HTTP (XE_FASTPATH dialect) Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** implement task-by-task, TDD, small commits.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a full HTTP read backend to gsm7252ps whose output agrees with its
|
|
6
|
+
SNMP backend, grounded in real captures. Design + live-verified XE format facts
|
|
7
|
+
are in `docs/superpowers/specs/2026-07-23-gsm7252ps-http-xe-dialect-design.md`
|
|
8
|
+
— READ THAT FIRST; it has the login scheme, page URLs, and the XE cell format.
|
|
9
|
+
|
|
10
|
+
**Template:** The M4300 dialect is the closest existing analogue. Mirror it:
|
|
11
|
+
`_M4300` spec in `protocols/http/endpoints.py`, `parse_m4300_*` +
|
|
12
|
+
`parse_cheetah_rows` in `protocols/http/parse.py`, `_is_m4300_dialect` wiring in
|
|
13
|
+
`http_read.py`, `web_m4300.py` mock face. The XE format differs (see spec §"XE
|
|
14
|
+
cell format") — cells are `NAME=<port>.v_<row>_<col> VALUE="…"` with NO
|
|
15
|
+
`<!-- field -->` comment, addressed by COLUMN INDEX.
|
|
16
|
+
|
|
17
|
+
## Status (2026-07-23)
|
|
18
|
+
Tasks 1-8 are DONE on branch `feat/gsm7252ps-http`. Task 9 (the live
|
|
19
|
+
HTTP<->SNMP cross-verify against 10.1.5.22, then flipping
|
|
20
|
+
`_GSM7252PS.reads_verified=True`) is the only remaining step and is
|
|
21
|
+
controller-only.
|
|
22
|
+
|
|
23
|
+
## Global Constraints
|
|
24
|
+
- Honesty: `scheme_verified`/`reads_verified` only where grounded.
|
|
25
|
+
- CORRECTION (2026-07-23): an earlier draft of this line claimed sensors and
|
|
26
|
+
mgmt_ip are JS-populated on gsm7252ps and must raise
|
|
27
|
+
`UnsupportedCapabilityError`. That was WRONG -- `sysInfo.html` carries both
|
|
28
|
+
in its static HTML (see the design doc's own CORRECTION section), and both
|
|
29
|
+
ops are implemented over HTTP. There is NO unsupported carve-out for this
|
|
30
|
+
model. An op may only be declared unsupported if it is PROVEN absent from
|
|
31
|
+
every capture.
|
|
32
|
+
- Every column map hardcoded in a parser MUST be justified by a committed
|
|
33
|
+
fixture; document the column→field mapping in a comment beside it.
|
|
34
|
+
- `uv run` for all python. Small commits per task.
|
|
35
|
+
|
|
36
|
+
## Grounding assets
|
|
37
|
+
Live captures are in `tmp/live-captures/gsm7252ps_*.html` (gitignored working
|
|
38
|
+
copies, session tokens present). Task 1 copies + scrubs them into fixtures.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### Task 1: Fixtures
|
|
43
|
+
- Copy these `tmp/live-captures/gsm7252ps_*` pages to
|
|
44
|
+
`tests/fixtures/http/gsm7252ps_<name>.html`, SCRUBBING any `SID=`/session
|
|
45
|
+
token to `SID=SCRUBBED`: portsConfiguration, portStatistics,
|
|
46
|
+
portPvidConfiguration, vlanStatus, basicAddressTable,
|
|
47
|
+
poeInterfaceConfiguration, lldpRemoteInventory, and the login page.
|
|
48
|
+
- Also copy `base_system_management_sysInfo.html` as
|
|
49
|
+
`gsm7252ps_sysInfo.html` (evidence that sensors/mgmt-IP are JS-populated).
|
|
50
|
+
- Commit.
|
|
51
|
+
|
|
52
|
+
### Task 2: `parse_xe_rows` + get_ports parser (TDD)
|
|
53
|
+
- In `parse.py`: `parse_xe_rows(html) -> dict[int, dict[str,str]]` grouping
|
|
54
|
+
`NAME=<u>.<s>.<p>.v_<row>_<col> VALUE="…"` cells by port number (the instance
|
|
55
|
+
prefix `1.0.52` → port 52), value keyed by the `v_<row>_<col>` coord. Add
|
|
56
|
+
`_XE_CELL_RE`. HTML-unescape values (interface names arrive escaped).
|
|
57
|
+
- `parse_xe_port_status(html) -> list[PortStatus]` using the column map decoded
|
|
58
|
+
from `gsm7252ps_portsConfiguration.html` (spec shows v_1_2_8=autoneg,
|
|
59
|
+
_9=speed, _10=link; CONFIRM the port-name/admin columns against the fixture).
|
|
60
|
+
- Test: `tests/protocols/http/test_parse.py` — assert exact port count (52) and
|
|
61
|
+
a few transcribed rows (port 52 link/speed) from the fixture.
|
|
62
|
+
- Commit.
|
|
63
|
+
|
|
64
|
+
### Task 3: remaining XE parsers (TDD, one commit each)
|
|
65
|
+
For each, decode the column map from the fixture, implement, test vs fixture:
|
|
66
|
+
- `parse_xe_stats` ← portStatistics.html (confirm counter columns; if the page
|
|
67
|
+
reports frames not octets, mirror parse_m4300_stats' honest None-bytes).
|
|
68
|
+
- `parse_xe_pvids` ← portPvidConfiguration.html → list[(port, pvid)].
|
|
69
|
+
- `parse_xe_vlans` ← vlanStatus.html → list[VLANInfo] (member/tagged/untagged).
|
|
70
|
+
- `parse_xe_macs` ← basicAddressTable.html → list[MacEntry] (skip non-physical;
|
|
71
|
+
raise on truncated/paginated table like parse_m4300_macs).
|
|
72
|
+
- `parse_xe_poe` ← poeInterfaceConfiguration.html → list[PoEStatus].
|
|
73
|
+
- `parse_xe_lldp` ← lldpRemoteInventory.html → list[LLDPNeighbor] (if the
|
|
74
|
+
fixture has no neighbours, return [] and note it; do not invent).
|
|
75
|
+
- `parse_xe_labelled_values` ← sysInfo.html: generic label-cell → value-cell
|
|
76
|
+
extractor for the format-(B) page.
|
|
77
|
+
- `parse_xe_sensors` ← sysInfo.html → list[Sensor]: Temperature Status table,
|
|
78
|
+
FAN Status table (Fan1..Fan8), and RPS / Power Module state.
|
|
79
|
+
- `parse_xe_mgmt_ip` ← sysInfo.html → MgmtIpConfig: `IPv4 Network Interface`
|
|
80
|
+
is "addr/netmask" (10.1.5.22/255.255.255.0); base_mac from
|
|
81
|
+
`System MAC Address` (E0:91:F5:0C:D6:DB on the fixture).
|
|
82
|
+
NOTE: an earlier draft wrongly called these two ops HTTP-infeasible. They are
|
|
83
|
+
NOT — see the design doc's CORRECTION section. Do not skip them.
|
|
84
|
+
|
|
85
|
+
### Task 4: endpoint spec + dialect
|
|
86
|
+
- `endpoints.py`: add `HtmlDialect.XE_FASTPATH`. Add `_GSM7252PS` HttpModelSpec:
|
|
87
|
+
scheme=CHEETAH_FORM, login_path=`/base/cheetah_login.html`,
|
|
88
|
+
username_field=`uname`, username=`admin`, password_field=`pwd`,
|
|
89
|
+
cookie_name=`SID`, read paths at `/` prefix (dashboard_path=
|
|
90
|
+
`/portsConfiguration.html`, stats_path=`/portStatistics.html`,
|
|
91
|
+
pvid_path=`/portPvidConfiguration.html`, vlan_config_path=`/vlanStatus.html`,
|
|
92
|
+
mac_table_path=`/basicAddressTable.html`, poe_status_path=
|
|
93
|
+
`/poeInterfaceConfiguration.html`,
|
|
94
|
+
sysinfo_path=`/base/system/management/sysInfo.html`), html_dialect=
|
|
95
|
+
XE_FASTPATH, scheme_verified=True, reads_verified=False (flipped after live
|
|
96
|
+
cross-verify). Register in `_SPECS`.
|
|
97
|
+
- Test `test_endpoints.py`: gsm7252ps has a spec; test_every_http_model_has_a_spec passes.
|
|
98
|
+
|
|
99
|
+
### Task 5: registry HTTP backend
|
|
100
|
+
- `registry.py`: gsm7252ps backends `{Backend.SNMP}` → `{Backend.SNMP, Backend.HTTP}`.
|
|
101
|
+
- Update the seed docstring/comment note. Test `test_registry.py`.
|
|
102
|
+
|
|
103
|
+
### Task 6: http_read dispatch
|
|
104
|
+
- `http_read.py`: add `_is_xe_fastpath_dialect(spec)`. Wire get_ports/stats/
|
|
105
|
+
pvids/vlans/macs/poe/lldp to the XE parsers (parallel to the m4300 branches).
|
|
106
|
+
get_sensors/get_mgmt_ip: wire to parse_xe_sensors / parse_xe_mgmt_ip via
|
|
107
|
+
sysinfo_path. NO UnsupportedCapabilityError carve-out for this model.
|
|
108
|
+
Mirror both Sync and Async readers.
|
|
109
|
+
- Tests `test_http_read.py` using the fixtures via a mock transport.
|
|
110
|
+
|
|
111
|
+
### Task 7: mock face + seed upgrade
|
|
112
|
+
- New `virtual/web_gsm7252ps.py`: render the XE (A) cell format from
|
|
113
|
+
`VirtualSwitchState` (instance-prefixed v_ cells) for ports/stats/pvids/
|
|
114
|
+
vlans/macs/poe pages.
|
|
115
|
+
- `faces/http.py`: CHEETAH_FORM login (uname+pwd → SID) + XE render dispatch
|
|
116
|
+
for the gsm7252ps model.
|
|
117
|
+
- `seed.py`: upgrade `seed_gsm7252ps` from ILLUSTRATIVE → transcribed from the
|
|
118
|
+
real 52-port capture (real link/speed/PVID/VLAN values from the fixtures).
|
|
119
|
+
- Test `test_virtual_http_face.py`.
|
|
120
|
+
|
|
121
|
+
### Task 8: cross-backend equivalence
|
|
122
|
+
- `test_cross_backend_equivalence.py`: parametrize gsm7252ps SNMP↔HTTP for
|
|
123
|
+
ports/stats/pvids/vlans/macs/poe/lldp, driving both faces of one
|
|
124
|
+
VirtualSwitch, asserting agreement.
|
|
125
|
+
- Full suite green: `uv run --extra http --extra async --extra mcp pytest`.
|
|
126
|
+
|
|
127
|
+
### Task 9 (CONTROLLER-ONLY, live): HTTP↔SNMP cross-verify
|
|
128
|
+
- The controller runs the real HTTP reads vs SNMP against 10.1.5.22, fixes any
|
|
129
|
+
live discrepancy, then flips `_GSM7252PS.reads_verified=True`. Not a subagent
|
|
130
|
+
task (needs the switch + credentials).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# gsm7252ps HTTP backend (XE_FASTPATH dialect) — Design
|
|
2
|
+
|
|
3
|
+
**Goal:** Give the gsm7252ps a full HTTP read backend whose output agrees with
|
|
4
|
+
its existing SNMP backend, grounded entirely in real captures from the live
|
|
5
|
+
switch (10.1.5.22). One step of the broader fleet-HTTP + CLI goal
|
|
6
|
+
(see memory `netgear-fleet-http-and-cli-scope`).
|
|
7
|
+
|
|
8
|
+
**Status:** Design approved 2026-07-23. Grounding captured live (see
|
|
9
|
+
`tmp/live-captures/gsm7252ps_*.html`). Next: writing-plans → subagent-driven.
|
|
10
|
+
|
|
11
|
+
## Live-verified facts (real switch 10.1.5.22, admin login)
|
|
12
|
+
|
|
13
|
+
- **Login:** `CHEETAH_FORM` — `POST /base/cheetah_login.html`, form fields
|
|
14
|
+
`uname=admin` + `pwd=<password>` (+ err_flag=0, submt, login_button.x/y),
|
|
15
|
+
sets an `SID` cookie. Validated live.
|
|
16
|
+
- **Read pages** live at the ROOT `/` prefix (NOT `/base/`, NOT `/v1/`), e.g.
|
|
17
|
+
`/portsConfiguration.html`, `/portStatistics.html`, `/vlanStatus.html`,
|
|
18
|
+
`/portPvidConfiguration.html`, `/basicAddressTable.html`,
|
|
19
|
+
`/poeInterfaceConfiguration.html`, `/portSummary.html`,
|
|
20
|
+
`/lldpRemoteInventory.html`, `/vlanConfiguration.html`, and
|
|
21
|
+
`/base/system/management/sysInfo.html`.
|
|
22
|
+
- **Switch:** 52 ports (1/0/1 .. 1/0/52).
|
|
23
|
+
|
|
24
|
+
## The XE cell format (the crux)
|
|
25
|
+
|
|
26
|
+
Pages are "auto-generated by XE". The M4300 parsers do NOT work here (verified:
|
|
27
|
+
they return 0 rows / raise). Two data formats appear:
|
|
28
|
+
|
|
29
|
+
### (A) Instance-prefixed `v_` cells — STATICALLY PARSEABLE (the core pages)
|
|
30
|
+
Each data cell is an HTML `<TD>` carrying a hidden input:
|
|
31
|
+
```
|
|
32
|
+
<TD class="def alt0" p="1.0.520" id=1_2_10>
|
|
33
|
+
<INPUT xid=1_2_10 TYPE=hidden NAME=1.0.52.v_1_2_10 VALUE="Link Up">Link Up</TD>
|
|
34
|
+
```
|
|
35
|
+
- `NAME=<instance>.v_<row>_<col>` — a row **instance prefix** and a **column
|
|
36
|
+
coordinate** `v_<row>_<col>`.
|
|
37
|
+
- **CORRECTION (2026-07-23, from the committed fixtures):** the instance
|
|
38
|
+
prefix is `1.<row-index>.<row-count>`, NOT `unit.slot.port`. In
|
|
39
|
+
`gsm7252ps_portsConfiguration.html` the FIRST row is
|
|
40
|
+
`NAME=1.0.52.v_1_2_1 VALUE="1/0/1"` and the last is `1.51.52 -> "1/0/52"`:
|
|
41
|
+
the trailing 52 is the ROW COUNT, identical on every row. Reading "1.0.52"
|
|
42
|
+
as "port 52" would label all 52 ports 52. Port identity comes from the row's
|
|
43
|
+
own cells (the `ifindex` column, or the `1/0/N` interface name).
|
|
44
|
+
- There is **NO `<!-- field_name -->` comment** (unlike M4300). Fields are
|
|
45
|
+
addressed by COLUMN INDEX; the column→field meaning is fixed per page and
|
|
46
|
+
must be hardcoded from a real capture (documented beside each map).
|
|
47
|
+
- Example column map for `portsConfiguration.html` (from capture):
|
|
48
|
+
`v_1_2_8`=autoneg ("Auto"), `v_1_2_9`=speed ("1000 Mbps"),
|
|
49
|
+
`v_1_2_10`=link ("Link Up"/"Link Down"). (Implementer: confirm the full
|
|
50
|
+
column set against the fixture; the numeric columns and their labels are
|
|
51
|
+
both present in the captured HTML.)
|
|
52
|
+
|
|
53
|
+
### (B) Plain label/value tables — ALSO STATICALLY PARSEABLE
|
|
54
|
+
`sysInfo.html` (sensors: Temperature/FAN/RPS/Power-Module; system name, MAC,
|
|
55
|
+
product name, up-time; mgmt-IP) does NOT use the `v_` cell format. It uses
|
|
56
|
+
plain label/value table cells, e.g. a `<td class="font10Bold">System MAC
|
|
57
|
+
Address</td>` label followed by a value cell. **The values ARE present in the
|
|
58
|
+
static HTML** — verified live on 10.1.5.22:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
System MAC Address E0:91:F5:0C:D6:DB
|
|
62
|
+
IPv4 Network Interface 10.1.5.22/255.255.255.0
|
|
63
|
+
RPS Operational
|
|
64
|
+
Power Module Operational
|
|
65
|
+
Product Name GSM7252PS 48-Port GE L2+ Managed Stackable PoE Switch
|
|
66
|
+
System Up Time 13 days 7 hours 44 mins 6 secs
|
|
67
|
+
Temperature Status (Sensor Type / Unit table)
|
|
68
|
+
FAN Status (Unit ID / Fan1..Fan8 OK table)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**CORRECTION (2026-07-23):** an earlier draft of this design claimed these
|
|
72
|
+
values were JS-populated and therefore declared `get_sensors`/`get_mgmt_ip`
|
|
73
|
+
HTTP-infeasible. That was WRONG — it came from grepping for the `v_` cell
|
|
74
|
+
pattern (absent here) and inferring "JS-only" instead of reading the page text.
|
|
75
|
+
`sysInfo.html` and its `_rw` variant are byte-identical in content; no extra
|
|
76
|
+
endpoint is needed. Both ops ARE supported over HTTP. Do not re-introduce an
|
|
77
|
+
`UnsupportedCapabilityError` for them.
|
|
78
|
+
|
|
79
|
+
## Read-op feasibility (full-parity target, honest boundary)
|
|
80
|
+
|
|
81
|
+
| Op | HTTP page | Format | HTTP support |
|
|
82
|
+
|----|-----------|--------|--------------|
|
|
83
|
+
| get_ports | portsConfiguration.html / portSummary.html | (A) | YES |
|
|
84
|
+
| get_stats | portStatistics.html | (A) — confirm counters | YES |
|
|
85
|
+
| get_pvids | portPvidConfiguration.html | (A) | YES |
|
|
86
|
+
| get_vlans | vlanStatus.html / vlanConfiguration.html | (A) | YES |
|
|
87
|
+
| get_macs (FDB) | basicAddressTable.html (169 KB) | (A) | YES |
|
|
88
|
+
| get_poe | poeInterfaceConfiguration.html | (A) | YES (48-PoE) |
|
|
89
|
+
| get_lldp | lldpRemoteInventory.html / lldpRemoteDevice.html | (A) | YES if neighbours present; capture to confirm |
|
|
90
|
+
| get_sensors | sysInfo.html | (B) label/value | **YES** — Temperature + FAN tables, RPS/Power Module status |
|
|
91
|
+
| get_mgmt_ip | sysInfo.html | (B) label/value | **YES** — `IPv4 Network Interface` (addr/netmask) + `System MAC Address` |
|
|
92
|
+
|
|
93
|
+
**Every read op the model supports is served over HTTP. There is no
|
|
94
|
+
UnsupportedCapabilityError carve-out for this model.** An op may only be
|
|
95
|
+
declared unsupported if it is PROVEN absent from every page (no value in any
|
|
96
|
+
capture), not merely because the first parse attempt missed it.
|
|
97
|
+
|
|
98
|
+
## Design
|
|
99
|
+
|
|
100
|
+
- **New `HtmlDialect.XE_FASTPATH`** (generic name — a firmware-family trait,
|
|
101
|
+
extensible to other XE switches later; validated only against gsm7252ps now
|
|
102
|
+
and marked as such).
|
|
103
|
+
- **Parser** `parse_xe_rows(html)`: group `NAME=<port>.v_<row>_<col> VALUE="…"`
|
|
104
|
+
cells by port instance (`1.0.N` → port N), each row = {column-coord: value}.
|
|
105
|
+
Then per-op `parse_xe_{port_status,stats,pvids,vlans,macs,poe,lldp}` apply a
|
|
106
|
+
hardcoded column map. (Implemented: each map is documented beside the parser
|
|
107
|
+
AND cross-checked against that page's own visible header row, which carries
|
|
108
|
+
the human label under the same coordinate.) **Plus** a separate `parse_xe_labelled_values(html)` for
|
|
109
|
+
format (B) (label cell → value cell), feeding `parse_xe_sensors` (Temperature
|
|
110
|
+
+ FAN tables, RPS/Power Module) and `parse_xe_mgmt_ip` (`IPv4 Network
|
|
111
|
+
Interface` "addr/netmask" + `System MAC Address`) from `sysInfo.html`.
|
|
112
|
+
Every (A) column map must be grounded in a committed fixture. Reuse existing
|
|
113
|
+
helpers (`_speed_text_to_mbps`, `_int`) where they fit.
|
|
114
|
+
- **Endpoint spec** `_GSM7252PS` (HttpModelSpec): scheme CHEETAH_FORM, login
|
|
115
|
+
`/base/cheetah_login.html`, username_field=uname, username=admin,
|
|
116
|
+
password_field=pwd, cookie SID, read paths at `/` prefix,
|
|
117
|
+
`sysinfo_path="/base/system/management/sysInfo.html"` (serves BOTH sensors and
|
|
118
|
+
mgmt-IP), html_dialect=XE_FASTPATH. `scheme_verified=True` (login validated live);
|
|
119
|
+
`reads_verified=True` only after the live HTTP↔SNMP cross-verify passes.
|
|
120
|
+
- **Registry:** gsm7252ps backends `{SNMP}` → `{SNMP, HTTP}`.
|
|
121
|
+
- **Dispatch:** `http_read.py` gains `_is_xe_fastpath_dialect` wiring each read
|
|
122
|
+
op to its XE parser, INCLUDING get_sensors + get_mgmt_ip via sysInfo.html.
|
|
123
|
+
No UnsupportedCapabilityError carve-out for this model.
|
|
124
|
+
- **Mock:** new `virtual/web_gsm7252ps.py` renders the XE (A) cell format from
|
|
125
|
+
`VirtualSwitchState`; `faces/http.py` adds the CHEETAH_FORM login + XE render.
|
|
126
|
+
`seed_gsm7252ps` upgraded from ILLUSTRATIVE → transcribed from the real
|
|
127
|
+
52-port capture (real link/speed/PVID/VLAN values) so the mock mimics
|
|
128
|
+
hardware.
|
|
129
|
+
- **Fixtures:** `tests/fixtures/http/gsm7252ps_*.html` — the real captures with
|
|
130
|
+
session tokens (SID) scrubbed.
|
|
131
|
+
|
|
132
|
+
## Testing / honesty
|
|
133
|
+
|
|
134
|
+
- Unit tests for each XE parser against the real fixtures (exact expected
|
|
135
|
+
values transcribed from the capture).
|
|
136
|
+
- `test_cross_backend_equivalence.py`: gsm7252ps SNMP↔HTTP for every supported
|
|
137
|
+
op, driving both faces of one VirtualSwitch (mock renders real XE format).
|
|
138
|
+
- One live HTTP↔SNMP cross-verify against 10.1.5.22 before flipping
|
|
139
|
+
`reads_verified=True`.
|
|
140
|
+
- Any op without a grounded capture stays `UnsupportedCapabilityError` — never
|
|
141
|
+
fabricated.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "python-netgear-switch-library"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Python library and CLI to query and control Netgear switches over SNMP, NSDP and HTTP."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Tim Ansell", email = "me@mith.ro" }]
|
|
14
|
+
# cryptography is a first-class core dep: the SSL-certificate upload API
|
|
15
|
+
# (HttpWriter.upload_certificate) uses it to convert an RSA private key to the
|
|
16
|
+
# PKCS#1 "traditional" form the GS728TPP GoAhead XML API requires, with no
|
|
17
|
+
# shelling out to openssl. It ships type stubs, so mypy stays clean.
|
|
18
|
+
dependencies = ["cryptography>=41"]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
# The sync SNMP transport shells out to the net-snmp CLI tools (snmpget/
|
|
22
|
+
# snmpbulkwalk/snmpset), which are a system package (apt install snmp), not a
|
|
23
|
+
# Python dependency; ezsnmp fails to build on arm64 and is intentionally not
|
|
24
|
+
# used.
|
|
25
|
+
sync = []
|
|
26
|
+
async = ["pysnmp>=7.0"]
|
|
27
|
+
http = ["httpx>=0.27"]
|
|
28
|
+
# FASTPATH CLI backend (SSH/telnet/console). paramiko is PINNED <3: old FASTPATH
|
|
29
|
+
# firmware (GSM7252PS/M4300) only offers diffie-hellman-group14-sha1 KEX + the
|
|
30
|
+
# ssh-rsa (SHA-1) host key, which paramiko 3.0 dropped from its default
|
|
31
|
+
# preferred lists (2.12 is confirmed working live). The SSH transport ALSO
|
|
32
|
+
# re-inserts those legacy algorithms explicitly (see transport/cli/ssh.py), so
|
|
33
|
+
# the pin is belt-and-suspenders. pyserial drives the serial-console transport;
|
|
34
|
+
# the telnet transport uses the stdlib telnetlib (removed in Python 3.13 -- see
|
|
35
|
+
# transport/cli/telnet.py).
|
|
36
|
+
ssh = ["paramiko>=2.12,<3", "pyserial>=3.5"]
|
|
37
|
+
testing = ["pysnmp>=7.0", "mcp>=1.2"]
|
|
38
|
+
# MCP server (ngsw-mcp): exposes the read/write API as MCP tools over stdio.
|
|
39
|
+
mcp = ["mcp>=1.2"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
ngsw = "netgear_switch.cli.main:main"
|
|
43
|
+
ngsw-mcp = "netgear_switch.mcp.server:main"
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = ["pytest>=8.0", "ruff>=0.6", "mypy>=1.11", "pytest-cov>=5.0"]
|
|
47
|
+
|
|
48
|
+
# Rolling release: the version is derived from git, never hand-edited and never
|
|
49
|
+
# tagged. Every commit on main increments the commit height, so post-release
|
|
50
|
+
# versioning yields a real, monotonic version (0.0.postN with no tag; X.Y.postN
|
|
51
|
+
# once a vX.Y tag ever exists). "no-local-version" keeps the version PyPI-upload
|
|
52
|
+
# safe. These forms are also valid Debian versions verbatim.
|
|
53
|
+
[tool.hatch.version]
|
|
54
|
+
source = "vcs"
|
|
55
|
+
|
|
56
|
+
[tool.hatch.version.raw-options]
|
|
57
|
+
version_scheme = "post-release"
|
|
58
|
+
local_scheme = "no-local-version"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.hooks.vcs]
|
|
61
|
+
version-file = "src/netgear_switch/_version.py"
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["src/netgear_switch"]
|
|
65
|
+
|
|
66
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
67
|
+
"src/netgear_switch/py.typed" = "netgear_switch/py.typed"
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
addopts = "-ra --cov=netgear_switch --cov-report=term-missing --cov-fail-under=90"
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
target-version = "py311"
|
|
75
|
+
src = ["src", "tests"]
|
|
76
|
+
extend-exclude = [
|
|
77
|
+
"src/netgear_switch/_version.py",
|
|
78
|
+
# Pure-data module: single-line HTML string constants copied byte-for-byte
|
|
79
|
+
# from real GS110EMX captures (see virtual/web_gs110emx.py) -- there is no
|
|
80
|
+
# meaningful line-length/style to enforce on a literal HTML capture.
|
|
81
|
+
"src/netgear_switch/virtual/web_gs110emx_templates.py",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.lint]
|
|
85
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "PT", "TC", "C4", "PIE", "RET", "N"]
|
|
86
|
+
|
|
87
|
+
[tool.mypy]
|
|
88
|
+
packages = ["netgear_switch"]
|
|
89
|
+
strict = true
|
|
90
|
+
python_version = "3.11"
|
|
91
|
+
|
|
92
|
+
[[tool.mypy.overrides]]
|
|
93
|
+
module = "netgear_switch._version"
|
|
94
|
+
ignore_errors = true
|
|
95
|
+
|
|
96
|
+
# The MCP server wraps the external, untyped `mcp` SDK: its FastMCP.tool()
|
|
97
|
+
# decorator is untyped (so every tool trips untyped-decorator) and its
|
|
98
|
+
# handlers return Any. Relax just those two checks for this one module rather
|
|
99
|
+
# than sprinkle per-line ignores; the wrapped library calls stay fully typed.
|
|
100
|
+
[[tool.mypy.overrides]]
|
|
101
|
+
module = "netgear_switch.mcp.*"
|
|
102
|
+
disallow_untyped_decorators = false
|
|
103
|
+
warn_return_any = false
|
|
104
|
+
|
|
105
|
+
[[tool.mypy.overrides]]
|
|
106
|
+
module = "mcp.*"
|
|
107
|
+
ignore_missing_imports = true
|
|
108
|
+
|
|
109
|
+
# The CLI transports lazily import optional, unstubbed third-party libraries
|
|
110
|
+
# (paramiko for SSH, pyserial for the serial console). They are behind the
|
|
111
|
+
# '[ssh]' extra and imported inside methods, so the library stays fully typed;
|
|
112
|
+
# relax only the missing-stub check for these two, like the mcp override above.
|
|
113
|
+
[[tool.mypy.overrides]]
|
|
114
|
+
module = ["paramiko.*", "serial.*"]
|
|
115
|
+
ignore_missing_imports = true
|