From 3cfc849b4675540e3bd5ab4bd8fc48aa78ac0c28 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Tue, 12 Aug 2025 15:29:34 +0200 Subject: [PATCH] Trying to use Pydantic to do the heavy work - defined utility classes to serialize into --- .../crate/metadata_entry.py | 7 +- .../crate/owl_restriction.py | 13 +++ .../lib_ro_crate_schema/crate/rdfs_class.py | 18 ++-- .../lib_ro_crate_schema/crate/restriction.py | 28 ++++++- .../src/lib_ro_crate_schema/crate/ro.py | 43 ++++++++++ .../crate/schema_facade.py | 55 ++++++++++++ .../src/lib_ro_crate_schema/crate/type.py | 30 ++++++- .../lib_ro_crate_schema/example/__init__.py | 0 .../lib_ro_crate_schema/example/examples.py | 83 +++++++++++++++++++ 9 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/owl_restriction.py create mode 100644 0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/ro.py create mode 100644 0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/schema_facade.py create mode 100644 0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/__init__.py create mode 100644 0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/examples.py diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/metadata_entry.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/metadata_entry.py index 956d716..82b6da4 100644 --- a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/metadata_entry.py +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/metadata_entry.py @@ -1,8 +1,9 @@ -from pydantic import BaseModel +from lib_ro_crate_schema.crate.ro import RO_ID_LITERAL +from pydantic import BaseModel, Field class MetadataEntry(BaseModel): - id: str - types: set[str] | None = None + id: str = Field(..., serialization_alias=RO_ID_LITERAL) + types: set[str] | None = Field(..., alias="@types") props: dict[str, str] | None = None references : dict[str, list[str]] | None = None children_identifiers: list[str] | None = None diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/owl_restriction.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/owl_restriction.py new file mode 100644 index 0000000..dc7bbd8 --- /dev/null +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/owl_restriction.py @@ -0,0 +1,13 @@ +from typing import Literal +from lib_ro_crate_schema.crate.ro import RO_ID_LITERAL, RoEntity, RO_TYPE_LITERAL +from lib_ro_crate_schema.crate.schema_facade import OWL_RESTRICTION, ON_PROPERTY, OWL_MAX_CARDINALITY, OWL_MIN_CARDINALITY +from pydantic import BaseModel, Field + + + +class OwlRestriction(RoEntity): + id: str = Field(..., serialization_alias=RO_ID_LITERAL) + self_type: str = Field(..., serialization_alias=RO_TYPE_LITERAL) + on_property: str = Field(..., serialization_alias=ON_PROPERTY) + min_cardinality: Literal[0, 1] = Field(...,serialization_alias= OWL_MIN_CARDINALITY) + max_cardinality: Literal[0, 1] = Field(...,serialization_alias= OWL_MAX_CARDINALITY) \ No newline at end of file diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/rdfs_class.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/rdfs_class.py index 73bca43..02e6b38 100644 --- a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/rdfs_class.py +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/rdfs_class.py @@ -1,13 +1,17 @@ -from typing import List +from typing import List, Literal +from lib_ro_crate_schema.crate.schema_facade import EQUIVALENT_CLASS, RDFS_SUBCLASS_OF from lib_ro_crate_schema.crate.type_property import TypeProperty -from pydantic import BaseModel +from lib_ro_crate_schema.crate.ro import RO_ID_LITERAL, RO_TYPE_LITERAL, RoEntity, RoReference +from pydantic import BaseModel, Field -class RdfsClass(BaseModel): - id: str - type: str - subclass_of: List[str] | None = None - ontological_annotations: List[str] | None = None + + +class RdfsClass(RoEntity): + id: str = Field(..., serialization_alias=RO_ID_LITERAL) + self_type: str = Field("rdfs:Class", serialization_alias=RO_TYPE_LITERAL) + subclass_of: RoReference | List[RoReference] | None = Field(..., serialization_alias=RDFS_SUBCLASS_OF) + ontological_annotations: List[str] | None = Field(..., serialization_alias=EQUIVALENT_CLASS) rdfs_properties: List[TypeProperty] | None = None diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/restriction.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/restriction.py index 48929ac..1cebc01 100644 --- a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/restriction.py +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/restriction.py @@ -1,9 +1,29 @@ -from pydantic import BaseModel +from typing import Literal +from lib_ro_crate_schema.crate.owl_restriction import OwlRestriction +from lib_ro_crate_schema.crate.ro import ToRo +from lib_ro_crate_schema.crate.schema_facade import OWL_MIN_CARDINALITY, OWL_MAX_CARDINALITY, OWL_RESTRICTION +from pydantic import BaseModel, Field from .type_property import TypeProperty + class Restriction(BaseModel): id: str - property_type: TypeProperty - min_cardinality: int - max_cardinality: int + property_type: str + min_cardinality: Literal[0, 1] + max_cardinality: Literal[0, 1] + + class Config: + validate_by_name = True + populate_by_name = True + + def to_ro(self): + return OwlRestriction( + id = self.id, + self_type=OWL_RESTRICTION, + on_property=self.property_type, + min_cardinality=self.min_cardinality, + max_cardinality=self.max_cardinality + ) + + diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/ro.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/ro.py new file mode 100644 index 0000000..4723eca --- /dev/null +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/ro.py @@ -0,0 +1,43 @@ +from lib_ro_crate_schema.crate.schema_facade import RDFS_CLASS, OWL_RESTRICTION +from pydantic import BaseModel, Field +from typing import Literal, Protocol, TypeVar, Generic + +RO_TYPE_LITERAL = "@type" +RO_ID_LITERAL = "@id" + +ALLOWED_RO_SCHEMA_TYPES = Literal["rdfs:Class"] | Literal["owl:Restriction"] | Literal["rdfs:Property"] + +# class RoId(BaseModel): +# """ +# This class is a wapper to represent the @id propery +# in JSON-LS +# """ +# id: str = Field(..., serialization_alias=RO_ID_LITERAL) + +class RoReference(BaseModel): + """ + This class encodes the reference to another object through its @id + """ + id: str = Field(..., serialization_alias=RO_ID_LITERAL) + + +class RoEntity(BaseModel): + """ + This is the base class to represent a RO-Crate graph entity + which as minimum members should offer id and its own type as + @id and @type + """ + id: str = Field(..., serialization_alias=RO_ID_LITERAL) + self_type: str = Field(..., serialization_alias=RO_TYPE_LITERAL) + + +T = TypeVar("T") + +class ToRo(Protocol[T]): + """ + This is a protocol (a static duck typed class) + that allows for each class to define what behavior it should implement. + In this way we can for example say that `Type` should implement ToRo[RdfsClass] + """ + def to_ro(self) -> T: + ... \ No newline at end of file diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/schema_facade.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/schema_facade.py new file mode 100644 index 0000000..a6a07ba --- /dev/null +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/schema_facade.py @@ -0,0 +1,55 @@ +# Constants from Java SchemaFacade +from typing import Literal + + + +RDFS_CLASS: Literal["rdfs:Class"] = "rdfs:Class" +RDFS_PROPERTY: Literal["rdfs:Property"] = "rdfs:Property" +EQUIVALENT_CLASS: Literal["owl:equivalentClass"] = "owl:equivalentClass" +EQUIVALENT_CONCEPT: Literal["owl:equivalentProperty"] = "owl:equivalentProperty" +TYPE_RESTRICTION: Literal["owl:restriction"] = "owl:restriction" +RANGE_IDENTIFIER: Literal["schema:rangeIncludes"] = "schema:rangeIncludes" +DOMAIN_IDENTIFIER: Literal["schema:domainIncludes"] = "schema:domainIncludes" +OWL_MIN_CARDINALITY: Literal["owl:minCardinality"] = "owl:minCardinality" +OWL_MAX_CARDINALITY: Literal["owl:maxCardinality"] = "owl:maxCardinality" +OWL_RESTRICTION: Literal["owl:restriction"] = "owl:restriction" +ON_PROPERTY: Literal["owl:onProperty"] = "owl:onProperty" +RDFS_LABEL: Literal["rdfs:label"] = "rdfs:label" +RDFS_COMMENT: Literal["rdfs:comment"] = "rdfs:comment" +RDFS_SUBCLASS_OF: Literal["rdfs:subClassOf"] = "rdfs:subClassOf" + +# Cardinality and other integer literals +MIN_CARDINALITY_MANDATORY: Literal[1] = 1 +MAX_CARDINALITY_SINGLE: Literal[1] = 1 +MAX_CARDINALITY_UNLIMITED: Literal[0] = 0 + +class SchemaFacade: + def __init__(self): + self.types = {} + self.property_types = {} + self.metadata_entries = {} + + def add_type(self, type_obj): + self.types[type_obj.id] = type_obj + + def add_property_type(self, property_obj): + self.property_types[property_obj.id] = property_obj + + def add_metadata_entry(self, entry_obj): + self.metadata_entries[entry_obj.id] = entry_obj + + def serialize_ro_crate_graph(self): + """ + Serialize all types, property types, and metadata entries as RO-Crate @graph list. + """ + graph = [] + # Serialize types + for t in self.types.values(): + graph.append(t.model_dump()) + # Serialize property types + for p in self.property_types.values(): + graph.append(p.model_dump()) + # Serialize metadata entries + for m in self.metadata_entries.values(): + graph.append(m.model_dump()) + return graph diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/type.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/type.py index 5b52a85..37db5b2 100644 --- a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/type.py +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/crate/type.py @@ -1,12 +1,24 @@ from typing import List, Optional, Union + +from lib_ro_crate_schema.crate.rdfs_class import RdfsClass from .literal_type import LiteralType from .restriction import Restriction from .type_property import TypeProperty +from .ro import RoReference, ToRo from pydantic import BaseModel +def serialize_subclass_of(value: List[str] | str | None): + match value: + case None: + return None + case str(val) | [val]: + return RoReference(id=val) + case [vals] as ls: + return [RoReference(id=sc) for sc in ls] + class Type(BaseModel): - id: str + id: str type: str subclass_of: List[str] | None ontological_annotations: List[str] | None @@ -14,3 +26,19 @@ class Type(BaseModel): comment: str label: str restrictions: List[Restriction] | None + + def to_ro(self) -> RdfsClass: + return RdfsClass(id=self.id, + self_type="rdfs:Class", + subclass_of=serialize_subclass_of(self.subclass_of), + ontological_annotations=None, rdfs_properties=None) + + + + # def to_ro(self): + # return RdfsClass( + # id=RoId(id=self.id), + # subclass_of=[RoId(id=i) for i in self.subclass_of if i] if self.subclass_of else [], + # ontological_annotations= + # equivalent_class= + # ) diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/__init__.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/examples.py b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/examples.py new file mode 100644 index 0000000..55dd52e --- /dev/null +++ b/0.2.x/lib/python/lib-ro-crate-schema/src/lib_ro_crate_schema/example/examples.py @@ -0,0 +1,83 @@ +from lib_ro_crate_schema.crate.type import Type +from lib_ro_crate_schema.crate.type_property import TypeProperty +from lib_ro_crate_schema.crate.literal_type import LiteralType +from lib_ro_crate_schema.crate.metadata_entry import MetadataEntry + +rdfs_type = Type( + id="Creator", + type="Type", + subclass_of=["https://schema.org/Thing"], + ontological_annotations=["https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/creator//"], + rdfs_property=None, + comment="", + label="", + restrictions=None +) + +serialized = rdfs_type.to_ro() +print(serialized.model_dump_json(by_alias=True)) + +# # Define properties for Creator +# has_name = TypeProperty( +# id="hasName", +# range_includes_data_type=[LiteralType.STRING] +# ) + +# has_identifier = TypeProperty( +# id="hasIdentifier", +# range_includes_data_type=[LiteralType.STRING], +# ontological_annotations=["https://www.dublincore.org/specifications/dublin-core/dcmi-terms/elements11/identifier/"] +# ) + +# # Define the Creator type with its properties +# creator_type = Type( +# id="Creator", +# type="Type", +# subclass_of=["https://schema.org/Thing"], +# ontological_annotations=["https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/creator//"], +# rdfs_property=[has_name, has_identifier], +# comment="", +# label="", +# restrictions=None +# ) + +# # Define properties for TextResource +# has_date_submitted = TypeProperty( +# id="hasDateSubmitted", +# range_includes_data_type=[LiteralType.DATETIME], +# ontological_annotations=["https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/dateSubmitted/"] +# ) + +# has_creator = TypeProperty( +# id="hasCreator", +# range_includes=[creator_type.id] +# ) + +# # Define the TextResource type with its properties +# text_resource_type = Type( +# id="TextResource", +# type="Type", +# subclass_of=["https://schema.org/Thing"], +# ontological_annotations=["https://www.dublincore.org/specifications/dublin-core/dcmi-terms/dcmitype/Text/"], +# rdfs_property=[has_date_submitted, has_creator], +# comment="", +# label="", +# restrictions=None +# ) + +# # Create metadata entries +# creator_entry = MetadataEntry( +# id="creator1", +# types={creator_type.id}, +# props={"hasName": "John Author", "hasIdentifier": "https://orcid.org/0000-0000-0000-0000"}, +# references={} +# ) + +# text_resource_entry = MetadataEntry( +# id="TextResource1", +# types={text_resource_type.id}, +# props={"hasDate": "2025-01-21T07:12:20Z"}, +# references={"hasCreator": ["creator1"]} +# ) + +# print(creator_entry.model_dump_json()) \ No newline at end of file