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

[NEAT-329]🧱Support Immutable #555

Merged
merged 15 commits into from
Jul 21, 2024
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: run-explorer run-tests run-linters build-ui build-python build-docker run-docker compose-up
version="0.87.3"
version="0.87.4"
run-explorer:
@echo "Running explorer API server..."
# open "http://localhost:8000/static/index.html" || true
Expand Down
2 changes: 1 addition & 1 deletion cognite/neat/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.87.3"
__version__ = "0.87.4"
10 changes: 5 additions & 5 deletions cognite/neat/graph/loaders/_rdf2dms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
from cognite.client import data_modeling as dm
from cognite.client.data_classes.capabilities import Capability, DataModelInstancesAcl
from cognite.client.data_classes.data_modeling import ViewId
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
from cognite.client.data_classes.data_modeling.ids import InstanceId
from cognite.client.data_classes.data_modeling.views import SingleEdgeConnection
from cognite.client.exceptions import CogniteAPIError
from pydantic import ValidationInfo, create_model, field_validator
from pydantic.main import Model
from pydantic import BaseModel, ValidationInfo, create_model, field_validator

from cognite.neat.graph._tracking import LogTracker, Tracker
from cognite.neat.graph.issues import loader as loader_issues
Expand Down Expand Up @@ -140,7 +140,7 @@ def write_to_file(self, filepath: Path) -> None:

def _create_validation_classes(
self, view: dm.View
) -> tuple[type[Model], dict[str, dm.EdgeConnection], NeatIssueList]:
) -> tuple[type[BaseModel], dict[str, dm.EdgeConnection], NeatIssueList]:
issues = NeatIssueList[NeatIssue]()
field_definitions: dict[str, tuple[type, Any]] = {}
edge_by_property: dict[str, dm.EdgeConnection] = {}
Expand Down Expand Up @@ -168,7 +168,7 @@ def _create_validation_classes(
if data_type == Json:
json_fields.append(prop_name)
python_type = data_type.python
if prop.type.is_list:
if isinstance(prop.type, ListablePropertyType) and prop.type.is_list:
python_type = list[python_type]
default_value: Any = prop.default_value
if prop.nullable:
Expand Down Expand Up @@ -223,7 +223,7 @@ def _create_node(
self,
identifier: str,
properties: dict[str, list[str]],
pydantic_cls: type[Model],
pydantic_cls: type[BaseModel],
view_id: dm.ViewId,
) -> dm.InstanceApply:
created = pydantic_cls.model_validate(properties)
Expand Down
9 changes: 7 additions & 2 deletions cognite/neat/legacy/rules/exporters/_rules2dms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import ClassVar, Literal, cast, no_type_check

import yaml
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType

from cognite.neat.legacy.rules.models.value_types import ValueTypeMapping

Expand Down Expand Up @@ -278,13 +279,17 @@ def containers_from_rules(cls, rules: Rules) -> dict[str, ContainerApply]:
existing_property, default_value=None
)

# scenario: property hold multiple values -> set is_list to True
# scenario: property holds multiple values -> set is_list to True
if (
not isinstance(existing_property.type, DirectRelation)
and not isinstance(api_container_property.type, DirectRelation)
and isinstance(existing_property.type, ListablePropertyType)
and isinstance(api_container_property.type, ListablePropertyType)
and existing_property.type.is_list != api_container_property.type.is_list
):
containers[container_id].properties[container_property_id].type.is_list = True
type_ = containers[container_id].properties[container_property_id].type
if isinstance(type_, ListablePropertyType):
type_.is_list = True

if errors:
raise ExceptionGroup("Properties value types have been redefined! This is prohibited! Aborting!", errors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from pydantic import BaseModel, ConfigDict, Field, create_model
from pydantic._internal._model_construction import ModelMetaclass
from rdflib import Graph, URIRef
from typing_extensions import TypeAliasType

from cognite.neat.legacy.graph.loaders.core.rdf_to_assets import NeatMetadataKeys
from cognite.neat.legacy.graph.transformations.query_generator.sparql import build_construct_query, triples2dictionary
Expand All @@ -30,8 +29,8 @@

UTC = timezone.utc

EdgeOneToOne: TypeAlias = TypeAliasType("EdgeOneToOne", str) # type: ignore[valid-type]
EdgeOneToMany: TypeAlias = TypeAliasType("EdgeOneToMany", list[str]) # type: ignore[valid-type]
EdgeOneToOne: TypeAlias = str # type: ignore[valid-type]
EdgeOneToMany: TypeAlias = list[str] # type: ignore[valid-type]


def default_model_configuration(
Expand Down
5 changes: 4 additions & 1 deletion cognite/neat/legacy/rules/importers/_dms2rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SingleHopConnectionDefinition,
View,
)
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
from cognite.client.data_classes.data_modeling.ids import DataModelIdentifier, ViewId

from cognite.neat.legacy.rules.models.tables import Tables
Expand Down Expand Up @@ -129,7 +130,9 @@ def to_tables(self) -> dict[str, pd.DataFrame]:

max_count: str | float = "1"
if isinstance(prop, SingleHopConnectionDefinition) or (
isinstance(prop, MappedProperty) and prop.type.is_list
isinstance(prop, MappedProperty)
and isinstance(prop.type, ListablePropertyType)
and prop.type.is_list
):
max_count = float("nan")

Expand Down
11 changes: 10 additions & 1 deletion cognite/neat/rules/importers/_dms2rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from cognite.client import data_modeling as dm
from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier
from cognite.client.data_classes.data_modeling.containers import BTreeIndex, InvertedIndex
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
from cognite.client.data_classes.data_modeling.views import (
MultiEdgeConnectionApply,
MultiReverseDirectRelationApply,
Expand Down Expand Up @@ -345,6 +346,7 @@ def _create_dms_property(
value_type=value_type,
is_list=self._get_is_list(prop),
nullable=self._get_nullable(prop),
immutable=self._get_immutable(prop),
default=self._get_default(prop),
container=ContainerEntity.from_id(prop.container) if isinstance(prop, dm.MappedPropertyApply) else None,
container_property=prop.container_property_identifier if isinstance(prop, dm.MappedPropertyApply) else None,
Expand Down Expand Up @@ -401,9 +403,16 @@ def _get_nullable(self, prop: ViewPropertyApply) -> bool | None:
else:
return None

def _get_immutable(self, prop: ViewPropertyApply) -> bool | None:
if isinstance(prop, dm.MappedPropertyApply):
return self._container_prop_unsafe(prop).immutable
else:
return None

def _get_is_list(self, prop: ViewPropertyApply) -> bool | None:
if isinstance(prop, dm.MappedPropertyApply):
return self._container_prop_unsafe(prop).type.is_list
prop_type = self._container_prop_unsafe(prop).type
return isinstance(prop_type, ListablePropertyType) and prop_type.is_list
elif isinstance(prop, MultiEdgeConnectionApply | MultiReverseDirectRelationApply):
return True
elif isinstance(prop, SingleEdgeConnectionApply | SingleReverseDirectRelationApply):
Expand Down
10 changes: 8 additions & 2 deletions cognite/neat/rules/models/dms/_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from cognite.client.data_classes import data_modeling as dm
from cognite.client.data_classes.data_modeling.containers import BTreeIndex
from cognite.client.data_classes.data_modeling.data_types import ListablePropertyType
from cognite.client.data_classes.data_modeling.views import (
SingleEdgeConnectionApply,
SingleReverseDirectRelationApply,
Expand Down Expand Up @@ -282,11 +283,16 @@ def _create_containers(
type_cls = prop.value_type.dms
else:
type_cls = dm.DirectRelation

type_ = type_cls(is_list=prop.is_list or False)
type_: dm.PropertyType
if issubclass(type_cls, ListablePropertyType):
type_ = type_cls(is_list=prop.is_list or False)
else:
type_ = type_cls()
container.properties[prop.container_property] = dm.ContainerProperty(
type=type_,
# If not set, nullable is True and immutable is False
nullable=prop.nullable if prop.nullable is not None else True,
immutable=prop.immutable if prop.immutable is not None else False,
default_value=prop.default,
name=prop.name,
description=prop.description,
Expand Down
1 change: 1 addition & 0 deletions cognite/neat/rules/models/dms/_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class DMSProperty(SheetEntity):
connection: Literal["direct", "edge", "reverse"] | None = Field(None, alias="Connection")
value_type: DataType | ViewPropertyEntity | ViewEntity | DMSUnknownEntity = Field(alias="Value Type")
nullable: bool | None = Field(default=None, alias="Nullable")
immutable: bool | None = Field(default=None, alias="Immutable")
is_list: bool | None = Field(default=None, alias="Is List")
default: str | int | dict | None = Field(None, alias="Default")
reference: URLEntity | ReferenceEntity | None = Field(default=None, alias="Reference", union_mode="left_to_right")
Expand Down
3 changes: 3 additions & 0 deletions cognite/neat/rules/models/dms/_rules_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class DMSPropertyInput:
description: str | None = None
connection: Literal["direct", "edge", "reverse"] | None = None
nullable: bool | None = None
immutable: bool | None = None
is_list: bool | None = None
default: str | int | dict | None = None
reference: str | None = None
Expand Down Expand Up @@ -119,6 +120,7 @@ def load(
description=data.get("description"),
connection=data.get("connection"),
nullable=data.get("nullable"),
immutable=data.get("immutable"),
is_list=data.get("is_list"),
default=data.get("default"),
reference=data.get("reference"),
Expand Down Expand Up @@ -154,6 +156,7 @@ def dump(self, default_space: str, default_version: str) -> dict[str, Any]:
"Description": self.description,
"Connection": self.connection,
"Nullable": self.nullable,
"Immutable": self.immutable,
"Is List": self.is_list,
"Default": self.default,
"Reference": self.reference,
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Changes are grouped as follows:
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [0.87.4] - 22-07-24
### Added
- Support for `Immutable` in `DMSRules`

## [0.87.3] - 18-07-24
### Added
- Handling of missing parents when generating assets
Expand Down
Binary file modified docs/artifacts/rules/cdf-dms-architect-alice.xlsx
Binary file not shown.
Binary file modified docs/artifacts/rules/dms-addition-svein-harald.xlsx
Binary file not shown.
Binary file modified docs/artifacts/rules/dms-analytics-olav.xlsx
Binary file not shown.
Binary file modified docs/artifacts/rules/dms-architect-rules-template.xlsx
Binary file not shown.
Binary file modified docs/artifacts/rules/dms-rebuild-olav.xlsx
Binary file not shown.
Loading
Loading