Skip to main content

An object mapper for the neo4j graph database.

Project description

========
neomodel
========

An object mapper for the neo4j graph database.

* Structured node definitions with type checking
* Lazy category node creation
* Automatic indexing
* Relationship traversal
* Soft cardinality restrictions
* pre and post save / delete hooks (and django signals!)

Supports: neo4j 1.8+ (1.9 recommended), python 2.6, 2.7

.. image:: https://secure.travis-ci.org/robinedwards/neomodel.png
:target: https://secure.travis-ci.org/robinedwards/neomodel/

Installation
------------
Install the module via git::

pip install -e git+git@github.com:robinedwards/neomodel.git@HEAD#egg=neomodel-dev

Introduction
------------

Connection::

export NEO4J_REST_URL=http://localhost:7474/db/data/

Or with authentication::

export NEO4J_REST_URL=http://user:password@localhost:7474/db/data/

Node definitions::

from neomodel import StructuredNode, StringProperty, IntegerProperty, RelationshipTo, RelationshipFrom
class Country(StructuredNode):
code = StringProperty(unique_index=True, required=True)

# traverse incoming IS_FROM relation, inflate to Person objects
inhabitant = RelationshipFrom('Person', 'IS_FROM')


class Person(StructuredNode):
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)

# traverse outgoing IS_FROM relations, inflate to Country objects
country = RelationshipTo('Country', 'IS_FROM')

In the above example, there is one type of relationship present `IS_FROM`,
we are defining two different methods for traversing it
one accessible via Person objects and one via Country objects.

CRUD
----

CReate Update Delete::

jim = Person(name='Jim', age=3).save()
jim.age = 4
jim.save() # validation happens here
jim.delete()

Batch create (atomic) which also validates and indexes::

people = Person.create(
{'name': 'Tim', 'age': 83},
{'name': 'Bob', 'age': 23},
{'name': 'Jill', 'age': 34},
)

Hooks and Signals
-----------------
You may define the following hook methods on your nodes::

pre_save, post_save, pre_delete, post_delete, post_create

Signals are also supported *if* django is available::

from django.db.models import signals
signals.post_save.connect(your_func, sender=Person)

Relationships
-------------
Access related nodes through your defined relations::

germany = Country(code='DE').save()
jim.country.connect(germany)

if jim.country.is_connected(germany):
print("Jim's from Germany")

for p in germany.inhabitant.all()
print(p.name) # Jim

len(germany.inhabitant) # 1

jim.country.disconnect(germany)

Search related nodes through your defined relations. This example starts at the germany node
and traverses incoming 'IS_FROM' relations and returns the nodes with the property name
that is equal to 'Jim'::

germany.inhabitant.search(name='Jim')

If you don't care about the direction of the relationship::

class Person(StructuredNode):
friends = Relationship('Friend', 'FRIEND')

You may also reference classes from another module::

class Person(StructuredNode):
car = RelationshipTo('transport.models.Car', 'CAR')

Cardinality
-------
It's possible to enforce cardinality restrictions on your relationships.
Remember this needs to be declared on both sides of the relationship for it to work::

class Person(StructuredNode):
car = RelationshipTo('Car', 'CAR', cardinality=One)

class Car(StructuredNode):
owner = RelationshipFrom('Person', cardinality=One)

The following cardinality classes are available::

ZeroOMore (default), OneOrMore, ZeroOrOne, One

If cardinality is broken by existing data a *CardinalityViolation* exception is raised.
On attempting to break a cardinality restriction a *AttemptedCardinalityViolation* is raised.

Custom cypher queries
-------------

You may handle more complex queries via cypher. Each node provides an 'inflate' class method,
this inflates py2neo nodes to neomodel node objects::

class Person(StructuredNode):
def friends(self):
results = self.cypher("START a=node({self}) MATCH a-[:FRIEND]->(b) RETURN b");
return [self.__class__.inflate(row[0]) for row in results]

The self query parameter is prepopulated with the current node id. It's possible to pass in your
own query parameters to the cypher method.


Relating to different node types
-------

You can define relations of a single relation type to different `StructuredNode` classes.::

class Humanbeing(StructuredNode):
name = StringProperty()
has_a = RelationshipTo(['Location', 'Nationality'], 'HAS_A')

class Location(StructuredNode):
name = StringProperty()

class Nationality(StructuredNode):
name = StringProperty()

Remember that when traversing the `has_a` relation you will retrieve objects of different types.


Category nodes
-------

Access your instances via the category node::

country_category = Country.category()
for c in country_category.instance.all()

Note that `connect` and `disconnect` are not available through the `instance` relation.
As these actions are handled for your via the save() and delete() methods.

Read-only nodes
------

If you have existing nodes you want to protect use the read-only base class::

from neomodel.core import ReadOnlyNode, ReadOnlyError

class ImmortalBeing(ReadOnlyNode):
name = StringProperty()

Now all write operations below raise a *ReadOnlyError*::

some_immortal_being.delete()
some_immortal_being.save()
some_immortal_being.update()

Indexing
-------

Make use of indexes::

jim = Person.index.get(name='Jim')
for p in Person.index.search(age=3):
print(p.name)

germany = Country(code='DE').save()

Use advanced Lucene queries with the `lucene-querybuilder` module::

from lucenequerybuilder import Q

Human(name='sarah', age=3).save()
Human(name='jim', age=4).save()
Human(name='bob', age=5).save()
Human(name='tim', age=2).save()

for h in Human.index.search(Q('age', inrange=[3, 5])):
print(h.name)

# prints: sarah, jim, bob

If you have an existing node index you can change the default name of your index.
This can be useful for integrating with neo4django schemas::

class Human(StructuredNode):
_index_name = 'myHumans'
name = StringProperty(indexed=True)

Human.index.name # myHumans

Properties
----------

The following basic properties are available::

StringProperty, IntegerProperty, FloatProperty, BooleanProperty

Additionally there is also::

DateProperty, DateTimeProperty, AliasProperty

The *DateTimeProperty* accepts datetime.datetime objects of any timezone and stores them as a UTC epoch value.

These epoch values are inflated to datetime.datetime objects with the UTC timezone set.

The *DateProperty* accepts datetime.date objects which are stored as a string property 'YYYY-MM-DD'.


*Default values* you may provide a default value to any property, this can also be a function or any callable::

def uid_generator():
# your algorithm here
pass

name = StringProperty(unique_index=True, default=uid_generator)

The *AliasProperty* a special property for aliasing other properties and providing 'magic' behaviour::

class Person(StructuredNode):
full_name = StringProperty(index=True)
name = AliasProperty(to='full_name')

Person.index.search(name='Jim') # just works

Custom properties can provide a setup method which will get invoked on class definition.

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

neomodel-0.2.6.tar.gz (19.5 kB view details)

Uploaded Source

File details

Details for the file neomodel-0.2.6.tar.gz.

File metadata

  • Download URL: neomodel-0.2.6.tar.gz
  • Upload date:
  • Size: 19.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for neomodel-0.2.6.tar.gz
Algorithm Hash digest
SHA256 36c78db2f84ed9a3bc1afb73c63d3766c5dc98a4f27c9383de0c6c5048efd3af
MD5 31f844b2f25e9d785ed722005a5c3605
BLAKE2b-256 2a398f630cad4dd1ffcb8ea4bc74e11ceb66268b798806c76a0086f2166951aa

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