diff --git a/aiogremlin/client.py b/aiogremlin/client.py index 93a0d5800442ba85e872b448d73f4073aaaf694f..9f8d2e18ba48927e3bd58e5d7e400e1f4943187e 100644 --- a/aiogremlin/client.py +++ b/aiogremlin/client.py @@ -7,119 +7,72 @@ import aiohttp from aiogremlin.response import GremlinClientWebSocketResponse from aiogremlin.exceptions import RequestError -from aiogremlin.log import logger, INFO from aiogremlin.connector import GremlinConnector from aiogremlin.subprotocol import gremlin_response_parser, GremlinWriter -__all__ = ("submit", "SimpleGremlinClient", "GremlinClient", - "GremlinClientSession") +__all__ = ("submit", "GremlinClient", "GremlinClientSession", + "GremlinResponse", "GremlinResponseStream") -class BaseGremlinClient: +class GremlinClient: + """Main interface for interacting with the Gremlin Server. - def __init__(self, *, lang="gremlin-groovy", op="eval", processor="", - loop=None, verbose=False): + :param str url: url for Gremlin Server (optional). 'ws://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 + :param connector: A class that implements the method ``ws_connect``. + Usually an instance of ``aiogremlin.connector.GremlinConnector`` + """ + + def __init__(self, *, url='ws://localhost:8182/', loop=None, + lang="gremlin-groovy", op="eval", processor="", + timeout=None, ws_connector=None): + """ + """ self._lang = lang self._op = op self._processor = processor self._loop = loop or asyncio.get_event_loop() self._closed = False - if verbose: - logger.setLevel(INFO) + self._session = None + self._url = url + self._timeout = timeout + if ws_connector is None: + ws_connector = GremlinConnector(loop=self._loop) + 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 - def submit(self): - raise NotImplementedError - - @asyncio.coroutine - def execute(self, gremlin, *, bindings=None, lang=None, - op=None, processor=None, binary=True): - """ - """ - 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, - op=op, processor=processor, - binary=binary) - - return (yield from resp.get()) - - -class SimpleGremlinClient(BaseGremlinClient): - - def __init__(self, connection, *, lang="gremlin-groovy", op="eval", - processor="", loop=None, verbose=False): - """This class is primarily designed to be used in the context - `manager""" - super().__init__(lang=lang, op=op, processor=processor, loop=loop, - verbose=verbose) - self._connection = connection - - @asyncio.coroutine - def close(self): - if self._closed: - return - self._closed = True - try: - yield from self._connection.release() - finally: - self._connection = None - - @property - def closed(self): - return (self._closed or self._connection.closed or - self._connection is None) - - @asyncio.coroutine - def submit(self, gremlin, *, bindings=None, lang="gremlin-groovy", - op="eval", processor="", session=None, binary=True): - """ - """ - writer = GremlinWriter(self._connection) - - connection = writer.write(gremlin, bindings=bindings, lang=lang, op=op, - processor=processor, session=session, - binary=binary) - - return GremlinResponse(self._connection, - session=session, - loop=self._loop) - - -class GremlinClient(BaseGremlinClient): - - def __init__(self, *, url='ws://localhost:8182/', loop=None, - protocols=None, lang="gremlin-groovy", op="eval", - processor="", timeout=None, verbose=False, connector=None): - """ - """ - super().__init__(lang=lang, op=op, processor=processor, loop=loop, - verbose=verbose) - self._url = url - self._timeout = timeout - self._session = None - if connector is None: - connector = GremlinConnector(loop=self._loop) - self._connector = connector - @property def url(self): + """Getter/setter for database url used by the client""" return self._url @url.setter @@ -128,10 +81,13 @@ class GremlinClient(BaseGremlinClient): @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): + """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 @@ -141,40 +97,72 @@ class GremlinClient(BaseGremlinClient): 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, - op=None, processor=None, binary=True): + op=None, processor=None, binary=True, session=None, + timeout=None): """ """ 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=self._timeout) + self.url, timeout=timeout) writer = GremlinWriter(ws) ws = writer.write(gremlin, bindings=bindings, lang=lang, op=op, processor=processor, binary=binary, - session=self._session) + session=session) + + return GremlinResponse(ws, session=session, loop=self._loop) + + @asyncio.coroutine + def execute(self, gremlin, *, bindings=None, lang=None, session=None, + op=None, processor=None, binary=True, timeout=None): + """ + """ + 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, + op=op, processor=processor, + binary=binary, session=session, + timeout=timeout) - return GremlinResponse(ws, session=self._session, loop=self._loop) + 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). 'ws://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='ws://localhost:8182/', loop=None, - protocols=None, lang="gremlin-groovy", op="eval", - processor="session", session=None, timeout=None, - verbose=False, connector=None): - """ - """ - super().__init__(url=url, protocols=protocols, lang=lang, op=op, - processor=processor, loop=loop, timeout=timeout, - verbose=verbose, connector=connector) + lang="gremlin-groovy", op="eval", processor="session", + session=None, timeout=None, + ws_connector=None): + super().__init__(url=url, lang=lang, op=op, processor=processor, + loop=loop, timeout=timeout, ws_connector=ws_connector) if session is None: session = str(uuid.uuid4()) @@ -196,7 +184,16 @@ class GremlinClientSession(GremlinClient): 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): self._loop = loop or asyncio.get_event_loop() self._session = session @@ -228,7 +225,16 @@ class GremlinResponse: 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, loop=None): self._ws = ws self._loop = loop or asyncio.get_event_loop() @@ -238,6 +244,7 @@ class GremlinResponseStream: @asyncio.coroutine def read(self): + """Read a message from the stream""" if self._stream.at_eof(): yield from self._ws.release() message = None @@ -258,8 +265,24 @@ def submit(gremlin, *, lang="gremlin-groovy", op="eval", processor="", - connector=None, + timeout=None, + session=None, loop=None): + """Submit a script to the Gremlin Server. + + :param str url: url for Gremlin Server (optional). 'ws://localhost:8182/' + by default + :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 + :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) + """ if loop is None: loop = asyncio.get_event_loop() @@ -271,11 +294,12 @@ def submit(gremlin, *, ws_response_class=GremlinClientWebSocketResponse) gremlin_client = GremlinClient(url=url, loop=loop, - connector=client_session) + ws_connector=client_session) try: resp = yield from gremlin_client.submit( - gremlin, bindings=bindings, lang=lang, op=op, processor=processor) + gremlin, bindings=bindings, lang=lang, op=op, processor=processor, + session=session, timeout=timeout) return resp diff --git a/aiogremlin/connector.py b/aiogremlin/connector.py index e623e4865f81a675ec895f17f4a10f7ed0418b9e..5c9fa791e5bca98d71851c18b0f5364cf55af70b 100644 --- a/aiogremlin/connector.py +++ b/aiogremlin/connector.py @@ -1,62 +1,33 @@ -import asyncio +"""Websocket connection factory and manager.""" -from contextlib import contextmanager +import asyncio from aiowebsocketclient import WebSocketConnector from aiogremlin.response import GremlinClientWebSocketResponse -from aiogremlin.contextmanager import ConnectionContextManager -from aiogremlin.log import logger __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 (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): - """ - :param float conn_timeout: timeout for establishing connection - (optional). Values ``0`` or ``None`` - mean no timeout - :param bool force_close: close underlying sockets after - releasing connection - :param int limit: limit for total open websocket connections - :param aiohttp.client.ClientSession client_session: Underlying HTTP - session used to - to establish - websocket - connections - :param loop: `event loop` - used for processing HTTP requests. - 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 - """ + 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) - - @contextmanager - @asyncio.coroutine - def connection(self, url, *, - protocols=(), - timeout=10.0, - autoclose=True, - autoping=True): - ws = yield from self.ws_connect(url='ws://localhost:8182/') - return ConnectionContextManager(ws) - - # aioredis style - def __enter__(self): - raise RuntimeError( - "'yield from' should be used as a context manager expression") - - def __exit__(self, *args): - pass - - def __iter__(self): - ws = yield from self.ws_connect(url='ws://localhost:8182/') - return ConnectionContextManager(ws) diff --git a/aiogremlin/contextmanager.py b/aiogremlin/contextmanager.py deleted file mode 100644 index 6216ba43b0c1c1fc4c0b4e4cf53aadde6cdf9b65..0000000000000000000000000000000000000000 --- a/aiogremlin/contextmanager.py +++ /dev/null @@ -1,19 +0,0 @@ -class ConnectionContextManager: - - __slots__ = ("_ws") - - def __init__(self, ws): - self._ws = ws - - def __enter__(self): - if self._ws.closed: - raise RuntimeError("Connection closed unexpectedly.") - return self._ws - - def __exit__(self, exception_type, exception_value, traceback): - try: - self._ws._close_code = 1000 - self._ws._closing = True - self._ws._do_close() - finally: - self._ws = None diff --git a/aiogremlin/exceptions.py b/aiogremlin/exceptions.py index 285e83d3b441f96a631bf7c794d7ba6c7157ca96..c131e1733c09f805199c477341bad439f4932da1 100644 --- a/aiogremlin/exceptions.py +++ b/aiogremlin/exceptions.py @@ -1,19 +1,12 @@ -""" -Gremlin Server exceptions. -""" +"""Gremlin Server exceptions.""" -__all__ = ("RequestError", "GremlinServerError", "SocketClientError") - - -class SocketClientError(IOError): - pass +__all__ = ("RequestError", "GremlinServerError") class StatusException(IOError): def __init__(self, value, result): """Handle all exceptions returned from the Gremlin Server as per: - https://github.com/apache/incubator-tinkerpop/blob/ddd0b36bed9a2b1ce5b335b1753d881f0614a6c4/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java """ self.value = value self.response = { diff --git a/aiogremlin/log.py b/aiogremlin/log.py deleted file mode 100644 index 4db936717f07bd9ca79df9b8e1054f3906a44d7d..0000000000000000000000000000000000000000 --- a/aiogremlin/log.py +++ /dev/null @@ -1,11 +0,0 @@ -import logging - - -INFO = logging.INFO - - -logging.basicConfig( - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") - - -logger = logging.getLogger("aiogremlin") diff --git a/aiogremlin/response.py b/aiogremlin/response.py index b22e0e766d11ad34f9c0198ca3647d7e48761b13..a5170e02fa381d2b83760846176d06e3ad245d40 100644 --- a/aiogremlin/response.py +++ b/aiogremlin/response.py @@ -1,5 +1,7 @@ """ +Class used to pass messages with the Gremlin Server. """ + import asyncio import base64 import hashlib @@ -8,8 +10,6 @@ import os import aiohttp from aiowebsocketclient.connector import ClientWebSocketResponse -from aiogremlin.exceptions import SocketClientError -from aiogremlin.log import INFO, logger __all__ = ('GremlinClientWebSocketResponse',) diff --git a/docs/aiogremlin.rst b/docs/aiogremlin.rst index 9e900446a46e450fade6c07d1b843198e1f87850..1a87eb4318adf5e41ee598f8b9c45b69db3a041b 100644 --- a/docs/aiogremlin.rst +++ b/docs/aiogremlin.rst @@ -1,5 +1,15 @@ -API -=== +.. _aiogremlin-client-reference: + +Client Reference +================ + +aiogremlin package +------------------------- + +.. automodule:: aiogremlin + :members: + :undoc-members: + :show-inheritance: aiogremlin.client module ------------------------ @@ -16,14 +26,7 @@ aiogremlin.connector module :members: :undoc-members: :show-inheritance: - -aiogremlin.contextmanager module --------------------------------- - -.. automodule:: aiogremlin.contextmanager - :members: - :undoc-members: - :show-inheritance: + :inherited-members: aiogremlin.exceptions module ---------------------------- @@ -33,14 +36,6 @@ aiogremlin.exceptions module :undoc-members: :show-inheritance: -aiogremlin.log module ---------------------- - -.. automodule:: aiogremlin.log - :members: - :undoc-members: - :show-inheritance: - aiogremlin.response module -------------------------- @@ -48,6 +43,7 @@ aiogremlin.response module :members: :undoc-members: :show-inheritance: + :inherited-members: aiogremlin.subprotocol module ----------------------------- diff --git a/docs/conf.py b/docs/conf.py index af3e9c04396d3ea5ed0e7ac867a837b877f7bb80..30a471d45b9915b0512b3e108027e76333077adc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import alabaster import sys import os import shlex @@ -33,6 +34,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'alabaster' ] # Add any paths that contain templates here, relative to this directory. @@ -117,10 +120,18 @@ html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +html_theme_options = { + # 'logo': 'logo.png', + 'description': 'Async client for the TP3 Gremlin Server', + 'logo_text_align': 'left', + 'github_user': 'davebshow', + 'github_repo': 'aiogremlin', + 'github_button': True, + 'github_banner': True +} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +html_theme_path = [alabaster.get_path()] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". @@ -157,7 +168,11 @@ html_static_path = ['_static'] #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +html_sidebars = { + '**': [ + 'about.html', 'navigation.html', 'searchbox.html', 'donate.html', + ] +} # Additional templates that should be rendered to pages, maps page names to # template names. @@ -286,3 +301,6 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.4', None), + 'aiohttp': ('http://aiohttp.readthedocs.org/en/stable/', None)} diff --git a/docs/getting_started.rst b/docs/getting_started.rst deleted file mode 100644 index 3ce1d78a117c4c8b1b614c6109375044d1f88568..0000000000000000000000000000000000000000 --- a/docs/getting_started.rst +++ /dev/null @@ -1,2 +0,0 @@ -Getting Started -=============== diff --git a/docs/index.rst b/docs/index.rst index ac9c9f6c1778e650cc459258ce7f4a6d63ac5fb9..d714c5f01c8afad02e0083e448ee34cb86c1264b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,15 +3,83 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to aiogremlin's documentation! -====================================== +========== +aiogremlin +========== + +:py:mod:`aiogremlin` is an asynchronous client for the `Tinkerpop 3 Gremlin Server`_ +based on the `asyncio`_ and `aiohttp`_ libraries. + +Releases +======== +The latest release of ``aiogremlin`` is **0.0.10**. + + +Requirements +============ + +- Python 3.4 +- Tinkerpop 3 Gremlin Server 3.0.0.M9 + + +Dependencies +============ +- aiohttp 0.16.5 +- aiowebsocketclient 0.0.3 + +To speed up serialization, you can also install `ujson`_. If not available, +aiogremlin will use the Python standard library :any:`json<json>` module. + +- ujson 1.33 + + +Installation +============ +Install using pip:: + + $ pip install aiogremlin + + +Getting Started +=============== + +:py:mod:`aiogremlin` has a simple API that is quite easy to use. However, as it relies +heavily on `asyncio`_ and `aiohttp`_, it is helpful to be familar with the +basics of these modules. If you're not, maybe check out the :py:mod:`asyncio` +documentation relating to the :ref:`event loop<asyncio-event-loop>` and the +concept of the :ref:`coroutine<coroutine>`. Also, I would recommend the +documentation relating to :py:mod:`aiohttp`'s +:ref:`websocket client<aiohttp-client-websockets>` and +:ref:`HTTP client<aiohttp-client-reference>` implementations. + +Minimal Example +--------------- +Submit a script to the Gremlin Server:: + + >>> import asyncio + >>> import aiogremlin + >>> @asyncio.coroutine + ... def go(): + ... resp = yield from aiogremlin.submit("1 + 1") + ... return (yield from resp.get()) + >>> loop = asyncio.get_event_loop() + >>> results = loop.run_until_complete(go()) + >>> results + [Message(status_code=200, data=[2], message={}, metadata='')] + + +The above example demonstrates how ``aiogremlin`` uses the +:ref:`event loop<asyncio-event-loop>` to drive communication with the Gremlin +Server, but the **rest of examples are written as if they were run in a Python +interpreter**. In reality, **this isn't possible**, so remember, code *must* +be wrapped in functions and run with the :ref:`event loop<asyncio-event-loop>`. Contents: .. toctree:: :maxdepth: 3 - getting_started + usage modules @@ -21,3 +89,8 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + +.. _Tinkerpop 3 Gremlin Server: http://tinkerpop.incubator.apache.org/ +.. _`asyncio`: https://docs.python.org/3/library/asyncio.html +.. _`aiohttp`: http://aiohttp.readthedocs.org/en/latest/ +.. _`ujson`: https://pypi.python.org/pypi/ujson diff --git a/docs/modules.rst b/docs/modules.rst index 0785e374b8938585dbc33b96757d6868799a1e7c..ef0aa53dcfaa1d1db9bd7f1d0fb48b5f272ec968 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -1,8 +1,7 @@ -aiogremlin -========== +API +=== .. toctree:: - :maxdepth: 4 + :maxdepth: 2 - usage aiogremlin diff --git a/docs/usage.rst b/docs/usage.rst index b2c0348973387f9fd33643056231fd4be3e6be2e..e4f0c5c9182b135cc2ca41daee3aef83ecf7ecce 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,2 +1,197 @@ Using aiogremlin ================ + +Before you get started, make sure you have the `Gremlin Server`_ up and running. +All of the following example assume a running Gremlin Server version 3.0.0.M9 at +'ws://localhost:8182/'. + + +Submitting a script with :py:func:`submit` +------------------------------------------ + + +The simplest way to interact with the Gremlin Server is by using the +:py:func:`aiogremlin.client.submit` function:: + + >>> resp = yield from aiogremlin.submit("x + x", bindings={"x": 2}) + +This returns an instance of :py:class:`aiogremlin.client.GremlinResponse`. This +class provides the interface used to read the underlying response stream. The +easiest way to read the stream is using the +:py:meth:`aiogremlin.client.GremlinResponse.get`:: + + >>> results = yield from resp.get() + +However, if you are expecting a huge result set from the server, you may want to +read the chunked responses one at a time:: + + >>> results = [] + >>> while True: + ... msg = yield from resp.stream.read(): + ... if msg is None: + ... break + ... results.append(msg) + + +.. function:: submit(gremlin, *, url='ws://localhost:8182/', bindings=None, + lang="gremlin-groovy", op="eval", processor="", + timeout=None, session=None, loop=None): + + Submit a script to the Gremlin Server. + + :param str gremlin: Gremlin script to submit to server. + + :param str url: url for Gremlin Server (optional). 'ws://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 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 + + +Reusing sockets with :py:class:`GremlinClient` +---------------------------------------------- + +To avoid the overhead of repeatedly establishing websocket connections, +``aiogremlin`` provides the class :py:class:`aiogremlin.client.GremlinClient`. +This class uses pooling to reuse websocket connections, and facilitates +concurrent message passing by yielding new websocket connections as needed:: + + >>> client = aiogremlin.GremlinClient() + >>> resp = client.submit("x + x", bindings={"x": 2}) + +For convenience, :py:class:`GremlinClient` provides the method +:py:meth:`aiogremlin.client.GremlinClient.execute`. This is equivalent of calling, +:py:meth:`GremlinClient.submit` and then :py:meth:`GremlinResponse.get`. +Therefore:: + + >>> results = client.execute("x + x", bindings={"x": 2}) + +Is equivalent to:: + + >>> resp = yield from aiogremlin.submit("x + x", bindings={"x": 2}) + >>> results = yield from resp.get() + +:py:class:`GremlinClient` encapsulates :py:class:`aiogremlin.connector.GremlinConnector`. +This class produces the websocket connections used by the client, and handles all +of the connection pooling. It can also handle pools for multiple servers. To do +so, you can share a :py:class:`GremlinConnector` amongst various client that +point to different endpoints:: + + >>> connector = aiogremlin.GremlinConnector() + >>> client1 = aiogremlin.GremlinClient(url=url='ws://localhost:8182/' + ... ws_connector=connector) + >>> client2 = aiogremlin.GremlinClient(url=url='ws://localhost:8080/' + ... ws_connector=connector) + + +.. class:: GremlinClient(self, *, url='ws://localhost:8182/', loop=None, + lang="gremlin-groovy", op="eval", processor="", + timeout=None, ws_connector=None) + + Main interface for interacting with the Gremlin Server. + + :param str url: url for Gremlin Server (optional). 'ws://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 + + :param ws_connector: A class that implements the method :py:meth:`ws_connect`. + Usually an instance of :py:class:`aiogremlin.connector.GremlinConnector` + +.. method:: close() + + :ref:`coroutine<coroutine>` method. + + Close client. If client has not been detached from underlying + ws_connector, this coroutinemethod closes the latter as well. + +.. method:: detach() + + Detach client from ws_connector. Client status is switched to closed. + +.. method:: submit(gremlin, *, bindings=None, lang=None, op=None, + processor=None, binary=True, session=None, timeout=None) + + :param str gremlin: Gremlin script to submit to server. + + :param str url: url for Gremlin Server (optional). 'ws://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 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 + + :returns: :py:class:`aiogremlin.client.GremlinResponse` object + +.. method:: execute(gremlin, *, bindings=None, lang=None, op=None, + processor=None, binary=True, session=None, timeout=None) + + :param str gremlin: Gremlin script to submit to server. + + :param str url: url for Gremlin Server (optional). 'ws://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 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 + + :returns: :py:class:`list` of messages + + +Using Gremlin Server sessions with :py:class:`GremlinClientSession`. + + + + + + +.. _Gremlin Server: http://tinkerpop.incubator.apache.org/ diff --git a/tests/tests.py b/tests/tests.py index f924506646cb6d22cafee66230f9074e4fe94d75..df6d8f8c63e8214f13140634d4a8b08821b4142e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -5,8 +5,8 @@ import asyncio import unittest import uuid -from aiogremlin import (submit, SimpleGremlinClient, GremlinConnector, - GremlinClient, GremlinClientSession) +from aiogremlin import (submit, GremlinConnector, GremlinClient, + GremlinClientSession) class SubmitTest(unittest.TestCase): @@ -31,43 +31,6 @@ class SubmitTest(unittest.TestCase): self.assertEqual(results[0].data[0], 8) -class SimpleGremlinClientTest(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - self.connector = GremlinConnector(force_close=True, loop=self.loop) - - def tearDown(self): - self.loop.close() - - def test_submit(self): - - @asyncio.coroutine - def go(): - ws = yield from self.connector.ws_connect('ws://localhost:8182/') - client = SimpleGremlinClient(ws, loop=self.loop) - resp = yield from client.submit("4 + 4", bindings={"x": 4}) - results = yield from resp.get() - return results - - results = self.loop.run_until_complete(go()) - self.assertEqual(results[0].data[0], 8) - - def test_close(self): - - @asyncio.coroutine - def go(): - ws = yield from self.connector.ws_connect('ws://localhost:8182/') - client = SimpleGremlinClient(ws, loop=self.loop) - yield from client.close() - self.assertTrue(client.closed) - self.assertTrue(ws.closed) - self.assertIsNone(client._connection) - - results = self.loop.run_until_complete(go()) - - class GremlinClientTest(unittest.TestCase): def setUp(self): @@ -229,20 +192,20 @@ class ContextMngrTest(unittest.TestCase): self.loop.run_until_complete(self.connector.close()) self.loop.close() - def test_connection_manager(self): - results = [] - - @asyncio.coroutine - def go(): - with (yield from self.connector) as conn: - client = SimpleGremlinClient(conn, loop=self.loop) - resp = yield from client.submit("1 + 1") - while True: - mssg = yield from resp.stream.read() - if mssg is None: - break - results.append(mssg) - self.loop.run_until_complete(go()) + # def test_connection_manager(self): + # results = [] + # + # @asyncio.coroutine + # def go(): + # with (yield from self.connector) as conn: + # client = SimpleGremlinClient(conn, loop=self.loop) + # resp = yield from client.submit("1 + 1") + # while True: + # mssg = yield from resp.stream.read() + # if mssg is None: + # break + # results.append(mssg) + # self.loop.run_until_complete(go()) if __name__ == "__main__":