Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 25 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,7 @@ RDF import functionality is available after registering `lodkit.RDFImporter` wit

## URI Tools

### uritools.utils

## URIConstructor
### URIConstructor

The `URIConstructor` class provides namespaced URI constructor functionality.

Expand All @@ -292,47 +290,41 @@ make_uri("test") == make_uri("test") # True

## Namespace Tools

### NamespaceGraph
`lodkit.NamespaceGraph` is a simple rdflib.Graph subclass for easy and convenient namespace binding.
### ClosedOntologyNamespace

```python
from lodkit import NamespaceGraph
from rdflib import Namespace
`ClosedOntologyNamespace` is an `rdflib.namespace.ClosedNamespace`-inspired utility class that constructs an immutable (*closed*) mapping of RDF term names to IRIs based on an Ontology or generally an RDF graph source.

class CLSGraph(NamespaceGraph):
crm = Namespace("http://www.cidoc-crm.org/cidoc-crm/")
crmcls = Namespace("https://clscor.io/ontologies/CRMcls/")
clscore = Namespace("https://clscor.io/entity/")
Given a `lodkit.types.GraphParseSource` or an `rdflib.Graph`, a `MappingProxyType[str, rdflib.URIRef]` mapping is created and stored in `ClosedOntologyNamespace.mapping` by

graph = CLSGraph()
1. Querying the RDF source for RDF class and property definitions (RDF/RDFS/OWL class/property type assertions and OWL named individual assertions)

ns_check: bool = all(
ns in map(lambda x: x[0], graph.namespaces())
for ns in ("crm", "crmcls", "clscore")
)
2. Deriving RDF term names by extracting the last IRI component delimited by `#`, `/` or `:` for generating the RDF term name -> IRI mapping.

print(ns_check) # True
```

## ClosedOntologyNamespace, DefinedOntologyNamespace
`lodkit.ClosedOntologyNamespace` and `lodkit.DefinedOntologyNamespace` are `rdflib.ClosedNamespace` and `rdflib.DefinedNameSpace` subclasses
that are able to load namespace members based on an ontology.
Namespace members are accessible as both attributes and items of a given `ClosedOntologyNamespace` instance, i.e. attribute and item access is routed to `ClosedOntologyNamespace.mapping`.
For dictionary operations over the namespace mapping, the public `ClosedOntologyNamespace.mapping` can be accessed directly.

```python
crm = ClosedOntologyNamespace(ontology="./CIDOC_CRM_v7.1.3.ttl")

crm.E39_Actor # URIRef('http://www.cidoc-crm.org/cidoc-crm/E39_Actor')
crm.E39_Author # AttributeError
```
The following example loads a remote Ontology and accesses namespace members using attribute and item lookup.

```python
class crm(DefinedOntologyNamespace):
ontology = "./CIDOC_CRM_v7.1.3.ttl"
from lodkit import ClosedOntologyNamespace

crm = ClosedOntologyNamespace(
source="https://cidoc-crm.org/rdfs/7.1.3/CIDOC_CRM_v7.1.3.rdf"
)

crm.E39_Actor # URIRef('http://www.cidoc-crm.org/cidoc-crm/E39_Actor')
crm.E39_Author # URIRef('http://www.cidoc-crm.org/cidoc-crm/E39_Author') + UserWarning
crm.E92_Spacetime_Volume # URIRef('http://www.cidoc-crm.org/cidoc-crm/E92_Spacetime_Volume')
crm["E52_Time-Span"] # URIRef('http://www.cidoc-crm.org/cidoc-crm/E52_Time-Span')

crm.E21_Author # AttributeError
crm["E21-Person"] # AttributeError
```

> Note that lookup failure for both attribute and item access on `ClosedOntologyNamespace` objects raises an `AttributeError`!

In the case of RDF term names conflicting with class namespace names, the class namespace names take precedence for attribute access; conflicting RDF terms are still accessible via item lookup or through the `ClosedOntologyNamespace.mapping` proxy.

Note that `rdflib.ClosedNamespaces` are meant to be instantiated and `rdflib.DefinedNameSpaces` are meant to be extended,
which is reflected in `lodkit.ClosedOntologyNamespace` and `lodkit.DefinedOntologyNamespace`.
> Note that *currently* `ClosedOntologyNamespace` is a highly dynamic runtime construct and does not support static analysis and IDE completion for namespace entries.


6 changes: 3 additions & 3 deletions lodkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Entry point for LODkit."""

from lodkit.namespace_tools.namespace_graph import NamespaceGraph
from lodkit.namespace_tools.ontology_namespaces import (
from lodkit.namespace_tools.ontology_namespace import (
ClosedOntologyNamespace,
DefinedOntologyNamespace,
EmptySolutionException,
NoSolutionException,
)
from lodkit.rdf_importer import RDFImporter, enable_rdf_import
from lodkit.triple_tools.triple_chain import TripleChain
Expand Down
Empty file removed lodkit/namespace_tools/__init__.py
Empty file.
21 changes: 0 additions & 21 deletions lodkit/namespace_tools/_exceptions.py

This file was deleted.

37 changes: 0 additions & 37 deletions lodkit/namespace_tools/_messages.py

This file was deleted.

40 changes: 0 additions & 40 deletions lodkit/namespace_tools/namespace_graph.py

This file was deleted.

106 changes: 106 additions & 0 deletions lodkit/namespace_tools/ontology_namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from types import MappingProxyType

from lodkit.types import GraphParseSource
from rdflib import Graph, URIRef
from rdflib.query import Result


class NoSolutionException(Exception): ...


class EmptySolutionException(Exception): ... # pragma: no cover


class ClosedOntologyNamespace:
"""Ontology-based Namespace constructor.

ClosedOntologyNamespace allows constructing a namespace
based on an Ontology or generally an RDF graph source.

Given a lodkit.types.GraphParseSource or an rdflib.Graph,
the source is queried for RDF class and property definition assertions.
RDF term names are extracted by splitting the last IRI segment delimited by
'#', '/' or ':' and matching name/IRI pairs are registered in the namespace mapping.

Namespace members are accessible as both attributes and items of a given
`ClosedOntologyNamespace` instance, i.e. attribute and item access is routed
to `ClosedOntologyNamespace.mapping`. For dictionary operations over the namespace mapping,
the public `ClosedOntologyNamespace.mapping` can be accessed directly.

In the case of RDF term names conflicting with class namespace names,
the class namespace names take precedence for attribute access;
conflicting RDF terms are still accessible via item lookup
or through the `ClosedOntologyNamespace.mapping` proxy.

"""

_query = """
prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix owl: <http://www.w3.org/2002/07/owl#>

select distinct ?name ?uri
where {
values ?type {
rdfs:Class
owl:Class

rdf:Property
owl:ObjectProperty
owl:DatatypeProperty
owl:AnnotationProperty

owl:NamedIndividual
}

?uri a ?type .
filter (isIRI(?uri))

bind (replace(str(?uri), "^.*[#/:]", "") AS ?name)
filter (?name != "")
}
"""

def __init__(self, source: GraphParseSource | Graph, *parse_args, **parse_kwargs):
self.source = source

graph: Graph = (
self.source
if isinstance(self.source, Graph)
else Graph().parse(source=self.source, *parse_args, **parse_kwargs)
)
sparql_result: Result = graph.query(self._query)

self.mapping: MappingProxyType[str, URIRef] = self._get_uris(
sparql_result=sparql_result
)

def __repr__(self) -> str: # pragma: no cover
return f"<{self.__class__.__name__} source={self.source!r}>"

def __getattr__(self, value):
return self[value]

def __getitem__(self, key: str) -> URIRef:
try:
return self.mapping[key]
except KeyError:
raise AttributeError(
f"'{self.__class__.__name__}' object has no attribute '{key}'."
)

def _get_uris(self, sparql_result: Result) -> MappingProxyType[str, URIRef]:
_bindings = sparql_result.bindings

match _bindings:
case []:
raise NoSolutionException()
case [{**items}] if not items: # pragma: no cover (unreachable)
raise EmptySolutionException()
case _:
return MappingProxyType(
{
str(binding["name"]): binding["uri"] # type: ignore
for binding in _bindings
}
)
81 changes: 0 additions & 81 deletions lodkit/namespace_tools/ontology_namespaces.py

This file was deleted.

Loading