Component
Python SDK, infrahubctl
Infrahub SDK version
1.20.1
Current Behavior
Generated protocols type relationship attributes as RelatedNode (the read type). At runtime the SDK accepts assigning either an id string or a node object to set a relationship (node.rel = "<id>" or node.rel = other_node). Under mypy both forms fail with assignment, because the annotation only models what you read back, not what is assignable.
Root cause — generated protocols declare relationships as a single read type, e.g. in infrahub_sdk/protocols.py:
class BuiltinIPAddress(CoreNode):
ip_namespace: RelatedNode
ip_prefix: RelatedNode
A bare attribute annotation uses the same type for get and set, but the runtime __setattr__ accepts str | CoreNode | RelatedNode. mypy therefore rejects the runtime-valid assignment forms. This affects every consumer that sets relationships and runs mypy.
Reproduces on both mypy 2.1.0 and mypy 1.17.1 (not a mypy-version regression).
Expected Behavior
Setting a relationship via an id string or a node — the documented way to establish relationships before save() — should type-check.
Steps to Reproduce
from infrahub_sdk import InfrahubClient
from infrahub_sdk.protocols import BuiltinIPAddress
async def main(client: InfrahubClient) -> None:
addr = await client.get(kind=BuiltinIPAddress, id="x")
addr.ip_prefix = "some-prefix-id" # set by id string
other = await client.get(kind=BuiltinIPAddress, id="y")
addr.ip_namespace = other # set by node
Run mypy (infrahub-sdk 1.20.1):
error: Incompatible types in assignment (expression has type "str", variable has type "RelatedNode") [assignment]
addr.ip_prefix = "some-prefix-id"
^~~~~~~~~~~~~~~~
error: Incompatible types in assignment (expression has type "BuiltinIPAddress", variable has type "RelatedNode") [assignment]
addr.ip_namespace = other
^~~~~
(The same error also commonly appears when assigning RelatedNode.id, i.e. str | None, to another relationship attribute.)
Additional Information
Impact
- Affects all relationship-setting code (generators, data loaders, migrations).
- Forces
# type: ignore[assignment] on every relationship assignment.
Suggested fix
Generate relationship fields using a descriptor that separates get and set types:
T = TypeVar("T", bound=CoreNode)
class RelationshipField(Generic[T]):
def __get__(self, obj: object, owner: type | None = None) -> RelatedNode[T]: ...
def __set__(self, obj: object, value: str | T | RelatedNode[T]) -> None: ...
so generated protocols can declare ip_prefix: RelationshipField[BuiltinIPPrefix], allowing assignment of an id string or a node while still reading back a typed RelatedNode. (This composes naturally with the generic-RelatedNode change proposed in the .peer issue.)
A lighter-weight alternative is widening the annotation to a documented assignable union (RelatedNode | str | CoreNode), at the cost of a looser read type.
Downstream workaround (interim)
Suppress per line, with a comment noting it's a stub limitation:
addr.ip_prefix = "some-prefix-id" # type: ignore[assignment] # SDK stub types rel as read-only RelatedNode
addr.ip_namespace = other # type: ignore[assignment]
There is no clean cast-based alternative, because the mismatch is on the assignment target (the attribute's declared type), not on the value being assigned. Once relationships are generated with separate get/set types, these ignores can be removed.
Filed separately: companion issues for type-abstract on kind= and for RelatedNode.peer losing the peer type on traversal.
Component
Python SDK, infrahubctl
Infrahub SDK version
1.20.1
Current Behavior
Generated protocols type relationship attributes as
RelatedNode(the read type). At runtime the SDK accepts assigning either an id string or a node object to set a relationship (node.rel = "<id>"ornode.rel = other_node). Under mypy both forms fail withassignment, because the annotation only models what you read back, not what is assignable.Root cause — generated protocols declare relationships as a single read type, e.g. in
infrahub_sdk/protocols.py:A bare attribute annotation uses the same type for get and set, but the runtime
__setattr__acceptsstr | CoreNode | RelatedNode. mypy therefore rejects the runtime-valid assignment forms. This affects every consumer that sets relationships and runs mypy.Reproduces on both mypy 2.1.0 and mypy 1.17.1 (not a mypy-version regression).
Expected Behavior
Setting a relationship via an id string or a node — the documented way to establish relationships before
save()— should type-check.Steps to Reproduce
Run mypy (infrahub-sdk 1.20.1):
(The same error also commonly appears when assigning
RelatedNode.id, i.e.str | None, to another relationship attribute.)Additional Information
Impact
# type: ignore[assignment]on every relationship assignment.Suggested fix
Generate relationship fields using a descriptor that separates get and set types:
so generated protocols can declare
ip_prefix: RelationshipField[BuiltinIPPrefix], allowing assignment of an id string or a node while still reading back a typedRelatedNode. (This composes naturally with the generic-RelatedNodechange proposed in the.peerissue.)A lighter-weight alternative is widening the annotation to a documented assignable union (
RelatedNode | str | CoreNode), at the cost of a looser read type.Downstream workaround (interim)
Suppress per line, with a comment noting it's a stub limitation:
There is no clean cast-based alternative, because the mismatch is on the assignment target (the attribute's declared type), not on the value being assigned. Once relationships are generated with separate get/set types, these ignores can be removed.
Filed separately: companion issues for
type-abstractonkind=and forRelatedNode.peerlosing the peer type on traversal.