Skip to content

Commit

Permalink
[PYG-54, PYG-65, PYG-93, PYG-175] 🦸Query Classes (#270)
Browse files Browse the repository at this point in the history
* refactor: shell for new query classes

* refactor: shell for new query method

* tests: added test for new query method

* refactor: moved over Qurey Builder

* refactor: first draft new query functionality

* refactor: simplify

* refactor: prepare for list call

* refactor; implemented list method

* refactor: regen core

* refactor: regen node api correctly

* refactor: adjustments

* refactor: generate node class

* refactor: regnen v2

* refactor: regen v2

* fix; query across edge

* refactor: shell for edge with properties class

* refactor; regen core data cls

* refactor; regen node data cls

* refactor; regen edge data cls

* refactor: regen

* refactor: fix NodeQueryClass

* refactor; also fix

* refactor: regen

* refactor: minor adjustments

* fix: include destination class

* refactor: regen v2

* fix: creation of end_node field

* refactor: query across edges with properties

* refactor: shell for filtering classes

* refactor: added external id and space filter

* refactor: regen

* refactor: implemented all filters

* refactor: finished all filters

* refactor: part 1

* refactor: implemented first primitive type filter

* refactor; setup all filtering classes

* refactor: regen

* refactor: html representation of query

* refactor: regen v2

* refactor: gen data class core

* refactor; handle cycle dependencies

* fix: unpack single step

* refactor: regen

* docs: documented new technique

* refactor: regen v1

* build: changelog + bump
  • Loading branch information
doctrino authored Aug 13, 2024
1 parent c60cc15 commit ac588e7
Show file tree
Hide file tree
Showing 217 changed files with 12,685 additions and 3,305 deletions.
23 changes: 23 additions & 0 deletions cognite/pygen/_core/models/data_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,29 @@ def write_base_class(self) -> str:
else:
return "DomainModelWrite"

@property
def query_cls_name(self) -> str:
"""The name of the class used to create queries for this data class."""
return f"{self.read_name}Query"

@property
def view_id_str(self) -> str:
return f'dm.ViewId("{self.view_id.space}", "{self.view_id.external_id}", "{self.view_id.version}")'

@property
def has_filtering_fields(self) -> bool:
return any(field_.support_filtering for field_ in self.fields_of_type(PrimitiveField))

@property
def filtering_fields(self) -> Iterable[PrimitiveField]:
return (field_ for field_ in self.fields_of_type(PrimitiveField) if field_.support_filtering)

@property
def filtering_import(self) -> str:
return "\n ".join(
f"{cls_name}," for cls_name in sorted(set(field_.filtering_cls for field_ in self.filtering_fields))
)

@property
def typed_read_bases_classes(self) -> str:
if self.implements:
Expand Down
27 changes: 18 additions & 9 deletions cognite/pygen/_core/models/fields/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ class BaseConnectionField(Field, ABC):
edge_type: dm.DirectRelationReference | None
edge_direction: Literal["outwards", "inwards"]
end_classes: list[DataClass] | None
use_node_reference: bool
use_node_reference_in_type_hint: bool
through: dm.PropertyId | None
destination_class: NodeDataClass | None

@property
def data_class(self) -> DataClass:
Expand All @@ -152,6 +153,10 @@ def reverse_property(self) -> BaseConnectionField:
def is_direct_relation(self) -> bool:
return self.edge_type is None and self.through is None

@property
def is_no_source_direct_relation(self) -> bool:
return self.edge_type is None and self.through is None and self.end_classes is None

@property
def is_reverse_direct_relation(self) -> bool:
return self.through is not None
Expand Down Expand Up @@ -230,17 +235,19 @@ def load(
prop.direction if isinstance(prop, dm.EdgeConnection) else "outwards"
)
through = prop.through if isinstance(prop, ReverseDirectRelation) else None
use_node_reference = True
use_node_reference_in_type_hint = True
destination_class: NodeDataClass | None = None
end_classes: list[DataClass] | None
if isinstance(prop, dm.EdgeConnection) and prop.edge_source:
end_classes = [edge_class_by_view_id[prop.edge_source]]
use_node_reference = False
use_node_reference_in_type_hint = False
destination_class = node_class_by_view_id[prop.source]
elif (
isinstance(prop, dm.ConnectionDefinition) or isinstance(prop, dm.MappedProperty)
) and prop.source is not None:
end_classes = [node_class_by_view_id[prop.source]]
if isinstance(prop, ReverseDirectRelation):
use_node_reference = False
use_node_reference_in_type_hint = False
else:
end_classes = None

Expand All @@ -256,7 +263,8 @@ def load(
edge_direction=direction,
description=prop.description,
end_classes=end_classes,
use_node_reference=use_node_reference,
use_node_reference_in_type_hint=use_node_reference_in_type_hint,
destination_class=destination_class,
)
elif cls._is_supported_one_to_one_connection(prop):
return OneToOneConnectionField(
Expand All @@ -269,7 +277,8 @@ def load(
edge_direction=direction,
description=prop.description,
end_classes=end_classes,
use_node_reference=use_node_reference,
use_node_reference_in_type_hint=use_node_reference_in_type_hint,
destination_class=destination_class,
)
else:
return None
Expand Down Expand Up @@ -298,7 +307,7 @@ def _is_supported_one_to_one_connection(cls, prop: dm.ConnectionDefinition | dm.

def as_read_type_hint(self) -> str:
return self._create_type_hint(
[data_class.read_name for data_class in self.end_classes or []], self.use_node_reference
[data_class.read_name for data_class in self.end_classes or []], self.use_node_reference_in_type_hint
)

def as_write_type_hint(self) -> str:
Expand All @@ -308,7 +317,7 @@ def as_write_type_hint(self) -> str:
for data_class in self.end_classes or []
if data_class.is_writable or data_class.is_interface
],
self.use_node_reference,
self.use_node_reference_in_type_hint,
)

def as_graphql_type_hint(self) -> str:
Expand Down Expand Up @@ -346,7 +355,7 @@ def as_write(self) -> str:
if isinstance(self.end_classes, list) and len(self.end_classes) == 1 and not self.end_classes[0].is_writable:
method = "as_id"

return self._create_as_method(method, "DomainModel", self.use_node_reference)
return self._create_as_method(method, "DomainModel", self.use_node_reference_in_type_hint)

def as_read_graphql(self) -> str:
return self._create_as_method("as_read", "GraphQLCore", False)
Expand Down
23 changes: 23 additions & 0 deletions cognite/pygen/_core/models/fields/primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ class PrimitiveField(BasePrimitiveField):

default: str | int | dict | None = None

@property
def support_filtering(self) -> bool:
return isinstance(
self.type_, (dm.Int32, dm.Int64, dm.Float32, dm.Float64, dm.Boolean, dm.Text, dm.Date, dm.Timestamp)
)

@property
def filtering_cls(self) -> str:
if isinstance(self.type_, (dm.Int32, dm.Int64)):
return "IntFilter"
elif isinstance(self.type_, (dm.Float32, dm.Float64)):
return "FloatFilter"
elif isinstance(self.type_, dm.Boolean):
return "BooleanFilter"
elif isinstance(self.type_, dm.Text):
return "StringFilter"
elif isinstance(self.type_, dm.Date):
return "DateFilter"
elif isinstance(self.type_, dm.Timestamp):
return "TimestampFilter"
else:
raise ValueError(f"type {self.type_} is not supported for filtering")

@property
def default_code(self) -> str:
if self.default is None:
Expand Down
20 changes: 14 additions & 6 deletions cognite/pygen/_core/templates/api_class_node.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ from cognite.client import CogniteClient
from cognite.client import data_modeling as dm{% if data_class.has_field_of_type(ft.BasePrimitiveField) %}
from cognite.client.data_classes.data_modeling.instances import InstanceAggregationResultList, InstanceSort{% endif %}

from {{ top_level_package }}.data_classes._core import DEFAULT_INSTANCE_SPACE
from {{ top_level_package }}.data_classes._core import (
DEFAULT_INSTANCE_SPACE,
DEFAULT_QUERY_LIMIT,
NodeQueryStep,
EdgeQueryStep,
QueryBuilder,
)
from {{ top_level_package }}.data_classes import (
DomainModelCore,
DomainModelWrite,
Expand All @@ -25,19 +31,16 @@ from {{ top_level_package }}.data_classes import (
{{ edge_data_class.read_list_name }},{% endfor %}{% for dependency in data_class.dependencies_with_edge_destinations %}{% if dependency.read_name != data_class.read_name %}
{{ dependency.read_name }},{% endif %}{% endfor %}
)
from {{ top_level_package }}.data_classes.{{ data_class.file_name }} import ({% if not data_class.has_only_one_to_many_edges and data_class.has_field_of_type(ft.BasePrimitiveField) %}
from {{ top_level_package }}.data_classes.{{ data_class.file_name }} import (
{{ data_class.query_cls_name }},{% if not data_class.has_only_one_to_many_edges and data_class.has_field_of_type(ft.BasePrimitiveField) %}
{{ data_class.properties_dict_name }},{% endif %}
{{ data_class.filter_name }},
)
from ._core import (
DEFAULT_LIMIT_READ,
DEFAULT_QUERY_LIMIT,
Aggregations,{% if data_class.is_writable %}
NodeAPI{% else %}NodeReadAPI{% endif %},
SequenceNotStr,
NodeQueryStep,
EdgeQueryStep,
QueryBuilder,
){% for class_ in edge_apis %}{% if class_.field.is_write_field %}
from .{{ class_.file_name }} import {{ class_.name }}{% endif %}{% endfor %}{% for class_ in timeseries_apis %}
from .{{ class_.file_name }} import {{ class_.name }}{% endfor %}
Expand Down Expand Up @@ -398,6 +401,11 @@ class {{ api_class.name }}({% if data_class.is_writable %}NodeAPI{% else %}NodeR
)
{% endif %}

def query(self) -> {{ data_class.query_cls_name }}:
"""Start a query for connection item as."""
warnings.warn("The .query is in alpha and is subject to breaking changes without notice.")
return {{ data_class.query_cls_name }}(self._client)

def list(
self,{% for parm in list_method.parameters %}
{{ parm.name }}: {{ parm.annotation }} = {{ parm.default }},{% endfor %}
Expand Down
Loading

0 comments on commit ac588e7

Please sign in to comment.