VocExcel - rdflib + pydantic Models

290 views
Skip to first unread message

Nicholas Car

unread,
May 25, 2021, 9:56:46 AM5/25/21
to rdfli...@googlegroups.com
Dear RDFlib devs,

I've just created a small tool to convert SKOS vocabularies presented in Excel to RDF: https://github.com/surroundaustralia/VocExcel

This is hardly a new concept (PoolParty, SKOSPlay! and others have Excel-to-SKOS tools) but we need vocabs of a particular style.

For this list's interest: I'm using RDFlib 6.0.0a0 along with pydantic Model classes which define Python classes mirroring major SKOS objects (ConceptScheme, Concept & Collection) and then convert those class instances to RDF, see https://github.com/surroundaustralia/VocExcel/blob/master/vocexcel/models.py.

Does anyone else pydantic with rdflib and, if so, what equivalent to my to_graph() methods do you use?

Cheers,

Nick

--

______________________________________________________________________________________
kind regards
Dr Nicholas Car
Data Systems Architect
SURROUND Australia Pty Ltd and
SURROUND NZ Limited
Address  Level 9, Nishi Building,
                  2 Phillip Law Street
                  New Acton Canberra 2601
Mobile     +61 477 560 177 
Email       nichol...@surroundaustralia.comWebsite   https://www.surroundaustralia.com

Enhancing Intelligence Within Organisations

delivering evidence that connects decisions to outcomes


Australian-National-University-Logo-1 – ANU Centre for Water and Landscape  Dynamics

Dr Nicholas Car
Adj. Senior Lecturer

Research School of Computer Science

The Australian National University
Canberra ACT Australia

 

 https://orcid.org/0000-0002-8742-7730

https://cs.anu.edu.au/people/nicholas-car 

Copyrights:

SURROUND Australia Pty Ltd is the copyright owner of all original content and attachments.

All rights reserved. 

Confidentiality Notice:
The contents of this email are confidential to the ordinary user of the email address to which
it is addressed, and may also be 
privileged. If you are not the addressee of this email, you
may 
not copy, forward, disclose, or otherwise use it or any part of it or its attachments in any
form whatsoever. If you have received 
this email in error, please email the sender by replying
to this 
message.

Miel Vander Sande

unread,
May 25, 2021, 10:10:41 AM5/25/21
to rdfli...@googlegroups.com
Hi Nicolas,

Thank you for this! This will be extremely handy to distribute thesauri work to my not so RDF savvy colleagues.
I like the work of SHACL play, but it's hard to contribute to the Java codebases. Python is much quicker.
In that respect, some time back I also ported (parts of) their shacl to plant uml generator (https://github.com/viaacode/shacl2plantuml), which I plan to extend towards a SHACL+RDFS datamodel markdown documentation tool that can be used in combination with static compile sites.

Unfortunately no experience with pydantic.

Best,

Miel

Op di 25 mei 2021 om 15:56 schreef Nicholas Car <nichol...@surroundaustralia.com>:
--
http://github.com/RDFLib
---
You received this message because you are subscribed to the Google Groups "rdflib-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rdflib-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rdflib-dev/CAP7nqh1donE5o_Fedup4EbKtfUBJSmGmahUi5O8vq_md9jftJA%40mail.gmail.com.

Donny Winston

unread,
May 25, 2021, 1:11:18 PM5/25/21
to rdflib-dev
Thanks for sharing, Nick! I am using pydantic now to model requests/responses for my FastAPI app, for which modeing with pydantic is helpful for generating an OpenAPI spec. I am planning to use an RDF-based store (TerminusDB) for metadata storage, but I haven't started implementing that yet. The approach of having RDF conversion directly attached to pydantic models, e.g. via a `to_graph()` method as you demonstrate, could be very nice for me as well. I will follow up if I end up using such an approach.

Edmond Chuc

unread,
May 26, 2021, 8:30:28 AM5/26/21
to rdfli...@googlegroups.com
Hi Nick,

Your email reminded me of an RDF ORM (Object RDF Model?) library I wrote a couple of months ago. The initial prototype used Pydantic, but I removed it to align the Python API with Django's ORM. 

It is not released yet, but please see the small example below and provide any feedback.

from rdflib import Graph
from rdflib.namespace import DCTERMS, SKOS, OWL, DCAT, RDFS, RDF

from rdflib_orm.db import models


class Common(models.Model):
provenance = models.CharField(predicate=DCTERMS.provenance, lang='en', required=True)

class Meta:
mixin = True


class ConceptScheme(Common):
class_type = models.IRIField(predicate=RDF.type, value=SKOS.ConceptScheme)
title = models.CharField(predicate=DCTERMS.title, lang='en', required=True)
description = models.CharField(predicate=SKOS.definition, lang='en', required=True)
created = models.DateTimeField(DCTERMS.created, auto_now_add=True)
modified = models.DateTimeField(DCTERMS.modified, auto_now=True)
creator = models.IRIField(predicate=DCTERMS.creator, required=True)
publisher = models.IRIField(predicate=DCTERMS.publisher, required=True)
version = models.CharField(predicate=OWL.versionInfo, required=True)
custodian = models.CharField(predicate=DCAT.contactPoint)
see_also = models.IRIField(predicate=RDFS.seeAlso, many=True)

def __repr__(self):
return f'<{self.__uri__}>'


class Concept(Common):
class_type = models.IRIField(predicate=RDF.type, value=SKOS.Concept)
pref_label = models.CharField(predicate=SKOS.prefLabel, lang='en', required=True)
alt_labels = models.CharField(predicate=SKOS.altLabel, lang='en', many=True)
definition = models.CharField(predicate=SKOS.definition, lang='en', required=True)
children = models.IRIField(predicate=SKOS.narrower, many=True, inverse=SKOS.broader)
other_ids = models.CharField(predicate=SKOS.notation, many=True)
home_vocab_uri = models.IRIField(predicate=RDFS.isDefinedBy)

def __repr__(self):
return f'<{self.__uri__}>'


if __name__ == '__main__':
g = Graph()
g.bind('dcterms', DCTERMS)
g.bind('skos', SKOS)
g.bind('dcat', DCAT)
g.bind('owl', OWL)
models.Database.set_db(g, base_uri='http://example.com/')

concept_scheme = ConceptScheme(
'https://linked.data.gov.au/def/concept_scheme',
title='A concept scheme',
description='A description of this concept scheme.',
creator='https://linked.data.gov.au/org/cgi', # Accepts a URIRef or a string since the field is an IRIField.
publisher='https://linked.data.gov.au/org/ga',
version='0.1.0',
provenance='Generated using Python',
custodian='A custodian name',
see_also=['http://example.com', 'http://another.example.com'] # Accepts a Python list since Field's many=True
)

concept_scheme.save() # Save to store - currently memory store, but works also for remote triplestore.

concept_scheme.title = 'Modified concept scheme title'
concept_scheme.save() # Save changes - we changed the title field.

concept_a = Concept(
uri='https://linked.data.gov.au/def/concept_a',
pref_label='A concept',
alt_labels=['An alt label', 'another alt label'],
definition='Definition of this concept.',
# children= # Optional field, no children on this concept.
other_ids=['123', '456'], # No language tag here :)
home_vocab_uri=concept_scheme, # Reference the Concept Scheme Python object directly.
provenance = 'Generated using RDFLib',
)

concept_a.save()

concept_b = Concept(
uri='https://linked.data.gov.au/def/concept_b',
pref_label='Another concept',
# alt_labels= # Alt labels are optional.
definition='Definition is not optional.',
children=[concept_a], # Reference the previous concept Python object directly. Notice it will also add the inverse property :)
# other_ids= # Optional field again.
home_vocab_uri=concept_scheme,
provenance='Generated using rdflib-orm',
)

concept_b.save()

# Let's do some queries.
queryset = Concept.objects.all()
print(queryset)
# <QuerySet [<https://linked.data.gov.au/def/concept_b>, <https://linked.data.gov.au/def/concept_a>]>

# Get object by URI.
concept_result = Concept.objects.get('https://linked.data.gov.au/def/concept_a')
print(concept_result)
# <https://linked.data.gov.au/def/concept_a>
print(concept_result.pref_label)
# A concept
print(concept_result.definition)
# Definition of this concept.

# Not implemented yet. Something planned for the future.
# Get object by any property, e.g. notation.
concept_result = Concept.objects.get(other_ids=123)

print(len(g)) # 28 triples

g.serialize('output.ttl', format='turtle')
""" # Output of Graph.serialize()
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<https://linked.data.gov.au/def/concept_a> a skos:Concept ;
dcterms:provenance "Generated using RDFLib"@en ;
rdfs:isDefinedBy <https://linked.data.gov.au/def/concept_scheme> ;
skos:altLabel "An alt label",
"another alt label" ;
skos:broader <https://linked.data.gov.au/def/concept_b> ;
skos:definition "Definition of this concept."@en ;
skos:notation "123",
"456" ;
skos:prefLabel "A concept"@en .

<https://linked.data.gov.au/def/concept_b> a skos:Concept ;
dcterms:provenance "Generated using rdflib-orm"@en ;
rdfs:isDefinedBy <https://linked.data.gov.au/def/concept_scheme> ;
skos:definition "Definition is not optional."@en ;
skos:narrower <https://linked.data.gov.au/def/concept_a> ;
skos:prefLabel "Another concept"@en .

<https://linked.data.gov.au/def/concept_scheme> a skos:ConceptScheme ;
dcterms:created "2021-05-26T22:13:50.720468"^^xsd:dateTime ;
dcterms:creator <https://linked.data.gov.au/org/cgi> ;
dcterms:modified "2021-05-26T22:13:50.723468"^^xsd:dateTime ;
dcterms:provenance "Generated using Python"@en ;
dcterms:publisher <https://linked.data.gov.au/org/ga> ;
dcterms:title "Modified concept scheme title"@en ;
rdfs:seeAlso <http://another.example.com>,
<http://example.com> ;
owl:versionInfo "0.1.0" ;
skos:definition "A description of this concept scheme."@en ;
dcat:contactPoint "A custodian name" .
"""

--
Reply all
Reply to author
Forward
0 new messages