-
Notifications
You must be signed in to change notification settings - Fork 11
fix: Print clear error when fetching/editing a relationship on an id-less node #1155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fetching or editing a relationship on a node that has no ID (e.g. a node created locally but not yet saved) now raises a clear error explaining the node needs to be saved first, instead of the generic `At least one filter must be provided to get()` message (for `fetch()`) or sending the caller in a circle between `add()` and `fetch()`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
|
|
||
| from collections import defaultdict | ||
| from collections.abc import Iterable | ||
| from typing import TYPE_CHECKING, Any, Generic, cast | ||
| from typing import TYPE_CHECKING, Any, Generic, NoReturn, cast | ||
|
|
||
| from ..exceptions import ( | ||
| Error, | ||
|
|
@@ -19,6 +19,25 @@ | |
| from .node import InfrahubNode, InfrahubNodeSync | ||
|
|
||
|
|
||
| def _raise_missing_identifier(node: InfrahubNode | InfrahubNodeSync, name: str) -> NoReturn: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this function really needs to access the whole |
||
| """Raise a clear error for fetching/editing a relationship on a node with no ID. | ||
|
|
||
| A relationship can only be fetched or edited once the parent node has an ID to look it up | ||
| by. When it does not, the underlying ``client.get()`` call would otherwise fail with a | ||
| generic "At least one filter must be provided to get()" message that gives the caller no | ||
| hint about the real cause. | ||
|
Comment on lines
+25
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if this documentation is needed, it should go somewhere else. the first line of the docstring and the text of the error message cover the actual description of this function's responsibility |
||
|
|
||
| Raises: | ||
| UninitializedError: Always; the parent node has no ID to look the relationship up by. | ||
|
|
||
| """ | ||
| raise UninitializedError( | ||
| f"Cannot access the '{name}' relationship because the {node._schema.kind} node has no ID to " | ||
| f"look it up by. This usually means the node was created locally but not saved yet — call " | ||
| f".save() on it first (or fetch it from Infrahub) before fetching or editing its relationships." | ||
| ) | ||
|
|
||
|
|
||
| class RelationshipManagerBase(Generic[PeerT]): | ||
| """Base class for :class:`RelationshipManager` and :class:`RelationshipManagerSync`. | ||
|
|
||
|
|
@@ -243,6 +262,8 @@ async def fetch(self) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| exclude = self.node._schema.relationship_names + self.node._schema.attribute_names | ||
| exclude.remove(self.schema.name) | ||
| node = await self.client.get( | ||
|
|
@@ -293,6 +314,8 @@ def add(self, data: str | RelatedNode | dict) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| raise UninitializedError("Must call fetch() on RelationshipManager before editing members") | ||
| new_node = cast( | ||
| "RelatedNode[PeerT]", RelatedNode(schema=self.schema, client=self.client, branch=self.branch, data=data) | ||
|
|
@@ -337,6 +360,8 @@ def remove(self, data: str | RelatedNode | dict) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| raise UninitializedError("Must call fetch() on RelationshipManager before editing members") | ||
| node_to_remove = RelatedNode(schema=self.schema, client=self.client, branch=self.branch, data=data) | ||
|
|
||
|
|
@@ -440,6 +465,8 @@ def fetch(self) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haven't tested this, but it looks like if I create a Node with no ID AND relationship data, then |
||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| exclude = self.node._schema.relationship_names + self.node._schema.attribute_names | ||
| exclude.remove(self.schema.name) | ||
| node = self.client.get( | ||
|
|
@@ -490,6 +517,8 @@ def add(self, data: str | RelatedNodeSync | dict) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| raise UninitializedError("Must call fetch() on RelationshipManager before editing members") | ||
| new_node = cast( | ||
| "RelatedNodeSync[PeerTSync]", | ||
|
|
@@ -535,6 +564,8 @@ def remove(self, data: str | RelatedNodeSync | dict) -> None: | |
|
|
||
| """ | ||
| if not self.initialized: | ||
| if not self.node.id: | ||
| _raise_missing_identifier(self.node, self.name) | ||
| raise UninitializedError("Must call fetch() on RelationshipManager before editing members") | ||
| node_to_remove = RelatedNodeSync(schema=self.schema, client=self.client, branch=self.branch, data=data) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if a node is initialized with no UUID, but an HFID? would an error still be raised in that case? probably worth adding as a test to confirm whatever the expected behavior should be