Skip to content

Commit

Permalink
[NEAT-146] ExcelExporter Reference Model. (#365)
Browse files Browse the repository at this point in the history
* tests: failing test

* fix: added reference to container

* refactor: first try of creating reference

* fix: bad serialization

* fix: deep copy

* fix: import

* fix; import try 2
  • Loading branch information
doctrino authored Apr 5, 2024
1 parent e59b199 commit e70c120
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 5 deletions.
5 changes: 4 additions & 1 deletion cognite/neat/rules/exporters/_rules2excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ def export(self, rules: Rules) -> Workbook:
# Remove default sheet named "Sheet"
workbook.remove(workbook["Sheet"])

dumped_rules = rules.model_dump(by_alias=True)
if rules.is_reference:
dumped_rules = rules.reference_self().model_dump(by_alias=True)
else:
dumped_rules = rules.model_dump(by_alias=True)
if rules.is_reference:
# Writes empty reference sheets
empty: dict[str, Any] = {field_alias: None for field_alias in dumped_rules["Metadata"].keys()}
Expand Down
10 changes: 6 additions & 4 deletions cognite/neat/rules/models/_rules/_types/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,14 @@ def as_prop_id(

@property
def versioned_id(self) -> str:
if self.version is None:
if self.version is None and self.property_ is None:
output = self.id
else:
elif self.version is None:
output = f"{self.id}(property={self.property_})"
elif self.property_ is None:
output = f"{self.id}(version={self.version})"
if self.property_:
output = f"{output}:{self.property_}"
else:
output = f"{self.id}(version={self.version}, property={self.property_})"
return output


Expand Down
5 changes: 5 additions & 0 deletions cognite/neat/rules/models/_rules/_types/_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ def _reference_entity_type_before_validator(value: Any | None = None) -> Any:
ReferenceType = Annotated[
ReferenceEntity | rdflib.URIRef | None,
BeforeValidator(_reference_entity_type_before_validator),
PlainSerializer(
lambda v: v.versioned_id if isinstance(v, ReferenceEntity) else str(v),
return_type=str,
when_used="unless-none",
),
]


Expand Down
14 changes: 14 additions & 0 deletions cognite/neat/rules/models/_rules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import math
import sys
import types
from abc import abstractmethod
from collections.abc import Callable, Iterator
from functools import wraps
from typing import Any, ClassVar, Generic, TypeAlias, TypeVar
Expand All @@ -19,8 +20,10 @@

if sys.version_info >= (3, 11):
from enum import StrEnum
from typing import Self
else:
from backports.strenum import StrEnum
from typing_extensions import Self


METADATA_VALUE_MAX_LENGTH = 5120
Expand Down Expand Up @@ -246,6 +249,17 @@ class BaseRules(RuleModel):

metadata: BaseMetadata

@abstractmethod
def reference_self(self) -> Self:
"""
Returns a copy of the rules with reference fields set to itself
For example, if the rules have a property with a reference field, then
the reference field will be set to the property itself. This is used when
exporting a reference model.
"""
raise NotImplementedError


# An sheet entity is either a class or a property.
class SheetEntity(RuleModel):
Expand Down
24 changes: 24 additions & 0 deletions cognite/neat/rules/models/_rules/dms_architect_rules.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc
import math
import re
import sys
import warnings
from collections import defaultdict
from datetime import datetime
Expand Down Expand Up @@ -30,6 +31,7 @@
ExternalIdType,
ParentClassEntity,
PropertyType,
ReferenceEntity,
ReferenceType,
StrListType,
Undefined,
Expand All @@ -46,6 +48,11 @@
if TYPE_CHECKING:
from .information_rules import InformationRules

if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


subclasses = list(CognitePropertyType.__subclasses__())
_PropertyType_by_name: dict[str, type[CognitePropertyType]] = {}
Expand Down Expand Up @@ -197,6 +204,7 @@ def relations_value_type(cls, value: CdfValueType, info: ValidationInfo) -> CdfV

class DMSContainer(SheetEntity):
container: ContainerType = Field(alias="Container")
reference: ReferenceType = Field(alias="Reference", default=None)
constraint: ContainerListType | None = Field(None, alias="Constraint")

def as_container(self, default_space: str, standardize_casing: bool = True) -> dm.ContainerApply:
Expand Down Expand Up @@ -564,6 +572,22 @@ def as_information_architect_rules(self) -> "InformationRules":
def as_domain_expert_rules(self) -> DomainRules:
return _DMSRulesConverter(self).as_domain_rules()

def reference_self(self) -> Self:
new_rules = self.copy(deep=True)
for prop in new_rules.properties:
prop.reference = ReferenceEntity(
prefix=prop.view.prefix, suffix=prop.view_property, version=prop.view.version, property_=prop.property_
)
view: DMSView
for view in new_rules.views:
view.reference = ReferenceEntity(
prefix=view.view.prefix, suffix=view.view.suffix, version=view.view.version
)
container: DMSContainer
for container in new_rules.containers or []:
container.reference = ReferenceEntity(prefix=container.container.prefix, suffix=container.container.suffix)
return new_rules


class _DMSExporter:
"""The DMS Exporter is responsible for exporting the DMSRules to a DMSSchema.
Expand Down
4 changes: 4 additions & 0 deletions cognite/neat/rules/models/_rules/domain_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ def domain_rules_serializer(self, info: SerializationInfo) -> dict[str, Any]:
cls.model_dump(**kwargs) for cls in self.classes or []
] or None
return output

def reference_self(self) -> "DomainRules":
"""DomainRules does not have reference field, so it returns a copy of itself."""
return self.copy(deep=True)
18 changes: 18 additions & 0 deletions cognite/neat/rules/models/_rules/information_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
ParentClassType,
PrefixType,
PropertyType,
ReferenceEntity,
ReferenceType,
SemanticValueType,
StrListType,
Expand Down Expand Up @@ -335,6 +336,23 @@ def as_domain_rules(self) -> DomainRules:
def as_dms_architect_rules(self) -> "DMSRules":
return _InformationRulesConverter(self).as_dms_architect_rules()

def reference_self(self) -> "InformationRules":
new_self = self.copy(deep=True)
for prop in new_self.properties:
prop.reference = ReferenceEntity(
prefix=prop.class_.prefix,
suffix=prop.class_.suffix,
version=prop.class_.version,
property_=prop.property_,
)

for cls_ in new_self.classes:
cls_.reference = ReferenceEntity(
prefix=cls_.class_.prefix, suffix=cls_.class_.suffix, version=cls_.class_.version
)

return new_self


class _InformationRulesConverter:
def __init__(self, information: InformationRules):
Expand Down
15 changes: 15 additions & 0 deletions tests/tests_unit/rules/test_exporters/test_rules2excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@ def test_export_dms_rules_alice_reference(self, alice_rules: DMSRules) -> None:
assert "RefProperties" in workbook.sheetnames
assert "RefContainers" in workbook.sheetnames
assert "RefViews" in workbook.sheetnames

rows = next((rows for rows in workbook["RefProperties"].columns if rows[1].value == "Reference"), None)
assert rows is not None, "Reference column not found in RefProperties sheet"

# Two first rows are headers
reference_count = sum(1 for row in rows[2:] if row.value is not None)
assert reference_count >= len(alice_copy.properties)

rows = next((rows for rows in workbook["RefContainers"].columns if rows[1].value == "Reference"), None)
assert rows is not None, "Reference column not found in RefContainers sheet"
assert sum(1 for row in rows[2:] if row.value is not None) >= len(alice_copy.containers)

rows = next((rows for rows in workbook["RefViews"].columns if rows[1].value == "Reference"), None)
assert rows is not None, "Reference column not found in RefViews sheet"
assert sum(1 for row in rows[2:] if row.value is not None) >= len(alice_copy.views)

0 comments on commit e70c120

Please sign in to comment.