Skip to main content

An object-graph mapper based on owlready2

Project description

ogmready is a python library that is built upon owlready2 and strives to be an easy to use Object-Graph Mapper, enabling the use of a Knowledge Graph based on an Ontology as a viable alternative to relational databases. ogmready lets the user define their domain classes and specify later how they should be mapped to ontology concepts, leaving the application logic and the persistance strategy decoupled.

Quickstart

First, install the package:

pip install ogmready

Then define an Ontology, using owlready2 or your tool of choice:

onto = owlready2.get_ontology("http://example.org/")

with onto:
    class Person(owlready2.Thing):
        pass

    class Dog(owlready2.Thing):
        pass

    class name(owlready2.DataProperty, owlready2.FunctionalProperty):
        range = [str]

    class age(owlready2.DataProperty, owlready2.FunctionalProperty):
        range = [int]

    class id(owlready2.DataProperty, owlready2.FunctionalProperty):
        range = [int]

    class hasDog(owlready2.ObjectProperty, owlready2.FunctionalProperty):
        domain = [Person]
        range = [Dog]

You can also add definitions under different namespaces:

other_namespace = "http://other.org/"
with onto.get_namespace(other_namespace):
    class color(owlready2.DataProperty):
        range = [str]

Define your domain classes:

@dataclass
class Dog:
    id: int
    name: str
    colors: Set[str]

@dataclass
class Person:
    id: int
    name: str
    age: int
    dog: Dog

And finally the mappers

# Create a subclass of Mapper
class DogMapper(Mapper):
    def __init__(self, ontology):
        # Define the mappings
        mappings = {
            # Data property, functional by default
            "id": DataPropertyMapping("id", primary_key=True),
            "name": DataPropertyMapping("name"),

            # functional = False means that the property is a Set
            # we can pass a tuple (name, namespace) to say that a name is in a
            # different namespace than the default one
            "colors": DataPropertyMapping(("color", other_namespace), functional=False)
        }
        # Specify the domain and ontology classes to perfom the mapping
        super().__init__(Dog, "Dog", mappings, ontology)

class PersonMapper(Mapper):
    def __init__(self, ontology):
        mappings = {
            "id" : DataPropertyMapping("id", primary_key=True),
            "name": DataPropertyMapping("name"),
            "age": DataPropertyMapping("age"),
            # We can reference to other object mappers
            "dog": ObjectPropertyMapping("hasDog", lambda: DogMapper(ontology))
        }
        super().__init__(Person, "Person", mappings, ontology)

At this point, we can use the methods from_owl and to_owl of the mappers:

# create the objects
d = Dog(1, "pluto", {"black", "white"})
p = Person(2, "mario", 10, d)

# create the mapper objects, passing the ontology as an argument
person_mapper = PersonMapper(onto)
dog_mapper = DogMapper(onto)

# map to owlready2 objects
onto_dog = dog_mapper.to_owl(d)
onto_person = person_mapper.to_owl(p)

# map back
p == person_mapper.from_owl(onto_person)
d == dog_mapper.from_owl(onto_dog)

About lists

Since Knowledge Graph are usually stored in RDF format, which is based on triples <subject, predicate, object>, storing lists is not straightforward. While we use an OWL Ontology, we cannot use rdf:List, because it is used in the OWL specification. A way around this is to use an Ontology that lets us express the relations between lists and their elements: an example is the Collections Ontology, which defines the semantics of lists. To express something like L = [a], using the Collections Ontology we would say something like (mind that this is a simplified RDF):

  • <L, is_a, List>
  • <L, item, a_in_L>
  • <a_in_L, is_a, ListItem>
  • <a_in_L, index, 0>
  • <a_in_L, itemContent, a>

So a_in_L acts as a connecting object between L and its content a. An intermediate element like a_in_L is needed because we could have more occurrences of a inside of L. Moreover, with index we can express the order of the elements.

In ogmready, an example could be (using "http://purl.org/co/", the Collections ontology):

@dataclass
class Person:
    friends: List[Person]

class PersonMapper(Mapper):
    def __init__(self, ontology):
        co = "http://purl.org/co/"
        mappings = {
            # the parameters are:
            # - relation to connect list to items (e.g. 'item')
            # - OWL class of the connecting item (e.g. 'ListItem')
            # - relation to get to the actual item (e.g. 'itemContent')
            # - mapper for the item contents
            # - property to express the ordering of the elements
            "friends": ListMapping(("item", co), ("ListItem", co), ("itemContent", co),
                                   lambda: PersonMapper(ontology), ("index", co))
        }
        super().__init__(Person, "Person", mappings, ontology)

Defining your own mappings

It suffices to create a subclass of Mapping and implement the methods from_owl and to_owl. The method to_query is relevant if the field that you are mapping will be used in the queries to search for an already available object in the ontology. The method is_primary_key by default returns False, so changing its implementation makes sense if a property that you are mapping could be a primary key, like DataPropertyMapping.

A note on object retrieval

TL;DR. It is always a good idea to specify a field as a primary key, if it is possible.

Since we could be mapping a deeply nested object, of course we don't want to create new objects inside the Knowledge Graph if they are referenced by others but are already storedy. By default, the Mapper class tries to search for the referenced objects based on the fields that were specified as primary_key, but in case no primary_key is defined, it defaults to a deep search (search of all object fields inside the Knowledge Graph), which could become slow and in certain cases it could loop if there are circular references.

Missing features (contributions are welcome!)

  • Allowing the use of multiple mappers for a field, e.g. for friend: Person | Dog it would be nice to say "use PersonMapper or DogMapper" based on what you find
  • Slim down the definition of new mappers. Using a dict for mappings seems a bit wonky to me, it could be done better maybe with class variables to make the code more readable

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ogmready-0.0.2.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ogmready-0.0.2-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

Details for the file ogmready-0.0.2.tar.gz.

File metadata

  • Download URL: ogmready-0.0.2.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ogmready-0.0.2.tar.gz
Algorithm Hash digest
SHA256 848feec8fc609c11b4ea0506d8e1e38db6cb6141058fa79c6ca56799b15cfb7b
MD5 284830366c068bff6e910b2afeb63a9b
BLAKE2b-256 3f414aad3d5cc1410d67f3195d69eae5e8993b2029a4ef6ba6f1ee5fb026191c

See more details on using hashes here.

File details

Details for the file ogmready-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: ogmready-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for ogmready-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 febd59c88aed034ade5b041863fd434eca3b13fa7a984aea781058e14731357f
MD5 975c2fac823732d6c201939912b5618b
BLAKE2b-256 8b93833dab2d4e069fce35ad75f09e4563b01ad5c45a2ab8d4e31f3614cdaad1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page