Skip to content

Commit 149b55e

Browse files
committed
Refactor InfrahubNode to avoid dynamic class creation
1 parent 216a6c6 commit 149b55e

File tree

2 files changed

+80
-87
lines changed

2 files changed

+80
-87
lines changed

infrahub_sdk/node/node.py

Lines changed: 79 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
from typing import TYPE_CHECKING, Any
55

66
from ..constants import InfrahubClientMode
7-
from ..exceptions import (
8-
FeatureNotSupportedError,
9-
NodeNotFoundError,
10-
)
7+
from ..exceptions import FeatureNotSupportedError, NodeNotFoundError, SchemaNotFoundError
118
from ..graphql import Mutation, Query
129
from ..schema import GenericSchemaAPI, RelationshipCardinality, RelationshipKind
1310
from ..utils import compare_lists, generate_short_id, get_flat_value
@@ -30,48 +27,6 @@
3027
from ..types import Order
3128

3229

33-
def generate_relationship_property(node: InfrahubNode | InfrahubNodeSync, name: str) -> property:
34-
"""Generates a property that stores values under a private non-public name.
35-
36-
Args:
37-
node (Union[InfrahubNode, InfrahubNodeSync]): The node instance.
38-
name (str): The name of the relationship property.
39-
40-
Returns:
41-
A property object for managing the relationship.
42-
43-
"""
44-
internal_name = "_" + name.lower()
45-
external_name = name
46-
47-
def prop_getter(self: InfrahubNodeBase) -> Any:
48-
return getattr(self, internal_name)
49-
50-
def prop_setter(self: InfrahubNodeBase, value: Any) -> None:
51-
if isinstance(value, RelatedNodeBase) or value is None:
52-
setattr(self, internal_name, value)
53-
else:
54-
schema = [rel for rel in self._schema.relationships if rel.name == external_name][0]
55-
if isinstance(node, InfrahubNode):
56-
setattr(
57-
self,
58-
internal_name,
59-
RelatedNode(
60-
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
61-
),
62-
)
63-
else:
64-
setattr(
65-
self,
66-
internal_name,
67-
RelatedNodeSync(
68-
name=external_name, branch=node._branch, client=node._client, schema=schema, data=value
69-
),
70-
)
71-
72-
return property(prop_getter, prop_setter)
73-
74-
7530
class InfrahubNodeBase:
7631
"""Base class for InfrahubNode and InfrahubNodeSync"""
7732

@@ -86,6 +41,9 @@ def __init__(self, schema: MainSchemaTypesAPI, branch: str, data: dict | None =
8641
self._data = data
8742
self._branch = branch
8843
self._existing: bool = True
44+
self._attribute_data: dict[str, Attribute] = {}
45+
self._relationship_cardinality_many_data: dict[str, RelationshipManager | RelationshipManagerSync] = {}
46+
self._relationship_cardinality_one_data: dict[str, RelatedNode | RelatedNodeSync] = {}
8947

9048
# Generate a unique ID only to be used inside the SDK
9149
# The format if this ID is purposely different from the ID used by the API
@@ -180,12 +138,30 @@ def hfid_str(self) -> str | None:
180138
def _init_attributes(self, data: dict | None = None) -> None:
181139
for attr_schema in self._schema.attributes:
182140
attr_data = data.get(attr_schema.name, None) if isinstance(data, dict) else None
183-
setattr(
184-
self,
185-
attr_schema.name,
186-
Attribute(name=attr_schema.name, schema=attr_schema, data=attr_data),
141+
self._attribute_data[attr_schema.name] = Attribute(
142+
name=attr_schema.name, schema=attr_schema, data=attr_data
187143
)
188144

145+
def __getattr__(
146+
self, name: str
147+
) -> Attribute | RelationshipManager | RelationshipManagerSync | RelatedNode | RelatedNodeSync:
148+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
149+
return self._attribute_data[name]
150+
if "_relationship_cardinality_many_data" in self.__dict__ and name in self._relationship_cardinality_many_data:
151+
return self._relationship_cardinality_many_data[name]
152+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
153+
return self._relationship_cardinality_one_data[name]
154+
155+
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
156+
157+
def __setattr__(self, name: str, value: Any) -> None:
158+
"""Set values for attributes that exist or revert to normal behaviour"""
159+
if "_attribute_data" in self.__dict__ and name in self._attribute_data:
160+
self._attribute_data[name].value = value
161+
return
162+
163+
super().__setattr__(name, value)
164+
189165
def _get_request_context(self, request_context: RequestContext | None = None) -> dict[str, Any] | None:
190166
if request_context:
191167
return request_context.model_dump(exclude_none=True)
@@ -506,7 +482,6 @@ def __init__(
506482
data: Optional data to initialize the node.
507483
"""
508484
self._client = client
509-
self.__class__ = type(f"{schema.kind}InfrahubNode", (self.__class__,), {})
510485

511486
if isinstance(data, dict) and isinstance(data.get("node"), dict):
512487
data = data.get("node")
@@ -535,27 +510,36 @@ def _init_relationships(self, data: dict | None = None) -> None:
535510
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
536511

537512
if rel_schema.cardinality == "one":
538-
setattr(self, f"_{rel_schema.name}", None)
539-
setattr(
540-
self.__class__,
541-
rel_schema.name,
542-
generate_relationship_property(name=rel_schema.name, node=self),
513+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNode(
514+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
543515
)
544-
setattr(self, rel_schema.name, rel_data)
545516
else:
546-
setattr(
547-
self,
548-
rel_schema.name,
549-
RelationshipManager(
550-
name=rel_schema.name,
551-
client=self._client,
552-
node=self,
553-
branch=self._branch,
554-
schema=rel_schema,
555-
data=rel_data,
556-
),
517+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManager(
518+
name=rel_schema.name,
519+
client=self._client,
520+
node=self,
521+
branch=self._branch,
522+
schema=rel_schema,
523+
data=rel_data,
557524
)
558525

526+
def __setattr__(self, name: str, value: Any) -> None:
527+
"""Set values for relationship names that exist or revert to normal behaviour"""
528+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
529+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
530+
if not rel_schemas:
531+
raise SchemaNotFoundError(
532+
identifier=self._schema.kind,
533+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
534+
)
535+
rel_schema = rel_schemas[0]
536+
self._relationship_cardinality_one_data[name] = RelatedNode(
537+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
538+
)
539+
return
540+
541+
super().__setattr__(name, value)
542+
559543
async def generate(self, nodes: list[str] | None = None) -> None:
560544
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
561545

@@ -1036,7 +1020,6 @@ def __init__(
10361020
branch (Optional[str]): The branch where the node resides.
10371021
data (Optional[dict]): Optional data to initialize the node.
10381022
"""
1039-
self.__class__ = type(f"{schema.kind}InfrahubNodeSync", (self.__class__,), {})
10401023
self._client = client
10411024

10421025
if isinstance(data, dict) and isinstance(data.get("node"), dict):
@@ -1066,26 +1049,36 @@ def _init_relationships(self, data: dict | None = None) -> None:
10661049
rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None
10671050

10681051
if rel_schema.cardinality == "one":
1069-
setattr(self, f"_{rel_schema.name}", None)
1070-
setattr(
1071-
self.__class__,
1072-
rel_schema.name,
1073-
generate_relationship_property(name=rel_schema.name, node=self),
1052+
self._relationship_cardinality_one_data[rel_schema.name] = RelatedNodeSync(
1053+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=rel_data
10741054
)
1075-
setattr(self, rel_schema.name, rel_data)
1055+
10761056
else:
1077-
setattr(
1078-
self,
1079-
rel_schema.name,
1080-
RelationshipManagerSync(
1081-
name=rel_schema.name,
1082-
client=self._client,
1083-
node=self,
1084-
branch=self._branch,
1085-
schema=rel_schema,
1086-
data=rel_data,
1087-
),
1057+
self._relationship_cardinality_many_data[rel_schema.name] = RelationshipManagerSync(
1058+
name=rel_schema.name,
1059+
client=self._client,
1060+
node=self,
1061+
branch=self._branch,
1062+
schema=rel_schema,
1063+
data=rel_data,
1064+
)
1065+
1066+
def __setattr__(self, name: str, value: Any) -> None:
1067+
"""Set values for relationship names that exist or revert to normal behaviour"""
1068+
if "_relationship_cardinality_one_data" in self.__dict__ and name in self._relationship_cardinality_one_data:
1069+
rel_schemas = [rel_schema for rel_schema in self._schema.relationships if rel_schema.name == name]
1070+
if not rel_schemas:
1071+
raise SchemaNotFoundError(
1072+
identifier=self._schema.kind,
1073+
message=f"Unable to find relationship schema for '{name}' on {self._schema.kind}",
10881074
)
1075+
rel_schema = rel_schemas[0]
1076+
self._relationship_cardinality_one_data[name] = RelatedNodeSync(
1077+
name=rel_schema.name, branch=self._branch, client=self._client, schema=rel_schema, data=value
1078+
)
1079+
return
1080+
1081+
super().__setattr__(name, value)
10891082

10901083
def generate(self, nodes: list[str] | None = None) -> None:
10911084
self._validate_artifact_definition_support(ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)

infrahub_sdk/query_groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ async def get_group(self, store_peers: bool = False) -> InfrahubNode | None:
9494
if not store_peers:
9595
return group
9696

97-
self.previous_members = group.members.peers # type: ignore[attr-defined]
97+
self.previous_members = group.members.peers # type: ignore[assignment, union-attr]
9898
return group
9999

100100
async def delete_unused(self) -> None:

0 commit comments

Comments
 (0)