Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Io tests #27

Merged
merged 2 commits into from
Jun 28, 2024
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
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