diff --git a/docs/driver.rst b/docs/driver.rst index 2a37e1a513426e8329fea03954a3033dbe72998c..81e68906f58f9734034f3972000a3c4d661a3615 100644 --- a/docs/driver.rst +++ b/docs/driver.rst @@ -9,7 +9,7 @@ Gremlin Server:: >>> import asyncio >>> from goblin import driver >>> loop = asyncio.get_event_loop() - >>> conn = await driver.Connection.open('ws://localhost:8182', loop) + >>> conn = await driver.Connection.open('ws://localhost:8182/gremlin', loop) The :py:class:`Connection<goblin.driver.connection.Connection>` object can be used to :py:meth:`submit<goblin.driver.connection.Connection.submit>` messages @@ -92,5 +92,5 @@ uses the following configuration: +-------------------+----------------------------------------------+-------------+ |message_serializer |String denoting the class used for message |'classpath' | | |serialization, currently only supports | | -| |basic GraphSONMessageSerializer | | +| |basic GraphSON2MessageSerializer | | +-------------------+----------------------------------------------+-------------+ diff --git a/docs/glv.rst b/docs/glv.rst new file mode 100644 index 0000000000000000000000000000000000000000..dd50e61aa7e31755f19a2daba444c1c4520b4dac --- /dev/null +++ b/docs/glv.rst @@ -0,0 +1,95 @@ +Using AsyncGraph (GLV) +====================== + +:py:mod:`Goblin` provides an 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 generated using the class +:py:class:`AsyncGraph<goblin.driver.graph.AsyncGraph>` combined with a remote +connection class, either :py:class:`Connection<goblin.driver.connection.Connection>` or +:py:class:`DriverRemoteConnection<goblin.driver.connection.DriverRemoteConnection>`:: + + >>> import asyncio + >>> from goblin import driver + + >>> loop = asyncio.get_event_loop() + >>> remote_conn = loop.run_until_complete( + ... driver.Connection.open( + ... "http://localhost:8182/gremlin", loop)) + >>> graph = driver.AsyncGraph() + >>> g = graph.traversal().withRemote(remote_conn) + +Once you have a traversal source, it's all Gremlin...:: + + >>> traversal = g.addV('query_language').property('name', 'gremlin') + +`traversal` is in an instance of +:py:class:`AsyncGraphTraversal<goblin.driver.graph.AsyncGraphTraversal>`, which +implements the Python 3.5 asynchronous iterator protocol:: + + >>> async def iterate_traversal(traversal): + >>> async for msg in traversal: + >>> print(msg) + + >>> loop.run_until_complete(iterate_traversal(traversal)) + # v[0] + +:py:class:`AsyncGraphTraversal<goblin.driver.graph.AsyncGraphTraversal>` also +provides several convenience methods to help iterate over results: + +- :py:meth:`next<goblin.driver.graph.AsyncGraphTraversal.next>` +- :py:meth:`toList<goblin.driver.graph.AsyncGraphTraversal.toList>` +- :py:meth:`toSet<goblin.driver.graph.AsyncGraphTraversal.toSet>` +- :py:meth:`oneOrNone<goblin.driver.graph.AsyncGraphTraversal.oneOrNone>` + +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... + +Note: Gremlin steps that are reserved words in Python, like `or`, `in`, use a +a trailing underscore `or_` and `in_`. + +The Side Effect Interface +------------------------- + +When using TinkerPop 3.2.2+ with the default +:py:class:`GraphSON2MessageSerializer<goblin.driver.serializer.GraphSON2MessageSerializer>`, +:py:mod:`Goblin` provides an asynchronous side effects interface using the +:py:class:`AsyncRemoteTraversalSideEffects<goblin.driver.graph.AsyncRemoteTraversalSideEffects>` +class. This allows side effects to be retrieved after executing the traversal:: + + >>> traversal = g.V().aggregate('a') + >>> results = loop.run_until_complete(traversal.toList()) + >>> print(results) + # [v[0]] + +Calling +:py:meth:`keys<goblin.driver.graph.AsyncRemoteTraversalSideEffects.keys>` +will then return an asynchronous iterator containing all keys for cached +side effects: + + >>> async def get_side_effect_keys(traversal): + ... resp = await traversal.side_effects.keys() + ... async for key in resp: + ... print(key) + + + >>> loop.run_until_complete(get_side_effect_keys(traversal)) + # 'a' + +Then calling +:py:meth:`get<goblin.driver.graph.AsyncRemoteTraversalSideEffects.get>` +using a valid key will return the cached side effects:: + + >>> async def get_side_effects(traversal): + ... resp = await traversal.side_effects.get('a') + ... async for side_effect in resp: + ... print(side_effect) + + + >>> loop.run_until_complete(get_side_effects(traversal)) + # v[0] + +And that's it! For more information on Gremlin Language Variants, please +visit the `Apache TinkerPop GLV Documentation`_. + + +.. _Apache TinkerPop GLV Documentation: http://tinkerpop.apache.org/docs/3.2.2/tutorials/gremlin-language-variants/ diff --git a/docs/goblin.driver.rst b/docs/goblin.driver.rst index 82a612bb700e4a26116ad23f322f9a51addd72d7..776807e89d5fccf56106292e722c4293c8e23851 100644 --- a/docs/goblin.driver.rst +++ b/docs/goblin.driver.rst @@ -44,6 +44,14 @@ goblin.driver.pool module :undoc-members: :show-inheritance: +goblin.driver.serializer module +------------------------------- + +.. automodule:: goblin.driver.serializer + :members: + :undoc-members: + :show-inheritance: + goblin.driver.server module --------------------------- diff --git a/docs/goblin.rst b/docs/goblin.rst index b75195c66a868d1d4ae7306c74458b2e8992af08..da9f726d77954985d490dfa8e22eb5134fa7d9a9 100644 --- a/docs/goblin.rst +++ b/docs/goblin.rst @@ -83,15 +83,6 @@ goblin.session module :undoc-members: :show-inheritance: -goblin.traversal module ------------------------ - -.. automodule:: goblin.traversal - :members: - :undoc-members: - :show-inheritance: - - Module contents --------------- diff --git a/docs/index.rst b/docs/index.rst index 66cc0db3343fda9da34b8c2ac635dd088aa4ba1e..1704c1732e48d19d3f558566c03999a406e2e75f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,7 +46,8 @@ Submit scripts and bindings to the `Gremlin Server`_:: >>> async def go(loop): ... script = "g.addV('developer').property(k1, v1)" ... bindings = {'k1': 'name', 'v1': 'Leif'} - ... conn = await driver.Connection.open('ws://localhost:8182/', loop) + ... conn = await driver.Connection.open( + ... 'ws://localhost:8182/gremlin', loop) ... async with conn: ... resp = await conn.submit(gremlin=script, bindings=bindings) ... async for msg in resp: @@ -58,28 +59,31 @@ Submit scripts and bindings to the `Gremlin Server`_:: For more information on using the driver, see the :doc:`Driver docs</driver>` -**AsyncRemoteGraph** +**AsyncGraph** Generate and submit Gremlin traversals in native Python:: - >>> from gremlin_python import process + >>> remote_conn = loop.run_until_complete( + ... driver.Connection.open( + ... "http://localhost:8182/gremlin", loop)) + >>> graph = driver.AsyncGraph() + >>> g = graph.traversal().withRemote(remote_conn) - >>> host = loop.run_until_complete( - ... driver.Connection.open("http://localhost:8182/", loop)) - >>> translator = process.GroovyTranslator('g') - >>> graph = driver.AsyncRemoteGraph(translator, connection) - >>> async def go(graph): - ... g = graph.traversal() - ... resp = await g.addV('developer').property('name', 'Leif').next() - ... async for msg in resp: + >>> async def go(g): + ... traversal = g.addV('developer').property('name', 'Leif') + ... async for msg in traversal: ... print(msg) - ... await graph.close() + ... await remote_conn.close() - >>> loop.run_until_complete(go(graph)) + + >>> 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:`goblin.driver.graph.AsyncGraph<AsyncGraph>`, +see the :doc:`GLV docs</glv>` + **OGM** @@ -123,9 +127,9 @@ database:: ... works_with = Knows(leif, jon) ... session.add(leif, jon, works_with) ... await session.flush() - ... result = await session.g.E(works_with.id).one_or_none() + ... result = await session.g.E(works_with.id).oneOrNone() ... assert result is works_with - ... people = await session.traversal(Person).all() # element class based traversal source + ... people = session.traversal(Person) # element class based traversal source ... async for person in people: ... print(person) @@ -142,12 +146,30 @@ for an element, that element will be updated to reflect these changes. For more information on using the OGM, see the :doc:`OGM docs</ogm>` +A note about GraphSON message serialization +------------------------------------------- + +The :py:mod:`goblin.driver` provides support for both GraphSON2 and GraphSON1 +out of the box. By default, it uses the +:py:class:`GraphSON2MessageSerializer<goblin.driver.serializer.GraphSON2MessageSerializer>`. +Since GraphSON2 was only recently included in the TinkerPop 3.2.2 release, +:py:mod:`goblin.driver` also ships with +:py:class:`GraphSONMessageSerializer<goblin.driver.serializer.GraphSONMessageSerializer>`. +In the near future (when projects like Titan and DSE support the 3.2 Gremlin +Server line), support for GraphsSON1 will be dropped. + +The :py:mod:`goblin<Goblin>` OGM still uses GraphSON1 by default and will do so +until :py:mod:`goblin.driver` support is dropped. It will then be updated to +use GraphSON2. + + Contents: .. toctree:: :maxdepth: 4 ogm + glv driver modules diff --git a/docs/ogm.rst b/docs/ogm.rst index bfdfb84fa61cb78867a68a4ea3ebecb47a970631..cf1b228eab111ae126ab57dc03fe52a1b220744a 100644 --- a/docs/ogm.rst +++ b/docs/ogm.rst @@ -317,14 +317,21 @@ the value to be specified:: >>> traversal = session.traversal(Person) >>> traversal.has(bindprop(Person, 'name', 'Leifur', binding='v1')) -Finally, to submit a traversal, :py:mod:`Goblin` provides two methods: -:py:meth:`all` and :py:meth:`one_or_none`. :py:meth:`all` returns all results -produced by the traversal, while :py:meth:`one_or_none` returns either the last -result, or in the case that the traversal did not return results, `None`. Remember -to `await` the traversal when calling these methods:: +Finally, there are a variety of ways to to submit a traversal to the server. +First of all, all traversals are themselve asynchronous iterators, and using +them as such will cause a traversal to be sent on the wire: + + >>> async for msg in session.g.V().hasLabel('person'): + ... print(msg) + +Furthermore, :py:mod:`Goblin` provides several convenience methods that +submit a traversal as well as process the results :py:meth:`toList`, +:py:meth:`toSet` and :py:meth:`oneOrNone`. These methods both submit a script +to the server and iterate over the results. Remember to `await` the traversal +when calling these methods:: >>> traversal = session.traversal(Person) >>> leif = await traversal.has( - ... bindprop(Person, 'name', 'Leifur', binding='v1')).one_or_none() + ... bindprop(Person, 'name', 'Leifur', binding='v1')).oneOrNone() And that is pretty much it. We hope you enjoy the :py:mod:`Goblin` OGM. diff --git a/goblin/app.py b/goblin/app.py index e2228e6fec90382195213ab18745361caf2850fc..c8c56a3453645db9b34c206b6bfa3849fe1fc507 100644 --- a/goblin/app.py +++ b/goblin/app.py @@ -40,19 +40,14 @@ class Goblin: :param dict config: Config parameters for application """ - def __init__(self, cluster, *, translator=None, traversal_source=None, - get_hashable_id=None, aliases=None): + def __init__(self, cluster, *, get_hashable_id=None, aliases=None): self._cluster = cluster self._loop = self._cluster._loop - self._traversal_source = traversal_source self._transactions = None self._cluster = cluster self._vertices = collections.defaultdict( lambda: element.GenericVertex) self._edges = collections.defaultdict(lambda: element.GenericEdge) - if not translator: - translator = process.GroovyTranslator('g') - self._translator = translator if not get_hashable_id: get_hashable_id = lambda x: x self._get_hashable_id = get_hashable_id @@ -62,7 +57,11 @@ class Goblin: @classmethod async def open(cls, loop, *, get_hashable_id=None, aliases=None, **config): - cluster = await driver.Cluster.open(loop, aliases=aliases, **config) + # App currently only supports GraphSON 1 + cluster = await driver.Cluster.open( + loop, aliases=aliases, + message_serializer=driver.GraphSONMessageSerializer, + **config) app = Goblin(cluster, get_hashable_id=get_hashable_id, aliases=aliases) await app.supports_transactions() return app @@ -81,11 +80,6 @@ class Goblin: """Registered edge classes""" return self._edges - @property - def translator(self): - """gremlin-python translator class""" - return self._translator - @property def url(self): """Database url""" @@ -103,8 +97,27 @@ class Goblin: if element.__type__ == 'edge': self._edges[element.__label__] = element + def config_from_file(self, filename): + """ + Load configuration from from file. + + :param str filename: Path to the configuration file. + """ + self._cluster.config_from_file(filename) + + def config_from_yaml(self, filename): + self._cluster.config_from_yaml(filename) + + def config_from_json(self, filename): + """ + Load configuration from from JSON file. + + :param str filename: Path to the configuration file. + """ + self._cluster.config_from_json(filename) + def register_from_module(self, modulename): - pass + raise NotImplementedError async def session(self, *, use_session=False, processor='', op='eval', aliases=None): @@ -131,6 +144,7 @@ class Goblin: gremlin='graph.features().graph().supportsTransactions()', aliases=self._aliases) msg = await stream.fetch_data() + msg = msg.object stream.close() self._transactions = msg return self._transactions diff --git a/goblin/driver/__init__.py b/goblin/driver/__init__.py index 565c9b555e98c11469c41fe4df9f0939d4aac938..f60c40ba52da3f22a2c584e4528530f732588036 100644 --- a/goblin/driver/__init__.py +++ b/goblin/driver/__init__.py @@ -16,8 +16,9 @@ # along with Goblin. If not, see <http://www.gnu.org/licenses/>. from goblin.driver.cluster import Cluster -from goblin.driver.client import Client +from goblin.driver.client import Client, SessionedClient from goblin.driver.connection import AbstractConnection, Connection -from goblin.driver.graph import AsyncRemoteGraph -from goblin.driver.serializer import GraphSONMessageSerializer +from goblin.driver.graph import AsyncGraph +from goblin.driver.serializer import ( + GraphSONMessageSerializer, GraphSON2MessageSerializer) from goblin.driver.server import GremlinServer diff --git a/goblin/driver/client.py b/goblin/driver/client.py index 0fedefef847c63f0c89200448323fdbe87767405..b00e51f291afaee75b485fab5dacfb1b98a3738d 100644 --- a/goblin/driver/client.py +++ b/goblin/driver/client.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU Affero General Public License # along with Goblin. If not, see <http://www.gnu.org/licenses/>. +from goblin import exception + class Client: """ @@ -26,8 +28,8 @@ class Client: client :param asyncio.BaseEventLoop loop: """ - def __init__(self, cluster, loop, *, aliases=None, - processor=None, op=None): + def __init__(self, cluster, loop, *, aliases=None, processor=None, + op=None): self._cluster = cluster self._loop = loop if aliases is None: @@ -40,6 +42,10 @@ class Client: op = 'eval' self._op = op + @property + def message_serializer(self): + return self.cluster.config['message_serializer'] + @property def cluster(self): """ @@ -63,11 +69,11 @@ class Client: **args): """ **coroutine** Submit a script and bindings to the Gremlin Server. + :param str processor: Gremlin Server processor argument :param str op: Gremlin Server op argument :param args: Keyword arguments for Gremlin Server. Depend on processor and op. - :returns: :py:class:`Response` object """ processor = processor or self._processor @@ -81,3 +87,25 @@ class Client: processor=processor, op=op, **args) self._loop.create_task(conn.release_task(resp)) return resp + + +class SessionedClient(Client): + + def __init__(self, cluster, loop, session, *, aliases=None): + super().__init__(cluster, loop, aliases=aliases, processor='session', + op='eval') + self._session = session + + @property + def session(self): + return self._session + + async def submit(self, **args): + if not args.get('gremlin', ''): + raise exception.ClientError('Session requires a gremlin string') + return await super().submit(processor='session', op='eval', + session=self.session, + **args) + + async def close(self): + raise NotImplementedError diff --git a/goblin/driver/cluster.py b/goblin/driver/cluster.py index d93bee03ad60341126f39de3871551bc4ba8b9de..5e0024a3697a52512632f4bf978900c754610876 100644 --- a/goblin/driver/cluster.py +++ b/goblin/driver/cluster.py @@ -56,7 +56,7 @@ class Cluster: 'min_conns': 1, 'max_times_acquired': 16, 'max_inflight': 64, - 'message_serializer': 'goblin.driver.GraphSONMessageSerializer' + 'message_serializer': 'goblin.driver.GraphSON2MessageSerializer' } def __init__(self, loop, aliases=None, **config): @@ -108,7 +108,7 @@ class Cluster: if not self._hosts: await self.establish_hosts() host = self._hosts.popleft() - conn = await host.connect() + conn = await host.get_connection() self._hosts.append(host) return conn @@ -164,7 +164,8 @@ class Cluster: def config_from_module(self, filename): raise NotImplementedError - async def connect(self, processor=None, op=None, aliases=None): + async def connect(self, processor=None, op=None, aliases=None, + session=None): """ **coroutine** Get a connected client. Main API method. @@ -173,8 +174,15 @@ class Cluster: aliases = aliases or self._aliases if not self._hosts: await self.establish_hosts() - return driver.Client(self, self._loop, processor=processor, op=op, - aliases=aliases) + if session: + host = self._hosts.popleft() + client = driver.SessionedClient(host, self._loop, session, + aliases=aliases) + self._hosts.append(host) + else: + client = driver.Client(self, self._loop, processor=processor, + op=op, aliases=aliases) + return client async def close(self): """**coroutine** Close cluster and all connected hosts.""" diff --git a/goblin/driver/connection.py b/goblin/driver/connection.py index f4cb864307cbc57822f44a15e4487f78cefab59c..1b63c42707e98a6f5db172287039ed28e10fe684 100644 --- a/goblin/driver/connection.py +++ b/goblin/driver/connection.py @@ -53,12 +53,17 @@ def error_handler(fn): class Response: """Gremlin Server response implementated as an async iterator.""" - def __init__(self, response_queue, timeout, loop): + def __init__(self, response_queue, request_id, timeout, loop): self._response_queue = response_queue + self._request_id = request_id self._loop = loop self._timeout = timeout self._done = asyncio.Event(loop=self._loop) + @property + def request_id(self): + return self._request_id + @property def done(self): """ @@ -74,7 +79,7 @@ class Response: async def __anext__(self): msg = await self.fetch_data() if msg: - return msg + return msg.object else: raise StopAsyncIteration @@ -147,11 +152,10 @@ class Connection(AbstractConnection): message_serializer = message_serializer() self._message_serializer = message_serializer - @classmethod async def open(cls, url, loop, *, ssl_context=None, username='', password='', max_inflight=64, response_timeout=None, - message_serializer=serializer.GraphSONMessageSerializer): + message_serializer=serializer.GraphSON2MessageSerializer): """ **coroutine** Open a connection to the Gremlin Server. @@ -173,6 +177,10 @@ class Connection(AbstractConnection): return cls(url, ws, loop, client_session, username, password, max_inflight, response_timeout, message_serializer) + @property + def message_serializer(self): + return self._message_serializer + @property def closed(self): """ @@ -198,6 +206,7 @@ class Connection(AbstractConnection): **args): """ Submit a script and bindings to the Gremlin Server + :param str processor: Gremlin Server processor argument :param str op: Gremlin Server op argument :param args: Keyword arguments for Gremlin Server. Depend on processor @@ -213,7 +222,7 @@ class Connection(AbstractConnection): if self._ws.closed: self._ws = await self.client_session.ws_connect(self.url) self._ws.send_bytes(message) - resp = Response(response_queue, self._response_timeout, self._loop) + resp = Response(response_queue, request_id, self._response_timeout, self._loop) self._loop.create_task(self._terminate_response(resp, request_id)) return resp @@ -266,9 +275,11 @@ class Connection(AbstractConnection): else: if data: for result in data: + result = self._message_serializer.deserialize_message(result) message = Message(status_code, result, msg) response_queue.put_nowait(message) else: + data = self._message_serializer.deserialize_message(data) message = Message(status_code, data, msg) response_queue.put_nowait(message) if status_code != 206: @@ -280,3 +291,6 @@ class Connection(AbstractConnection): async def __aexit__(self, exc_type, exc, tb): await self.close() self._conn = None + + +DriverRemoteConnection = Connection diff --git a/goblin/driver/graph.py b/goblin/driver/graph.py index 553cfffefc0fe99c483ded423a15eb5c61021980..4720b8ddcb7bf9b1e3d986b9498e27d943130b1c 100644 --- a/goblin/driver/graph.py +++ b/goblin/driver/graph.py @@ -17,75 +17,151 @@ """A temporary solution to allow integration with gremlin_python package.""" +import functools + from gremlin_python.process.graph_traversal import ( - GraphTraversalSource, GraphTraversal) -from gremlin_python.process.traversal import ( - TraversalStrategy, TraversalStrategies) + GraphTraversal, GraphTraversalSource) +from gremlin_python.process.traversal import TraversalStrategies +from gremlin_python.driver.remote_connection import ( + RemoteStrategy, RemoteTraversalSideEffects) +from gremlin_python.structure.graph import Graph +from goblin.driver.serializer import GraphSON2MessageSerializer -class AsyncGraphTraversal(GraphTraversal): - def __init__(self, graph, traversal_strategies, bytecode): - GraphTraversal.__init__(self, graph, traversal_strategies, bytecode) +class AsyncRemoteTraversalSideEffects(RemoteTraversalSideEffects): - def __repr__(self): - return self.graph.translator.translate(self.bytecode) + async def keys(self): + return await self.keys_lambda() - def toList(self): - raise NotImplementedError - - def toSet(self): - raise NotImplementedError + async def get(self, key): + return await self.value_lambda(sideEffectKey=key) - async def next(self): - resp = await self.traversal_strategies.apply(self) - return resp +class AsyncRemoteStrategy(RemoteStrategy): -class AsyncRemoteStrategy(TraversalStrategy): async def apply(self, traversal): - result = await traversal.graph.remote_connection.submit( - gremlin=traversal.graph.translator.translate(traversal.bytecode), - bindings=traversal.bindings, - lang=traversal.graph.translator.target_language) + if isinstance(self.remote_connection.message_serializer, + GraphSON2MessageSerializer): + processor = 'traversal' + op = 'bytecode' + side_effects = AsyncRemoteTraversalSideEffects + else: + processor = '' + op = 'eval' + side_effects = None + if traversal.traversers is None: + resp = await self.remote_connection.submit( + gremlin=traversal.bytecode, processor=processor, op=op) + traversal.traversers = resp + if side_effects: + keys_lambda = functools.partial(self.remote_connection.submit, + processor='traversal', + op='keys', + sideEffect=resp.request_id) + value_lambda = functools.partial(self.remote_connection.submit, + processor='traversal', + op='gather', + sideEffect=resp.request_id) + side_effects = side_effects(keys_lambda, value_lambda) + traversal.side_effects = side_effects + + + +class AsyncGraphTraversal(GraphTraversal): + + async def __aiter__(self): + return self + + async def __anext__(self): + if self.traversers is None: + await self._get_traversers() + if self.last_traverser is None: + self.last_traverser = await self.traversers.fetch_data() + if self.last_traverser is None: + raise StopAsyncIteration + obj = self.last_traverser.object + self.last_traverser.bulk = self.last_traverser.bulk - 1 + if self.last_traverser.bulk <= 0: + self.last_traverser = None + return obj + + async def _get_traversers(self): + for ts in self.traversal_strategies.traversal_strategies: + await ts.apply(self) + + async def next(self, amount=None): + """ + **coroutine** Return the next result from the iterator. + + :param int amount: The number of results returned, defaults to None + (1 result) + """ + if amount is None: + try: + return await self.__anext__() + except StopAsyncIteration: + pass + else: + count = 0 + tempList = [] + while count < amount: + count = count + 1 + try: temp = await self.__anext__() + except StopIteration: return tempList + tempList.append(temp) + return tempList + + async def toList(self): + """**coroutine** Submit the travesal, iterate results, return a list""" + results = [] + async for msg in self: + results.append(msg) + return results + + async def toSet(self): + """**coroutine** Submit the travesal, iterate results, return a set""" + results = set() + async for msg in self: + results.add(msg) + return results + + async def oneOrNone(self): + """ + **coroutine** Get one or zero results from a traveral. Returns last + iterated result. + """ + result = None + async for msg in self: + result = msg return result + def iterate(self): + raise NotImplementedError -class AsyncGraph: - def traversal(self): - return GraphTraversalSource(self, self.traversal_strategy, - graph_traversal=self.graph_traversal) - - -class AsyncRemoteGraph(AsyncGraph): - """ - Generate asynchronous gremlin traversals using native Python. - - :param gremlin_python.process.GroovyTranslator translator: - gremlin_python translator class, typically - :py:class:`GroovyTranslator<gremlin_python.process.GroovyTranslator>` - :param goblin.driver.connection connection: underlying remote - connection - :param gremlin_python.process.GraphTraversal graph_traversal: - Custom graph traversal class - """ - def __init__(self, translator, remote_connection, *, graph_traversal=None): - self.traversal_strategy = AsyncRemoteStrategy() # A single traversal strategy - self.translator = translator - self.remote_connection = remote_connection - if graph_traversal is None: - graph_traversal = AsyncGraphTraversal - self.graph_traversal = graph_traversal + def nextTraverser(self): + raise NotImplementedError - def __repr__(self): - return "remotegraph[" + self.remote_connection.url + "]" - async def close(self): - """Close underlying remote connection""" - await self.remote_connection.close() - self.remote_connection = None +class AsyncGraph(Graph): + """Generate asynchronous gremlin traversals using native Python""" - async def __aenter__(self): - return self + def traversal(self, *, graph_traversal=None, remote_strategy=None): + """ + Get a traversal source from the Graph + + :param gremlin_python.process.GraphTraversal graph_traversal: + Custom graph traversal class + :param gremlin_python.driver.remote_connection.RemoteStrategy remote_strategy: + Custom remote strategy class - async def __aexit__(self, exc_type, exc, tb): - await self.close() + :returns: + :py:class:`gremlin_python.process.graph_traversal.GraphTraversalSource` + """ + if graph_traversal is None: + graph_traversal = AsyncGraphTraversal + if remote_strategy is None: + remote_strategy = AsyncRemoteStrategy + return GraphTraversalSource( + self, TraversalStrategies.global_cache[self.__class__], + remote_strategy=remote_strategy, + graph_traversal=graph_traversal) diff --git a/goblin/driver/serializer.py b/goblin/driver/serializer.py index 8c1f2230ee02298005cbe66a9ed42a95faa30aa0..d39a759e6aafd6f94d17641d04528fc79ac49c3c 100644 --- a/goblin/driver/serializer.py +++ b/goblin/driver/serializer.py @@ -17,19 +17,132 @@ import json +from gremlin_python.process.traversal import Bytecode, Traverser +from gremlin_python.process.translator import GroovyTranslator +from gremlin_python.structure.io.graphson import GraphSONWriter, GraphSONReader + + +class Processor: + """Base class for OpProcessor serialization system.""" + + def get_op(self, op): + op = getattr(self, op, None) + if not op: + raise Exception("Processor does not support op") + return op + class GraphSONMessageSerializer: + """Message serializer for GraphSONv1""" + # processors and ops + class standard(Processor): + + def authentication(self, args): + return args + + def eval(self, args): + gremlin = args['gremlin'] + if isinstance(gremlin, Bytecode): + translator = GroovyTranslator('g') + args['gremlin'] = translator.translate(gremlin) + args['bindings'] = gremlin.bindings + return args + + + class session(standard): + pass + + + def get_processor(self, processor): + processor = getattr(self, processor, None) + if not processor: + raise Exception("Unknown processor") + return processor() def serialize_message(self, request_id, processor, op, **args): + if not processor: + processor_obj = self.get_processor('standard') + else: + processor_obj = self.get_processor(processor) + op_method = processor_obj.get_op(op) + args = op_method(args) + message = self.build_message(request_id, processor, op, args) + return message + + def build_message(self, request_id, processor, op, args): message = { 'requestId': request_id, 'processor': processor, 'op': op, 'args': args } + return self.finalize_message(message, b'\x10', b'application/json') + + def finalize_message(self, message, mime_len, mime_type): message = json.dumps(message) - mime_len = b'\x10' - mime_type = b'application/json' message = b''.join([mime_len, mime_type, message.encode('utf-8')]) - print(message) return message + + def deserialize_message(self, message): + return Traverser(message) + + +class GraphSON2MessageSerializer(GraphSONMessageSerializer): + """Message serializer for GraphSONv2""" + + class session(GraphSONMessageSerializer.session): + + def close(self, args): + return args + + + class traversal(Processor): + + def authentication(self, args): + return args + + def bytecode(self, args): + gremlin = args['gremlin'] + args['gremlin'] = GraphSONWriter.writeObject(gremlin) + aliases = args.get('aliases', '') + if not aliases: + aliases = {'g': 'g'} + args['aliases'] = aliases + return args + + def close(self, args): + return self.keys(args) + + def gather(self, args): + side_effect = args['sideEffect'] + args['sideEffect'] = {'@type': 'g:UUID', '@value': side_effect} + aliases = args.get('aliases', '') + if not aliases: + aliases = {'g': 'g'} + args['aliases'] = aliases + return args + + def keys(self, args): + side_effect = args['sideEffect'] + args['sideEffect'] = {'@type': 'g:UUID', '@value': side_effect} + return args + + def build_message(self, request_id, processor, op, args): + message = { + 'requestId': {'@type': 'g:UUID', '@value': request_id}, + 'processor': processor, + 'op': op, + 'args': args + } + return self.finalize_message(message, b"\x21", + b"application/vnd.gremlin-v2.0+json") + + def deserialize_message(self, message): + if isinstance(message, dict): + if message.get('@type', '') == 'g:Traverser': + obj = GraphSONReader._objectify(message) + else: + obj = Traverser(message.get('@value', message)) + else: + obj = Traverser(message) + return obj diff --git a/goblin/driver/server.py b/goblin/driver/server.py index 130e59e80b5ae9072b89ee0c86023d03f3fbb680..3ba5a24b1fd0763b11cbbdfbad2b2e7a57f1425e 100644 --- a/goblin/driver/server.py +++ b/goblin/driver/server.py @@ -70,7 +70,7 @@ class GremlinServer: await self._pool.close() self._pool = None - async def connect(self): + async def get_connection(self): """**coroutine** Acquire a connection from the pool.""" try: conn = await self._pool.acquire() diff --git a/goblin/exception.py b/goblin/exception.py index ccdfdef7f6b86a4da6a8014b6e30863f22d56bb9..203102b9e8db4217ac6903fc63ccb9492402b011 100644 --- a/goblin/exception.py +++ b/goblin/exception.py @@ -15,6 +15,11 @@ # You should have received a copy of the GNU Affero General Public License # along with Goblin. If not, see <http://www.gnu.org/licenses/>. + +class ClientError(Exception): + pass + + class MappingError(Exception): pass diff --git a/goblin/session.py b/goblin/session.py index e832e6bcdd718d34bdc055ef55aa9d5652da7243..6a1671acf961a5c1e46ef458ec032aa5ea3663e4 100644 --- a/goblin/session.py +++ b/goblin/session.py @@ -22,14 +22,77 @@ import collections import logging import weakref -from goblin import exception, mapper, traversal +from goblin import cardinality, exception, mapper from goblin.driver import connection, graph from goblin.element import GenericVertex +from gremlin_python.driver.remote_connection import RemoteStrategy +from gremlin_python.process.traversal import Cardinality + + logger = logging.getLogger(__name__) +def bindprop(element_class, ogm_name, val, *, binding=None): + """ + Helper function for binding ogm properties/values to corresponding db + properties/values for traversals. + + :param goblin.element.Element element_class: User defined element class + :param str ogm_name: Name of property as defined in the ogm + :param val: The property value + :param str binding: The binding for val (optional) + + :returns: tuple object ('db_property_name', ('binding(if passed)', val)) + """ + db_name = getattr(element_class, ogm_name, ogm_name) + _, data_type = element_class.__mapping__.ogm_properties[ogm_name] + val = data_type.to_db(val) + if binding: + val = (binding, val) + return db_name, val + + +class TraversalResponse: + """Asynchronous iterator that encapsulates a traversal response queue""" + def __init__(self, response_queue, request_id): + self._queue = response_queue + self._request_id = request_id + self._done = False + + @property + def request_id(self): + return self._request_id + + async def __aiter__(self): + return self + + async def __anext__(self): + if self._done: + return + msg = await self.fetch_data() + if msg: + return msg + else: + self._done = True + raise StopAsyncIteration + + async def fetch_data(self): + return await self._queue.get() + + +class GoblinAsyncRemoteStrategy(RemoteStrategy): + + async def apply(self, traversal): + + if traversal.traversers is None: + resp = await self.remote_connection.submit( + gremlin=traversal.bytecode, processor='', op='eval') + traversal.traversers = resp + traversal.side_effects = None + + class Session(connection.AbstractConnection): """ Provides the main API for interacting with the database. Does not @@ -50,10 +113,15 @@ class Session(connection.AbstractConnection): self._pending = collections.deque() self._current = weakref.WeakValueDictionary() self._get_hashable_id = get_hashable_id - remote_graph = graph.AsyncRemoteGraph( - self._app.translator, self, - graph_traversal=traversal.GoblinTraversal) - self._traversal_factory = traversal.TraversalFactory(remote_graph) + self._graph = graph.AsyncGraph() + + @property + def graph(self): + return self._graph + + @property + def message_serializer(self): + return self.conn.message_serializer @property def app(self): @@ -63,10 +131,6 @@ class Session(connection.AbstractConnection): def conn(self): return self._conn - @property - def traversal_factory(self): - return self._traversal_factory - @property def current(self): return self._current @@ -93,19 +157,40 @@ class Session(connection.AbstractConnection): :py:class:`goblin.gremlin_python.process.GraphTraversalSource` object """ - return self.traversal_factory.traversal() + return self.traversal() - def traversal(self, element_class): + @property + def _g(self): + """ + Traversal source for internal use. Uses undelying conn. Doesn't + trigger complex deserailization. """ - Get a traversal spawned from an element class. + return self.graph.traversal( + graph_traversal=graph.AsyncGraphTraversal, + remote_strategy=GoblinAsyncRemoteStrategy).withRemote(self.conn) - :param :goblin.element.Element element_class: Element class - used to spawn traversal. + def traversal(self, element_class=None): + """ + Generate a traversal using a user defined element class as a + starting point. - :returns: :py:class:`GoblinTraversal<goblin.traversal.GoblinTraversal>` - object + :param goblin.element.Element element_class: An optional element + class that will dictate the element type (vertex/edge) as well as + the label for the traversal source + + :returns: :py:class:`AsyncGraphTraversal` """ - return self.traversal_factory.traversal(element_class=element_class) + traversal = self.graph.traversal( + graph_traversal=graph.AsyncGraphTraversal, + remote_strategy=GoblinAsyncRemoteStrategy).withRemote(self) + if element_class: + label = element_class.__mapping__.label + if element_class.__type__ == 'vertex': + traversal = traversal.V() + if element_class.__type__ == 'edge': + traversal = traversal.E() + traversal = traversal.hasLabel(label) + return traversal async def submit(self, **args): @@ -124,25 +209,30 @@ class Session(connection.AbstractConnection): response_queue = asyncio.Queue(loop=self._loop) self._loop.create_task( self._receive(async_iter, response_queue)) - return traversal.TraversalResponse(response_queue) + return TraversalResponse(response_queue, async_iter.request_id) async def _receive(self, async_iter, response_queue): - async for result in async_iter: - if (isinstance(result, dict) and - result.get('type', '') in ['vertex', 'edge']): - hashable_id = self._get_hashable_id(result['id']) + while True: + result = await async_iter.fetch_data() + if result is None: + break + obj = result.object + if (isinstance(obj, dict) and + obj.get('type', '') in ['vertex', 'edge']): + hashable_id = self._get_hashable_id(obj['id']) current = self.current.get(hashable_id, None) if not current: - element_type = result['type'] - label = result['label'] + element_type = obj['type'] + label = obj['label'] if element_type == 'vertex': current = self.app.vertices[label]() else: current = self.app.edges[label]() current.source = GenericVertex() current.target = GenericVertex() - element = current.__mapping__.mapper_func(result, current) - response_queue.put_nowait(element) + element = current.__mapping__.mapper_func(obj, current) + result.object = element + response_queue.put_nowait(result) else: response_queue.put_nowait(result) response_queue.put_nowait(None) @@ -172,7 +262,7 @@ class Session(connection.AbstractConnection): :param goblin.element.Vertex vertex: Vertex to be removed """ - traversal = self.traversal_factory.remove_vertex(vertex) + traversal = self._g.V(vertex.id).drop() result = await self._simple_traversal(traversal, vertex) hashable_id = self._get_hashable_id(vertex.id) vertex = self.current.pop(hashable_id) @@ -185,7 +275,7 @@ class Session(connection.AbstractConnection): :param goblin.element.Edge edge: Element to be removed """ - traversal = self.traversal_factory.remove_edge(edge) + traversal = self._g.E(edge.id).drop() result = await self._simple_traversal(traversal, edge) hashable_id = self._get_hashable_id(edge.id) edge = self.current.pop(hashable_id) @@ -252,8 +342,7 @@ class Session(connection.AbstractConnection): :returns: :py:class:`Vertex<goblin.element.Vertex>` | None """ - return await self.traversal_factory.get_vertex_by_id( - vertex).one_or_none() + return await self.g.V(vertex.id).oneOrNone() async def get_edge(self, edge): """ @@ -263,8 +352,7 @@ class Session(connection.AbstractConnection): :returns: :py:class:`Edge<goblin.element.Edge>` | None """ - return await self.traversal_factory.get_edge_by_id( - edge).one_or_none() + return await self.g.E(edge.id).oneOrNone() async def update_vertex(self, vertex): """ @@ -276,7 +364,7 @@ class Session(connection.AbstractConnection): """ props = mapper.map_props_to_db(vertex, vertex.__mapping__) # vert_props = mapper.map_vert_props_to_db - traversal = self.g.V(vertex.id) + traversal = self._g.V(vertex.id) return await self._update_vertex_properties(vertex, traversal, props) async def update_edge(self, edge): @@ -288,7 +376,7 @@ class Session(connection.AbstractConnection): :returns: :py:class:`Edge<goblin.element.Edge>` object """ props = mapper.map_props_to_db(edge, edge.__mapping__) - traversal = self.g.E(edge.id) + traversal = self._g.E(edge.id) return await self._update_edge_properties(edge, traversal, props) # Transaction support @@ -308,13 +396,10 @@ class Session(connection.AbstractConnection): # *metodos especiales privados for creation API async def _simple_traversal(self, traversal, element): - stream = await self.conn.submit( - gremlin=repr(traversal), bindings=traversal.bindings) - msg = await stream.fetch_data() - stream.close() + msg = await traversal.oneOrNone() if msg: msg = element.__mapping__.mapper_func(msg, element) - return msg + return msg async def _save_element(self, elem, @@ -331,66 +416,57 @@ class Session(connection.AbstractConnection): result = await create_func(elem) return result - async def _add_vertex(self, elem): + async def _add_vertex(self, vertex): """Convenience function for generating crud traversals.""" - props = mapper.map_props_to_db(elem, elem.__mapping__) - traversal = self.g.addV(elem.__mapping__.label) - traversal, _, metaprops = self.traversal_factory.add_properties( - traversal, props) - result = await self._simple_traversal(traversal, elem) + props = mapper.map_props_to_db(vertex, vertex.__mapping__) + traversal = self._g.addV(vertex.__mapping__.label) + traversal, _, metaprops = self._add_properties(traversal, props) + result = await self._simple_traversal(traversal, vertex) if metaprops: await self._add_metaprops(result, metaprops) - traversal = self.traversal_factory.get_vertex_by_id(elem) - result = await self._simple_traversal(traversal, elem) + traversal = self._g.V(vertex.id) + result = await self._simple_traversal(traversal, vertex) return result - async def _add_edge(self, elem): + async def _add_edge(self, edge): """Convenience function for generating crud traversals.""" - props = mapper.map_props_to_db(elem, elem.__mapping__) - traversal = self.g.V(elem.source.id) - traversal = traversal.addE(elem.__mapping__._label) + props = mapper.map_props_to_db(edge, edge.__mapping__) + traversal = self._g.V(edge.source.id) + traversal = traversal.addE(edge.__mapping__._label) traversal = traversal.to( - self.g.V(elem.target.id)) - traversal, _, _ = self.traversal_factory.add_properties( + self._g.V(edge.target.id)) + traversal, _, _ = self._add_properties( traversal, props) - return await self._simple_traversal(traversal, elem) + return await self._simple_traversal(traversal, edge) async def _check_vertex(self, vertex): """Used to check for existence, does not update session vertex""" - traversal = self.g.V(vertex.id) - stream = await self.conn.submit(gremlin=repr(traversal)) - msg = await stream.fetch_data() - stream.close() + msg = await self._g.V(vertex.id).oneOrNone() return msg async def _check_edge(self, edge): """Used to check for existence, does not update session edge""" - traversal = self.g.E(edge.id) - stream = await self.conn.submit(gremlin=repr(traversal)) - msg = await stream.fetch_data() - stream.close() + msg = await self._g.E(edge.id).oneOrNone() return msg async def _update_vertex_properties(self, vertex, traversal, props): - traversal, removals, metaprops = self.traversal_factory.add_properties( - traversal, props) + traversal, removals, metaprops = self._add_properties(traversal, props) for k in removals: - await self.g.V(vertex.id).properties(k).drop().one_or_none() + await self._g.V(vertex.id).properties(k).drop().oneOrNone() result = await self._simple_traversal(traversal, vertex) if metaprops: removals = await self._add_metaprops(result, metaprops) for db_name, key, value in removals: - await self.g.V(vertex.id).properties( - db_name).has(key, value).drop().one_or_none() - traversal = self.traversal_factory.get_vertex_by_id(vertex) + await self._g.V(vertex.id).properties( + db_name).has(key, value).drop().oneOrNone() + traversal = self._g.V(vertex.id) result = await self._simple_traversal(traversal, vertex) return result async def _update_edge_properties(self, edge, traversal, props): - traversal, removals, _ = self.traversal_factory.add_properties( - traversal, props) + traversal, removals, _ = self._add_properties(traversal, props) for k in removals: - await self.g.E(edge.id).properties(k).drop().one_or_none() + await self._g.E(edge.id).properties(k).drop().oneOrNone() return await self._simple_traversal(traversal, edge) async def _add_metaprops(self, result, metaprops): @@ -399,12 +475,35 @@ class Session(connection.AbstractConnection): db_name, (binding, value), metaprops = metaprop for key, val in metaprops.items(): if val: - traversal = self.g.V(result.id).properties( + traversal = self._g.V(result.id).properties( db_name).hasValue(value).property(key, val) - stream = await self.conn.submit( - gremlin=repr(traversal), bindings=traversal.bindings) - await stream.fetch_data() - stream.close() + await traversal.oneOrNone() else: potential_removals.append((db_name, key, value)) return potential_removals + + def _add_properties(self, traversal, props): + binding = 0 + potential_removals = [] + potential_metaprops = [] + for card, db_name, val, metaprops in props: + if val: + key = ('k' + str(binding), db_name) + val = ('v' + str(binding), val) + if card: + # Maybe use a dict here as a translator + if card == cardinality.Cardinality.list: + card = Cardinality.list + elif card == cardinality.Cardinality.set: + card = Cardinality.set + else: + card = Cardinality.single + traversal = traversal.property(card, key, val) + else: + traversal = traversal.property(key, val) + binding += 1 + if metaprops: + potential_metaprops.append((db_name, val, metaprops)) + else: + potential_removals.append(db_name) + return traversal, potential_removals, potential_metaprops diff --git a/goblin/traversal.py b/goblin/traversal.py deleted file mode 100644 index f516a65c55276e12163257628e20ba97a8d39c62..0000000000000000000000000000000000000000 --- a/goblin/traversal.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright 2016 ZEROFAIL -# -# This file is part of Goblin. -# -# Goblin is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Goblin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Goblin. If not, see <http://www.gnu.org/licenses/>. - -"""Query API and helpers""" - -import asyncio -import functools -import logging - -from goblin import cardinality, element, mapper -from goblin.driver import connection, graph -from gremlin_python import process - - -logger = logging.getLogger(__name__) - - -def bindprop(element_class, ogm_name, val, *, binding=None): - """ - Helper function for binding ogm properties/values to corresponding db - properties/values for traversals. - - :param goblin.element.Element element_class: User defined element class - :param str ogm_name: Name of property as defined in the ogm - :param val: The property value - :param str binding: The binding for val (optional) - - :returns: tuple object ('db_property_name', ('binding(if passed)', val)) - """ - db_name = getattr(element_class, ogm_name, ogm_name) - _, data_type = element_class.__mapping__.ogm_properties[ogm_name] - val = data_type.to_db(val) - if binding: - val = (binding, val) - return db_name, val - - -class TraversalResponse: - """Asynchronous iterator that encapsulates a traversal response queue""" - def __init__(self, response_queue): - self._queue = response_queue - self._done = False - - async def __aiter__(self): - return self - - async def __anext__(self): - if self._done: - return - msg = await self._queue.get() - if msg: - return msg - else: - self._done = True - raise StopAsyncIteration - - -# This is all until we figure out GLV integration... -class GoblinTraversal(graph.AsyncGraphTraversal): - - async def all(self): - """ - Get all results from traversal. - - :returns: :py:class:`TraversalResponse` object - """ - return await self.next() - - async def one_or_none(self): - """ - Get one or zero results from a traveral. - - :returns: :py:class:`Element<goblin.element.Element>` object - """ - result = None - async for msg in await self.next(): - result = msg - return result - - -class TraversalFactory: - """Helper that wraps a AsyncRemoteGraph""" - def __init__(self, graph): - self._graph = graph - - @property - def graph(self): - return self._graph - - def traversal(self, *, element_class=None): - """ - Generate a traversal using a user defined element class as a - starting point. - - :param goblin.element.Element element_class: An optional element - class that will dictate the element type (vertex/edge) as well as - the label for the traversal source - - :returns: :py:class:`GoblinTraversal` - """ - traversal = self.graph.traversal() - if element_class: - label = element_class.__mapping__.label - traversal = self._graph.traversal() - if element_class.__type__ == 'vertex': - traversal = traversal.V() - if element_class.__type__ == 'edge': - traversal = traversal.E() - traversal = traversal.hasLabel(label) - return traversal - - def remove_vertex(self, elem): - """Convenience function for generating crud traversals.""" - return self.traversal().V(elem.id).drop() - - def remove_edge(self, elem): - """Convenience function for generating crud traversals.""" - return self.traversal().E(elem.id).drop() - - def get_vertex_by_id(self, elem): - """Convenience function for generating crud traversals.""" - return self.traversal().V(elem.id) - - def get_edge_by_id(self, elem): - """Convenience function for generating crud traversals.""" - return self.traversal().E(elem.id) - - def add_properties(self, traversal, props): - binding = 0 - potential_removals = [] - potential_metaprops = [] - for card, db_name, val, metaprops in props: - if val: - key = ('k' + str(binding), db_name) - val = ('v' + str(binding), val) - if card: - # Maybe use a dict here as a translator - if card == cardinality.Cardinality.list: - card = process.Cardinality.list - elif card == cardinality.Cardinality.set: - card = process.Cardinality.set - else: - card = process.Cardinality.single - traversal = traversal.property(card, key, val) - else: - traversal = traversal.property(key, val) - binding += 1 - if metaprops: - potential_metaprops.append((db_name, val, metaprops)) - else: - potential_removals.append(db_name) - return traversal, potential_removals, potential_metaprops diff --git a/gremlin_python/__init__.py b/gremlin_python/__init__.py index 518d8e008b256688b90e792343fd0cf1e7bb26e2..7626550738f12ccd5e18b8c06a8d68aaab882066 100644 --- a/gremlin_python/__init__.py +++ b/gremlin_python/__init__.py @@ -16,6 +16,5 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from . import statics __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' diff --git a/gremlin_python/driver/__init__.py b/gremlin_python/driver/__init__.py index 7e1c0f1a812f96353a3b4b12daf4db66b2ce4a51..7626550738f12ccd5e18b8c06a8d68aaab882066 100644 --- a/gremlin_python/driver/__init__.py +++ b/gremlin_python/driver/__init__.py @@ -16,7 +16,5 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from .remote_connection import RemoteConnection -from .rest_remote_connection import RESTRemoteConnection __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' diff --git a/gremlin_python/driver/remote_connection.py b/gremlin_python/driver/remote_connection.py index 1651b92ad7809e01fd6fecf06371edacd3a7b937..452fcaf5f6e5c6915bcdeafaa28851911002a46e 100644 --- a/gremlin_python/driver/remote_connection.py +++ b/gremlin_python/driver/remote_connection.py @@ -16,16 +16,64 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from abc import abstractmethod +import abc +import six + +from ..process.traversal import Traversal +from ..process.traversal import TraversalStrategy +from ..process.traversal import TraversalSideEffects __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' +@six.add_metaclass(abc.ABCMeta) class RemoteConnection(object): - def __init__(self, url): - self.url = url + def __init__(self, url, traversal_source): + self._url = url + self._traversal_source = traversal_source + + @property + def url(self): + return self._url + + @property + def traversal_source(self): + return self._traversal_source + + @abc.abstractmethod + def submit(self, bytecode): + print("sending " + bytecode + " to GremlinServer...") + return RemoteTraversal(iter([]), TraversalSideEffects()) + + def __repr__(self): + return "remoteconnection[" + self._url + "," + self._traversal_source + "]" + + +class RemoteTraversal(Traversal): + def __init__(self, traversers, side_effects): + Traversal.__init__(self, None, None, None) + self.traversers = traversers + self.side_effects = side_effects + + +class RemoteTraversalSideEffects(TraversalSideEffects): + def __init__(self, keys_lambda, value_lambda): + self.keys_lambda = keys_lambda + self.value_lambda = value_lambda + + def keys(self): + return self.keys_lambda() + + def get(self, key): + return self.value_lambda(key) + + +class RemoteStrategy(TraversalStrategy): + def __init__(self, remote_connection): + self.remote_connection = remote_connection - @abstractmethod - def submit(self, target_language, script, bindings): - print "sending " + script + " to GremlinServer..." - return iter([]) + def apply(self, traversal): + if traversal.traversers is None: + remote_traversal = self.remote_connection.submit(traversal.bytecode) + # traversal.side_effects = remote_traversal.side_effects + traversal.traversers = remote_traversal#.traversers diff --git a/gremlin_python/process/__init__.py b/gremlin_python/process/__init__.py index e93356e95724d6eaab0892313c6744643294229b..7626550738f12ccd5e18b8c06a8d68aaab882066 100644 --- a/gremlin_python/process/__init__.py +++ b/gremlin_python/process/__init__.py @@ -17,22 +17,4 @@ specific language governing permissions and limitations under the License. ''' -from .graph_traversal import GraphTraversal -from .graph_traversal import GraphTraversalSource -from .graph_traversal import __ -from .groovy_translator import GroovyTranslator -from .jython_translator import JythonTranslator -from .traversal import Barrier -from .traversal import Bytecode -from .traversal import Cardinality -from .traversal import Column -from .traversal import Direction -from .traversal import Operator -from .traversal import Order -from .traversal import P -from .traversal import Pop -from .traversal import Scope -from .traversal import T -from .traversal import Traversal - __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' diff --git a/gremlin_python/process/graph_traversal.py b/gremlin_python/process/graph_traversal.py index 37f9deb66a251c8d75ab2e316501ff6755f184c9..9010f603076efc69e6ad08950fa7fbda890ed9b6 100644 --- a/gremlin_python/process/graph_traversal.py +++ b/gremlin_python/process/graph_traversal.py @@ -16,933 +16,402 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from .traversal import RawExpression +import sys from .traversal import Traversal +from .traversal import TraversalStrategies from .traversal import Bytecode -from gremlin_python import statics +from ..driver.remote_connection import RemoteStrategy +from .. import statics class GraphTraversalSource(object): - def __init__(self, graph, traversal_strategies, graph_traversal=None, bytecode=Bytecode()): + def __init__(self, graph, traversal_strategies, bytecode=None, + graph_traversal=None, remote_strategy=None): self.graph = graph self.traversal_strategies = traversal_strategies if graph_traversal is None: graph_traversal = GraphTraversal self.graph_traversal = graph_traversal + if remote_strategy is None: + remote_strategy = RemoteStrategy + self.remote_strategy = remote_strategy + if bytecode is None: + bytecode = Bytecode() self.bytecode = bytecode def __repr__(self): return "graphtraversalsource[" + str(self.graph) + "]" - def E(self, *args): - traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) - traversal.bytecode.add_step("E", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return traversal - def V(self, *args): - traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) - traversal.bytecode.add_step("V", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return traversal - def addV(self, *args): - traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) - traversal.bytecode.add_step("addV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return traversal - def inject(self, *args): - traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) - traversal.bytecode.add_step("inject", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return traversal def withBulk(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withBulk", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withComputer(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withComputer", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withPath(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withPath", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withSack(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withSack", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withSideEffect(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withSideEffect", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withStrategies(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withStrategies", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return source - def withTranslator(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) - source.bytecode.add_source("withTranslator", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source def withoutStrategies(self, *args): - source = GraphTraversalSource(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) source.bytecode.add_source("withoutStrategies", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return source + def withRemote(self, remote_connection): + source = GraphTraversalSource( + self.graph, TraversalStrategies(self.traversal_strategies), + Bytecode(self.bytecode), self.graph_traversal, self.remote_strategy) + source.traversal_strategies.add_strategies([self.remote_strategy(remote_connection)]) + return source + def withBindings(self, bindings): + return self + def E(self, *args): + traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("E", *args) + return traversal + def V(self, *args): + traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("V", *args) + return traversal + def addV(self, *args): + traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("addV", *args) + return traversal + def inject(self, *args): + traversal = self.graph_traversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("inject", *args) + return traversal class GraphTraversal(Traversal): def __init__(self, graph, traversal_strategies, bytecode): Traversal.__init__(self, graph, traversal_strategies, bytecode) + def __getitem__(self, index): + if isinstance(index, int): + return self.range(index, index + 1) + elif isinstance(index, slice): + return self.range(0 if index.start is None else index.start, sys.maxint if index.stop is None else index.stop) + else: + raise TypeError("Index must be int or slice") + def __getattr__(self, key): + return self.values(key) def V(self, *args): self.bytecode.add_step("V", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _and(self, *args): - self.bytecode.add_step("_and", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _as(self, *args): - self.bytecode.add_step("_as", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _from(self, *args): - self.bytecode.add_step("_from", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _in(self, *args): - self.bytecode.add_step("_in", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _is(self, *args): - self.bytecode.add_step("_is", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _not(self, *args): - self.bytecode.add_step("_not", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def _or(self, *args): - self.bytecode.add_step("_or", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def addE(self, *args): self.bytecode.add_step("addE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def addInE(self, *args): self.bytecode.add_step("addInE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def addOutE(self, *args): self.bytecode.add_step("addOutE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def addV(self, *args): self.bytecode.add_step("addV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def aggregate(self, *args): self.bytecode.add_step("aggregate", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def asAdmin(self, *args): - self.bytecode.add_step("asAdmin", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def and_(self, *args): + self.bytecode.add_step("and", *args) + return self + def as_(self, *args): + self.bytecode.add_step("as", *args) return self def barrier(self, *args): self.bytecode.add_step("barrier", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def both(self, *args): self.bytecode.add_step("both", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def bothE(self, *args): self.bytecode.add_step("bothE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def bothV(self, *args): self.bytecode.add_step("bothV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def branch(self, *args): self.bytecode.add_step("branch", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def by(self, *args): self.bytecode.add_step("by", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def cap(self, *args): self.bytecode.add_step("cap", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def choose(self, *args): self.bytecode.add_step("choose", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def coalesce(self, *args): self.bytecode.add_step("coalesce", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def coin(self, *args): self.bytecode.add_step("coin", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def constant(self, *args): self.bytecode.add_step("constant", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def count(self, *args): self.bytecode.add_step("count", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def cyclicPath(self, *args): self.bytecode.add_step("cyclicPath", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def dedup(self, *args): self.bytecode.add_step("dedup", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def drop(self, *args): self.bytecode.add_step("drop", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def emit(self, *args): self.bytecode.add_step("emit", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def filter(self, *args): self.bytecode.add_step("filter", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def flatMap(self, *args): self.bytecode.add_step("flatMap", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def fold(self, *args): self.bytecode.add_step("fold", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def from_(self, *args): + self.bytecode.add_step("from", *args) return self def group(self, *args): self.bytecode.add_step("group", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def groupCount(self, *args): self.bytecode.add_step("groupCount", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def groupV3d0(self, *args): self.bytecode.add_step("groupV3d0", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def has(self, *args): self.bytecode.add_step("has", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def hasId(self, *args): self.bytecode.add_step("hasId", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def hasKey(self, *args): self.bytecode.add_step("hasKey", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def hasLabel(self, *args): self.bytecode.add_step("hasLabel", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def hasNot(self, *args): self.bytecode.add_step("hasNot", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def hasValue(self, *args): self.bytecode.add_step("hasValue", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def id(self, *args): self.bytecode.add_step("id", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def identity(self, *args): self.bytecode.add_step("identity", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def inE(self, *args): self.bytecode.add_step("inE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def inV(self, *args): self.bytecode.add_step("inV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def in_(self, *args): + self.bytecode.add_step("in", *args) return self def inject(self, *args): self.bytecode.add_step("inject", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) - return self - def iterate(self, *args): - self.bytecode.add_step("iterate", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def is_(self, *args): + self.bytecode.add_step("is", *args) return self def key(self, *args): self.bytecode.add_step("key", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def label(self, *args): self.bytecode.add_step("label", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def limit(self, *args): self.bytecode.add_step("limit", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def local(self, *args): self.bytecode.add_step("local", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def loops(self, *args): self.bytecode.add_step("loops", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def map(self, *args): self.bytecode.add_step("map", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def mapKeys(self, *args): self.bytecode.add_step("mapKeys", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def mapValues(self, *args): self.bytecode.add_step("mapValues", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def match(self, *args): self.bytecode.add_step("match", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def max(self, *args): self.bytecode.add_step("max", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def mean(self, *args): self.bytecode.add_step("mean", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def min(self, *args): self.bytecode.add_step("min", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def not_(self, *args): + self.bytecode.add_step("not", *args) return self def option(self, *args): self.bytecode.add_step("option", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def optional(self, *args): self.bytecode.add_step("optional", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) + return self + def or_(self, *args): + self.bytecode.add_step("or", *args) return self def order(self, *args): self.bytecode.add_step("order", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def otherV(self, *args): self.bytecode.add_step("otherV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def out(self, *args): self.bytecode.add_step("out", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def outE(self, *args): self.bytecode.add_step("outE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def outV(self, *args): self.bytecode.add_step("outV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def pageRank(self, *args): self.bytecode.add_step("pageRank", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def path(self, *args): self.bytecode.add_step("path", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def peerPressure(self, *args): self.bytecode.add_step("peerPressure", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def profile(self, *args): self.bytecode.add_step("profile", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def program(self, *args): self.bytecode.add_step("program", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def project(self, *args): self.bytecode.add_step("project", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def properties(self, *args): self.bytecode.add_step("properties", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def property(self, *args): self.bytecode.add_step("property", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def propertyMap(self, *args): self.bytecode.add_step("propertyMap", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def range(self, *args): self.bytecode.add_step("range", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def repeat(self, *args): self.bytecode.add_step("repeat", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def sack(self, *args): self.bytecode.add_step("sack", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def sample(self, *args): self.bytecode.add_step("sample", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def select(self, *args): self.bytecode.add_step("select", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def sideEffect(self, *args): self.bytecode.add_step("sideEffect", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def simplePath(self, *args): self.bytecode.add_step("simplePath", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def store(self, *args): self.bytecode.add_step("store", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def subgraph(self, *args): self.bytecode.add_step("subgraph", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def sum(self, *args): self.bytecode.add_step("sum", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def tail(self, *args): self.bytecode.add_step("tail", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def timeLimit(self, *args): self.bytecode.add_step("timeLimit", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def times(self, *args): self.bytecode.add_step("times", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def to(self, *args): self.bytecode.add_step("to", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def toE(self, *args): self.bytecode.add_step("toE", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def toV(self, *args): self.bytecode.add_step("toV", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def tree(self, *args): self.bytecode.add_step("tree", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def unfold(self, *args): self.bytecode.add_step("unfold", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def union(self, *args): self.bytecode.add_step("union", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def until(self, *args): self.bytecode.add_step("until", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def value(self, *args): self.bytecode.add_step("value", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def valueMap(self, *args): self.bytecode.add_step("valueMap", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def values(self, *args): self.bytecode.add_step("values", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self def where(self, *args): self.bytecode.add_step("where", *args) - for arg in args: - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - elif isinstance(arg, RawExpression): - self.bindings.update(arg.bindings) return self @@ -954,24 +423,6 @@ class __(object): def __(*args): return GraphTraversal(None, None, Bytecode()).__(*args) @staticmethod - def _and(*args): - return GraphTraversal(None, None, Bytecode())._and(*args) - @staticmethod - def _as(*args): - return GraphTraversal(None, None, Bytecode())._as(*args) - @staticmethod - def _in(*args): - return GraphTraversal(None, None, Bytecode())._in(*args) - @staticmethod - def _is(*args): - return GraphTraversal(None, None, Bytecode())._is(*args) - @staticmethod - def _not(*args): - return GraphTraversal(None, None, Bytecode())._not(*args) - @staticmethod - def _or(*args): - return GraphTraversal(None, None, Bytecode())._or(*args) - @staticmethod def addE(*args): return GraphTraversal(None, None, Bytecode()).addE(*args) @staticmethod @@ -987,6 +438,12 @@ class __(object): def aggregate(*args): return GraphTraversal(None, None, Bytecode()).aggregate(*args) @staticmethod + def and_(*args): + return GraphTraversal(None, None, Bytecode()).and_(*args) + @staticmethod + def as_(*args): + return GraphTraversal(None, None, Bytecode()).as_(*args) + @staticmethod def barrier(*args): return GraphTraversal(None, None, Bytecode()).barrier(*args) @staticmethod @@ -1080,9 +537,15 @@ class __(object): def inV(*args): return GraphTraversal(None, None, Bytecode()).inV(*args) @staticmethod + def in_(*args): + return GraphTraversal(None, None, Bytecode()).in_(*args) + @staticmethod def inject(*args): return GraphTraversal(None, None, Bytecode()).inject(*args) @staticmethod + def is_(*args): + return GraphTraversal(None, None, Bytecode()).is_(*args) + @staticmethod def key(*args): return GraphTraversal(None, None, Bytecode()).key(*args) @staticmethod @@ -1119,9 +582,15 @@ class __(object): def min(*args): return GraphTraversal(None, None, Bytecode()).min(*args) @staticmethod + def not_(*args): + return GraphTraversal(None, None, Bytecode()).not_(*args) + @staticmethod def optional(*args): return GraphTraversal(None, None, Bytecode()).optional(*args) @staticmethod + def or_(*args): + return GraphTraversal(None, None, Bytecode()).or_(*args) + @staticmethod def order(*args): return GraphTraversal(None, None, Bytecode()).order(*args) @staticmethod @@ -1233,36 +702,6 @@ def V(*args): statics.add_static('V', V) -def _and(*args): - return __._and(*args) - -statics.add_static('_and', _and) - -def _as(*args): - return __._as(*args) - -statics.add_static('_as', _as) - -def _in(*args): - return __._in(*args) - -statics.add_static('_in', _in) - -def _is(*args): - return __._is(*args) - -statics.add_static('_is', _is) - -def _not(*args): - return __._not(*args) - -statics.add_static('_not', _not) - -def _or(*args): - return __._or(*args) - -statics.add_static('_or', _or) - def addE(*args): return __.addE(*args) @@ -1288,6 +727,16 @@ def aggregate(*args): statics.add_static('aggregate', aggregate) +def and_(*args): + return __.and_(*args) + +statics.add_static('and_', and_) + +def as_(*args): + return __.as_(*args) + +statics.add_static('as_', as_) + def barrier(*args): return __.barrier(*args) @@ -1443,11 +892,21 @@ def inV(*args): statics.add_static('inV', inV) +def in_(*args): + return __.in_(*args) + +statics.add_static('in_', in_) + def inject(*args): return __.inject(*args) statics.add_static('inject', inject) +def is_(*args): + return __.is_(*args) + +statics.add_static('is_', is_) + def key(*args): return __.key(*args) @@ -1508,11 +967,21 @@ def min(*args): statics.add_static('min', min) +def not_(*args): + return __.not_(*args) + +statics.add_static('not_', not_) + def optional(*args): return __.optional(*args) statics.add_static('optional', optional) +def or_(*args): + return __.or_(*args) + +statics.add_static('or_', or_) + def order(*args): return __.order(*args) diff --git a/gremlin_python/process/jython_translator.py b/gremlin_python/process/jython_translator.py deleted file mode 100644 index c3169f07c71a0fb0e5a35a33ef2c60bf66e7b5e6..0000000000000000000000000000000000000000 --- a/gremlin_python/process/jython_translator.py +++ /dev/null @@ -1,102 +0,0 @@ -''' -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -''' - -import inspect -import sys -from aenum import Enum - -from .traversal import Barrier -from .traversal import Bytecode -from .traversal import Cardinality -from .traversal import Column -from .traversal import P -from .traversal import RawExpression -from .traversal import SymbolHelper -from .traversal import Translator - -if sys.version_info.major > 2: - long = int - -__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' - - -class JythonTranslator(Translator): - def __init__(self, traversal_source, anonymous_traversal="__", target_language="gremlin-jython"): - Translator.__init__(self, traversal_source, anonymous_traversal, target_language) - - def translate(self, bytecode): - return self.__internalTranslate(self.traversal_source, bytecode) - - def __internalTranslate(self, start, bytecode): - traversal_script = start - for instruction in bytecode.source_instructions: - traversal_script = traversal_script + "." + SymbolHelper.toJava( - instruction[0]) + "(" + self.stringify(*instruction[1]) + ")" - for instruction in bytecode.step_instructions: - traversal_script = traversal_script + "." + SymbolHelper.toJava( - instruction[0]) + "(" + self.stringify(*instruction[1]) + ")" - return traversal_script - - def stringOrObject(self, arg): - if isinstance(arg, str): - return "\"" + arg + "\"" - elif isinstance(arg, long): - return str(arg) - elif isinstance(arg, Barrier): - return "Barrier" + "." + SymbolHelper.toJava(str(arg.name)) - elif isinstance(arg, Column): - return "Column.valueOf('" + SymbolHelper.toJava(str(arg.name)) + "')" - elif isinstance(arg, Cardinality): - return "Cardinality" + "." + SymbolHelper.toJava(str(arg.name)) - elif isinstance(arg, Enum): # Order, Direction, Scope, T, etc. - return SymbolHelper.toJava(type(arg).__name__) + "." + SymbolHelper.toJava(str(arg.name)) - elif isinstance(arg, P): - if arg.other is None: - return "P." + SymbolHelper.toJava(arg.operator) + "(" + self.stringOrObject( - arg.value) + ")" - else: - return self.stringOrObject(arg.other) + "." + SymbolHelper.toJava( - arg.operator) + "(" + self.stringOrObject(arg.value) + ")" - elif isinstance(arg, Bytecode): - return self.__internalTranslate(self.anonymous_traversal, arg) - elif callable(arg): # lambda that produces a string that is a lambda - argLambdaString = arg().strip() - argLength = len(inspect.getargspec(eval(argLambdaString)).args) - if argLength == 0: - return "JythonZeroArgLambda(" + argLambdaString + ")" - elif argLength == 1: - return "JythonOneArgLambda(" + argLambdaString + ")" - elif argLength == 2: - return "JythonTwoArgLambda(" + argLambdaString + ")" - else: - raise - elif isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): # bindings - return arg[0] - elif isinstance(arg, RawExpression): - return "".join(self.stringOrObject(i) for i in arg.parts) - else: - return str(arg) - - def stringify(self, *args): - if len(args) == 0: - return "" - elif len(args) == 1: - return self.stringOrObject(args[0]) - else: - return ", ".join(self.stringOrObject(i) for i in args) diff --git a/gremlin_python/process/groovy_translator.py b/gremlin_python/process/translator.py similarity index 60% rename from gremlin_python/process/groovy_translator.py rename to gremlin_python/process/translator.py index 2c2d79a860da80b70bb427df6351793e06c8df45..ac1ebe73cb7e02db5cc00c07927fcbd164a85b63 100644 --- a/gremlin_python/process/groovy_translator.py +++ b/gremlin_python/process/translator.py @@ -17,19 +17,68 @@ specific language governing permissions and limitations under the License. ''' -import sys +# Translate bytecode. Used for backwards compatiblitity + +from abc import abstractmethod from aenum import Enum -from .traversal import Bytecode -from .traversal import P -from .traversal import RawExpression -from .traversal import SymbolHelper -from .traversal import Translator +from gremlin_python.process.traversal import P, Bytecode, Binding + + +class RawExpression(object): + def __init__(self, *args): + self.bindings = dict() + self.parts = [self._process_arg(arg) for arg in args] + + def _process_arg(self, arg): + if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): + self.bindings[arg[0]] = arg[1] + return Raw(arg[0]) + else: + return Raw(arg) + +class Raw(object): + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + +TO_JAVA_MAP = {"_global": "global", "_as": "as", "_in": "in", "_and": "and", + "_or": "or", "_is": "is", "_not": "not", "_from": "from", + "Cardinality": "VertexProperty.Cardinality", "Barrier": "SackFunctions.Barrier"} -if sys.version_info.major > 2: - long = int -__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' +class Translator(object): + def __init__(self, traversal_source, anonymous_traversal, target_language): + self.traversal_source = traversal_source + self.anonymous_traversal = anonymous_traversal + self.target_language = target_language + + @abstractmethod + def translate(self, bytecode): + return + + @abstractmethod + def __repr__(self): + return "translator[" + self.traversal_source + ":" + self.target_language + "]" + + +class SymbolHelper(object): + @staticmethod + def toJava(symbol): + if (symbol in TO_JAVA_MAP): + return TO_JAVA_MAP[symbol] + else: + return symbol + + @staticmethod + def mapEnum(enum): + if (enum in enumMap): + return enumMap[enum] + else: + return enum class GroovyTranslator(Translator): @@ -37,16 +86,16 @@ class GroovyTranslator(Translator): Translator.__init__(self, traversal_source, anonymous_traversal, target_language) def translate(self, bytecode): - return self.__internalTranslate(self.traversal_source, bytecode) + return self._internalTranslate(self.traversal_source, bytecode) - def __internalTranslate(self, start, bytecode): + def _internalTranslate(self, start, bytecode): traversal_script = start for instruction in bytecode.source_instructions: traversal_script = traversal_script + "." + SymbolHelper.toJava( - instruction[0]) + "(" + self.stringify(*instruction[1]) + ")" + instruction[0]) + "(" + self.stringify(*instruction[1:]) + ")" for instruction in bytecode.step_instructions: traversal_script = traversal_script + "." + SymbolHelper.toJava( - instruction[0]) + "(" + self.stringify(*instruction[1]) + ")" + instruction[0]) + "(" + self.stringify(*instruction[1:]) + ")" return traversal_script def stringOrObject(self, arg): @@ -54,7 +103,7 @@ class GroovyTranslator(Translator): return "\"" + arg + "\"" elif isinstance(arg, bool): return str(arg).lower() - elif isinstance(arg, long): + elif isinstance(arg, int): return str(arg) + "L" elif isinstance(arg, float): return str(arg) + "f" @@ -67,8 +116,10 @@ class GroovyTranslator(Translator): else: return self.stringOrObject(arg.other) + "." + SymbolHelper.toJava( arg.operator) + "(" + self.stringOrObject(arg.value) + ")" + elif isinstance(arg, Binding): + return arg.key elif isinstance(arg, Bytecode): - return self.__internalTranslate(self.anonymous_traversal, arg) + return self._internalTranslate(self.anonymous_traversal, arg) elif callable(arg): # closures lambdaString = arg().strip() if lambdaString.startswith("{"): diff --git a/gremlin_python/process/traversal.py b/gremlin_python/process/traversal.py index 7558151f79f9ca7d837bc11e0c650c810eb52a19..cbba42d8bfd8d16817cf2ac7ca74658262184cde 100644 --- a/gremlin_python/process/traversal.py +++ b/gremlin_python/process/traversal.py @@ -16,53 +16,50 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from abc import abstractmethod +import abc +import six from aenum import Enum -from gremlin_python import statics +from .. import statics class Traversal(object): def __init__(self, graph, traversal_strategies, bytecode): self.graph = graph self.traversal_strategies = traversal_strategies self.bytecode = bytecode - self.results = None + self.side_effects = TraversalSideEffects() + self.traversers = None self.last_traverser = None - self.bindings = {} - def __repr__(self): - return self.graph.translator.translate(self.bytecode) - - def __getitem__(self, index): - if isinstance(index, int): - return self.range(index, index + 1) - elif isinstance(index, slice): - return self.range(index.start, index.stop) - else: - raise TypeError("Index must be int or slice") - - def __getattr__(self, key): - return self.values(key) - + return str(self.bytecode) def __iter__(self): return self - def __next__(self): - if self.results is None: + if self.traversers is None: self.traversal_strategies.apply_strategies(self) if self.last_traverser is None: - self.last_traverser = next(self.results) + self.last_traverser = next(self.traversers) object = self.last_traverser.object self.last_traverser.bulk = self.last_traverser.bulk - 1 if self.last_traverser.bulk <= 0: self.last_traverser = None return object - def toList(self): return list(iter(self)) - def toSet(self): return set(iter(self)) - + def iterate(self): + while True: + try: self.nextTraverser() + except StopIteration: return self + def nextTraverser(self): + if self.traversers is None: + self.traversal_strategies.apply_strategies(self) + if self.last_traverser is None: + return next(self.traversers) + else: + temp = self.last_traverser + self.last_traverser = None + return temp def next(self, amount=None): if amount is None: return self.__next__() @@ -76,29 +73,25 @@ class Traversal(object): tempList.append(temp) return tempList -Barrier = Enum('Barrier', 'normSack') +Barrier = Enum('Barrier', 'normSack') statics.add_static('normSack', Barrier.normSack) Cardinality = Enum('Cardinality', 'list set single') - statics.add_static('single', Cardinality.single) statics.add_static('list', Cardinality.list) statics.add_static('set', Cardinality.set) Column = Enum('Column', 'keys values') - statics.add_static('keys', Column.keys) statics.add_static('values', Column.values) Direction = Enum('Direction', 'BOTH IN OUT') - statics.add_static('OUT', Direction.OUT) statics.add_static('IN', Direction.IN) statics.add_static('BOTH', Direction.BOTH) -Operator = Enum('Operator', 'addAll _and assign div max min minus mult _or sum sumLong') - +Operator = Enum('Operator', 'addAll and_ assign div max min minus mult or_ sum sumLong') statics.add_static('sum', Operator.sum) statics.add_static('minus', Operator.minus) statics.add_static('mult', Operator.mult) @@ -106,13 +99,12 @@ statics.add_static('div', Operator.div) statics.add_static('min', Operator.min) statics.add_static('max', Operator.max) statics.add_static('assign', Operator.assign) -statics.add_static('_and', Operator._and) -statics.add_static('_or', Operator._or) +statics.add_static('and_', Operator.and_) +statics.add_static('or_', Operator.or_) statics.add_static('addAll', Operator.addAll) statics.add_static('sumLong', Operator.sumLong) Order = Enum('Order', 'decr incr keyDecr keyIncr shuffle valueDecr valueIncr') - statics.add_static('incr', Order.incr) statics.add_static('decr', Order.decr) statics.add_static('keyIncr', Order.keyIncr) @@ -121,19 +113,16 @@ statics.add_static('keyDecr', Order.keyDecr) statics.add_static('valueDecr', Order.valueDecr) statics.add_static('shuffle', Order.shuffle) -Pop = Enum('Pop', 'all first last') - +Pop = Enum('Pop', 'all_ first last') statics.add_static('first', Pop.first) statics.add_static('last', Pop.last) -statics.add_static('all', Pop.all) +statics.add_static('all_', Pop.all_) -Scope = Enum('Scope', '_global local') - -statics.add_static('_global', Scope._global) +Scope = Enum('Scope', 'global_ local') +statics.add_static('global_', Scope.global_) statics.add_static('local', Scope.local) T = Enum('T', 'id key label value') - statics.add_static('label', T.label) statics.add_static('id', T.id) statics.add_static('key', T.key) @@ -145,9 +134,6 @@ class P(object): self.value = value self.other = other @staticmethod - def _not(*args): - return P("not", *args) - @staticmethod def between(*args): return P("between", *args) @staticmethod @@ -172,6 +158,9 @@ class P(object): def neq(*args): return P("neq", *args) @staticmethod + def not_(*args): + return P("not", *args) + @staticmethod def outside(*args): return P("outside", *args) @staticmethod @@ -184,106 +173,94 @@ class P(object): def without(*args): return P("without", *args) def _and(self, arg): - return P("_and", arg, self) + return P("and", arg, self) def _or(self, arg): - return P("_or", arg, self) - -def _not(*args): - return P._not(*args) - -statics.add_static('_not',_not) + return P("or", arg, self) + def __eq__(self, other): + return isinstance(other, self.__class__) and self.operator == other.operator and self.value == other.value and self.other == other.other + def __repr__(self): + return self.operator + "(" + str(self.value) + ")" if self.other is None else self.operator + "(" + str(self.value) + "," + str(self.other) + ")" def between(*args): return P.between(*args) - statics.add_static('between',between) def eq(*args): return P.eq(*args) - statics.add_static('eq',eq) def gt(*args): return P.gt(*args) - statics.add_static('gt',gt) def gte(*args): return P.gte(*args) - statics.add_static('gte',gte) def inside(*args): return P.inside(*args) - statics.add_static('inside',inside) def lt(*args): return P.lt(*args) - statics.add_static('lt',lt) def lte(*args): return P.lte(*args) - statics.add_static('lte',lte) def neq(*args): return P.neq(*args) - statics.add_static('neq',neq) +def not_(*args): + return P.not_(*args) +statics.add_static('not_',not_) + def outside(*args): return P.outside(*args) - statics.add_static('outside',outside) def test(*args): return P.test(*args) - statics.add_static('test',test) def within(*args): return P.within(*args) - statics.add_static('within',within) def without(*args): return P.without(*args) - statics.add_static('without',without) -class RawExpression(object): - def __init__(self, *args): - self.bindings = dict() - self.parts = [self._process_arg(arg) for arg in args] - - def _process_arg(self, arg): - if isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): - self.bindings[arg[0]] = arg[1] - return Raw(arg[0]) - else: - return Raw(arg) - -class Raw(object): - def __init__(self, value): - self.value = value - - def __str__(self): - return str(self.value) - ''' TRAVERSER ''' class Traverser(object): - def __init__(self, object, bulk): + def __init__(self, object, bulk=1): self.object = object self.bulk = bulk def __repr__(self): return str(self.object) + def __eq__(self, other): + return isinstance(other, self.__class__) and self.object == other.object + +''' +TRAVERSAL SIDE-EFFECTS +''' + +class TraversalSideEffects(object): + def keys(self): + return set() + def get(self, key): + raise KeyError(key) + def __getitem__(self, key): + return self.get(key) + def __repr__(self): + return "sideEffects[size:" + str(len(self.keys())) + "]" ''' TRAVERSAL STRATEGIES @@ -291,87 +268,68 @@ TRAVERSAL STRATEGIES class TraversalStrategies(object): global_cache = {} - - def __init__(self, traversal_strategies): - self.traversal_strategies = traversal_strategies - return - + def __init__(self, traversal_strategies=None): + self.traversal_strategies = traversal_strategies.traversal_strategies if traversal_strategies is not None else [] + def add_strategies(self, traversal_strategies): + self.traversal_strategies = self.traversal_strategies + traversal_strategies def apply_strategies(self, traversal): for traversal_strategy in self.traversal_strategies: traversal_strategy.apply(traversal) - return +@six.add_metaclass(abc.ABCMeta) class TraversalStrategy(object): - @abstractmethod + @abc.abstractmethod def apply(self, traversal): return ''' -BYTECODE AND TRANSLATOR +BYTECODE ''' class Bytecode(object): def __init__(self, bytecode=None): self.source_instructions = [] self.step_instructions = [] + self.bindings = {} if bytecode is not None: self.source_instructions = list(bytecode.source_instructions) self.step_instructions = list(bytecode.step_instructions) - def add_source(self, source_name, *args): - newArgs = () + instruction = [source_name] for arg in args: - newArgs = newArgs + (Bytecode.__convertArgument(arg),) - self.source_instructions.append((source_name, newArgs)) - return - + instruction.append(self._convertArgument(arg)) + self.source_instructions.append(instruction) def add_step(self, step_name, *args): - newArgs = () + instruction = [step_name] for arg in args: - newArgs = newArgs + (Bytecode.__convertArgument(arg),) - self.step_instructions.append((step_name, newArgs)) - return - - @staticmethod - def __convertArgument(arg): + instruction.append(self._convertArgument(arg)) + self.step_instructions.append(instruction) + def _convertArgument(self,arg): if isinstance(arg, Traversal): + self.bindings.update(arg.bytecode.bindings) return arg.bytecode + elif isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): + self.bindings[arg[0]] = arg[1] + return Binding(arg[0],arg[1]) else: return arg - - -TO_JAVA_MAP = {"_global": "global", "_as": "as", "_in": "in", "_and": "and", - "_or": "or", "_is": "is", "_not": "not", "_from": "from", - "Cardinality": "VertexProperty.Cardinality", "Barrier": "SackFunctions.Barrier"} - - -class Translator(object): - def __init__(self, traversal_source, anonymous_traversal, target_language): - self.traversal_source = traversal_source - self.anonymous_traversal = anonymous_traversal - self.target_language = target_language - - @abstractmethod - def translate(self, bytecode): - return - - @abstractmethod def __repr__(self): - return "translator[" + self.traversal_source + ":" + self.target_language + "]" + return (str(self.source_instructions) if len(self.source_instructions) > 0 else "") + \ + (str(self.step_instructions) if len(self.step_instructions) > 0 else "") -class SymbolHelper(object): - @staticmethod - def toJava(symbol): - if (symbol in TO_JAVA_MAP): - return TO_JAVA_MAP[symbol] - else: - return symbol +''' +BINDINGS +''' - @staticmethod - def mapEnum(enum): - if (enum in enumMap): - return enumMap[enum] - else: - return enum +class Bindings(object): + def of(self,key,value): + if not isinstance(key, str): + raise TypeError("Key must be str") + return (key,value) + +class Binding(object): + def __init__(self,key,value): + self.key = key + self.value = value diff --git a/gremlin_python/statics.py b/gremlin_python/statics.py index 12cbb702c22b2366eaf0679fc32b941f46eba030..293ff93a6200ac7084fea76ffae509d45bc4d6d1 100644 --- a/gremlin_python/statics.py +++ b/gremlin_python/statics.py @@ -20,6 +20,7 @@ from aenum import Enum staticMethods = {} staticEnums = {} +default_lambda_language = "gremlin-python" def add_static(key, value): diff --git a/gremlin_python/structure/__init__.py b/gremlin_python/structure/__init__.py index 1c69b5c0e00ef94926514351d5f58116fc30f5c6..7626550738f12ccd5e18b8c06a8d68aaab882066 100644 --- a/gremlin_python/structure/__init__.py +++ b/gremlin_python/structure/__init__.py @@ -16,7 +16,5 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from .graph import Graph -from .remote_graph import RemoteGraph __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' diff --git a/gremlin_python/structure/graph.py b/gremlin_python/structure/graph.py index 60b22113b57b5ac40156ad0a4adaaeded39b6586..22403b653b715b2b8b3c4b3f113e3c74864d9156 100644 --- a/gremlin_python/structure/graph.py +++ b/gremlin_python/structure/graph.py @@ -24,5 +24,67 @@ from gremlin_python.process.traversal import TraversalStrategies class Graph(object): + def __init__(self): + if self.__class__ not in TraversalStrategies.global_cache: + TraversalStrategies.global_cache[self.__class__] = TraversalStrategies() + def traversal(self): return GraphTraversalSource(self, TraversalStrategies.global_cache[self.__class__]) + + def __repr__(self): + return "graph[empty]" + + +class Element(object): + def __init__(self, id, label): + self.id = id + self.label = label + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.id == other.id + + def __hash__(self): + return hash(self.id) + + +class Vertex(Element): + def __init__(self, id, label="vertex"): + Element.__init__(self, id, label) + + def __repr__(self): + return "v[" + str(self.id) + "]" + + +class Edge(Element): + def __init__(self, id, outV, label, inV): + Element.__init__(self, id, label) + self.outV = outV + self.inV = inV + + def __repr__(self): + return "e[" + str(self.id) + "][" + str(self.outV.id) + "-" + self.label + "->" + str(self.inV.id) + "]" + + +class VertexProperty(Element): + def __init__(self, id, label, value): + Element.__init__(self, id, label) + self.value = value + self.key = self.label + + def __repr__(self): + return "vp[" + str(self.label) + "->" + str(self.value)[0:20] + "]" + + +class Property(object): + def __init__(self, key, value): + self.key = key + self.value = value + + def __repr__(self): + return "p[" + str(self.key) + "->" + str(self.value)[0:20] + "]" + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.key == other.key and self.value == other.value + + def __hash__(self): + return hash(self.key) + hash(self.value) diff --git a/gremlin_python/driver/rest_remote_connection.py b/gremlin_python/structure/io/__init__.py similarity index 51% rename from gremlin_python/driver/rest_remote_connection.py rename to gremlin_python/structure/io/__init__.py index 6d967f8268efeb7749209879d6ae86a5e9bbd1f3..7626550738f12ccd5e18b8c06a8d68aaab882066 100644 --- a/gremlin_python/driver/rest_remote_connection.py +++ b/gremlin_python/structure/io/__init__.py @@ -16,28 +16,5 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -import json -import requests - -from gremlin_python.process.traversal import Traverser -from .remote_connection import RemoteConnection __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' - - -class RESTRemoteConnection(RemoteConnection): - def __init__(self, url): - RemoteConnection.__init__(self, url) - - def __repr__(self): - return "RESTRemoteConnection[" + self.url + "]" - - def submit(self, target_language, script, bindings): - response = requests.post(self.url, data=json.dumps( - {"gremlin": script, "language": target_language, "bindings": bindings})) - if response.status_code != requests.codes.ok: - raise BaseException(response.text) - results = [] - for x in response.json()['result']['data']: - results.append(Traverser(x, 1)) - return iter(results) diff --git a/gremlin_python/structure/io/graphson.py b/gremlin_python/structure/io/graphson.py new file mode 100644 index 0000000000000000000000000000000000000000..ed3114554061a772f5396f4e2dad55dbecaccb57 --- /dev/null +++ b/gremlin_python/structure/io/graphson.py @@ -0,0 +1,292 @@ +''' +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +''' + +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import json +from abc import abstractmethod +from aenum import Enum +from types import FunctionType + +from gremlin_python import statics +from gremlin_python.process.traversal import Binding +from gremlin_python.process.traversal import Bytecode +from gremlin_python.process.traversal import P +from gremlin_python.process.traversal import Traversal +from gremlin_python.process.traversal import Traverser +from gremlin_python.structure.graph import Edge +from gremlin_python.structure.graph import Property +from gremlin_python.structure.graph import Vertex +from gremlin_python.structure.graph import VertexProperty + + +class long(int): pass +FloatType = float +IntType = int +LongType = long + + + +class GraphSONWriter(object): + @staticmethod + def _dictify(object): + for key in serializers: + if isinstance(object, key): + return serializers[key]._dictify(object) + # list and map are treated as normal json objects (could be isolated serializers) + if isinstance(object, list): + newList = [] + for item in object: + newList.append(GraphSONWriter._dictify(item)) + return newList + elif isinstance(object, dict): + newDict = {} + for key in object: + newDict[GraphSONWriter._dictify(key)] = GraphSONWriter._dictify(object[key]) + return newDict + else: + return object + + @staticmethod + def writeObject(objectData): + return json.dumps(GraphSONWriter._dictify(objectData), separators=(',', ':')) + + +class GraphSONReader(object): + @staticmethod + def _objectify(object): + if isinstance(object, dict): + if _SymbolHelper._TYPE in object: + type = object[_SymbolHelper._TYPE] + if type in deserializers: + return deserializers[type]._objectify(object) + # list and map are treated as normal json objects (could be isolated deserializers) + newDict = {} + for key in object: + newDict[GraphSONReader._objectify(key)] = GraphSONReader._objectify(object[key]) + return newDict + elif isinstance(object, list): + newList = [] + for item in object: + newList.append(GraphSONReader._objectify(item)) + return newList + else: + return object + + @staticmethod + def readObject(jsonData): + return GraphSONReader._objectify(json.loads(jsonData)) + + +''' +SERIALIZERS +''' + + +class GraphSONSerializer(object): + @abstractmethod + def _dictify(self, object): + return object + + +class BytecodeSerializer(GraphSONSerializer): + def _dictify(self, bytecode): + if isinstance(bytecode, Traversal): + bytecode = bytecode.bytecode + dict = {} + sources = [] + for instruction in bytecode.source_instructions: + inst = [] + inst.append(instruction[0]) + for arg in instruction[1:]: + inst.append(GraphSONWriter._dictify(arg)) + sources.append(inst) + steps = [] + for instruction in bytecode.step_instructions: + inst = [] + inst.append(instruction[0]) + for arg in instruction[1:]: + inst.append(GraphSONWriter._dictify(arg)) + steps.append(inst) + if len(sources) > 0: + dict["source"] = sources + if len(steps) > 0: + dict["step"] = steps + return _SymbolHelper.objectify("Bytecode", dict) + + +class TraverserSerializer(GraphSONSerializer): + def _dictify(self, traverser): + return _SymbolHelper.objectify("Traverser", {"value": GraphSONWriter._dictify(traverser.object), + "bulk": GraphSONWriter._dictify(traverser.bulk)}) + + +class EnumSerializer(GraphSONSerializer): + def _dictify(self, enum): + return _SymbolHelper.objectify(_SymbolHelper.toGremlin(type(enum).__name__), + _SymbolHelper.toGremlin(str(enum.name))) + + +class PSerializer(GraphSONSerializer): + def _dictify(self, p): + dict = {} + dict["predicate"] = p.operator + if p.other is None: + dict["value"] = GraphSONWriter._dictify(p.value) + else: + dict["value"] = [GraphSONWriter._dictify(p.value), GraphSONWriter._dictify(p.other)] + return _SymbolHelper.objectify("P", dict) + + +class BindingSerializer(GraphSONSerializer): + def _dictify(self, binding): + dict = {} + dict["key"] = binding.key + dict["value"] = GraphSONWriter._dictify(binding.value) + return _SymbolHelper.objectify("Binding", dict) + + +class LambdaSerializer(GraphSONSerializer): + def _dictify(self, lambdaObject): + lambdaResult = lambdaObject() + dict = {} + script = lambdaResult if isinstance(lambdaResult, str) else lambdaResult[0] + language = statics.default_lambda_language if isinstance(lambdaResult, str) else lambdaResult[1] + dict["script"] = script + dict["language"] = language + if language == "gremlin-jython" or language == "gremlin-python": + if not script.strip().startswith("lambda"): + script = "lambda " + script + dict["script"] = script + dict["arguments"] = eval(dict["script"]).func_code.co_argcount + else: + dict["arguments"] = -1 + return _SymbolHelper.objectify("Lambda", dict) + + +class NumberSerializer(GraphSONSerializer): + def _dictify(self, number): + if isinstance(number, bool): # python thinks that 0/1 integers are booleans + return number + elif isinstance(number, int): + return _SymbolHelper.objectify("Int64", number) + elif isinstance(number, float): + return _SymbolHelper.objectify("Float", number) + else: + return number + + +''' +DESERIALIZERS +''' + + +class GraphSONDeserializer(object): + @abstractmethod + def _objectify(self, dict): + return dict + + +class TraverserDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + return Traverser(GraphSONReader._objectify(dict[_SymbolHelper._VALUE]["value"]), + GraphSONReader._objectify(dict[_SymbolHelper._VALUE]["bulk"])) + + +class NumberDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + type = dict[_SymbolHelper._TYPE] + value = dict[_SymbolHelper._VALUE] + if type == "g:Int32": + return int(value) + elif type == "g:Int64": + return long(value) + else: + return float(value) + + +class VertexDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + value = dict[_SymbolHelper._VALUE] + return Vertex(GraphSONReader._objectify(value["id"]), value["label"] if "label" in value else "") + + +class EdgeDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + value = dict[_SymbolHelper._VALUE] + return Edge(GraphSONReader._objectify(value["id"]), + Vertex(GraphSONReader._objectify(value["outV"]), ""), + value["label"] if "label" in value else "vertex", + Vertex(GraphSONReader._objectify(value["inV"]), "")) + + +class VertexPropertyDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + value = dict[_SymbolHelper._VALUE] + return VertexProperty(GraphSONReader._objectify(value["id"]), value["label"], + GraphSONReader._objectify(value["value"])) + + +class PropertyDeserializer(GraphSONDeserializer): + def _objectify(self, dict): + value = dict[_SymbolHelper._VALUE] + return Property(value["key"], GraphSONReader._objectify(value["value"])) + + +class _SymbolHelper(object): + symbolMap = {"global_": "global", "as_": "as", "in_": "in", "and_": "and", + "or_": "or", "is_": "is", "not_": "not", "from_": "from", + "set_": "set", "list_": "list", "all_": "all"} + + _TYPE = "@type" + _VALUE = "@value" + + @staticmethod + def toGremlin(symbol): + return _SymbolHelper.symbolMap[symbol] if symbol in _SymbolHelper.symbolMap else symbol + + @staticmethod + def objectify(type, value, prefix="g"): + return {_SymbolHelper._TYPE: prefix + ":" + type, _SymbolHelper._VALUE: value} + + +serializers = { + Traversal: BytecodeSerializer(), + Traverser: TraverserSerializer(), + Bytecode: BytecodeSerializer(), + Binding: BindingSerializer(), + P: PSerializer(), + Enum: EnumSerializer(), + FunctionType: LambdaSerializer(), + LongType: NumberSerializer(), + IntType: NumberSerializer(), + FloatType: NumberSerializer() +} + +deserializers = { + "g:Traverser": TraverserDeserializer(), + "g:Int32": NumberDeserializer(), + "g:Int64": NumberDeserializer(), + "g:Float": NumberDeserializer(), + "g:Double": NumberDeserializer(), + "g:Vertex": VertexDeserializer(), + "g:Edge": EdgeDeserializer(), + "g:VertexProperty": VertexPropertyDeserializer(), + "g:Property": PropertyDeserializer() +} diff --git a/gremlin_python/structure/remote_graph.py b/gremlin_python/structure/remote_graph.py deleted file mode 100644 index 778895b24a4438477a328b383bedc900935eae5e..0000000000000000000000000000000000000000 --- a/gremlin_python/structure/remote_graph.py +++ /dev/null @@ -1,47 +0,0 @@ -''' -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -''' - -__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' - -from .graph import Graph -from gremlin_python.process.traversal import TraversalStrategies -from gremlin_python.process.traversal import TraversalStrategy - - -class RemoteGraph(Graph): - def __init__(self, translator, remote_connection): - TraversalStrategies.global_cache[self.__class__] = TraversalStrategies([RemoteStrategy()]) - self.translator = translator - self.remote_connection = remote_connection - - def __repr__(self): - return "remotegraph[" + self.remote_connection.url + "]" - - -class RemoteStrategy(TraversalStrategy): - def apply(self, traversal): - if not (traversal.graph.__class__.__name__ == "RemoteGraph"): - raise BaseException( - "RemoteStrategy can only be used with a RemoteGraph: " + traversal.graph.__class__.__name__) - if traversal.results is None: - traversal.results = traversal.graph.remote_connection.submit( - traversal.graph.translator.target_language, # script engine - traversal.graph.translator.translate(traversal.bytecode), # script - traversal.bindings) # bindings - return diff --git a/tests/conftest.py b/tests/conftest.py index 3b5255e1bb5927a7ea13f5c07d928e3be8134925..9468d106aa32532b57e9ab5fbf4f35d06daeff12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,11 @@ from goblin.driver import pool, serializer from gremlin_python import process +def pytest_generate_tests(metafunc): + if 'cluster' in metafunc.fixturenames: + metafunc.parametrize("cluster", ['c1', 'c2'], indirect=True) + + class HistoricalName(element.VertexProperty): notes = properties.Property(properties.String) year = properties.Property(properties.Integer) # this is dumb but handy @@ -66,7 +71,9 @@ def unused_server_url(unused_tcp_port): @pytest.fixture def connection(event_loop): conn = event_loop.run_until_complete( - driver.Connection.open("http://localhost:8182/gremlin", event_loop)) + driver.Connection.open( + "http://localhost:8182/gremlin", event_loop, + message_serializer=serializer.GraphSONMessageSerializer)) return conn @@ -78,23 +85,32 @@ def connection_pool(event_loop): @pytest.fixture -def cluster(event_loop): - return driver.Cluster(event_loop) +def cluster(request, event_loop): + if request.param == 'c1': + cluster = driver.Cluster( + event_loop, + message_serializer=serializer.GraphSONMessageSerializer) + elif request.param == 'c2': + cluster = driver.Cluster( + event_loop, + message_serializer=serializer.GraphSON2MessageSerializer) + return cluster @pytest.fixture -def remote_graph(connection): - translator = process.GroovyTranslator('g') - return driver.AsyncRemoteGraph(translator, connection) +def remote_graph(): + return driver.AsyncGraph() @pytest.fixture def app(request, event_loop): app = event_loop.run_until_complete( Goblin.open(event_loop, aliases={'g': 'g'})) + app.register(Person, Place, Knows, LivesIn) return app + # Instance fixtures @pytest.fixture def string(): diff --git a/tests/test_app.py b/tests/test_app.py index d28dd9e66da31340713b668fe633587033b66cc7..89067f1a3dcdab29121562b75350b88ece3cc8d5 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -18,6 +18,7 @@ import pytest from goblin import element +from goblin.driver import serializer from gremlin_python import process @@ -45,10 +46,6 @@ async def test_transaction_discovery(app): assert app._transactions is not None await app.close() -@pytest.mark.asyncio -async def test_translator(app): - assert isinstance(app.translator, process.GroovyTranslator) - await app.close() @pytest.mark.asyncio async def test_aliases(app): diff --git a/tests/test_client.py b/tests/test_client.py index 8077b952f783ad8c2429faebcf95104e9b479c53..889ffe6fad09cb1350544992f7402ddedf08b74a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,8 +16,12 @@ # along with Goblin. If not, see <http://www.gnu.org/licenses/>. import asyncio +import uuid + import pytest +from goblin.driver.server import GremlinServer + @pytest.mark.asyncio async def test_client_auto_release(cluster): @@ -38,3 +42,23 @@ async def test_alias(cluster): assert aliased_client._aliases == {"g": "g1"} assert aliased_client._cluster is client._cluster assert aliased_client._loop is client._loop + await cluster.close() + + +@pytest.mark.asyncio +async def test_sessioned_client(cluster): + session = str(uuid.uuid4()) + client = await cluster.connect(session=session) + assert isinstance(client.cluster, GremlinServer) + resp = await client.submit(gremlin="v = g.addV('person').property('name', 'joe').next(); v") + async for msg in resp: + try: + assert msg['properties']['name'][0]['value'] == 'joe' + except KeyError: + assert msg['properties']['name'][0]['@value']['value'] == 'joe' + + resp = await client.submit(gremlin="g.V(v.id).values('name')") + + async for msg in resp: + assert msg == 'joe' + await cluster.close() diff --git a/tests/test_config.py b/tests/test_config.py index b86c3d30cbdcfea53e63bce5e4fa6cf4b6ec314d..72d4bcf4189a5c03e87f25ab43bb6fc543d1ae9c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -21,6 +21,9 @@ import pytest from goblin import driver, exception +dirname = os.path.dirname(os.path.dirname(__file__)) + + def test_cluster_default_config(cluster): assert cluster.config['scheme'] == 'ws' assert cluster.config['hosts'] == ['localhost'] @@ -63,7 +66,6 @@ def test_cluster_custom_config(event_loop, cluster_class): def test_cluster_config_from_json(event_loop, cluster_class): - dirname = os.path.dirname(os.path.dirname(__file__)) cluster = cluster_class(event_loop) cluster.config_from_file(dirname + '/tests/config/config.json') assert cluster.config['scheme'] == 'wss' @@ -78,8 +80,8 @@ def test_cluster_config_from_json(event_loop, cluster_class): assert issubclass(cluster.config['message_serializer'], driver.GraphSONMessageSerializer) + def test_cluster_config_from_yaml(event_loop, cluster_class): - dirname = os.path.dirname(os.path.dirname(__file__)) cluster = cluster_class(event_loop) cluster.config_from_file(dirname + '/tests/config/config.yml') assert cluster.config['scheme'] == 'wss' @@ -92,3 +94,35 @@ def test_cluster_config_from_yaml(event_loop, cluster_class): assert cluster.config['password'] == '' assert issubclass(cluster.config['message_serializer'], driver.GraphSONMessageSerializer) + +@pytest.mark.asyncio +async def test_app_config_from_json(app): + app.config_from_file(dirname + '/tests/config/config.json') + assert app.config['scheme'] == 'wss' + assert app.config['hosts'] == ['localhost'] + assert app.config['port'] == 8182 + assert app.config['ssl_certfile'] == '' + assert app.config['ssl_keyfile'] == '' + assert app.config['ssl_password'] == '' + assert app.config['username'] == 'dave' + assert app.config['password'] == 'mypass' + + assert issubclass(app.config['message_serializer'], + driver.GraphSONMessageSerializer) + await app.close() + + +@pytest.mark.asyncio +async def test_app_config_from_yaml(app): + app.config_from_file(dirname + '/tests/config/config.yml') + assert app.config['scheme'] == 'wss' + assert app.config['hosts'] == ['localhost'] + assert app.config['port'] == 8183 + assert app.config['ssl_certfile'] == '' + assert app.config['ssl_keyfile'] == '' + assert app.config['ssl_password'] == '' + assert app.config['username'] == '' + assert app.config['password'] == '' + assert issubclass(app.config['message_serializer'], + driver.GraphSONMessageSerializer) + await app.close() diff --git a/tests/test_connection.py b/tests/test_connection.py index 12b33b546be542a9aa28846eb9653e9be5ad72ce..5ed44f641decd3aa836381796bed489c6ffb7fa1 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -94,7 +94,7 @@ async def test_stream_done(connection): assert stream.done @pytest.mark.asyncio -async def test_connection(connection): +async def test_connection_response_timeout(connection): async with connection: connection._response_timeout = 0.0000001 with pytest.raises(exception.ResponseTimeoutError): diff --git a/tests/test_connection_protocol.py b/tests/test_connection_protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..227cdec02c2580772ca3a5a246b6196ad4491f5d --- /dev/null +++ b/tests/test_connection_protocol.py @@ -0,0 +1,137 @@ +# Copyright 2016 ZEROFAIL +# +# This file is part of Goblin. +# +# Goblin is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Goblin is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Goblin. If not, see <http://www.gnu.org/licenses/>. + +import asyncio +import uuid +import pytest + +from goblin import exception +from goblin.driver import serializer + + +@pytest.mark.asyncio +async def test_eval(remote_graph, connection): + async with connection: + connection._message_serializer = serializer.GraphSON2MessageSerializer() + g = remote_graph.traversal() + traversal = "g.addV('person').property('name', 'leifur')" + resp = await connection.submit( + processor='', op='eval', gremlin=traversal, scriptEvalTimeout=1) + + async for msg in resp: + assert msg['label'] == 'person' + + +@pytest.mark.asyncio +async def test_bytecode(remote_graph, connection): + async with connection: + connection._message_serializer = serializer.GraphSON2MessageSerializer() + g = remote_graph.traversal() + traversal = g.addV('person').property('name', 'leifur') + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + vid = msg.id + traversal = g.V(vid).label() + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + assert msg == 'person' + traversal = g.V(vid).name + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + assert msg == 'leifur' + + +@pytest.mark.asyncio +async def test_side_effects(remote_graph, connection): + async with connection: + connection._message_serializer = serializer.GraphSON2MessageSerializer() + g = remote_graph.traversal() + # Add some nodes + traversal = g.addV('person').property('name', 'leifur') + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + pass + traversal = g.addV('person').property('name', 'dave') + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + pass + traversal = g.addV('person').property('name', 'jonathan') + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + async for msg in resp: + pass + + # # Make a query + traversal = g.V().aggregate('a').aggregate('b') + resp = await connection.submit( + processor='traversal', op='bytecode', gremlin=traversal.bytecode) + request_id = resp.request_id + async for msg in resp: + pass + resp = await connection.submit(processor='traversal', op='keys', + sideEffect=request_id) + keys = [] + async for msg in resp: + keys.append(msg) + assert keys == ['a', 'b'] + + resp = await connection.submit(processor='traversal', op='gather', + sideEffect=request_id, + sideEffectKey='a') + side_effects = [] + async for msg in resp: + side_effects.append(msg) + assert side_effects + + # Close isn't implmented yet + # resp = await connection.submit(processor='traversal', op='close', + # sideEffect=request_id) + # async for msg in resp: + # print(msg) + + +@pytest.mark.asyncio +async def test_session(connection): + async with connection: + connection._message_serializer = serializer.GraphSON2MessageSerializer() + session = str(uuid.uuid4()) + resp = await connection.submit( + gremlin="v = g.addV('person').property('name', 'unused_name').next(); v", + processor='session', + op='eval', + session=session) + async for msg in resp: + assert msg['label'] == 'person' + resp = await connection.submit( + gremlin="v.values('name')", + processor='session', + op='eval', + session=session) + async for msg in resp: + assert msg == 'unused_name' + # Close isnt' implemented yet + # resp = await connection.submit( + # processor='session', + # op='close', + # session=session) + # async for msg in resp: + # print(msg) diff --git a/tests/test_graph.py b/tests/test_graph.py index c538f3a30927bc4c6c2f112356bfa92f576ef19d..c4a9ec7f71df191045641217ced28904475602a1 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -17,42 +17,64 @@ import pytest -from gremlin_python import process - - -@pytest.mark.asyncio -async def test_close_graph(remote_graph): - remote_connection = remote_graph.remote_connection - await remote_graph.close() - assert remote_connection.closed - +from goblin.driver import serializer -@pytest.mark.asyncio -async def test_conn_context_manager(remote_graph): - remote_connection = remote_graph.remote_connection - async with remote_graph: - assert not remote_graph.remote_connection.closed - assert remote_connection.closed +from gremlin_python import process @pytest.mark.asyncio -async def test_generate_traversal(remote_graph): - async with remote_graph: - traversal = remote_graph.traversal().V().hasLabel(('v1', 'person')) - assert isinstance(traversal, process.GraphTraversal) - assert traversal.bindings['v1'] == 'person' +async def test_generate_traversal(remote_graph, connection): + async with connection: + g = remote_graph.traversal().withRemote(connection) + traversal = g.V().hasLabel(('v1', 'person')) + assert isinstance(traversal, process.graph_traversal.GraphTraversal) + assert traversal.bytecode.bindings['v1'] == 'person' @pytest.mark.asyncio -async def test_submit_traversal(remote_graph): - async with remote_graph: - g = remote_graph.traversal() - resp = await g.addV('person').property('name', 'leifur').next() - leif = await resp.fetch_data() - resp.close() +async def test_submit_traversal(remote_graph, connection): + async with connection: + g = remote_graph.traversal().withRemote(connection) + resp = g.addV('person').property('name', 'leifur') + leif = await resp.next() + resp.traversers.close() assert leif['properties']['name'][0]['value'] == 'leifur' assert leif['label'] == 'person' - resp = await g.V(leif['id']).drop().next() - none = await resp.fetch_data() - resp.close() + resp = g.V(leif['id']).drop() + none = await resp.next() assert none is None + + +@pytest.mark.asyncio +async def test_side_effects(remote_graph, connection): + async with connection: + connection._message_serializer = serializer.GraphSON2MessageSerializer() + g = remote_graph.traversal().withRemote(connection) + # create some nodes + resp = g.addV('person').property('name', 'leifur') + leif = await resp.next() + resp.traversers.close() + resp = g.addV('person').property('name', 'dave') + dave = await resp.next() + resp.traversers.close() + resp = g.addV('person').property('name', 'jon') + jonthan = await resp.next() + resp.traversers.close() + traversal = g.V().aggregate('a').aggregate('b') + async for msg in traversal: + pass + keys = [] + resp = await traversal.side_effects.keys() + async for msg in resp: + keys.append(msg) + assert keys == ['a', 'b'] + side_effects = [] + resp = await traversal.side_effects.get('a') + async for msg in resp: + side_effects.append(msg) + assert side_effects + side_effects = [] + resp = await traversal.side_effects.get('b') + async for msg in resp: + side_effects.append(msg) + assert side_effects diff --git a/tests/test_session.py b/tests/test_session.py index 25dc2886854cc809005abf65970c71c0a9a300c9..59b2eeb55dbeda15e32ad545427565c8999d19e6 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -20,7 +20,15 @@ import pytest from goblin import element -from goblin.traversal import bindprop +from goblin.session import bindprop +from gremlin_python.process.translator import GroovyTranslator + + +def test_bindprop(person_class): + db_val, (binding, val) = bindprop(person_class, 'name', 'dave', binding='n1') + assert db_val == 'name' + assert binding == 'n1' + assert val == 'dave' class TestCreationApi: @@ -149,14 +157,14 @@ class TestCreationApi: person.name = 'dave' person.age = 35 await session.save(person) - result = await session.g.V(person.id).one_or_none() + result = await session.g.V(person.id).oneOrNone() assert result is person rid = result.id await session.remove_vertex(person) - result = await session.g.V(rid).one_or_none() + result = await session.g.V(rid).oneOrNone() assert not result await app.close() - +# @pytest.mark.asyncio async def test_remove_edge(self, app, person_class, place_class, lives_in_class): @@ -169,11 +177,11 @@ class TestCreationApi: lives_in = lives_in_class(jon, montreal) session.add(jon, montreal, lives_in) await session.flush() - result = await session.g.E(lives_in.id).one_or_none() + result = await session.g.E(lives_in.id).oneOrNone() assert result is lives_in rid = result.id await session.remove_edge(lives_in) - result = await session.g.E(rid).one_or_none() + result = await session.g.E(rid).oneOrNone() assert not result await app.close() @@ -219,9 +227,10 @@ class TestTraversalApi: knows_class): session = await app.session() traversal = session.traversal(person_class) - assert repr(traversal) == 'g.V().hasLabel("person")' + translator = GroovyTranslator('g') + assert translator.translate(traversal.bytecode) == 'g.V().hasLabel("person")' traversal = session.traversal(knows_class) - assert repr(traversal) == 'g.E().hasLabel("knows")' + assert translator.translate(traversal.bytecode) == 'g.E().hasLabel("knows")' await app.close() @@ -233,7 +242,7 @@ class TestTraversalApi: jon = person_class() session.add(dave, leif, jon) await session.flush() - resp = await session.traversal(person_class).all() + resp = session.traversal(person_class) results = [] async for msg in resp: assert isinstance(msg, person_class) @@ -242,14 +251,14 @@ class TestTraversalApi: await app.close() @pytest.mark.asyncio - async def test_one_or_none_one(self, app, person_class): + async def test_oneOrNone_one(self, app, person_class): session = await app.session() dave = person_class() leif = person_class() jon = person_class() session.add(dave, leif, jon) await session.flush() - resp = await session.traversal(person_class).one_or_none() + resp = await session.traversal(person_class).oneOrNone() assert isinstance(resp, person_class) await app.close() @@ -261,14 +270,14 @@ class TestTraversalApi: result1 = await session.save(itziri) bound_name = bindprop(person_class, 'name', 'itziri', binding='v1') p1 = await session.traversal(person_class).has( - *bound_name).one_or_none() + *bound_name).oneOrNone() await app.close() @pytest.mark.asyncio - async def test_one_or_none_none(self, app): + async def test_oneOrNone_none(self, app): session = await app.session() none = await session.g.V().hasLabel( - 'a very unlikey label').one_or_none() + 'a very unlikey label').oneOrNone() assert not none await app.close() @@ -276,7 +285,7 @@ class TestTraversalApi: async def test_vertex_deserialization(self, app, person_class): session = await app.session() resp = await session.g.addV('person').property( - person_class.name, 'leif').property('place_of_birth', 'detroit').one_or_none() + person_class.name, 'leif').property('place_of_birth', 'detroit').oneOrNone() assert isinstance(resp, person_class) assert resp.name == 'leif' assert resp.place_of_birth == 'detroit' @@ -285,12 +294,12 @@ class TestTraversalApi: @pytest.mark.asyncio async def test_edge_desialization(self, app, knows_class): session = await app.session() - p1 = await session.g.addV('person').one_or_none() - p2 = await session.g.addV('person').one_or_none() + p1 = await session.g.addV('person').oneOrNone() + p2 = await session.g.addV('person').oneOrNone() e1 = await session.g.V(p1.id).addE('knows').to( session.g.V(p2.id)).property( knows_class.notes, 'somehow').property( - 'how_long', 1).one_or_none() + 'how_long', 1).oneOrNone() assert isinstance(e1, knows_class) assert e1.notes == 'somehow' assert e1.how_long == 1 @@ -300,7 +309,7 @@ class TestTraversalApi: async def test_unregistered_vertex_deserialization(self, app): session = await app.session() dave = await session.g.addV( - 'unregistered').property('name', 'dave').one_or_none() + 'unregistered').property('name', 'dave').oneOrNone() assert isinstance(dave, element.GenericVertex) assert dave.name == 'dave' assert dave.__label__ == 'unregistered' @@ -309,10 +318,10 @@ class TestTraversalApi: @pytest.mark.asyncio async def test_unregistered_edge_desialization(self, app): session = await app.session() - p1 = await session.g.addV('person').one_or_none() - p2 = await session.g.addV('person').one_or_none() + p1 = await session.g.addV('person').oneOrNone() + p2 = await session.g.addV('person').oneOrNone() e1 = await session.g.V(p1.id).addE('unregistered').to( - session.g.V(p2.id)).property('how_long', 1).one_or_none() + session.g.V(p2.id)).property('how_long', 1).oneOrNone() assert isinstance(e1, element.GenericEdge) assert e1.how_long == 1 assert e1.__label__ == 'unregistered' @@ -322,8 +331,8 @@ class TestTraversalApi: async def test_property_deserialization(self, app): session = await app.session() p1 = await session.g.addV('person').property( - 'name', 'leif').one_or_none() - name = await session.g.V(p1.id).properties('name').one_or_none() + 'name', 'leif').oneOrNone() + name = await session.g.V(p1.id).properties('name').oneOrNone() assert name['value'] == 'leif' assert name['label'] == 'name' await app.close() @@ -332,7 +341,7 @@ class TestTraversalApi: async def test_non_element_deserialization(self, app): session = await app.session() p1 = await session.g.addV('person').property( - 'name', 'leif').one_or_none() - one = await session.g.V(p1.id).count().one_or_none() + 'name', 'leif').oneOrNone() + one = await session.g.V(p1.id).count().oneOrNone() assert one == 1 await app.close() diff --git a/tests/test_traversal.py b/tests/test_traversal.py deleted file mode 100644 index 999821c264c3ffda6585bd5a77dcc43a64388b99..0000000000000000000000000000000000000000 --- a/tests/test_traversal.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2016 ZEROFAIL -# -# This file is part of Goblin. -# -# Goblin is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Goblin is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Goblin. If not, see <http://www.gnu.org/licenses/>. - -from goblin.traversal import bindprop - - -def test_bindprop(person_class): - db_val, (binding, val) = bindprop(person_class, 'name', 'dave', binding='n1') - assert db_val == 'name' - assert binding == 'n1' - assert val == 'dave' diff --git a/tests/test_vertex_properties_functional.py b/tests/test_vertex_properties_functional.py index 703d085fe769ea3f81ab03f8bd9a2133f8f5c0e9..ae9c9997ca3bacfc40268b5df930dc9fade5de9d 100644 --- a/tests/test_vertex_properties_functional.py +++ b/tests/test_vertex_properties_functional.py @@ -1,3 +1,20 @@ +# Copyright 2016 ZEROFAIL +# +# This file is part of Goblin. +# +# Goblin is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Goblin is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Goblin. If not, see <http://www.gnu.org/licenses/>. + import pytest