Skip to content

Commit

Permalink
Corrected and enhanced IO behaviour
Browse files Browse the repository at this point in the history
* Allowed access to prefix mapping

* Added write_to_string method
  • Loading branch information
b-gehrke authored Jun 28, 2024
1 parent 9732a90 commit 935c1b4
Show file tree
Hide file tree
Showing 13 changed files with 454 additions and 207 deletions.
36 changes: 9 additions & 27 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,6 @@ An exception to this is the the function :func:`PyIndexedOntology.curie <pyhorne



.. note::
Similar to the OWL Manchester Syntax, for the empty prefix the colon can be omitted.

.. code-block:: python
import pyhornedowl
ontology = pyhornedowl.open_ontology("path/to/ontology.owl")
ontology.add_prefix_mapping("", "https://example.com/")
i1 = ontology.iri("https://example.com/test/A")
i2 = ontology.iri(":A")
i3 = ontology.iri("A")
assert i1 == i2 == i3
Prefixes
--------

Expand All @@ -100,12 +82,12 @@ Classes, Individuals, Data- and Objectproperties can be created using convenienc
o = pyhornedowl.open_ontology("path/to/ontology.owl")
o.add_prefix_mapping("", "https://example.com/")
c = o.clazz("A")
op = o.object_property("op")
dp = o.data_property("dp")
ap = o.annotation_property("ap")
i = o.named_individual("I")
n = o.anonymous_individual("n")
c = o.clazz(":A")
op = o.object_property(":op")
dp = o.data_property(":dp")
ap = o.annotation_property(":ap")
i = o.named_individual(":I")
n = o.anonymous_individual(":n")
Write class expressions
Expand All @@ -123,9 +105,9 @@ Instead of writing class expressions as nested constructor calls, some expressio
o = pyhornedowl.PyIndexedOntology()
o.add_prefix_mapping("", "https://example.com/")
A = o.clazz("A")
B = o.clazz("B")
C = o.clazz("C")
A = o.clazz(":A")
B = o.clazz(":B")
C = o.clazz(":C")
r = o.object_property("r")
assert A & B == ObjectIntersectionOf([A, B])
Expand Down
13 changes: 10 additions & 3 deletions gen_pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@
for ann in annotations:
f.write(f" {ann}\n")

f.write(f" def {sign}:\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n ...\n\n')
if callable(member):
f.write(f" def {sign}:\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n ...\n\n')
else:
f.write(f" {sign}\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n\n')


f.write("\n")
elif callable(entry):
if hasattr(entry, "__doc__"):
Expand Down
4 changes: 2 additions & 2 deletions pyhornedowl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import annotations
from .pyhornedowl import PyIndexedOntology, IndexCreationStrategy, open_ontology, open_ontology_from_file, open_ontology_from_string, get_descendants, get_ancestors
from .pyhornedowl import PyIndexedOntology, IndexCreationStrategy, PrefixMapping, open_ontology, open_ontology_from_file, open_ontology_from_string, get_descendants, get_ancestors

__all__ = ["PyIndexedOntology", "IndexCreationStrategy", "open_ontology", "open_ontology_from_file", "open_ontology_from_string", "get_descendants", "get_ancestors"]
__all__ = ["PyIndexedOntology", "IndexCreationStrategy", "PrefixMapping", "open_ontology", "open_ontology_from_file", "open_ontology_from_string", "get_descendants", "get_ancestors"]
63 changes: 50 additions & 13 deletions pyhornedowl/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ class PyIndexedOntology:
"""
Represents a loaded ontology.
"""
def add_default_prefix_names(self) -> None:
"""
Adds the prefix for rdf, rdfs, xsd, and owl
"""
...

def get_id_for_iri(self, iri: str, iri_is_absolute: Optional[bool] = None) -> Optional[str]:
"""
Gets the ID of term by it IRI.
Expand All @@ -30,12 +24,6 @@ class PyIndexedOntology:
"""
...

def add_prefix_mapping(self, iriprefix: str, mappedid: str) -> None:
"""
Adds the prefix `iriprefix`.
"""
...

def set_label(self, iri: str, label: str, *, absolute: Optional[bool] = None) -> None:
"""
Sets the label of a term by iri.
Expand Down Expand Up @@ -107,6 +95,18 @@ class PyIndexedOntology:
"""
...

def save_to_bytes(self, serialization: typing.Literal['owl', 'rdf','ofn', 'owx']) -> bytes:
"""
Saves the ontology to a byte buffer.
"""
...

def save_to_string(self, serialization: typing.Literal['owl', 'rdf','ofn', 'owx']) -> str:
"""
Saves the ontology to a UTF8 string.
"""
...

def save_to_file(self, file_name: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> None:
"""
Saves the ontology to disk. If no serialization is given it is guessed by the file extension.
Expand Down Expand Up @@ -286,6 +286,11 @@ class PyIndexedOntology:
"""
...

prefix_mapping: PrefixMapping
"""
The prefix mapping
"""


class IndexCreationStrategy:
"""
Expand All @@ -305,6 +310,38 @@ class IndexCreationStrategy:
Only create the additional indexes when explicity requested
"""

class PrefixMapping:
def add_default_prefix_names(self) -> None:
"""
Adds the prefix for rdf, rdfs, xsd, and owl
"""
...

def add_prefix(self, iriprefix: str, mappedid: str) -> None:
"""
Adds the prefix `iriprefix`.
"""
...

def remove_prefix(self, iriprefix: str) -> None:
"""
Remove a prefix from the mapping.
"""
...

def expand_curie(self, curie: str) -> str:
"""
Expands a curie. Throws a ValueError if the prefix is invalid or unknown
"""
...

def shring_iri(self, iri: str) -> str:
"""
Shrinks an absolute IRI to a CURIE. Throws a ValueError on failure
"""
...


def open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology:
"""
Opens an ontology from a path or plain text.
Expand Down Expand Up @@ -343,7 +380,7 @@ def get_descendants(onto: PyIndexedOntology, parent: str) -> Set[str]:
...


@deprecated(please use `PyIndexedOntology.get_ancestors` instead)
@deprecated("please use `PyIndexedOntology.get_ancestors` instead")
def get_ancestors(onto: PyIndexedOntology, child: str) -> Set[str]:
"""
Gets all direct and indirect super classes of a class.
Expand Down
81 changes: 53 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,47 @@ use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

use crate::ontology::{get_ancestors, get_descendants, PyIndexedOntology, IndexCreationStrategy};
use crate::ontology::{get_ancestors, get_descendants, IndexCreationStrategy, PyIndexedOntology};

#[macro_use]
mod doc;
mod model;
mod ontology;


mod prefix_mapping;

#[macro_export]
macro_rules! to_py_err {
($message:literal) => {
| error | PyValueError::new_err(format!("{}: {:?}", $message, error))
|error| PyValueError::new_err(format!("{}: {:?}", $message, error))
};
}


fn parse_serialization(serialization: Option<&str>) -> Option<ResourceType> {
match serialization.map(|s| s.to_lowercase()).as_deref() {
Some("owx") => Some(ResourceType::OWX),
Some("ofn") => Some(ResourceType::OFN),
Some("rdf") => Some(ResourceType::RDF),
Some("owl") => Some(ResourceType::RDF),
_ => None
_ => None,
}
}

fn guess_serialization(path: &String, serialization: Option<&str>) -> PyResult<ResourceType> {
parse_serialization(serialization).map(Ok).unwrap_or(
match serialization.map(|s| s.to_lowercase()).as_deref() {
Some(f) => Err(PyValueError::new_err(format!("Unsupported serialization '{}'", f))),
None => Ok(path_type(path.as_ref()).unwrap_or(ResourceType::OWX))
})
Some(f) => Err(PyValueError::new_err(format!(
"Unsupported serialization '{}'",
f
))),
None => Ok(path_type(path.as_ref()).unwrap_or(ResourceType::OWX)),
},
)
}

fn open_ontology_owx<R: BufRead>(
content: &mut R,
b: &Build<Arc<str>>,
index_strategy: IndexCreationStrategy
index_strategy: IndexCreationStrategy,
) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> {
let (o, m) = horned_owl::io::owx::reader::read_with_build(content, &b)?;
Ok((PyIndexedOntology::from_set_ontology(o, index_strategy), m))
Expand All @@ -59,7 +61,7 @@ fn open_ontology_owx<R: BufRead>(
fn open_ontology_ofn<R: BufRead>(
content: &mut R,
b: &Build<Arc<str>>,
index_strategy: IndexCreationStrategy
index_strategy: IndexCreationStrategy,
) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> {
let (o, m) = horned_owl::io::ofn::reader::read_with_build(content, &b)?;
Ok((PyIndexedOntology::from_set_ontology(o, index_strategy), m))
Expand All @@ -68,16 +70,22 @@ fn open_ontology_ofn<R: BufRead>(
fn open_ontology_rdf<R: BufRead>(
content: &mut R,
b: &Build<ArcStr>,
index_strategy: IndexCreationStrategy
index_strategy: IndexCreationStrategy,
) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> {
horned_owl::io::rdf::reader::read_with_build::<ArcStr, ArcAnnotatedComponent, R>(
content,
&b,
&b,
ParserConfiguration {
rdf: RDFParserConfiguration { lax: true },
..Default::default()
},
).map(|(o, _)| (PyIndexedOntology::from_rdf_ontology(o, index_strategy), PrefixMapping::default()))
)
.map(|(o, _)| {
(
PyIndexedOntology::from_rdf_ontology(o, index_strategy),
PrefixMapping::default(),
)
})
}

/// open_ontology_from_file(path: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology
Expand All @@ -86,7 +94,12 @@ fn open_ontology_rdf<R: BufRead>(
///
/// If the serialization is not specified it is guessed from the file extension. Defaults to OWL/XML.
#[pyfunction(signature = (path, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))]
fn open_ontology_from_file(path: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult<PyIndexedOntology> {
fn open_ontology_from_file(
py: Python<'_>,
path: String,
serialization: Option<&str>,
index_strategy: IndexCreationStrategy,
) -> PyResult<PyIndexedOntology> {
let serialization = guess_serialization(&path, serialization)?;

let file = File::open(path)?;
Expand All @@ -97,10 +110,11 @@ fn open_ontology_from_file(path: String, serialization: Option<&str>, index_stra
let (mut pio, mapping) = match serialization {
ResourceType::OFN => open_ontology_ofn(&mut f, &b, index_strategy),
ResourceType::OWX => open_ontology_owx(&mut f, &b, index_strategy),
ResourceType::RDF => open_ontology_rdf(&mut f, &b, index_strategy)
}.map_err(to_py_err!("Failed to open ontology"))?;
ResourceType::RDF => open_ontology_rdf(&mut f, &b, index_strategy),
}
.map_err(to_py_err!("Failed to open ontology"))?;

pio.mapping = mapping;
pio.mapping = Py::new(py, prefix_mapping::PrefixMapping::from(mapping))?;
Ok(pio)
}

Expand All @@ -110,24 +124,29 @@ fn open_ontology_from_file(path: String, serialization: Option<&str>, index_stra
///
/// If no serialization is specified, all parsers are tried until one succeeds
#[pyfunction(signature = (ontology, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))]
fn open_ontology_from_string(ontology: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult<PyIndexedOntology> {
fn open_ontology_from_string(
py: Python<'_>,
ontology: String,
serialization: Option<&str>,
index_strategy: IndexCreationStrategy,
) -> PyResult<PyIndexedOntology> {
let serialization = parse_serialization(serialization);
let mut f = BufReader::new(ontology.as_bytes());

let b = Build::new_arc();

let (imo, mapping) = match serialization {
let (mut pio, mapping) = match serialization {
Some(ResourceType::OFN) => open_ontology_ofn(&mut f, &b, index_strategy),
Some(ResourceType::OWX) => open_ontology_owx(&mut f, &b, index_strategy),
Some(ResourceType::RDF) => open_ontology_rdf(&mut f, &b, index_strategy),
None => open_ontology_ofn(&mut f, &b, index_strategy)
.or_else(|_| open_ontology_rdf(&mut f, &b, index_strategy))
.or_else(|_| open_ontology_owx(&mut f, &b, index_strategy))
}.map_err(to_py_err!("Failed to open ontology"))?;
.or_else(|_| open_ontology_owx(&mut f, &b, index_strategy)),
}
.map_err(to_py_err!("Failed to open ontology"))?;

let mut lo = PyIndexedOntology::from(imo);
lo.mapping = mapping; //Needed when saving
Ok(lo)
pio.mapping = Py::new(py, prefix_mapping::PrefixMapping::from(mapping))?;
Ok(pio)
}

/// open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology
Expand All @@ -139,18 +158,24 @@ fn open_ontology_from_string(ontology: String, serialization: Option<&str>, inde
/// If no serialization is specified the serialization is guessed by the file extension or all parsers are tried
/// until one succeeds.
#[pyfunction(signature = (ontology, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))]
fn open_ontology(ontology: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult<PyIndexedOntology> {
fn open_ontology(
py: Python<'_>,
ontology: String,
serialization: Option<&str>,
index_strategy: IndexCreationStrategy,
) -> PyResult<PyIndexedOntology> {
if Path::exists(ontology.as_ref()) {
open_ontology_from_file(ontology, serialization, index_strategy)
open_ontology_from_file(py, ontology, serialization, index_strategy)
} else {
open_ontology_from_string(ontology, serialization, index_strategy)
open_ontology_from_string(py, ontology, serialization, index_strategy)
}
}

#[pymodule]
fn pyhornedowl(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyIndexedOntology>()?;
m.add_class::<IndexCreationStrategy>()?;
m.add_class::<prefix_mapping::PrefixMapping>()?;

m.add_function(wrap_pyfunction!(open_ontology, m)?)?;
m.add_function(wrap_pyfunction!(open_ontology_from_file, m)?)?;
Expand Down
Loading

0 comments on commit 935c1b4

Please sign in to comment.