netbox-super-cli 1.6.2__tar.gz → 1.7.0__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.
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/docs.yml +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.gitignore +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.pre-commit-config.yaml +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/AGENTS.md +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/CHANGELOG.md +50 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/CLAUDE.md +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/PKG-INFO +3 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/http-client.md +5 -5
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/overview.md +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/release-process.md +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/justfile +4 -4
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/_version.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/auth/verify.py +17 -7
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cache/store.py +11 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/cache_commands.py +3 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/runtime.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/bulk.py +3 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/cache_probe.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/callbacks.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/audit.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/client.py +19 -14
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/retry.py +5 -4
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/loader.py +4 -4
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/source.py +28 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/pyproject.toml +8 -5
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/scripts/gen_docs.py +1 -1
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/auth/test_verify.py +18 -32
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/benchmarks/test_startup.py +7 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/test_store.py +14 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_aliases_commands.py +91 -129
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_app_smoke.py +23 -27
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_commands_dump.py +10 -7
- netbox_super_cli-1.7.0/tests/cli/test_lazy_httpx2_import.py +52 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_login_commands.py +31 -43
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_ndjson_input.py +14 -15
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_profiles_commands.py +6 -10
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_respx_integration.py +39 -37
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_writes_respx.py +108 -120
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/conftest.py +5 -5
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_audit_redaction.py +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_bulk_create_with_loop_fallback.py +3 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_full_cycle.py +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_login.py +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_ndjson.py +3 -3
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_preflight_blocks_apply.py +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_validation_error_envelope_from_netbox.py +2 -2
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_client.py +135 -160
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_client_redaction_threading.py +10 -10
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_retry.py +7 -7
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_loader.py +14 -24
- netbox_super_cli-1.7.0/tests/schema/test_source.py +224 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_source_ttl.py +46 -50
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/uv.lock +141 -272
- netbox_super_cli-1.6.2/tests/schema/test_source.py +0 -159
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/docs.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/dependabot.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/pull_request_template.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/bench.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/claude.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/e2e.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/lint.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/release.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/skill-convention-review.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/test.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.python-version +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/LICENSE +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/README.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/caching.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/command-generation.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/schema-loading.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/detail.svg +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/filter.svg +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/list.svg +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/picker.svg +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/search.svg +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/adding-bundled-schemas.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/branching.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/development.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/concepts.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/first-run.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/install.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/audit-log.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/ci-and-automation.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/interactive-tui.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/managing-profiles.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/output-formats.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/using-with-ai-agents.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/working-with-plugins.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/writes-and-safety.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/index.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/cli.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/config.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/exit-codes.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/schemas.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/mkdocs.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/__main__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/aliases/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/aliases/resolver.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/auth/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/builder/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/builder/build.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cache/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/aliases_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/app.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/commands_dump.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/config_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/globals.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/handlers.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/init_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/login_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/profiles_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/registration.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/skill_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/tui_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/apply.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/coercion.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/confirmation.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/input.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/preflight.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/providers.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/loader.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/models.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/saved_searches.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/settings.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/writer.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/model/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/model/command_model.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/_console.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/colors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/csv_.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/explain.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/flatten.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/headers.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/json_.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/jsonl.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/render.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/table.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/yaml_.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/custom_fields.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/objecttypes.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/params.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/store.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/tags.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/hashing.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/models.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/manifest.yaml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/netbox-4.5.10.json.gz +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/netbox-4.6.0.json.gz +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/skill/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/_bindings.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/app.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/bulk.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/catalog.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/columns.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/filters.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/fk.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/forms.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/keymap.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/nav.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/relations.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/bulk_edit_form.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/columns.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/detail.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/edit_form.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/filter.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/global_search.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/list.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/picker.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/record_picker.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/saved_search_picker.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/search.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/selection.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/styles.tcss +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/view.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/_modal.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/bulk_diff.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/bulk_summary.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/confirm.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/diff.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/help.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/nav_tree.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/scripts/sync_agents_md.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/skills/netbox-super-cli/SKILL.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/aliases/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/aliases/test_resolver.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/audit/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/audit/test_redaction_full.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/auth/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/benchmarks/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_build.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_default_columns.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_fk_metadata.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_redaction_marking.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_request_body_shape.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/test_prune.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_cache_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_completion_protocol_smoke.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_completion_smoke.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_config_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_dynamic_completion.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_explain.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_globals_color.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_handlers.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_init_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_lazy_textual_import.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_meta_subcommands_under_broken_config.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_registration.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_runtime.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_runtime_color.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_skill_commands.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_stdin_sniffer.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_tui_command.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_apply.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_bulk.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_confirmation.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_handler_helpers.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_handlers_audit.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_input.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_preflight.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_loader.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_models.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_saved_search_fallback.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_saved_searches.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_settings_private_dir.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_atomicity.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_dotted_paths.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_roundtrip.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/conftest.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/README.md +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/docker-compose.yml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_auth_error_envelope.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/wait_for_netbox.sh +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/profiles/single_profile.yaml +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/auth_401.json +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/circuits_providers_list.json +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_get.json +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_list_p1.json +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_list_p2.json +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit_permissions.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit_redaction.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/test_command_model.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/test_request_body_shape.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_colors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_console.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_csv.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors_aliases.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors_color.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_explain.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_explain_color.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_flatten.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_input_error_envelope.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_json.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_jsonl.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_render_dispatch.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_table.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_table_color.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_yaml.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_custom_fields.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_objecttypes.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_params.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_store.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_tags.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_hashing.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_models.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/test_gen_docs.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/test_sync_agents_md.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/skill/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/skill/test_bundle.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/test_packaging.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/__init__.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_app.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bindings.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_apply.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_diff.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_diff_modal.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_edit_form.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_import_isolation.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_summary.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_catalog.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_column_chooser.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_columns.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_detail_screen.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_diff.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_diff_modal.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_edit_form.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_errors.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_filter_screen.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_filters.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_fk_resolve.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_forms.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_global_search.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_help_overlay.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_keymap.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_list_screen.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_picker.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_record_picker.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_relations.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_saved_filter_integration.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_search.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_selection.py +0 -0
- {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_view.py +0 -0
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
- name: Sync deps (runtime + docs)
|
|
37
37
|
run: uv sync --frozen --group docs
|
|
38
38
|
- name: Build site (strict)
|
|
39
|
-
run: uv run
|
|
39
|
+
run: uv run zensical build --strict
|
|
40
40
|
- name: Upload Pages artifact
|
|
41
41
|
if: github.ref_type == 'tag'
|
|
42
42
|
uses: actions/upload-pages-artifact@v5
|
|
@@ -15,7 +15,7 @@ This is the contributor guide for AI agents (and humans!) modifying this repo. T
|
|
|
15
15
|
- `nsc/model/` — the normalized command tree (data only, framework-free). The "brain".
|
|
16
16
|
- `nsc/builder/` — converts a parsed schema into a `CommandModel`.
|
|
17
17
|
- `nsc/cli/` — the Typer app; consumes a `CommandModel`.
|
|
18
|
-
- `nsc/http/` — thin
|
|
18
|
+
- `nsc/http/` — thin httpx2 wrapper: auth, retries, audit log.
|
|
19
19
|
- `nsc/output/` — formatters (table/json/jsonl/yaml/csv) + error envelope.
|
|
20
20
|
- `nsc/config/` — config loader + Pydantic models + ruamel.yaml round-trip writer.
|
|
21
21
|
- `nsc/cache/` — disk cache for generated command-models.
|
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to netbox-super-cli are tracked here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loosely. From v1.0.0 onward, releases follow [Semantic Versioning](https://semver.org/) and the version in `pyproject.toml` matches the git tag. Pre-1.0 milestones (Phase 1-5) were pinned by tag while `pyproject.toml` stayed at `0.0.1`.
|
|
4
4
|
|
|
5
|
+
## v1.7.0 — 2026-09-10
|
|
6
|
+
|
|
7
|
+
Minor release. Migrates the HTTP stack from `httpx` to Pydantic's maintained
|
|
8
|
+
`httpx2` fork, and keeps `httpx2` off the `nsc --help` cold-start path. No
|
|
9
|
+
user-facing CLI or behavior changes.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Migrated the HTTP stack from `httpx` to `httpx2`** ([#146]). `httpx2` is
|
|
14
|
+
Pydantic's stewardship continuation of httpx (same author, API-identical) —
|
|
15
|
+
httpx itself has seen limited activity, and httpx2 is the reliably-maintained
|
|
16
|
+
path forward with timely security updates. The dependency floor is now
|
|
17
|
+
`httpx2>=2.5`; the source layer is a mechanical rename (`import httpx2`,
|
|
18
|
+
`httpx2.Client`/`httpx2.Response`/…). Test mocking moves from global `respx`
|
|
19
|
+
to the `pytest-httpx2` plugin's `httpx2_mock` fixture (a `respx.Router`, by
|
|
20
|
+
respx's own author), which targets httpx2's `httpcore2` transport. No
|
|
21
|
+
user-facing behavior or CLI surface changes.
|
|
22
|
+
- **httpx2 is now kept off the `nsc --help` / cold-start path** ([#13]). httpx2 is
|
|
23
|
+
the single heaviest import in the tree (~65ms cold), and nothing on the startup
|
|
24
|
+
path needs an HTTP client. It was being pulled in eagerly through the
|
|
25
|
+
runtime → client → retry chain (plus `cache`, `schema`, and `login`
|
|
26
|
+
command modules). All of these now import httpx2 lazily, at first request. Cold
|
|
27
|
+
startup drops by roughly httpx2's import cost, bringing the median back under the
|
|
28
|
+
300ms project target. A new guard test (`test_lazy_httpx2_import.py`) asserts
|
|
29
|
+
httpx2 never loads on `nsc --help`, and the startup benchmark's threshold is
|
|
30
|
+
re-baselined to 300ms so an over-threshold skip once again signals a real
|
|
31
|
+
regression instead of firing on every run.
|
|
32
|
+
|
|
33
|
+
[#13]: https://github.com/thomaschristory/netbox-super-cli/issues/13
|
|
34
|
+
[#146]: https://github.com/thomaschristory/netbox-super-cli/issues/146
|
|
35
|
+
|
|
36
|
+
## v1.6.3 — 2026-07-03
|
|
37
|
+
|
|
38
|
+
Patch release. Stops an offline instance from rebuilding the command model from
|
|
39
|
+
the bundled schema on every invocation after a format-version bump.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **Offline invocations rebuilt from the bundled schema every time when the only
|
|
44
|
+
cached model was format-stale** ([#140]). After the v1.6.2 format-version bump
|
|
45
|
+
(#139), a cache entry built by an older `nsc` is rejected so it rebuilds with
|
|
46
|
+
the new metadata. Online this self-heals on the first call, but while NetBox
|
|
47
|
+
was unreachable nothing re-saved: each invocation re-parsed the bundled schema
|
|
48
|
+
(~180ms) and tab-completion went briefly blank for the profile. The offline
|
|
49
|
+
bundled fallback is now persisted under the profile — without a fetch
|
|
50
|
+
timestamp, so the TTL fast-path still refetches once NetBox is reachable — so
|
|
51
|
+
the next invocation hits the cache and completion recovers immediately.
|
|
52
|
+
|
|
53
|
+
[#140]: https://github.com/thomaschristory/netbox-super-cli/issues/140
|
|
54
|
+
|
|
5
55
|
## v1.6.2 — 2026-06-30
|
|
6
56
|
|
|
7
57
|
Patch release. Fixes TUI foreign-key pickers resolving to the wrong resource.
|
|
@@ -8,7 +8,7 @@ This is the contributor guide for AI agents (and humans!) modifying this repo. T
|
|
|
8
8
|
- `nsc/model/` — the normalized command tree (data only, framework-free). The "brain".
|
|
9
9
|
- `nsc/builder/` — converts a parsed schema into a `CommandModel`.
|
|
10
10
|
- `nsc/cli/` — the Typer app; consumes a `CommandModel`.
|
|
11
|
-
- `nsc/http/` — thin
|
|
11
|
+
- `nsc/http/` — thin httpx2 wrapper: auth, retries, audit log.
|
|
12
12
|
- `nsc/output/` — formatters (table/json/jsonl/yaml/csv) + error envelope.
|
|
13
13
|
- `nsc/config/` — config loader + Pydantic models + ruamel.yaml round-trip writer.
|
|
14
14
|
- `nsc/cache/` — disk cache for generated command-models.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: netbox-super-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Dynamic NetBox CLI generated from the live OpenAPI schema.
|
|
5
5
|
Project-URL: Homepage, https://github.com/thomaschristory/netbox-super-cli
|
|
6
6
|
Project-URL: Documentation, https://thomaschristory.github.io/netbox-super-cli/
|
|
@@ -23,7 +23,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
23
23
|
Classifier: Topic :: System :: Networking
|
|
24
24
|
Requires-Python: >=3.12
|
|
25
25
|
Requires-Dist: click>=8.1
|
|
26
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: httpx2>=2.5
|
|
27
27
|
Requires-Dist: platformdirs>=4.2
|
|
28
28
|
Requires-Dist: pydantic>=2.7
|
|
29
29
|
Requires-Dist: rich>=13.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# HTTP client
|
|
2
2
|
|
|
3
|
-
`nsc/http/` is a thin wrapper around `
|
|
3
|
+
`nsc/http/` is a thin wrapper around `httpx2.Client`.
|
|
4
4
|
|
|
5
5
|
## What it adds
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
Bulk write commands accept `--workers N` (default 1, max 32) to keep up to N
|
|
29
29
|
requests in flight. Concurrency is **thread-based**: a `ThreadPoolExecutor`
|
|
30
|
-
fans the per-record loop out over the single sync `
|
|
30
|
+
fans the per-record loop out over the single sync `httpx2.Client` — there is no
|
|
31
31
|
async path. Per-record `--on-error` semantics are preserved regardless of
|
|
32
32
|
worker count. Audit appends are serialized by a module-level lock
|
|
33
33
|
(`_APPEND_LOCK` in `nsc/http/audit.py`) wrapped around the whole
|
|
@@ -40,9 +40,9 @@ each record is one well-formed JSON line.
|
|
|
40
40
|
|
|
41
41
|
A schema fetch may run before the first command request (only when the
|
|
42
42
|
TTL fast-path misses — see [Caching](caching.md)); it uses its own
|
|
43
|
-
short-lived `
|
|
43
|
+
short-lived `httpx2.Client` in `nsc/schema/`, not this `NetBoxClient`.
|
|
44
44
|
Command requests then go through a single `NetBoxClient` whose
|
|
45
|
-
`
|
|
45
|
+
`httpx2.Client` carries the auth header for the life of the process.
|
|
46
46
|
Token rotation via `nsc login --rotate` does NOT invalidate the cached
|
|
47
47
|
model (the schema hash is what keys the cache; the token never affected
|
|
48
48
|
it).
|
|
@@ -91,7 +91,7 @@ contract.
|
|
|
91
91
|
|
|
92
92
|
## What it deliberately does not do
|
|
93
93
|
|
|
94
|
-
- Async — sync only in v1, kept feasible by
|
|
94
|
+
- Async — sync only in v1, kept feasible by httpx2 if a future async path lands.
|
|
95
95
|
- Connection pooling across profiles — each profile gets its own `Client`.
|
|
96
96
|
- Caching responses — caching the command-model is enough; caching response
|
|
97
97
|
payloads would surprise users.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Architecture overview
|
|
2
2
|
|
|
3
3
|
`nsc` is layered around a framework-free "brain". The brain — schema parsing
|
|
4
|
-
and the normalized command-model — knows nothing about Typer, Rich, or
|
|
4
|
+
and the normalized command-model — knows nothing about Typer, Rich, or httpx2.
|
|
5
5
|
The CLI layer consumes the brain; a future TUI could too without changing the
|
|
6
6
|
brain.
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@ nsc/
|
|
|
11
11
|
├── model/ # Normalized command-model (data only, framework-free)
|
|
12
12
|
├── builder/ # Schema → CommandModel
|
|
13
13
|
├── cli/ # Typer application; walks the model, registers commands
|
|
14
|
-
├── http/ # Thin
|
|
14
|
+
├── http/ # Thin httpx2 wrapper: auth, retries, audit
|
|
15
15
|
├── output/ # Formatters (table/json/jsonl/yaml/csv) + error envelope
|
|
16
16
|
├── config/ # Pydantic config models + ruamel.yaml writer
|
|
17
17
|
├── cache/ # On-disk cache for generated CommandModels
|
|
@@ -11,7 +11,7 @@ on a `chore/release-X.Y.Z` feature branch, gets PR-merged into `main`,
|
|
|
11
11
|
and only then is `vX.Y.Z` tagged on the resulting merge commit.
|
|
12
12
|
|
|
13
13
|
The same `v*` tag push also publishes the documentation site.
|
|
14
|
-
`docs.yml` runs build-only (`
|
|
14
|
+
`docs.yml` runs build-only (`zensical build --strict`) on docs-touching
|
|
15
15
|
pull requests, but the GitHub Pages **deploy** job only runs on a `v*`
|
|
16
16
|
tag push — the live docs site updates on release, not on merge to
|
|
17
17
|
`main`.
|
|
@@ -54,10 +54,10 @@ e2e:
|
|
|
54
54
|
docker compose -f tests/e2e/docker-compose.yml down -v; \
|
|
55
55
|
exit $rc
|
|
56
56
|
|
|
57
|
-
# Serve the
|
|
57
|
+
# Serve the docs site locally at http://127.0.0.1:8000 (preview only)
|
|
58
58
|
docs:
|
|
59
|
-
uv run
|
|
59
|
+
uv run zensical serve
|
|
60
60
|
|
|
61
|
-
# Build the
|
|
61
|
+
# Build the docs site (strict mode — fails on broken links, missing anchors, etc.)
|
|
62
62
|
docs-build:
|
|
63
|
-
uv run
|
|
63
|
+
uv run zensical build --strict
|
|
@@ -17,11 +17,15 @@ out of `audit.jsonl`. There is no retry loop: a single failed probe fails fast.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
21
22
|
from pydantic import BaseModel, ConfigDict
|
|
22
23
|
|
|
23
24
|
from nsc.config.models import Profile
|
|
24
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
import httpx2
|
|
28
|
+
|
|
25
29
|
_DEFAULT_TIMEOUT = 10.0
|
|
26
30
|
|
|
27
31
|
|
|
@@ -65,6 +69,8 @@ def verify(profile: Profile, *, timeout: float = _DEFAULT_TIMEOUT) -> VerifyResu
|
|
|
65
69
|
|
|
66
70
|
Raises `VerifyError` on any failure. Returns a `VerifyResult` on success.
|
|
67
71
|
"""
|
|
72
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
73
|
+
|
|
68
74
|
if not profile.token:
|
|
69
75
|
raise VerifyError(message="profile has no token; cannot verify")
|
|
70
76
|
base = str(profile.url).rstrip("/")
|
|
@@ -72,7 +78,7 @@ def verify(profile: Profile, *, timeout: float = _DEFAULT_TIMEOUT) -> VerifyResu
|
|
|
72
78
|
"Authorization": f"Token {profile.token}",
|
|
73
79
|
"Accept": "application/json",
|
|
74
80
|
}
|
|
75
|
-
with
|
|
81
|
+
with httpx2.Client(
|
|
76
82
|
base_url=base,
|
|
77
83
|
headers=headers,
|
|
78
84
|
verify=profile.verify_ssl,
|
|
@@ -83,10 +89,12 @@ def verify(profile: Profile, *, timeout: float = _DEFAULT_TIMEOUT) -> VerifyResu
|
|
|
83
89
|
return VerifyResult(username=username, netbox_version=version)
|
|
84
90
|
|
|
85
91
|
|
|
86
|
-
def _probe_status(client:
|
|
92
|
+
def _probe_status(client: httpx2.Client) -> str:
|
|
93
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
94
|
+
|
|
87
95
|
try:
|
|
88
96
|
response = client.get("/api/status/")
|
|
89
|
-
except (
|
|
97
|
+
except (httpx2.RequestError, OSError) as exc:
|
|
90
98
|
raise VerifyError(message=f"could not reach NetBox: {exc}") from exc
|
|
91
99
|
if not response.is_success:
|
|
92
100
|
raise VerifyError(
|
|
@@ -98,7 +106,7 @@ def _probe_status(client: httpx.Client) -> str:
|
|
|
98
106
|
return str(version) if version else "unknown"
|
|
99
107
|
|
|
100
108
|
|
|
101
|
-
def _probe_users_me(client:
|
|
109
|
+
def _probe_users_me(client: httpx2.Client) -> str:
|
|
102
110
|
"""Verify the token via `GET /api/users/tokens/?limit=1`.
|
|
103
111
|
|
|
104
112
|
NetBox does not expose a top-level "current user" endpoint (`/api/users/me/`
|
|
@@ -109,9 +117,11 @@ def _probe_users_me(client: httpx.Client) -> str:
|
|
|
109
117
|
calling user's identity in the common case. If the user has no visible
|
|
110
118
|
tokens (an unusual admin state), we surface "(unknown)" rather than failing.
|
|
111
119
|
"""
|
|
120
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
121
|
+
|
|
112
122
|
try:
|
|
113
123
|
response = client.get("/api/users/tokens/", params={"limit": 1})
|
|
114
|
-
except (
|
|
124
|
+
except (httpx2.RequestError, OSError) as exc:
|
|
115
125
|
raise VerifyError(message=f"token probe failed: {exc}") from exc
|
|
116
126
|
if not response.is_success:
|
|
117
127
|
raise VerifyError(
|
|
@@ -136,7 +146,7 @@ def _probe_users_me(client: httpx.Client) -> str:
|
|
|
136
146
|
return "(unknown)"
|
|
137
147
|
|
|
138
148
|
|
|
139
|
-
def _safe_json(response:
|
|
149
|
+
def _safe_json(response: httpx2.Response) -> object:
|
|
140
150
|
try:
|
|
141
151
|
return response.json()
|
|
142
152
|
except ValueError:
|
|
@@ -82,13 +82,21 @@ class CacheStore:
|
|
|
82
82
|
return None
|
|
83
83
|
return model
|
|
84
84
|
|
|
85
|
-
def save(self, profile: str, model: CommandModel) -> Path:
|
|
85
|
+
def save(self, profile: str, model: CommandModel, *, record_fetch: bool = True) -> Path:
|
|
86
|
+
"""Persist `model` under the profile. `record_fetch=True` also stamps
|
|
87
|
+
the `fetched_at` sidecar (a live fetch just produced this model, so
|
|
88
|
+
the TTL fast-path may trust it). Pass `record_fetch=False` for models
|
|
89
|
+
that were *not* fetched from this NetBox — e.g. the offline bundled
|
|
90
|
+
fallback (issue #140): we want the persisted copy so future offline
|
|
91
|
+
invocations skip the rebuild, but the TTL fast-path must keep
|
|
92
|
+
distrusting it so a real fetch is attempted once NetBox is reachable."""
|
|
86
93
|
self._validate_profile(profile)
|
|
87
94
|
target = self._path_for(profile, model.schema_hash)
|
|
88
95
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
89
96
|
_atomic_write(target, model.model_dump_json(indent=2))
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
if record_fetch:
|
|
98
|
+
meta = self._meta_path_for(profile, model.schema_hash)
|
|
99
|
+
_atomic_write(meta, json.dumps({"fetched_at": time.time()}))
|
|
92
100
|
return target
|
|
93
101
|
|
|
94
102
|
def touch_fetched_at(self, profile: str, schema_hash: str) -> None:
|
|
@@ -7,7 +7,6 @@ from collections.abc import Callable
|
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
from typing import Annotated
|
|
9
9
|
|
|
10
|
-
import httpx
|
|
11
10
|
import typer
|
|
12
11
|
|
|
13
12
|
from nsc.cache.store import (
|
|
@@ -45,12 +44,14 @@ def _load_config_or_empty() -> Config:
|
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def _fetch_live_hash(profile: Profile, *, default_timeout: float) -> str:
|
|
47
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
48
|
+
|
|
48
49
|
url = str(profile.url).rstrip("/") + "/api/schema/?format=json"
|
|
49
50
|
headers = {"Accept": "application/json"}
|
|
50
51
|
if profile.token:
|
|
51
52
|
headers["Authorization"] = f"Token {profile.token}"
|
|
52
53
|
timeout = profile.timeout if profile.timeout is not None else default_timeout
|
|
53
|
-
with
|
|
54
|
+
with httpx2.Client(verify=profile.verify_ssl, timeout=timeout, headers=headers) as c:
|
|
54
55
|
resp = c.get(url)
|
|
55
56
|
resp.raise_for_status()
|
|
56
57
|
return canonical_sha256(resp.content)
|
|
@@ -206,7 +206,7 @@ def resolve_object_colors(mode: ObjectColorMode, *, color: bool) -> bool:
|
|
|
206
206
|
class RuntimeContext(BaseModel):
|
|
207
207
|
"""Per-invocation runtime state.
|
|
208
208
|
|
|
209
|
-
Not frozen because `client` (a NetBoxClient wrapping
|
|
209
|
+
Not frozen because `client` (a NetBoxClient wrapping httpx2.Client) is mutable.
|
|
210
210
|
"""
|
|
211
211
|
|
|
212
212
|
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Bulk routing brain (Phase 3c).
|
|
2
2
|
|
|
3
|
-
Pure logic. No I/O, no Typer, no
|
|
3
|
+
Pure logic. No I/O, no Typer, no httpx2. Three responsibilities:
|
|
4
4
|
|
|
5
5
|
1. `detect_bulk_capability(operation)` — classify a parsed Operation as
|
|
6
6
|
bulk-capable, single-only, or ambiguous (spec §4.5).
|
|
@@ -195,7 +195,7 @@ def run_loop(
|
|
|
195
195
|
With `workers == 1` the loop is sequential and deterministic: requests run
|
|
196
196
|
in input order and audit fires once per attempted request in that order.
|
|
197
197
|
With `workers > 1` up to `workers` requests are in flight concurrently via
|
|
198
|
-
a thread pool (the NetBox client is sync
|
|
198
|
+
a thread pool (the NetBox client is sync httpx2, so threads — not asyncio —
|
|
199
199
|
are the right mechanism). Each record's send/retry/audit is independent.
|
|
200
200
|
|
|
201
201
|
On `on_error="stop"` the loop stops submitting *new* work after the first
|
|
@@ -211,7 +211,7 @@ def run_loop(
|
|
|
211
211
|
|
|
212
212
|
`send_one` does the wire send; `audit_attempt` writes the audit entry;
|
|
213
213
|
`to_envelope` converts a raised exception into an ErrorEnvelope. Callers
|
|
214
|
-
inject these so this loop has no dependency on
|
|
214
|
+
inject these so this loop has no dependency on httpx2 or the audit module.
|
|
215
215
|
"""
|
|
216
216
|
if workers < 1:
|
|
217
217
|
raise ValueError(f"workers must be >= 1, got {workers}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Cheap on-disk reads for completion.
|
|
2
2
|
|
|
3
3
|
This module deliberately avoids importing `nsc.schema.source` (which pulls in
|
|
4
|
-
|
|
4
|
+
httpx2 and the schema-build pipeline). It reads the cached `CommandModel`
|
|
5
5
|
JSON directly via `CacheStore`, which validates the file but performs no
|
|
6
6
|
network I/O. Picking the newest cache file for a profile means we never need
|
|
7
7
|
the live schema hash at TAB time.
|
|
@@ -10,7 +10,7 @@ the `#82` note in `nsc/cli/app.py`).
|
|
|
10
10
|
Every public function swallows exceptions and degrades to `[]`: a raised
|
|
11
11
|
exception during completion would corrupt the user's shell prompt.
|
|
12
12
|
|
|
13
|
-
Imports are kept lazy/cheap — no `nsc.cli`, no
|
|
13
|
+
Imports are kept lazy/cheap — no `nsc.cli`, no httpx2 — so loading this module
|
|
14
14
|
never drags in the full app cold-start path.
|
|
15
15
|
"""
|
|
16
16
|
|
|
@@ -129,7 +129,7 @@ def _sanitize_url(url: str) -> str:
|
|
|
129
129
|
|
|
130
130
|
The query string of a debug-mode GET can carry secret filter values
|
|
131
131
|
(e.g. `?private_key=...`), and `Profile.url` permits `https://user:pass@host`
|
|
132
|
-
whose `user:pass@`
|
|
132
|
+
whose `user:pass@` httpx2 keeps in `str(request.url)`. Both are stripped so a
|
|
133
133
|
`full` line cannot leak a credential through the one remaining string field.
|
|
134
134
|
|
|
135
135
|
Robust to URLs with no query/userinfo (no-op) and to relative/edge URLs: if
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Sync
|
|
1
|
+
"""Sync httpx2-based NetBox client."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,11 +7,9 @@ import time
|
|
|
7
7
|
from collections.abc import Iterator
|
|
8
8
|
from datetime import UTC, datetime
|
|
9
9
|
from types import TracebackType
|
|
10
|
-
from typing import Any, Protocol, cast
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Protocol, cast
|
|
11
11
|
from urllib.parse import parse_qs, urlsplit
|
|
12
12
|
|
|
13
|
-
import httpx
|
|
14
|
-
|
|
15
13
|
from nsc.config.models import AuditRedaction
|
|
16
14
|
from nsc.config.settings import default_paths
|
|
17
15
|
from nsc.http.audit import (
|
|
@@ -29,6 +27,9 @@ from nsc.http.retry import (
|
|
|
29
27
|
)
|
|
30
28
|
from nsc.model.command_model import HttpMethod
|
|
31
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
import httpx2
|
|
32
|
+
|
|
32
33
|
_BODY_SNIPPET_BYTES = 2048
|
|
33
34
|
_HTTP_5XX_MIN = 500
|
|
34
35
|
_HTTP_4XX_MIN = 400
|
|
@@ -50,13 +51,15 @@ class NetBoxClient:
|
|
|
50
51
|
redaction: AuditRedaction = AuditRedaction.SAFE,
|
|
51
52
|
profile_name: str | None = None,
|
|
52
53
|
) -> None:
|
|
54
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
55
|
+
|
|
53
56
|
if profile.token is None:
|
|
54
57
|
raise ValueError("NetBoxClient requires a non-None token on the profile")
|
|
55
58
|
self._url = str(profile.url).rstrip("/")
|
|
56
59
|
self._debug = debug
|
|
57
60
|
self._redaction = redaction
|
|
58
61
|
self._profile_name = profile_name
|
|
59
|
-
self._client =
|
|
62
|
+
self._client = httpx2.Client(
|
|
60
63
|
base_url=self._url,
|
|
61
64
|
headers={
|
|
62
65
|
"Authorization": f"Token {profile.token}",
|
|
@@ -173,7 +176,7 @@ class NetBoxClient:
|
|
|
173
176
|
if first:
|
|
174
177
|
req_path, req_params = url, params
|
|
175
178
|
else:
|
|
176
|
-
# next URLs are absolute; split them so respx (and
|
|
179
|
+
# next URLs are absolute; split them so respx (and httpx2) can
|
|
177
180
|
# match on path + params independently rather than a raw string.
|
|
178
181
|
parsed = urlsplit(url)
|
|
179
182
|
req_path = parsed.path
|
|
@@ -199,7 +202,9 @@ class NetBoxClient:
|
|
|
199
202
|
operation_id: str | None = None,
|
|
200
203
|
record_indices: list[int] | None = None,
|
|
201
204
|
sensitive_paths: tuple[str, ...] = (),
|
|
202
|
-
) ->
|
|
205
|
+
) -> httpx2.Response:
|
|
206
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
207
|
+
|
|
203
208
|
policy = policy_for_method(method)
|
|
204
209
|
attempt = 0
|
|
205
210
|
is_write = method not in {HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS}
|
|
@@ -209,7 +214,7 @@ class NetBoxClient:
|
|
|
209
214
|
started = time.monotonic()
|
|
210
215
|
try:
|
|
211
216
|
response = self._client.request(method.value, path, params=params, json=json_body)
|
|
212
|
-
except
|
|
217
|
+
except httpx2.RequestError as exc:
|
|
213
218
|
error_class: ErrorClass = classify_error(exc)
|
|
214
219
|
duration_ms = int((time.monotonic() - started) * 1000)
|
|
215
220
|
retry = should_retry(
|
|
@@ -264,7 +269,7 @@ class NetBoxClient:
|
|
|
264
269
|
request = self._client.build_request("GET", path, params=params)
|
|
265
270
|
return str(request.url)
|
|
266
271
|
|
|
267
|
-
def _raise_for_status(self, response:
|
|
272
|
+
def _raise_for_status(self, response: httpx2.Response) -> None:
|
|
268
273
|
try:
|
|
269
274
|
body = response.text[:_BODY_SNIPPET_BYTES]
|
|
270
275
|
except Exception: # pragma: no cover
|
|
@@ -283,7 +288,7 @@ class NetBoxClient:
|
|
|
283
288
|
path: str,
|
|
284
289
|
params: dict[str, Any] | None,
|
|
285
290
|
request_body: Any | None,
|
|
286
|
-
response:
|
|
291
|
+
response: httpx2.Response | None,
|
|
287
292
|
duration_ms: int,
|
|
288
293
|
attempt: int,
|
|
289
294
|
final: bool,
|
|
@@ -293,7 +298,7 @@ class NetBoxClient:
|
|
|
293
298
|
sensitive_paths: tuple[str, ...] = (),
|
|
294
299
|
) -> None:
|
|
295
300
|
url = self._absolute(path, params)
|
|
296
|
-
#
|
|
301
|
+
# httpx2 lowercases all header names; title-case them so the audit log
|
|
297
302
|
# uses the conventional HTTP capitalisation (e.g. "Authorization").
|
|
298
303
|
request_headers = {k.title(): v for k, v in self._client.headers.items()}
|
|
299
304
|
if response is not None:
|
|
@@ -346,13 +351,13 @@ class NetBoxClient:
|
|
|
346
351
|
append_audit_jsonl(entry, path=log_dir / "audit.jsonl")
|
|
347
352
|
|
|
348
353
|
def _event_hooks(self) -> dict[str, list[Any]]:
|
|
349
|
-
def on_request(request:
|
|
354
|
+
def on_request(request: httpx2.Request) -> None:
|
|
350
355
|
print(f">>> {request.method} {request.url}", file=sys.stderr)
|
|
351
356
|
for k, v in request.headers.items():
|
|
352
357
|
masked = "<redacted>" if k.lower() == "authorization" else v
|
|
353
358
|
print(f">>> {k}: {masked}", file=sys.stderr)
|
|
354
359
|
|
|
355
|
-
def on_response(response:
|
|
360
|
+
def on_response(response: httpx2.Response) -> None:
|
|
356
361
|
response.read()
|
|
357
362
|
print(f"<<< {response.status_code} {response.reason_phrase}", file=sys.stderr)
|
|
358
363
|
for k, v in response.headers.items():
|
|
@@ -368,7 +373,7 @@ def _now_iso() -> str:
|
|
|
368
373
|
return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
|
369
374
|
|
|
370
375
|
|
|
371
|
-
def _parse_json(response:
|
|
376
|
+
def _parse_json(response: httpx2.Response) -> dict[str, Any]:
|
|
372
377
|
if not response.content:
|
|
373
378
|
return {}
|
|
374
379
|
return cast(dict[str, Any], response.json())
|
|
@@ -9,7 +9,6 @@ from __future__ import annotations
|
|
|
9
9
|
import random
|
|
10
10
|
from enum import Enum
|
|
11
11
|
|
|
12
|
-
import httpx
|
|
13
12
|
from pydantic import BaseModel, ConfigDict
|
|
14
13
|
|
|
15
14
|
from nsc.model.command_model import HttpMethod
|
|
@@ -48,10 +47,12 @@ def policy_for_method(method: HttpMethod) -> RetryPolicy:
|
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
def classify_error(exc: BaseException) -> ErrorClass:
|
|
51
|
-
"""Map an
|
|
52
|
-
|
|
50
|
+
"""Map an httpx2 transport exception to a retry-relevant class."""
|
|
51
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
52
|
+
|
|
53
|
+
if isinstance(exc, (httpx2.ConnectError, httpx2.ConnectTimeout)):
|
|
53
54
|
return ErrorClass.CONNECT
|
|
54
|
-
if isinstance(exc,
|
|
55
|
+
if isinstance(exc, httpx2.ReadTimeout):
|
|
55
56
|
return ErrorClass.READ_TIMEOUT
|
|
56
57
|
return ErrorClass.TRANSPORT_AMBIGUOUS
|
|
57
58
|
|
|
@@ -7,8 +7,6 @@ from collections.abc import Iterator
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
import httpx
|
|
11
|
-
|
|
12
10
|
from nsc.schema.hashing import canonical_sha256
|
|
13
11
|
from nsc.schema.models import OpenAPIDocument
|
|
14
12
|
|
|
@@ -122,8 +120,10 @@ def _decode_content_encoding(raw: bytes, encoding: str, source: str) -> bytes:
|
|
|
122
120
|
|
|
123
121
|
|
|
124
122
|
def _fetch_http_body(source: str, *, verify_ssl: bool, timeout: float) -> bytes:
|
|
123
|
+
import httpx2 # noqa: PLC0415 # deferred: keeps httpx2 off the CLI-startup path.
|
|
124
|
+
|
|
125
125
|
try:
|
|
126
|
-
with
|
|
126
|
+
with httpx2.stream(
|
|
127
127
|
"GET",
|
|
128
128
|
source,
|
|
129
129
|
verify=verify_ssl,
|
|
@@ -139,5 +139,5 @@ def _fetch_http_body(source: str, *, verify_ssl: bool, timeout: float) -> bytes:
|
|
|
139
139
|
raw = _read_capped(response.iter_raw(), source)
|
|
140
140
|
encoding = response.headers.get("content-encoding", "")
|
|
141
141
|
return _decode_content_encoding(raw, encoding, source)
|
|
142
|
-
except
|
|
142
|
+
except httpx2.HTTPError as exc:
|
|
143
143
|
raise SchemaLoadError(f"{source}: request failed ({exc})") from exc
|