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.
Files changed (327) hide show
  1. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/ci.yml +2 -2
  2. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/deb.yml +8 -8
  3. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.github/workflows/publish-pypi.yml +4 -4
  4. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/.gitignore +3 -0
  5. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/PKG-INFO +57 -1
  6. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/README.md +49 -0
  7. python_netgear_switch_library-0.0.post290/docs/superpowers/plans/2026-07-23-gsm7252ps-http-xe-dialect.md +130 -0
  8. python_netgear_switch_library-0.0.post290/docs/superpowers/specs/2026-07-23-gsm7252ps-http-xe-dialect-design.md +141 -0
  9. python_netgear_switch_library-0.0.post290/pyproject.toml +115 -0
  10. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/_dispatch.py +101 -25
  11. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/_version.py +2 -2
  12. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/aio_api.py +95 -12
  13. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/capture.py +2 -3
  14. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/format.py +40 -1
  15. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/main.py +323 -4
  16. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/resolve.py +73 -5
  17. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/safety.py +1 -0
  18. python_netgear_switch_library-0.0.post290/src/netgear_switch/cli_read.py +107 -0
  19. python_netgear_switch_library-0.0.post290/src/netgear_switch/cli_write.py +89 -0
  20. python_netgear_switch_library-0.0.post290/src/netgear_switch/http_read.py +572 -0
  21. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/http_write.py +263 -1
  22. python_netgear_switch_library-0.0.post290/src/netgear_switch/mcp/__init__.py +14 -0
  23. python_netgear_switch_library-0.0.post290/src/netgear_switch/mcp/server.py +519 -0
  24. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/models.py +23 -15
  25. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/nsdp_read.py +22 -3
  26. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/nsdp_write.py +29 -10
  27. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/__init__.py +1 -0
  28. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/commands.py +237 -0
  29. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/cli/parse.py +671 -0
  30. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/crypt.py +1 -0
  31. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/endpoints.py +662 -0
  32. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/forms.py +2 -3
  33. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/parse.py +2131 -0
  34. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/session.py +30 -0
  35. python_netgear_switch_library-0.0.post290/src/netgear_switch/protocols/http/types.py +48 -0
  36. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/__init__.py +1 -0
  37. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/auth.py +1 -0
  38. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/client.py +2 -2
  39. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/parsers.py +11 -5
  40. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/protocol.py +12 -5
  41. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/types.py +8 -1
  42. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/nsdp/write.py +1 -0
  43. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/client.py +1 -0
  44. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/oids.py +78 -8
  45. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/parse.py +240 -29
  46. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/write.py +1 -0
  47. python_netgear_switch_library-0.0.post290/src/netgear_switch/registry.py +334 -0
  48. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/snmp_read.py +132 -33
  49. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/snmp_write.py +139 -68
  50. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/sync_api.py +237 -20
  51. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/nsdp_udp.py +16 -9
  52. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/snmp_pysnmp.py +36 -13
  53. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/__init__.py +1 -0
  54. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/console.py +103 -0
  55. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/session.py +241 -0
  56. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/ssh.py +147 -0
  57. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/cli/telnet.py +104 -0
  58. python_netgear_switch_library-0.0.post290/src/netgear_switch/transport/http/client.py +563 -0
  59. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/sync/nsdp_udp.py +17 -8
  60. {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
  61. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/__init__.py +1 -0
  62. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/cli_fastpath.py +375 -0
  63. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/__init__.py +1 -0
  64. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/faces/cli.py +114 -0
  65. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/faces/http.py +560 -0
  66. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/mibview.py +22 -0
  67. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/nsdp.py +8 -3
  68. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/faces/snmp.py +40 -7
  69. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/seed.py +2580 -0
  70. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/server.py +247 -0
  71. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/state.py +242 -62
  72. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/virtual/web.py +4 -3
  73. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs105pe.py +179 -0
  74. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs110emx.py +173 -0
  75. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs110emx_templates.py +35 -0
  76. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gs728tpp.py +246 -0
  77. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gsm7228ps.py +145 -0
  78. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_gsm7252ps.py +347 -0
  79. python_netgear_switch_library-0.0.post290/src/netgear_switch/virtual/web_m4300.py +202 -0
  80. python_netgear_switch_library-0.0.post290/tests/capture_parity.py +156 -0
  81. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_cli_integration.py +1 -0
  82. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_control_vlan_ip.py +2 -6
  83. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_format.py +11 -5
  84. python_netgear_switch_library-0.0.post290/tests/cli/test_new_commands.py +279 -0
  85. python_netgear_switch_library-0.0.post290/tests/cli/test_op_coverage.py +90 -0
  86. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_prompt_wiring.py +1 -0
  87. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_resolve.py +108 -10
  88. python_netgear_switch_library-0.0.post290/tests/cli/test_serve.py +57 -0
  89. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/conftest.py +37 -0
  90. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/equivalence.py +187 -14
  91. python_netgear_switch_library-0.0.post290/tests/fixtures/captures/gsm7228ps.json +1763 -0
  92. python_netgear_switch_library-0.0.post290/tests/fixtures/captures/gsm7252ps.json +4533 -0
  93. python_netgear_switch_library-0.0.post290/tests/fixtures/captures/m4300-16x.json +4009 -0
  94. python_netgear_switch_library-0.0.post290/tests/fixtures/captures/m4300-24x.json +4085 -0
  95. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_environment.txt +20 -0
  96. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_lldp.txt +57 -0
  97. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_mac_table.txt +21 -0
  98. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_network.txt +17 -0
  99. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_poe.txt +52 -0
  100. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_port_all.txt +81 -0
  101. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_show_version.txt +10 -0
  102. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan.txt +10 -0
  103. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan_brief.txt +2 -0
  104. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7228ps_vlan_port_all.txt +82 -0
  105. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_environment.txt +26 -0
  106. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_interface_ethernet_1_0_1.txt +81 -0
  107. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_lldp_remote_device_all.txt +21 -0
  108. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_mac_addr_table.txt +16 -0
  109. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_network.txt +18 -0
  110. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_poe_port_info_all.txt +20 -0
  111. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_port_all.txt +59 -0
  112. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_version.txt +12 -0
  113. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_90.txt +62 -0
  114. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_brief.txt +17 -0
  115. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/gsm7252ps_show_vlan_port_all.txt +60 -0
  116. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_environment.txt +19 -0
  117. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_interface_ethernet_1_0_1.txt +89 -0
  118. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_ip_management.txt +22 -0
  119. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_lldp_remote_device_all.txt +41 -0
  120. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_mac_addr_table.txt +485 -0
  121. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_poe_port_info_all.txt +20 -0
  122. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_port_all.txt +148 -0
  123. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_version.txt +11 -0
  124. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan.txt +19 -0
  125. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_1.txt +150 -0
  126. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_4.txt +150 -0
  127. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_5.txt +150 -0
  128. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_16x_show_vlan_port_all.txt +148 -0
  129. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_environment.txt +18 -0
  130. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_interface_ethernet_1_0_1.txt +89 -0
  131. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_ip_management.txt +14 -0
  132. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_lldp_remote_device_all.txt +29 -0
  133. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_mac_addr_table.txt +649 -0
  134. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_port_all.txt +157 -0
  135. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_version.txt +11 -0
  136. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan.txt +19 -0
  137. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_5.txt +158 -0
  138. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_90.txt +158 -0
  139. python_netgear_switch_library-0.0.post290/tests/fixtures/cli/m4300_24x_show_vlan_port_all.txt +156 -0
  140. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_membership.html +60 -0
  141. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_portstats.html +113 -0
  142. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_pvid.html +64 -0
  143. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_status.html +162 -0
  144. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_switch_info.html +142 -0
  145. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs105pe_vlancfg.html +73 -0
  146. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_cf8021q.html +173 -0
  147. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_interface_stats.html +157 -0
  148. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_login.html +126 -0
  149. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_port_settings.html +370 -0
  150. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_pvid.html +147 -0
  151. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_redirect.html +15 -0
  152. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_sysinfo.html +150 -0
  153. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs110emx_vlanmembership.html +101 -0
  154. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_device_info_and_sensors.xml +258 -0
  155. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_lldp.xml +153 -0
  156. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_macs.xml +1166 -0
  157. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_mgmt_ip.xml +115 -0
  158. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_poe.xml +557 -0
  159. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_ports.xml +1458 -0
  160. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_pvids_membership.xml +2046 -0
  161. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_vlan_membership_per_vlan.xml +460 -0
  162. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gs728tpp_vlans.xml +153 -0
  163. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_basicAddressTable.html +325 -0
  164. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_lldpRemoteInventory.html +184 -0
  165. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_poeInterfaceConfiguration.html +1590 -0
  166. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portPvidConfiguration.html +1437 -0
  167. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portStatistics.html +1340 -0
  168. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_portsConfiguration.html +1830 -0
  169. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_sysInfo.html +498 -0
  170. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7228ps_vlanStatus.html +179 -0
  171. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_basicAddressTable.html +2082 -0
  172. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_lldpRemoteInventory.html +498 -0
  173. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_login.html +257 -0
  174. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_poeInterfaceConfiguration.html +1555 -0
  175. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portPvidConfiguration.html +1075 -0
  176. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portStatistics.html +1201 -0
  177. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_portsConfiguration.html +1284 -0
  178. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_sysInfo.html +500 -0
  179. python_netgear_switch_library-0.0.post290/tests/fixtures/http/gsm7252ps_vlanStatus.html +233 -0
  180. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_addresstable.html +432 -0
  181. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_ports.html +1569 -0
  182. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_portstats.html +1162 -0
  183. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_pvid.html +1043 -0
  184. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_sysinfo.html +579 -0
  185. python_netgear_switch_library-0.0.post290/tests/fixtures/http/m4300_vlanstatus.html +311 -0
  186. python_netgear_switch_library-0.0.post290/tests/http_specs.py +40 -0
  187. python_netgear_switch_library-0.0.post290/tests/protocols/cli/test_cli_parse.py +593 -0
  188. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/http/test_crypt.py +13 -0
  189. python_netgear_switch_library-0.0.post290/tests/protocols/http/test_endpoints.py +233 -0
  190. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/http/test_forms.py +2 -2
  191. python_netgear_switch_library-0.0.post290/tests/protocols/http/test_parse.py +785 -0
  192. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_parsers.py +32 -8
  193. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_protocol.py +27 -3
  194. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_write_frame.py +1 -1
  195. python_netgear_switch_library-0.0.post290/tests/protocols/snmp/test_oids.py +94 -0
  196. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_mgmt_ip.py +54 -1
  197. python_netgear_switch_library-0.0.post290/tests/protocols/snmp/test_parse_poe_sensors.py +147 -0
  198. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_ports.py +92 -0
  199. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_system_info.py +85 -36
  200. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_vlans.py +3 -9
  201. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_write_encode.py +4 -1
  202. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_aio_api.py +150 -94
  203. python_netgear_switch_library-0.0.post290/tests/test_cli_read.py +324 -0
  204. python_netgear_switch_library-0.0.post290/tests/test_cli_write.py +162 -0
  205. python_netgear_switch_library-0.0.post290/tests/test_cross_backend_equivalence.py +669 -0
  206. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_deb_version.py +25 -7
  207. python_netgear_switch_library-0.0.post290/tests/test_facade_equivalence.py +85 -0
  208. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_http_dispatch.py +16 -7
  209. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_http_equivalence.py +1 -0
  210. python_netgear_switch_library-0.0.post290/tests/test_http_read.py +625 -0
  211. python_netgear_switch_library-0.0.post290/tests/test_http_write.py +803 -0
  212. python_netgear_switch_library-0.0.post290/tests/test_mcp_server.py +396 -0
  213. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_integration.py +4 -3
  214. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_read.py +61 -12
  215. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_nsdp_write.py +18 -10
  216. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_quality_gates.py +1 -0
  217. python_netgear_switch_library-0.0.post290/tests/test_registry.py +166 -0
  218. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_integration.py +8 -6
  219. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_read.py +52 -6
  220. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_snmp_write.py +95 -60
  221. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_sync_api.py +192 -88
  222. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_version.py +1 -0
  223. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_write_equivalence.py +4 -2
  224. python_netgear_switch_library-0.0.post290/tests/transport/_fastpath_fake.py +323 -0
  225. python_netgear_switch_library-0.0.post290/tests/transport/test_cli_console.py +86 -0
  226. python_netgear_switch_library-0.0.post290/tests/transport/test_cli_scp_cert.py +234 -0
  227. python_netgear_switch_library-0.0.post290/tests/transport/test_cli_telnet.py +81 -0
  228. python_netgear_switch_library-0.0.post290/tests/transport/test_http_client.py +523 -0
  229. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_nsdp_udp_sync.py +151 -24
  230. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_set_transport.py +20 -10
  231. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_snmp_netsnmp_cli.py +54 -9
  232. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_snmp_pysnmp.py +78 -23
  233. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/transport/test_value_parity.py +1 -0
  234. python_netgear_switch_library-0.0.post290/tests/virtual/test_cli_cross_backend.py +145 -0
  235. python_netgear_switch_library-0.0.post290/tests/virtual/test_cli_gsm7228ps_parity.py +140 -0
  236. python_netgear_switch_library-0.0.post290/tests/virtual/test_gsm7228ps_seed.py +111 -0
  237. python_netgear_switch_library-0.0.post290/tests/virtual/test_m4300_seeds.py +126 -0
  238. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_mibview.py +39 -6
  239. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_mutable_state.py +3 -3
  240. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_nsdp_state.py +29 -10
  241. python_netgear_switch_library-0.0.post290/tests/virtual/test_serve_daemon.py +146 -0
  242. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_snmp_write_face.py +86 -56
  243. python_netgear_switch_library-0.0.post290/tests/virtual/test_state_seed.py +232 -0
  244. python_netgear_switch_library-0.0.post290/tests/virtual/test_virtual_http_face.py +633 -0
  245. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_virtual_nsdp_face.py +6 -3
  246. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_virtual_snmp_face.py +147 -2
  247. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/virtual/test_web_projection.py +21 -7
  248. python_netgear_switch_library-0.0.post290/uv.lock +1357 -0
  249. python_netgear_switch_library-0.0.post154/pyproject.toml +0 -72
  250. python_netgear_switch_library-0.0.post154/src/netgear_switch/http_read.py +0 -174
  251. python_netgear_switch_library-0.0.post154/src/netgear_switch/protocols/http/endpoints.py +0 -165
  252. python_netgear_switch_library-0.0.post154/src/netgear_switch/protocols/http/parse.py +0 -238
  253. python_netgear_switch_library-0.0.post154/src/netgear_switch/registry.py +0 -227
  254. python_netgear_switch_library-0.0.post154/src/netgear_switch/transport/http/client.py +0 -217
  255. python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/faces/http.py +0 -164
  256. python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/seed.py +0 -220
  257. python_netgear_switch_library-0.0.post154/src/netgear_switch/virtual/server.py +0 -106
  258. python_netgear_switch_library-0.0.post154/tests/protocols/http/test_endpoints.py +0 -59
  259. python_netgear_switch_library-0.0.post154/tests/protocols/http/test_parse.py +0 -132
  260. python_netgear_switch_library-0.0.post154/tests/protocols/snmp/test_oids.py +0 -40
  261. python_netgear_switch_library-0.0.post154/tests/protocols/snmp/test_parse_poe_sensors.py +0 -72
  262. python_netgear_switch_library-0.0.post154/tests/test_facade_equivalence.py +0 -13
  263. python_netgear_switch_library-0.0.post154/tests/test_http_read.py +0 -124
  264. python_netgear_switch_library-0.0.post154/tests/test_http_write.py +0 -409
  265. python_netgear_switch_library-0.0.post154/tests/test_registry.py +0 -95
  266. python_netgear_switch_library-0.0.post154/tests/transport/test_http_client.py +0 -283
  267. python_netgear_switch_library-0.0.post154/tests/virtual/test_state_seed.py +0 -137
  268. python_netgear_switch_library-0.0.post154/tests/virtual/test_virtual_http_face.py +0 -130
  269. python_netgear_switch_library-0.0.post154/uv.lock +0 -573
  270. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/LICENSE +0 -0
  271. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/RELEASING.md +0 -0
  272. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/changelog +0 -0
  273. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/control +0 -0
  274. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/copyright +0 -0
  275. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/rules +0 -0
  276. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/debian/source/format +0 -0
  277. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/docs/superpowers/plans/2026-07-15-foundation.md +0 -0
  278. {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
  279. {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
  280. {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
  281. {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
  282. {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
  283. {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
  284. {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
  285. {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
  286. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/apt-index.html +0 -0
  287. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/ci-build.sh +0 -0
  288. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/packaging/deb-version.py +0 -0
  289. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/__init__.py +0 -0
  290. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/__init__.py +0 -0
  291. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/cli/context.py +0 -0
  292. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/config.py +0 -0
  293. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/errors.py +0 -0
  294. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/__init__.py +0 -0
  295. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/http/__init__.py +0 -0
  296. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/protocols/snmp/__init__.py +0 -0
  297. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/py.typed +0 -0
  298. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/__init__.py +0 -0
  299. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/aio/__init__.py +0 -0
  300. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/http/__init__.py +0 -0
  301. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/src/netgear_switch/transport/sync/__init__.py +0 -0
  302. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_capture.py +0 -0
  303. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_control_poe_port.py +0 -0
  304. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_main_framework.py +0 -0
  305. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_read_commands.py +0 -0
  306. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/cli/test_safety.py +0 -0
  307. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_dashboard.html +0 -0
  308. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_login.html +0 -0
  309. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_membership.html +0 -0
  310. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_poestatus.html +0 -0
  311. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_portstats.html +0 -0
  312. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_pvid.html +0 -0
  313. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/http/gs305ep_vlancfg.html +0 -0
  314. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_fans.txt +0 -0
  315. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_ifhighspeed.txt +0 -0
  316. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_ifoperstatus.txt +0 -0
  317. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/fixtures/snmp/gsm7252ps_psu.txt +0 -0
  318. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/nsdp/test_auth.py +0 -0
  319. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_client.py +0 -0
  320. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/protocols/snmp/test_parse_lldp_macs.py +0 -0
  321. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_config.py +0 -0
  322. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_dispatch.py +0 -0
  323. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_errors.py +0 -0
  324. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_import.py +0 -0
  325. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_models.py +0 -0
  326. {python_netgear_switch_library-0.0.post154 → python_netgear_switch_library-0.0.post290}/tests/test_models_snmp_read.py +0 -0
  327. {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@v4
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@v6
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@v4
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@v4
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@v4
48
+ - uses: actions/checkout@v5
49
49
 
50
50
  - name: Download trixie debs
51
- uses: actions/download-artifact@v4
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@v4
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@v5
100
+ - uses: actions/configure-pages@v6
101
101
 
102
- - uses: actions/upload-pages-artifact@v3
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@v4
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@v4
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@v6
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@v4
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@v4
44
+ - uses: actions/download-artifact@v8
45
45
  with:
46
46
  name: dist
47
47
  path: dist/
@@ -54,3 +54,6 @@ tmp/
54
54
  /.vscode
55
55
  /.zprofile
56
56
  /.zshrc
57
+
58
+ # Playwright MCP scratch output
59
+ .playwright-mcp/
@@ -1,17 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-netgear-switch-library
3
- Version: 0.0.post154
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
@@ -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