Commit c55e185f authored by davebshow's avatar davebshow

added readme

parent b5b5a978
## Why davebshow/goblin???
Let's face it, ZEROFAIL/goblin has problems. It's not really anyone's fault, it is just a fact of life. The mogwai codebase is monumental and it makes heavy use of metaprogramming, instrumentation, class inheritance, global variables and configuration, and third-party libraries. This results in opaque code that is hard to read and maintain, resulting in less community interest and more work for developers looking to make improvements. My port to TinkerPop3 just made things worse, as it introduced a level of callback hell only know to the most hardcore JavaScript developers. At this point, while ZEROFAIL/goblin basically works, implementing new functionality is a daunting task, and simple debugging and reasoning have become serious work. I was trying to think how to remedy these problems and rescue goblin from an ugly, bloated fate as we move forward adding new functionality. Then, it dawned on me: all I need to do is drag that whole folder to the virtual trash bin on my desktop and start from scratch.
But, wait, a whole new OGM from scratch...? Well, yes and no. Borrowing a few concepts from SQLAlchemy and using what I've learned from working on previous Python software targeting the Gremlin Server, I was able to piece together a fully functioning system in just a few hours of work and less than 1/10 the code. So, here is my *community prototype* OGM contribution to the Python TinkerPop3 ecosystem.
## Features
1. *Transparent* Python 3.5 codebase using PEP 492 await/async syntax that leverages asynchronous iterators and asynchronous context managers
2. SQLAlchemy style element creation and queries
3. Full integration with the official GLV gremlin-python
4. Transparent mapping between OGM and database values
5. Support for session/transaction management
6. Graph database vendor agnostic/configurable
7. Fully extensible data type system
8. Descriptor based property assignment
9. And more...!
### Install
$ pip install git+
### Create/update elements
import asyncio
from goblin.api import create_engine, Vertex, Edge
from import Property, String
class TestVertex(Vertex):
__label__ = 'test_vertex'
name = Property(String)
notes = Property(String, initval='N/A')
class TestEdge(Edge):
__label__ = 'test_edge'
notes = Property(String, initval='N/A')
loop = asyncio.get_event_loop()
engine = loop.run_until_complete(
create_engine("http://localhost:8182/", loop))
async def create():
session = engine.session()
leif = TestVertex() = 'leifur'
jon = TestVertex() = 'jonathan'
works_for = TestEdge()
works_for.source = jon = leif
assert works_for.notes == 'N/A'
works_for.notes = 'zerofail'
session.add(leif, jon, works_for)
await session.flush()
works_for.notes, = 'leif'
await session.flush()
# leifur 0 jonathan 3 zerofail 6
# leif 0
### Query the db:
async def query():
session = engine.session()
stream = await session.query(TestVertex).all()
async for msg in stream:
# [<__main__.TestVertex object at 0x7f46d833e588>, <__main__.TestVertex object at 0x7f46d833e780>]
### See how objects map to the db:
# <Mapping(type=vertex, label=test_vertex, properties=[
# {'db_name': 'test_vertex__name', 'ogm_name': 'name', 'data_type': <class ''>},
# {'db_name': 'test_vertex__notes', 'ogm_name': 'notes', 'data_type': <class ''>}])
### Close the engine
......@@ -44,7 +44,7 @@ async def create_engine(url, loop):
class Engine:
"""Class used to encapsulate database connection configuration and generate
database connections. Used as a factory to create :py:class:`Session`
objects. More config coming soon."""
def __init__(self, url, loop, *, force_close=True, **features):
self._url = url
......@@ -196,7 +196,7 @@ class Session:
result = await stream.fetch_data()
edge = mapper.map_edge_to_ogm([0], element,
return edge
def _get_edge_by_id(self, eid):
......@@ -14,7 +14,8 @@ class Property:
class PropertyDescriptor:
"""Descriptor that validates user property input."""
"""Descriptor that validates user property input and gets/sets properties
as instance attributes."""
def __init__(self, name, data_type, *, initval=None):
self._name = '_' + name
......@@ -22,7 +23,6 @@ class PropertyDescriptor:
self._initval = initval
def __get__(self, obj, objtype):
if obj is None:
return self
return getattr(obj, self._name, self._initval)
......@@ -12,7 +12,9 @@ setup(
packages=["goblin", "goblin.gremlin_python",
"goblin.gremlin_python_driver", "tests"],
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment