python-netgear-switch-library 0.0.post354__tar.gz → 0.0.post355__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 (396) hide show
  1. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/PKG-INFO +1 -1
  2. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/_version.py +2 -2
  3. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/capabilities.py +17 -1
  4. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/http_write.py +23 -0
  5. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/endpoints.py +21 -0
  6. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web.py +2 -2
  7. python_netgear_switch_library-0.0.post355/tests/virtual/test_write_outcomes.py +136 -0
  8. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/.github/workflows/ci.yml +0 -0
  9. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/.github/workflows/deb.yml +0 -0
  10. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/.github/workflows/publish-pypi.yml +0 -0
  11. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/.gitignore +0 -0
  12. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/.readthedocs.yaml +0 -0
  13. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/CLAUDE.md +0 -0
  14. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/LICENSE +0 -0
  15. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/README.md +0 -0
  16. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/RELEASING.md +0 -0
  17. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/debian/changelog +0 -0
  18. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/debian/control +0 -0
  19. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/debian/copyright +0 -0
  20. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/debian/rules +0 -0
  21. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/debian/source/format +0 -0
  22. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/Makefile +0 -0
  23. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_ext/filelinks.py +0 -0
  24. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_ext/support_tables.py +0 -0
  25. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs105pe.png +0 -0
  26. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs110emx.png +0 -0
  27. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs305ep.png +0 -0
  28. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs728tpp.png +0 -0
  29. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gsm7228ps.jpg +0 -0
  30. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gsm7252ps.jpg +0 -0
  31. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/m4300-16x.png +0 -0
  32. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/models/m4300-24x.png +0 -0
  33. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/_static/ngsw.css +0 -0
  34. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/cli.rst +0 -0
  35. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/core.rst +0 -0
  36. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/facades.rst +0 -0
  37. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/index.rst +0 -0
  38. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/mcp.rst +0 -0
  39. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/operations.rst +0 -0
  40. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/protocols.rst +0 -0
  41. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/transport.rst +0 -0
  42. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/api/virtual.rst +0 -0
  43. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/cli.rst +0 -0
  44. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/conf.py +0 -0
  45. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/development.rst +0 -0
  46. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/fake/index.rst +0 -0
  47. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/fake/internals.rst +0 -0
  48. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/fake/serving.rst +0 -0
  49. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/fake/testing.rst +0 -0
  50. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/concepts.rst +0 -0
  51. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/configuration.rst +0 -0
  52. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/installation.rst +0 -0
  53. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/principles.rst +0 -0
  54. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/quickstart.rst +0 -0
  55. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/reading.rst +0 -0
  56. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/guide/writing.rst +0 -0
  57. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/index.rst +0 -0
  58. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/mcp.rst +0 -0
  59. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gs105pe.rst +0 -0
  60. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gs110emx.rst +0 -0
  61. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gs305ep.rst +0 -0
  62. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gs728tpp.rst +0 -0
  63. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gsm7228ps.rst +0 -0
  64. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/gsm7252ps.rst +0 -0
  65. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/index.rst +0 -0
  66. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/m4300-16x.rst +0 -0
  67. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/m4300-24x.rst +0 -0
  68. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/models/support.rst +0 -0
  69. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/cli.rst +0 -0
  70. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/cheetah-form.rst +0 -0
  71. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/cheetah-v1.rst +0 -0
  72. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/gambit.rst +0 -0
  73. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/merge-hash-cgi.rst +0 -0
  74. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/xml-api.rst +0 -0
  75. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/http.rst +0 -0
  76. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/index.rst +0 -0
  77. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/nsdp.rst +0 -0
  78. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/protocols/snmp.rst +0 -0
  79. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-15-foundation.md +0 -0
  80. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-17-slice-02-snmp-read-core.md +0 -0
  81. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-17-slice-03-read-apis.md +0 -0
  82. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-04-snmp-write.md +0 -0
  83. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-05-nsdp.md +0 -0
  84. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-06-http.md +0 -0
  85. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-07-cli.md +0 -0
  86. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-08-packaging-ci.md +0 -0
  87. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-23-gsm7252ps-http-xe-dialect.md +0 -0
  88. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-07-15-netgear-control-library-design.md +0 -0
  89. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-07-23-gsm7252ps-http-xe-dialect-design.md +0 -0
  90. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-08-02-snmp-surface-findings.md +0 -0
  91. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/packaging/apt-index.html +0 -0
  92. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/packaging/ci-build.sh +0 -0
  93. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/packaging/deb-version.py +0 -0
  94. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/pyproject.toml +0 -0
  95. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/__init__.py +0 -0
  96. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/_dispatch.py +0 -0
  97. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/aio_api.py +0 -0
  98. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/__init__.py +0 -0
  99. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/capture.py +0 -0
  100. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/context.py +0 -0
  101. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/format.py +0 -0
  102. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/main.py +0 -0
  103. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/resolve.py +0 -0
  104. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/safety.py +0 -0
  105. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli_read.py +0 -0
  106. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli_write.py +0 -0
  107. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/config.py +0 -0
  108. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/errors.py +0 -0
  109. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/http_read.py +0 -0
  110. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/mcp/__init__.py +0 -0
  111. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/mcp/server.py +0 -0
  112. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/models.py +0 -0
  113. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/nsdp_read.py +0 -0
  114. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/nsdp_write.py +0 -0
  115. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/__init__.py +0 -0
  116. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/__init__.py +0 -0
  117. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/commands.py +0 -0
  118. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/parse.py +0 -0
  119. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/__init__.py +0 -0
  120. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/crypt.py +0 -0
  121. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/forms.py +0 -0
  122. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/parse.py +0 -0
  123. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/session.py +0 -0
  124. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/types.py +0 -0
  125. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/__init__.py +0 -0
  126. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/auth.py +0 -0
  127. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/client.py +0 -0
  128. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/parsers.py +0 -0
  129. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/protocol.py +0 -0
  130. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/types.py +0 -0
  131. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/write.py +0 -0
  132. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/__init__.py +0 -0
  133. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/client.py +0 -0
  134. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/oids.py +0 -0
  135. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/parse.py +0 -0
  136. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/write.py +0 -0
  137. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/py.typed +0 -0
  138. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/registry.py +0 -0
  139. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/snmp_read.py +0 -0
  140. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/snmp_write.py +0 -0
  141. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/sync_api.py +0 -0
  142. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/__init__.py +0 -0
  143. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/__init__.py +0 -0
  144. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/nsdp_udp.py +0 -0
  145. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/snmp_pysnmp.py +0 -0
  146. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/__init__.py +0 -0
  147. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/console.py +0 -0
  148. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/session.py +0 -0
  149. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/ssh.py +0 -0
  150. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/telnet.py +0 -0
  151. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/http/__init__.py +0 -0
  152. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/http/client.py +0 -0
  153. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/__init__.py +0 -0
  154. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/nsdp_udp.py +0 -0
  155. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/snmp_netsnmp_cli.py +0 -0
  156. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/__init__.py +0 -0
  157. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/cli_fastpath.py +0 -0
  158. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/__init__.py +0 -0
  159. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/cli.py +0 -0
  160. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/http.py +0 -0
  161. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/mibview.py +0 -0
  162. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/nsdp.py +0 -0
  163. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/snmp.py +0 -0
  164. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/seed.py +0 -0
  165. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/server.py +0 -0
  166. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/state.py +0 -0
  167. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_fastpath_vlan.py +0 -0
  168. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_fastpath_xui.py +0 -0
  169. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs105pe.py +0 -0
  170. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs110emx.py +0 -0
  171. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs110emx_templates.py +0 -0
  172. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs728tpp.py +0 -0
  173. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gsm7228ps.py +0 -0
  174. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gsm7252ps.py +0 -0
  175. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_m4300.py +0 -0
  176. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/capture_parity.py +0 -0
  177. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_capture.py +0 -0
  178. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_cli_integration.py +0 -0
  179. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_control_poe_port.py +0 -0
  180. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_control_vlan_ip.py +0 -0
  181. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_format.py +0 -0
  182. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_main_framework.py +0 -0
  183. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_new_commands.py +0 -0
  184. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_op_coverage.py +0 -0
  185. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_prompt_wiring.py +0 -0
  186. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_read_commands.py +0 -0
  187. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_resolve.py +0 -0
  188. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_safety.py +0 -0
  189. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/cli/test_serve.py +0 -0
  190. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/conftest.py +0 -0
  191. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/equivalence.py +0 -0
  192. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/gsm7228ps.json +0 -0
  193. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/gsm7252ps.json +0 -0
  194. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/m4300-16x.json +0 -0
  195. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/m4300-24x.json +0 -0
  196. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_environment.txt +0 -0
  197. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_lldp.txt +0 -0
  198. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_mac_table.txt +0 -0
  199. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_network.txt +0 -0
  200. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_poe.txt +0 -0
  201. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_port_all.txt +0 -0
  202. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_show_version.txt +0 -0
  203. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan.txt +0 -0
  204. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan_brief.txt +0 -0
  205. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan_port_all.txt +0 -0
  206. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_environment.txt +0 -0
  207. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_interface_ethernet_1_0_1.txt +0 -0
  208. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_lldp_remote_device_all.txt +0 -0
  209. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_mac_addr_table.txt +0 -0
  210. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_network.txt +0 -0
  211. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_poe_port_info_all.txt +0 -0
  212. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_port_all.txt +0 -0
  213. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_version.txt +0 -0
  214. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_90.txt +0 -0
  215. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_brief.txt +0 -0
  216. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_port_all.txt +0 -0
  217. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_environment.txt +0 -0
  218. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_interface_ethernet_1_0_1.txt +0 -0
  219. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_ip_management.txt +0 -0
  220. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_lldp_remote_device_all.txt +0 -0
  221. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_mac_addr_table.txt +0 -0
  222. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_poe_port_info_all.txt +0 -0
  223. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_port_all.txt +0 -0
  224. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_version.txt +0 -0
  225. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan.txt +0 -0
  226. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_1.txt +0 -0
  227. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_4.txt +0 -0
  228. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_5.txt +0 -0
  229. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_port_all.txt +0 -0
  230. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_environment.txt +0 -0
  231. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_interface_ethernet_1_0_1.txt +0 -0
  232. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_ip_management.txt +0 -0
  233. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_lldp_remote_device_all.txt +0 -0
  234. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_mac_addr_table.txt +0 -0
  235. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_port_all.txt +0 -0
  236. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_version.txt +0 -0
  237. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan.txt +0 -0
  238. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_5.txt +0 -0
  239. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_90.txt +0 -0
  240. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_port_all.txt +0 -0
  241. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_membership.html +0 -0
  242. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_portstats.html +0 -0
  243. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_pvid.html +0 -0
  244. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_status.html +0 -0
  245. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_switch_info.html +0 -0
  246. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_vlancfg.html +0 -0
  247. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_cf8021q.html +0 -0
  248. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_interface_stats.html +0 -0
  249. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_login.html +0 -0
  250. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_port_settings.html +0 -0
  251. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_pvid.html +0 -0
  252. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_redirect.html +0 -0
  253. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_sysinfo.html +0 -0
  254. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_vlanmembership.html +0 -0
  255. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_dashboard.html +0 -0
  256. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_login.html +0 -0
  257. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_membership.html +0 -0
  258. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_poestatus.html +0 -0
  259. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_portstats.html +0 -0
  260. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_pvid.html +0 -0
  261. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_vlancfg.html +0 -0
  262. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_device_info_and_sensors.xml +0 -0
  263. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_lldp.xml +0 -0
  264. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_macs.xml +0 -0
  265. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_mgmt_ip.xml +0 -0
  266. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_poe.xml +0 -0
  267. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_ports.xml +0 -0
  268. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_pvids_membership.xml +0 -0
  269. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_vlan_membership_per_vlan.xml +0 -0
  270. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_vlans.xml +0 -0
  271. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_basicAddressTable.html +0 -0
  272. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_ipConfiguration.html +0 -0
  273. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_lldpRemoteInventory.html +0 -0
  274. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_poeInterfaceConfiguration.html +0 -0
  275. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portPvidConfiguration.html +0 -0
  276. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portStatistics.html +0 -0
  277. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portsConfiguration.html +0 -0
  278. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_sysInfo.html +0 -0
  279. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_vlanPortCfg_vlan5.html +0 -0
  280. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_vlanStatus.html +0 -0
  281. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_basicAddressTable.html +0 -0
  282. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_http_configuration.html +0 -0
  283. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_ipConfiguration.html +0 -0
  284. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_lldpRemoteInventory.html +0 -0
  285. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_login.html +0 -0
  286. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_poeInterfaceConfiguration.html +0 -0
  287. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portPvidConfiguration.html +0 -0
  288. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portStatistics.html +0 -0
  289. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portsConfiguration.html +0 -0
  290. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_sysInfo.html +0 -0
  291. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_syslog_configuration.html +0 -0
  292. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_user_configuration.html +0 -0
  293. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanPortCfg_vlan1.html +0 -0
  294. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanPortCfg_vlan141.html +0 -0
  295. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanStatus.html +0 -0
  296. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlan_status_live.html +0 -0
  297. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_lldpRemoteInventory.html +0 -0
  298. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_mgmtVlanIpv4Configuration.html +0 -0
  299. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_poeInterfaceConfiguration.html +0 -0
  300. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_portsConfiguration.html +0 -0
  301. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_vlanportcfg_vlan4.html +0 -0
  302. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_24x_poeInterfaceConfiguration.html +0 -0
  303. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_addresstable.html +0 -0
  304. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_lldpRemoteInventory.html +0 -0
  305. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_mgmtVlanIpv4Configuration.html +0 -0
  306. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_ports.html +0 -0
  307. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_portstats.html +0 -0
  308. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_pvid.html +0 -0
  309. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_sysinfo.html +0 -0
  310. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_vlanportcfg_vlan1.html +0 -0
  311. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_vlanstatus.html +0 -0
  312. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_fans.txt +0 -0
  313. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_ifhighspeed.txt +0 -0
  314. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_ifoperstatus.txt +0 -0
  315. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_psu.txt +0 -0
  316. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/http_specs.py +0 -0
  317. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/cli/test_cli_parse.py +0 -0
  318. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_crypt.py +0 -0
  319. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_endpoints.py +0 -0
  320. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_forms.py +0 -0
  321. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_parse.py +0 -0
  322. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_auth.py +0 -0
  323. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_parsers.py +0 -0
  324. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_protocol.py +0 -0
  325. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_write_frame.py +0 -0
  326. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_client.py +0 -0
  327. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_oids.py +0 -0
  328. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_lldp_macs.py +0 -0
  329. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_mgmt_ip.py +0 -0
  330. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_poe_sensors.py +0 -0
  331. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_ports.py +0 -0
  332. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_system_info.py +0 -0
  333. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_vlans.py +0 -0
  334. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_write_encode.py +0 -0
  335. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_aio_api.py +0 -0
  336. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_capabilities.py +0 -0
  337. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_cli_read.py +0 -0
  338. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_cli_vlan_write.py +0 -0
  339. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_cli_write.py +0 -0
  340. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_config.py +0 -0
  341. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_cross_backend_equivalence.py +0 -0
  342. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_deb_version.py +0 -0
  343. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_dispatch.py +0 -0
  344. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_docs_coverage.py +0 -0
  345. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_errors.py +0 -0
  346. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_facade_equivalence.py +0 -0
  347. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_dispatch.py +0 -0
  348. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_equivalence.py +0 -0
  349. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_read.py +0 -0
  350. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_vlan_membership.py +0 -0
  351. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_write.py +0 -0
  352. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_http_xui_writes.py +0 -0
  353. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_import.py +0 -0
  354. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_mcp_server.py +0 -0
  355. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_models.py +0 -0
  356. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_models_snmp_read.py +0 -0
  357. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_integration.py +0 -0
  358. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_read.py +0 -0
  359. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_write.py +0 -0
  360. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_public_api.py +0 -0
  361. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_quality_gates.py +0 -0
  362. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_registry.py +0 -0
  363. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_integration.py +0 -0
  364. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_read.py +0 -0
  365. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_write.py +0 -0
  366. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_sync_api.py +0 -0
  367. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_version.py +0 -0
  368. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/test_write_equivalence.py +0 -0
  369. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/_fastpath_fake.py +0 -0
  370. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_console.py +0 -0
  371. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_scp_cert.py +0 -0
  372. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_telnet.py +0 -0
  373. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_http_client.py +0 -0
  374. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_nsdp_udp_sync.py +0 -0
  375. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_set_transport.py +0 -0
  376. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_snmp_netsnmp_cli.py +0 -0
  377. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_snmp_pysnmp.py +0 -0
  378. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/transport/test_value_parity.py +0 -0
  379. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_cli_cross_backend.py +0 -0
  380. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_cli_gsm7228ps_parity.py +0 -0
  381. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_gsm7228ps_seed.py +0 -0
  382. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_hostname.py +0 -0
  383. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_m4300_seeds.py +0 -0
  384. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_mibview.py +0 -0
  385. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_mutable_state.py +0 -0
  386. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_nsdp_state.py +0 -0
  387. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_serve_daemon.py +0 -0
  388. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_snmp_write_face.py +0 -0
  389. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_state_seed.py +0 -0
  390. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_switchport_vlan_write.py +0 -0
  391. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_syslog_fidelity.py +0 -0
  392. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_http_face.py +0 -0
  393. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_nsdp_face.py +0 -0
  394. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_snmp_face.py +0 -0
  395. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_web_projection.py +0 -0
  396. {python_netgear_switch_library-0.0.post354 → python_netgear_switch_library-0.0.post355}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-netgear-switch-library
3
- Version: 0.0.post354
3
+ Version: 0.0.post355
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
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.0.post354'
22
- __version_tuple__ = version_tuple = (0, 0, 'post354')
21
+ __version__ = version = '0.0.post355'
22
+ __version_tuple__ = version_tuple = (0, 0, 'post355')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -101,6 +101,11 @@ class Operation:
101
101
 
102
102
  _CLI_BACKENDS = frozenset({Backend.SSH, Backend.TELNET, Backend.CONSOLE})
103
103
 
104
+ #: HTTP writes implemented by scraping the Plus dialect's CSRF token. A dialect
105
+ #: without that token cannot serve any of them -- see
106
+ #: ``endpoints.dialect_has_csrf_hash`` for the measurement.
107
+ _CSRF_HTTP_WRITES = frozenset({"create_vlan", "delete_vlan"})
108
+
104
109
  READ_OPERATIONS: tuple[Operation, ...] = (
105
110
  Operation("get_ports", OperationKind.READ, "Per-port link/admin status"),
106
111
  Operation("get_stats", OperationKind.READ, "Per-port octet/packet counters"),
@@ -351,7 +356,7 @@ def _http_path_for(spec: HttpModelSpec, op: Operation) -> str | None:
351
356
 
352
357
  def _http_support(model: SwitchModel, op: Operation) -> tuple[Support, str]:
353
358
  from .http_write import CERT_UPLOAD_KNOWN_UNIMPLEMENTED
354
- from .protocols.http.endpoints import http_spec
359
+ from .protocols.http.endpoints import dialect_has_csrf_hash, http_spec
355
360
 
356
361
  spec = http_spec(model)
357
362
  if not spec.reads_verified:
@@ -374,6 +379,17 @@ def _http_support(model: SwitchModel, op: Operation) -> tuple[Support, str]:
374
379
  f"this model takes a certificate by {mechanism}, not over the "
375
380
  "web UI -- use upload_certificate_scp",
376
381
  )
382
+ if op.name in _CSRF_HTTP_WRITES and not dialect_has_csrf_hash(spec.html_dialect):
383
+ # These writers scrape an <input name="hash"> before posting, and this
384
+ # dialect's pages do not carry one -- MEASURED on gsm7252ps and
385
+ # gs110emx, see endpoints.dialect_has_csrf_hash. Driving them raises
386
+ # HttpUnexpectedPageError on real hardware, so claiming support here
387
+ # would publish a support table that contradicts the device.
388
+ return (
389
+ Support.UNSUPPORTED,
390
+ f"model {model.key!r} web UI carries no CSRF 'hash' token, which "
391
+ f"the HTTP {op.name} writer requires",
392
+ )
377
393
  path = _http_path_for(spec, op)
378
394
  if path is None:
379
395
  return (
@@ -268,6 +268,25 @@ def _check_goahead_upload_response(text: str) -> None:
268
268
  )
269
269
 
270
270
 
271
+ def _require_csrf_dialect(spec: HttpModelSpec, model_key: str, op: str) -> None:
272
+ """Refuse a hash-scraping write on a dialect whose pages carry no token.
273
+
274
+ This is a CAPABILITY refusal, not a surprise, so it raises
275
+ UnsupportedCapabilityError rather than HttpUnexpectedPageError: the writer
276
+ is built on the Plus form, and MEASURED probes of gsm7252ps and gs110emx
277
+ found no ``<input name="hash">`` anywhere in their write pages. Saying so by
278
+ type is what lets the facade, the capability table and the caller agree --
279
+ and what stops a bare "unexpected page" from reading like a transient fault.
280
+ """
281
+ from .protocols.http.endpoints import dialect_has_csrf_hash
282
+
283
+ if not dialect_has_csrf_hash(spec.html_dialect):
284
+ raise UnsupportedCapabilityError(
285
+ f"model {model_key!r} web UI carries no CSRF 'hash' token, which "
286
+ f"the HTTP {op} writer requires"
287
+ )
288
+
289
+
271
290
  def _csrf(html: str) -> str:
272
291
  token = parse.parse_csrf_hash(html)
273
292
  if token is None:
@@ -747,6 +766,7 @@ class HttpWriter:
747
766
  return shown
748
767
 
749
768
  def create_vlan(self, vlan: int, name: str, *, force: bool = False) -> None:
769
+ _require_csrf_dialect(self._spec, self.model.key, "create_vlan")
750
770
  del name, force # web UI 8021qCf.cgi has no VLAN-name field (GROUNDED).
751
771
  path = _require_path(self.model.key, self._spec.vlan_config_path, "VLAN config")
752
772
  page = self.session.get_page(path)
@@ -760,6 +780,7 @@ class HttpWriter:
760
780
  )
761
781
 
762
782
  def delete_vlan(self, vlan: int, *, force: bool = False) -> None:
783
+ _require_csrf_dialect(self._spec, self.model.key, "delete_vlan")
763
784
  del force # VLAN delete disruptiveness is guarded per-member elsewhere.
764
785
  path = _require_path(self.model.key, self._spec.vlan_config_path, "VLAN config")
765
786
  page = self.session.get_page(path)
@@ -1215,6 +1236,7 @@ class AsyncHttpWriter:
1215
1236
  return shown
1216
1237
 
1217
1238
  async def create_vlan(self, vlan: int, name: str, *, force: bool = False) -> None:
1239
+ _require_csrf_dialect(self._spec, self.model.key, "create_vlan")
1218
1240
  del name, force
1219
1241
  path = _require_path(self.model.key, self._spec.vlan_config_path, "VLAN config")
1220
1242
  page = await self.session.get_page(path)
@@ -1228,6 +1250,7 @@ class AsyncHttpWriter:
1228
1250
  )
1229
1251
 
1230
1252
  async def delete_vlan(self, vlan: int, *, force: bool = False) -> None:
1253
+ _require_csrf_dialect(self._spec, self.model.key, "delete_vlan")
1231
1254
  del force
1232
1255
  path = _require_path(self.model.key, self._spec.vlan_config_path, "VLAN config")
1233
1256
  page = await self.session.get_page(path)
@@ -140,6 +140,27 @@ class HtmlDialect(enum.Enum):
140
140
  GOAHEAD_XML = "goahead_xml"
141
141
 
142
142
 
143
+ def dialect_has_csrf_hash(dialect: HtmlDialect) -> bool:
144
+ """Whether this dialect's pages carry an ``<input name="hash">`` CSRF token.
145
+
146
+ ``http_write`` scrapes that token before every form post, so a dialect
147
+ without one cannot be driven by those writers at all.
148
+
149
+ MEASURED 2026-08-02, not inferred. Live probes found NO hash on any write
150
+ page of gsm7252ps (10.1.5.22: vlanStatus, poeInterfaceConfiguration,
151
+ portPvidConfiguration, vlan_port_cfg, portsConfiguration) nor of gs110emx
152
+ (10.1.5.25: Cf8021q, vlan_pvidsetting). Only the Plus ``.cgi`` pages have
153
+ it -- the surface ``HttpWriter`` was originally written against.
154
+
155
+ ONE definition, read by the capability oracle AND by the virtual switch, so
156
+ the mock can never emit a token the hardware lacks. That divergence is
157
+ exactly how HTTP ``create_vlan`` passed the entire test suite while failing
158
+ on all four FASTPATH switches.
159
+ """
160
+ return dialect in {HtmlDialect.STANDARD, HtmlDialect.GS105PE}
161
+
162
+
163
+
143
164
  @dataclass(frozen=True)
144
165
  class XuiMgmtIpFields:
145
166
  """Which fields of a FASTPATH XUI management-IP page carry what.
@@ -57,9 +57,9 @@ def _has_csrf_hash(spec: HttpModelSpec) -> bool:
57
57
  where the hardware does makes the mock refuse the same write the switch
58
58
  refuses.
59
59
  """
60
- from ..protocols.http.endpoints import HtmlDialect
60
+ from ..protocols.http.endpoints import dialect_has_csrf_hash
61
61
 
62
- return spec.html_dialect in {HtmlDialect.STANDARD, HtmlDialect.GS105PE}
62
+ return dialect_has_csrf_hash(spec.html_dialect)
63
63
 
64
64
 
65
65
  def render_page(
@@ -0,0 +1,136 @@
1
+ # tests/virtual/test_write_outcomes.py
2
+ """Writes must CHANGE STATE, not merely reach a backend.
3
+
4
+ ``tests/test_capabilities.py`` deliberately asserts something weaker: that the
5
+ facade dispatches an operation to the backend the table names. Its ``_refused``
6
+ counts only ``UnsupportedCapabilityError``/``NotImplementedError`` as a refusal,
7
+ so any other exception means "the backend accepted the op and tried" -- which is
8
+ exactly what ``Support.SUPPORTED`` claims there.
9
+
10
+ That is a reasonable contract, and it is why a write which reaches the right
11
+ backend and then FAILS there is invisible to it. HTTP ``create_vlan`` did that
12
+ on four FASTPATH models for as long as the operation has existed: the writer
13
+ scrapes an ``<input name="hash">`` CSRF token those pages do not carry, and the
14
+ mock hid it by emitting one anyway. 27 green capability tests coexisted with a
15
+ write that had never worked on real hardware.
16
+
17
+ This module closes that gap for the VLAN lifecycle: for every (model, backend)
18
+ the capability table claims is SUPPORTED, create a VLAN and assert it is
19
+ actually there afterwards, then delete it and assert it is actually gone.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ import pytest
27
+
28
+ from netgear_switch.capabilities import backends_for, operation, support
29
+ from netgear_switch.protocols.cli.commands import CLI_BACKENDS
30
+ from netgear_switch.protocols.http.endpoints import http_spec
31
+ from netgear_switch.registry import Backend, get_model
32
+ from netgear_switch.sync_api import SyncSwitch
33
+ from netgear_switch.transport.http.client import HttpClient
34
+ from netgear_switch.transport.sync.nsdp_udp import UdpNsdpClient
35
+ from netgear_switch.transport.sync.snmp_netsnmp_cli import NetsnmpCliClient
36
+ from netgear_switch.virtual.server import VirtualSwitch
37
+
38
+ if TYPE_CHECKING:
39
+ from netgear_switch.registry import SwitchModel
40
+
41
+ #: gs105pe is expected to FAIL: its HTTP create_vlan reports
42
+ #: WriteVerificationError("VLAN 4007 was not created") against the mock. That is
43
+ #: a REAL defect this module exposed, not a test artefact -- an earlier ad-hoc
44
+ #: probe called it working only because it never deleted between backends, so
45
+ #: the HTTP check saw the VLAN NSDP had already created. Marked xfail(strict) so
46
+ #: the suite stays honest AND tells us the moment it starts passing.
47
+ XFAIL_MODELS = {"gs105pe"}
48
+
49
+ SEEDED_MODELS = (
50
+ "gsm7252ps",
51
+ "gsm7228ps",
52
+ "m4300-24x",
53
+ "m4300-16x",
54
+ "gs110emx",
55
+ "gs305ep",
56
+ "gs105pe",
57
+ "gs728tpp",
58
+ )
59
+
60
+ #: A throwaway id from the range this project reserves for testing (4001-4008).
61
+ TEST_VLAN = 4007
62
+
63
+
64
+ def _facade(mock: VirtualSwitch, model: SwitchModel) -> SyncSwitch:
65
+ """A facade wired to every face this mock bound, so no op reaches a network."""
66
+ kwargs: dict[str, Any] = {}
67
+ if Backend.SNMP in model.backends:
68
+ snmp = NetsnmpCliClient(f"{mock.host}:{mock.port}", mock.community)
69
+ kwargs["snmp_client"] = snmp
70
+ kwargs["snmp_write_client"] = snmp
71
+ if Backend.NSDP in model.backends:
72
+ nsdp = UdpNsdpClient(
73
+ mock.host, client_port=0, server_port=mock.port, timeout=2.0
74
+ )
75
+ kwargs["nsdp_client"] = nsdp
76
+ kwargs["nsdp_write_client"] = nsdp
77
+ kwargs["nsdp_password"] = mock.nsdp_password
78
+ if Backend.HTTP in model.backends:
79
+ kwargs["http_client"] = HttpClient(
80
+ f"{mock.host}:{mock.http_port}", mock.http_password, http_spec(model)
81
+ )
82
+ if CLI_BACKENDS & model.backends:
83
+ kwargs["cli_client"] = mock.cli_session()
84
+ return SyncSwitch(model, host=mock.host, **kwargs)
85
+
86
+
87
+ @pytest.mark.parametrize(
88
+ "key",
89
+ [
90
+ pytest.param(
91
+ k,
92
+ marks=pytest.mark.xfail(
93
+ strict=True,
94
+ reason="HTTP create_vlan does not apply on this dialect; see "
95
+ "XFAIL_MODELS",
96
+ ),
97
+ )
98
+ if k in XFAIL_MODELS
99
+ else k
100
+ for k in SEEDED_MODELS
101
+ ],
102
+ )
103
+ def test_claimed_vlan_writes_actually_change_state(key: str) -> None:
104
+ model = get_model(key)
105
+ create, delete = operation("create_vlan"), operation("delete_vlan")
106
+ with VirtualSwitch(model=key) as mock:
107
+ switch = _facade(mock, model)
108
+ for backend in backends_for(model):
109
+ if not support(model, backend, create).supported:
110
+ continue
111
+
112
+ def vlans(b: Backend = backend) -> set[int]:
113
+ return {v.vlan_id for v in switch.get_vlans(backend=b)}
114
+
115
+ before = vlans()
116
+ assert TEST_VLAN not in before, f"{key}/{backend.name}: seed uses the id"
117
+
118
+ switch.create_vlan(TEST_VLAN, "outcome", backend=backend)
119
+ assert TEST_VLAN in vlans(), (
120
+ f"{key}/{backend.name}: capabilities says create_vlan is "
121
+ "supported, and the call returned, but the VLAN is not there"
122
+ )
123
+
124
+ if support(model, backend, delete).supported:
125
+ # force=True: NSDP force-gates delete_vlan because it drops every
126
+ # member port. The gate is a deliberate safety feature, not the
127
+ # thing under test here.
128
+ switch.delete_vlan(TEST_VLAN, force=True, backend=backend)
129
+ assert TEST_VLAN not in vlans(), (
130
+ f"{key}/{backend.name}: delete_vlan returned but the VLAN "
131
+ "is still present"
132
+ )
133
+ assert vlans() == before, (
134
+ f"{key}/{backend.name}: VLAN set not restored after the "
135
+ "create/delete round trip"
136
+ )