Commit e1ccc710 authored by davebshow's avatar davebshow
Browse files

working on docs

parent 76b471e8
......@@ -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
......
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)
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
"""
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 = {
......
import logging
INFO = logging.INFO
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger("aiogremlin")
"""
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',)
......
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
-----------------------------
......
......@@ -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)}
Getting Started
===============
......@@ -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
============