Skip to content

Commit 066ffc3

Browse files
authored
Merge pull request #18 from eccenca/feature/customEntitySchema-CMEM-6095
Add TypedEntitySchema
2 parents 08c05bb + 39583a4 commit 066ffc3

File tree

12 files changed

+752
-495
lines changed

12 files changed

+752
-495
lines changed

.copier-answers.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier
2-
_commit: v7.0.0
2+
_commit: v7.1.0
33
_src_path: gh:eccenca/cmem-plugin-template
44
author_mail: [email protected]
55
author_name: eccenca GmbH

.gitlab-ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
default:
3-
image: docker-registry.eccenca.com/eccenca-python:v3.11.4
3+
image: docker-registry.eccenca.com/eccenca-python:v3.11.9-2
44
# all jobs can be interrupted in case a new commit is pushed
55
interruptible: true
66
before_script:

Taskfile.yaml

+1-3
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,7 @@ tasks:
157157
<<: *preparation
158158
cmds:
159159
# ignore 51358 safety - dev dependency only
160-
# ignore 67599 pip - dev dependency only
161-
# ignore 70612 jinja2 - dev dependency only
162-
- poetry run safety check -i 51358 -i 67599 -i 70612
160+
- poetry run safety check -i 51358
163161

164162
check:ruff:
165163
desc: Complain about everything else

cmem_plugin_base/dataintegration/parameter/resource.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""DI Resource Parameter Type."""
2+
# ruff: noqa: A005
23

34
from typing import Any
45

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Custom entity schema that holds entities of a specific type (e.g. files)"""
2+
3+
4+
def type_uri(suffix: str) -> str:
5+
"""Create a new entity schema type URI."""
6+
return "https://vocab.eccenca.com/di/entity/" + suffix
7+
8+
9+
def path_uri(suffix: str) -> str:
10+
"""Create a new entity schema path."""
11+
return "https://vocab.eccenca.com/di/entity/" + suffix
12+
13+
14+
def instance_uri(suffix: str) -> str:
15+
"""Create a new typed entity instance URI"""
16+
return "https://eccenca.com/di/entity/" + suffix
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""File entities"""
2+
3+
from cmem_plugin_base.dataintegration.entity import Entity, EntityPath
4+
from cmem_plugin_base.dataintegration.typed_entities import instance_uri, path_uri, type_uri
5+
from cmem_plugin_base.dataintegration.typed_entities.typed_entities import (
6+
TypedEntitySchema,
7+
)
8+
9+
10+
class File:
11+
"""A file entity that can be held in a FileEntitySchema."""
12+
13+
def __init__(self, path: str, file_type: str, mime: str | None) -> None:
14+
self.path = path
15+
self.file_type = file_type
16+
self.mime = mime
17+
18+
19+
class LocalFile(File):
20+
"""A file that's located on the local file system."""
21+
22+
def __init__(self, path: str, mime: str | None = None) -> None:
23+
super().__init__(path, "Local", mime)
24+
25+
26+
class ProjectFile(File):
27+
"""A project file"""
28+
29+
def __init__(self, path: str, mime: str | None = None) -> None:
30+
super().__init__(path, "Project", mime)
31+
32+
33+
class FileEntitySchema(TypedEntitySchema[File]):
34+
"""Entity schema that holds a collection of files."""
35+
36+
def __init__(self):
37+
super().__init__(
38+
type_uri=type_uri("File"),
39+
paths=[
40+
EntityPath(path_uri("filePath")),
41+
EntityPath(path_uri("fileType")),
42+
EntityPath(path_uri("mimeType")),
43+
],
44+
)
45+
46+
def to_entity(self, value: File) -> Entity:
47+
"""Create a generic entity from a file"""
48+
return Entity(
49+
uri=instance_uri(value.path),
50+
values=[[value.path], [value.file_type], [value.mime or ""]],
51+
)
52+
53+
def from_entity(self, entity: Entity) -> File:
54+
"""Create a file entity from a generic entity."""
55+
path = entity.values[0][0]
56+
file_type = entity.values[1][0]
57+
mime = entity.values[2][0] if entity.values[2][0] else None
58+
match file_type:
59+
case "Local":
60+
return LocalFile(path, mime)
61+
case "Project":
62+
return ProjectFile(path, mime)
63+
case _:
64+
raise ValueError(f"File '{path}' has unexpected type '{file_type}'.")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Custom entity schema that holds entities of a specific type (e.g. files)"""
2+
3+
from abc import abstractmethod
4+
from collections.abc import Iterator, Sequence
5+
from typing import Generic, TypeVar
6+
7+
from cmem_plugin_base.dataintegration.entity import Entities, Entity, EntityPath, EntitySchema
8+
9+
T = TypeVar("T")
10+
11+
12+
class TypedEntitySchema(EntitySchema, Generic[T]):
13+
"""A custom entity schema that holds entities of a specific type (e.g. files)."""
14+
15+
def __init__(self, type_uri: str, paths: Sequence[EntityPath]):
16+
super().__init__(type_uri, paths)
17+
18+
@abstractmethod
19+
def to_entity(self, value: T) -> Entity:
20+
"""Create a generic entity from a typed entity."""
21+
22+
@abstractmethod
23+
def from_entity(self, entity: Entity) -> T:
24+
"""Create a typed entity from a generic entity.
25+
26+
Implementations may assume that the incoming schema matches the schema expected by
27+
this typed schema, i.e., schema validation is not required.
28+
"""
29+
30+
def to_entities(self, values: Iterator[T]) -> "TypedEntities[T]":
31+
"""Given a collection of values, create a new typed entities instance."""
32+
return TypedEntities(values, self)
33+
34+
def from_entities(self, entities: Entities) -> "TypedEntities[T]":
35+
"""Create typed entities from generic entities.
36+
37+
Returns None if the entities do not match the target type.
38+
"""
39+
# TODO(robert): add validation
40+
# CMEM-6095
41+
if entities.schema.type_uri == self.type_uri:
42+
if isinstance(entities, TypedEntities):
43+
return entities
44+
return TypedEntities(map(self.from_entity, entities.entities), self)
45+
raise ValueError(
46+
f"Expected entities of type '{self.type_uri}' but got '{entities.schema.type_uri}'."
47+
)
48+
49+
50+
class TypedEntities(Entities, Generic[T]):
51+
"""Collection of entities of a particular type."""
52+
53+
def __init__(self, values: Iterator[T], schema: TypedEntitySchema[T]):
54+
super().__init__(map(schema.to_entity, values), schema)
55+
self.values = values
56+
self.schema = schema

cmem_plugin_base/dataintegration/types.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Parameter types."""
2+
# ruff: noqa: A005
23

34
from collections.abc import Iterable
45
from dataclasses import dataclass

0 commit comments

Comments
 (0)