diff --git a/aiogremlin/__init__.py b/aiogremlin/__init__.py index 6b56a30e79f0c6e3e4cadc0207dc91d136789e5e..48332a24203b242857bc873f758473eb601176ce 100644 --- a/aiogremlin/__init__.py +++ b/aiogremlin/__init__.py @@ -1,7 +1,9 @@ -from .response import * -from .client import * -from .exceptions import * -from .connector import * -from .subprotocol import * +from aiogremlin.driver.cluster import Cluster +from aiogremlin.remote.driver_remote_connection import DriverRemoteConnection +from aiogremlin.gremlin_python import statics +from aiogremlin.gremlin_python.process import strategies +from aiogremlin.gremlin_python.process.graph_traversal import __ +from aiogremlin.gremlin_python.process.traversal import Binding +from aiogremlin.gremlin_python.structure.graph import Graph -__version__ = "0.1.1" +__version__ = "3.2.4" diff --git a/aiogremlin/client.py b/aiogremlin/client.py deleted file mode 100644 index 6f4459bf4b3c52a000536389dd95763449de04ec..0000000000000000000000000000000000000000 --- a/aiogremlin/client.py +++ /dev/null @@ -1,416 +0,0 @@ -"""Client for the Tinkerpop 3 Gremlin Server.""" - -import asyncio -import uuid - -import aiohttp - -from aiogremlin.response import GremlinClientWebSocketResponse -from aiogremlin.exceptions import RequestError, GremlinServerError -from aiogremlin.connector import GremlinConnector -from aiogremlin.subprotocol import gremlin_response_parser, GremlinWriter - -__all__ = ("submit", "GremlinClient", "GremlinClientSession", - "GremlinResponse", "GremlinResponseStream") - - -class GremlinClient: - """Main interface for interacting with the Gremlin Server. - - :param str url: url for Gremlin Server (optional). 'http://localhost:8182/' - by default - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None``, - `asyncio.get_event_loop` is used for getting default event loop - (optional) - :param str lang: Language of scripts submitted to the server. - "gremlin-groovy" by default - :param str op: Gremlin Server op argument. "eval" by default. - :param str processor: Gremlin Server processor argument. "" by default. - :param float timeout: timeout for websocket read (seconds)(optional). - Values ``0`` or ``None`` mean no timeout - :param ws_connector: A class that implements the method ``ws_connect``. - Usually an instance of ``aiogremlin.connector.GremlinConnector`` - :param float conn_timeout: timeout for establishing connection (seconds) - (optional). Values ``0`` or ``None`` mean no timeout - :param username: Username for SASL auth - :param password: Password for SASL auth - """ - - def __init__(self, *, url='http://localhost:8182/', loop=None, - lang="gremlin-groovy", op="eval", processor="", - timeout=None, ws_connector=None, client_session=None, - conn_timeout=None, username="", password=""): - self._lang = lang - self._op = op - self._processor = processor - self._loop = loop or asyncio.get_event_loop() - self._closed = False - self._session = None - self._url = url - self._timeout = timeout - self._username = username - self._password = password - if ws_connector is None: - ws_connector = GremlinConnector(loop=self._loop, - client_session=client_session, - conn_timeout=conn_timeout) - self._connector = ws_connector - - @property - def loop(self): - """Readonly property that returns event loop used by client""" - return self._loop - - @property - def op(self): - """Readonly property that returns op argument for Gremlin Server""" - return self._op - - @property - def processor(self): - """Readonly property. The processor argument for Gremlin - Server""" - return self._processor - - @property - def lang(self): - """Readonly property. The language used for Gremlin scripts""" - return self._lang - - @property - def url(self): - """Getter/setter for database url used by the client""" - return self._url - - @url.setter - def url(self, value): - self._url = value - - @property - def closed(self): - """Readonly property. Return True if client has been closed""" - return self._closed or self._connector is None - - @asyncio.coroutine - def close(self): - """ - :ref:`coroutine<coroutine>` method. - - Close client. If client has not been detached from underlying - ws_connector, this coroutinemethod closes the latter as well.""" - if self._closed: - return - self._closed = True - try: - yield from self._connector.close() - finally: - self._connector = None - - def detach(self): - """Detach client from ws_connector. Client status is now closed""" - self._connector = None - - @asyncio.coroutine - def submit(self, gremlin, *, bindings=None, lang=None, rebindings=None, - op=None, processor=None, binary=True, session=None, - timeout=None): - """ - :ref:`coroutine<coroutine>` method. - - Submit a script to the Gremlin Server. - - :param str gremlin: Gremlin script to submit to server. - :param dict bindings: A mapping of bindings for Gremlin script. - :param str lang: Language of scripts submitted to the server. - "gremlin-groovy" by default - :param dict rebindings: Rebind ``Graph`` and ``TraversalSource`` - objects to different variable names in the current request - :param str op: Gremlin Server op argument. "eval" by default. - :param str processor: Gremlin Server processor argument. "" by default. - :param float timeout: timeout for establishing connection (optional). - Values ``0`` or ``None`` mean no timeout - :param str session: Session id (optional). Typically a uuid - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None`` - `asyncio.get_event_loop` is used for getting default event loop - (optional) - :returns: :py:class:`aiogremlin.client.GremlinResponse` object - """ - lang = lang or self.lang - op = op or self.op - processor = processor or self.processor - if session is None: - session = self._session - if timeout is None: - timeout = self._timeout - - ws = yield from self._connector.ws_connect( - self.url, timeout=timeout) - - writer = GremlinWriter(ws) - - ws = writer.write(gremlin=gremlin, bindings=bindings, lang=lang, - rebindings=rebindings, op=op, - processor=processor, binary=binary, - session=session) - - return GremlinResponse(ws, username=self._username, - password=self._password, session=session, - loop=self._loop) - - @asyncio.coroutine - def execute(self, gremlin, *, bindings=None, lang=None, rebindings=None, - session=None, op=None, processor=None, binary=True, - timeout=None): - """ - :ref:`coroutine<coroutine>` method. - - Submit a script to the Gremlin Server and get the result. - - :param str gremlin: Gremlin script to submit to server. - :param dict bindings: A mapping of bindings for Gremlin script. - :param str lang: Language of scripts submitted to the server. - "gremlin-groovy" by default - :param dict rebindings: Rebind ``Graph`` and ``TraversalSource`` - objects to different variable names in the current request - :param str op: Gremlin Server op argument. "eval" by default. - :param str processor: Gremlin Server processor argument. "" by default. - :param float timeout: timeout for establishing connection (optional). - Values ``0`` or ``None`` mean no timeout - :param str session: Session id (optional). Typically a uuid - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None`` - `asyncio.get_event_loop` is used for getting default event loop - (optional) - :returns: :py:class:`list` of - :py:class:`aiogremlin.subprotocol.Message` - """ - lang = lang or self.lang - op = op or self.op - processor = processor or self.processor - resp = yield from self.submit(gremlin, bindings=bindings, lang=lang, - rebindings=rebindings, op=op, - processor=processor, binary=binary, - session=session, timeout=timeout) - - return (yield from resp.get()) - - -class GremlinClientSession(GremlinClient): - """Interface for interacting with the Gremlin Server using sessions. - - :param str url: url for Gremlin Server (optional). 'http://localhost:8182/' - by default - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None``, - `asyncio.get_event_loop` is used for getting default event loop - (optional) - :param str lang: Language of scripts submitted to the server. - "gremlin-groovy" by default - :param str op: Gremlin Server op argument. "eval" by default. - :param str processor: Gremlin Server processor argument. "" by default. - :param float timeout: timeout for establishing connection (optional). - Values ``0`` or ``None`` mean no timeout - """ - - def __init__(self, *, url='http://localhost:8182/', loop=None, - lang="gremlin-groovy", op="eval", processor="session", - session=None, timeout=None, client_session=None, - ws_connector=None, username="", password=""): - super().__init__(url=url, lang=lang, op=op, processor=processor, - loop=loop, timeout=timeout, ws_connector=ws_connector, - client_session=client_session, username=username, - password=password) - - if session is None: - session = str(uuid.uuid4()) - self._session = session - - @property - def session(self): - """Getter setter property for session id.""" - return self._session - - @session.setter - def session(self, value): - self._session = value - - def reset_session(self, session=None): - """ - Reset session id. - - :param str session: A unique session id (optional). If None, an id will - be generated using :py:func:`uuid.uuid4`. - - :returns: New session id. - """ - if session is None: - session = str(uuid.uuid4()) - self._session = session - return self._session - - -class GremlinResponse: - """Main interface for reading Gremlin Server responses. Typically returned - by ``GremlinClient.submit``, not created by user. - - :param ``aiogremlin.response.GremlinClientWebSocketResponse`` ws: Websocket - connection. - :param str session: Session id (optional). Typically a uuid - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None``, - `asyncio.get_event_loop` is used for getting default event loop - (optional) - """ - def __init__(self, ws, *, session=None, loop=None, username="", - password=""): - self._loop = loop or asyncio.get_event_loop() - self._session = session - self._stream = GremlinResponseStream(ws, username, password, - loop=self._loop) - - @property - def stream(self): - """Read-only property used to get data from the stream in chunks. - - :returns: :py:class:`aiogremlin.client.ResponseStream`""" - return self._stream - - @property - def session(self): - """Session ID (if applicable).""" - return self._session - - @asyncio.coroutine - def get(self): - """ - :ref:`coroutine<coroutine>` method. - - Get all messages from the stream. - - :returns: :py:class:`list` :py:class:`aiogremlin.subprotocol.Message` - """ - return (yield from self._run()) - - @asyncio.coroutine - def _run(self): - results = [] - while True: - message = yield from self._stream.read() - if message is None: - break - results.append(message) - return results - - -class GremlinResponseStream: - """ - Encapsulate and read Gremlin Server responses. Typically instantiated by - GremlinResponse constructor, not by user. - - :param ``aiogremlin.response.GremlinClientWebSocketResponse`` ws: Websocket - connection. - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None``, - `asyncio.get_event_loop` is used for getting default event loop - (optional) - """ - def __init__(self, ws, username, password, loop=None): - self._ws = ws - self._username = username - self._password = password - self._loop = loop or asyncio.get_event_loop() - data_stream = aiohttp.DataQueue(loop=self._loop) - self._stream = self._ws.parser.set_parser(gremlin_response_parser, - output=data_stream) - - @asyncio.coroutine - def read(self): - """ - :ref:`coroutine<coroutine>` method - - Read a message from the stream. - - :returns: :py:class:`aiogremlin.subprotocol.Message` - """ - if self._stream.at_eof(): - yield from self._ws.release() - message = None - else: - asyncio.Task(self._ws.receive(), loop=self._loop) - try: - message = yield from self._stream.read() - if message.status_code == 407: - writer = GremlinWriter(self._ws) - writer.write(op="authentication", username=self._username, - password=self._password) - asyncio.Task(self._ws.receive(), loop=self._loop) - message = yield from self._stream.read() - except (RequestError, GremlinServerError): - yield from self._ws.release() - raise - return message - - -@asyncio.coroutine -def submit(gremlin, *, - url='http://localhost:8182/', - bindings=None, - lang="gremlin-groovy", - rebindings=None, - op="eval", - processor="", - timeout=None, - session=None, - loop=None, - conn_timeout=None, - username="", - password=""): - """ - :ref:`coroutine<coroutine>` - - Submit a script to the Gremlin Server. - - :param str gremlin: The Gremlin script. - :param str url: url for Gremlin Server (optional). 'http://localhost:8182/' - by default - :param dict bindings: A mapping of bindings for Gremlin script. - :param str lang: Language of scripts submitted to the server. - "gremlin-groovy" by default - :param dict rebindings: Rebind ``Graph`` and ``TraversalSource`` - objects to different variable names in the current request - :param str op: Gremlin Server op argument. "eval" by default. - :param str processor: Gremlin Server processor argument. "" by default. - :param float timeout: timeout for establishing connection (optional). - Values ``0`` or ``None`` mean no timeout - :param str session: Session id (optional). Typically a uuid - :param loop: :ref:`event loop<asyncio-event-loop>` If param is ``None``, - `asyncio.get_event_loop` is used for getting default event loop - (optional) - :param float conn_timeout: timeout for establishing connection (seconds) - (optional). Values ``0`` or ``None`` mean no timeout - :param username: Username for SASL auth - :param password: Password for SASL auth - :returns: :py:class:`aiogremlin.client.GremlinResponse` object - """ - - if loop is None: - loop = asyncio.get_event_loop() - - connector = aiohttp.TCPConnector(force_close=True, loop=loop, - verify_ssl=False, - conn_timeout=conn_timeout) - - client_session = aiohttp.ClientSession( - connector=connector, loop=loop, - ws_response_class=GremlinClientWebSocketResponse) - - gremlin_client = GremlinClient(url=url, loop=loop, - ws_connector=client_session, - username=username, password=password) - - try: - resp = yield from gremlin_client.submit( - gremlin, bindings=bindings, lang=lang, rebindings=rebindings, - op=op, processor=processor, session=session, timeout=timeout) - - return resp - - finally: - gremlin_client.detach() - client_session.detach() diff --git a/aiogremlin/connector.py b/aiogremlin/connector.py deleted file mode 100644 index f3eae44c5ab613ab0797d84e7b0067b3680f7783..0000000000000000000000000000000000000000 --- a/aiogremlin/connector.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Websocket connection factory and manager.""" - -import asyncio - -from aiowebsocketclient import WebSocketConnector - -from aiogremlin.response import GremlinClientWebSocketResponse - -__all__ = ("GremlinConnector",) - - -class GremlinConnector(WebSocketConnector): - """Create and manage reusable websocket connections. Out of the box - support for multiple enpoints (databases). - - :param float conn_timeout: timeout for establishing connection (seconds) - (optional). Values ``0`` or ``None`` mean no timeout - :param bool force_close: close websockets after release - :param int limit: limit for total open websocket connections - :param aiohttp.client.ClientSession client_session: Underlying HTTP - session used to establish websocket connections - :param loop: `event loop` If param is ``None``, `asyncio.get_event_loop` - is used for getting default event loop (optional) - :param ws_response_class: WebSocketResponse class implementation. - ``ClientWebSocketResponse`` by default - :param bool verbose: Set log level to info. False by default - """ - def __init__(self, *, conn_timeout=None, force_close=False, limit=1024, - client_session=None, loop=None, verbose=False): - - super().__init__(conn_timeout=conn_timeout, force_close=force_close, - limit=limit, client_session=client_session, loop=loop, - ws_response_class=GremlinClientWebSocketResponse) diff --git a/aiogremlin/driver/__init__.py b/aiogremlin/driver/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4861b633fc5f4293603229f4cab9d3282df4c4a1 --- /dev/null +++ b/aiogremlin/driver/__init__.py @@ -0,0 +1,7 @@ +from aiogremlin.driver import provider +from aiogremlin.driver.client import Client +from aiogremlin.driver.cluster import Cluster +from aiogremlin.driver.connection import Connection +from aiogremlin.driver.pool import ConnectionPool +from aiogremlin.driver.protocol import GremlinServerWSProtocol +from aiogremlin.driver.server import GremlinServer diff --git a/aiogremlin/driver/aiohttp/__init__.py b/aiogremlin/driver/aiohttp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/aiogremlin/driver/aiohttp/transport.py b/aiogremlin/driver/aiohttp/transport.py new file mode 100644 index 0000000000000000000000000000000000000000..8c15013ed8c7270e901c2dbea76b4cba12a95b8d --- /dev/null +++ b/aiogremlin/driver/aiohttp/transport.py @@ -0,0 +1,36 @@ +import aiohttp + +from aiogremlin.gremlin_python.driver import transport + + +class AiohttpTransport(transport.AbstractBaseTransport): + + def __init__(self, loop): + self._loop = loop + self._connected = False + + async def connect(self, url, *, ssl_context=None): + await self.close() + connector = aiohttp.TCPConnector( + ssl_context=ssl_context, loop=self._loop) + self._client_session = aiohttp.ClientSession( + loop=self._loop, connector=connector) + self._ws = await self._client_session.ws_connect(url) + self._connected = True + + def write(self, message): + self._ws.send_bytes(message) + + async def read(self): + return await self._ws.receive() + + async def close(self): + if self._connected: + if not self._ws.closed: + await self._ws.close() + if not self._client_session.closed: + await self._client_session.close() + + @property + def closed(self): + return self._ws.closed or self._client_session.closed diff --git a/aiogremlin/driver/client.py b/aiogremlin/driver/client.py new file mode 100644 index 0000000000000000000000000000000000000000..6bd597fe16108b4bac1bdbbda392b2b0716f6ead --- /dev/null +++ b/aiogremlin/driver/client.py @@ -0,0 +1,81 @@ +"""Client for the Tinkerpop 3 Gremlin Server.""" + +from aiogremlin import exception +from aiogremlin.gremlin_python.driver import request +from aiogremlin.gremlin_python.process import traversal + + +class Client: + """ + Client that utilizes a :py:class:`Cluster<aiogremlin.cluster.Cluster>` + to access a cluster of Gremlin Server hosts. Issues requests to hosts using + a round robin strategy. + + :param aiogremlin.cluster.Cluster cluster: Cluster used by + client + :param asyncio.BaseEventLoop loop: + """ + def __init__(self, cluster, loop, *, aliases=None, processor=None, + op=None): + self._cluster = cluster + self._loop = loop + if aliases is None: + aliases = {} + self._aliases = aliases + if processor is None: + processor = '' + self._processor = processor + if op is None: + op = 'eval' + self._op = op + + @property + def aliases(self): + return self._aliases + + @property + def message_serializer(self): + return self.cluster.config['message_serializer'] + + @property + def cluster(self): + """ + Readonly property. + + :returns: The instance of + :py:class:`Cluster<aiogremlin.driver.cluster.Cluster>` associated with + client. + """ + return self._cluster + + async def close(self): + await self._cluster.close() + + def alias(self, aliases): + client = Client(self._cluster, self._loop, + aliases=aliases) + return client + + async def submit(self, message, bindings=None): + """ + **coroutine** Submit a script and bindings to the Gremlin Server. + + :returns: :py:class:`ResultSet<aiogremlin.driver.resultset.ResultSet>` + object + """ + if isinstance(message, traversal.Bytecode): + message = request.RequestMessage( + processor='traversal', op='bytecode', + args={'gremlin': message, + 'aliases': self._aliases}) + elif isinstance(message, str): + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': message, + 'aliases': self._aliases}) + if bindings: + message.args.update({'bindings': bindings}) + conn = await self.cluster.get_connection() + resp = await conn.write(message) + self._loop.create_task(conn.release_task(resp)) + return resp diff --git a/aiogremlin/driver/cluster.py b/aiogremlin/driver/cluster.py new file mode 100644 index 0000000000000000000000000000000000000000..abaa8af7c6d639ca257feecdac29a02c430f357d --- /dev/null +++ b/aiogremlin/driver/cluster.py @@ -0,0 +1,199 @@ +import asyncio +import collections +import configparser +import importlib +import ssl + +try: + import ujson as json +except ImportError: + import json + +import yaml + +from aiogremlin import exception +from aiogremlin import driver +from aiogremlin.gremlin_python.driver import serializer + + +def my_import(name): + names = name.rsplit('.', maxsplit=1) + if len(names) != 2: + raise exception.ConfigError("not a valid absolute python path to a class: {}".format(name)) + module_name, class_name = names + try: + module = importlib.import_module(module_name) + except ImportError: + raise exception.ConfigError( + "Error processing cluster configuration: could not import {}".format(name)) + return getattr(module, class_name) + + +class Cluster: + """ + A cluster of Gremlin Server hosts. This object provides the main high + level interface used by the :py:mod:`aiogremlin` module. + + :param asyncio.BaseEventLoop loop: + """ + + DEFAULT_CONFIG = { + 'scheme': 'ws', + 'hosts': ['localhost'], + 'port': 8182, + 'ssl_certfile': '', + 'ssl_keyfile': '', + 'ssl_password': '', + 'username': '', + 'password': '', + 'response_timeout': None, + 'max_conns': 4, + 'min_conns': 1, + 'max_times_acquired': 16, + 'max_inflight': 64, + 'message_serializer': 'aiogremlin.gremlin_python.driver.serializer.GraphSONMessageSerializer', + 'provider': 'aiogremlin.driver.provider.TinkerGraph' + } + + def __init__(self, loop, aliases=None, **config): + self._loop = loop + default_config = dict(self.DEFAULT_CONFIG) + default_config.update(config) + self._config = self._process_config_imports(default_config) + self._hosts = collections.deque() + self._closed = False + if aliases is None: + aliases = {} + self._aliases = aliases + + @classmethod + async def open(cls, loop, *, aliases=None, configfile=None, **config): + """ + **coroutine** Open a cluster, connecting to all available hosts as + specified in configuration. + + :param asyncio.BaseEventLoop loop: + :param str configfile: Optional configuration file in .json or + .yml format + """ + cluster = cls(loop, aliases=aliases, **config) + if configfile: + cluster.config_from_file(configfile) + await cluster.establish_hosts() + return cluster + + @property + def hosts(self): + return self._hosts + + @property + def config(self): + """ + Readonly property. + + :returns: `dict` containing the cluster configuration + """ + return self._config + + async def get_connection(self): + """ + **coroutine** Get connection from next available host in a round robin + fashion. + + :returns: :py:class:`Connection<aiogremlin.connection.Connection>` + """ + if not self._hosts: + await self.establish_hosts() + host = self._hosts.popleft() + conn = await host.get_connection() + self._hosts.append(host) + return conn + + async def establish_hosts(self): + """ + **coroutine** Connect to all hosts as specified in configuration. + """ + scheme = self._config['scheme'] + hosts = self._config['hosts'] + port = self._config['port'] + for host in hosts: + url = '{}://{}:{}/gremlin'.format(scheme, host, port) + host = await driver.GremlinServer.open( + url, self._loop, **dict(self._config)) + self._hosts.append(host) + + def config_from_file(self, filename): + """ + Load configuration from from file. + + :param str filename: Path to the configuration file. + """ + if filename.endswith('yml') or filename.endswith('yaml'): + self.config_from_yaml(filename) + elif filename.endswith('.json'): + self.config_from_json(filename) + else: + raise exception.ConfigurationError('Unknown config file format') + + def config_from_yaml(self, filename): + with open(filename, 'r') as f: + config = yaml.load(f) + config = self._process_config_imports(config) + self._config.update(config) + + def config_from_json(self, filename): + """ + Load configuration from from JSON file. + + :param str filename: Path to the configuration file. + """ + with open(filename, 'r') as f: + config = json.load(f) + config = self._process_config_imports(config) + self.config.update(config) + + def _process_config_imports(self, config): + message_serializer = config.get('message_serializer') + provider = config.get('provider') + if isinstance(message_serializer, str): + config['message_serializer'] = my_import(message_serializer) + if isinstance(provider, str): + config['provider'] = my_import(provider) + return config + + def config_from_module(self, module): + if isinstance(module, str): + module = importlib.import_module(module) + config = dict() + for item in dir(module): + if not item.startswith('_') and item.lower() in self.DEFAULT_CONFIG: + config[item.lower()] = getattr(module, item) + config = self._process_config_imports(config) + self.config.update(config) + + async def connect(self, aliases=None): + """ + **coroutine** Get a connected client. Main API method. + + :returns: A connected instance of `Client<aiogremlin.client.Client>` + """ + aliases = aliases or self._aliases + if not self._hosts: + await self.establish_hosts() + # if session: + # host = self._hosts.popleft() + # client = client.SessionedClient(host, self._loop, session, + # aliases=aliases) + # self._hosts.append(host) + # else: + client = driver.Client(self, self._loop, aliases=aliases) + return client + + async def close(self): + """**coroutine** Close cluster and all connected hosts.""" + waiters = [] + while self._hosts: + host = self._hosts.popleft() + waiters.append(host.close()) + await asyncio.gather(*waiters, loop=self._loop) + self._closed = True diff --git a/aiogremlin/driver/connection.py b/aiogremlin/driver/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..ceab16ea6d54f24dab5fd1d8ec585b4bf2423a6c --- /dev/null +++ b/aiogremlin/driver/connection.py @@ -0,0 +1,166 @@ +import abc +import asyncio +import base64 +import collections +import logging +import uuid + +import aiohttp + +try: + import ujson as json +except ImportError: + import json + +from aiogremlin.driver import provider, resultset +from aiogremlin.driver.protocol import GremlinServerWSProtocol +from aiogremlin.driver.aiohttp.transport import AiohttpTransport +from aiogremlin.gremlin_python.driver import serializer + + +logger = logging.getLogger(__name__) + + +class Connection: + """ + Main classd for interacting with the Gremlin Server. Encapsulates a + websocket connection. Not instantiated directly. Instead use + :py:meth:`Connection.open<aiogremlin.connection.Connection.open>`. + + :param str url: url for host Gremlin Server + :param aiohttp.ClientWebSocketResponse ws: open websocket connection + :param asyncio.BaseEventLoop loop: + :param aiohttp.ClientSession: Client session used to establish websocket + connections + :param float response_timeout: (optional) `None` by default + :param str username: Username for database auth + :param str password: Password for database auth + :param int max_inflight: Maximum number of unprocessed requests at any + one time on the connection + """ + def __init__(self, url, transport, protocol, loop, username, password, + max_inflight, response_timeout, message_serializer, provider): + self._url = url + self._transport = transport + self._protocol = protocol + self._loop = loop + self._response_timeout = response_timeout + self._username = username + self._password = password + self._closed = False + self._result_sets = {} + self._receive_task = self._loop.create_task(self._receive()) + self._semaphore = asyncio.Semaphore(value=max_inflight, + loop=self._loop) + if isinstance(message_serializer, type): + message_serializer = message_serializer() + self._message_serializer = message_serializer + self._provider = provider + + @classmethod + async def open(cls, url, loop, *, + protocol=None, + transport_factory=None, + ssl_context=None, + username='', + password='', + max_inflight=64, + response_timeout=None, + message_serializer=serializer.GraphSONMessageSerializer, + provider=provider.TinkerGraph): + """ + **coroutine** Open a connection to the Gremlin Server. + + :param str url: url for host Gremlin Server + :param asyncio.BaseEventLoop loop: + :param ssl.SSLContext ssl_context: + :param str username: Username for database auth + :param str password: Password for database auth + + :param int max_inflight: Maximum number of unprocessed requests at any + one time on the connection + :param float response_timeout: (optional) `None` by default + + :returns: :py:class:`Connection<aiogremlin.connection.Connection>` + """ + if not protocol: + protocol = GremlinServerWSProtocol(message_serializer) + if not transport_factory: + transport_factory = lambda: AiohttpTransport(loop) + transport = transport_factory() + await transport.connect(url, ssl_context=ssl_context) + return cls(url, transport, protocol, loop, username, password, + max_inflight, response_timeout, message_serializer, + provider) + + @property + def message_serializer(self): + return self._message_serializer + + @property + def closed(self): + """ + Check if connection has been closed. + + :returns: `bool` + """ + return self._closed or self._transport.closed + + @property + def url(self): + """ + Readonly property. + + :returns: str The url association with this connection. + """ + return self._url + + async def write(self, message): + """ + 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:`ResultSet<aiogremlin.driver.resultset.ResultSet>` + object + """ + await self._semaphore.acquire() + request_id = str(uuid.uuid4()) + message = self._message_serializer.serialize_message( + request_id, message) + if self._transport.closed: + await self._transport.connect(self.url) + self._transport.write(message) + result_set = resultset.ResultSet(request_id, self._response_timeout, + self._loop) + self._result_sets[request_id] = result_set + self._loop.create_task( + self._terminate_response(result_set, request_id)) + return result_set + + submit = write + + async def close(self): + """**coroutine** Close underlying connection and mark as closed.""" + self._receive_task.cancel() + await self._transport.close() + self._closed = True + + async def _terminate_response(self, resp, request_id): + await resp.done.wait() + del self._result_sets[request_id] + self._semaphore.release() + + async def _receive(self): + while True: + data = await self._transport.read() + await self._protocol.data_received(data, self._result_sets) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.close() + self._conn = None diff --git a/aiogremlin/driver/pool.py b/aiogremlin/driver/pool.py new file mode 100644 index 0000000000000000000000000000000000000000..32d74721ede6f81262807c9437ae6c6e9af1bb93 --- /dev/null +++ b/aiogremlin/driver/pool.py @@ -0,0 +1,202 @@ +import asyncio +import collections + +import aiohttp + +from aiogremlin.driver import connection + + +class PooledConnection: + """ + Wrapper for :py:class:`Connection<aiogremlin.connection.Connection>` + that helps manage tomfoolery associated with connection pooling. + + :param aiogremlin.connection.Connection conn: + :param aiogremlin.pool.ConnectionPool pool: + """ + def __init__(self, conn, pool): + self._conn = conn + self._pool = pool + self._times_acquired = 0 + + @property + def times_acquired(self): + """ + Readonly property. + + :returns: int + """ + return self._times_acquired + + def increment_acquired(self): + """Increment times acquired attribute by 1""" + self._times_acquired += 1 + + def decrement_acquired(self): + """Decrement times acquired attribute by 1""" + self._times_acquired -= 1 + + async def write(self, message): + """ + **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 + """ + return await self._conn.write(message) + + submit = write + + async def release_task(self, resp): + await resp.done.wait() + self.release() + + def release(self): + self._pool.release(self) + + async def close(self): + """Close underlying connection""" + await self._conn.close() + self._conn = None + self._pool = None + + @property + def closed(self): + """ + Readonly property. + + :returns: bool + """ + return self._conn.closed + + +class ConnectionPool: + """ + A pool of connections to a Gremlin Server host. + + :param str url: url for host Gremlin Server + :param asyncio.BaseEventLoop loop: + :param ssl.SSLContext ssl_context: + :param str username: Username for database auth + :param str password: Password for database auth + :param float response_timeout: (optional) `None` by default + :param int max_conns: Maximum number of conns to a host + :param int min_connsd: Minimum number of conns to a host + :param int max_times_acquired: Maximum number of times a conn can be + shared by multiple coroutines (clients) + :param int max_inflight: Maximum number of unprocessed requests at any + one time on the connection + """ + + def __init__(self, url, loop, ssl_context, username, password, max_conns, + min_conns, max_times_acquired, max_inflight, response_timeout, + message_serializer, provider): + self._url = url + self._loop = loop + self._ssl_context = ssl_context + self._username = username + self._password = password + self._max_conns = max_conns + self._min_conns = min_conns + self._max_times_acquired = max_times_acquired + self._max_inflight = max_inflight + self._response_timeout = response_timeout + self._message_serializer = message_serializer + self._condition = asyncio.Condition(loop=self._loop) + self._available = collections.deque() + self._acquired = collections.deque() + self._provider = provider + + @property + def url(self): + """ + Readonly property. + + :returns: str + """ + return self._url + + async def init_pool(self): + """**coroutine** Open minumum number of connections to host""" + for i in range(self._min_conns): + conn = await self._get_connection(self._username, + self._password, + self._max_inflight, + self._response_timeout, + self._message_serializer, + self._provider) + self._available.append(conn) + + def release(self, conn): + """ + Release connection back to pool after use. + + :param PooledConnection conn: + """ + if conn.closed: + self._acquired.remove(conn) + else: + conn.decrement_acquired() + if not conn.times_acquired: + self._acquired.remove(conn) + self._available.append(conn) + self._loop.create_task(self._notify()) + + async def _notify(self): + async with self._condition: + self._condition.notify() + + async def acquire(self): + """**coroutine** Acquire a new connection from the pool.""" + async with self._condition: + while True: + while self._available: + conn = self._available.popleft() + if not conn.closed: + conn.increment_acquired() + self._acquired.append(conn) + return conn + if len(self._acquired) < self._max_conns: + conn = await self._get_connection(self._username, self._password, + self._max_inflight, + self._response_timeout, + self._message_serializer, + self._provider) + conn.increment_acquired() + self._acquired.append(conn) + return conn + else: + for x in range(len(self._acquired)): + conn = self._acquired.popleft() + if conn.times_acquired < self._max_times_acquired: + conn.increment_acquired() + self._acquired.append(conn) + return conn + self._acquired.append(conn) + else: + await self._condition.wait() + + async def close(self): + """**coroutine** Close connection pool.""" + waiters = [] + while self._available: + conn = self._available.popleft() + waiters.append(conn.close()) + while self._acquired: + conn = self._acquired.popleft() + waiters.append(conn.close()) + await asyncio.gather(*waiters, loop=self._loop) + + async def _get_connection(self, username, password, max_inflight, + response_timeout, message_serializer, provider): + conn = await connection.Connection.open( + self._url, self._loop, ssl_context=self._ssl_context, + username=username, password=password, + response_timeout=response_timeout, + message_serializer=message_serializer, provider=provider) + conn = PooledConnection(conn, self) + return conn diff --git a/aiogremlin/driver/protocol.py b/aiogremlin/driver/protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..03d189c127f9a8cc068cab98f98c9e0d61513e41 --- /dev/null +++ b/aiogremlin/driver/protocol.py @@ -0,0 +1,87 @@ +import base64 +import collections +import logging + +import aiohttp + +try: + import ujson as json +except ImportError: + import json + +from aiogremlin.gremlin_python.driver import protocol, request, serializer + + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +logger = logging.getLogger(__name__) + + +Message = collections.namedtuple( + "Message", + ["status_code", "data", "message"]) + + +class GremlinServerWSProtocol(protocol.AbstractBaseProtocol): + + def __init__(self, message_serializer, username='', password=''): + if isinstance(message_serializer, type): + message_serializer = message_serializer() + self._message_serializer = message_serializer + self._username = username + self._password = password + + def connection_made(self, transport): + self._transport = transport + + def write(self, request_id, request_message): + message = self._message_serializer.serialize_message( + request_id, request_message) + self._transport.write(message) + + async def data_received(self, data, results_dict): + if data.tp == aiohttp.MsgType.close: + await self._transport.close() + elif data.tp == aiohttp.MsgType.error: + # This won't raise properly, fix + raise data.data + elif data.tp == aiohttp.MsgType.closed: + # Hmm + pass + else: + if data.tp == aiohttp.MsgType.binary: + data = data.data.decode() + elif data.tp == aiohttp.MsgType.text: + data = data.data.strip() + message = json.loads(data) + request_id = message['requestId'] + status_code = message['status']['code'] + data = message['result']['data'] + msg = message['status']['message'] + if request_id in results_dict: + result_set = results_dict[request_id] + aggregate_to = message['result']['meta'].get('aggregateTo', + 'list') + result_set.aggregate_to = aggregate_to + if status_code == 407: + auth = b''.join([b'\x00', self._username.encode('utf-8'), + b'\x00', self._password.encode('utf-8')]) + request_message = request.RequestMessage( + 'traversal', 'authentication', + {'sasl': base64.b64encode(auth).decode()}) + self.write(request_id, request_message) + elif status_code == 204: + result_set.queue_result(None) + else: + if data: + for result in data: + result = self._message_serializer.deserialize_message(result) + message = Message(status_code, result, msg) + result_set.queue_result(message) + else: + data = self._message_serializer.deserialize_message(data) + message = Message(status_code, data, msg) + result_set.queue_result(message) + if status_code != 206: + result_set.queue_result(None) diff --git a/aiogremlin/driver/provider.py b/aiogremlin/driver/provider.py new file mode 100644 index 0000000000000000000000000000000000000000..5325612d1518338332caf9c1462db142a9e3aca4 --- /dev/null +++ b/aiogremlin/driver/provider.py @@ -0,0 +1,14 @@ +class Provider: + """Superclass for provider plugins""" + DEFAULT_OP_ARGS = {} + + @classmethod + def get_default_op_args(cls, processor): + return cls.DEFAULT_OP_ARGS.get(processor, dict()) + + +class TinkerGraph(Provider): # TODO + """Default provider""" + @staticmethod + def get_hashable_id(val): + return val diff --git a/aiogremlin/driver/resultset.py b/aiogremlin/driver/resultset.py new file mode 100644 index 0000000000000000000000000000000000000000..a53765d28f3682e6e9b3d97bf84a5925357e28b5 --- /dev/null +++ b/aiogremlin/driver/resultset.py @@ -0,0 +1,98 @@ +import asyncio +import functools + +from aiogremlin import exception + + +def error_handler(fn): + @functools.wraps(fn) + async def wrapper(self): + msg = await fn(self) + if msg: + if msg.status_code not in [200, 206]: + self.close() + raise exception.GremlinServerError( + "{0}: {1}".format(msg.status_code, msg.message)) + msg = msg.data + return msg + return wrapper + + +class ResultSet: + """Gremlin Server response implementated as an async iterator.""" + def __init__(self, request_id, timeout, loop): + self._response_queue = asyncio.Queue(loop=loop) + self._request_id = request_id + self._loop = loop + self._timeout = timeout + self._done = asyncio.Event(loop=self._loop) + self._aggregate_to = None + + @property + def request_id(self): + return self._request_id + + @property + def stream(self): + return self._response_queue + + def queue_result(self, result): + if result is None: + self.done.set() + self._response_queue.put_nowait(result) + + @property + def done(self): + """ + Readonly property. + + :returns: `asyncio.Event` object + """ + return self._done + + @property + def aggregate_to(self): + return self._aggregate_to + + @aggregate_to.setter + def aggregate_to(self, val): + self._aggregate_to = val + + async def __aiter__(self): + return self + + async def __anext__(self): + msg = await self.one() + if not msg: + raise StopAsyncIteration + return msg + + def close(self): + """Close response stream by setting done flag to true.""" + self.done.set() + self._loop = None + self._response_queue = None + + @error_handler + async def one(self): + """Get a single message from the response stream""" + if not self._response_queue.empty(): + msg = self._response_queue.get_nowait() + elif self.done.is_set(): + self.close() + msg = None + else: + try: + msg = await asyncio.wait_for(self._response_queue.get(), + timeout=self._timeout, + loop=self._loop) + except asyncio.TimeoutError: + self.close() + raise exception.ResponseTimeoutError('Response timed out') + return msg + + async def all(self): + results = [] + async for result in self: + results.append(result) + return results diff --git a/aiogremlin/driver/server.py b/aiogremlin/driver/server.py new file mode 100644 index 0000000000000000000000000000000000000000..7e392fe73948f740211aa4fe88154299aa3c0ad0 --- /dev/null +++ b/aiogremlin/driver/server.py @@ -0,0 +1,97 @@ +from aiogremlin.driver import pool + + +class GremlinServer: + """ + Class that wraps a connection pool. Currently doesn't do much, but may + be useful in the future.... + + :param pool.ConnectionPool pool: + """ + + def __init__(self, url, loop, **config): + self._pool = None + self._url = url + self._loop = loop + self._response_timeout = config['response_timeout'] + self._username = config['username'] + self._password = config['password'] + self._max_times_acquired = config['max_times_acquired'] + self._max_conns = config['max_conns'] + self._min_conns = config['min_conns'] + self._max_inflight = config['max_inflight'] + self._message_serializer = config['message_serializer'] + self._provider = config['provider'] + scheme = config['scheme'] + if scheme in ['https', 'wss']: + certfile = config['ssl_certfile'] + keyfile = config['ssl_keyfile'] + ssl_password = config['ssl_password'] + ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ssl_context.load_cert_chain( + certfile, keyfile=keyfile, password=ssl_password) + self._ssl_context = ssl_context + else: + self._ssl_context = None + + @property + def url(self): + return self._url + + @property + def pool(self): + """ + Readonly property. + + :returns: :py:class:`ConnectionPool<goblin.driver.pool.ConnectionPool>` + """ + if self._pool: + return self._pool + + async def close(self): + """**coroutine** Close underlying connection pool.""" + if self._pool: + await self._pool.close() + self._pool = None + + async def get_connection(self): + """**coroutine** Acquire a connection from the pool.""" + try: + conn = await self._pool.acquire() + except AttributeError: + raise Exception("Please initialize pool") + return conn + + async def initialize(self): + conn_pool = pool.ConnectionPool( + self._url, self._loop, self._ssl_context, self._username, + self._password, self._max_conns, self._min_conns, + self._max_times_acquired, self._max_inflight, + self._response_timeout, self._message_serializer, self._provider) + await conn_pool.init_pool() + self._pool = conn_pool + + @classmethod + async def open(cls, url, loop, **config): + """ + **coroutine** Establish connection pool and host to Gremlin Server. + + :param str url: url for host Gremlin Server + :param asyncio.BaseEventLoop loop: + :param ssl.SSLContext ssl_context: + :param str username: Username for database auth + :param str password: Password for database auth + :param float response_timeout: (optional) `None` by default + :param int max_conns: Maximum number of conns to a host + :param int min_connsd: Minimum number of conns to a host + :param int max_times_acquired: Maximum number of times a conn can be + shared by multiple coroutines (clients) + :param int max_inflight: Maximum number of unprocessed requests at any + one time on the connection + + :returns: :py:class:`GremlinServer` + """ + + host = cls(url, loop, **config) + await host.initialize() + return host diff --git a/aiogremlin/exception.py b/aiogremlin/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..51f0ea5967de894e2e8fd6e64c936d37cd6bf17b --- /dev/null +++ b/aiogremlin/exception.py @@ -0,0 +1,30 @@ +class ConfigError(Exception): + pass + + +class ClientError(Exception): + pass + + +class MappingError(Exception): + pass + + +class ValidationError(Exception): + pass + + +class ElementError(Exception): + pass + + +class ConfigurationError(Exception): + pass + + +class GremlinServerError(Exception): + pass + + +class ResponseTimeoutError(Exception): + pass diff --git a/aiogremlin/exceptions.py b/aiogremlin/exceptions.py deleted file mode 100644 index 890932a5cf11f87340c4ca99f6f506b941be99a1..0000000000000000000000000000000000000000 --- a/aiogremlin/exceptions.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Gremlin Server exceptions.""" - -__all__ = ("RequestError", "GremlinServerError") - - -class StatusException(IOError): - - def __init__(self, value, result): - """Handle all exceptions returned from the Gremlin Server. - """ - self.value = value - self.response = { - 401: ("UNAUTHORIZED", ("The request attempted to access resources " + - "that the requesting user did not have access to")), - 498: ("MALFORMED_REQUEST", - ("The request message was not properly formatted which " + - "means it could not be parsed at all or the 'op' code " + - "was not recognized such that Gremlin Server could " + - "properly route it for processing. Check the message " + - "format and retry the request")), - 499: ("INVALID_REQUEST_ARGUMENTS", - ("The request message was parseable, but the arguments " + - "supplied in the message were in conflict or incomplete. " + - "Check the message format and retry the request.")), - 500: ("SERVER_ERROR", - ("A general server error occurred that prevented the " + - "request from being processed.")), - 596: ("TRAVERSAL_EVALUATION", - ("The remote " + - "{@link org.apache.tinkerpop.gremlin.process.Traversal} " + - "submitted for processing evaluated in on the server " + - "with errors and could not be processed")), - 597: ("SCRIPT_EVALUATION", - ("The script submitted for processing evaluated in the " + - "{@code ScriptEngine} with errors and could not be " + - "processed.Check the script submitted for syntax errors " + - "or other problems and then resubmit.")), - 598: ("TIMEOUT", - ("The server exceeded one of the timeout settings for the " + - "request and could therefore only partially respond or " + - " not respond at all.")), - 599: ("SERIALIZATION", - ("The server was not capable of serializing an object " + - "that was returned from the script supplied on the " + - "requst. Either transform the object into something " + - "Gremlin Server can process within the script or install " + - "mapper serialization classes to Gremlin Server.")) - } - if result: - result = "\n\n{}".format(result) - self.message = 'Code [{}]: {}. {}.{}'.format( - self.value, - self.response[self.value][0], - self.response[self.value][1], - result) - super().__init__(self.message) - - -class RequestError(StatusException): - pass - - -class GremlinServerError(StatusException): - pass diff --git a/aiogremlin/gremlin_python/__init__.py b/aiogremlin/gremlin_python/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7626550738f12ccd5e18b8c06a8d68aaab882066 --- /dev/null +++ b/aiogremlin/gremlin_python/__init__.py @@ -0,0 +1,20 @@ +''' +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)' diff --git a/aiogremlin/gremlin_python/driver/__init__.py b/aiogremlin/gremlin_python/driver/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7626550738f12ccd5e18b8c06a8d68aaab882066 --- /dev/null +++ b/aiogremlin/gremlin_python/driver/__init__.py @@ -0,0 +1,20 @@ +''' +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)' diff --git a/aiogremlin/gremlin_python/driver/protocol.py b/aiogremlin/gremlin_python/driver/protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..af88ba8eda170a5e3982f7add2ebe392af89d4ec --- /dev/null +++ b/aiogremlin/gremlin_python/driver/protocol.py @@ -0,0 +1,45 @@ +""" +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 abc +import base64 +import collections +import uuid + +try: + import ujson as json +except ImportError: + import json + + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +class AbstractBaseProtocol(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def connection_made(self, transport): + self._transport = transport + + @abc.abstractmethod + def data_received(self, message): + pass + + @abc.abstractmethod + def write(self, request_id, request_message): + pass diff --git a/aiogremlin/gremlin_python/driver/remote_connection.py b/aiogremlin/gremlin_python/driver/remote_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..4101a8c7b1340e69c743c2d05e56b57c11f1d44d --- /dev/null +++ b/aiogremlin/gremlin_python/driver/remote_connection.py @@ -0,0 +1,75 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import abc +import collections + +from aiogremlin.gremlin_python.driver import request +from aiogremlin.gremlin_python.process import traversal + +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + + +class RemoteConnection(metaclass=abc.ABCMeta): + 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): + pass + + def __repr__(self): + return "remoteconnection[" + self._url + "," + self._traversal_source + "]" + + +class RemoteTraversal(traversal.Traversal): + def __init__(self, traversers, side_effects): + super(RemoteTraversal, self).__init__(None, None, None) + self.traversers = traversers + self._side_effects = side_effects + + @property + def side_effects(self): + return self._side_effects + + @side_effects.setter + def side_effects(self, val): + self._side_effects = val + + +class RemoteStrategy(traversal.TraversalStrategy): + def __init__(self, remote_connection): + self.remote_connection = remote_connection + + async def apply(self, traversal): + if traversal.traversers is None: + remote_traversal = await self.remote_connection.submit( + traversal.bytecode) + traversal.remote_results = remote_traversal + traversal.side_effects = remote_traversal.side_effects + traversal.traversers = remote_traversal.traversers diff --git a/aiogremlin/gremlin_python/driver/request.py b/aiogremlin/gremlin_python/driver/request.py new file mode 100644 index 0000000000000000000000000000000000000000..ac7b845c659013ac13ace9c6dca1f75076b86ee8 --- /dev/null +++ b/aiogremlin/gremlin_python/driver/request.py @@ -0,0 +1,25 @@ +""" +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 collections + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +RequestMessage = collections.namedtuple( + 'RequestMessage', ['processor', 'op', 'args']) diff --git a/aiogremlin/gremlin_python/driver/serializer.py b/aiogremlin/gremlin_python/driver/serializer.py new file mode 100644 index 0000000000000000000000000000000000000000..f6c1e86ace1db586bc55f1525bf9d73cd5e23529 --- /dev/null +++ b/aiogremlin/gremlin_python/driver/serializer.py @@ -0,0 +1,130 @@ +""" +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. +""" +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +try: + import ujson as json +except ImportError: + import json + +from aiogremlin.gremlin_python.structure.io import graphson + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +class Processor: + """Base class for OpProcessor serialization system.""" + + def __init__(self, writer): + self._graphson_writer = writer + + def get_op_args(self, op, args): + op_method = getattr(self, op, None) + if not op_method: + raise Exception("Processor does not support op: {}".format(op)) + return op_method(args) + + +class Standard(Processor): + + def authentication(self, args): + return args + + def eval(self, args): + return args + + +class Traversal(Processor): + + def authentication(self, args): + return args + + def bytecode(self, args): + gremlin = args['gremlin'] + args['gremlin'] = self._graphson_writer.toDict(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 + + +class GraphSONMessageSerializer: + """Message serializer for GraphSON""" + + def __init__(self, reader=None, writer=None): + if not reader: + reader = graphson.GraphSONReader() + self._graphson_reader = reader + if not writer: + writer = graphson.GraphSONWriter() + self.standard = Standard(writer) + self.traversal = Traversal(writer) + + 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, request_message): + processor = request_message.processor + op = request_message.op + args = request_message.args + if not processor: + processor_obj = self.get_processor('standard') + else: + processor_obj = self.get_processor(processor) + args = processor_obj.get_op_args(op, args) + message = self.build_message(request_id, processor, op, args) + return message + + 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 finalize_message(self, message, mime_len, mime_type): + message = json.dumps(message) + message = b''.join([mime_len, mime_type, message.encode('utf-8')]) + return message + + def deserialize_message(self, message): + return self._graphson_reader.toObject(message) diff --git a/aiogremlin/gremlin_python/driver/transport.py b/aiogremlin/gremlin_python/driver/transport.py new file mode 100644 index 0000000000000000000000000000000000000000..000028383fc4e34880ce4a46625b7fd57a7aa2db --- /dev/null +++ b/aiogremlin/gremlin_python/driver/transport.py @@ -0,0 +1,45 @@ +""" +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 abc + + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +class AbstractBaseTransport(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def connect(self, url, *, ssl_context=None): + pass + + @abc.abstractmethod + def write(self, message): + pass + + @abc.abstractmethod + def read(self): + pass + + @abc.abstractmethod + def close(self): + pass + + @abc.abstractproperty + def closed(self): + pass diff --git a/aiogremlin/gremlin_python/process/__init__.py b/aiogremlin/gremlin_python/process/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7626550738f12ccd5e18b8c06a8d68aaab882066 --- /dev/null +++ b/aiogremlin/gremlin_python/process/__init__.py @@ -0,0 +1,20 @@ +''' +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)' diff --git a/aiogremlin/gremlin_python/process/graph_traversal.py b/aiogremlin/gremlin_python/process/graph_traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..0528178775b5c616d33e063a111dc05895c67767 --- /dev/null +++ b/aiogremlin/gremlin_python/process/graph_traversal.py @@ -0,0 +1,1129 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import sys +from aiogremlin.gremlin_python.process.traversal import Traversal +from aiogremlin.gremlin_python.process.traversal import TraversalStrategies +from aiogremlin.gremlin_python.process.strategies import VertexProgramStrategy +from .traversal import Bytecode +from aiogremlin.gremlin_python.driver.remote_connection import RemoteStrategy +from aiogremlin.gremlin_python import statics +from aiogremlin.gremlin_python.statics import long + +class GraphTraversalSource(object): + def __init__(self, graph, traversal_strategies, bytecode=None): + self.graph = graph + self.traversal_strategies = traversal_strategies + if bytecode is None: + bytecode = Bytecode() + self.bytecode = bytecode + def __repr__(self): + return "graphtraversalsource[" + str(self.graph) + "]" + def withBulk(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withBulk", *args) + return source + def withPath(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withPath", *args) + return source + def withSack(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withSack", *args) + return source + def withSideEffect(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withSideEffect", *args) + return source + def withStrategies(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withStrategies", *args) + return source + def withoutStrategies(self, *args): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.bytecode.add_source("withoutStrategies", *args) + return source + def withRemote(self, remote_connection): + source = GraphTraversalSource(self.graph, TraversalStrategies(self.traversal_strategies), Bytecode(self.bytecode)) + source.traversal_strategies.add_strategies([RemoteStrategy(remote_connection)]) + return source + def withComputer(self,graph_computer=None, workers=None, result=None, persist=None, vertices=None, edges=None, configuration=None): + return self.withStrategies(VertexProgramStrategy(graph_computer,workers,result,persist,vertices,edges,configuration)) + def E(self, *args): + traversal = GraphTraversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("E", *args) + return traversal + def V(self, *args): + traversal = GraphTraversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("V", *args) + return traversal + def addV(self, *args): + traversal = GraphTraversal(self.graph, self.traversal_strategies, Bytecode(self.bytecode)) + traversal.bytecode.add_step("addV", *args) + return traversal + def inject(self, *args): + traversal = GraphTraversal(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(long(index), long(index + 1)) + elif isinstance(index, slice): + return self.range(long(0) if index.start is None else long(index.start), long(sys.maxsize) if index.stop is None else long(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) + return self + def addE(self, *args): + self.bytecode.add_step("addE", *args) + return self + def addInE(self, *args): + self.bytecode.add_step("addInE", *args) + return self + def addOutE(self, *args): + self.bytecode.add_step("addOutE", *args) + return self + def addV(self, *args): + self.bytecode.add_step("addV", *args) + return self + def aggregate(self, *args): + self.bytecode.add_step("aggregate", *args) + 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) + return self + def both(self, *args): + self.bytecode.add_step("both", *args) + return self + def bothE(self, *args): + self.bytecode.add_step("bothE", *args) + return self + def bothV(self, *args): + self.bytecode.add_step("bothV", *args) + return self + def branch(self, *args): + self.bytecode.add_step("branch", *args) + return self + def by(self, *args): + self.bytecode.add_step("by", *args) + return self + def cap(self, *args): + self.bytecode.add_step("cap", *args) + return self + def choose(self, *args): + self.bytecode.add_step("choose", *args) + return self + def coalesce(self, *args): + self.bytecode.add_step("coalesce", *args) + return self + def coin(self, *args): + self.bytecode.add_step("coin", *args) + return self + def constant(self, *args): + self.bytecode.add_step("constant", *args) + return self + def count(self, *args): + self.bytecode.add_step("count", *args) + return self + def cyclicPath(self, *args): + self.bytecode.add_step("cyclicPath", *args) + return self + def dedup(self, *args): + self.bytecode.add_step("dedup", *args) + return self + def drop(self, *args): + self.bytecode.add_step("drop", *args) + return self + def emit(self, *args): + self.bytecode.add_step("emit", *args) + return self + def filter(self, *args): + self.bytecode.add_step("filter", *args) + return self + def flatMap(self, *args): + self.bytecode.add_step("flatMap", *args) + return self + def fold(self, *args): + self.bytecode.add_step("fold", *args) + return self + def from_(self, *args): + self.bytecode.add_step("from", *args) + return self + def group(self, *args): + self.bytecode.add_step("group", *args) + return self + def groupCount(self, *args): + self.bytecode.add_step("groupCount", *args) + return self + def groupV3d0(self, *args): + self.bytecode.add_step("groupV3d0", *args) + return self + def has(self, *args): + self.bytecode.add_step("has", *args) + return self + def hasId(self, *args): + self.bytecode.add_step("hasId", *args) + return self + def hasKey(self, *args): + self.bytecode.add_step("hasKey", *args) + return self + def hasLabel(self, *args): + self.bytecode.add_step("hasLabel", *args) + return self + def hasNot(self, *args): + self.bytecode.add_step("hasNot", *args) + return self + def hasValue(self, *args): + self.bytecode.add_step("hasValue", *args) + return self + def id(self, *args): + self.bytecode.add_step("id", *args) + return self + def identity(self, *args): + self.bytecode.add_step("identity", *args) + return self + def inE(self, *args): + self.bytecode.add_step("inE", *args) + return self + def inV(self, *args): + self.bytecode.add_step("inV", *args) + return self + def in_(self, *args): + self.bytecode.add_step("in", *args) + return self + def inject(self, *args): + self.bytecode.add_step("inject", *args) + return self + def is_(self, *args): + self.bytecode.add_step("is", *args) + return self + def key(self, *args): + self.bytecode.add_step("key", *args) + return self + def label(self, *args): + self.bytecode.add_step("label", *args) + return self + def limit(self, *args): + self.bytecode.add_step("limit", *args) + return self + def local(self, *args): + self.bytecode.add_step("local", *args) + return self + def loops(self, *args): + self.bytecode.add_step("loops", *args) + return self + def map(self, *args): + self.bytecode.add_step("map", *args) + return self + def mapKeys(self, *args): + self.bytecode.add_step("mapKeys", *args) + return self + def mapValues(self, *args): + self.bytecode.add_step("mapValues", *args) + return self + def match(self, *args): + self.bytecode.add_step("match", *args) + return self + def max(self, *args): + self.bytecode.add_step("max", *args) + return self + def mean(self, *args): + self.bytecode.add_step("mean", *args) + return self + def min(self, *args): + self.bytecode.add_step("min", *args) + return self + def not_(self, *args): + self.bytecode.add_step("not", *args) + return self + def option(self, *args): + self.bytecode.add_step("option", *args) + return self + def optional(self, *args): + self.bytecode.add_step("optional", *args) + return self + def or_(self, *args): + self.bytecode.add_step("or", *args) + return self + def order(self, *args): + self.bytecode.add_step("order", *args) + return self + def otherV(self, *args): + self.bytecode.add_step("otherV", *args) + return self + def out(self, *args): + self.bytecode.add_step("out", *args) + return self + def outE(self, *args): + self.bytecode.add_step("outE", *args) + return self + def outV(self, *args): + self.bytecode.add_step("outV", *args) + return self + def pageRank(self, *args): + self.bytecode.add_step("pageRank", *args) + return self + def path(self, *args): + self.bytecode.add_step("path", *args) + return self + def peerPressure(self, *args): + self.bytecode.add_step("peerPressure", *args) + return self + def profile(self, *args): + self.bytecode.add_step("profile", *args) + return self + def program(self, *args): + self.bytecode.add_step("program", *args) + return self + def project(self, *args): + self.bytecode.add_step("project", *args) + return self + def properties(self, *args): + self.bytecode.add_step("properties", *args) + return self + def property(self, *args): + self.bytecode.add_step("property", *args) + return self + def propertyMap(self, *args): + self.bytecode.add_step("propertyMap", *args) + return self + def range(self, *args): + self.bytecode.add_step("range", *args) + return self + def repeat(self, *args): + self.bytecode.add_step("repeat", *args) + return self + def sack(self, *args): + self.bytecode.add_step("sack", *args) + return self + def sample(self, *args): + self.bytecode.add_step("sample", *args) + return self + def select(self, *args): + self.bytecode.add_step("select", *args) + return self + def sideEffect(self, *args): + self.bytecode.add_step("sideEffect", *args) + return self + def simplePath(self, *args): + self.bytecode.add_step("simplePath", *args) + return self + def store(self, *args): + self.bytecode.add_step("store", *args) + return self + def subgraph(self, *args): + self.bytecode.add_step("subgraph", *args) + return self + def sum(self, *args): + self.bytecode.add_step("sum", *args) + return self + def tail(self, *args): + self.bytecode.add_step("tail", *args) + return self + def timeLimit(self, *args): + self.bytecode.add_step("timeLimit", *args) + return self + def times(self, *args): + self.bytecode.add_step("times", *args) + return self + def to(self, *args): + self.bytecode.add_step("to", *args) + return self + def toE(self, *args): + self.bytecode.add_step("toE", *args) + return self + def toV(self, *args): + self.bytecode.add_step("toV", *args) + return self + def tree(self, *args): + self.bytecode.add_step("tree", *args) + return self + def unfold(self, *args): + self.bytecode.add_step("unfold", *args) + return self + def union(self, *args): + self.bytecode.add_step("union", *args) + return self + def until(self, *args): + self.bytecode.add_step("until", *args) + return self + def value(self, *args): + self.bytecode.add_step("value", *args) + return self + def valueMap(self, *args): + self.bytecode.add_step("valueMap", *args) + return self + def values(self, *args): + self.bytecode.add_step("values", *args) + return self + def where(self, *args): + self.bytecode.add_step("where", *args) + return self + + +class __(object): + @staticmethod + def start(): + return GraphTraversal(None, None, Bytecode()) + @staticmethod + def __(*args): + return __.inject(*args) + @staticmethod + def V(*args): + return GraphTraversal(None, None, Bytecode()).V(*args) + @staticmethod + def addE(*args): + return GraphTraversal(None, None, Bytecode()).addE(*args) + @staticmethod + def addInE(*args): + return GraphTraversal(None, None, Bytecode()).addInE(*args) + @staticmethod + def addOutE(*args): + return GraphTraversal(None, None, Bytecode()).addOutE(*args) + @staticmethod + def addV(*args): + return GraphTraversal(None, None, Bytecode()).addV(*args) + @staticmethod + 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 + def both(*args): + return GraphTraversal(None, None, Bytecode()).both(*args) + @staticmethod + def bothE(*args): + return GraphTraversal(None, None, Bytecode()).bothE(*args) + @staticmethod + def bothV(*args): + return GraphTraversal(None, None, Bytecode()).bothV(*args) + @staticmethod + def branch(*args): + return GraphTraversal(None, None, Bytecode()).branch(*args) + @staticmethod + def cap(*args): + return GraphTraversal(None, None, Bytecode()).cap(*args) + @staticmethod + def choose(*args): + return GraphTraversal(None, None, Bytecode()).choose(*args) + @staticmethod + def coalesce(*args): + return GraphTraversal(None, None, Bytecode()).coalesce(*args) + @staticmethod + def coin(*args): + return GraphTraversal(None, None, Bytecode()).coin(*args) + @staticmethod + def constant(*args): + return GraphTraversal(None, None, Bytecode()).constant(*args) + @staticmethod + def count(*args): + return GraphTraversal(None, None, Bytecode()).count(*args) + @staticmethod + def cyclicPath(*args): + return GraphTraversal(None, None, Bytecode()).cyclicPath(*args) + @staticmethod + def dedup(*args): + return GraphTraversal(None, None, Bytecode()).dedup(*args) + @staticmethod + def drop(*args): + return GraphTraversal(None, None, Bytecode()).drop(*args) + @staticmethod + def emit(*args): + return GraphTraversal(None, None, Bytecode()).emit(*args) + @staticmethod + def filter(*args): + return GraphTraversal(None, None, Bytecode()).filter(*args) + @staticmethod + def flatMap(*args): + return GraphTraversal(None, None, Bytecode()).flatMap(*args) + @staticmethod + def fold(*args): + return GraphTraversal(None, None, Bytecode()).fold(*args) + @staticmethod + def group(*args): + return GraphTraversal(None, None, Bytecode()).group(*args) + @staticmethod + def groupCount(*args): + return GraphTraversal(None, None, Bytecode()).groupCount(*args) + @staticmethod + def groupV3d0(*args): + return GraphTraversal(None, None, Bytecode()).groupV3d0(*args) + @staticmethod + def has(*args): + return GraphTraversal(None, None, Bytecode()).has(*args) + @staticmethod + def hasId(*args): + return GraphTraversal(None, None, Bytecode()).hasId(*args) + @staticmethod + def hasKey(*args): + return GraphTraversal(None, None, Bytecode()).hasKey(*args) + @staticmethod + def hasLabel(*args): + return GraphTraversal(None, None, Bytecode()).hasLabel(*args) + @staticmethod + def hasNot(*args): + return GraphTraversal(None, None, Bytecode()).hasNot(*args) + @staticmethod + def hasValue(*args): + return GraphTraversal(None, None, Bytecode()).hasValue(*args) + @staticmethod + def id(*args): + return GraphTraversal(None, None, Bytecode()).id(*args) + @staticmethod + def identity(*args): + return GraphTraversal(None, None, Bytecode()).identity(*args) + @staticmethod + def inE(*args): + return GraphTraversal(None, None, Bytecode()).inE(*args) + @staticmethod + 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 + def label(*args): + return GraphTraversal(None, None, Bytecode()).label(*args) + @staticmethod + def limit(*args): + return GraphTraversal(None, None, Bytecode()).limit(*args) + @staticmethod + def local(*args): + return GraphTraversal(None, None, Bytecode()).local(*args) + @staticmethod + def loops(*args): + return GraphTraversal(None, None, Bytecode()).loops(*args) + @staticmethod + def map(*args): + return GraphTraversal(None, None, Bytecode()).map(*args) + @staticmethod + def mapKeys(*args): + return GraphTraversal(None, None, Bytecode()).mapKeys(*args) + @staticmethod + def mapValues(*args): + return GraphTraversal(None, None, Bytecode()).mapValues(*args) + @staticmethod + def match(*args): + return GraphTraversal(None, None, Bytecode()).match(*args) + @staticmethod + def max(*args): + return GraphTraversal(None, None, Bytecode()).max(*args) + @staticmethod + def mean(*args): + return GraphTraversal(None, None, Bytecode()).mean(*args) + @staticmethod + 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 + def otherV(*args): + return GraphTraversal(None, None, Bytecode()).otherV(*args) + @staticmethod + def out(*args): + return GraphTraversal(None, None, Bytecode()).out(*args) + @staticmethod + def outE(*args): + return GraphTraversal(None, None, Bytecode()).outE(*args) + @staticmethod + def outV(*args): + return GraphTraversal(None, None, Bytecode()).outV(*args) + @staticmethod + def path(*args): + return GraphTraversal(None, None, Bytecode()).path(*args) + @staticmethod + def project(*args): + return GraphTraversal(None, None, Bytecode()).project(*args) + @staticmethod + def properties(*args): + return GraphTraversal(None, None, Bytecode()).properties(*args) + @staticmethod + def property(*args): + return GraphTraversal(None, None, Bytecode()).property(*args) + @staticmethod + def propertyMap(*args): + return GraphTraversal(None, None, Bytecode()).propertyMap(*args) + @staticmethod + def range(*args): + return GraphTraversal(None, None, Bytecode()).range(*args) + @staticmethod + def repeat(*args): + return GraphTraversal(None, None, Bytecode()).repeat(*args) + @staticmethod + def sack(*args): + return GraphTraversal(None, None, Bytecode()).sack(*args) + @staticmethod + def sample(*args): + return GraphTraversal(None, None, Bytecode()).sample(*args) + @staticmethod + def select(*args): + return GraphTraversal(None, None, Bytecode()).select(*args) + @staticmethod + def sideEffect(*args): + return GraphTraversal(None, None, Bytecode()).sideEffect(*args) + @staticmethod + def simplePath(*args): + return GraphTraversal(None, None, Bytecode()).simplePath(*args) + @staticmethod + def store(*args): + return GraphTraversal(None, None, Bytecode()).store(*args) + @staticmethod + def subgraph(*args): + return GraphTraversal(None, None, Bytecode()).subgraph(*args) + @staticmethod + def sum(*args): + return GraphTraversal(None, None, Bytecode()).sum(*args) + @staticmethod + def tail(*args): + return GraphTraversal(None, None, Bytecode()).tail(*args) + @staticmethod + def timeLimit(*args): + return GraphTraversal(None, None, Bytecode()).timeLimit(*args) + @staticmethod + def times(*args): + return GraphTraversal(None, None, Bytecode()).times(*args) + @staticmethod + def to(*args): + return GraphTraversal(None, None, Bytecode()).to(*args) + @staticmethod + def toE(*args): + return GraphTraversal(None, None, Bytecode()).toE(*args) + @staticmethod + def toV(*args): + return GraphTraversal(None, None, Bytecode()).toV(*args) + @staticmethod + def tree(*args): + return GraphTraversal(None, None, Bytecode()).tree(*args) + @staticmethod + def unfold(*args): + return GraphTraversal(None, None, Bytecode()).unfold(*args) + @staticmethod + def union(*args): + return GraphTraversal(None, None, Bytecode()).union(*args) + @staticmethod + def until(*args): + return GraphTraversal(None, None, Bytecode()).until(*args) + @staticmethod + def value(*args): + return GraphTraversal(None, None, Bytecode()).value(*args) + @staticmethod + def valueMap(*args): + return GraphTraversal(None, None, Bytecode()).valueMap(*args) + @staticmethod + def values(*args): + return GraphTraversal(None, None, Bytecode()).values(*args) + @staticmethod + def where(*args): + return GraphTraversal(None, None, Bytecode()).where(*args) + + +def V(*args): + return __.V(*args) + +statics.add_static('V', V) + +def addE(*args): + return __.addE(*args) + +statics.add_static('addE', addE) + +def addInE(*args): + return __.addInE(*args) + +statics.add_static('addInE', addInE) + +def addOutE(*args): + return __.addOutE(*args) + +statics.add_static('addOutE', addOutE) + +def addV(*args): + return __.addV(*args) + +statics.add_static('addV', addV) + +def aggregate(*args): + return __.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) + +statics.add_static('barrier', barrier) + +def both(*args): + return __.both(*args) + +statics.add_static('both', both) + +def bothE(*args): + return __.bothE(*args) + +statics.add_static('bothE', bothE) + +def bothV(*args): + return __.bothV(*args) + +statics.add_static('bothV', bothV) + +def branch(*args): + return __.branch(*args) + +statics.add_static('branch', branch) + +def cap(*args): + return __.cap(*args) + +statics.add_static('cap', cap) + +def choose(*args): + return __.choose(*args) + +statics.add_static('choose', choose) + +def coalesce(*args): + return __.coalesce(*args) + +statics.add_static('coalesce', coalesce) + +def coin(*args): + return __.coin(*args) + +statics.add_static('coin', coin) + +def constant(*args): + return __.constant(*args) + +statics.add_static('constant', constant) + +def count(*args): + return __.count(*args) + +statics.add_static('count', count) + +def cyclicPath(*args): + return __.cyclicPath(*args) + +statics.add_static('cyclicPath', cyclicPath) + +def dedup(*args): + return __.dedup(*args) + +statics.add_static('dedup', dedup) + +def drop(*args): + return __.drop(*args) + +statics.add_static('drop', drop) + +def emit(*args): + return __.emit(*args) + +statics.add_static('emit', emit) + +def filter(*args): + return __.filter(*args) + +statics.add_static('filter', filter) + +def flatMap(*args): + return __.flatMap(*args) + +statics.add_static('flatMap', flatMap) + +def fold(*args): + return __.fold(*args) + +statics.add_static('fold', fold) + +def group(*args): + return __.group(*args) + +statics.add_static('group', group) + +def groupCount(*args): + return __.groupCount(*args) + +statics.add_static('groupCount', groupCount) + +def groupV3d0(*args): + return __.groupV3d0(*args) + +statics.add_static('groupV3d0', groupV3d0) + +def has(*args): + return __.has(*args) + +statics.add_static('has', has) + +def hasId(*args): + return __.hasId(*args) + +statics.add_static('hasId', hasId) + +def hasKey(*args): + return __.hasKey(*args) + +statics.add_static('hasKey', hasKey) + +def hasLabel(*args): + return __.hasLabel(*args) + +statics.add_static('hasLabel', hasLabel) + +def hasNot(*args): + return __.hasNot(*args) + +statics.add_static('hasNot', hasNot) + +def hasValue(*args): + return __.hasValue(*args) + +statics.add_static('hasValue', hasValue) + +def id(*args): + return __.id(*args) + +statics.add_static('id', id) + +def identity(*args): + return __.identity(*args) + +statics.add_static('identity', identity) + +def inE(*args): + return __.inE(*args) + +statics.add_static('inE', inE) + +def inV(*args): + return __.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) + +statics.add_static('key', key) + +def label(*args): + return __.label(*args) + +statics.add_static('label', label) + +def limit(*args): + return __.limit(*args) + +statics.add_static('limit', limit) + +def local(*args): + return __.local(*args) + +statics.add_static('local', local) + +def loops(*args): + return __.loops(*args) + +statics.add_static('loops', loops) + +def map(*args): + return __.map(*args) + +statics.add_static('map', map) + +def mapKeys(*args): + return __.mapKeys(*args) + +statics.add_static('mapKeys', mapKeys) + +def mapValues(*args): + return __.mapValues(*args) + +statics.add_static('mapValues', mapValues) + +def match(*args): + return __.match(*args) + +statics.add_static('match', match) + +def max(*args): + return __.max(*args) + +statics.add_static('max', max) + +def mean(*args): + return __.mean(*args) + +statics.add_static('mean', mean) + +def min(*args): + return __.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) + +statics.add_static('order', order) + +def otherV(*args): + return __.otherV(*args) + +statics.add_static('otherV', otherV) + +def out(*args): + return __.out(*args) + +statics.add_static('out', out) + +def outE(*args): + return __.outE(*args) + +statics.add_static('outE', outE) + +def outV(*args): + return __.outV(*args) + +statics.add_static('outV', outV) + +def path(*args): + return __.path(*args) + +statics.add_static('path', path) + +def project(*args): + return __.project(*args) + +statics.add_static('project', project) + +def properties(*args): + return __.properties(*args) + +statics.add_static('properties', properties) + +def property(*args): + return __.property(*args) + +statics.add_static('property', property) + +def propertyMap(*args): + return __.propertyMap(*args) + +statics.add_static('propertyMap', propertyMap) + +def range(*args): + return __.range(*args) + +statics.add_static('range', range) + +def repeat(*args): + return __.repeat(*args) + +statics.add_static('repeat', repeat) + +def sack(*args): + return __.sack(*args) + +statics.add_static('sack', sack) + +def sample(*args): + return __.sample(*args) + +statics.add_static('sample', sample) + +def select(*args): + return __.select(*args) + +statics.add_static('select', select) + +def sideEffect(*args): + return __.sideEffect(*args) + +statics.add_static('sideEffect', sideEffect) + +def simplePath(*args): + return __.simplePath(*args) + +statics.add_static('simplePath', simplePath) + +def store(*args): + return __.store(*args) + +statics.add_static('store', store) + +def subgraph(*args): + return __.subgraph(*args) + +statics.add_static('subgraph', subgraph) + +def sum(*args): + return __.sum(*args) + +statics.add_static('sum', sum) + +def tail(*args): + return __.tail(*args) + +statics.add_static('tail', tail) + +def timeLimit(*args): + return __.timeLimit(*args) + +statics.add_static('timeLimit', timeLimit) + +def times(*args): + return __.times(*args) + +statics.add_static('times', times) + +def to(*args): + return __.to(*args) + +statics.add_static('to', to) + +def toE(*args): + return __.toE(*args) + +statics.add_static('toE', toE) + +def toV(*args): + return __.toV(*args) + +statics.add_static('toV', toV) + +def tree(*args): + return __.tree(*args) + +statics.add_static('tree', tree) + +def unfold(*args): + return __.unfold(*args) + +statics.add_static('unfold', unfold) + +def union(*args): + return __.union(*args) + +statics.add_static('union', union) + +def until(*args): + return __.until(*args) + +statics.add_static('until', until) + +def value(*args): + return __.value(*args) + +statics.add_static('value', value) + +def valueMap(*args): + return __.valueMap(*args) + +statics.add_static('valueMap', valueMap) + +def values(*args): + return __.values(*args) + +statics.add_static('values', values) + +def where(*args): + return __.where(*args) + +statics.add_static('where', where) diff --git a/aiogremlin/gremlin_python/process/strategies.py b/aiogremlin/gremlin_python/process/strategies.py new file mode 100644 index 0000000000000000000000000000000000000000..1ac779d5156be617e2fd300e9c0edcc3f75f5eee --- /dev/null +++ b/aiogremlin/gremlin_python/process/strategies.py @@ -0,0 +1,184 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' + +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +from aiogremlin.gremlin_python.process.traversal import TraversalStrategy + + +######################### +# DECORATION STRATEGIES # +######################### + +class ConnectiveStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class ElementIdStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +# EventStrategy doesn't make sense outside JVM traversal machine + +class HaltedTraverserStrategy(TraversalStrategy): + def __init__(self, halted_traverser_factory=None): + TraversalStrategy.__init__(self) + if halted_traverser_factory is not None: + self.configuration["haltedTraverserFactory"] = halted_traverser_factory + + +class PartitionStrategy(TraversalStrategy): + def __init__(self, partition_key=None, write_partition=None, read_partitions=None, include_meta_properties=None): + TraversalStrategy.__init__(self) + if partition_key is not None: + self.configuration["partitionKey"] = partition_key + if write_partition is not None: + self.configuration["writePartition"] = write_partition + if write_partition is not None: + self.configuration["readPartitions"] = read_partitions + if include_meta_properties is not None: + self.configuration["includeMetaProperties"] = include_meta_properties + + +class SubgraphStrategy(TraversalStrategy): + def __init__(self, vertices=None, edges=None, vertex_properties=None): + TraversalStrategy.__init__(self) + if vertices is not None: + self.configuration["vertices"] = vertices + if edges is not None: + self.configuration["edges"] = edges + if vertex_properties is not None: + self.configuration["vertexProperties"] = vertex_properties + + +class VertexProgramStrategy(TraversalStrategy): + def __init__(self, graph_computer=None, workers=None, persist=None, result=None, vertices=None, edges=None, + configuration=None): + TraversalStrategy.__init__(self) + if graph_computer is not None: + self.configuration["graphComputer"] = graph_computer + if workers is not None: + self.configuration["workers"] = workers + if persist is not None: + self.configuration["persist"] = persist + if result is not None: + self.configuration["result"] = result + if vertices is not None: + self.configuration["vertices"] = vertices + if edges is not None: + self.configuration["edges"] = edges + if configuration is not None: + self.configuration.update(configuration) + + +########################### +# FINALIZATION STRATEGIES # +########################### + +class MatchAlgorithmStrategy(TraversalStrategy): + def __init__(self, match_algorithm=None): + TraversalStrategy.__init__(self) + if match_algorithm is not None: + self.configuration["matchAlgorithm"] = match_algorithm + + +########################### +# OPTIMIZATION STRATEGIES # +########################### + +class AdjacentToIncidentStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class FilterRankingStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class IdentityRemoveStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class IncidentToAdjacentStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class InlineFilterStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class LazyBarrierStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class MatchPredicateStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class OrderLimitStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class PathProcessorStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class PathRetractionStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class RangeByIsCountStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class RepeatUnrollStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class GraphFilterStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +########################### +# VERIFICATION STRATEGIES # +########################### + +class LambdaRestrictionStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) + + +class ReadOnlyStrategy(TraversalStrategy): + def __init__(self): + TraversalStrategy.__init__(self) diff --git a/aiogremlin/gremlin_python/process/traversal.py b/aiogremlin/gremlin_python/process/traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..6e3290fa68ebe5035de29144726e492c6bc1d2e9 --- /dev/null +++ b/aiogremlin/gremlin_python/process/traversal.py @@ -0,0 +1,396 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import asyncio +from enum import Enum +from aiogremlin.gremlin_python import statics + + +class Traversal(object): + def __init__(self, graph, traversal_strategies, bytecode): + self.graph = graph + self.traversal_strategies = traversal_strategies + self.bytecode = bytecode + self.side_effects = TraversalSideEffects() + self.traversers = None + self.last_traverser = None + def __repr__(self): + return str(self.bytecode) + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.bytecode == other.bytecode + else: + return False + async def __aiter__(self): + return self + async def __anext__(self): + if self.traversers is None: + await self.traversal_strategies.apply_strategies(self) + if self.last_traverser is None: + self.last_traverser = await self.traversers.__anext__() + 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 + async def toList(self): + results = [] + async for result in self: + results.append(result) + return results + async def toSet(self): + results = set() + async for result in self: + results.add(result) + return results + async def iterate(self): + while True: + try: + await self.nextTraverser() + except StopAsyncIteration: + return self + async def nextTraverser(self): + if self.traversers is None: + await self.traversal_strategies.apply_strategies(self) + if self.last_traverser is None: + return await self.traversers.__anext__() + else: + temp = self.last_traverser + self.last_traverser = None + return temp + async def next(self, amount=None): + if amount is None: + return await self.__anext__() + else: + count = 0 + tempList = [] + while count < amount: + count = count + 1 + try: + temp = await self.__anext__() + except StopAsyncIteration: + return tempList + tempList.append(temp) + return tempList + + +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') +statics.add_static('sum', Operator.sum) +statics.add_static('minus', Operator.minus) +statics.add_static('mult', Operator.mult) +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('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) +statics.add_static('valueIncr', Order.valueIncr) +statics.add_static('keyDecr', Order.keyDecr) +statics.add_static('valueDecr', Order.valueDecr) +statics.add_static('shuffle', Order.shuffle) + +Pick = Enum('Pick', 'any none') +statics.add_static('any', Pick.any) +statics.add_static('none', Pick.none) + +Pop = Enum('Pop', 'all_ first last') +statics.add_static('first', Pop.first) +statics.add_static('last', Pop.last) +statics.add_static('all_', Pop.all_) + +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) +statics.add_static('value', T.value) + +class P(object): + def __init__(self, operator, value, other=None): + self.operator = operator + self.value = value + self.other = other + @staticmethod + def between(*args): + return P("between", *args) + @staticmethod + def eq(*args): + return P("eq", *args) + @staticmethod + def gt(*args): + return P("gt", *args) + @staticmethod + def gte(*args): + return P("gte", *args) + @staticmethod + def inside(*args): + return P("inside", *args) + @staticmethod + def lt(*args): + return P("lt", *args) + @staticmethod + def lte(*args): + return P("lte", *args) + @staticmethod + def neq(*args): + return P("neq", *args) + @staticmethod + def not_(*args): + return P("not", *args) + @staticmethod + def outside(*args): + return P("outside", *args) + @staticmethod + def test(*args): + return P("test", *args) + @staticmethod + def within(*args): + return P("within", *args) + @staticmethod + def without(*args): + return P("without", *args) + def and_(self, arg): + return P("and", self, arg) + def or_(self, arg): + return P("or", self, arg) + 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) + + + +''' +TRAVERSER +''' + +class Traverser(object): + def __init__(self, object, bulk=None): + if bulk is None: + bulk = statics.long(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 +''' + +class TraversalStrategies(object): + global_cache = {} + 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 + async def apply_strategies(self, traversal): + for traversal_strategy in self.traversal_strategies: + func = traversal_strategy.apply(traversal) + if asyncio.iscoroutine(func): + await func + def __repr__(self): + return str(self.traversal_strategies) + + +class TraversalStrategy(object): + def __init__(self, strategy_name=None, configuration=None): + self.strategy_name = type(self).__name__ if strategy_name is None else strategy_name + self.configuration = {} if configuration is None else configuration + def apply(self, traversal): + return + def apply_async(self, traversal): + return + def __eq__(self, other): + return isinstance(other, self.__class__) + def __hash__(self): + return hash(self.strategy_name) + def __repr__(self): + return self.strategy_name + +''' +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): + instruction = [source_name] + for arg in args: + instruction.append(self.__convertArgument(arg)) + self.source_instructions.append(instruction) + def add_step(self, step_name, *args): + instruction = [step_name] + for arg in args: + instruction.append(self.__convertArgument(arg)) + self.step_instructions.append(instruction) + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.source_instructions == other.source_instructions and self.step_instructions == other.step_instructions + else: + return False + def __convertArgument(self,arg): + if isinstance(arg, Traversal): + self.bindings.update(arg.bytecode.bindings) + return arg.bytecode + elif isinstance(arg, dict): + newDict = {} + for key in arg: + newDict[self.__convertArgument(key)] = self.__convertArgument(arg[key]) + return newDict + elif isinstance(arg, list): + newList = [] + for item in arg: + newList.append(self.__convertArgument(item)) + return newList + elif isinstance(arg, set): + newSet = set() + for item in arg: + newSet.add(self.__convertArgument(item)) + return newSet + elif isinstance(arg, tuple) and 2 == len(arg) and isinstance(arg[0], str): + self.bindings[arg[0]] = arg[1] + return Binding(arg[0],self.__convertArgument(arg[1])) + else: + return arg + def __repr__(self): + return (str(self.source_instructions) if len(self.source_instructions) > 0 else "") + \ + (str(self.step_instructions) if len(self.step_instructions) > 0 else "") + + +''' +BINDINGS +''' + +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 + 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) + def __repr__(self): + return "binding[" + self.key + "=" + str(self.value) + "]" diff --git a/aiogremlin/gremlin_python/statics.py b/aiogremlin/gremlin_python/statics.py new file mode 100644 index 0000000000000000000000000000000000000000..e8ee220005bc01904969c7d802397ddb2eaa3fa3 --- /dev/null +++ b/aiogremlin/gremlin_python/statics.py @@ -0,0 +1,57 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +from types import FunctionType + +from enum import Enum + + +class long(int): pass +FloatType = float +IntType = int +LongType = long +TypeType = type + + +staticMethods = {} +staticEnums = {} +default_lambda_language = "gremlin-python" + + +def add_static(key, value): + if isinstance(value, Enum): + staticEnums[key] = value + else: + staticMethods[key] = value + + +def load_statics(global_dict): + for key in staticMethods: + global_dict[key] = staticMethods[key] + for key in staticEnums: + global_dict[key] = staticEnums[key] + + +def unload_statics(global_dict): + for key in staticMethods: + if key in global_dict: + del global_dict[key] + for key in staticEnums: + if key in global_dict: + del global_dict[key] diff --git a/aiogremlin/gremlin_python/structure/__init__.py b/aiogremlin/gremlin_python/structure/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7626550738f12ccd5e18b8c06a8d68aaab882066 --- /dev/null +++ b/aiogremlin/gremlin_python/structure/__init__.py @@ -0,0 +1,20 @@ +''' +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)' diff --git a/aiogremlin/gremlin_python/structure/graph.py b/aiogremlin/gremlin_python/structure/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..e7fe22e85d1a32436a14d35742fb5faec4196fd7 --- /dev/null +++ b/aiogremlin/gremlin_python/structure/graph.py @@ -0,0 +1,123 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +from aiogremlin.gremlin_python.process.graph_traversal import GraphTraversalSource +from aiogremlin.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) + + +class Path(object): + def __init__(self, labels, objects): + self.labels = labels + self.objects = objects + + def __repr__(self): + return str(self.objects) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.objects == other.objects and self.labels == other.labels + + def __hash__(self): + return hash(str(self.objects)) + hash(str(self.labels)) + + def __getitem__(self, key): + if isinstance(key, str): + objects = [] + for i, labels in enumerate(self.labels): + if key in labels: + objects.append(self.objects[i]) + if 0 == len(objects): + raise KeyError("The step with label " + key + " does not exist") + return objects if len(objects) > 1 else objects[0] + elif isinstance(key, int): + return self.objects[key] + else: + raise TypeError("The path access key must be either a string label or integer index") + + def __len__(self): + return len(self.objects) diff --git a/aiogremlin/gremlin_python/structure/io/__init__.py b/aiogremlin/gremlin_python/structure/io/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7626550738f12ccd5e18b8c06a8d68aaab882066 --- /dev/null +++ b/aiogremlin/gremlin_python/structure/io/__init__.py @@ -0,0 +1,20 @@ +''' +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)' diff --git a/aiogremlin/gremlin_python/structure/io/graphson.py b/aiogremlin/gremlin_python/structure/io/graphson.py new file mode 100644 index 0000000000000000000000000000000000000000..6a29ace617945777d0f6f24948086636bd9a6fda --- /dev/null +++ b/aiogremlin/gremlin_python/structure/io/graphson.py @@ -0,0 +1,341 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +from enum import Enum +import json + +from aiogremlin.gremlin_python import statics +from aiogremlin.gremlin_python.statics import FloatType, FunctionType, IntType, LongType, TypeType +from aiogremlin.gremlin_python.process.traversal import Binding, Bytecode, P, Traversal, Traverser, TraversalStrategy +from aiogremlin.gremlin_python.structure.graph import Edge, Property, Vertex, VertexProperty, Path + + +_serializers = {} +_deserializers = {} + + +class GraphSONTypeType(type): + def __new__(mcs, name, bases, dct): + cls = super(GraphSONTypeType, mcs).__new__(mcs, name, bases, dct) + if not name.startswith('_'): + if cls.python_type: + _serializers[cls.python_type] = cls + if cls.graphson_type: + _deserializers[cls.graphson_type] = cls + return cls + + +class GraphSONUtil(object): + TYPE_KEY = "@type" + VALUE_KEY = "@value" + + @classmethod + def typedValue(cls, type_name, value, prefix="g"): + out = {cls.TYPE_KEY: cls.formatType(prefix, type_name)} + if value is not None: + out[cls.VALUE_KEY] = value + return out + + @classmethod + def formatType(cls, prefix, type_name): + return "%s:%s" % (prefix, type_name) + +# Read/Write classes split to follow precedence of the Java API +class GraphSONWriter(object): + + def __init__(self, serializer_map=None): + """ + :param serializer_map: map from Python type to serializer instance implementing `dictify` + """ + self.serializers = _serializers.copy() + if serializer_map: + self.serializers.update(serializer_map) + + def writeObject(self, objectData): + # to JSON + return json.dumps(self.toDict(objectData), separators=(',', ':')) + + def toDict(self, obj): + """ + Encodes python objects in GraphSON type-tagged dict values + """ + try: + return self.serializers[type(obj)].dictify(obj, self) + except KeyError: + for key, serializer in self.serializers.items(): + if isinstance(obj, key): + return serializer.dictify(obj, self) + + # list and map are treated as normal json objs (could be isolated serializers) + if isinstance(obj, (list, set)): + return [self.toDict(o) for o in obj] + elif isinstance(obj, dict): + return dict((self.toDict(k), self.toDict(v)) for k, v in obj.items()) + else: + return obj + + +class GraphSONReader(object): + + def __init__(self, deserializer_map=None): + """ + :param deserializer_map: map from GraphSON type tag to deserializer instance implementing `objectify` + """ + self.deserializers = _deserializers.copy() + if deserializer_map: + self.deserializers.update(deserializer_map) + + def readObject(self, jsonData): + # from JSON + return self.toObject(json.loads(jsonData)) + + def toObject(self, obj): + """ + Unpacks GraphSON type-tagged dict values into objects mapped in self.deserializers + """ + if isinstance(obj, dict): + try: + return self.deserializers[obj[GraphSONUtil.TYPE_KEY]].objectify(obj[GraphSONUtil.VALUE_KEY], self) + except KeyError: + pass + # list and map are treated as normal json objs (could be isolated deserializers) + return dict((self.toObject(k), self.toObject(v)) for k, v in obj.items()) + elif isinstance(obj, list): + return [self.toObject(o) for o in obj] + else: + return obj + + +class _GraphSONTypeIO(metaclass=GraphSONTypeType): + python_type = None + graphson_type = None + + symbolMap = {"global_": "global", "as_": "as", "in_": "in", "and_": "and", + "or_": "or", "is_": "is", "not_": "not", "from_": "from", + "set_": "set", "list_": "list", "all_": "all"} + + @classmethod + def unmangleKeyword(cls, symbol): + return cls.symbolMap.get(symbol, symbol) + + def dictify(self, obj, writer): + raise NotImplementedError() + + def objectify(self, d, reader): + raise NotImplementedError() + + +class _BytecodeSerializer(_GraphSONTypeIO): + + @classmethod + def _dictify_instructions(cls, instructions, writer): + out = [] + for instruction in instructions: + inst = [instruction[0]] + inst.extend(writer.toDict(arg) for arg in instruction[1:]) + out.append(inst) + return out + + @classmethod + def dictify(cls, bytecode, writer): + if isinstance(bytecode, Traversal): + bytecode = bytecode.bytecode + out = {} + if bytecode.source_instructions: + out["source"] = cls._dictify_instructions(bytecode.source_instructions, writer) + if bytecode.step_instructions: + out["step"] = cls._dictify_instructions(bytecode.step_instructions, writer) + return GraphSONUtil.typedValue("Bytecode", out) + + +class TraversalSerializer(_BytecodeSerializer): + python_type = Traversal + + +class BytecodeSerializer(_BytecodeSerializer): + python_type = Bytecode + + +class TraversalStrategySerializer(_GraphSONTypeIO): + python_type = TraversalStrategy + + @classmethod + def dictify(cls, strategy, writer): + return GraphSONUtil.typedValue(strategy.strategy_name, writer.toDict(strategy.configuration)) + + +class TraverserIO(_GraphSONTypeIO): + python_type = Traverser + graphson_type = "g:Traverser" + + @classmethod + def dictify(cls, traverser, writer): + return GraphSONUtil.typedValue("Traverser", {"value": writer.toDict(traverser.object), + "bulk": writer.toDict(traverser.bulk)}) + + @classmethod + def objectify(cls, d, reader): + return Traverser(reader.toObject(d["value"]), + reader.toObject(d["bulk"])) + + +class EnumSerializer(_GraphSONTypeIO): + python_type = Enum + + @classmethod + def dictify(cls, enum, _): + return GraphSONUtil.typedValue(cls.unmangleKeyword(type(enum).__name__), + cls.unmangleKeyword(str(enum.name))) + + +class PSerializer(_GraphSONTypeIO): + python_type = P + + @classmethod + def dictify(cls, p, writer): + out = {"predicate": p.operator, + "value": [writer.toDict(p.value), writer.toDict(p.other)] if p.other is not None else + writer.toDict(p.value)} + return GraphSONUtil.typedValue("P", out) + + +class BindingSerializer(_GraphSONTypeIO): + python_type = Binding + + @classmethod + def dictify(cls, binding, writer): + out = {"key": binding.key, + "value": writer.toDict(binding.value)} + return GraphSONUtil.typedValue("Binding", out) + + +class LambdaSerializer(_GraphSONTypeIO): + python_type = FunctionType + + @classmethod + def dictify(cls, lambda_object, writer): + lambda_result = lambda_object() + script = lambda_result if isinstance(lambda_result, str) else lambda_result[0] + language = statics.default_lambda_language if isinstance(lambda_result, str) else lambda_result[1] + out = {"script": script, + "language": language} + if language == "gremlin-jython" or language == "gremlin-python": + if not script.strip().startswith("lambda"): + script = "lambda " + script + out["script"] = script + out["arguments"] = eval(out["script"]).__code__.co_argcount + else: + out["arguments"] = -1 + return GraphSONUtil.typedValue("Lambda", out) + + +class TypeSerializer(_GraphSONTypeIO): + python_type = TypeType + + @classmethod + def dictify(cls, typ, writer): + return writer.toDict(typ()) + + +class _NumberIO(_GraphSONTypeIO): + + @classmethod + def dictify(cls, n, writer): + if isinstance(n, bool): # because isinstance(False, int) and isinstance(True, int) + return n + return GraphSONUtil.typedValue(cls.graphson_base_type, n) + + @classmethod + def objectify(cls, v, _): + return cls.python_type(v) + + +class FloatIO(_NumberIO): + python_type = FloatType + graphson_type = "g:Float" + graphson_base_type = "Float" + + +class DoubleIO(FloatIO): + graphson_type = "g:Double" + graphson_base_type = "Double" + + +class Int64IO(_NumberIO): + python_type = LongType + graphson_type = "g:Int64" + graphson_base_type = "Int64" + + +class Int32IO(_NumberIO): + python_type = IntType + graphson_type = "g:Int32" + graphson_base_type = "Int32" + + @classmethod + def dictify(cls, n, writer): + if isinstance(n, bool): + return n + return GraphSONUtil.typedValue(cls.graphson_base_type, n) + + +class VertexDeserializer(_GraphSONTypeIO): + graphson_type = "g:Vertex" + + @classmethod + def objectify(cls, d, reader): + return Vertex(reader.toObject(d["id"]), d.get("label", "")) + + +class EdgeDeserializer(_GraphSONTypeIO): + graphson_type = "g:Edge" + + @classmethod + def objectify(cls, d, reader): + return Edge(reader.toObject(d["id"]), + Vertex(reader.toObject(d["outV"]), ""), + d.get("label", "vertex"), + Vertex(reader.toObject(d["inV"]), "")) + + +class VertexPropertyDeserializer(_GraphSONTypeIO): + graphson_type = "g:VertexProperty" + + @classmethod + def objectify(cls, d, reader): + return VertexProperty(reader.toObject(d["id"]), d["label"], + reader.toObject(d["value"])) + + +class PropertyDeserializer(_GraphSONTypeIO): + graphson_type = "g:Property" + + @classmethod + def objectify(cls, d, reader): + return Property(d["key"], reader.toObject(d["value"])) + + +class PathDeserializer(_GraphSONTypeIO): + graphson_type = "g:Path" + + @classmethod + def objectify(cls, d, reader): + labels = [set(label) for label in d["labels"]] + objects = [reader.toObject(o) for o in d["objects"]] + return Path(labels, objects) diff --git a/aiogremlin/remote/__init__.py b/aiogremlin/remote/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/aiogremlin/remote/driver_remote_connection.py b/aiogremlin/remote/driver_remote_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..60531b4bbf42d38dfc4d8138e59bfbea4ac6b41e --- /dev/null +++ b/aiogremlin/remote/driver_remote_connection.py @@ -0,0 +1,64 @@ +import asyncio +from urllib.parse import urlparse + +from aiogremlin.driver.cluster import Cluster +from aiogremlin.gremlin_python.driver import serializer +from aiogremlin.remote.driver_remote_side_effects import RemoteTraversalSideEffects +from aiogremlin.gremlin_python.driver.remote_connection import RemoteTraversal + + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +class DriverRemoteConnection: + + def __init__(self, client, loop, *, cluster=None): + self._client = client + self._loop = loop + self._cluster = cluster + + @property + def client(self): + return self._client + + @property + def config(self): + return self._cluster.config + + @classmethod + async def using(cls, cluster, aliases=None, *, loop=None): + client = await cluster.connect(aliases=aliases) + if not loop: + loop = asyncio.get_event_loop() + return cls(client, loop) + + @classmethod + async def open(cls, url=None, aliases=None, loop=None, *, + graphson_reader=None, graphson_writer=None, **config): + if url: + parsed_url = urlparse(url) + config.update({ + 'scheme': parsed_url.scheme, + 'hosts': [parsed_url.hostname], + 'port': parsed_url.port}) + if isinstance(aliases, str): + aliases = {'g': aliases} + if not loop: + loop = asyncio.get_event_loop() + message_serializer = serializer.GraphSONMessageSerializer( + reader=graphson_reader, + writer=graphson_writer) + config.update({'message_serializer': message_serializer}) + cluster = await Cluster.open(loop, aliases=aliases, **config) + client = await cluster.connect() + return cls(client, loop, cluster=cluster) + + async def close(self): + if self._cluster: + await self._cluster.close() + + async def submit(self, bytecode): + result_set = await self._client.submit(bytecode) + side_effects = RemoteTraversalSideEffects(result_set.request_id, + self._client) + return RemoteTraversal(result_set, side_effects) diff --git a/aiogremlin/remote/driver_remote_side_effects.py b/aiogremlin/remote/driver_remote_side_effects.py new file mode 100644 index 0000000000000000000000000000000000000000..9dc59c7c82e7603f11a15a748e90268ac833eb14 --- /dev/null +++ b/aiogremlin/remote/driver_remote_side_effects.py @@ -0,0 +1,84 @@ +from aiogremlin.gremlin_python.driver import request +from aiogremlin.gremlin_python.process import traversal + + + +class RemoteTraversalSideEffects(traversal.TraversalSideEffects): + def __init__(self, side_effect, client): + self._side_effect = side_effect + self._client = client + self._keys = set() + self._side_effects = {} + self._closed = False + + async def __getitem__(self, key): + if isinstance(key, slice): + raise TypeError( + 'RemoteTraversalSideEffects does not support slicing') + return await self.get(key) + + async def keys(self): + if not self._closed: + message = request.RequestMessage( + 'traversal', 'keys', + {'sideEffect': self._side_effect, + 'aliases': self._client.aliases}) + result_set = await self._client.submit(message) + results = await result_set.all() + self._keys = set(results) + return self._keys + + async def get(self, key): + if not self._side_effects.get(key): + if not self._closed: + results = await self._get(key) + self._side_effects[key] = results + self._keys.add(key) + else: + return None + return self._side_effects[key] + + async def _get(self, key): + message = request.RequestMessage( + 'traversal', 'gather', + {'sideEffect': self._side_effect, 'sideEffectKey': key, + 'aliases': self._client.aliases}) + result_set = await self._client.submit(message) + return await self._aggregate_results(result_set) + + async def close(self): + if not self._closed: + message = request.RequestMessage( + 'traversal', 'close', + {'sideEffect': self._side_effect, + 'aliases': {'g': self._client.aliases}}) + result_set = await self._client.submit(message) + self._closed = True + return await result_set.one() + + async def _aggregate_results(self, result_set): + aggregates = {'list': [], 'set': set(), 'map': {}, 'bulkset': {}, + 'none': None} + results = None + async for msg in result_set: + if results is None: + aggregate_to = result_set.aggregate_to + results = aggregates.get(aggregate_to, []) + # on first message, get the right result data structure + # if there is no update to a structure, then the item is the result + if results is None: + results = msg + # updating a map is different than a list or a set + elif isinstance(results, dict): + if aggregate_to == "map": + results.update(msg) + else: + results[msg.object] = msg.bulk + elif isinstance(results, set): + results.update(msg) + # flat add list to result list + else: + results.append(msg) + if results is None: + results = [] + return results diff --git a/aiogremlin/response.py b/aiogremlin/response.py deleted file mode 100644 index fedf9d8af50806ef04d9e75981bf133e2efb1b20..0000000000000000000000000000000000000000 --- a/aiogremlin/response.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Class used to pass messages with the Gremlin Server. -""" - -import asyncio -import base64 -import hashlib -import os - -import aiohttp - -from aiowebsocketclient.connector import ClientWebSocketResponse - -__all__ = ('GremlinClientWebSocketResponse',) - - -class GremlinClientWebSocketResponse(ClientWebSocketResponse): - """Wraps :py:class:`aiohttp.websocket_client.ClientWebSocketResponse` - with minimal added functionality for the Gremln Server use case. - """ - def __init__(self, reader, writer, protocol, response, timeout, autoclose, - autoping, loop): - ClientWebSocketResponse.__init__(self, reader, writer, protocol, - response, timeout, autoclose, - autoping, loop) - self._parser = aiohttp.StreamParser(buf=aiohttp.DataQueue(loop=loop), - loop=loop) - - @property - def parser(self): - """ - Read-only property. - - :returns: :py:class:`aiohttp.parsers.StreamParser` - """ - return self._parser - - @asyncio.coroutine - def _close(self, *, code=1000, message=b''): - if not self._closed: - did_close = self._do_close() - if did_close: - return True - while True: - try: - msg = yield from asyncio.wait_for( - self._reader.read(), self._timeout, loop=self._loop) - except asyncio.CancelledError: - self._close_code = 1006 - self._response.close(force=True) - raise - except Exception as exc: - self._close_code = 1006 - self._exception = exc - self._response.close(force=True) - return True - - if msg.tp == aiohttp.MsgType.close: - self._close_code = msg.data - self._response.close(force=True) - return True - else: - return False - - def _do_close(self, code=1000, message=b''): - self._closed = True - try: - self._writer.close(code, message) - except asyncio.CancelledError: - self._close_code = 1006 - self._response.close(force=True) - raise - except Exception as exc: - self._close_code = 1006 - self._exception = exc - self._response.close(force=True) - return True - - if self._closing: - self._response.close(force=True) - return True - - def send(self, message, *, binary=True): - """Send a message to the server.""" - if binary: - method = self.send_bytes - else: - method = self.send_str - try: - method(message) - except RuntimeError: - # Socket closed. - raise - except TypeError: - # Bytes/string input error. - raise - - @asyncio.coroutine - def receive(self): - """ - :ref:`coroutine<coroutine>` method - - Receive a message from the server and push it into the parser. - """ - msg = yield from super().receive() - if msg.tp == aiohttp.MsgType.binary: - self.parser.feed_data(msg.data.decode()) - elif msg.tp == aiohttp.MsgType.text: - self.parser.feed_data(msg.data.strip()) - else: - if msg.tp == aiohttp.MsgType.close: - yield from ws.close() - elif msg.tp == aiohttp.MsgType.error: - raise msg.data - elif msg.tp == aiohttp.MsgType.closed: - pass diff --git a/aiogremlin/subprotocol.py b/aiogremlin/subprotocol.py deleted file mode 100644 index e75f55e048693df498a68b9605f3ffc6b03d692a..0000000000000000000000000000000000000000 --- a/aiogremlin/subprotocol.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Implements the Gremlin Server subprotocol.""" - -import base64 -import collections -import uuid - -try: - import ujson as json -except ImportError: - import json - -from aiogremlin.exceptions import RequestError, GremlinServerError - -__all__ = ("GremlinWriter", "gremlin_response_parser", "Message") - - -Message = collections.namedtuple( - "Message", - ["status_code", "data", "message", "metadata"]) - - -def gremlin_response_parser(out, buf): - while True: - message = yield - message = json.loads(message) - message = Message(message["status"]["code"], - message["result"]["data"], - message["status"]["message"], - message["result"]["meta"]) - if message.status_code == 200: - out.feed_data(message) - out.feed_eof() - elif message.status_code == 206 or message.status_code == 407: - out.feed_data(message) - elif message.status_code == 204: - out.feed_data(message) - out.feed_eof() - else: - if message.status_code < 500: - raise RequestError(message.status_code, message.message) - else: - raise GremlinServerError(message.status_code, message.message) - - -class GremlinWriter: - - def __init__(self, ws): - self.ws = ws - - def write(self, *, gremlin="", bindings=None, lang="gremlin-groovy", - rebindings=None, op="eval", processor="", session=None, - binary=True, mime_type="application/json", username="", - password=""): - if rebindings is None: - rebindings = {} - if op == "eval": - message = self._prepare_message(gremlin, - bindings, - lang, - rebindings, - op, - processor, - session) - - if op == "authentication": - message = self._authenticate( - username, password, session, processor) - message = json.dumps(message) - if binary: - message = self._set_message_header(message, mime_type) - self.ws.send(message, binary=binary) - return self.ws - - @staticmethod - def _set_message_header(message, mime_type): - if mime_type == "application/json": - mime_len = b"\x10" - mime_type = b"application/json" - else: - raise ValueError("Unknown mime type.") - return b"".join([mime_len, mime_type, bytes(message, "utf-8")]) - - @staticmethod - def _prepare_message(gremlin, bindings, lang, rebindings, op, processor, - session): - message = { - "requestId": str(uuid.uuid4()), - "op": op, - "processor": processor, - "args": { - "gremlin": gremlin, - "bindings": bindings, - "language": lang, - "rebindings": rebindings - } - } - if session is None: - if processor == "session": - raise RuntimeError("session processor requires a session id") - else: - message["args"].update({"session": session}) - return message - - - @staticmethod - def _authenticate(username, password, session, processor): - auth = b"".join([b"\x00", bytes(username, "utf-8"), b"\x00", bytes(password, "utf-8")]) - message = { - "requestId": str(uuid.uuid4()), - "op": "authentication", - "processor": processor, - "args": { - "sasl": base64.b64encode(auth).decode() - } - } - if session is None: - if processor == "session": - raise RuntimeError("session processor requires a session id") - else: - message["args"].update({"session": session}) - return message diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..cfcbfea95cdd0b95dd262f7a44e723e8df284400 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[aliases] +test=pytest + +[tool:pytest] +norecursedirs = '.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg' lib lib64 diff --git a/setup.py b/setup.py index 78cd7c4b25fa282326c45ab8b34f15c9c4f4b9f5..a041ad2d86b1fd0f5548815b51140f29d2b2f198 100644 --- a/setup.py +++ b/setup.py @@ -2,20 +2,27 @@ from setuptools import setup setup( - name="aiogremlin", - version="0.1.3", - url="", - license="MIT", - author="davebshow", - author_email="davebshow@gmail.com", - description="Python 3 driver for TP3 Gremlin Server built on Asyncio and aiohttp", - long_description=open("README.txt").read(), - packages=["aiogremlin", "tests"], + name='aiogremlin', + version='3.2.4', + url='', + license='MIT', + author='davebshow', + author_email='davebshow@gmail.com', + description='Async Gremlin-Python', + long_description=open('README.txt').read(), + packages=['aiogremlin', 'aiogremlin.driver', 'aiogremlin.driver.aiohttp', + 'aiogremlin.gremlin_python', 'aiogremlin.gremlin_python.driver', + 'aiogremlin.gremlin_python.process', + 'aiogremlin.gremlin_python.structure', + 'aiogremlin.gremlin_python.structure.io', + 'aiogremlin.remote'], install_requires=[ - "aiohttp==0.18.4", - "aiowebsocketclient==0.0.5" + 'aiohttp==1.3.3', + 'PyYAML==3.12' ], - test_suite="tests", + test_suite='tests', + setup_requires=['pytest-runner'], + tests_require=['pytest-asyncio', 'pytest', 'mock'], classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e4bafe5e06b56f3dc57fdfc30682aabc2246659b..0000000000000000000000000000000000000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import tests diff --git a/tests/config/config.json b/tests/config/config.json new file mode 100644 index 0000000000000000000000000000000000000000..025136e47ae4f118856f1ed97ac8e612878916a7 --- /dev/null +++ b/tests/config/config.json @@ -0,0 +1,13 @@ +{ + "ssl_password":"", + "port":8182, + "ssl_certfile":"", + "scheme":"wss", + "hosts":[ + "localhost" + ], + "username":"dave", + "password":"mypass", + "ssl_keyfile":"", + "message_serializer":"aiogremlin.gremlin_python.driver.serializer.GraphSONMessageSerializer" +} diff --git a/tests/config/config.yml b/tests/config/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..5762be39a72c7ca94771c1d6076985d166018c96 --- /dev/null +++ b/tests/config/config.yml @@ -0,0 +1,4 @@ +scheme: 'wss' +hosts: ['localhost'] +port: 8183 +message_serializer: 'aiogremlin.gremlin_python.driver.serializer.GraphSONMessageSerializer' diff --git a/tests/config_module.py b/tests/config_module.py new file mode 100644 index 0000000000000000000000000000000000000000..cd609ccd281927c45873f8211aaea63b493566d1 --- /dev/null +++ b/tests/config_module.py @@ -0,0 +1,4 @@ +SCHEME = 'wss' +HOSTS = ['localhost'] +PORT = 8183 +MESSAGE_SERIALIZER = 'aiogremlin.gremlin_python.driver.serializer.GraphSONMessageSerializer' diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..0e860e0b35b9e88e0666d9d8d7484ba5cc420a0e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,135 @@ +# Copyright 2016 David M. Brown +# +# 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 pytest +from aiogremlin import driver +from aiogremlin.driver.provider import TinkerGraph +from aiogremlin.gremlin_python.driver import serializer + + +# def pytest_generate_tests(metafunc): +# if 'cluster' in metafunc.fixturenames: +# metafunc.parametrize("cluster", ['c1', 'c2'], indirect=True) + + +def pytest_addoption(parser): + parser.addoption('--provider', default='tinkergraph', + choices=('tinkergraph', 'dse',)) + parser.addoption('--gremlin-host', default='localhost') + parser.addoption('--gremlin-port', default='8182') + + +@pytest.fixture +def provider(request): + provider = request.config.getoption('provider') + if provider == 'tinkergraph': + return TinkerGraph + elif provider == 'dse': + try: + import goblin_dse + except ImportError: + raise RuntimeError("Couldn't run tests with DSEGraph provider: the goblin_dse package " + "must be installed") + else: + return goblin_dse.DSEGraph + + +@pytest.fixture +def aliases(request): + if request.config.getoption('provider') == 'tinkergraph': + return {'g': 'g'} + elif request.config.getoption('provider') == 'dse': + return {'g': 'testgraph.g'} + + +@pytest.fixture +def gremlin_server(): + return driver.GremlinServer + + +@pytest.fixture +def unused_server_url(unused_tcp_port): + return 'http://localhost:{}/gremlin'.format(unused_tcp_port) + + +@pytest.fixture +def gremlin_host(request): + return request.config.getoption('gremlin_host') + + +@pytest.fixture +def gremlin_port(request): + return request.config.getoption('gremlin_port') + + +@pytest.fixture +def gremlin_url(gremlin_host, gremlin_port): + return "http://{}:{}/gremlin".format(gremlin_host, gremlin_port) + + +@pytest.fixture +def connection(gremlin_url, event_loop, provider): + try: + conn = event_loop.run_until_complete( + driver.Connection.open( + gremlin_url, event_loop, + message_serializer=serializer.GraphSONMessageSerializer, + provider=provider + )) + except OSError: + pytest.skip('Gremlin Server is not running') + return conn + + +@pytest.fixture +def connection_pool(gremlin_url, event_loop, provider): + return driver.ConnectionPool( + gremlin_url, event_loop, None, '', '', 4, 1, 16, + 64, None, serializer.GraphSONMessageSerializer, provider=provider) + + +@pytest.fixture +def cluster(request, gremlin_host, gremlin_port, event_loop, provider, aliases): + # if request.param == 'c1': + cluster = driver.Cluster( + event_loop, + hosts=[gremlin_host], + port=gremlin_port, + aliases=aliases, + message_serializer=serializer.GraphSONMessageSerializer, + provider=provider + ) + # elif request.param == 'c2': + # cluster = driver.Cluster( + # event_loop, + # hosts=[gremlin_host], + # port=gremlin_port, + # aliases=aliases, + # message_serializer=serializer.GraphSONMessageSerializer, + # provider=provider + # ) + return cluster + +# TOOO FIX +# @pytest.fixture +# def remote_graph(): +# return driver.AsyncGraph() + +# Class fixtures +@pytest.fixture +def cluster_class(event_loop): + return driver.Cluster diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000000000000000000000000000000000000..bf9aacde9766d4d5fc3c4ecc04142058b1c066f9 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,64 @@ +# Copyright 2016 David M. Brown +# +# 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 aiogremlin.driver.server import GremlinServer + + +@pytest.mark.asyncio +async def test_client_auto_release(cluster): + client = await cluster.connect() + resp = await client.submit("1 + 1") + async for msg in resp: + pass + await asyncio.sleep(0) + host = cluster._hosts.popleft() + assert len(host._pool._available) == 1 + await host.close() + + +@pytest.mark.asyncio +async def test_alias(cluster): + client = await cluster.connect() + aliased_client = client.alias({"g": "g1"}) + 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 new file mode 100644 index 0000000000000000000000000000000000000000..e4bdee3b2db19d4954fd8bb275da4bf679075c82 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,102 @@ +# Copyright 2016 David M. Brown +# +# 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 os + +import pytest + +from aiogremlin import driver, exception +from aiogremlin.gremlin_python.driver import serializer + +import config_module + + +dirname = os.path.dirname(os.path.dirname(__file__)) + + +@pytest.fixture(params=[0, 1]) +def conf_module(request): + if request.param: + return 'config_module' + else: + return config_module + + +def test_cluster_default_config(event_loop): + cluster = driver.Cluster(event_loop) + assert cluster.config['scheme'] == 'ws' + assert cluster.config['hosts'] == ['localhost'] + assert cluster.config['port'] == 8182 + assert cluster.config['ssl_certfile'] == '' + assert cluster.config['ssl_keyfile'] == '' + assert cluster.config['ssl_password'] == '' + assert cluster.config['username'] == '' + assert cluster.config['password'] == '' + + +def test_cluster_custom_config(event_loop, cluster_class): + cluster = cluster_class(event_loop, username='dave', password='mypass', + hosts=['127.0.0.1']) + assert cluster.config['scheme'] == 'ws' + assert cluster.config['hosts'] == ['127.0.0.1'] + assert cluster.config['port'] == 8182 + assert cluster.config['ssl_certfile'] == '' + assert cluster.config['ssl_keyfile'] == '' + assert cluster.config['ssl_password'] == '' + assert cluster.config['username'] == 'dave' + assert cluster.config['password'] == 'mypass' + assert issubclass(cluster.config['message_serializer'], + serializer.GraphSONMessageSerializer) + + +def test_cluster_config_from_json(event_loop, cluster_class): + cluster = cluster_class(event_loop) + cluster.config_from_file(dirname + '/tests/config/config.json') + assert cluster.config['scheme'] == 'wss' + assert cluster.config['hosts'] == ['localhost'] + assert cluster.config['port'] == 8182 + assert cluster.config['ssl_certfile'] == '' + assert cluster.config['ssl_keyfile'] == '' + assert cluster.config['ssl_password'] == '' + assert cluster.config['username'] == 'dave' + assert cluster.config['password'] == 'mypass' + + assert issubclass(cluster.config['message_serializer'], + serializer.GraphSONMessageSerializer) + + +def test_cluster_config_from_yaml(event_loop, cluster_class): + cluster = cluster_class(event_loop) + cluster.config_from_file(dirname + '/tests/config/config.yml') + assert cluster.config['scheme'] == 'wss' + assert cluster.config['hosts'] == ['localhost'] + assert cluster.config['port'] == 8183 + assert cluster.config['ssl_certfile'] == '' + assert cluster.config['ssl_keyfile'] == '' + assert cluster.config['ssl_password'] == '' + assert cluster.config['username'] == '' + assert cluster.config['password'] == '' + assert issubclass(cluster.config['message_serializer'], + serializer.GraphSONMessageSerializer) + + +def test_cluster_config_from_module(event_loop, cluster_class, conf_module): + cluster = cluster_class(event_loop) + cluster.config_from_module(conf_module) + assert cluster.config['scheme'] == 'wss' + assert cluster.config['hosts'] == ['localhost'] + assert cluster.config['port'] == 8183 + assert cluster.config['message_serializer'] is serializer.GraphSONMessageSerializer diff --git a/tests/test_connection.py b/tests/test_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..e7b557a6a2b2b32013d4193a47d7f330d48f220e --- /dev/null +++ b/tests/test_connection.py @@ -0,0 +1,183 @@ +# Copyright 2016 David M. Brown +# +# 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 json + +import base64 +import pytest + +import aiohttp +from aiohttp import web + +from aiogremlin import driver +from aiogremlin import exception +from aiogremlin.driver import provider +from aiogremlin.gremlin_python.driver import request, serializer + + +@pytest.mark.asyncio +async def test_get_close_conn(connection): + ws = connection._transport + assert not ws.closed + assert not connection.closed + await connection.close() + assert connection.closed + assert ws.closed + + +@pytest.mark.asyncio +async def test_conn_context_manager(connection): + async with connection: + assert not connection.closed + assert connection.closed + + +@pytest.mark.asyncio +async def test_submit(connection): + async with connection: + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': '1 + 1'}) + stream = await connection.submit(message) + results = [] + async for msg in stream: + results.append(msg) + assert len(results) == 1 + assert results[0] == 2 + + +@pytest.mark.asyncio +async def test_204_empty_stream(connection): + resp = False + async with connection: + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': 'g.V().has("unlikely", "even less likely")'}) + stream = await connection.submit(message) + async for msg in stream: + resp = True + assert not resp + + +@pytest.mark.asyncio +async def test_server_error(connection): + async with connection: + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': 'g. V jla;sdf'}) + stream = await connection.submit(message) + with pytest.raises(exception.GremlinServerError): + async for msg in stream: + pass + + +@pytest.mark.asyncio +async def test_cant_connect(event_loop, gremlin_server, unused_server_url): + with pytest.raises(Exception): + await gremlin_server.get_connection(unused_server_url, event_loop) + + +@pytest.mark.asyncio +async def test_resp_queue_removed_from_conn(connection): + async with connection: + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': '1 + 1'}) + stream = await connection.submit(message) + async for msg in stream: + pass + await asyncio.sleep(0) + assert stream not in list(connection._result_sets.values()) + + +@pytest.mark.asyncio +async def test_stream_done(connection): + async with connection: + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': '1 + 1'}) + stream = await connection.submit(message) + async for msg in stream: + pass + assert stream.done + +@pytest.mark.asyncio +async def test_connection_response_timeout(connection): + async with connection: + connection._response_timeout = 0.0000001 + with pytest.raises(exception.ResponseTimeoutError): + message = request.RequestMessage( + processor='', op='eval', + args={'gremlin': '1 + 1'}) + stream = await connection.submit(message) + async for msg in stream: + pass + + +# @pytest.mark.asyncio +# async def test_authenticated_connection(event_loop, unused_tcp_port): +# authentication_request_queue = asyncio.Queue(loop=event_loop) +# +# username, password = 'test_username', 'test_password' +# +# async def fake_auth(request): +# ws = web.WebSocketResponse() +# await ws.prepare(request) +# +# msg = await ws.receive() +# data = json.loads(msg.data.decode()[17:]) +# await authentication_request_queue.put(data) +# +# auth_resp = { +# "requestId": data["requestId"], +# "status": {"code": 407, "attributes": {}, "message": ""}, +# "result": {"data": None, "meta": {}} +# } +# resp_payload = json.dumps(auth_resp) +# ws.send_str(resp_payload) +# +# auth_msg = await ws.receive() +# auth_msg_data = json.loads(auth_msg.data.decode()[17:]) +# await authentication_request_queue.put(auth_msg_data) +# +# return ws +# +# aiohttp_app = web.Application(loop=event_loop) +# aiohttp_app.router.add_route('GET', '/gremlin', fake_auth) +# handler = aiohttp_app.make_handler() +# srv = await event_loop.create_server(handler, '0.0.0.0', unused_tcp_port) +# +# async with aiohttp.ClientSession(loop=event_loop) as session: +# url = 'ws://0.0.0.0:{}/gremlin'.format(unused_tcp_port) +# async with session.ws_connect(url) as ws_client: +# connection = driver.Connection( +# url=url, ws=ws_client, loop=event_loop, client_session=session, +# username=username, password=password, max_inflight=64, response_timeout=None, +# message_serializer=serializer.GraphSONMessageSerializer, +# provider=provider.TinkerGraph +# ) +# task = event_loop.create_task(connection.submit("1+1")) +# initial_request = await authentication_request_queue.get() +# auth_request = await authentication_request_queue.get() +# print(auth_request) +# auth_str = auth_request['args']['sasl'] +# assert base64.b64decode(auth_str).decode().split('\x00')[1:] == [username, password] +# assert auth_request['requestId'] == initial_request['requestId'] +# resp = await task +# resp.close() +# +# await connection.close() diff --git a/tests/test_connection_protocol.py b/tests/test_connection_protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..96420128e6975db721ed3469150cdd9a4fb3f3c3 --- /dev/null +++ b/tests/test_connection_protocol.py @@ -0,0 +1,152 @@ +# # Copyright 2016 David M. Brown +# # +# # 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 aiogremlin import exception +# from aiogremlin.gremlin_python.driver import request, serializer +# +# +# @pytest.mark.asyncio +# async def test_eval(remote_graph, connection, aliases): +# async with connection: +# connection._message_serializer = serializer.GraphSONMessageSerializer() +# g = remote_graph.traversal() +# traversal = "g.addV('person').property('name', 'leifur')" +# message = request.RequestMessage( +# processor='', op='eval', +# args={'gremlin': message, +# 'aliases': aliases}) +# resp = await connection.submit( +# processor='', op='eval', gremlin=traversal, scriptEvalTimeout=1, aliases=aliases) +# +# async for msg in resp: +# assert msg['label'] == 'person' +# +# +# @pytest.mark.asyncio +# async def test_bytecode(remote_graph, connection, aliases): +# async with connection: +# connection._message_serializer = serializer.GraphSONMessageSerializer() +# g = remote_graph.traversal() +# traversal = g.addV('person').property('name', 'leifur') +# message = request.RequestMessage( +# processor='traversal', op='bytecode', +# args={'gremlin': traversal.bytecode, +# 'aliases': aliases}) +# resp = await connection.submit(message) +# async for msg in resp: +# vid = msg.id +# traversal = g.V(vid).label() +# message = request.RequestMessage( +# processor='traversal', op='bytecode', +# args={'gremlin': traversal.bytecode, +# 'aliases': aliases}) +# resp = await connection.submit(message) +# async for msg in resp: +# assert msg == 'person' +# traversal = g.V(vid).name +# message = request.RequestMessage( +# processor='traversal', op='bytecode', +# args={'gremlin': traversal.bytecode, +# 'aliases': aliases}) +# resp = await connection.submit(message) +# async for msg in resp: +# assert msg == 'leifur' +# +# +# @pytest.mark.asyncio +# async def test_side_effects(remote_graph, connection, aliases): +# async with connection: +# connection._message_serializer = serializer.GraphSONMessageSerializer() +# 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, aliases=aliases) +# async for msg in resp: +# pass +# traversal = g.addV('person').property('name', 'dave') +# resp = await connection.submit( +# processor='traversal', op='bytecode', gremlin=traversal.bytecode, aliases=aliases) +# async for msg in resp: +# pass +# traversal = g.addV('person').property('name', 'jonathan') +# resp = await connection.submit( +# processor='traversal', op='bytecode', gremlin=traversal.bytecode, aliases=aliases) +# 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, aliases=aliases) +# request_id = resp.request_id +# async for msg in resp: +# pass +# resp = await connection.submit(processor='traversal', op='keys', +# sideEffect=request_id, aliases=aliases) +# 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', aliases=aliases) +# 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, aliases): +# async with connection: +# connection._message_serializer = serializer.GraphSONMessageSerializer() +# 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, +# aliases=aliases) +# async for msg in resp: +# assert msg['label'] == 'person' +# resp = await connection.submit( +# gremlin="v.values('name')", +# processor='session', +# op='eval', +# session=session, +# aliases=aliases) +# 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_gremlin_python/__init__.py b/tests/test_gremlin_python/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6df110081846e563a662bdf254f6640f35d621b2 --- /dev/null +++ b/tests/test_gremlin_python/__init__.py @@ -0,0 +1,20 @@ +''' +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)' \ No newline at end of file diff --git a/tests/test_gremlin_python/conftest.py b/tests/test_gremlin_python/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..4bac4aec88f7bcb82da51f15d7089e5b25b8918b --- /dev/null +++ b/tests/test_gremlin_python/conftest.py @@ -0,0 +1,46 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import pytest + +from aiogremlin.remote.driver_remote_connection import ( + DriverRemoteConnection) +from aiogremlin.driver.protocol import GremlinServerWSProtocol +from aiogremlin.driver.aiohttp.transport import AiohttpTransport +from aiogremlin.gremlin_python.driver.serializer import GraphSONMessageSerializer + + +@pytest.fixture +def client(event_loop, cluster): + try: + client = event_loop.run_until_complete(cluster.connect()) + except OSError: + pytest.skip('Gremlin Server is not running') + else: + return client + +@pytest.fixture +def remote_connection(event_loop, gremlin_url): + try: + remote_conn = event_loop.run_until_complete( + DriverRemoteConnection.open(gremlin_url, 'g')) + except OSError: + pytest.skip('Gremlin Server is not running') + else: + return remote_conn diff --git a/tests/test_gremlin_python/driver/__init__.py b/tests/test_gremlin_python/driver/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6df110081846e563a662bdf254f6640f35d621b2 --- /dev/null +++ b/tests/test_gremlin_python/driver/__init__.py @@ -0,0 +1,20 @@ +''' +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)' \ No newline at end of file diff --git a/tests/test_gremlin_python/driver/test_client.py b/tests/test_gremlin_python/driver/test_client.py new file mode 100644 index 0000000000000000000000000000000000000000..da0f9dc5cc1ae85be6beaf90d162a74f165a230e --- /dev/null +++ b/tests/test_gremlin_python/driver/test_client.py @@ -0,0 +1,83 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import pytest + +from aiogremlin.gremlin_python.driver.request import RequestMessage +from aiogremlin.gremlin_python.structure.graph import Graph + +__author__ = 'David M. Brown (davebshow@gmail.com)' + + +@pytest.mark.asyncio +async def test_connection(connection): + g = Graph().traversal() + t = g.V() + message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode}) + results_set = await connection.write(message) + results = await results_set.all() + assert len(results) == 6 + assert isinstance(results, list) + await connection.close() + + +@pytest.mark.asyncio +async def test_client_simple_eval(client): + result_set = await client.submit('1 + 1') + results = await result_set.all() + assert results[0] == 2 + await client.close() + + +@pytest.mark.asyncio +async def test_client_simple_eval_bindings(client): + result_set = await client.submit('x + x', {'x': 2}) + results = await result_set.all() + assert results[0] == 4 + await client.close() + +@pytest.mark.asyncio +async def test_client_eval_traversal(client): + result_set = await client.submit('g.V()') + results = await result_set.all() + assert len(results) == 6 + await client.close() + + +@pytest.mark.asyncio +async def test_client_bytecode(client): + g = Graph().traversal() + t = g.V() + message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode}) + result_set = await client.submit(message) + results = await result_set.all() + assert len(results) == 6 + await client.close() + +@pytest.mark.asyncio +async def test_iterate_result_set(client): + g = Graph().traversal() + t = g.V() + message = RequestMessage('traversal', 'bytecode', {'gremlin': t.bytecode}) + result_set = await client.submit(message) + results = [] + async for result in result_set: + results.append(result) + assert len(results) == 6 + await client.close() diff --git a/tests/test_gremlin_python/driver/test_driver_remote_connection.py b/tests/test_gremlin_python/driver/test_driver_remote_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..911c1044bb90c9810d74a33778603a125eda3436 --- /dev/null +++ b/tests/test_gremlin_python/driver/test_driver_remote_connection.py @@ -0,0 +1,189 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +import pytest + +from aiogremlin.gremlin_python import statics +from aiogremlin.gremlin_python.statics import long +from aiogremlin.remote.driver_remote_connection import ( + DriverRemoteConnection) +from aiogremlin.gremlin_python.process.traversal import Traverser +from aiogremlin.gremlin_python.process.traversal import TraversalStrategy +from aiogremlin.gremlin_python.process.graph_traversal import __ +from aiogremlin.gremlin_python.structure.graph import Graph +from aiogremlin.gremlin_python.structure.graph import Vertex +from aiogremlin.gremlin_python.process.strategies import SubgraphStrategy + +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + + +class TestDriverRemoteConnection(object): + + @pytest.mark.asyncio + async def test_traversals(self, remote_connection): + statics.load_statics(globals()) + g = Graph().traversal().withRemote(remote_connection) + result = await g.V().count().toList() + assert long(6) == result[0] + # # + assert Vertex(1) == await g.V(1).next() + assert 1 == await g.V(1).id().next() + assert Traverser(Vertex(1)) == await g.V(1).nextTraverser() + result = await g.V(1).toList() + assert 1 == len(result) + result = await g.V(1).toList() + assert isinstance(result, list) + results = g.V().repeat(out()).times(2).name + results = await results.toList() + assert 2 == len(results) + assert "lop" in results + assert "ripple" in results + # # # + assert 10 == await g.V().repeat(both()).times(5)[0:10].count().next() + assert 1 == await g.V().repeat(both()).times(5)[0:1].count().next() + assert 0 == await g.V().repeat(both()).times(5)[0:0].count().next() + assert 4 == await g.V()[2:].count().next() + assert 2 == await g.V()[:2].count().next() + # # # + results = await g.withSideEffect('a',['josh','peter']).V(1).out('created').in_('created').values('name').where(within('a')).toList() + assert 2 == len(results) + assert 'josh' in results + assert 'peter' in results + # # # todo: need a traversal metrics deserializer + # g.V().out().profile().next() + await remote_connection.close() + + @pytest.mark.asyncio + async def test_strategies(self, remote_connection): + statics.load_statics(globals()) + # + g = Graph().traversal().withRemote(remote_connection). \ + withStrategies(TraversalStrategy("SubgraphStrategy", + {"vertices": __.hasLabel("person"), + "edges": __.hasLabel("created")})) + assert 4 == await g.V().count().next() + assert 0 == await g.E().count().next() + assert 1 == await g.V().label().dedup().count().next() + assert "person" == await g.V().label().dedup().next() + # + g = Graph().traversal().withRemote(remote_connection). \ + withStrategies(SubgraphStrategy(vertices=__.hasLabel("person"), edges=__.hasLabel("created"))) + assert 4 == await g.V().count().next() + assert 0 == await g.E().count().next() + assert 1 == await g.V().label().dedup().count().next() + assert "person" == await g.V().label().dedup().next() + # + g = g.withoutStrategies(SubgraphStrategy). \ + withComputer(vertices=__.has("name", "marko"), edges=__.limit(0)) + assert 1 == await g.V().count().next() + assert 0 == await g.E().count().next() + assert "person" == await g.V().label().next() + assert "marko" == await g.V().name.next() + # + g = Graph().traversal().withRemote(remote_connection).withComputer() + assert 6 == await g.V().count().next() + assert 6 == await g.E().count().next() + await remote_connection.close() + + @pytest.mark.asyncio + async def test_side_effects(self, remote_connection): + statics.load_statics(globals()) + g = Graph().traversal().withRemote(remote_connection) + t = await g.V().hasLabel("project").name.iterate() + keys = await t.side_effects.keys() + assert 0 == len(keys) + with pytest.raises(Exception): + m = await t.side_effects["m"] + t = g.V().out("created").groupCount("m").by("name") + results = await t.toSet() + assert 2 == len(results) + assert Vertex(3) in results + assert Vertex(5) in results + keys = await t.side_effects.keys() + assert 1 == len(keys) + assert "m" in keys + m = await t.side_effects["m"] + assert isinstance(m, dict) + assert 2 == len(m) + assert 3 == m["lop"] + assert 1 == m["ripple"] + assert isinstance(m["lop"], long) + assert isinstance(m["ripple"], long) + # ## + t = g.V().out("created").groupCount("m").by("name").name.aggregate("n") + results = await t.toSet() + assert 2 == len(results) + assert "lop" in results + assert "ripple" in results + keys = await t.side_effects.keys() + assert 2 == len(keys) + assert "m" in keys + assert "n" in keys + n = await t.side_effects.get("n") + assert isinstance(n, dict) + assert 2 == len(n) + assert "lop" in n.keys() + assert "ripple" in n.keys() + assert 3 == n["lop"] + assert 1 == n["ripple"] + # + t = g.withSideEffect('m', 32).V().map(lambda: "x: x.sideEffects('m')") + results = await t.toSet() + assert 1 == len(results) + assert 32 == list(results)[0] + assert 32 == await t.side_effects['m'] + keys = await t.side_effects.keys() + assert 1 == len(keys) + with pytest.raises(Exception): + x = await t.side_effects["x"] + await remote_connection.close() + + @pytest.mark.asyncio + async def test_side_effect_close(self, remote_connection): + g = Graph().traversal().withRemote(remote_connection) + t = g.V().aggregate('a').aggregate('b') + await t.iterate() + + # The 'a' key should return some side effects + results = await t.side_effects.get('a') + assert results + + # Close result is None + results = await t.side_effects.close() + assert not results + + # Shouldn't get any new info from server + # 'b' isn't in local cache + results = await t.side_effects.get('b') + assert not results + + # But 'a' should still be cached locally + results = await t.side_effects.get('a') + assert results + + # 'a' should have been added to local keys cache, but not 'b' + results = await t.side_effects.keys() + assert len(results) == 1 + a, = results + assert a == 'a' + + # Try to get 'b' directly from server, should throw error + with pytest.raises(Exception): + await t.side_effects._get('b') + await remote_connection.close() diff --git a/tests/test_gremlin_python/process/__init__.py b/tests/test_gremlin_python/process/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6df110081846e563a662bdf254f6640f35d621b2 --- /dev/null +++ b/tests/test_gremlin_python/process/__init__.py @@ -0,0 +1,20 @@ +''' +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)' \ No newline at end of file diff --git a/tests/test_gremlin_python/process/test_strategies.py b/tests/test_gremlin_python/process/test_strategies.py new file mode 100644 index 0000000000000000000000000000000000000000..133f46f9b3ff06dcb9ad845ab81347d4ddfc0aae --- /dev/null +++ b/tests/test_gremlin_python/process/test_strategies.py @@ -0,0 +1,106 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import unittest +from unittest import TestCase + +from aiogremlin.gremlin_python.structure.graph import Graph +from aiogremlin.gremlin_python.process.strategies import * +from aiogremlin.gremlin_python.process.graph_traversal import __ + + +class TestTraversalStrategies(TestCase): + def test_singletons(self): + g = Graph().traversal() + bytecode = g.withStrategies(ReadOnlyStrategy()).bytecode + assert 1 == len(bytecode.source_instructions) + assert 2 == len(bytecode.source_instructions[0]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[0][1] + assert "ReadOnlyStrategy" == str(bytecode.source_instructions[0][1]) + assert hash(ReadOnlyStrategy()) == hash(bytecode.source_instructions[0][1]) + assert 0 == len(g.traversal_strategies.traversal_strategies) # these strategies are proxies + ## + g = g.withStrategies(ReadOnlyStrategy(), IncidentToAdjacentStrategy()) + bytecode = g.bytecode + assert 1 == len(bytecode.source_instructions) + assert 3 == len(bytecode.source_instructions[0]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[0][1] + assert IncidentToAdjacentStrategy() == bytecode.source_instructions[0][2] + ## + bytecode = g.V().bytecode + assert 1 == len(bytecode.source_instructions) + assert 3 == len(bytecode.source_instructions[0]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[0][1] + assert IncidentToAdjacentStrategy() == bytecode.source_instructions[0][2] + assert 1 == len(bytecode.step_instructions) + assert "V" == bytecode.step_instructions[0][0] + ## + bytecode = g.withoutStrategies(ReadOnlyStrategy()).V().bytecode + assert 2 == len(bytecode.source_instructions) + assert 3 == len(bytecode.source_instructions[0]) + assert 2 == len(bytecode.source_instructions[1]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[0][1] + assert IncidentToAdjacentStrategy() == bytecode.source_instructions[0][2] + assert "withoutStrategies" == bytecode.source_instructions[1][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[1][1] + assert 1 == len(bytecode.step_instructions) + assert "V" == bytecode.step_instructions[0][0] + ## + bytecode = g.withoutStrategies(ReadOnlyStrategy(), LazyBarrierStrategy()).V().bytecode + assert 2 == len(bytecode.source_instructions) + assert 3 == len(bytecode.source_instructions[0]) + assert 3 == len(bytecode.source_instructions[1]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[0][1] + assert IncidentToAdjacentStrategy() == bytecode.source_instructions[0][2] + assert "withoutStrategies" == bytecode.source_instructions[1][0] + assert ReadOnlyStrategy() == bytecode.source_instructions[1][1] + assert LazyBarrierStrategy() == bytecode.source_instructions[1][2] + assert 1 == len(bytecode.step_instructions) + assert "V" == bytecode.step_instructions[0][0] + + def test_configurable(self): + g = Graph().traversal() + bytecode = g.withStrategies(MatchAlgorithmStrategy("greedy")).bytecode + assert 1 == len(bytecode.source_instructions) + assert 2 == len(bytecode.source_instructions[0]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert MatchAlgorithmStrategy() == bytecode.source_instructions[0][1] + assert "MatchAlgorithmStrategy" == str(bytecode.source_instructions[0][1]) + assert hash(MatchAlgorithmStrategy()) == hash( + bytecode.source_instructions[0][1]) # even though different confs, same strategy + assert 0 == len(g.traversal_strategies.traversal_strategies) # these strategies are proxies + ### + bytecode = g.withStrategies(SubgraphStrategy(vertices=__.has("name","marko"))).bytecode + assert 1 == len(bytecode.source_instructions) + assert 2 == len(bytecode.source_instructions[0]) + assert "withStrategies" == bytecode.source_instructions[0][0] + assert SubgraphStrategy() == bytecode.source_instructions[0][1] + strategy = bytecode.source_instructions[0][1] + assert 1 == len(strategy.configuration) + assert __.has("name","marko") == strategy.configuration["vertices"] + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_gremlin_python/process/test_traversal.py b/tests/test_gremlin_python/process/test_traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..325a7c914f098fedd3c8c5f01d9a5a515a241051 --- /dev/null +++ b/tests/test_gremlin_python/process/test_traversal.py @@ -0,0 +1,91 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import unittest +from unittest import TestCase + +from aiogremlin.gremlin_python.structure.graph import Graph +from aiogremlin.gremlin_python.process.traversal import P +from aiogremlin.gremlin_python.process.traversal import Binding +from aiogremlin.gremlin_python.process.graph_traversal import __ + + +class TestTraversal(TestCase): + def test_bytecode(self): + g = Graph().traversal() + bytecode = g.V().out("created").bytecode + assert 0 == len(bytecode.bindings.keys()) + assert 0 == len(bytecode.source_instructions) + assert 2 == len(bytecode.step_instructions) + assert "V" == bytecode.step_instructions[0][0] + assert "out" == bytecode.step_instructions[1][0] + assert "created" == bytecode.step_instructions[1][1] + assert 1 == len(bytecode.step_instructions[0]) + assert 2 == len(bytecode.step_instructions[1]) + ## + bytecode = g.withSack(1).E().groupCount().by("weight").bytecode + assert 0 == len(bytecode.bindings.keys()) + assert 1 == len(bytecode.source_instructions) + assert "withSack" == bytecode.source_instructions[0][0] + assert 1 == bytecode.source_instructions[0][1] + assert 3 == len(bytecode.step_instructions) + assert "E" == bytecode.step_instructions[0][0] + assert "groupCount" == bytecode.step_instructions[1][0] + assert "by" == bytecode.step_instructions[2][0] + assert "weight" == bytecode.step_instructions[2][1] + assert 1 == len(bytecode.step_instructions[0]) + assert 1 == len(bytecode.step_instructions[1]) + assert 2 == len(bytecode.step_instructions[2]) + ## + bytecode = g.V(('a',[1,2,3])).out(('b','created')).where(__.in_(('c','created'),('d','knows')).count().is_(('e',P.gt(2)))).bytecode + assert 5 == len(bytecode.bindings.keys()) + assert [1,2,3] == bytecode.bindings['a'] + assert 'created' == bytecode.bindings['b'] + assert 'created' == bytecode.bindings['c'] + assert 'knows' == bytecode.bindings['d'] + assert P.gt(2) == bytecode.bindings['e'] + assert Binding('b','created') == bytecode.step_instructions[1][1] + assert 'binding[b=created]' == str(bytecode.step_instructions[1][1]) + assert isinstance(hash(bytecode.step_instructions[1][1]),int) + + def test_P(self): + # verify that the order of operations is respected + assert "and(eq(a),lt(b))" == str(P.eq("a").and_(P.lt("b"))) + assert "and(or(lt(b),gt(c)),neq(d))" == str(P.lt("b").or_(P.gt("c")).and_(P.neq("d"))) + assert "and(or(lt(b),gt(c)),or(neq(d),gte(e)))" == str( + P.lt("b").or_(P.gt("c")).and_(P.neq("d").or_(P.gte("e")))) + + def test_anonymous_traversal(self): + bytecode = __.__(1).bytecode + assert 0 == len(bytecode.bindings.keys()) + assert 0 == len(bytecode.source_instructions) + assert 1 == len(bytecode.step_instructions) + assert "inject" == bytecode.step_instructions[0][0] + assert 1 == bytecode.step_instructions[0][1] + ## + bytecode = __.start().bytecode + assert 0 == len(bytecode.bindings.keys()) + assert 0 == len(bytecode.source_instructions) + assert 0 == len(bytecode.step_instructions) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_gremlin_python/structure/__init__.py b/tests/test_gremlin_python/structure/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6df110081846e563a662bdf254f6640f35d621b2 --- /dev/null +++ b/tests/test_gremlin_python/structure/__init__.py @@ -0,0 +1,20 @@ +''' +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)' \ No newline at end of file diff --git a/tests/test_gremlin_python/structure/io/__init__.py b/tests/test_gremlin_python/structure/io/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6df110081846e563a662bdf254f6640f35d621b2 --- /dev/null +++ b/tests/test_gremlin_python/structure/io/__init__.py @@ -0,0 +1,20 @@ +''' +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)' \ No newline at end of file diff --git a/tests/test_gremlin_python/structure/io/test_graphson.py b/tests/test_gremlin_python/structure/io/test_graphson.py new file mode 100644 index 0000000000000000000000000000000000000000..6c88a2d8d0e5940b3652ee5e5bcf25dc27630dfb --- /dev/null +++ b/tests/test_gremlin_python/structure/io/test_graphson.py @@ -0,0 +1,206 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import json +from mock import Mock +import unittest +from unittest import TestCase + +from aiogremlin.gremlin_python.statics import * +from aiogremlin.gremlin_python.structure.graph import Vertex +from aiogremlin.gremlin_python.structure.graph import Path +from aiogremlin.gremlin_python.structure.io.graphson import GraphSONWriter, GraphSONReader, GraphSONUtil +import aiogremlin.gremlin_python.structure.io.graphson +from aiogremlin.gremlin_python.process.traversal import P +from aiogremlin.gremlin_python.process.strategies import SubgraphStrategy +from aiogremlin.gremlin_python.process.graph_traversal import __ + + +class TestGraphSONReader(TestCase): + graphson_reader = GraphSONReader() + + def test_number_input(self): + x = self.graphson_reader.readObject(json.dumps({ + "@type": "g:Int32", + "@value": 31 + })) + assert isinstance(x, int) + assert 31 == x + ## + x = self.graphson_reader.readObject(json.dumps({ + "@type": "g:Int64", + "@value": 31 + })) + assert isinstance(x, long) + assert long(31) == x + ## + x = self.graphson_reader.readObject(json.dumps({ + "@type": "g:Float", + "@value": 31.3 + })) + assert isinstance(x, float) + assert 31.3 == x + ## + x = self.graphson_reader.readObject(json.dumps({ + "@type": "g:Double", + "@value": 31.2 + })) + assert isinstance(x, float) + assert 31.2 == x + + def test_graph(self): + vertex = self.graphson_reader.readObject( + """{"@type":"g:Vertex", "@value":{"id":{"@type":"g:Int32","@value":1},"label":"person","outE":{"created":[{"id":{"@type":"g:Int32","@value":9},"inV":{"@type":"g:Int32","@value":3},"properties":{"weight":{"@type":"g:Double","@value":0.4}}}],"knows":[{"id":{"@type":"g:Int32","@value":7},"inV":{"@type":"g:Int32","@value":2},"properties":{"weight":{"@type":"g:Double","@value":0.5}}},{"id":{"@type":"g:Int32","@value":8},"inV":{"@type":"g:Int32","@value":4},"properties":{"weight":{"@type":"g:Double","@value":1.0}}}]},"properties":{"name":[{"id":{"@type":"g:Int64","@value":0},"value":"marko"}],"age":[{"id":{"@type":"g:Int64","@value":1},"value":{"@type":"g:Int32","@value":29}}]}}}""") + assert isinstance(vertex, Vertex) + assert "person" == vertex.label + assert 1 == vertex.id + assert isinstance(vertex.id, int) + assert vertex == Vertex(1) + + def test_path(self): + path = self.graphson_reader.readObject( + """{"@type":"g:Path","@value":{"labels":[["a"],["b","c"],[]],"objects":[{"@type":"g:Vertex","@value":{"id":{"@type":"g:Int32","@value":1},"label":"person","properties":{"name":[{"@type":"g:VertexProperty","@value":{"id":{"@type":"g:Int64","@value":0},"value":"marko","label":"name"}}],"age":[{"@type":"g:VertexProperty","@value":{"id":{"@type":"g:Int64","@value":1},"value":{"@type":"g:Int32","@value":29},"label":"age"}}]}}},{"@type":"g:Vertex","@value":{"id":{"@type":"g:Int32","@value":3},"label":"software","properties":{"name":[{"@type":"g:VertexProperty","@value":{"id":{"@type":"g:Int64","@value":4},"value":"lop","label":"name"}}],"lang":[{"@type":"g:VertexProperty","@value":{"id":{"@type":"g:Int64","@value":5},"value":"java","label":"lang"}}]}}},"lop"]}}""" + ) + assert isinstance(path, Path) + assert "[v[1], v[3], 'lop']" == str(path) + assert Vertex(1) == path[0] + assert Vertex(1) == path["a"] + assert "lop" == path[2] + assert 3 == len(path) + + def test_custom_mapping(self): + + # extended mapping + class X(object): + pass + + type_string = "test:Xtype" + override_string = "g:Int64" + serdes = Mock() + + reader = GraphSONReader(deserializer_map={type_string: serdes}) + assert type_string in reader.deserializers + + # base dicts are not modified + assert type_string not in aiogremlin.gremlin_python.structure.io.graphson._deserializers + + x = X() + o = reader.toObject({GraphSONUtil.TYPE_KEY: type_string, GraphSONUtil.VALUE_KEY: x}) + serdes.objectify.assert_called_once_with(x, reader) + assert o is serdes.objectify() + + # overridden mapping + type_string = "g:Int64" + serdes = Mock() + reader = GraphSONReader(deserializer_map={type_string: serdes, override_string: serdes}) + assert aiogremlin.gremlin_python.structure.io.graphson._deserializers[type_string] is not reader.deserializers[type_string] + + value = 3 + o = reader.toObject({GraphSONUtil.TYPE_KEY: type_string, GraphSONUtil.VALUE_KEY: value}) + serdes.objectify.assert_called_once_with(value, reader) + assert o is serdes.objectify() + + +class TestGraphSONWriter(TestCase): + + graphson_writer = GraphSONWriter() + + def test_number_output(self): + assert {"@type":"g:Int64","@value":2} == json.loads(self.graphson_writer.writeObject(long(2))) + assert {"@type":"g:Int32","@value":1} == json.loads(self.graphson_writer.writeObject(1)) + assert {"@type":"g:Double","@value":3.2} == json.loads(self.graphson_writer.writeObject(3.2)) + assert """true""" == self.graphson_writer.writeObject(True) + + def test_numbers(self): + assert {"@type": "g:Int64", "@value": 2} == json.loads(self.graphson_writer.writeObject(long(2))) + assert {"@type": "g:Int32", "@value": 1} == json.loads(self.graphson_writer.writeObject(1)) + assert {"@type": "g:Double", "@value": 3.2} == json.loads(self.graphson_writer.writeObject(3.2)) + assert """true""" == self.graphson_writer.writeObject(True) + + def test_P(self): + result = {'@type': 'g:P', + '@value': { + 'predicate': 'and', + 'value': [{ + '@type': 'g:P', + '@value': { + 'predicate': 'or', + 'value': [{ + '@type': 'g:P', + '@value': {'predicate': 'lt', 'value': 'b'} + }, + {'@type': 'g:P', '@value': {'predicate': 'gt', 'value': 'c'}} + ] + } + }, + {'@type': 'g:P', '@value': {'predicate': 'neq', 'value': 'd'}}]}} + + assert result == json.loads( + self.graphson_writer.writeObject(P.lt("b").or_(P.gt("c")).and_(P.neq("d")))) + + def test_strategies(self): + # we have a proxy model for now given that we don't want to have to have g:XXX all registered on the Gremlin traversal machine (yet) + assert {"@type": "g:SubgraphStrategy", "@value": {}} == json.loads(self.graphson_writer.writeObject(SubgraphStrategy)) + assert {"@type": "g:SubgraphStrategy", "@value": { + "vertices": {"@type": "g:Bytecode", "@value": {"step": [["has", "name", "marko"]]}}}} == json.loads( + self.graphson_writer.writeObject(SubgraphStrategy(vertices=__.has("name", "marko")))) + + def test_custom_mapping(self): + + # extended mapping + class X(object): + pass + + serdes = Mock() + writer = GraphSONWriter(serializer_map={X: serdes}) + assert X in writer.serializers + + # base dicts are not modified + assert X not in aiogremlin.gremlin_python.structure.io.graphson._serializers + + obj = X() + d = writer.toDict(obj) + serdes.dictify.assert_called_once_with(obj, writer) + assert d is serdes.dictify() + + # overridden mapping + serdes = Mock() + writer = GraphSONWriter(serializer_map={int: serdes}) + assert aiogremlin.gremlin_python.structure.io.graphson._serializers[int] is not writer.serializers[int] + + value = 3 + d = writer.toDict(value) + serdes.dictify.assert_called_once_with(value, writer) + assert d is serdes.dictify() + + def test_write_long(self): + + mapping = self.graphson_writer.toDict(1) + assert mapping['@type'] == 'g:Int32' + assert mapping['@value'] == 1 + + mapping = self.graphson_writer.toDict(long(1)) + assert mapping['@type'] == 'g:Int64' + assert mapping['@value'] == 1 + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_gremlin_python/structure/test_graph.py b/tests/test_gremlin_python/structure/test_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..98b48ba12b69c7b86c5ca4e62a27547675afdb08 --- /dev/null +++ b/tests/test_gremlin_python/structure/test_graph.py @@ -0,0 +1,116 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import unittest +from unittest import TestCase + +from aiogremlin.gremlin_python.statics import long +from aiogremlin.gremlin_python.structure.graph import Edge +from aiogremlin.gremlin_python.structure.graph import Property +from aiogremlin.gremlin_python.structure.graph import Vertex +from aiogremlin.gremlin_python.structure.graph import VertexProperty +from aiogremlin.gremlin_python.structure.graph import Path + + +class TestGraph(TestCase): + def test_graph_objects(self): + vertex = Vertex(1) + assert "v[1]" == str(vertex) + assert "vertex" == vertex.label + assert "person" == Vertex(1, "person").label + assert vertex == Vertex(1) + # + edge = Edge(2, Vertex(1), "said", Vertex("hello", "phrase")) + assert "e[2][1-said->hello]" == str(edge) + assert Vertex(1) == edge.outV + assert Vertex("hello") == edge.inV + assert "said" == edge.label + assert "phrase" == edge.inV.label + assert edge.inV != edge.outV + # + vertex_property = VertexProperty(long(24), "name", "marko") + assert "vp[name->marko]" == str(vertex_property) + assert "name" == vertex_property.label + assert "name" == vertex_property.key + assert "marko" == vertex_property.value + assert long(24) == vertex_property.id + assert isinstance(vertex_property.id, long) + assert vertex_property == VertexProperty(long(24), "name", "marko") + # + property = Property("age", 29) + assert "p[age->29]" == str(property) + assert "age" == property.key + assert 29 == property.value + assert isinstance(property.value, int) + assert property == Property("age", 29) + # + for i in [vertex, edge, vertex_property, property]: + for j in [vertex, edge, vertex_property, property]: + if type(i) != type(j): + assert i != j + else: + assert i == j + assert i.__hash__() == hash(i) + + def test_path(self): + path = Path([set(["a", "b"]), set(["c", "b"]), set([])], [1, Vertex(1), "hello"]) + assert "[1, v[1], 'hello']" == str(path) + assert 1 == path["a"] + assert Vertex(1) == path["c"] + assert [1, Vertex(1)] == path["b"] + assert path[0] == 1 + assert path[1] == Vertex(1) + assert path[2] == "hello" + assert 3 == len(path) + assert "hello" in path + assert "goodbye" not in path + assert Vertex(1) in path + assert Vertex(123) not in path + # + try: + temp = path[3] + raise Exception("Accessing beyond the list index should throw an index error") + except IndexError: + pass + # + try: + temp = path["zz"] + raise Exception("Accessing nothing should throw a key error") + except KeyError: + pass + # + try: + temp = path[1:2] + raise Exception("Accessing using slices should throw a type error") + except TypeError: + pass + # + assert path == path + assert hash(path) == hash(path) + path2 = Path([set(["a", "b"]), set(["c", "b"]), set([])], [1, Vertex(1), "hello"]) + assert path == path2 + assert hash(path) == hash(path2) + assert path != Path([set(["a"]), set(["c", "b"]), set([])], [1, Vertex(1), "hello"]) + assert path != Path([set(["a", "b"]), set(["c", "b"]), set([])], [3, Vertex(1), "hello"]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_gremlin_python/test_statics.py b/tests/test_gremlin_python/test_statics.py new file mode 100644 index 0000000000000000000000000000000000000000..ea2e993584ba3217f10136cf5db377716a2e88a4 --- /dev/null +++ b/tests/test_gremlin_python/test_statics.py @@ -0,0 +1,46 @@ +''' +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. +''' +'''THIS FILE HAS BEEN MODIFIED BY DAVID M. BROWN TO SUPPORT PEP 492''' +__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' + +import unittest +from unittest import TestCase + +from aiogremlin.gremlin_python import statics +from aiogremlin.gremlin_python.process.traversal import Cardinality +from aiogremlin.gremlin_python.process.traversal import P +from aiogremlin.gremlin_python.process.traversal import Pop + + +class TestStatics(TestCase): + def test_enums(self): + statics.load_statics(globals()) + assert isinstance(list_, Cardinality) + assert list_ is Cardinality.list_ + # + assert isinstance(eq(2), P) + assert eq(2) == P.eq(2) + # + assert isinstance(first, Pop) + assert first == Pop.first + statics.unload_statics(globals()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_pool.py b/tests/test_pool.py new file mode 100644 index 0000000000000000000000000000000000000000..cf1eef666c0b84008feab16eb94b9c0a43d26735 --- /dev/null +++ b/tests/test_pool.py @@ -0,0 +1,105 @@ +# Copyright 2016 David M. Brown +# +# 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 pytest + + +@pytest.mark.asyncio +async def test_pool_init(connection_pool): + await connection_pool.init_pool() + assert len(connection_pool._available) == 1 + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_acquire_release(connection_pool): + conn = await connection_pool.acquire() + assert not len(connection_pool._available) + assert len(connection_pool._acquired) == 1 + assert conn.times_acquired == 1 + connection_pool.release(conn) + assert len(connection_pool._available) == 1 + assert not len(connection_pool._acquired) + assert not conn.times_acquired + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_acquire_multiple(connection_pool): + conn1 = await connection_pool.acquire() + conn2 = await connection_pool.acquire() + assert not conn1 is conn2 + assert len(connection_pool._acquired) == 2 + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_share(connection_pool): + connection_pool._max_conns = 1 + conn1 = await connection_pool.acquire() + conn2 = await connection_pool.acquire() + assert conn1 is conn2 + assert conn1.times_acquired == 2 + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_acquire_multiple_and_share(connection_pool): + connection_pool._max_conns = 2 + connection_pool._max_times_acquired = 2 + conn1 = await connection_pool.acquire() + conn2 = await connection_pool.acquire() + assert not conn1 is conn2 + conn3 = await connection_pool.acquire() + conn4 = await connection_pool.acquire() + assert not conn3 is conn4 + assert conn3 is conn1 + assert conn4 is conn2 + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_max_acquired(connection_pool): + connection_pool._max_conns = 2 + connection_pool._max_times_acquired = 2 + conn1 = await connection_pool.acquire() + conn2 = await connection_pool.acquire() + conn3 = await connection_pool.acquire() + conn4 = await connection_pool.acquire() + with pytest.raises(asyncio.TimeoutError): + await asyncio.wait_for(connection_pool.acquire(), timeout=0.1) + await connection_pool.close() + + +@pytest.mark.asyncio +async def test_release_notify(connection_pool): + connection_pool._max_conns = 2 + connection_pool._max_times_acquired = 2 + conn1 = await connection_pool.acquire() + conn2 = await connection_pool.acquire() + conn3 = await connection_pool.acquire() + conn4 = await connection_pool.acquire() + + async def release(conn): + conn.release() + + results = await asyncio.gather( + *[connection_pool.acquire(), release(conn4)]) + conn4 = results[0] + assert conn4 is conn2 + await connection_pool.close() diff --git a/tests/test_provider_conf.py b/tests/test_provider_conf.py new file mode 100644 index 0000000000000000000000000000000000000000..5e92bbf1a5c7f3047371c47c1f97dab7685a9adf --- /dev/null +++ b/tests/test_provider_conf.py @@ -0,0 +1,144 @@ +# import asyncio +# import uuid +# from unittest import mock +# +# import json +# import pytest +# +# import aiohttp +# from aiohttp import client_ws +# +# import goblin +# from goblin import driver +# from aiogremlin import serializer +# from goblin import provider +# +# request_id = uuid.UUID(int=215449331521667564889692237976543325869, version=4) +# +# +# # based on this handy tip on SO: http://stackoverflow.com/a/29905620/6691423 +# def get_mock_coro(return_value): +# async def mock_coro(*args, **kwargs): +# return return_value +# +# return mock.Mock(wraps=mock_coro) +# +# +# async def mock_receive(): +# message = mock.Mock() +# message.tp = aiohttp.MsgType.close +# return message +# +# +# async def mock_ws_connect(*args, **kwargs): +# mock_client = mock.Mock(spec=client_ws.ClientWebSocketResponse) +# mock_client.closed = False +# mock_client.receive = mock.Mock(wraps=mock_receive) +# mock_client.close = get_mock_coro(None) +# return mock_client +# +# +# class TestProvider(provider.Provider): +# DEFAULT_OP_ARGS = { +# 'standard': { +# 'eval': { +# 'fictional_argument': 'fictional_value' +# }, +# }, +# 'session': { +# 'eval': { +# 'manageTransaction': True +# }, +# +# } +# } +# +# @staticmethod +# def get_hashable_id(val): +# return val +# +# +# def deserialize_json_request(request): +# header_len = request[0] + 1 +# payload = request[header_len:] +# return json.loads(payload.decode()) +# +# +# @pytest.fixture(params=( +# serializer.GraphSONMessageSerializer, +# serializer.GraphSONMessageSerializer +# )) +# def message_serializer(request): +# return request.param +# +# +# @pytest.mark.parametrize('processor_name,key,value', ( +# ('standard', 'fictional_argument', 'fictional_value'), +# ('session', 'manageTransaction', True) +# )) +# def test_get_processor_provider_default_args(processor_name, key, value): +# processor = serializer.GraphSONMessageSerializer.get_processor(TestProvider, processor_name) +# assert processor._default_args == TestProvider.DEFAULT_OP_ARGS[processor_name] +# eval_args = processor.get_op_args('eval', {'gremlin': 'g.V()'}) +# assert eval_args['gremlin'] == 'g.V()' +# assert eval_args[key] == value +# +# +# @pytest.mark.parametrize('processor,key,value', ( +# ('', 'fictional_argument', 'fictional_value'), +# ('session', 'manageTransaction', True) +# )) +# def test_serializer_default_op_args(message_serializer, processor, key, value): +# g = driver.AsyncGraph().traversal() +# traversal = g.V().hasLabel('stuff').has('foo', 'bar') +# serialized_message = message_serializer.serialize_message( +# TestProvider, str(uuid.uuid4()), processor=processor, op='eval', gremlin=traversal.bytecode) +# message = deserialize_json_request(serialized_message) +# assert message['args'][key] == value +# +# +# @pytest.mark.parametrize('processor,key,value', ( +# ('', 'fictional_argument', 'fictional_value'), +# ('session', 'manageTransaction', True) +# )) +# @pytest.mark.asyncio +# async def test_conn_default_op_args(event_loop, monkeypatch, processor, key, value): +# mock_client_session = mock.Mock(spec=aiohttp.ClientSession) +# mock_client_session_instance = mock.Mock(spec=aiohttp.ClientSession) +# mock_client_session.return_value = mock_client_session_instance +# mock_client_session_instance.ws_connect = mock.Mock(wraps=mock_ws_connect) +# mock_client_session_instance.close = get_mock_coro(None) # otherwise awaiting ws.close is an error +# +# monkeypatch.setattr(aiohttp, 'ClientSession', mock_client_session) +# monkeypatch.setattr(uuid, 'uuid4', mock.Mock(return_value=request_id)) +# +# conn = await driver.Connection.open( +# 'some_url', +# event_loop, +# message_serializer=serializer.GraphSONMessageSerializer, +# provider=TestProvider +# ) +# +# resp = await conn.submit( +# gremlin='g.V().hasLabel("foo").count()', processor=processor, op='eval') +# +# submitted_bytes = conn._ws.send_bytes.call_args[0][0] +# submitted_json = submitted_bytes[17:].decode() +# submitted_dict = json.loads(submitted_json) +# +# assert submitted_dict['args'][key] == value +# +# await conn.close() +# resp.close() +# +# +# @pytest.mark.asyncio +# async def test_cluster_conn_provider(event_loop, gremlin_host, gremlin_port): +# cluster = await driver.Cluster.open( +# event_loop, provider=TestProvider, hosts=[gremlin_host], port=gremlin_port) +# assert cluster.config['provider'] == TestProvider +# +# pooled_conn = await cluster.get_connection() +# assert pooled_conn._conn._provider == TestProvider +# +# await cluster.close() diff --git a/tests/test_sasl.py b/tests/test_sasl.py deleted file mode 100644 index 59e403e615452622b427aaaf9029d928ccaf7b34..0000000000000000000000000000000000000000 --- a/tests/test_sasl.py +++ /dev/null @@ -1,264 +0,0 @@ -""" -""" - -import asyncio -import unittest -import uuid -import aiohttp -from aiogremlin import (submit, GremlinConnector, GremlinClient, - GremlinClientSession, GremlinServerError, - GremlinClientWebSocketResponse) - - -class SubmitTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - - def tearDown(self): - self.loop.close() - - def test_submit(self): - - @asyncio.coroutine - def go(): - resp = yield from submit("x + x", url='https://localhost:8182/', - bindings={"x": 4}, loop=self.loop, - username="stephen", password="password") - results = yield from resp.get() - return results - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0], 8) - - def test_rebinding(self): - - @asyncio.coroutine - def go1(): - result = yield from submit("graph2.addVertex()", - url='https://localhost:8182/', - loop=self.loop, username="stephen", - password="password") - resp = yield from result.get() - - try: - self.loop.run_until_complete(go1()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - @asyncio.coroutine - def go2(): - result = yield from submit( - "graph2.addVertex()", url='https://localhost:8182/', - rebindings={"graph2": "graph"}, loop=self.loop, - username="stephen", password="password") - resp = yield from result.get() - self.assertEqual(len(resp), 1) - - try: - self.loop.run_until_complete(go2()) - except GremlinServerError: - print("RELEASE DOES NOT SUPPORT REBINDINGS") - - -class GremlinClientTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - connector = aiohttp.TCPConnector(force_close=False, loop=self.loop, - verify_ssl=False) - - client_session = aiohttp.ClientSession( - connector=connector, loop=self.loop, - ws_response_class=GremlinClientWebSocketResponse) - - self.gc = GremlinClient(url="https://localhost:8182/", loop=self.loop, - username="stephen", password="password", - client_session=client_session) - - def tearDown(self): - self.loop.run_until_complete(self.gc.close()) - self.loop.close() - - def test_connection(self): - - @asyncio.coroutine - def go(): - ws = yield from self.gc._connector.ws_connect(self.gc.url) - self.assertFalse(ws.closed) - yield from ws.close() - - self.loop.run_until_complete(go()) - - def test_execute(self): - - @asyncio.coroutine - def go(): - resp = yield from self.gc.execute("x + x", bindings={"x": 4}) - return resp - - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0], 8) - - def test_sub_waitfor(self): - sub1 = self.gc.execute("x + x", bindings={"x": 1}) - sub2 = self.gc.execute("x + x", bindings={"x": 2}) - sub3 = self.gc.execute("x + x", bindings={"x": 4}) - coro = asyncio.gather(*[asyncio.async(sub1, loop=self.loop), - asyncio.async(sub2, loop=self.loop), - asyncio.async(sub3, loop=self.loop)], - loop=self.loop) - # Here I am looking for resource warnings. - results = self.loop.run_until_complete(coro) - self.assertIsNotNone(results) - - def test_resp_stream(self): - @asyncio.coroutine - def stream_coro(): - results = [] - resp = yield from self.gc.submit("x + x", bindings={"x": 4}) - while True: - f = yield from resp.stream.read() - if f is None: - break - results.append(f) - self.assertEqual(results[0].data[0], 8) - self.loop.run_until_complete(stream_coro()) - - def test_execute_error(self): - execute = self.gc.execute("x + x g.asdfas", bindings={"x": 4}) - try: - self.loop.run_until_complete(execute) - error = False - except: - error = True - self.assertTrue(error) - - def test_rebinding(self): - execute = self.gc.execute("graph2.addVertex()") - try: - self.loop.run_until_complete(execute) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - @asyncio.coroutine - def go(): - result = yield from self.gc.execute( - "graph2.addVertex()", rebindings={"graph2": "graph"}) - self.assertEqual(len(result), 1) - - try: - self.loop.run_until_complete(go()) - except GremlinServerError: - print("RELEASE DOES NOT SUPPORT REBINDINGS") - - -class GremlinClientSessionTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - connector = aiohttp.TCPConnector(force_close=False, loop=self.loop, - verify_ssl=False) - - client_session = aiohttp.ClientSession( - connector=connector, loop=self.loop, - ws_response_class=GremlinClientWebSocketResponse) - - self.gc = GremlinClientSession(url="https://localhost:8182/", - loop=self.loop, - username="stephen", password="password", - client_session=client_session) - - self.script1 = """v=graph.addVertex('name', 'Dave')""" - - self.script2 = "v.property('name')" - - def tearDown(self): - self.loop.run_until_complete(self.gc.close()) - self.loop.close() - - def test_session(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - results = yield from self.gc.execute(self.script2) - return results - - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0]['value'], 'Dave') - - def test_session_reset(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - self.gc.reset_session() - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_session_manual_reset(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - new_sess = str(uuid.uuid4()) - sess = self.gc.reset_session(session=new_sess) - self.assertEqual(sess, new_sess) - self.assertEqual(self.gc.session, new_sess) - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_session_set(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - new_sess = str(uuid.uuid4()) - self.gc.session = new_sess - self.assertEqual(self.gc.session, new_sess) - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_resp_session(self): - - @asyncio.coroutine - def go(): - session = str(uuid.uuid4()) - self.gc.session = session - resp = yield from self.gc.submit("x + x", bindings={"x": 4}) - while True: - f = yield from resp.stream.read() - if f is None: - break - self.assertEqual(resp.session, session) - - self.loop.run_until_complete(go()) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/tests.py b/tests/tests.py deleted file mode 100644 index b823e7dbd0e13d31187c0579461909a70b677bd7..0000000000000000000000000000000000000000 --- a/tests/tests.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -""" - -import asyncio -import unittest -import uuid -import aiohttp -from aiogremlin import (submit, GremlinConnector, GremlinClient, - GremlinClientSession, GremlinServerError, - GremlinClientWebSocketResponse) - - -class SubmitTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - - def tearDown(self): - self.loop.close() - - def test_submit(self): - - @asyncio.coroutine - def go(): - resp = yield from submit("x + x", url='http://localhost:8182/', - bindings={"x": 4}, loop=self.loop, - username="stephen", password="password") - results = yield from resp.get() - return results - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0], 8) - - def test_rebinding(self): - - @asyncio.coroutine - def go1(): - result = yield from submit("graph2.addVertex()", - url='http://localhost:8182/', - loop=self.loop, username="stephen", - password="password") - resp = yield from result.get() - - try: - self.loop.run_until_complete(go1()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - @asyncio.coroutine - def go2(): - result = yield from submit( - "graph2.addVertex()", url='http://localhost:8182/', - rebindings={"graph2": "graph"}, loop=self.loop, - username="stephen", password="password") - resp = yield from result.get() - self.assertEqual(len(resp), 1) - - try: - self.loop.run_until_complete(go2()) - except GremlinServerError: - print("RELEASE DOES NOT SUPPORT REBINDINGS") - - -class GremlinClientTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - - self.gc = GremlinClient(url="http://localhost:8182/", loop=self.loop) - - def tearDown(self): - self.loop.run_until_complete(self.gc.close()) - self.loop.close() - - def test_connection(self): - - @asyncio.coroutine - def go(): - ws = yield from self.gc._connector.ws_connect(self.gc.url) - self.assertFalse(ws.closed) - yield from ws.close() - - self.loop.run_until_complete(go()) - - def test_execute(self): - - @asyncio.coroutine - def go(): - resp = yield from self.gc.execute("x + x", bindings={"x": 4}) - return resp - - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0], 8) - - def test_sub_waitfor(self): - sub1 = self.gc.execute("x + x", bindings={"x": 1}) - sub2 = self.gc.execute("x + x", bindings={"x": 2}) - sub3 = self.gc.execute("x + x", bindings={"x": 4}) - coro = asyncio.gather(*[asyncio.async(sub1, loop=self.loop), - asyncio.async(sub2, loop=self.loop), - asyncio.async(sub3, loop=self.loop)], - loop=self.loop) - # Here I am looking for resource warnings. - results = self.loop.run_until_complete(coro) - self.assertIsNotNone(results) - - def test_resp_stream(self): - @asyncio.coroutine - def stream_coro(): - results = [] - resp = yield from self.gc.submit("x + x", bindings={"x": 4}) - while True: - f = yield from resp.stream.read() - if f is None: - break - results.append(f) - self.assertEqual(results[0].data[0], 8) - self.loop.run_until_complete(stream_coro()) - - def test_execute_error(self): - execute = self.gc.execute("x + x g.asdfas", bindings={"x": 4}) - try: - self.loop.run_until_complete(execute) - error = False - except: - error = True - self.assertTrue(error) - - def test_rebinding(self): - execute = self.gc.execute("graph2.addVertex()") - try: - self.loop.run_until_complete(execute) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - @asyncio.coroutine - def go(): - result = yield from self.gc.execute( - "graph2.addVertex()", rebindings={"graph2": "graph"}) - self.assertEqual(len(result), 1) - - try: - self.loop.run_until_complete(go()) - except GremlinServerError: - print("RELEASE DOES NOT SUPPORT REBINDINGS") - - -class GremlinClientSessionTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - self.gc = GremlinClientSession(url="http://localhost:8182/", - loop=self.loop) - - self.script1 = """v=graph.addVertex('name', 'Dave')""" - - self.script2 = "v.property('name')" - - def tearDown(self): - self.loop.run_until_complete(self.gc.close()) - self.loop.close() - - def test_session(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - results = yield from self.gc.execute(self.script2) - return results - - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0]['value'], 'Dave') - - def test_session_reset(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - self.gc.reset_session() - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_session_manual_reset(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - new_sess = str(uuid.uuid4()) - sess = self.gc.reset_session(session=new_sess) - self.assertEqual(sess, new_sess) - self.assertEqual(self.gc.session, new_sess) - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_session_set(self): - - @asyncio.coroutine - def go(): - yield from self.gc.execute(self.script1) - new_sess = str(uuid.uuid4()) - self.gc.session = new_sess - self.assertEqual(self.gc.session, new_sess) - results = yield from self.gc.execute(self.script2) - return results - try: - results = self.loop.run_until_complete(go()) - error = False - except GremlinServerError: - error = True - self.assertTrue(error) - - def test_resp_session(self): - - @asyncio.coroutine - def go(): - session = str(uuid.uuid4()) - self.gc.session = session - resp = yield from self.gc.submit("x + x", bindings={"x": 4}) - while True: - f = yield from resp.stream.read() - if f is None: - break - self.assertEqual(resp.session, session) - - self.loop.run_until_complete(go()) - - -if __name__ == "__main__": - unittest.main()