diff --git a/.travis.yml b/.travis.yml index 933f9265a92f120ee77b9d00b731d41b810f3c78..68b464947fb0bab530869bbdb01b1ca53582c515 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ dist: trusty language: python python: - 3.5 - - nightly + - 3.6 services: - docker diff --git a/docs/glv.rst b/docs/glv.rst index cd575f36487a20f26ce739a3ccfe6067f5a2d7c5..6bddf240abba1fcf06d846bbe241b22cec604e01 100644 --- a/docs/glv.rst +++ b/docs/glv.rst @@ -3,9 +3,9 @@ Using Graph (GLV) :py:mod:`Goblin` provides access to the underlying :py:mod:`aiogremlin` asynchronous version of the Gremlin-Python Gremlin Language Variant (GLV) that -is bundled with Apache TinkerPop beginning with the 3.2.2 release. Traversal are +is bundled with Apache TinkerPop beginning with the 3.2.2 release. Traversals are generated using the class -:py:class:`Graph<aiogremlin.gremlin_python.structure.graph.Graph>` combined with a remote +:py:class:`Graph<aiogremlin.structure.graph.Graph>` combined with a remote connection class, either :py:class:`DriverRemoteConnection<aiogremlin.remote.driver_remote_connection.DriverRemoteConnection>`:: @@ -23,8 +23,8 @@ Once you have a traversal source, it's all Gremlin...:: >>> traversal = g.addV('query_language').property('name', 'gremlin') -`traversal` is in an child instance of -:py:class:`Traversal<aiogremlin.gremlin_python.process.traversal.Traversal>`, which +`traversal` is in an instance of +:py:class:`AsyncGraphTraversal<aiogremlin.process.graph_traversal.AsyncGraphTraversal>`, which implements the Python 3.5 asynchronous iterator protocol:: >>> async def iterate_traversal(traversal): @@ -33,12 +33,12 @@ implements the Python 3.5 asynchronous iterator protocol:: >>> loop.run_until_complete(iterate_traversal(traversal)) -:py:class:`Traversal<aiogremlin.gremlin_python.process.traversal.Traversal>` also +:py:class:`AsyncGraphTraversal<aiogremlin.process.graph_traversal.AsyncGraphTraversal>` also provides several convenience coroutine methods to help iterate over results: -- :py:meth:`next<aiogremlin.gremlin_python.process.traversal.Traversal.next>` -- :py:meth:`toList<aiogremlin.gremlin_python.process.traversal.Traversal.toList>` -- :py:meth:`toSet<aiogremlin.gremlin_python.process.traversal.Traversal.toSet>` +- :py:meth:`next<aiogremlin.process.traversal.AsyncGraphTraversal.next>` +- :py:meth:`toList<aiogremlin.process.traversal.AsyncGraphTraversal.toList>` +- :py:meth:`toSet<aiogremlin.process.traversal.AsyncGraphTraversal.toSet>` Notice the mixedCase? Not very pythonic? Well no, but it maintains continuity with the Gremlin query language, and that's what the GLV is all about... @@ -51,15 +51,15 @@ The Side Effect Interface When using TinkerPop 3.2.2+ with the default :py:mod:`Goblin` provides an asynchronous side effects interface using the -:py:class:`RemoteTraversalSideEffects<aiogremlin.gremlin_python.driver.remote_connection.RemoteTraversalSideEffects>` +:py:class:`AsyncRemoteTraversalSideEffects<aiogremlin.driver_remote_side_effects.AsyncRemoteTraversalSideEffects>` class. This allows side effects to be retrieved after executing the traversal:: >>> traversal = g.V().aggregate('a') >>> loop.run_until_complete(traversal.iterate()) Calling -:py:meth:`keys<aiogremlin.gremlin_python.driver.remote_connection.RemoteTraversalSideEffects.keys>` -will then return an asynchronous iterator containing all keys for cached +:py:meth:`keys<aiogremlin.driver_remote_side_effects.AsyncRemoteTraversalSideEffects.keys>` +will return an asynchronous iterator containing all keys for cached side effects: >>> async def get_side_effect_keys(traversal): @@ -69,7 +69,7 @@ side effects: >>> loop.run_until_complete(get_side_effect_keys(traversal)) Then calling -:py:meth:`get<aiogremlin.gremlin_python.driver.remote_connection.RemoteTraversalSideEffects.get>` +:py:meth:`get<aiogremlin.driver_remote_side_effects.AsyncRemoteTraversalSideEffects.get>` using a valid key will return the cached side effects:: >>> async def get_side_effects(traversal): diff --git a/docs/goblin.driver.rst b/docs/goblin.driver.rst index 0f1833e4aff978f48e566af75c55cfb64c81bd9d..8536488ee2fa4595fe4a8726f066bcf193db266b 100644 --- a/docs/goblin.driver.rst +++ b/docs/goblin.driver.rst @@ -4,10 +4,10 @@ goblin.driver package Contains aliases to classes from :py:mod:`aiogremlin`: - :py:class:`Cluster<aiogremlin.driver.cluster.Cluster>` -- :py:class:`Graph<aiogremlin.gremlin_python.structure.graph.Graph>` +- :py:class:`Graph<aiogremlin.structure.graph.Graph>` - :py:class:`DriverRemoteConnection<aiogremlin.remote.driver_remote_connection.DriverRemoteConnection>` - :py:class:`Client<aiogremlin.driver.client.Client>` - :py:class:`Connection<aiogremlin.driver.connection.Connection>` - :py:class:`ConnectionPool<aiogremlin.driver.pool.ConnectionPool>` - :py:class:`GremlinServer<aiogremlin.driver.server.GremlinServer>` -- :py:class:`GraphSONMessageSerializer<aiogremlin.gremlin_python.driver.serializer.GraphSONMessageSerializer>` +- :py:class:`GraphSONMessageSerializer<gremlin_python.driver.serializer.GraphSONMessageSerializer>` diff --git a/docs/index.rst b/docs/index.rst index 0be973a3949b87bc575a5bb73db64dc799a9f909..4462097448b6020aa8c221d0a1ca48dd096d893e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -134,7 +134,7 @@ Generate and submit Gremlin traversals in native Python:: >>> loop.run_until_complete(go(g)) # {'properties': {'name': [{'value': 'Leif', 'id': 3}]}, 'label': 'developer', 'id': 2, 'type': 'vertex'} -For more information on using the :py:class:`Graph<aiogremlin.gremlin_python.structure.graph.Graph>`, +For more information on using the :py:class:`Graph<aiogremlin.structure.graph.Graph>`, see the `aiogremlin`_ documentation or the :doc:`GLV docs</glv>` diff --git a/docs/modules.rst b/docs/modules.rst index 8b7f906082ce89a2a791cbf0277f2ef8ecd04c83..7eee4a9125181e7a3461d44fd9750e2cea9fe686 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -1,5 +1,5 @@ -goblin -====== +Goblin API +========== .. toctree:: :maxdepth: 4 diff --git a/docs/ogm.rst b/docs/ogm.rst index 22d28471351e449a3dc582009d978a02d1bcbb54..34c948e9af8dd3ff2f94d0251dd167345f990d2b 100644 --- a/docs/ogm.rst +++ b/docs/ogm.rst @@ -21,7 +21,7 @@ modeling data, simply create *model* element classes that inherit from the :py:mod:`goblin.element` classes. For example:: import goblin - from aiogremlin.gremlin_python import Cardinality + from gremlin_python import Cardinality class Person(goblin.Vertex): @@ -150,7 +150,7 @@ to create multi-cardinality properties:: Notice that the cardinality of the :py:class:`VertexProperty<goblin.element.VertexProperty>` must be explicitly set using the `card` kwarg and the -:py:class:`Cardinality<aiogremlin.gremlin_python.Cardinality>` enumerator. +:py:class:`Cardinality<gremlin_python.Cardinality>` enumerator. :py:class:`VertexProperty<goblin.element.VertexProperty>` provides a different interface than the simple, key/value style diff --git a/goblin/abc.py b/goblin/abc.py index 26ae3423199fa32ade17e6b5a9cdc555bdc5c25d..f8378c40774b2b52d035b0b08c0a34066a7d6d4b 100644 --- a/goblin/abc.py +++ b/goblin/abc.py @@ -18,7 +18,7 @@ import abc import logging -from aiogremlin.gremlin_python.process.traversal import Cardinality +from gremlin_python.process.traversal import Cardinality from goblin import manager, element, exception diff --git a/goblin/driver/__init__.py b/goblin/driver/__init__.py index bd35d05e68df3970058f26a9183c7e3a0a749441..26119c92bd2d7f7ad90921043732a7fb957b48a8 100644 --- a/goblin/driver/__init__.py +++ b/goblin/driver/__init__.py @@ -20,8 +20,7 @@ from aiogremlin.driver.client import Client from aiogremlin.driver.connection import Connection from aiogremlin.driver.pool import ConnectionPool from aiogremlin.driver.server import GremlinServer -from aiogremlin.gremlin_python.driver.serializer import ( - GraphSONMessageSerializer) +from gremlin_python.driver.serializer import GraphSONMessageSerializer AsyncGraph = Graph diff --git a/goblin/element.py b/goblin/element.py index aa4f90e418e03d1e4066432908f4b9a706ee5fa2..8008b47b03088cfa350aa894438d8707665ddabb 100644 --- a/goblin/element.py +++ b/goblin/element.py @@ -19,7 +19,7 @@ import logging import inflection -from aiogremlin.gremlin_python.process.traversal import Cardinality +from gremlin_python.process.traversal import Cardinality from goblin import abc, exception, mapper, properties diff --git a/goblin/properties.py b/goblin/properties.py index 912bb20c1787e0551d0ec2ac7cb0673468974762..d8022751376f3f27bda1419242a9541db6b638bf 100644 --- a/goblin/properties.py +++ b/goblin/properties.py @@ -19,7 +19,7 @@ import logging -from aiogremlin.gremlin_python.statics import long +from gremlin_python.statics import long from goblin import abc, exception diff --git a/goblin/session.py b/goblin/session.py index ea37a6a663d4403a7fd8d7dcb3af522fe1d5248c..0cb286ecdf3713a9e414429ed7c8208978f92590 100644 --- a/goblin/session.py +++ b/goblin/session.py @@ -25,11 +25,11 @@ import weakref import aiogremlin from aiogremlin.driver.protocol import Message from aiogremlin.driver.resultset import ResultSet -from aiogremlin.gremlin_python.driver.remote_connection import RemoteTraversal -from aiogremlin.gremlin_python.process.graph_traversal import __ -from aiogremlin.gremlin_python.process.traversal import ( +from gremlin_python.driver.remote_connection import RemoteTraversal +from aiogremlin.process.graph_traversal import __ +from gremlin_python.process.traversal import ( Cardinality, Traverser, Binding, Traverser) -from aiogremlin.gremlin_python.structure.graph import Vertex, Edge +from gremlin_python.structure.graph import Vertex, Edge from goblin import exception, mapper from goblin.element import GenericVertex, GenericEdge, VertexProperty @@ -189,13 +189,11 @@ class Session: hashable_id = self._get_hashable_id(obj.id) current = self.current.get(hashable_id, None) if isinstance(obj, Vertex): - props = await self._g.V(obj.id).valueMap(True).next() + # why doesn't this come in on the vertex? + label = await self._g.V(obj.id).label().next() if not current: - current = self.app.vertices.get( - props.get('label'), GenericVertex)() - props = await self._get_vertex_properties(current, props) - else: - props = await self._get_vertex_properties(current, props) + current = self.app.vertices.get(label, GenericVertex)() + props = await self._get_vertex_properties(obj.id, label) if isinstance(obj, Edge): props = await self._g.E(obj.id).valueMap(True).next() if not current: @@ -218,18 +216,26 @@ class Session: else: return result - async def _get_vertex_properties(self, element, props): - new_props = {} - for key, val in props.items(): - if key in element.__mapping__.db_properties: - key, _ = element.__mapping__.db_properties[key] - if isinstance(element.__properties__.get(key), VertexProperty): - trav = self._g.V( - props['id']).properties(key).valueMap(True) - vert_prop = await trav.toList() - new_props[key] = vert_prop - else: - new_props[key] = val + async def _get_vertex_properties(self, vid, label): + projection = self._g.V(vid).properties() \ + .project('id', 'key', 'value', 'meta') \ + .by(__.id()).by(__.key()).by(__.value()) \ + .by(__.valueMap()) + props = await projection.toList() + new_props = {'label': label, 'id': vid} + for prop in props: + key = prop['key'] + val = prop['value'] + # print('val_type', type(val)) + meta = prop['meta'] + new_props.setdefault(key, []) + if meta: + meta['key'] = key + meta['value'] = val + meta['id'] = prop['id'] + val = meta + + new_props[key].append(val) return new_props # Creation API @@ -393,12 +399,14 @@ class Session: return await self._update_edge_properties(edge, traversal, props) # *metodos especiales privados for creation API + async def _simple_traversal(self, traversal, element): elem = await traversal.next() if elem: if element.__type__ == 'vertex': - props = await self._g.V(elem.id).valueMap(True).next() - props = await self._get_vertex_properties(element, props) + # Look into this + label = await self._g.V(elem.id).label().next() + props = await self._get_vertex_properties(elem.id, label) elif element.__type__ == 'edge': props = await self._g.E(elem.id).valueMap(True).next() elem = element.__mapping__.mapper_func( @@ -485,17 +493,17 @@ class Session: # Make sure to get vp ids here. for key, val in metaprops.items(): if val: - # prop_name = vertex.__mapping__.db_properties[db_name][0] - # vp = vertex.__properties__[prop_name] - # # Select and add by id here if possible - # if vp.cardinality == Cardinality.single: - # traversal = self._g.V(Binding('vid', result.id)).properties( - # db_name).property(key, val) - # else: - # traversal = self._g.V(Binding('vid', result.id)).properties( - # db_name).hasValue(value).property(key, val) - # await traversal.iterate() - pass + prop_name = vertex.__mapping__.db_properties[db_name][0] + vp = vertex.__properties__[prop_name] + # Select and add by id here if possible + if vp.cardinality == Cardinality.single: + traversal = self._g.V(Binding('vid', result.id)).properties( + db_name).property(key, val) + else: + traversal = self._g.V(Binding('vid', result.id)).properties( + db_name).hasValue(value).property(key, val) + await traversal.iterate() + # pass else: potential_removals.append((db_name, key, value)) return potential_removals diff --git a/requirements.txt b/requirements.txt index 0230f1201b8933071145d829ac5f07f260d51f86..21e7a34b8c5eb3ffe00298c6079e484559788092 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,43 @@ -aiogremlin==3.2.4 -coveralls==1.1 +aenum==1.4.5 +aiogremlin==3.2.6 +aiohttp==2.2.5 +alabaster==0.7.10 +async-generator==1.8 +async-timeout==1.4.0 +Babel==2.5.0 +certifi==2017.7.27.1 +chardet==3.0.4 +decorator==4.1.2 +docutils==0.14 +-e git+https://github.com/davebshow/goblin.git@961486ed98f7687768a68d80f415df3f23889483#egg=goblin +gremlinpython==3.2.6 +idna==2.6 +imagesize==0.7.1 inflection==0.3.1 +ipdb==0.10.3 +ipython==6.1.0 +ipython-genutils==0.2.0 +jedi==0.10.2 +Jinja2==2.9.6 +MarkupSafe==1.0 +multidict==3.1.3 +pexpect==4.2.1 +pickleshare==0.7.4 +prompt-toolkit==1.0.15 +ptyprocess==0.5.2 +py==1.4.34 +Pygments==2.2.0 +pytest==3.2.2 +pytest-asyncio==0.7.0 +pytz==2017.2 +PyYAML==3.12 +requests==2.18.4 +simplegeneric==0.8.1 +six==1.10.0 +snowballstemmer==1.2.1 +Sphinx==1.6.3 +sphinxcontrib-websupport==1.0.1 +traitlets==4.3.2 +urllib3==1.22 +wcwidth==0.1.7 +yarl==0.12.0 diff --git a/setup.py b/setup.py index 9b34b4227d89c9cc422a765a8b3347a625deca04..1ce568e75135803bade28d9e8a1e30a1f59662cd 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup setup( name="goblin", - version="2.0.0", + version="2.1.0", url="", license="AGPL", author="davebshow", @@ -11,7 +11,7 @@ setup( description="Python toolkit for TP3 Gremlin Server", packages=["goblin", "goblin.driver"], install_requires=[ - "aiogremlin==3.2.4", + "aiogremlin==3.2.6", "inflection==0.3.1", ], test_suite="tests", diff --git a/tests/conftest.py b/tests/conftest.py index 98a6bd49bdedcac2f2ab828fab69af4e99a3be59..9dee508189eb5d96bc9018360f5598ec09f453d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ # along with Goblin. If not, see <http://www.gnu.org/licenses/>. import asyncio import pytest -from aiogremlin.gremlin_python.process.traversal import Cardinality +from gremlin_python.process.traversal import Cardinality from goblin import Goblin, driver, element, properties from goblin.driver import ( Connection, DriverRemoteConnection, GraphSONMessageSerializer) diff --git a/tests/test_app.py b/tests/test_app.py index 17d0fe9e54caf6562022f792f1df2ad246c1435a..9ef40e66ff7218183c32851d3a24052b1a51c88a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -19,7 +19,6 @@ import pytest import goblin from goblin import element -from aiogremlin.gremlin_python import process @pytest.mark.asyncio diff --git a/tests/test_connection.py b/tests/test_connection.py index 5b47b66c7220963fb57e1aaafc68626f0b527e0c..1bcae41bbdde70f25c1bd5655a9518aee0d140b5 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -23,7 +23,7 @@ import pytest import aiohttp from aiohttp import web -from aiogremlin.gremlin_python.driver import request +from gremlin_python.driver import request from goblin import driver from aiogremlin import exception diff --git a/tests/test_graph.py b/tests/test_graph.py index b54e75ba3cacc07edea6df08dcc4327233462ed2..9e62f644d29fd4b0afe7608d66c298ff1721be5e 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -19,8 +19,8 @@ import pytest from goblin import driver -from aiogremlin.gremlin_python import process -from aiogremlin.gremlin_python.process.traversal import Binding +from aiogremlin import process +from gremlin_python.process.traversal import Binding @pytest.mark.asyncio @@ -28,7 +28,7 @@ async def test_generate_traversal(remote_graph, remote_connection): async with remote_connection: g = remote_graph.traversal().withRemote(remote_connection) traversal = g.V().hasLabel(('v1', 'person')) - assert isinstance(traversal, process.graph_traversal.GraphTraversal) + assert isinstance(traversal, process.graph_traversal.AsyncGraphTraversal) assert traversal.bytecode.bindings['v1'] == 'person' diff --git a/tests/test_properties.py b/tests/test_properties.py index c264e5a1d581afbd040b16cb2ed085b4da3bac46..e3e5c159a62345a4280358a02af9792614f84c4e 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -17,7 +17,7 @@ import pytest -from aiogremlin.gremlin_python.statics import long +from gremlin_python.statics import long from goblin import element, exception, manager, properties @@ -148,7 +148,7 @@ def test_set_card_union(place): def test_set_card_64bit_integer(place): place.important_numbers = set([long(1), long(2), long(3)]) assert all(isinstance(i.value, long) for i in place.important_numbers) - + def test_set_card_validation_vertex_property(place): with pytest.raises(exception.ValidationError): place.important_numbers = set(['hello', 2, 3]) diff --git a/tests/test_session.py b/tests/test_session.py index 3b1f3fddccc80c8b90e0c44e5e2c168c17a66e0c..9bae9a5b8431c930399347747d7ffcb20aa5496c 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -22,7 +22,7 @@ import pytest from goblin import element from goblin.session import bindprop -from aiogremlin.gremlin_python.process.traversal import Binding +from gremlin_python.process.traversal import Binding def test_bindprop(person_class): diff --git a/tests/test_vertex_properties_functional.py b/tests/test_vertex_properties_functional.py index 0e83dff2c048fd21efe1a0b230c55dc68eebd6c1..d78060c9264a6fdf1188002296820582be46108b 100644 --- a/tests/test_vertex_properties_functional.py +++ b/tests/test_vertex_properties_functional.py @@ -17,7 +17,7 @@ import pytest from aiogremlin import Graph -from aiogremlin.gremlin_python.process.traversal import Cardinality +from gremlin_python.process.traversal import Cardinality @pytest.mark.asyncio @@ -86,29 +86,35 @@ async def test_add_update_set_card_property(app, place): @pytest.mark.asyncio async def test_metas(app, place, remote_connection): g = Graph().traversal().withRemote(remote_connection) - # Property API - v = await g.addV('person').property('name', 'dave').next() - props = await g.V(v.id).properties().toList() - meta = await g.V(v.id).properties('name').property('nickname', 'davebshow').next() - nickname = await g.V(v.id).properties('name').valueMap(True).next() - # List card - v2 = await g.addV('person').property(Cardinality.list_, 'name', 'dave').property(Cardinality.list_, 'name', 'dave brown').next() - props2 = await g.V(v2.id).properties().toList() - meta2 = await g.V(v2.id).properties('name').hasValue('dave').property('nickname', 'davebshow').next() - nickname2 = await g.V(v2.id).properties('name').valueMap(True).next() - session = await app.session() + place.zipcode = 98402 place.historical_name = ['Detroit'] place.historical_name('Detroit').notes = 'rock city' place.historical_name('Detroit').year = 1900 + place.historical_name.append('Other') + place.historical_name[-1].notes = 'unknown' + place.historical_name[-1].year = 1700 detroit = await session.save(place) + dprops = await g.V(detroit.id).properties().toList() + assert len(dprops) == 3 trav = g.V(detroit.id).properties('historical_name').valueMap(True) - dmetas = await trav.next() - + dmetas = await trav.toList() + assert dmetas[0]['value'] == 'Detroit' + assert dmetas[0]['notes'] == 'rock city' + assert dmetas[0]['year'] == 1900 + assert dmetas[1]['value'] == 'Other' + assert dmetas[1]['notes'] == 'unknown' + assert dmetas[1]['year'] == 1700 new_session = await app.session() new_detroit = await new_session.g.V(detroit.id).next() - + assert new_detroit.zipcode == detroit.zipcode + assert new_detroit.historical_name[-1].value == detroit.historical_name[-1].value + assert new_detroit.historical_name[-1].notes == detroit.historical_name[-1].notes + assert new_detroit.historical_name[-1].year == detroit.historical_name[-1].year + assert new_detroit.historical_name[0].value == detroit.historical_name[0].value + assert new_detroit.historical_name[0].notes == detroit.historical_name[0].notes + assert new_detroit.historical_name[0].year == detroit.historical_name[0].year await remote_connection.close() await app.close()