Skip to content

bug: assigning to a cardinality-many relationship is silently accepted and fails at save() with a misleading error #1152

Description

@PhillSimonds

Component

Python SDK

Infrahub SDK version

1.22.0 (behavior present at least since 1.17.0)

Current Behavior

Setting a cardinality-many relationship via attribute assignment (node.rel = value) is silently accepted at assignment time but corrupts the node, and only fails later at save() with an error that names the wrong thing.

InfrahubNode.__setattr__ (infrahub_sdk/node/node.py:954) only intercepts cardinality-one relationships — it checks name in self._relationship_cardinality_one_data and nothing else. Any assignment to a cardinality-many relationship falls through to super().__setattr__(...), which stores the raw value as a normal instance attribute, shadowing the RelationshipManager held in _relationship_cardinality_many_data.

At save(), _generate_input_data (node.py:480) reads rel.initialized for every relationship. The shadowing value is now a raw object with no .initialized, so:

  • assigning a single node (e.g. acl.entries = entry) → 'InfrahubNode' object has no attribute 'initialized'
  • assigning a list (e.g. acl.entries = [entry]) → 'list' object has no attribute 'initialized'

The error names the object itself (InfrahubNode/list) as the cause, never the relationship or the real cause, so it's very hard to trace back to the assignment line.

There is an asymmetry that makes this particularly surprising: passing the same many-relationship as a list inside the data= dict at create() time works fine (that path builds the manager via _init_relationships). Only attribute assignment after initial construction is unsupported — and it fails silently rather than loudly.

Expected Behavior

Assigning to a cardinality-many relationship should either work or fail immediately and clearly. Ideally the SDK would both:

  1. Support list assignmentnode.rel = [peer1, peer2] should populate the RelationshipManager (mirroring the data= behavior), removing the asymmetry.
  2. Reject non-list values at assignment time with an actionable message, e.g. "'entries' is a cardinality-many relationship; assign a list (or use .add()/.extend()), not a single node."

Either way the failure must happen at the time of assignment, not be deferred to save() with a type-name error.

Steps to Reproduce

# Schema: node kind "Parent" with a cardinality-many relationship "children" -> "Child"
parent = await client.create(kind="Parent", data={"name": "p1"})
child = await client.create(kind="Child", data={"name": "c1"})
await child.save()
await parent.save()

parent.children = child        # single node assigned to a many-relationship
await parent.save()
# -> AttributeError: 'InfrahubNode' object has no attribute 'initialized'

parent.children = [child]      # a list does NOT help either
await parent.save()
# -> AttributeError: 'list' object has no attribute 'initialized'

Working alternatives today: pass children as a list in the create() data=, use parent.children.add(...) / .extend(...), use parent.add_relationships("children", [child.id]), or set the cardinality-one side from the peer.

Additional Information

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugSomething isn't working as expected

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions