python-netgear-switch-library 0.0.post353__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.
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/PKG-INFO +1 -1
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-08-02-snmp-surface-findings.md +34 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/_version.py +2 -2
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/capabilities.py +17 -1
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/http_write.py +23 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/endpoints.py +21 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web.py +32 -0
- python_netgear_switch_library-0.0.post355/tests/virtual/test_write_outcomes.py +136 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.github/workflows/ci.yml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.github/workflows/deb.yml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.github/workflows/publish-pypi.yml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.gitignore +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.readthedocs.yaml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/CLAUDE.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/LICENSE +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/README.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/RELEASING.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/changelog +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/control +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/copyright +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/rules +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/source/format +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/Makefile +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_ext/filelinks.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_ext/support_tables.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs105pe.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs110emx.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs305ep.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gs728tpp.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gsm7228ps.jpg +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/gsm7252ps.jpg +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/m4300-16x.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/models/m4300-24x.png +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/_static/ngsw.css +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/cli.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/core.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/facades.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/index.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/mcp.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/operations.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/protocols.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/transport.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/api/virtual.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/cli.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/conf.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/development.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/fake/index.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/fake/internals.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/fake/serving.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/fake/testing.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/concepts.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/configuration.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/installation.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/principles.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/quickstart.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/reading.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/guide/writing.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/index.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/mcp.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gs105pe.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gs110emx.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gs305ep.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gs728tpp.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gsm7228ps.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/gsm7252ps.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/index.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/m4300-16x.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/m4300-24x.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/models/support.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/cli.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/cheetah-form.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/cheetah-v1.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/gambit.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/merge-hash-cgi.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http/xml-api.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/http.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/index.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/nsdp.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/protocols/snmp.rst +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-15-foundation.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-17-slice-02-snmp-read-core.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-17-slice-03-read-apis.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-04-snmp-write.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-05-nsdp.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-06-http.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-07-cli.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-18-slice-08-packaging-ci.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/plans/2026-07-23-gsm7252ps-http-xe-dialect.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-07-15-netgear-control-library-design.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/superpowers/specs/2026-07-23-gsm7252ps-http-xe-dialect-design.md +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/packaging/apt-index.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/packaging/ci-build.sh +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/packaging/deb-version.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/pyproject.toml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/_dispatch.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/aio_api.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/capture.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/context.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/format.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/main.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/resolve.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli/safety.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/cli_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/config.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/errors.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/http_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/mcp/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/mcp/server.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/models.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/nsdp_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/nsdp_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/commands.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/cli/parse.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/crypt.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/forms.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/parse.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/session.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/http/types.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/auth.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/client.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/parsers.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/protocol.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/types.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/nsdp/write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/client.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/oids.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/parse.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/protocols/snmp/write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/py.typed +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/registry.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/snmp_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/snmp_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/sync_api.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/nsdp_udp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/aio/snmp_pysnmp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/console.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/session.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/ssh.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/cli/telnet.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/http/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/http/client.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/nsdp_udp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/transport/sync/snmp_netsnmp_cli.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/cli_fastpath.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/__init__.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/cli.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/http.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/mibview.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/nsdp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/faces/snmp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/seed.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/server.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/state.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_fastpath_vlan.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_fastpath_xui.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs105pe.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs110emx.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs110emx_templates.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gs728tpp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gsm7228ps.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_gsm7252ps.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/src/netgear_switch/virtual/web_m4300.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/capture_parity.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_capture.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_cli_integration.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_control_poe_port.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_control_vlan_ip.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_format.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_main_framework.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_new_commands.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_op_coverage.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_prompt_wiring.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_read_commands.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_resolve.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_safety.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/cli/test_serve.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/conftest.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/equivalence.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/gsm7228ps.json +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/gsm7252ps.json +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/m4300-16x.json +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/captures/m4300-24x.json +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_environment.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_lldp.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_mac_table.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_network.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_poe.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_show_version.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan_brief.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7228ps_vlan_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_environment.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_interface_ethernet_1_0_1.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_lldp_remote_device_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_mac_addr_table.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_network.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_poe_port_info_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_version.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_90.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_brief.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/gsm7252ps_show_vlan_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_environment.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_interface_ethernet_1_0_1.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_ip_management.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_lldp_remote_device_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_mac_addr_table.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_poe_port_info_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_version.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_1.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_4.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_5.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_16x_show_vlan_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_environment.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_interface_ethernet_1_0_1.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_ip_management.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_lldp_remote_device_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_mac_addr_table.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_version.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_5.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_90.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/cli/m4300_24x_show_vlan_port_all.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_membership.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_portstats.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_pvid.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_status.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_switch_info.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs105pe_vlancfg.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_cf8021q.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_interface_stats.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_login.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_port_settings.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_pvid.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_redirect.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_sysinfo.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs110emx_vlanmembership.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_dashboard.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_login.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_membership.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_poestatus.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_portstats.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_pvid.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs305ep_vlancfg.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_device_info_and_sensors.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_lldp.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_macs.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_mgmt_ip.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_poe.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_ports.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_pvids_membership.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_vlan_membership_per_vlan.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gs728tpp_vlans.xml +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_basicAddressTable.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_ipConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_lldpRemoteInventory.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_poeInterfaceConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portPvidConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portStatistics.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_portsConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_sysInfo.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_vlanPortCfg_vlan5.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7228ps_vlanStatus.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_basicAddressTable.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_http_configuration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_ipConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_lldpRemoteInventory.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_login.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_poeInterfaceConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portPvidConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portStatistics.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_portsConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_sysInfo.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_syslog_configuration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_user_configuration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanPortCfg_vlan1.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanPortCfg_vlan141.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlanStatus.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/gsm7252ps_vlan_status_live.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_lldpRemoteInventory.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_mgmtVlanIpv4Configuration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_poeInterfaceConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_portsConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_16x_vlanportcfg_vlan4.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_24x_poeInterfaceConfiguration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_addresstable.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_lldpRemoteInventory.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_mgmtVlanIpv4Configuration.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_ports.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_portstats.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_pvid.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_sysinfo.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_vlanportcfg_vlan1.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/http/m4300_vlanstatus.html +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_fans.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_ifhighspeed.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_ifoperstatus.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/fixtures/snmp/gsm7252ps_psu.txt +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/http_specs.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/cli/test_cli_parse.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_crypt.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_endpoints.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_forms.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/http/test_parse.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_auth.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_parsers.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_protocol.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/nsdp/test_write_frame.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_client.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_oids.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_lldp_macs.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_mgmt_ip.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_poe_sensors.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_ports.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_system_info.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_parse_vlans.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/protocols/snmp/test_write_encode.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_aio_api.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_capabilities.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_cli_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_cli_vlan_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_cli_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_config.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_cross_backend_equivalence.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_deb_version.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_dispatch.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_docs_coverage.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_errors.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_facade_equivalence.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_dispatch.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_equivalence.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_vlan_membership.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_http_xui_writes.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_import.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_mcp_server.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_models.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_models_snmp_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_integration.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_nsdp_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_public_api.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_quality_gates.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_registry.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_integration.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_read.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_snmp_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_sync_api.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_version.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/test_write_equivalence.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/_fastpath_fake.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_console.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_scp_cert.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_cli_telnet.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_http_client.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_nsdp_udp_sync.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_set_transport.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_snmp_netsnmp_cli.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_snmp_pysnmp.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/transport/test_value_parity.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_cli_cross_backend.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_cli_gsm7228ps_parity.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_gsm7228ps_seed.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_hostname.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_m4300_seeds.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_mibview.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_mutable_state.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_nsdp_state.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_serve_daemon.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_snmp_write_face.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_state_seed.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_switchport_vlan_write.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_syslog_fidelity.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_http_face.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_nsdp_face.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_virtual_snmp_face.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/tests/virtual/test_web_projection.py +0 -0
- {python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/uv.lock +0 -0
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-netgear-switch-library
|
|
3
|
-
Version: 0.0.
|
|
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
|
|
@@ -305,3 +305,37 @@ inference, and each needs its own live check before it is recorded as fact.
|
|
|
305
305
|
2. `create_vlan` needs an XUI implementation for the FASTPATH dialects.
|
|
306
306
|
3. Until it exists, the oracle must report HTTP `create_vlan` unsupported on
|
|
307
307
|
those models rather than claiming it works.
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
### Follow-up: no FASTPATH page carries the token, and the tests cannot see it
|
|
311
|
+
|
|
312
|
+
Probing every write page on the live gsm7252ps:
|
|
313
|
+
|
|
314
|
+
| Page | `hash` present |
|
|
315
|
+
|---|---|
|
|
316
|
+
| `/vlanStatus.html` | no |
|
|
317
|
+
| `/poeInterfaceConfiguration.html` | no |
|
|
318
|
+
| `/portPvidConfiguration.html` | no |
|
|
319
|
+
| `/switching/dot1q/vlan_port_cfg.html` | no |
|
|
320
|
+
| `/portsConfiguration.html` | no |
|
|
321
|
+
|
|
322
|
+
So the token is absent from the **whole XE FASTPATH dialect**, not just the VLAN
|
|
323
|
+
page, and every `_csrf`-scraping write is affected on those models — not only
|
|
324
|
+
`create_vlan`.
|
|
325
|
+
|
|
326
|
+
The mock is now faithful: `virtual/web.py` emits the token only for the Plus
|
|
327
|
+
dialects that really have it, and driving HTTP `create_vlan` against the fake
|
|
328
|
+
reproduces the live failure on gsm7252ps while still succeeding on gs305ep.
|
|
329
|
+
|
|
330
|
+
**`tests/test_capabilities.py` stayed green through that change, and it was
|
|
331
|
+
right to.** Its `_refused()` counts only `UnsupportedCapabilityError` and
|
|
332
|
+
`NotImplementedError` as a refusal; anything else "means the backend ACCEPTED
|
|
333
|
+
the operation and tried, which is exactly what Support.SUPPORTED claims". An
|
|
334
|
+
`HttpUnexpectedPageError` is therefore success by that definition. The test
|
|
335
|
+
verifies **dispatch**, not **outcome** — by design — so it can never catch a
|
|
336
|
+
write that reaches the right backend and then fails there.
|
|
337
|
+
|
|
338
|
+
That is a gap in the safety net, not a bug in the test. Catching this class
|
|
339
|
+
needs a different check: drive each write against the mock and assert the state
|
|
340
|
+
actually changed. Recorded here so the next person does not assume a green
|
|
341
|
+
capability suite means the writes work.
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 0, '
|
|
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.
|
|
@@ -39,8 +39,40 @@ def _hash_input() -> str:
|
|
|
39
39
|
return f'<input type="hidden" name="hash" value="{_HASH}">'
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def _has_csrf_hash(spec: HttpModelSpec) -> bool:
|
|
43
|
+
"""Whether this model's real web UI carries an ``<input name="hash">``.
|
|
44
|
+
|
|
45
|
+
ONLY the Plus ``.cgi`` dialects do. MEASURED 2026-08-02: a live capture of
|
|
46
|
+
the FASTPATH ``vlanStatus.html`` on gsm7252ps (10.1.5.22, committed as
|
|
47
|
+
``tests/fixtures/http/gsm7252ps_vlan_status_live.html``) carries no ``hash``
|
|
48
|
+
input anywhere -- only ``applet_port``, ``applet_unit``, ``dbgopt`` and the
|
|
49
|
+
XUI cell hiddens.
|
|
50
|
+
|
|
51
|
+
This mock used to emit the token on EVERY page regardless of dialect, and
|
|
52
|
+
that is how ``HttpWriter.create_vlan`` -- which scrapes the token and is
|
|
53
|
+
written for the Plus form -- passed here while failing on real FASTPATH
|
|
54
|
+
hardware with "no CSRF 'hash' token on page before write". The mock and the
|
|
55
|
+
writer agreed with each other while both disagreed with the device, which
|
|
56
|
+
is exactly the failure principle 5 exists to prevent. Emitting it only
|
|
57
|
+
where the hardware does makes the mock refuse the same write the switch
|
|
58
|
+
refuses.
|
|
59
|
+
"""
|
|
60
|
+
from ..protocols.http.endpoints import dialect_has_csrf_hash
|
|
61
|
+
|
|
62
|
+
return dialect_has_csrf_hash(spec.html_dialect)
|
|
63
|
+
|
|
64
|
+
|
|
42
65
|
def render_page(
|
|
43
66
|
state: VirtualSwitchState, spec: HttpModelSpec, path: str, form: dict[str, str]
|
|
67
|
+
) -> str:
|
|
68
|
+
html = _render_page_body(state, spec, path, form)
|
|
69
|
+
if not _has_csrf_hash(spec):
|
|
70
|
+
html = html.replace(_hash_input(), "")
|
|
71
|
+
return html
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _render_page_body(
|
|
75
|
+
state: VirtualSwitchState, spec: HttpModelSpec, path: str, form: dict[str, str]
|
|
44
76
|
) -> str:
|
|
45
77
|
if path == spec.dashboard_path:
|
|
46
78
|
return _render_dashboard(state)
|
|
@@ -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
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/CLAUDE.md
RENAMED
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/LICENSE
RENAMED
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/README.md
RENAMED
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/RELEASING.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/debian/rules
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/cli.rst
RENAMED
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_netgear_switch_library-0.0.post353 → python_netgear_switch_library-0.0.post355}/docs/mcp.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|