Trying to use Pydantic to do the heavy work - defined utility classes to serialize into

This commit is contained in:
Simone Baffelli
2025-08-12 15:29:34 +02:00
parent 89aab35b23
commit 3cfc849b46
9 changed files with 262 additions and 15 deletions
@@ -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
@@ -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)
@@ -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
@@ -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
)
@@ -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:
...
@@ -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
@@ -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=
# )
@@ -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())