Skip to content

Commit

Permalink
Merge branch 'master' into 580-error-messages-from-python-plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
jesper-friis authored Aug 12, 2023
2 parents 7677f81 + 6b0fc01 commit 8b68959
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
cmake_minimum_required(VERSION 3.14)

project(dlite
VERSION 0.3.21
VERSION 0.3.22
DESCRIPTION "Lightweight data-centric framework for semantic interoperability"
HOMEPAGE_URL "https://github.com/SINTEF/dlite"
LANGUAGES C
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(python_examples
storage_plugin
entity_service
minio_storage
datamodel_as_rdf
)
endif()

Expand Down
5 changes: 5 additions & 0 deletions examples/datamodel_as_rdf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Datamodels as RDF
=================
This example will show how DLite and Pydantic data models can be serialised to RDF.

The example requires that you have Pydantic v1.x installed.
149 changes: 149 additions & 0 deletions examples/datamodel_as_rdf/dataresource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""RDF serialisation of a OTEAPI data resource."""
from typing import Optional

from pydantic import AnyUrl, BaseModel, Field, root_validator

import dlite
from dlite.rdf import from_rdf, to_rdf
from dlite.utils import pydantic_to_instance, pydantic_to_metadata


class HostlessAnyUrl(AnyUrl):
"""AnyUrl, but allow not having a host."""
host_required = False


class ResourceConfig(BaseModel):
"""Resource Strategy Data Configuration.
Important:
Either of the pairs of attributes `downloadUrl`/`mediaType` or
`accessUrl`/`accessService` MUST be specified.
"""

downloadUrl: Optional[HostlessAnyUrl] = Field(
None,
description=(
"""Definition: The URL of the downloadable file in a given
format. E.g. CSV " "file or RDF file.
Usage: `downloadURL` *SHOULD* be used for the URL at which
this distribution is available directly, typically through a
HTTPS GET request or SFTP."""
),
)
mediaType: Optional[str] = Field(
None,
description=(
"""The media type of the distribution as defined by [IANA].
Usage: This property *SHOULD* be used when the media
type of the distribution is defined in [IANA].
[IANA]: https://www.w3.org/TR/vocab-dcat-2/#bib-iana-media-types
"""
),
)
accessUrl: Optional[HostlessAnyUrl] = Field(
None,
description=(
"""A URL of the resource that gives access to a distribution of
the dataset. E.g. landing page, feed, SPARQL endpoint.
Usage: `accessURL` *SHOULD* be used for the URL of a
service or location that can provide access to this
distribution, typically through a Web form, query or API
call.
`downloadURL` is preferred for direct links to downloadable
resources."""
),
)
accessService: Optional[str] = Field(
None,
description=(
"""A data service that gives access to the distribution of the
dataset."""
),
)
license: Optional[str] = Field(
None,
description=(
"A legal document under which the distribution is made available."
),
)
accessRights: Optional[str] = Field(
None,
description=(
"A rights statement that concerns how the distribution is accessed."
),
)
publisher: Optional[str] = Field(
None,
description=(
"The entity responsible for making the resource/item available."
),
)

@root_validator
def ensure_unique_url_pairs(cls, values: "Dict[str, Any]") -> "Dict[str, Any]":
"""Ensure either downloadUrl/mediaType or accessUrl/accessService are
defined.
It's fine to define them all, but at least one complete pair
MUST be specified.
"""
if not (
all(values.get(_) for _ in ["downloadUrl", "mediaType"])
or all(values.get(_) for _ in ["accessUrl", "accessService"])
):
raise ValueError(
"Either of the pairs of attributes downloadUrl/mediaType or "
"accessUrl/accessService MUST be specified."
)
return values


config = {
"downloadUrl": "http://example.com/testdata.csv",
"mediaType": "text/csv",
"license": "CC-BY-4.0",
}
resource_config = ResourceConfig(**config)


# Serialise to RDF
DLiteResourceConfig = pydantic_to_metadata(ResourceConfig)
dlite_config = pydantic_to_instance(DLiteResourceConfig, resource_config)


print(to_rdf(dlite_config, format="turtle", include_meta=False, decode=True))


# Try now to go from RDF to data model
turtle = """
@prefix dm: <http://emmo.info/datamodel/0.0.2#> .
<6de3394f-615f-4b18-bae6-efcff13a2f0f> a dm:DataInstance ;
dm:hasProperty
<5ecf9c76-5997-4923-b085-566753cb59d7#downloadUrl>,
<5ecf9c76-5997-4923-b085-566753cb59d7#license>,
<5ecf9c76-5997-4923-b085-566753cb59d7#mediaType> ;
dm:hasUUID "6de3394f-615f-4b18-bae6-efcff13a2f0f" ;
dm:instanceOf <http://onto-ns.com/meta/0.1/ResourceConfig> .
<5ecf9c76-5997-4923-b085-566753cb59d7#downloadUrl> dm:hasLabel "downloadUrl" ;
dm:hasValue "http://example.com/testdata.csv" ;
dm:instanceOf <http://onto-ns.com/meta/0.1/ResourceConfig#downloadUrl> .
<5ecf9c76-5997-4923-b085-566753cb59d7#license> dm:hasLabel "license" ;
dm:hasValue "CC-BY-4.0" ;
dm:instanceOf <http://onto-ns.com/meta/0.1/ResourceConfig#license> .
<5ecf9c76-5997-4923-b085-566753cb59d7#mediaType> dm:hasLabel "mediaType" ;
dm:hasValue "text/csv" ;
dm:instanceOf <http://onto-ns.com/meta/0.1/ResourceConfig#mediaType> .
"""

inst = from_rdf(data=turtle, format="turtle")
new_resource_config = ResourceConfig(**inst.properties)
3 changes: 3 additions & 0 deletions examples/datamodel_as_rdf/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Main script running all tests in this repo."""
import pydantic_nested
import dataresource
39 changes: 39 additions & 0 deletions examples/datamodel_as_rdf/pydantic_nested.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""RDF serialisation of instance of nested Pydantic data model."""
from typing import List, Optional

from pydantic import BaseModel, Field

import dlite
from dlite.rdf import to_rdf
from dlite.utils import pydantic_to_instance, pydantic_to_metadata


# Some toy nested Pydantic data models
class Foo(BaseModel):
count: int
size: Optional[float] = -1


class Bar(BaseModel):
apple: str = Field(..., description="An apple")
banana: str = Field('European banana', description="A banana")


class Spam(BaseModel):
foo: Foo
bars: List[Bar]


# Create an instance of Spam that we want to serialise as RDF
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])

# Create DLite instance (data models must be created first)
MetaFoo = pydantic_to_metadata(Foo)
MetaBar = pydantic_to_metadata(Bar)
MetaSpam = pydantic_to_metadata(Spam)
spam = pydantic_to_instance(MetaSpam, m)
print(spam)
print("---")

# Serialise to RDF
print(to_rdf(spam, format="turtle", include_meta=True, decode=True))

0 comments on commit 8b68959

Please sign in to comment.