Skip to content
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

[PYG-199] 🦔Edge #274

Merged
merged 6 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions cognite/pygen/_core/models/data_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,17 +509,21 @@ def is_edge_class(self) -> bool:
return False

@property
def one_to_many_edges_docs(self) -> str:
edges = [f for f in self.fields_of_type(OneToManyConnectionField) if f.is_write_field]
if len(edges) == 1:
return f"`{edges[0].name}`"
def connections_docs_write(self) -> str:
connections = [f for f in self.fields_of_type(BaseConnectionField) if f.end_classes and f.is_write_field] # type: ignore[type-abstract]
if len(connections) == 0:
raise ValueError("No connections found")
elif len(connections) == 1:
return f"`{connections[0].name}`"
else:
return ", ".join(f"`{field_.name}`" for field_ in edges[:-1]) + f" or `{edges[-1].name}`"
return ", ".join(f"`{field_.name}`" for field_ in connections[:-1]) + f" or `{connections[-1].name}`"

@property
def connections_docs(self) -> str:
connections = [f for f in self.fields_of_type(BaseConnectionField) if f.end_classes] # type: ignore[type-abstract]
if len(connections) == 1:
if len(connections) == 0:
raise ValueError("No connections found")
elif len(connections) == 1:
return f"`{connections[0].name}`"
else:
return ", ".join(f"`{field_.name}`" for field_ in connections[:-1]) + f" and `{connections[-1].name}`"
Expand Down
7 changes: 3 additions & 4 deletions cognite/pygen/_core/models/fields/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,6 @@ def load(
def _is_supported_one_to_many_connection(cls, prop: dm.ConnectionDefinition | dm.MappedProperty) -> bool:
if isinstance(prop, dm.MultiEdgeConnection):
return True
elif isinstance(prop, SingleEdgeConnection) and prop.direction == "inwards" and not prop.edge_source:
return True
elif isinstance(prop, dm.MappedProperty) and isinstance(prop.type, dm.DirectRelation) and prop.type.is_list:
return True
elif isinstance(prop, dm.MultiReverseDirectRelation):
Expand All @@ -297,9 +295,10 @@ def _is_supported_one_to_many_connection(cls, prop: dm.ConnectionDefinition | dm

@classmethod
def _is_supported_one_to_one_connection(cls, prop: dm.ConnectionDefinition | dm.MappedProperty) -> bool:
if isinstance(prop, dm.MappedProperty) and isinstance(prop.type, dm.DirectRelation):
if isinstance(prop, dm.MappedProperty) and isinstance(prop.type, dm.DirectRelation) and not prop.type.is_list:
return True
elif isinstance(prop, SingleEdgeConnection) and prop.direction == "outwards" and not prop.edge_source:
# SingleEdgeConnection with properties are not yet supported
elif isinstance(prop, SingleEdgeConnection) and not prop.edge_source:
return True
elif isinstance(prop, SingleReverseDirectRelation):
return True
Expand Down
2 changes: 1 addition & 1 deletion cognite/pygen/_core/templates/api_class_node.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class {{ api_class.name }}({% if data_class.is_writable %}NodeAPI{% else %}NodeR
"""Add or update (upsert) {{data_class.doc_list_name}}.{% if data_class.has_field_of_type(ft.OneToManyConnectionField) %}

Note: This method iterates through all nodes and timeseries linked to {{ data_class.variable }} and creates them including the edges
between the nodes. For example, if any of {{ data_class.one_to_many_edges_docs }} are set, then these
between the nodes. For example, if any of {{ data_class.connections_docs_write }} are set, then these
nodes as well as any nodes linked to them, and all the edges linking these nodes will be created.{% endif %}

Args:
Expand Down
22 changes: 6 additions & 16 deletions cognite/pygen/_core/templates/data_class_node.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -396,29 +396,19 @@ class {{ data_class.write_name }}({{ data_class.write_base_class }}{% if is_pyda
{% endfor %}{% for field in data_class.primitive_fields_of_type(dm.TimeSeriesReference) %}
if isinstance(self.{{ field.name }}, {% if is_pydantic_v2 %}CogniteTimeSeries{% else %}TimeSeries{% endif %}):
resources.time_series.append(self.{{ field.name }})
{% endfor %}{% for field in data_class.one_to_one_edge_without_properties %}{% if field.edge_direction == 'outwards' %}
{% endfor %}{% for field in data_class.one_to_one_edge_without_properties %}
if self.{{field.name}} is not None:
other_resources = DomainRelationWrite.from_edge_to_resources(
cache,
cache,{% if field.edge_direction == 'outwards' %}
start_node=self,
end_node=self.{{ field.name }},
edge_type=dm.DirectRelationReference("{{ field.edge_type.space }}", "{{ field.edge_type.external_id }}"),
write_none=write_none,
allow_version_increase=allow_version_increase,
)
resources.extend(other_resources)
{% else %}
for {{ field.variable }} in self.{{ field.name }} or []:
other_resources = DomainRelationWrite.from_edge_to_resources(
cache,
start_node={{field.variable}},
end_node=self,
edge_type=dm.DirectRelationReference("{{ field.edge_type.space }}", "{{ field.edge_type.external_id }}"),
end_node=self.{{ field.name }},{% else %}
start_node=self.{{ field.name }},
end_node=self,{% endif %}
edge_type={{ field.edge_type_str }},
write_none=write_none,
allow_version_increase=allow_version_increase,
)
resources.extend(other_resources)
{% endif %}
{% endfor %}
return resources

Expand Down
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Changes are grouped as follows
- The `external_id_factory` will no longer be called when creating a `DomainModelWrite` object
if the `external_id` is set. This is to avoid the `external_id_factory` to overwrite the `external_id`
when creating a new object.
- Edges of type `single_edge_connection` with direction `inwards` no longer creates a list
field.

## [0.99.30] - 24-08-13
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def apply(
"""Add or update (upsert) connection item as.

Note: This method iterates through all nodes and timeseries linked to connection_item_a and creates them including the edges
between the nodes. For example, if any of `outwards` are set, then these
between the nodes. For example, if any of `other_direct`, `outwards` or `self_direct` are set, then these
nodes as well as any nodes linked to them, and all the edges linking these nodes will be created.

Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ConnectionItemEGraphQL(GraphQLCore):
direct_reverse_single: Optional[ConnectionItemDGraphQL] = Field(
default=None, repr=False, alias="directReverseSingle"
)
inwards_single: Optional[list[ConnectionItemDGraphQL]] = Field(default=None, repr=False, alias="inwardsSingle")
inwards_single: Optional[ConnectionItemDGraphQL] = Field(default=None, repr=False, alias="inwardsSingle")
name: Optional[str] = None

@root_validator(pre=True)
Expand Down Expand Up @@ -125,7 +125,9 @@ def as_read(self) -> ConnectionItemE:
if isinstance(self.direct_reverse_single, GraphQLCore)
else self.direct_reverse_single
),
inwards_single=[inwards_single.as_read() for inwards_single in self.inwards_single or []],
inwards_single=(
self.inwards_single.as_read() if isinstance(self.inwards_single, GraphQLCore) else self.inwards_single
),
name=self.name,
)

Expand All @@ -138,7 +140,9 @@ def as_write(self) -> ConnectionItemEWrite:
external_id=self.external_id,
data_record=DataRecordWrite(existing_version=0),
direct_no_source=self.direct_no_source,
inwards_single=[inwards_single.as_write() for inwards_single in self.inwards_single or []],
inwards_single=(
self.inwards_single.as_write() if isinstance(self.inwards_single, GraphQLCore) else self.inwards_single
),
name=self.name,
)

Expand Down Expand Up @@ -166,7 +170,7 @@ class ConnectionItemE(DomainModel):
direct_no_source: Union[str, dm.NodeId, None] = Field(default=None, alias="directNoSource")
direct_reverse_multi: Optional[list[ConnectionItemD]] = Field(default=None, repr=False, alias="directReverseMulti")
direct_reverse_single: Optional[ConnectionItemD] = Field(default=None, repr=False, alias="directReverseSingle")
inwards_single: Optional[list[Union[ConnectionItemD, str, dm.NodeId]]] = Field(
inwards_single: Union[ConnectionItemD, str, dm.NodeId, None] = Field(
default=None, repr=False, alias="inwardsSingle"
)
name: Optional[str] = None
Expand All @@ -178,10 +182,9 @@ def as_write(self) -> ConnectionItemEWrite:
external_id=self.external_id,
data_record=DataRecordWrite(existing_version=self.data_record.version),
direct_no_source=self.direct_no_source,
inwards_single=[
inwards_single.as_write() if isinstance(inwards_single, DomainModel) else inwards_single
for inwards_single in self.inwards_single or []
],
inwards_single=(
self.inwards_single.as_write() if isinstance(self.inwards_single, DomainModel) else self.inwards_single
),
name=self.name,
)

Expand All @@ -205,7 +208,6 @@ def _update_connections(

for instance in instances.values():
if edges := edges_by_source_node.get(instance.as_id()):
inwards_single: list[ConnectionItemD | str | dm.NodeId] = []
for edge in edges:
value: DomainModel | DomainRelation | str | dm.NodeId
if isinstance(edge, DomainRelation):
Expand All @@ -231,9 +233,15 @@ def _update_connections(
if edge_type == dm.DirectRelationReference("pygen-models", "bidirectionalSingle") and isinstance(
value, (ConnectionItemD, str, dm.NodeId)
):
inwards_single.append(value)

instance.inwards_single = inwards_single or None
if instance.inwards_single is None:
instance.inwards_single = value
elif are_nodes_equal(value, instance.inwards_single):
instance.inwards_single = select_best_node(value, instance.inwards_single)
else:
warnings.warn(
f"Expected one edge for 'inwards_single' in {instance.as_id()}."
f"Ignoring new edge {value!s} in favor of {instance.inwards_single!s}."
)

for node in nodes_by_id.values():
if (
Expand Down Expand Up @@ -282,7 +290,7 @@ class ConnectionItemEWrite(DomainModelWrite):
space: str = DEFAULT_INSTANCE_SPACE
node_type: Union[dm.DirectRelationReference, None] = dm.DirectRelationReference("pygen-models", "ConnectionItemE")
direct_no_source: Union[str, dm.NodeId, None] = Field(default=None, alias="directNoSource")
inwards_single: Optional[list[Union[ConnectionItemDWrite, str, dm.NodeId]]] = Field(
inwards_single: Union[ConnectionItemDWrite, str, dm.NodeId, None] = Field(
default=None, repr=False, alias="inwardsSingle"
)
name: Optional[str] = None
Expand Down Expand Up @@ -328,13 +336,12 @@ def _to_instances_write(
resources.nodes.append(this_node)
cache.add(self.as_tuple_id())

edge_type = dm.DirectRelationReference("pygen-models", "bidirectionalSingle")
for inwards_single in self.inwards_single or []:
if self.inwards_single is not None:
other_resources = DomainRelationWrite.from_edge_to_resources(
cache,
start_node=inwards_single,
start_node=self.inwards_single,
end_node=self,
edge_type=edge_type,
edge_type=dm.DirectRelationReference("pygen-models", "bidirectionalSingle"),
write_none=write_none,
allow_version_increase=allow_version_increase,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def apply(
"""Add or update (upsert) windmills.

Note: This method iterates through all nodes and timeseries linked to windmill and creates them including the edges
between the nodes. For example, if any of `blades` or `metmast` are set, then these
between the nodes. For example, if any of `blades`, `metmast`, `nacelle` or `rotor` are set, then these
nodes as well as any nodes linked to them, and all the edges linking these nodes will be created.

Args:
Expand Down
2 changes: 1 addition & 1 deletion examples/omni/_api/connection_item_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def apply(
"""Add or update (upsert) connection item as.

Note: This method iterates through all nodes and timeseries linked to connection_item_a and creates them including the edges
between the nodes. For example, if any of `outwards` are set, then these
between the nodes. For example, if any of `other_direct`, `outwards` or `self_direct` are set, then these
nodes as well as any nodes linked to them, and all the edges linking these nodes will be created.

Args:
Expand Down
41 changes: 24 additions & 17 deletions examples/omni/data_classes/_connection_item_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ConnectionItemEGraphQL(GraphQLCore):
direct_reverse_single: Optional[ConnectionItemDGraphQL] = Field(
default=None, repr=False, alias="directReverseSingle"
)
inwards_single: Optional[list[ConnectionItemDGraphQL]] = Field(default=None, repr=False, alias="inwardsSingle")
inwards_single: Optional[ConnectionItemDGraphQL] = Field(default=None, repr=False, alias="inwardsSingle")
name: Optional[str] = None

@model_validator(mode="before")
Expand Down Expand Up @@ -127,7 +127,9 @@ def as_read(self) -> ConnectionItemE:
if isinstance(self.direct_reverse_single, GraphQLCore)
else self.direct_reverse_single
),
inwards_single=[inwards_single.as_read() for inwards_single in self.inwards_single or []],
inwards_single=(
self.inwards_single.as_read() if isinstance(self.inwards_single, GraphQLCore) else self.inwards_single
),
name=self.name,
)

Expand All @@ -140,7 +142,9 @@ def as_write(self) -> ConnectionItemEWrite:
external_id=self.external_id,
data_record=DataRecordWrite(existing_version=0),
direct_no_source=self.direct_no_source,
inwards_single=[inwards_single.as_write() for inwards_single in self.inwards_single or []],
inwards_single=(
self.inwards_single.as_write() if isinstance(self.inwards_single, GraphQLCore) else self.inwards_single
),
name=self.name,
)

Expand Down Expand Up @@ -168,7 +172,7 @@ class ConnectionItemE(DomainModel):
direct_no_source: Union[str, dm.NodeId, None] = Field(default=None, alias="directNoSource")
direct_reverse_multi: Optional[list[ConnectionItemD]] = Field(default=None, repr=False, alias="directReverseMulti")
direct_reverse_single: Optional[ConnectionItemD] = Field(default=None, repr=False, alias="directReverseSingle")
inwards_single: Optional[list[Union[ConnectionItemD, str, dm.NodeId]]] = Field(
inwards_single: Union[ConnectionItemD, str, dm.NodeId, None] = Field(
default=None, repr=False, alias="inwardsSingle"
)
name: Optional[str] = None
Expand All @@ -180,10 +184,9 @@ def as_write(self) -> ConnectionItemEWrite:
external_id=self.external_id,
data_record=DataRecordWrite(existing_version=self.data_record.version),
direct_no_source=self.direct_no_source,
inwards_single=[
inwards_single.as_write() if isinstance(inwards_single, DomainModel) else inwards_single
for inwards_single in self.inwards_single or []
],
inwards_single=(
self.inwards_single.as_write() if isinstance(self.inwards_single, DomainModel) else self.inwards_single
),
name=self.name,
)

Expand All @@ -207,7 +210,6 @@ def _update_connections(

for instance in instances.values():
if edges := edges_by_source_node.get(instance.as_id()):
inwards_single: list[ConnectionItemD | str | dm.NodeId] = []
for edge in edges:
value: DomainModel | DomainRelation | str | dm.NodeId
if isinstance(edge, DomainRelation):
Expand All @@ -233,9 +235,15 @@ def _update_connections(
if edge_type == dm.DirectRelationReference("pygen-models", "bidirectionalSingle") and isinstance(
value, (ConnectionItemD, str, dm.NodeId)
):
inwards_single.append(value)

instance.inwards_single = inwards_single or None
if instance.inwards_single is None:
instance.inwards_single = value
elif are_nodes_equal(value, instance.inwards_single):
instance.inwards_single = select_best_node(value, instance.inwards_single)
else:
warnings.warn(
f"Expected one edge for 'inwards_single' in {instance.as_id()}."
f"Ignoring new edge {value!s} in favor of {instance.inwards_single!s}."
)

for node in nodes_by_id.values():
if (
Expand Down Expand Up @@ -284,7 +292,7 @@ class ConnectionItemEWrite(DomainModelWrite):
space: str = DEFAULT_INSTANCE_SPACE
node_type: Union[dm.DirectRelationReference, None] = dm.DirectRelationReference("pygen-models", "ConnectionItemE")
direct_no_source: Union[str, dm.NodeId, None] = Field(default=None, alias="directNoSource")
inwards_single: Optional[list[Union[ConnectionItemDWrite, str, dm.NodeId]]] = Field(
inwards_single: Union[ConnectionItemDWrite, str, dm.NodeId, None] = Field(
default=None, repr=False, alias="inwardsSingle"
)
name: Optional[str] = None
Expand Down Expand Up @@ -330,13 +338,12 @@ def _to_instances_write(
resources.nodes.append(this_node)
cache.add(self.as_tuple_id())

edge_type = dm.DirectRelationReference("pygen-models", "bidirectionalSingle")
for inwards_single in self.inwards_single or []:
if self.inwards_single is not None:
other_resources = DomainRelationWrite.from_edge_to_resources(
cache,
start_node=inwards_single,
start_node=self.inwards_single,
end_node=self,
edge_type=edge_type,
edge_type=dm.DirectRelationReference("pygen-models", "bidirectionalSingle"),
write_none=write_none,
allow_version_increase=allow_version_increase,
)
Expand Down
2 changes: 1 addition & 1 deletion examples/windmill/_api/windmill.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def apply(
"""Add or update (upsert) windmills.

Note: This method iterates through all nodes and timeseries linked to windmill and creates them including the edges
between the nodes. For example, if any of `blades` or `metmast` are set, then these
between the nodes. For example, if any of `blades`, `metmast`, `nacelle` or `rotor` are set, then these
nodes as well as any nodes linked to them, and all the edges linking these nodes will be created.

Args:
Expand Down
Loading