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.
Files changed (337) hide show
  1. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/docs.yml +1 -1
  2. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.gitignore +1 -1
  3. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.pre-commit-config.yaml +1 -1
  4. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/AGENTS.md +1 -1
  5. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/CHANGELOG.md +50 -0
  6. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/CLAUDE.md +1 -1
  7. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/PKG-INFO +3 -3
  8. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/http-client.md +5 -5
  9. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/overview.md +2 -2
  10. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/release-process.md +1 -1
  11. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/justfile +4 -4
  12. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/_version.py +1 -1
  13. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/auth/verify.py +17 -7
  14. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cache/store.py +11 -3
  15. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/cache_commands.py +3 -2
  16. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/runtime.py +1 -1
  17. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/bulk.py +3 -3
  18. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/cache_probe.py +1 -1
  19. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/callbacks.py +1 -1
  20. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/audit.py +1 -1
  21. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/client.py +19 -14
  22. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/retry.py +5 -4
  23. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/loader.py +4 -4
  24. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/source.py +28 -3
  25. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/pyproject.toml +8 -5
  26. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/scripts/gen_docs.py +1 -1
  27. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/auth/test_verify.py +18 -32
  28. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/benchmarks/test_startup.py +7 -2
  29. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/test_store.py +14 -0
  30. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_aliases_commands.py +91 -129
  31. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_app_smoke.py +23 -27
  32. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_commands_dump.py +10 -7
  33. netbox_super_cli-1.7.0/tests/cli/test_lazy_httpx2_import.py +52 -0
  34. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_login_commands.py +31 -43
  35. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_ndjson_input.py +14 -15
  36. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_profiles_commands.py +6 -10
  37. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_respx_integration.py +39 -37
  38. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_writes_respx.py +108 -120
  39. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/conftest.py +5 -5
  40. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_audit_redaction.py +2 -2
  41. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_bulk_create_with_loop_fallback.py +3 -3
  42. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_full_cycle.py +2 -2
  43. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_login.py +2 -2
  44. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_ndjson.py +3 -3
  45. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_preflight_blocks_apply.py +2 -2
  46. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_validation_error_envelope_from_netbox.py +2 -2
  47. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_client.py +135 -160
  48. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_client_redaction_threading.py +10 -10
  49. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_retry.py +7 -7
  50. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_loader.py +14 -24
  51. netbox_super_cli-1.7.0/tests/schema/test_source.py +224 -0
  52. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_source_ttl.py +46 -50
  53. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/uv.lock +141 -272
  54. netbox_super_cli-1.6.2/tests/schema/test_source.py +0 -159
  55. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  56. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  57. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/docs.yml +0 -0
  58. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  59. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/dependabot.yml +0 -0
  60. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/pull_request_template.md +0 -0
  61. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/bench.yml +0 -0
  62. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/claude.yml +0 -0
  63. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/e2e.yml +0 -0
  64. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/lint.yml +0 -0
  65. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/release.yml +0 -0
  66. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/skill-convention-review.yml +0 -0
  67. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.github/workflows/test.yml +0 -0
  68. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/.python-version +0 -0
  69. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/LICENSE +0 -0
  70. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/README.md +0 -0
  71. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/caching.md +0 -0
  72. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/command-generation.md +0 -0
  73. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/architecture/schema-loading.md +0 -0
  74. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/detail.svg +0 -0
  75. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/filter.svg +0 -0
  76. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/list.svg +0 -0
  77. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/picker.svg +0 -0
  78. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/assets/tui/search.svg +0 -0
  79. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/adding-bundled-schemas.md +0 -0
  80. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/branching.md +0 -0
  81. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/contributing/development.md +0 -0
  82. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/concepts.md +0 -0
  83. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/first-run.md +0 -0
  84. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/getting-started/install.md +0 -0
  85. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/audit-log.md +0 -0
  86. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/ci-and-automation.md +0 -0
  87. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/interactive-tui.md +0 -0
  88. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/managing-profiles.md +0 -0
  89. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/output-formats.md +0 -0
  90. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/using-with-ai-agents.md +0 -0
  91. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/working-with-plugins.md +0 -0
  92. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/guides/writes-and-safety.md +0 -0
  93. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/index.md +0 -0
  94. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/cli.md +0 -0
  95. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/config.md +0 -0
  96. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/exit-codes.md +0 -0
  97. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/docs/reference/schemas.md +0 -0
  98. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/mkdocs.yml +0 -0
  99. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/__init__.py +0 -0
  100. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/__main__.py +0 -0
  101. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/aliases/__init__.py +0 -0
  102. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/aliases/resolver.py +0 -0
  103. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/auth/__init__.py +0 -0
  104. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/builder/__init__.py +0 -0
  105. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/builder/build.py +0 -0
  106. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cache/__init__.py +0 -0
  107. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/__init__.py +0 -0
  108. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/aliases_commands.py +0 -0
  109. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/app.py +0 -0
  110. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/commands_dump.py +0 -0
  111. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/config_commands.py +0 -0
  112. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/globals.py +0 -0
  113. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/handlers.py +0 -0
  114. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/init_commands.py +0 -0
  115. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/login_commands.py +0 -0
  116. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/profiles_commands.py +0 -0
  117. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/registration.py +0 -0
  118. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/skill_commands.py +0 -0
  119. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/tui_commands.py +0 -0
  120. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/__init__.py +0 -0
  121. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/apply.py +0 -0
  122. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/coercion.py +0 -0
  123. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/confirmation.py +0 -0
  124. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/input.py +0 -0
  125. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/cli/writes/preflight.py +0 -0
  126. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/__init__.py +0 -0
  127. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/completion/providers.py +0 -0
  128. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/__init__.py +0 -0
  129. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/loader.py +0 -0
  130. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/models.py +0 -0
  131. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/saved_searches.py +0 -0
  132. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/settings.py +0 -0
  133. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/config/writer.py +0 -0
  134. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/__init__.py +0 -0
  135. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/http/errors.py +0 -0
  136. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/model/__init__.py +0 -0
  137. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/model/command_model.py +0 -0
  138. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/__init__.py +0 -0
  139. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/_console.py +0 -0
  140. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/colors.py +0 -0
  141. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/csv_.py +0 -0
  142. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/errors.py +0 -0
  143. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/explain.py +0 -0
  144. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/flatten.py +0 -0
  145. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/headers.py +0 -0
  146. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/json_.py +0 -0
  147. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/jsonl.py +0 -0
  148. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/render.py +0 -0
  149. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/table.py +0 -0
  150. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/output/yaml_.py +0 -0
  151. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/__init__.py +0 -0
  152. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/custom_fields.py +0 -0
  153. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/objecttypes.py +0 -0
  154. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/params.py +0 -0
  155. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/store.py +0 -0
  156. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/savedfilters/tags.py +0 -0
  157. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/__init__.py +0 -0
  158. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/hashing.py +0 -0
  159. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schema/models.py +0 -0
  160. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/__init__.py +0 -0
  161. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/__init__.py +0 -0
  162. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/manifest.yaml +0 -0
  163. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/netbox-4.5.10.json.gz +0 -0
  164. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/schemas/bundled/netbox-4.6.0.json.gz +0 -0
  165. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/skill/__init__.py +0 -0
  166. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/__init__.py +0 -0
  167. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/_bindings.py +0 -0
  168. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/app.py +0 -0
  169. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/bulk.py +0 -0
  170. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/catalog.py +0 -0
  171. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/columns.py +0 -0
  172. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/errors.py +0 -0
  173. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/filters.py +0 -0
  174. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/fk.py +0 -0
  175. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/forms.py +0 -0
  176. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/keymap.py +0 -0
  177. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/nav.py +0 -0
  178. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/relations.py +0 -0
  179. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/__init__.py +0 -0
  180. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/bulk_edit_form.py +0 -0
  181. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/columns.py +0 -0
  182. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/detail.py +0 -0
  183. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/edit_form.py +0 -0
  184. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/filter.py +0 -0
  185. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/global_search.py +0 -0
  186. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/list.py +0 -0
  187. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/picker.py +0 -0
  188. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/record_picker.py +0 -0
  189. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/screens/saved_search_picker.py +0 -0
  190. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/search.py +0 -0
  191. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/selection.py +0 -0
  192. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/styles.tcss +0 -0
  193. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/view.py +0 -0
  194. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/__init__.py +0 -0
  195. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/_modal.py +0 -0
  196. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/bulk_diff.py +0 -0
  197. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/bulk_summary.py +0 -0
  198. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/confirm.py +0 -0
  199. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/diff.py +0 -0
  200. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/help.py +0 -0
  201. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/nsc/tui/widgets/nav_tree.py +0 -0
  202. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/scripts/sync_agents_md.py +0 -0
  203. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/skills/netbox-super-cli/SKILL.md +0 -0
  204. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/__init__.py +0 -0
  205. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/aliases/__init__.py +0 -0
  206. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/aliases/test_resolver.py +0 -0
  207. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/audit/__init__.py +0 -0
  208. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/audit/test_redaction_full.py +0 -0
  209. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/auth/__init__.py +0 -0
  210. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/benchmarks/__init__.py +0 -0
  211. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/__init__.py +0 -0
  212. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_build.py +0 -0
  213. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_default_columns.py +0 -0
  214. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_fk_metadata.py +0 -0
  215. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_redaction_marking.py +0 -0
  216. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/builder/test_request_body_shape.py +0 -0
  217. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/__init__.py +0 -0
  218. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cache/test_prune.py +0 -0
  219. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/__init__.py +0 -0
  220. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_cache_commands.py +0 -0
  221. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_completion_protocol_smoke.py +0 -0
  222. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_completion_smoke.py +0 -0
  223. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_config_commands.py +0 -0
  224. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_dynamic_completion.py +0 -0
  225. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_explain.py +0 -0
  226. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_globals_color.py +0 -0
  227. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_handlers.py +0 -0
  228. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_init_commands.py +0 -0
  229. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_lazy_textual_import.py +0 -0
  230. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_meta_subcommands_under_broken_config.py +0 -0
  231. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_registration.py +0 -0
  232. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_runtime.py +0 -0
  233. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_runtime_color.py +0 -0
  234. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_skill_commands.py +0 -0
  235. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_stdin_sniffer.py +0 -0
  236. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/test_tui_command.py +0 -0
  237. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/__init__.py +0 -0
  238. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_apply.py +0 -0
  239. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_bulk.py +0 -0
  240. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_confirmation.py +0 -0
  241. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_handler_helpers.py +0 -0
  242. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_handlers_audit.py +0 -0
  243. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_input.py +0 -0
  244. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/cli/writes/test_preflight.py +0 -0
  245. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/__init__.py +0 -0
  246. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_loader.py +0 -0
  247. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_models.py +0 -0
  248. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_saved_search_fallback.py +0 -0
  249. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_saved_searches.py +0 -0
  250. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_settings_private_dir.py +0 -0
  251. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_atomicity.py +0 -0
  252. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_dotted_paths.py +0 -0
  253. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/config/test_writer_roundtrip.py +0 -0
  254. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/conftest.py +0 -0
  255. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/README.md +0 -0
  256. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/__init__.py +0 -0
  257. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/docker-compose.yml +0 -0
  258. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/test_auth_error_envelope.py +0 -0
  259. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/e2e/wait_for_netbox.sh +0 -0
  260. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/profiles/single_profile.yaml +0 -0
  261. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/auth_401.json +0 -0
  262. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/circuits_providers_list.json +0 -0
  263. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_get.json +0 -0
  264. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_list_p1.json +0 -0
  265. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/fixtures/responses/dcim_devices_list_p2.json +0 -0
  266. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/__init__.py +0 -0
  267. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit.py +0 -0
  268. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit_permissions.py +0 -0
  269. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_audit_redaction.py +0 -0
  270. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/http/test_errors.py +0 -0
  271. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/__init__.py +0 -0
  272. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/test_command_model.py +0 -0
  273. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/model/test_request_body_shape.py +0 -0
  274. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/__init__.py +0 -0
  275. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_colors.py +0 -0
  276. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_console.py +0 -0
  277. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_csv.py +0 -0
  278. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors.py +0 -0
  279. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors_aliases.py +0 -0
  280. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_errors_color.py +0 -0
  281. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_explain.py +0 -0
  282. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_explain_color.py +0 -0
  283. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_flatten.py +0 -0
  284. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_input_error_envelope.py +0 -0
  285. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_json.py +0 -0
  286. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_jsonl.py +0 -0
  287. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_render_dispatch.py +0 -0
  288. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_table.py +0 -0
  289. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_table_color.py +0 -0
  290. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/output/test_yaml.py +0 -0
  291. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/__init__.py +0 -0
  292. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_custom_fields.py +0 -0
  293. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_objecttypes.py +0 -0
  294. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_params.py +0 -0
  295. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_store.py +0 -0
  296. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/savedfilters/test_tags.py +0 -0
  297. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/__init__.py +0 -0
  298. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_hashing.py +0 -0
  299. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/schema/test_models.py +0 -0
  300. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/__init__.py +0 -0
  301. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/test_gen_docs.py +0 -0
  302. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/scripts/test_sync_agents_md.py +0 -0
  303. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/skill/__init__.py +0 -0
  304. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/skill/test_bundle.py +0 -0
  305. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/test_packaging.py +0 -0
  306. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/__init__.py +0 -0
  307. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_app.py +0 -0
  308. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bindings.py +0 -0
  309. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_apply.py +0 -0
  310. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_diff.py +0 -0
  311. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_diff_modal.py +0 -0
  312. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_edit_form.py +0 -0
  313. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_import_isolation.py +0 -0
  314. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_bulk_summary.py +0 -0
  315. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_catalog.py +0 -0
  316. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_column_chooser.py +0 -0
  317. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_columns.py +0 -0
  318. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_detail_screen.py +0 -0
  319. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_diff.py +0 -0
  320. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_diff_modal.py +0 -0
  321. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_edit_form.py +0 -0
  322. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_errors.py +0 -0
  323. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_filter_screen.py +0 -0
  324. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_filters.py +0 -0
  325. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_fk_resolve.py +0 -0
  326. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_forms.py +0 -0
  327. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_global_search.py +0 -0
  328. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_help_overlay.py +0 -0
  329. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_keymap.py +0 -0
  330. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_list_screen.py +0 -0
  331. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_picker.py +0 -0
  332. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_record_picker.py +0 -0
  333. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_relations.py +0 -0
  334. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_saved_filter_integration.py +0 -0
  335. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_search.py +0 -0
  336. {netbox_super_cli-1.6.2 → netbox_super_cli-1.7.0}/tests/tui/test_selection.py +0 -0
  337. {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 mkdocs build --strict
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
@@ -33,7 +33,7 @@ venv/
33
33
  # Local-only design notes (kept out of git history)
34
34
  docs/superpowers/
35
35
 
36
- # mkdocs
36
+ # docs build output
37
37
  site/
38
38
 
39
39
  # git worktrees
@@ -27,7 +27,7 @@ repos:
27
27
  - click>=8.1
28
28
  - typer>=0.12
29
29
  - rich>=13.7
30
- - httpx>=0.27
30
+ - httpx2>=2.5
31
31
  - "ruamel.yaml>=0.18"
32
32
  - structlog>=24.1
33
33
  - platformdirs>=4.2
@@ -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 httpx wrapper: auth, retries, audit log.
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 httpx wrapper: auth, retries, audit log.
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: netbox-super-cli
3
- Version: 1.6.2
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: httpx>=0.27
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 `httpx.Client`.
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 `httpx.Client` — there is no
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 `httpx.Client` in `nsc/schema/`, not this `NetBoxClient`.
43
+ short-lived `httpx2.Client` in `nsc/schema/`, not this `NetBoxClient`.
44
44
  Command requests then go through a single `NetBoxClient` whose
45
- `httpx.Client` carries the auth header for the life of the process.
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 httpx if a future async path lands.
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 httpx.
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 httpx wrapper: auth, retries, audit
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 (`mkdocs build --strict`) on docs-touching
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 MkDocs site locally at http://127.0.0.1:8000
57
+ # Serve the docs site locally at http://127.0.0.1:8000 (preview only)
58
58
  docs:
59
- uv run mkdocs serve
59
+ uv run zensical serve
60
60
 
61
- # Build the MkDocs site (strict mode — fails on broken links, missing nav targets, etc.)
61
+ # Build the docs site (strict mode — fails on broken links, missing anchors, etc.)
62
62
  docs-build:
63
- uv run mkdocs build --strict
63
+ uv run zensical build --strict
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "1.6.2"
3
+ __version__ = "1.7.0"
@@ -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 httpx
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 httpx.Client(
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: httpx.Client) -> str:
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 (httpx.RequestError, OSError) as exc:
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: httpx.Client) -> str:
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 (httpx.RequestError, OSError) as exc:
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: httpx.Response) -> object:
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
- meta = self._meta_path_for(profile, model.schema_hash)
91
- _atomic_write(meta, json.dumps({"fetched_at": time.time()}))
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 httpx.Client(verify=profile.verify_ssl, timeout=timeout, headers=headers) as c:
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 httpx.Client) is mutable.
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 httpx. Three responsibilities:
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 httpx, so threads — not asyncio —
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 httpx or the audit module.
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
- httpx and the schema-build pipeline). It reads the cached `CommandModel`
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 httpx — so loading this module
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@` httpx keeps in `str(request.url)`. Both are stripped so a
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 httpx-based NetBox client."""
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 = httpx.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 httpx) can
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
- ) -> httpx.Response:
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 httpx.RequestError as exc:
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: httpx.Response) -> None:
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: httpx.Response | None,
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
- # httpx lowercases all header names; title-case them so the audit log
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: httpx.Request) -> None:
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: httpx.Response) -> None:
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: httpx.Response) -> dict[str, Any]:
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 httpx transport exception to a retry-relevant class."""
52
- if isinstance(exc, (httpx.ConnectError, httpx.ConnectTimeout)):
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, httpx.ReadTimeout):
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 httpx.stream(
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 httpx.HTTPError as exc:
142
+ except httpx2.HTTPError as exc:
143
143
  raise SchemaLoadError(f"{source}: request failed ({exc})") from exc