diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5a1683fae36d758e6162a399902baae5dc30d04c --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +## 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+https://github.com/davebshow/goblin.git +``` +### Create/update elements + +```python + +import asyncio + +from goblin.api import create_engine, Vertex, Edge +from goblin.properties 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() + leif.name = 'leifur' + jon = TestVertex() + jon.name = 'jonathan' + works_for = TestEdge() + works_for.source = jon + works_for.target = leif + assert works_for.notes == 'N/A' + works_for.notes = 'zerofail' + session.add(leif, jon, works_for) + await session.flush() + print(leif.name, leif.id, jon.name, jon.id, + works_for.notes, works_for.id) + leif.name = 'leif' + session.add(leif) + await session.flush() + print(leif.name, leif.id) + + +loop.run_until_complete(create()) +# leifur 0 jonathan 3 zerofail 6 +# leif 0 +``` + +### Query the db: + +```python +async def query(): + session = engine.session() + stream = await session.query(TestVertex).all() + async for msg in stream: + print(msg) + + +loop.run_until_complete(query()) +# [<__main__.TestVertex object at 0x7f46d833e588>, <__main__.TestVertex object at 0x7f46d833e780>] + +``` + +### See how objects map to the db: + +```python +TestVertex.__mapping__ +# <Mapping(type=vertex, label=test_vertex, properties=[ + # {'db_name': 'test_vertex__name', 'ogm_name': 'name', 'data_type': <class 'goblin.properties.String'>}, + # {'db_name': 'test_vertex__notes', 'ogm_name': 'notes', 'data_type': <class 'goblin.properties.String'>}]) + +``` + +### Close the engine + +```python +loop.run_until_complete(engine.close()) +``` diff --git a/goblin/api.py b/goblin/api.py index 340442c9e9c0f4b17186a8d90459a0b9be8659c7..3b021059246b1b7f6a910ae94e980bbdc7e2fa50 100644 --- a/goblin/api.py +++ b/goblin/api.py @@ -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.""" + 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() if result.data: edge = mapper.map_edge_to_ogm(result.data[0], element, - element.__mapping__) + element.__mapping__) return edge def _get_edge_by_id(self, eid): diff --git a/goblin/properties.py b/goblin/properties.py index d664b0831f548d20adc4f96accce4e2bce6f7ecb..96d37682aaabf44341d7b2b393df8d15ebea574f 100644 --- a/goblin/properties.py +++ b/goblin/properties.py @@ -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): - print(self._data_type) if obj is None: return self return getattr(obj, self._name, self._initval) diff --git a/setup.py b/setup.py index 228fe68bd47758606ae371968ec7dbc151fbf668..3001d3831d292534aaf6bdf8bc3736b188ac89ae 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,9 @@ setup( packages=["goblin", "goblin.gremlin_python", "goblin.gremlin_python_driver", "tests"], install_requires=[ - "aiohttp==0.21.6" + "aenum==1.4.5", + "aiohttp==0.21.6", + "inflection==0.3.1" ], test_suite="tests", classifiers=[