arango-orm


Namearango-orm JSON
Version 0.7.2 PyPI version JSON
download
home_pagehttps://arango-orm.readthedocs.io/en/latest/
SummaryA SQLAlchemy like ORM implementation for arangodb
upload_time2023-06-04 18:27:09
maintainer
docs_urlNone
authorKashif Iftikhar
requires_python>=3.8,<4.0
licenseGNU General Public License v2 (GPLv2)
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Python ORM Layer For ArangoDB
=============================

**arango_orm** is a python ORM layer inspired by SQLAlchemy but aimed to work
with the multi-model database ArangoDB. It supports accessing both collections
and graphs using the ORM. The actual communication with the database is done
using **python-arango** (the database driver for accessing arangodb from
python) and object serialization and de-serialization is handled using
**marshmallow**.


Installation:
-------------

::

    pip install arango-orm


Connecting to a Database
-------------------------

.. code-block:: python

    from arango import ArangoClient
    from arango_orm import Database

    client = ArangoClient(hosts='http://localhost:8529')
    test_db = client.db('test', username='test', password='test')

    db = Database(test_db)


Using a Connection Pool
-----------------------

Note: This is deprecated since python arango version 5.0. Since now the base
library supports the hosts parameter. This will be removed in future versions.
Users should instead use the pool in ArangoClient like:

.. code-block:: python

  client = ArangoClient(
    hosts=['http://host1:8529', 'http://host2:8529'],
    host_resolver='roundrobin'
  )


Connection pools allow using multiple connections for accessing the database.
Though these can be used on a single machine setup, they are more useful to use
with arango clusters.

Connection pools support the same methods and properties that the Database class
does. So they can be used interchangeably with Database.

.. code-block:: python

    from arango import ArangoClient
    from arango_orm import ConnectionPool

    client1 = ArangoClient(protocol='http', host='localhost', port=8529)
    client2 = ArangoClient(protocol='http', host='127.0.0.1', port=8529)

    db = ConnectionPool([client1, client2], 'test', 'test', 'test')


Working With Collections
-------------------------

First we need to define data models (similar to SQLAlchemy's models) to specify what data our collection(s) will contain and how to marshal it

.. code-block:: python

    from arango_orm import Collection
    from arango_orm.fields import String, Date

    class Student(Collection):

        __collection__ = 'students'
        _index = [{'type': 'hash', 'fields': ['name'], 'unique': True}]

        _key = String(required=True)  # registration number
        name = String(required=True, allow_none=False)
        dob = Date()


Create Collection in the Database
_________________________________

.. code-block:: python

    db.create_collection(Student)


Drop a Collection
__________________

.. code-block:: python

    db.drop_collection(Student)

Check if a collection exists
____________________________

.. code-block:: python

    db.has_collection(Student)
    db.has_collection('students')

Add Records
___________

.. code-block:: python

    from datetime import date
    s = Student(name='test', _key='12312', dob=date(year=2016, month=9, day=12))
    db.add(s)
    print(s._id)  # students/12312


Get Total Records in the Collection
___________________________________

.. code-block:: python

    db.query(Student).count()


Get Record By Key
_________________

.. code-block:: python

    s = db.query(Student).by_key('12312')


Update a Record
________________

.. code-block:: python

    s = db.query(Student).by_key('12312')
    s.name = 'Anonymous'
    db.update(s)

Delete a Record
________________

.. code-block:: python

    s = db.query(Student).by_key('12312')
    db.delete(s)

Get All Records in a Collection
________________________________

.. code-block:: python

    students = db.query(Student).all()

Get First Record Matching the Query
____________________________________

.. code-block:: python

    first_student = db.query(Student).first()

Filter Records
______________

Using bind parameters (recommended)

.. code-block:: python

    records = db.query(Student).filter("name==@name", name='Anonymous').all()

Using plain condition strings (not safe in case of unsanitized user supplied input)

.. code-block:: python

    records = db.query(Student).filter("name=='Anonymous'").all()


Filter Using OR
_______________

.. code-block:: python

    # Get all documents where student name starts with A or B
    records = db.query(Student).filter(
                "LIKE(rec.name, 'A%')", prepend_rec_name=False).filter(
                "LIKE(rec.name, 'B%')", prepend_rec_name=False, _or=True).all()


Filter, Sort and Limit
______________________

.. code-block:: python

    # Last 5 students with names starting with A
    records = db.query(Student).filter(
                "LIKE(rec.name, 'A%')", prepend_rec_name=False).sort("name DESC").limit(5).all()

    # Query students with pagination (limit&offset)
    page_num, per_page = 2, 10
    page = db.query(Student).sort("name DESC").limit(per_page, start_from=(page_num - 1) * per_page)

Fetch Only Some Fields
______________________

    .. code-block:: python

        c = db.query(Student).limit(2).returns('_key', 'name').first()

Update Multiple Records
_______________________

.. code-block:: python

    db.query(Student).filter("name==@name", name='Anonymous').update(name='Mr. Anonymous')


Delete Multiple Records
_______________________

.. code-block:: python

    db.query(Student).filter("LIKE(rec.name, 'test%')", prepend_rec_name=False).delete()


Delete All Records
___________________

.. code-block:: python

    db.query(Student).delete()



Bulk Create Records
_______________________

.. code-block:: python

    s1 = Student(name='test1', _key='12345', dob=date(year=2016, month=9, day=12))
    s2 = Student(name='test2', _key='22346', dob=date(year=2015, month=9, day=12)
    car1 = Car(make="Honda", model="Fiat", year=2010)
    car2 = Car(make="Honda", model="Skoda", year=2015)

    db.bulk_add(entity_list=[p_ref_10, p_ref_11, car1, car2])


Bulk Update Records
_______________________

.. code-block:: python

    p_ref1 = db.query(Person).by_key("12312")
    p_ref2 = db.query(Person).by_key("12345")
    p_ref1.name = "Bruce"
    p_ref2.name = "Eliza"
    db.bulk_update(entity_list=[p_ref1, p_ref2])


Query Using AQL
________________

.. code-block:: python

    db.add(Student(name='test1', _key='12345', dob=date(year=2016, month=9, day=12)))
    db.add(Student(name='test2', _key='22346', dob=date(year=2015, month=9, day=12)))

    students = [Student._load(s) for s in db.aql.execute("FOR st IN students RETURN st")]

Reference Fields
----------------

Reference fields allow linking documents from another collection class within a collection instance.
These are similar in functionality to SQLAlchemy's relationship function.

.. code-block:: python

    from arango import ArangoClient
    from arango_orm.database import Database

    from arango_orm.fields import String
    from arango_orm import Collection, Relation, Graph, GraphConnection
    from arango_orm.references import relationship, graph_relationship


    class Person(Collection):

        __collection__ = 'persons'

        _index = [{'type': 'hash', 'unique': False, 'fields': ['name']}]
        _allow_extra_fields = False  # prevent extra properties from saving into DB

        _key = String(required=True)
        name = String(required=True, allow_none=False)

        cars = relationship(__name__ + ".Car", '_key', target_field='owner_key')

        def __str__(self):
            return "<Person(" + self.name + ")>"


    class Car(Collection):

        __collection__ = 'cars'
        _allow_extra_fields = True

        make = String(required=True)
        model = String(required=True)
        year = Integer(required=True)
        owner_key = String()

        owner = relationship(Person, 'owner_key', cache=False)

        def __str__(self):
            return "<Car({} - {} - {})>".format(self.make, self.model, self.year)

    client = ArangoClient(hosts='http://localhost:8529')
    test_db = client.db('test', username='test', password='test')
    db = Database(test_db)

    p = Person(_key='kashif', name='Kashif Iftikhar')
    db.add(p)
    p2 = Person(_key='azeen', name='Azeen Kashif')
    db.add(p2)

    c1 = Car(make='Honda', model='Civic', year=1984, owner_key='kashif')
    db.add(c1)

    c2 = Car(make='Mitsubishi', model='Lancer', year=2005, owner_key='kashif')
    db.add(c2)

    c3 = Car(make='Acme', model='Toy Racer', year=2016, owner_key='azeen')
    db.add(c3)

    print(c1.owner)
    print(c1.owner.name)
    print(c2.owner.name)
    print(c3.owner.name)

    print(p.cars)
    print(p.cars[0].make)
    print(p2.cars)


Working With Graphs
-------------------

Working with graphs involves creating collection classes and optionally Edge/Relation classes. Users can use the built-in Relation class for specifying relations but if relations need to contain extra attributes then it's required to create a sub-class of Relation class. Graph functionality is explain below with the help of a university graph example containing students, teachers, subjects and the areas where students and teachers reside in.

First we create some collections and relationships

.. code-block:: python

    from arango_orm.fields import String, Date, Integer, Boolean
    from arango_orm import Collection, Relation, Graph, GraphConnection


    class Student(Collection):

        __collection__ = 'students'

        _key = String(required=True)  # registration number
        name = String(required=True, allow_none=False)
        age = Integer()

        def __str__(self):
            return "<Student({})>".format(self.name)


    class Teacher(Collection):

        __collection__ = 'teachers'

        _key = String(required=True)  # employee id
        name = String(required=True)

        def __str__(self):
            return "<Teacher({})>".format(self.name)


    class Subject(Collection):

        __collection__ = 'subjects'

        _key = String(required=True)  # subject code
        name = String(required=True)
        credit_hours = Integer()
        has_labs = Boolean(missing=True)

        def __str__(self):
            return "<Subject({})>".format(self.name)


    class Area(Collection):

        __collection__ = 'areas'

        _key = String(required=True)  # area name


    class SpecializesIn(Relation):

        __collection__ = 'specializes_in'

        expertise_level = String(required=True, options=["expert", "medium", "basic"])

        def __str__(self):
            return "<SpecializesIn(_key={}, expertise_level={}, _from={}, _to={})>".format(
                self._key, self.expertise_level, self._from, self._to)


Next we sub-class the Graph class to specify the relationships between the various collections

.. code-block:: python

    class UniversityGraph(Graph):

        __graph__ = 'university_graph'

        graph_connections = [
            # Using general Relation class for relationship
            GraphConnection(Student, Relation("studies"), Subject),
            GraphConnection(Teacher, Relation("teaches"), Subject),

            # Using specific classes for vertex and edges
            GraphConnection(Teacher, SpecializesIn, Subject),
            GraphConnection([Teacher, Student], Relation("resides_in"), Area)
        ]

Now it's time to create the graph. Note that we don't need to create the collections individually, creating the graph will create all collections that it contains

.. code-block:: python

    from arango import ArangoClient
    from arango_orm.database import Database

    client = ArangoClient(hosts='http://localhost:8529')
    test_db = client.db('test', username='test', password='test')

    db = Database(test_db)

    uni_graph = UniversityGraph(connection=db)
    db.create_graph(uni_graph)


Now the graph and all it's collections have been created, we can verify their existence:

.. code-block:: python

    [c['name'] for c in db.collections()]
    db.graphs()

Now let's insert some data into our graph:

.. code-block:: python

    students_data = [
        Student(_key='S1001', name='John Wayne', age=30),
        Student(_key='S1002', name='Lilly Parker', age=22),
        Student(_key='S1003', name='Cassandra Nix', age=25),
        Student(_key='S1004', name='Peter Parker', age=20)
    ]

    teachers_data = [
        Teacher(_key='T001', name='Bruce Wayne'),
        Teacher(_key='T002', name='Barry Allen'),
        Teacher(_key='T003', name='Amanda Waller')
    ]

    subjects_data = [
        Subject(_key='ITP101', name='Introduction to Programming', credit_hours=4, has_labs=True),
        Subject(_key='CS102', name='Computer History', credit_hours=3, has_labs=False),
        Subject(_key='CSOOP02', name='Object Oriented Programming', credit_hours=3, has_labs=True),
    ]

    areas_data = [
        Area(_key="Gotham"),
        Area(_key="Metropolis"),
        Area(_key="StarCity")
    ]

    for s in students_data:
        db.add(s)

    for t in teachers_data:
        db.add(t)

    for s in subjects_data:
        db.add(s)

    for a in areas_data:
        db.add(a)

Next let's add some relations, we can add relations by manually adding the relation/edge record into the edge collection, like:

.. code-block:: python

    db.add(SpecializesIn(_from="teachers/T001", _to="subjects/ITP101", expertise_level="medium"))

Or we can use the graph object's relation method to generate a relation document from given objects:

.. code-block:: python

    gotham = db.query(Area).by_key("Gotham")
    metropolis = db.query(Area).by_key("Metropolis")
    star_city = db.query(Area).by_key("StarCity")

    john_wayne = db.query(Student).by_key("S1001")
    lilly_parker = db.query(Student).by_key("S1002")
    cassandra_nix = db.query(Student).by_key("S1003")
    peter_parker = db.query(Student).by_key("S1004")

    intro_to_prog = db.query(Subject).by_key("ITP101")
    comp_history = db.query(Subject).by_key("CS102")
    oop = db.query(Subject).by_key("CSOOP02")

    barry_allen = db.query(Teacher).by_key("T002")
    bruce_wayne = db.query(Teacher).by_key("T001")
    amanda_waller = db.query(Teacher).by_key("T003")

    db.add(uni_graph.relation(peter_parker, Relation("studies"), oop))
    db.add(uni_graph.relation(peter_parker, Relation("studies"), intro_to_prog))
    db.add(uni_graph.relation(john_wayne, Relation("studies"), oop))
    db.add(uni_graph.relation(john_wayne, Relation("studies"), comp_history))
    db.add(uni_graph.relation(lilly_parker, Relation("studies"), intro_to_prog))
    db.add(uni_graph.relation(lilly_parker, Relation("studies"), comp_history))
    db.add(uni_graph.relation(cassandra_nix, Relation("studies"), oop))
    db.add(uni_graph.relation(cassandra_nix, Relation("studies"), intro_to_prog))

    db.add(uni_graph.relation(barry_allen, SpecializesIn(expertise_level="expert"), oop))
    db.add(uni_graph.relation(barry_allen, SpecializesIn(expertise_level="expert"), intro_to_prog))
    db.add(uni_graph.relation(bruce_wayne, SpecializesIn(expertise_level="medium"), oop))
    db.add(uni_graph.relation(bruce_wayne, SpecializesIn(expertise_level="expert"), comp_history))
    db.add(uni_graph.relation(amanda_waller, SpecializesIn(expertise_level="basic"), intro_to_prog))
    db.add(uni_graph.relation(amanda_waller, SpecializesIn(expertise_level="medium"), comp_history))

    db.add(uni_graph.relation(bruce_wayne, Relation("teaches"), oop))
    db.add(uni_graph.relation(barry_allen, Relation("teaches"), intro_to_prog))
    db.add(uni_graph.relation(amanda_waller, Relation("teaches"), comp_history))

    db.add(uni_graph.relation(bruce_wayne, Relation("resides_in"), gotham))
    db.add(uni_graph.relation(barry_allen, Relation("resides_in"), star_city))
    db.add(uni_graph.relation(amanda_waller, Relation("resides_in"), metropolis))
    db.add(uni_graph.relation(john_wayne, Relation("resides_in"), gotham))
    db.add(uni_graph.relation(lilly_parker, Relation("resides_in"), metropolis))
    db.add(uni_graph.relation(cassandra_nix, Relation("resides_in"), star_city))
    db.add(uni_graph.relation(peter_parker, Relation("resides_in"), metropolis))

With our graph populated with some sample data, let's explore the ways we can work with the graph.


Expanding Documents
___________________

We can expand any Collection (not Relation) object to access the data that is linked to it. We can sepcify which links ('inbound', 'outbound', 'any') to expand and the depth to which those should be expanded to. Let's see all immediate connections that Bruce Wayne has in our graph:

.. code-block:: python

    bruce = db.query(Teacher).by_key("T001")
    uni_graph.expand(bruce, depth=1, direction='any')

Graph expansion on an object adds a **_relations** dictionary that contains all the relations for the object according to the expansion criteria:

.. code-block:: python

    bruce._relations

Returns::

    {
    'resides_in': [<Relation(_key=4205290, _from=teachers/T001, _to=areas/Gotham)>],
    'specializes_in': [<SpecializesIn(_key=4205114, expertise_level=medium, _from=teachers/T001, _to=subjects/ITP101)>,
     <SpecializesIn(_key=4205271, expertise_level=expert, _from=teachers/T001, _to=subjects/CS102)>,
     <SpecializesIn(_key=4205268, expertise_level=medium, _from=teachers/T001, _to=subjects/CSOOP02)>],
    'teaches': [<Relation(_key=4205280, _from=teachers/T001, _to=subjects/CSOOP02)>]
    }

We can use _from and _to of a relation object to access the id's for both sides of the link. We also have _object_from and _object_to to access the objects on both sides, for example:

.. code-block:: python

    bruce._relations['resides_in'][0]._object_from.name
    # 'Bruce Wayne'

    bruce._relations['resides_in'][0]._object_to._key
    # 'Gotham'

There is also a special attribute called **_next** that allows accessing the other side of the relationship irrespective of the relationship direction. For example, for outbound relationships the _object_from contains the source object while for inbound_relationships _object_to contains the source object. But if we're only interested in traversal of the graph then it's more useful at times to access the other side of the relationship w.r.t the current object irrespective of it's direction:

.. code-block:: python

    bruce._relations['resides_in'][0]._next._key
    # 'Gotham'

Let's expand the bruce object to 2 levels and see **_next** in more action:

.. code-block:: python

    uni_graph.expand(bruce, depth=2)

    # All relations of the area where bruce resides in
    bruce._relations['resides_in'][0]._object_to._relations
    # -> {'resides_in': [<Relation(_key=4205300, _from=students/S1001, _to=areas/Gotham)>]}

    # Name of the student that resides in the same area as bruce
    bruce._relations['resides_in'][0]._object_to._relations['resides_in'][0]._object_from.name
    # 'John Wayne'

    # The same action using _next without worrying about direction
    bruce._relations['resides_in'][0]._next._relations['resides_in'][0]._next.name
    # 'John Wayne'

    # Get names of all people that reside in the same area and Bruce Wayne
    [p._next.name for p in bruce._relations['resides_in'][0]._next._relations['resides_in']]
    # ['John Wayne']


Inheritance Mapping
__________________________

For inheritance mapping, **arango_orm** offers you two ways to define it.

1. Discriminator field/mapping:

Discriminator field/mapping are defined at entity level:

.. code-block:: python

    class Vehicle(Collection):
        __collection__ = "vehicle"

        _inheritance_field = "discr"
        _inheritance_mapping = {
            'Bike': 'moto',
            'Truck': 'truck'
        }

        _key = String()
        brand = String()
        model = String()
        # discr field match what you defined in _inheritance_field
        # the field type depends on the values of your _inheritance_mapping
        discr = String(required=True)


    class Bike(Vehicle):
        motor_size = Float()


    class Truck(Vehicle):
        traction_power = Float()


2. Inheritance mapping resolver:

The `inheritance_mapping_resolver` is a function defined at graph level; it allows you to make either a simple test
on a discriminator field, or complex inference

.. code-block:: python

    class OwnershipGraph2(Graph):
        __graph__ = "ownership_graph"

        graph_connections = [
            GraphConnection(Owner2, Own2, Vehicle2)
        ]

        def inheritance_mapping_resolver(self, col_name: str, doc_dict: dict = {}):
            if col_name == 'vehicle':
                if 'traction_power' in doc_dict:
                    return Truck2
                else:
                    return Bike2

            return self.vertices[col_name]


Graph Traversal Using AQL
__________________________

The graph module also supports traversals using AQL, the results are converted to objects and have the
same structure as graph.expand method:

.. code-block:: python

    obj = uni_graph.aql("FOR v, e, p IN 1..2 INBOUND 'areas/Gotham' GRAPH 'university_graph' RETURN p")
    print(obj._key)
    # Gotham

    gotham_residents = [rel._next.name for rel in obj._relations['resides_in']]
    print(gotham_residents)
    # ['Bruce Wayne', 'John Wayne']


For Developers
==============

Running the Test Cases
----------------------

```bash
ARANGO_HOSTS="http://127.0.0.1:8529" ARANGO_USERNAME=root ARANGO_PASSWORD=toor ARANGO_DATABASE=test_db pytest tests
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://arango-orm.readthedocs.io/en/latest/",
    "name": "arango-orm",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Kashif Iftikhar",
    "author_email": "kashif@compulife.com.pk",
    "download_url": "https://files.pythonhosted.org/packages/9f/5e/3761b23bec83c3aa80fad64869dfdd97bffed859b5aca533a17f69e279de/arango_orm-0.7.2.tar.gz",
    "platform": null,
    "description": "Python ORM Layer For ArangoDB\n=============================\n\n**arango_orm** is a python ORM layer inspired by SQLAlchemy but aimed to work\nwith the multi-model database ArangoDB. It supports accessing both collections\nand graphs using the ORM. The actual communication with the database is done\nusing **python-arango** (the database driver for accessing arangodb from\npython) and object serialization and de-serialization is handled using\n**marshmallow**.\n\n\nInstallation:\n-------------\n\n::\n\n    pip install arango-orm\n\n\nConnecting to a Database\n-------------------------\n\n.. code-block:: python\n\n    from arango import ArangoClient\n    from arango_orm import Database\n\n    client = ArangoClient(hosts='http://localhost:8529')\n    test_db = client.db('test', username='test', password='test')\n\n    db = Database(test_db)\n\n\nUsing a Connection Pool\n-----------------------\n\nNote: This is deprecated since python arango version 5.0. Since now the base\nlibrary supports the hosts parameter. This will be removed in future versions.\nUsers should instead use the pool in ArangoClient like:\n\n.. code-block:: python\n\n  client = ArangoClient(\n    hosts=['http://host1:8529', 'http://host2:8529'],\n    host_resolver='roundrobin'\n  )\n\n\nConnection pools allow using multiple connections for accessing the database.\nThough these can be used on a single machine setup, they are more useful to use\nwith arango clusters.\n\nConnection pools support the same methods and properties that the Database class\ndoes. So they can be used interchangeably with Database.\n\n.. code-block:: python\n\n    from arango import ArangoClient\n    from arango_orm import ConnectionPool\n\n    client1 = ArangoClient(protocol='http', host='localhost', port=8529)\n    client2 = ArangoClient(protocol='http', host='127.0.0.1', port=8529)\n\n    db = ConnectionPool([client1, client2], 'test', 'test', 'test')\n\n\nWorking With Collections\n-------------------------\n\nFirst we need to define data models (similar to SQLAlchemy's models) to specify what data our collection(s) will contain and how to marshal it\n\n.. code-block:: python\n\n    from arango_orm import Collection\n    from arango_orm.fields import String, Date\n\n    class Student(Collection):\n\n        __collection__ = 'students'\n        _index = [{'type': 'hash', 'fields': ['name'], 'unique': True}]\n\n        _key = String(required=True)  # registration number\n        name = String(required=True, allow_none=False)\n        dob = Date()\n\n\nCreate Collection in the Database\n_________________________________\n\n.. code-block:: python\n\n    db.create_collection(Student)\n\n\nDrop a Collection\n__________________\n\n.. code-block:: python\n\n    db.drop_collection(Student)\n\nCheck if a collection exists\n____________________________\n\n.. code-block:: python\n\n    db.has_collection(Student)\n    db.has_collection('students')\n\nAdd Records\n___________\n\n.. code-block:: python\n\n    from datetime import date\n    s = Student(name='test', _key='12312', dob=date(year=2016, month=9, day=12))\n    db.add(s)\n    print(s._id)  # students/12312\n\n\nGet Total Records in the Collection\n___________________________________\n\n.. code-block:: python\n\n    db.query(Student).count()\n\n\nGet Record By Key\n_________________\n\n.. code-block:: python\n\n    s = db.query(Student).by_key('12312')\n\n\nUpdate a Record\n________________\n\n.. code-block:: python\n\n    s = db.query(Student).by_key('12312')\n    s.name = 'Anonymous'\n    db.update(s)\n\nDelete a Record\n________________\n\n.. code-block:: python\n\n    s = db.query(Student).by_key('12312')\n    db.delete(s)\n\nGet All Records in a Collection\n________________________________\n\n.. code-block:: python\n\n    students = db.query(Student).all()\n\nGet First Record Matching the Query\n____________________________________\n\n.. code-block:: python\n\n    first_student = db.query(Student).first()\n\nFilter Records\n______________\n\nUsing bind parameters (recommended)\n\n.. code-block:: python\n\n    records = db.query(Student).filter(\"name==@name\", name='Anonymous').all()\n\nUsing plain condition strings (not safe in case of unsanitized user supplied input)\n\n.. code-block:: python\n\n    records = db.query(Student).filter(\"name=='Anonymous'\").all()\n\n\nFilter Using OR\n_______________\n\n.. code-block:: python\n\n    # Get all documents where student name starts with A or B\n    records = db.query(Student).filter(\n                \"LIKE(rec.name, 'A%')\", prepend_rec_name=False).filter(\n                \"LIKE(rec.name, 'B%')\", prepend_rec_name=False, _or=True).all()\n\n\nFilter, Sort and Limit\n______________________\n\n.. code-block:: python\n\n    # Last 5 students with names starting with A\n    records = db.query(Student).filter(\n                \"LIKE(rec.name, 'A%')\", prepend_rec_name=False).sort(\"name DESC\").limit(5).all()\n\n    # Query students with pagination (limit&offset)\n    page_num, per_page = 2, 10\n    page = db.query(Student).sort(\"name DESC\").limit(per_page, start_from=(page_num - 1) * per_page)\n\nFetch Only Some Fields\n______________________\n\n    .. code-block:: python\n\n        c = db.query(Student).limit(2).returns('_key', 'name').first()\n\nUpdate Multiple Records\n_______________________\n\n.. code-block:: python\n\n    db.query(Student).filter(\"name==@name\", name='Anonymous').update(name='Mr. Anonymous')\n\n\nDelete Multiple Records\n_______________________\n\n.. code-block:: python\n\n    db.query(Student).filter(\"LIKE(rec.name, 'test%')\", prepend_rec_name=False).delete()\n\n\nDelete All Records\n___________________\n\n.. code-block:: python\n\n    db.query(Student).delete()\n\n\n\nBulk Create Records\n_______________________\n\n.. code-block:: python\n\n    s1 = Student(name='test1', _key='12345', dob=date(year=2016, month=9, day=12))\n    s2 = Student(name='test2', _key='22346', dob=date(year=2015, month=9, day=12)\n    car1 = Car(make=\"Honda\", model=\"Fiat\", year=2010)\n    car2 = Car(make=\"Honda\", model=\"Skoda\", year=2015)\n\n    db.bulk_add(entity_list=[p_ref_10, p_ref_11, car1, car2])\n\n\nBulk Update Records\n_______________________\n\n.. code-block:: python\n\n    p_ref1 = db.query(Person).by_key(\"12312\")\n    p_ref2 = db.query(Person).by_key(\"12345\")\n    p_ref1.name = \"Bruce\"\n    p_ref2.name = \"Eliza\"\n    db.bulk_update(entity_list=[p_ref1, p_ref2])\n\n\nQuery Using AQL\n________________\n\n.. code-block:: python\n\n    db.add(Student(name='test1', _key='12345', dob=date(year=2016, month=9, day=12)))\n    db.add(Student(name='test2', _key='22346', dob=date(year=2015, month=9, day=12)))\n\n    students = [Student._load(s) for s in db.aql.execute(\"FOR st IN students RETURN st\")]\n\nReference Fields\n----------------\n\nReference fields allow linking documents from another collection class within a collection instance.\nThese are similar in functionality to SQLAlchemy's relationship function.\n\n.. code-block:: python\n\n    from arango import ArangoClient\n    from arango_orm.database import Database\n\n    from arango_orm.fields import String\n    from arango_orm import Collection, Relation, Graph, GraphConnection\n    from arango_orm.references import relationship, graph_relationship\n\n\n    class Person(Collection):\n\n        __collection__ = 'persons'\n\n        _index = [{'type': 'hash', 'unique': False, 'fields': ['name']}]\n        _allow_extra_fields = False  # prevent extra properties from saving into DB\n\n        _key = String(required=True)\n        name = String(required=True, allow_none=False)\n\n        cars = relationship(__name__ + \".Car\", '_key', target_field='owner_key')\n\n        def __str__(self):\n            return \"<Person(\" + self.name + \")>\"\n\n\n    class Car(Collection):\n\n        __collection__ = 'cars'\n        _allow_extra_fields = True\n\n        make = String(required=True)\n        model = String(required=True)\n        year = Integer(required=True)\n        owner_key = String()\n\n        owner = relationship(Person, 'owner_key', cache=False)\n\n        def __str__(self):\n            return \"<Car({} - {} - {})>\".format(self.make, self.model, self.year)\n\n    client = ArangoClient(hosts='http://localhost:8529')\n    test_db = client.db('test', username='test', password='test')\n    db = Database(test_db)\n\n    p = Person(_key='kashif', name='Kashif Iftikhar')\n    db.add(p)\n    p2 = Person(_key='azeen', name='Azeen Kashif')\n    db.add(p2)\n\n    c1 = Car(make='Honda', model='Civic', year=1984, owner_key='kashif')\n    db.add(c1)\n\n    c2 = Car(make='Mitsubishi', model='Lancer', year=2005, owner_key='kashif')\n    db.add(c2)\n\n    c3 = Car(make='Acme', model='Toy Racer', year=2016, owner_key='azeen')\n    db.add(c3)\n\n    print(c1.owner)\n    print(c1.owner.name)\n    print(c2.owner.name)\n    print(c3.owner.name)\n\n    print(p.cars)\n    print(p.cars[0].make)\n    print(p2.cars)\n\n\nWorking With Graphs\n-------------------\n\nWorking with graphs involves creating collection classes and optionally Edge/Relation classes. Users can use the built-in Relation class for specifying relations but if relations need to contain extra attributes then it's required to create a sub-class of Relation class. Graph functionality is explain below with the help of a university graph example containing students, teachers, subjects and the areas where students and teachers reside in.\n\nFirst we create some collections and relationships\n\n.. code-block:: python\n\n    from arango_orm.fields import String, Date, Integer, Boolean\n    from arango_orm import Collection, Relation, Graph, GraphConnection\n\n\n    class Student(Collection):\n\n        __collection__ = 'students'\n\n        _key = String(required=True)  # registration number\n        name = String(required=True, allow_none=False)\n        age = Integer()\n\n        def __str__(self):\n            return \"<Student({})>\".format(self.name)\n\n\n    class Teacher(Collection):\n\n        __collection__ = 'teachers'\n\n        _key = String(required=True)  # employee id\n        name = String(required=True)\n\n        def __str__(self):\n            return \"<Teacher({})>\".format(self.name)\n\n\n    class Subject(Collection):\n\n        __collection__ = 'subjects'\n\n        _key = String(required=True)  # subject code\n        name = String(required=True)\n        credit_hours = Integer()\n        has_labs = Boolean(missing=True)\n\n        def __str__(self):\n            return \"<Subject({})>\".format(self.name)\n\n\n    class Area(Collection):\n\n        __collection__ = 'areas'\n\n        _key = String(required=True)  # area name\n\n\n    class SpecializesIn(Relation):\n\n        __collection__ = 'specializes_in'\n\n        expertise_level = String(required=True, options=[\"expert\", \"medium\", \"basic\"])\n\n        def __str__(self):\n            return \"<SpecializesIn(_key={}, expertise_level={}, _from={}, _to={})>\".format(\n                self._key, self.expertise_level, self._from, self._to)\n\n\nNext we sub-class the Graph class to specify the relationships between the various collections\n\n.. code-block:: python\n\n    class UniversityGraph(Graph):\n\n        __graph__ = 'university_graph'\n\n        graph_connections = [\n            # Using general Relation class for relationship\n            GraphConnection(Student, Relation(\"studies\"), Subject),\n            GraphConnection(Teacher, Relation(\"teaches\"), Subject),\n\n            # Using specific classes for vertex and edges\n            GraphConnection(Teacher, SpecializesIn, Subject),\n            GraphConnection([Teacher, Student], Relation(\"resides_in\"), Area)\n        ]\n\nNow it's time to create the graph. Note that we don't need to create the collections individually, creating the graph will create all collections that it contains\n\n.. code-block:: python\n\n    from arango import ArangoClient\n    from arango_orm.database import Database\n\n    client = ArangoClient(hosts='http://localhost:8529')\n    test_db = client.db('test', username='test', password='test')\n\n    db = Database(test_db)\n\n    uni_graph = UniversityGraph(connection=db)\n    db.create_graph(uni_graph)\n\n\nNow the graph and all it's collections have been created, we can verify their existence:\n\n.. code-block:: python\n\n    [c['name'] for c in db.collections()]\n    db.graphs()\n\nNow let's insert some data into our graph:\n\n.. code-block:: python\n\n    students_data = [\n        Student(_key='S1001', name='John Wayne', age=30),\n        Student(_key='S1002', name='Lilly Parker', age=22),\n        Student(_key='S1003', name='Cassandra Nix', age=25),\n        Student(_key='S1004', name='Peter Parker', age=20)\n    ]\n\n    teachers_data = [\n        Teacher(_key='T001', name='Bruce Wayne'),\n        Teacher(_key='T002', name='Barry Allen'),\n        Teacher(_key='T003', name='Amanda Waller')\n    ]\n\n    subjects_data = [\n        Subject(_key='ITP101', name='Introduction to Programming', credit_hours=4, has_labs=True),\n        Subject(_key='CS102', name='Computer History', credit_hours=3, has_labs=False),\n        Subject(_key='CSOOP02', name='Object Oriented Programming', credit_hours=3, has_labs=True),\n    ]\n\n    areas_data = [\n        Area(_key=\"Gotham\"),\n        Area(_key=\"Metropolis\"),\n        Area(_key=\"StarCity\")\n    ]\n\n    for s in students_data:\n        db.add(s)\n\n    for t in teachers_data:\n        db.add(t)\n\n    for s in subjects_data:\n        db.add(s)\n\n    for a in areas_data:\n        db.add(a)\n\nNext let's add some relations, we can add relations by manually adding the relation/edge record into the edge collection, like:\n\n.. code-block:: python\n\n    db.add(SpecializesIn(_from=\"teachers/T001\", _to=\"subjects/ITP101\", expertise_level=\"medium\"))\n\nOr we can use the graph object's relation method to generate a relation document from given objects:\n\n.. code-block:: python\n\n    gotham = db.query(Area).by_key(\"Gotham\")\n    metropolis = db.query(Area).by_key(\"Metropolis\")\n    star_city = db.query(Area).by_key(\"StarCity\")\n\n    john_wayne = db.query(Student).by_key(\"S1001\")\n    lilly_parker = db.query(Student).by_key(\"S1002\")\n    cassandra_nix = db.query(Student).by_key(\"S1003\")\n    peter_parker = db.query(Student).by_key(\"S1004\")\n\n    intro_to_prog = db.query(Subject).by_key(\"ITP101\")\n    comp_history = db.query(Subject).by_key(\"CS102\")\n    oop = db.query(Subject).by_key(\"CSOOP02\")\n\n    barry_allen = db.query(Teacher).by_key(\"T002\")\n    bruce_wayne = db.query(Teacher).by_key(\"T001\")\n    amanda_waller = db.query(Teacher).by_key(\"T003\")\n\n    db.add(uni_graph.relation(peter_parker, Relation(\"studies\"), oop))\n    db.add(uni_graph.relation(peter_parker, Relation(\"studies\"), intro_to_prog))\n    db.add(uni_graph.relation(john_wayne, Relation(\"studies\"), oop))\n    db.add(uni_graph.relation(john_wayne, Relation(\"studies\"), comp_history))\n    db.add(uni_graph.relation(lilly_parker, Relation(\"studies\"), intro_to_prog))\n    db.add(uni_graph.relation(lilly_parker, Relation(\"studies\"), comp_history))\n    db.add(uni_graph.relation(cassandra_nix, Relation(\"studies\"), oop))\n    db.add(uni_graph.relation(cassandra_nix, Relation(\"studies\"), intro_to_prog))\n\n    db.add(uni_graph.relation(barry_allen, SpecializesIn(expertise_level=\"expert\"), oop))\n    db.add(uni_graph.relation(barry_allen, SpecializesIn(expertise_level=\"expert\"), intro_to_prog))\n    db.add(uni_graph.relation(bruce_wayne, SpecializesIn(expertise_level=\"medium\"), oop))\n    db.add(uni_graph.relation(bruce_wayne, SpecializesIn(expertise_level=\"expert\"), comp_history))\n    db.add(uni_graph.relation(amanda_waller, SpecializesIn(expertise_level=\"basic\"), intro_to_prog))\n    db.add(uni_graph.relation(amanda_waller, SpecializesIn(expertise_level=\"medium\"), comp_history))\n\n    db.add(uni_graph.relation(bruce_wayne, Relation(\"teaches\"), oop))\n    db.add(uni_graph.relation(barry_allen, Relation(\"teaches\"), intro_to_prog))\n    db.add(uni_graph.relation(amanda_waller, Relation(\"teaches\"), comp_history))\n\n    db.add(uni_graph.relation(bruce_wayne, Relation(\"resides_in\"), gotham))\n    db.add(uni_graph.relation(barry_allen, Relation(\"resides_in\"), star_city))\n    db.add(uni_graph.relation(amanda_waller, Relation(\"resides_in\"), metropolis))\n    db.add(uni_graph.relation(john_wayne, Relation(\"resides_in\"), gotham))\n    db.add(uni_graph.relation(lilly_parker, Relation(\"resides_in\"), metropolis))\n    db.add(uni_graph.relation(cassandra_nix, Relation(\"resides_in\"), star_city))\n    db.add(uni_graph.relation(peter_parker, Relation(\"resides_in\"), metropolis))\n\nWith our graph populated with some sample data, let's explore the ways we can work with the graph.\n\n\nExpanding Documents\n___________________\n\nWe can expand any Collection (not Relation) object to access the data that is linked to it. We can sepcify which links ('inbound', 'outbound', 'any') to expand and the depth to which those should be expanded to. Let's see all immediate connections that Bruce Wayne has in our graph:\n\n.. code-block:: python\n\n    bruce = db.query(Teacher).by_key(\"T001\")\n    uni_graph.expand(bruce, depth=1, direction='any')\n\nGraph expansion on an object adds a **_relations** dictionary that contains all the relations for the object according to the expansion criteria:\n\n.. code-block:: python\n\n    bruce._relations\n\nReturns::\n\n    {\n    'resides_in': [<Relation(_key=4205290, _from=teachers/T001, _to=areas/Gotham)>],\n    'specializes_in': [<SpecializesIn(_key=4205114, expertise_level=medium, _from=teachers/T001, _to=subjects/ITP101)>,\n     <SpecializesIn(_key=4205271, expertise_level=expert, _from=teachers/T001, _to=subjects/CS102)>,\n     <SpecializesIn(_key=4205268, expertise_level=medium, _from=teachers/T001, _to=subjects/CSOOP02)>],\n    'teaches': [<Relation(_key=4205280, _from=teachers/T001, _to=subjects/CSOOP02)>]\n    }\n\nWe can use _from and _to of a relation object to access the id's for both sides of the link. We also have _object_from and _object_to to access the objects on both sides, for example:\n\n.. code-block:: python\n\n    bruce._relations['resides_in'][0]._object_from.name\n    # 'Bruce Wayne'\n\n    bruce._relations['resides_in'][0]._object_to._key\n    # 'Gotham'\n\nThere is also a special attribute called **_next** that allows accessing the other side of the relationship irrespective of the relationship direction. For example, for outbound relationships the _object_from contains the source object while for inbound_relationships _object_to contains the source object. But if we're only interested in traversal of the graph then it's more useful at times to access the other side of the relationship w.r.t the current object irrespective of it's direction:\n\n.. code-block:: python\n\n    bruce._relations['resides_in'][0]._next._key\n    # 'Gotham'\n\nLet's expand the bruce object to 2 levels and see **_next** in more action:\n\n.. code-block:: python\n\n    uni_graph.expand(bruce, depth=2)\n\n    # All relations of the area where bruce resides in\n    bruce._relations['resides_in'][0]._object_to._relations\n    # -> {'resides_in': [<Relation(_key=4205300, _from=students/S1001, _to=areas/Gotham)>]}\n\n    # Name of the student that resides in the same area as bruce\n    bruce._relations['resides_in'][0]._object_to._relations['resides_in'][0]._object_from.name\n    # 'John Wayne'\n\n    # The same action using _next without worrying about direction\n    bruce._relations['resides_in'][0]._next._relations['resides_in'][0]._next.name\n    # 'John Wayne'\n\n    # Get names of all people that reside in the same area and Bruce Wayne\n    [p._next.name for p in bruce._relations['resides_in'][0]._next._relations['resides_in']]\n    # ['John Wayne']\n\n\nInheritance Mapping\n__________________________\n\nFor inheritance mapping, **arango_orm** offers you two ways to define it.\n\n1. Discriminator field/mapping:\n\nDiscriminator field/mapping are defined at entity level:\n\n.. code-block:: python\n\n    class Vehicle(Collection):\n        __collection__ = \"vehicle\"\n\n        _inheritance_field = \"discr\"\n        _inheritance_mapping = {\n            'Bike': 'moto',\n            'Truck': 'truck'\n        }\n\n        _key = String()\n        brand = String()\n        model = String()\n        # discr field match what you defined in _inheritance_field\n        # the field type depends on the values of your _inheritance_mapping\n        discr = String(required=True)\n\n\n    class Bike(Vehicle):\n        motor_size = Float()\n\n\n    class Truck(Vehicle):\n        traction_power = Float()\n\n\n2. Inheritance mapping resolver:\n\nThe `inheritance_mapping_resolver` is a function defined at graph level; it allows you to make either a simple test\non a discriminator field, or complex inference\n\n.. code-block:: python\n\n    class OwnershipGraph2(Graph):\n        __graph__ = \"ownership_graph\"\n\n        graph_connections = [\n            GraphConnection(Owner2, Own2, Vehicle2)\n        ]\n\n        def inheritance_mapping_resolver(self, col_name: str, doc_dict: dict = {}):\n            if col_name == 'vehicle':\n                if 'traction_power' in doc_dict:\n                    return Truck2\n                else:\n                    return Bike2\n\n            return self.vertices[col_name]\n\n\nGraph Traversal Using AQL\n__________________________\n\nThe graph module also supports traversals using AQL, the results are converted to objects and have the\nsame structure as graph.expand method:\n\n.. code-block:: python\n\n    obj = uni_graph.aql(\"FOR v, e, p IN 1..2 INBOUND 'areas/Gotham' GRAPH 'university_graph' RETURN p\")\n    print(obj._key)\n    # Gotham\n\n    gotham_residents = [rel._next.name for rel in obj._relations['resides_in']]\n    print(gotham_residents)\n    # ['Bruce Wayne', 'John Wayne']\n\n\nFor Developers\n==============\n\nRunning the Test Cases\n----------------------\n\n```bash\nARANGO_HOSTS=\"http://127.0.0.1:8529\" ARANGO_USERNAME=root ARANGO_PASSWORD=toor ARANGO_DATABASE=test_db pytest tests\n```\n",
    "bugtrack_url": null,
    "license": "GNU General Public License v2 (GPLv2)",
    "summary": "A SQLAlchemy like ORM implementation for arangodb",
    "version": "0.7.2",
    "project_urls": {
        "Homepage": "https://arango-orm.readthedocs.io/en/latest/",
        "Repository": "https://github.com/kashifpk/arango-orm"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8a45aa163ce917da8d385bf8e4596bc2b3f94f43f7c362e04b7e00c5ceb99b16",
                "md5": "74fea01dedaba91d226e6d74d6d6289c",
                "sha256": "62c7a8610d9364e4a8447befdbbd27fca5263211b0b95be5e4d978b070f7b03c"
            },
            "downloads": -1,
            "filename": "arango_orm-0.7.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "74fea01dedaba91d226e6d74d6d6289c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 36818,
            "upload_time": "2023-06-04T18:27:06",
            "upload_time_iso_8601": "2023-06-04T18:27:06.802844Z",
            "url": "https://files.pythonhosted.org/packages/8a/45/aa163ce917da8d385bf8e4596bc2b3f94f43f7c362e04b7e00c5ceb99b16/arango_orm-0.7.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9f5e3761b23bec83c3aa80fad64869dfdd97bffed859b5aca533a17f69e279de",
                "md5": "3b2800791fc1a96495417936ffe9efa6",
                "sha256": "501a32dc7a2ff7afdf7c52a2c97e2aabbeecd21ddf27c1b3a3b090b8366ffc9c"
            },
            "downloads": -1,
            "filename": "arango_orm-0.7.2.tar.gz",
            "has_sig": false,
            "md5_digest": "3b2800791fc1a96495417936ffe9efa6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 41149,
            "upload_time": "2023-06-04T18:27:09",
            "upload_time_iso_8601": "2023-06-04T18:27:09.450953Z",
            "url": "https://files.pythonhosted.org/packages/9f/5e/3761b23bec83c3aa80fad64869dfdd97bffed859b5aca533a17f69e279de/arango_orm-0.7.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-04 18:27:09",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kashifpk",
    "github_project": "arango-orm",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "arango-orm"
}
        
Elapsed time: 0.07871s