-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from ontology-tools/documentation
Added documentation
- Loading branch information
Showing
5 changed files
with
262 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
Details | ||
======= | ||
|
||
Py-Horned-OWL provides Python bindings via PyO3. This bridge presents some challenges. | ||
|
||
Mapping Rust ADT enums to Python classes | ||
---------------------------------------- | ||
While Horned-OWL C like structs can be directly translated to Python classes bridging Rust ADTs to Python is not fully supported by PyO3 yet (`https://github.com/PyO3/pyo3/issues/417 <https://github.com/PyO3/pyo3/issues/417>`_). Hence Py-Horned-OWL uses a custom design. | ||
|
||
|
||
For each tuple structs, a C like struct with fields ``first`` and ``second`` (depending on their airity) is generated. They are then directly translated to Python classes. | ||
|
||
|
||
For each enum type ``enum E ( V1(...), V2{...}, ... )`` struct types ``E(E_Inner), V1(...), V2{...}, ...`` and an internal enum type ``enum E_Inner(V1(V1), V2(V2), ...)`` are generated. The structs ``V1, V2, ...`` are translated to Python classes. | ||
The struct ``E`` manually implements the ``FromPyObject`` and ``IntoPy`` traits to hide the inner enum. Most notably, ``E`` is **not** exposed to Python. Instead, Py-Horned-OWL exposes ``E`` a ``typing.Union`` consisting of all variants ``E = typing.Union[V1, V2, ...]``. Ideally ``E`` would be a Python class as well and ``V1, V2, ...`` would be subclasses of ``E``. Unfortunately, class hierarchies are not supported by PyO3 to a level where this would be easily possible. The current approach, however, still allows for type hints and even runtime instance checking (as ``isinstance(V1(...), E) == True``). | ||
|
||
|
||
Wrapper types | ||
------------- | ||
|
||
Exposing Rust datatypes to Python via PyO3 requires implementing certrain traits ``PyClass`` or ``FromPyObjectBound`` (or to use their macros like ``#[pyclass]``). Due to Rusts `orphan rules <https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules>`_ the traits cannot be directly implemented for the datatypes in Horned-OWL. Therefore, each Horned-OWL type is wrapped (new type idiom). For each type ``T`` the procedure would be the same: | ||
|
||
#. Define the wrapper type ``T_W`` depending on the Rust data type | ||
#. Conversion from ``hornedowl::model::T`` to ``T_W`` and vice versa | ||
#. Add python methods e.g. for creating, string conversion, equality, and hash. | ||
|
||
As the tasks are very repetitive, macros are defined. The main macro is ``wrapped`` which takes Rust struct and enum definitions as defined in Horned-OWL and produces the wrapper types and implementations. It accepts custom arguments to control the wrapped datatypes: | ||
|
||
``transparent pub enum ...`` | ||
Only valid on enums of the form ``pub enum E{ V1(V1), V2(V2), ... }``. It prevents the creation of intermediate types for ``V1, V2, ...``. | ||
|
||
``#[transparent] V`` | ||
Only valid on variants of the form ``V1(V1)``. It also prevents the creation of an intermediate type. | ||
|
||
``#[suffixed] pub enum ...`` | ||
For an enum ``pub enum E{ V1(...), V2{...}}`` the ``suffixed`` argument creates structs (and python classes) by concatenating enum and variant name (e.g. ``SimpleLiteral``). For some datatypes this makes their intention clearer and avoids name conflicts (e.g. ``Language`` vs. ``LanguageLiteral``). | ||
|
||
|
||
To ensure the same interface in the macros, the ``FromCompatible`` trait is introduced as a wrapper around the ``From`` trait. This allows to redefine the conversion from data types from the Rust standard library e.g. Box or Vec for the wrapper types in Py-Horned-OWL. | ||
|
||
Similarly, newtypes are defined for ``String``, ``Vec``, ``Box``, and ``BTreeSet``. | ||
|
||
|
||
Python documentation | ||
-------------------- | ||
Unfortunately, the documentation of rust datatypes vanishes at compile time. Therefore, we cannot simply copy the documentation of Horned-OWL data types to the wrapped data types. But a helper script extracts the doc strings from Horned-OWL and provides them in a form of a macro. Additionally, Py-Horned-OWL datatypes and functions follow the convention to include their signature as the first line of their documentation. | ||
|
||
Python stubs / type hints | ||
------------------------- | ||
Currently, PyO3 does not output python type hints. So, all of the asserted type information in Rust is lost during the bridging to Python. To counter it, Py-Horned-OWL datatypes implement the trait ``ToPyi`` which contains a ``pyi(module: Option<String>) -> String`` function which returns a python stub. A helper script ``gen_pyi.py`` then iterates over all members and generates python stub files. If no such method is defined, the script searches the first line of the ``__doc__`` field for a signature and uses it instead. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,9 @@ Contents | |
.. toctree:: | ||
:maxdepth: 2 | ||
|
||
quickstart | ||
installation | ||
usage | ||
details | ||
api | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
Installation | ||
============ | ||
|
||
With pip | ||
--------- | ||
The simplest and quickest way to install Py-Horned-OWL is via pip. | ||
|
||
.. code-block:: console | ||
(.venv) $ pip install py-horned-owl | ||
From source | ||
----------- | ||
Clone the repository from github | ||
|
||
.. code-block:: console | ||
$ git checkout https://github.com/ontology-tools/py-horned-owl.git | ||
$ cd py-horned-owl | ||
It is recommended to use a virtual environment | ||
|
||
.. code-block:: console | ||
$ python3 -m virtualenv .venv | ||
$ source .venv/bin/activate | ||
Install the build tool maturin and use ``maturin develop`` to build and install the project in the current virtual environment, or ``maturin build`` to create wheels. Consult the matuin documentation for further details. | ||
|
||
.. code-block:: console | ||
(.venv) $ pip install maturin | ||
(.venv) $ maturin develop | ||
(.venv) $ maturin build | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
Quickstart | ||
========== | ||
|
||
Installation | ||
------------ | ||
|
||
To use py-horned-owl, first install it using pip: | ||
|
||
.. code-block:: console | ||
(.venv) $ pip install py-horned-owl | ||
Work with ontologies | ||
-------------------- | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
ontology = pyhornedowl.open_ontology("<path/to/ontology>") | ||
# Get all axioms | ||
axioms = ontology.get_axioms() | ||
# Add a prefix | ||
ontology.add_prefix_mapping(":", "https://example.com/test#") | ||
# Construct an axiom | ||
from pyhornedowl.model import * | ||
axiom = SubClassOf( | ||
o.clazz(':Child'), | ||
ObjectSomeValuesFrom( | ||
o.object_property(':has_parent'), | ||
o.clazz(':Human') | ||
) | ||
) | ||
# Add the axiom | ||
ontology.add_axiom(axiom) | ||
# Save the ontology | ||
o.save_to_file("output.owx") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,137 @@ | ||
Usage | ||
===== | ||
|
||
Installation | ||
------------ | ||
|
||
To use py-horned-owl, first install it using pip: | ||
Open an existing ontology | ||
------------------------- | ||
|
||
.. code-block:: console | ||
To open an ontology use the :func:`~pyhornedowl.open_ontology` function. It guesses the serialization of the ontology by the file extension or tries all parsers. Alternatively, specify the serialization format explicitely with the ``serialization`` option. | ||
|
||
(.venv) $ pip install py-horned-owl | ||
.. code-block:: python | ||
import pyhornedowl | ||
rdf_ontology = pyhornedowl.open_ontology("path/to/ontology.owl") | ||
owx_ontology = pyhornedowl.open_ontology("path/to/ontology.owx") | ||
ofn_ontology = pyhornedowl.open_ontology("path/to/ontology", serialization='ofn') | ||
Work with ontologies | ||
-------------------- | ||
Save an ontology | ||
---------------- | ||
|
||
Use the :func:`PyIndexedOntology.save_to_file <pyhornedowl.PyIndexedOntology.save_to_file>` function to write the ontology to a file. Again, the serialization is guessed by the file extension and defaults to OWL/XML. Alternatively, specify the serialization format explicitely with the ``serialization`` option. | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
ontology = pyhornedowl.open_ontology("<path/to/ontology>") | ||
# Get all axioms | ||
axioms = ontology.get_axioms() | ||
# Construct an axiom | ||
from pyhornedowl.model import * | ||
axiom = SubClassOf( | ||
Class(IRI.parse(':Child')), | ||
ObjectSomeValuesFrom( | ||
ObjectProperty(IRI.parse(':has_parent')), | ||
Class(IRI.parse(':Human')) | ||
) | ||
) | ||
# Add the axiom | ||
ontology.add_axiom(axiom) | ||
ontology = pyhornedowl.open_ontology("path/to/ontology.owl") | ||
ontology.save_to_file("path/to/ontology.owl") | ||
ontology.save_to_file("path/to/ontology.owx") | ||
ontology.save_to_file("path/to/ontology", serialization='ofn') | ||
IRIs and CURIEs | ||
-------------------------- | ||
The preferred way to create IRIs is through an ontology instance as it enables Horned-OWLs caching mechanism. Alternatively, they can be created by hand using :func:`IRI.parse <pyhornedowl.model.IRI.parse>`. | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
from pyhornedowl.model import IRI | ||
ontology = pyhornedowl.open_ontology("path/to/ontology.owl") | ||
i1 = ontology.iri("https://example.com/test") | ||
i2 = IRI.parse("https://example.com/test") | ||
assert i1 == i2 | ||
The :func:`PyIndexedOntology.iri <pyhornedowl.PyIndexedOntology.iri>` function guesses if you passed it an absolute IRI or a CURIE based on the existence of ``://`` in the value. This is also true for all other convenience functions accepting IRIs as an argument. You can explicitely specify if the value is an absolute IRI or a CURIE by using the optional parameter ``absolute``. | ||
|
||
An exception to this is the the function :func:`PyIndexedOntology.curie <pyhornedowl.PyIndexedOntology.curie>` which only accepts CURIEs. | ||
|
||
.. note:: | ||
To create a curie the prefix must be defined. | ||
|
||
|
||
|
||
.. 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 | ||
-------- | ||
|
||
By default, no prefixes are defined. The standard prefixes for ``rdf``, ``rdfs``, ``xsd``, and ``owl`` can be added via the :func:`PyIndexedOntology.add_default_prefix_names <pyhornedowl.PyIndexedOntology.add_default_prefix_names>`. Other prefixes can be added using the :func:`PyIndexedOntology.add_prefix_mapping <pyhornedowl.PyIndexedOntology.add_prefix_mapping>` method. | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
ontology = pyhornedowl.open_ontology("path/to/ontology.owl") | ||
ontology.add_default_prefix_names() | ||
ontology.add_prefix_mapping("ex", "https://example.com/") | ||
Create entities | ||
--------------- | ||
Classes, Individuals, Data- and Objectproperties can be created using convenience methods on an ontology. | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
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") | ||
Write class expressions | ||
----------------------- | ||
.. warning:: | ||
Experimental feature! Only available on latest development branch! | ||
|
||
Instead of writing class expressions as nested constructor calls, some expressions can be expressed using operators. | ||
|
||
.. code-block:: python | ||
import pyhornedowl | ||
from pyhornedowl.model import * | ||
o = pyhornedowl.PyIndexedOntology() | ||
o.add_prefix_mapping("", "https://example.com/") | ||
A = o.clazz("A") | ||
B = o.clazz("B") | ||
C = o.clazz("C") | ||
r = o.object_property("r") | ||
assert A & B == ObjectIntersectionOf([A, B]) | ||
assert A | B == ObjectUnionOf([A, B]) | ||
assert ~A == ObjectComplementOf(A) | ||
assert ~r == InverseObjectProperty(r) | ||
assert r.some(A) == ObjectSomeValuesFrom(r, A) | ||
assert r.only(A) == ObjectAllValuesFrom(r, A) | ||
assert r.some(A & B | (~r).only(C)) == ObjectSomeValuesFrom(r, ObjectUnionOf([ObjectIntersectionOf([A, B]), ObjectAllValuesFrom(InverseObjectProperty(r), C)])) |