Skip to content

fix: emit loadable YAML for cardinality-many relationships - #1306

Open
rootsec1 wants to merge 5 commits into
opsmill:stablefrom
rootsec1:fix/yaml-many-relationship-roundtrip
Open

fix: emit loadable YAML for cardinality-many relationships#1306
rootsec1 wants to merge 5 commits into
opsmill:stablefrom
rootsec1:fix/yaml-many-relationship-roundtrip

Conversation

@rootsec1

@rootsec1 rootsec1 commented Aug 31, 2026

Copy link
Copy Markdown

Fixes #1274.

object get --output yaml wraps populated cardinality-many HFID references in a data dictionary, which the loader reserves for nested objects and therefore rejects when it contains a string list. Emit the already-supported plain HFID list instead and cover the formatter-to-loader path with a regression test.

Cardinality-one output, empty-relationship omission, and multi-component HFIDs are unchanged. Generic-kind relationship omission is excluded and remains tracked in #1275.

Tested with:

  • uv run pytest -q tests/unit/ctl/formatters/test_yaml.py tests/unit/sdk/spec/test_object.py (49 passed)
  • COLUMNS=140 uv run pytest -q tests/unit (1819 passed, 1 xfailed)
  • uv run invoke format
  • uv run invoke lint-code
  • uv run invoke docs-validate
  • uv run invoke lint-docs
  • uv run towncrier build --draft --version 1.23.2

@rootsec1
rootsec1 requested a review from a team as a code owner August 31, 2026 05:28

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@gmazoyer gmazoyer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit test proves the formatter output reaches MANY_REF and passes validation. It stops there. Nothing runs the real object get --output yaml then object load path against a server. test_get_list_yaml in tests/integration/test_enduser_cli.py only parses the YAML, and I found no object load call anywhere in tests/integration/. Issue #1274 says the failing round trip is covered by an integration test, but I could not find one in this repo. Maybe we should consider to add one.

refs = [r for p in peers if (r := _related_node_ref(p)) is not None]
if refs:
entry[rel_name] = {"data": refs}
entry[rel_name] = refs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a correct a minimal fix that describe what our documentation actually says.

Comment on lines 3 to 5
Produces YAML that is round-trippable with ``infrahubctl object load``.
Empty/null attribute values and unset relationships are omitted so the
output can be loaded back without validation errors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docstring says the output is round-trippable with infrahubctl object load. With #1275 open, many relationships of kind Generic are still dropped from the output (infrahub_sdk/node/node.py:1507-1508 skips them). A loaded document silently loses those. Not introduced by this PR though.

@polmichel polmichel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mocking pattern does exist in other tests in the project, but we're trying to move away from it. Let me know if it makes sense!

Thanks for the contribution


assert rel_info.format == RelationshipDataFormat.MANY_REF
assert rel_info.is_reference
assert errors == []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to avoid MagicMock() and similar mocking libraries in our unit tests. We recently added this rule to our LLM coding rules: .agents/rules/python-testing.md.

The reason is that we'd rather have a less isolated and slower test than one that looks like it protects against regressions but doesn't, because the mocks no longer match the real code.

Existing tests such tests.unit.sdk.spec.test_object.test_validate_object use InfrahubClient() and the client.schema.set_cache() method to do so.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 49feedc: I replaced the mock-based regression setup with a real InfrahubClient and client.schema.set_cache(), following the existing project pattern. The formatter tests pass 21/21, and format, Ruff, ty, and mypy are all clean. Thanks for the guidance!

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/integration/test_enduser_cli.py">

<violation number="1" location="tests/integration/test_enduser_cli.py:235">
P3: This test persists 3 named objects (two BuiltinTag, one InfraRack) in the shared Infrahub test instance and never deletes them, and it extends the shared schema via schema.load without reverting. tests/AGENTS.md requires cleaning up resources in integration tests, and sibling tests in this class follow a create/verify/delete pattern. Add a teardown/delete step for the created objects so repeated runs don't accumulate data in the persistent instance.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/integration/test_enduser_cli.py Outdated
tags.append(tag)

rack_name = f"yaml-round-trip-rack-{suffix}"
rack = await client.create(kind="InfraRack", name=rack_name, tags=tags)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test persists 3 named objects (two BuiltinTag, one InfraRack) in the shared Infrahub test instance and never deletes them, and it extends the shared schema via schema.load without reverting. tests/AGENTS.md requires cleaning up resources in integration tests, and sibling tests in this class follow a create/verify/delete pattern. Add a teardown/delete step for the created objects so repeated runs don't accumulate data in the persistent instance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/integration/test_enduser_cli.py, line 235:

<comment>This test persists 3 named objects (two BuiltinTag, one InfraRack) in the shared Infrahub test instance and never deletes them, and it extends the shared schema via schema.load without reverting. tests/AGENTS.md requires cleaning up resources in integration tests, and sibling tests in this class follow a create/verify/delete pattern. Add a teardown/delete step for the created objects so repeated runs don't accumulate data in the persistent instance.</comment>

<file context>
@@ -208,6 +212,46 @@ def test_create_missing_args(self, base_dataset: None) -> None:
+            tags.append(tag)
+
+        rack_name = f"yaml-round-trip-rack-{suffix}"
+        rack = await client.create(kind="InfraRack", name=rack_name, tags=tags)
+        await rack.save()
+
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the test now deletes the rack and both tags in a finally block, so cleanup also runs when the get/load round trip fails. The schema extension remains scoped to the disposable class-owned Infrahub instance.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/integration/test_enduser_cli.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: object get YAML output is not loadable by object load (many relationships exported as invalid nested data dict)

3 participants