Add the ability to export the schema in yaml#835
Conversation
Adds `infrahubctl schema export` to fetch user-defined schemas from an Infrahub server and write them as YAML files (one per namespace). Restricted namespaces (Core, Builtin, Internal, etc.) and auto-generated types (ProfileSchemaAPI, TemplateSchemaAPI) are excluded by default. RESTRICTED_NAMESPACES is mirrored from the Infrahub server constants so the server can later consume it from the SDK. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Strip relationship fields that match schema loading defaults (direction: bidirectional, on_delete: no-action, cardinality: many, optional: true, min_count: 0, max_count: 0) - Exclude branch from attribute/relationship dumps (inherited from node) - Ensure scalar fields appear before attributes/relationships lists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
read_only was unconditionally excluded from attribute dumps; move it to _ATTR_EXPORT_DEFAULTS so it is stripped only when False (the default) and kept when True (computed/read-only attributes). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…export - Drop relationships with kind Group, Profile, or Hierarchy from export output — these are always auto-generated by Infrahub and must not be re-loaded manually (doing so caused validator_not_available errors for hierarchical generics) - For GenericSchemaAPI objects that had Hierarchy relationships, restore the `hierarchical: true` flag so the schema round-trips cleanly - Add read_only: false to _REL_EXPORT_DEFAULTS to stop it leaking into relationship output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ints - generics key now appears before nodes in the exported YAML payload - uniqueness_constraints entries that are auto-generated from unique: true attributes (single-field ["<attr>__value"] entries) are removed on export; user-defined multi-field constraints are preserved Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploying infrahub-sdk-python with
|
| Latest commit: |
e63783e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://74ae813b.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://bkr-add-schema-exporter.infrahub-sdk-python.pages.dev |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## stable #835 +/- ##
==========================================
- Coverage 80.36% 72.53% -7.83%
==========================================
Files 115 116 +1
Lines 9875 9952 +77
Branches 1504 1517 +13
==========================================
- Hits 7936 7219 -717
- Misses 1417 2227 +810
+ Partials 522 506 -16
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 31 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Move export helpers from ctl/schema.py to infrahub_sdk/schema/export.py so the conversion logic is reusable outside the CLI. Regenerate infrahubctl docs to include the new export subcommand. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| "Schema", | ||
| "Profile", | ||
| "Template", | ||
| ] |
There was a problem hiding this comment.
Instead of doing it like this I'd suggest that we have this part be included within the schema component of the SDK. The backend will return this information (to indicate if the namespace is editable by the user or not). It could be that the SDK doesn't yet deal with that part of the API. But we should not introduce a new list for this and support them separately.
There was a problem hiding this comment.
Can we do it as part of another PR later on ?
WalkthroughThis pull request implements schema export functionality for Infrahub. It introduces a new 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
infrahub_sdk/constants.py (1)
6-19: Consider usingfrozensetforRESTRICTED_NAMESPACES.As a constant used exclusively for membership tests (
in),frozensetgives O(1) lookup and prevents accidental mutation.♻️ Proposed change
-RESTRICTED_NAMESPACES: list[str] = [ - "Account", - "Branch", - "Builtin", - "Core", - "Deprecated", - "Diff", - "Infrahub", - "Internal", - "Lineage", - "Schema", - "Profile", - "Template", -] +RESTRICTED_NAMESPACES: frozenset[str] = frozenset({ + "Account", + "Branch", + "Builtin", + "Core", + "Deprecated", + "Diff", + "Infrahub", + "Internal", + "Lineage", + "Schema", + "Profile", + "Template", +})🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@infrahub_sdk/constants.py` around lines 6 - 19, RESTRICTED_NAMESPACES is currently a mutable list but used only for membership checks; change its type to an immutable frozenset to prevent accidental mutation and get O(1) lookups by replacing the list literal with a frozenset literal (e.g., frozenset({...})) where RESTRICTED_NAMESPACES is defined in infrahub_sdk/constants.py and ensure any code that treats it as a sequence (indexing/ordering) is not relying on list behavior.infrahub_sdk/schema/__init__.py (1)
533-553:export()always fetches the full schema even whennamespacesis specified.The client-side namespace filtering in
_build_export_schemasis applied after fetching all schemas from the server. For large deployments, passing the namespace filter to_fetch(already supported via thenamespacesparameter ofInfrahubSchema.fetch) would reduce payload size. Not a bug, but worth considering for performance at scale.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@infrahub_sdk/schema/__init__.py` around lines 533 - 553, The export() method currently fetches the full schema then filters client-side; change it to pass the provided namespaces to the server fetch to reduce payload. Update the call in export (function export) from awaiting self.fetch(branch=branch) to await self.fetch(branch=branch, namespaces=namespaces) so server-side filtering is used before calling _build_export_schemas(schema_nodes=..., namespaces=namespaces); ensure the namespaces parameter shape (None or list[str]) matches InfrahubSchema.fetch's namespaces arg.tests/unit/sdk/test_schema_export.py (1)
19-97: Duplicated test data builders across both test files.
_BASE_NODE,_BASE_GENERIC,_schema_response, and the_make_*helpers are defined in near-identical form in bothtests/unit/sdk/test_schema_export.pyandtests/unit/ctl/test_schema_export.py. Extracting these into a sharedtests/helpers/schema.pymodule would eliminate the duplication.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/sdk/test_schema_export.py` around lines 19 - 97, Duplicate test data and builders (_BASE_NODE, _BASE_GENERIC, _schema_response and helpers _make_node_schema, _make_generic_schema, _make_profile_schema, _make_template_schema) should be extracted into a single shared helper module and both test files should import them; create a new helpers module that exports those constants and factory functions, replace the duplicated definitions in tests/unit/sdk/test_schema_export.py and tests/unit/ctl/test_schema_export.py with imports from the new module, and update any references to use the imported symbols so tests still construct NodeSchemaAPI, GenericSchemaAPI, ProfileSchemaAPI, and TemplateSchemaAPI using the shared builders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrahub_sdk/ctl/schema.py`:
- Around line 217-219: The default export directory function
_default_export_directory currently returns a str while the export command's
directory parameter is annotated as Path; update _default_export_directory to
return a Path (e.g., wrap the formatted string with Path(...)) so the default
value matches the annotated type for export, and/or add a unit/integration test
that calls the export CLI without --directory to assert the resulting type is
Path and no AttributeError occurs (look for references to
_default_export_directory and the export command / directory parameter to modify
code and add tests).
---
Nitpick comments:
In `@infrahub_sdk/constants.py`:
- Around line 6-19: RESTRICTED_NAMESPACES is currently a mutable list but used
only for membership checks; change its type to an immutable frozenset to prevent
accidental mutation and get O(1) lookups by replacing the list literal with a
frozenset literal (e.g., frozenset({...})) where RESTRICTED_NAMESPACES is
defined in infrahub_sdk/constants.py and ensure any code that treats it as a
sequence (indexing/ordering) is not relying on list behavior.
In `@infrahub_sdk/schema/__init__.py`:
- Around line 533-553: The export() method currently fetches the full schema
then filters client-side; change it to pass the provided namespaces to the
server fetch to reduce payload. Update the call in export (function export) from
awaiting self.fetch(branch=branch) to await self.fetch(branch=branch,
namespaces=namespaces) so server-side filtering is used before calling
_build_export_schemas(schema_nodes=..., namespaces=namespaces); ensure the
namespaces parameter shape (None or list[str]) matches InfrahubSchema.fetch's
namespaces arg.
In `@tests/unit/sdk/test_schema_export.py`:
- Around line 19-97: Duplicate test data and builders (_BASE_NODE,
_BASE_GENERIC, _schema_response and helpers _make_node_schema,
_make_generic_schema, _make_profile_schema, _make_template_schema) should be
extracted into a single shared helper module and both test files should import
them; create a new helpers module that exports those constants and factory
functions, replace the duplicated definitions in
tests/unit/sdk/test_schema_export.py and tests/unit/ctl/test_schema_export.py
with imports from the new module, and update any references to use the imported
symbols so tests still construct NodeSchemaAPI, GenericSchemaAPI,
ProfileSchemaAPI, and TemplateSchemaAPI using the shared builders.
| def _default_export_directory() -> str: | ||
| timestamp = datetime.now(timezone.utc).astimezone().strftime("%Y%m%d-%H%M%S") | ||
| return f"infrahub-schema-export-{timestamp}" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, examine the actual code in the file
cat -n infrahub_sdk/ctl/schema.py | sed -n '210,230p'Repository: opsmill/infrahub-sdk-python
Length of output: 1088
🏁 Script executed:
# Find the Typer version being used
fd -e "txt" -e "toml" -e "cfg" -e "yaml" | xargs grep -l "typer" | head -5Repository: opsmill/infrahub-sdk-python
Length of output: 85
🏁 Script executed:
# Check if there are CLI tests that exercise the default directory path
fd -e "test_*.py" -e "*_test.py" | xargs grep -l "schema" | head -10Repository: opsmill/infrahub-sdk-python
Length of output: 53
🌐 Web query:
Typer callable default type coercion Path annotation documentation
💡 Result:
-
Callable defaults (dynamic defaults): Typer documents
default_factoryfor bothtyper.Argument(...)andtyper.Option(...). It must be a callable likeCallable[[], Any]that Typer calls to produce the default value when the user doesn’t provide one. See Reference → Parameters →default_factory. [1] -
Type coercion / parsing (custom coercion): If you need custom conversion from the CLI string token(s) to your Python type, Typer documents:
parser: a callable likeCallable[[str], Any]that receives the raw string and returns your custom type.click_type: provide a custom ClickParamTypeto handle conversion/validation.
These are in the same Reference → Parameters page. [1]
-
Pathannotation behavior: Typer documents that if you annotate a parameter aspathlib.Path(often viatyping.Annotated[..., typer.Option(...)]/typer.Argument(...)), Typer/Click will coerce the CLI input into aPathobject, and you can add path validations likeexists,file_okay,dir_okay,readable,writable,resolve_path, plus advanced settings likeallow_dashandpath_type. See Tutorial → Parameter Types → Path. [2]
Sources: Typer “Parameters” reference [1]; Typer “Path” parameter type tutorial [2].
🏁 Script executed:
# Check Typer version in pyproject.toml
cat pyproject.toml | grep -A 5 -B 5 "typer"Repository: opsmill/infrahub-sdk-python
Length of output: 1615
🏁 Script executed:
# Search for any tests that call export() without --directory argument
find . -name "test_*.py" -o -name "*_test.py" | xargs grep -l "export" | head -5Repository: opsmill/infrahub-sdk-python
Length of output: 190
🏁 Script executed:
# Check if there's actual test coverage calling export without directory arg
rg "async def export|def export" infrahub_sdk/ctl/schema.py -A 20 | head -40Repository: opsmill/infrahub-sdk-python
Length of output: 866
🏁 Script executed:
# Look for Click/Typer source or test to understand callable default behavior
rg "default_factory|default\(" --type py infrahub_sdk/ctl/ -B 2 -A 2 | head -60Repository: opsmill/infrahub-sdk-python
Length of output: 53
🏁 Script executed:
# Examine the test file for schema export
cat -n tests/unit/ctl/test_schema_export.py | head -100Repository: opsmill/infrahub-sdk-python
Length of output: 3224
🏁 Script executed:
# Search for any test that calls export() without --directory
rg "def test.*export|async def test.*export" tests/unit/ctl/test_schema_export.py -A 30 | head -80Repository: opsmill/infrahub-sdk-python
Length of output: 2974
🏁 Script executed:
# Check if there's any test that exercises the default directory behavior
rg "from_cli|CliRunner|invoke" tests/unit/ctl/test_schema_export.py -B 3 -A 10Repository: opsmill/infrahub-sdk-python
Length of output: 6496
Default directory code path is untested — add test coverage or apply explicit type conversion.
_default_export_directory() returns a str, but directory is annotated as Path. All tests explicitly supply --directory, leaving the default callable behavior untested. While Typer/Click applies type coercion for CLI input, callable return value coercion through type annotations is not documented. To eliminate the risk of AttributeError at runtime when the default is used, either:
- Add a test that invokes
exportwithout--directoryto verify type coercion works, or - Explicitly convert the return value:
return Path(f"infrahub-schema-export-{timestamp}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrahub_sdk/ctl/schema.py` around lines 217 - 219, The default export
directory function _default_export_directory currently returns a str while the
export command's directory parameter is annotated as Path; update
_default_export_directory to return a Path (e.g., wrap the formatted string with
Path(...)) so the default value matches the annotated type for export, and/or
add a unit/integration test that calls the export CLI without --directory to
assert the resulting type is Path and no AttributeError occurs (look for
references to _default_export_directory and the export command / directory
parameter to modify code and add tests).
Fixes #151
Summary by CodeRabbit
Release Notes
New Features
infrahubctl schema exportcommand to export schemas as YAML files, organized by namespaceDocumentation