Skip to content

Commit

Permalink
Add more methods for async API and improve tests and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
joowani committed Aug 23, 2016
1 parent adf8ba8 commit a2fb431
Show file tree
Hide file tree
Showing 53 changed files with 1,566 additions and 490 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*.egg-info
*build/
*dist/
*htmlcov/
*.coverage
.coveragerc
.cache/
tests/__pycache__/
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ Features

- Clean, Pythonic interface
- Lightweight
- 95%+ API coverage
- 95%+ ArangoDB REST API coverage

Compatibility
=============

- Python versions 2.7.x, 3.4.x and 3.5.x are supported
- Latest version of python-arango (3.x) supports ArangoDB 3.x only
- Older versions of python-arango (2.x) support ArangoDB 2.x only
- Older versions of python-arango support ArangoDB 1.x ~ 2.x only

Installation
============
Expand Down Expand Up @@ -74,7 +74,7 @@ Here is simple usage example:
# Initialize the client for ArangoDB
client = ArangoClient(
protocol='http',
host="localhost",
host='localhost',
port=8529,
username='root',
password='',
Expand Down
12 changes: 6 additions & 6 deletions arango/aql.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
AQLQueryExecuteError,
AQLFunctionCreateError,
AQLFunctionDeleteError,
AQLFunctionsListError,
AQLFunctionListError,
AQLCacheClearError,
AQLCacheConfigureError,
AQLCacheGetPropertiesError
AQLCachePropertiesError
)
from arango.request import Request

Expand Down Expand Up @@ -168,14 +168,14 @@ def functions(self):
:returns: a mapping of AQL function names to its javascript code
:rtype: dict
:raises arango.exceptions.AQLFunctionsListError: if the AQL functions
:raises arango.exceptions.AQLFunctionListError: if the AQL functions
cannot be retrieved
"""
request = Request(method='get', endpoint='/_api/aqlfunction')

def handler(res):
if res.status_code not in HTTP_OK:
raise AQLFunctionsListError(res)
raise AQLFunctionListError(res)
body = res.body or {}
return {func['name']: func['code'] for func in map(dict, body)}

Expand Down Expand Up @@ -259,7 +259,7 @@ def properties(self):
:returns: the cache properties
:rtype: dict
:raises arango.exceptions.AQLCacheGetPropertiesError: if the cache
:raises arango.exceptions.AQLCachePropertiesError: if the cache
properties cannot be retrieved
"""
request = Request(
Expand All @@ -269,7 +269,7 @@ def properties(self):

def handler(res):
if res.status_code not in HTTP_OK:
raise AQLCacheGetPropertiesError(res)
raise AQLCachePropertiesError(res)
return {'mode': res.body['mode'], 'limit': res.body['maxResults']}

return request, handler
Expand Down
101 changes: 34 additions & 67 deletions arango/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
from arango.utils import HTTP_OK
from arango.exceptions import (
AsyncExecuteError,
AsyncJobInvalidError,
AsyncJobNotDoneError,
AsyncJobNotFoundError,
AsyncJobCancelError,
AsyncJobGetStatusError,
AsyncJobGetResultError,
AsyncJobStatusError,
AsyncJobResultError,
AsyncJobClearError
)
from arango.graph import Graph
Expand Down Expand Up @@ -39,14 +36,15 @@ def __init__(self, connection, return_result=True):
username=connection.username,
password=connection.password,
http_client=connection.http_client,
database=connection.database
database=connection.database,
enable_logging=connection.has_logging
)
self._return_result = return_result
self._aql = AQL(self)
self._type = 'async'

def __repr__(self):
return '<ArangoDB asynchronous request>'
return '<ArangoDB asynchronous execution>'

def handle_request(self, request, handler):
"""Handle the incoming request and response handler.
Expand All @@ -57,11 +55,13 @@ def handle_request(self, request, handler):
:type handler: callable
:returns: the async job or None
:rtype: arango.async.AsyncJob
:raises arango.exceptions.AsyncExecuteError: if the async request
cannot be executed
"""
if self._return_result:
request.headers['x-arango-async'] = 'store'
else:
request.headers['x-arango-async'] = True
request.headers['x-arango-async'] = 'true'

res = getattr(self, request.method)(**request.kwargs)
if res.status_code not in HTTP_OK:
Expand Down Expand Up @@ -145,66 +145,48 @@ def status(self):
"""Return the status of the async job from the server.
:returns: the status of the async job, which can be ``"pending"`` (the
job is still in the queue), ``"done"`` (the job completed or raised
job is still in the queue), ``"done"`` (the job finished or raised
an exception)
:rtype: str
:raises arango.exceptions.AsyncJobInvalidError: if the async job is
not valid
:raises arango.exceptions.AsyncJobNotFoundError: if the async job
cannot be found in the server
:raises arango.exceptions.AsyncJobGetStatusError: if the status of the
:raises arango.exceptions.AsyncJobStatusError: if the status of the
async job cannot be retrieved from the server
"""
res = self._conn.get('/_api/job/{}'.format(self._id))
res = self._conn.get('/_api/job/{}'.format(self.id))
if res.status_code == 204:
return 'pending'
elif res.status_code in HTTP_OK:
return 'done'
elif res.status_code == 400:
raise AsyncJobInvalidError(res)
elif res.status_code == 404:
raise AsyncJobNotFoundError(res)
raise AsyncJobStatusError(res, 'Job {} missing'.format(self.id))
else:
raise AsyncJobGetStatusError(res)
raise AsyncJobStatusError(res)

def result(self):
"""Return the result of the async job if available.
:returns: the result or the exception from the async job
:rtype: object
:raises arango.exceptions.AsyncJobInvalidError: if the async job is
not valid
:raises arango.exceptions.AsyncJobNotFoundError: if the async job
cannot be found in the server
:raises arango.exceptions.AsyncJobNotDoneError: if the async job is
still pending in the queue
:raises arango.exceptions.AsyncJobGetResultError: if the result of the
:raises arango.exceptions.AsyncJobResultError: if the result of the
async job cannot be retrieved from the server
.. note::
An async job result will automatically be cleared from the server
once fetched and will *not* be available in subsequent calls.
"""
_id = self._id
res = self._conn.put('/_api/job/{}'.format(_id))
if (
res.status_code == 404 and
res.error_code == 404 and
res.error_message == 'not found'
):
raise AsyncJobNotFoundError(res, 'Job {} not found'.format(_id))
elif res.body is not None:
res = self._conn.put('/_api/job/{}'.format(self._id))
if 'X-Arango-Async-Id' in res.headers:
try:
result = self._handler(res)
except Exception as error:
return error
else:
return result
elif res.status_code == 204:
raise AsyncJobNotDoneError(res, 'Job {} pending'.format(_id))
elif res.status_code == 400:
raise AsyncJobInvalidError(res, 'Job {} invalid'.format(_id))
raise AsyncJobGetResultError(res, 'Failed to query job {}'.format(_id))
raise AsyncJobResultError(res, 'Job {} not done'.format(self._id))
elif res.status_code == 404:
raise AsyncJobResultError(res, 'Job {} missing'.format(self._id))
else:
raise AsyncJobResultError(res)

def cancel(self, ignore_missing=False):
"""Cancel the async job if it is still pending.
Expand All @@ -214,55 +196,40 @@ def cancel(self, ignore_missing=False):
:returns: ``True`` if the job was cancelled successfully, ``False`` if
the job was not found but **ignore_missing** was set to ``True``
:rtype: bool
:raises arango.exceptions.AsyncJobInvalidError: if the async job is
not valid
:raises arango.exceptions.AsyncJobNotFoundError: if the async job
cannot be found in the server
:raises arango.exceptions.AsyncJobCancelError: if the async job cannot
be cancelled
.. note::
An async job cannot be cancelled once it is taken out of the queue.
An async job cannot be cancelled once it is taken out of the queue
(i.e. started, finished or cancelled).
"""
_id = self._id
res = self._conn.put('/_api/job/{}/cancel'.format(_id))
res = self._conn.put('/_api/job/{}/cancel'.format(self._id))
if res.status_code == 200:
return True
elif res.status_code == 400:
raise AsyncJobInvalidError(res, 'Job {} invalid'.format(_id))
elif res.status_code == 404:
if ignore_missing:
return False
raise AsyncJobNotFoundError(res, 'Job {} not found'.format(_id))
raise AsyncJobCancelError(res, 'Failed to cancel job {}'.format(_id))
raise AsyncJobCancelError(res, 'Job {} missing'.format(self._id))
else:
raise AsyncJobCancelError(res)

def clear(self, ignore_missing=False):
"""Clear the result of the job from the server if available.
If the result is deleted successfully, boolean True is returned. If
the job was not found but ``ignore_missing`` was set, boolean False
is returned.
"""Delete the result of the job from the server.
:param ignore_missing: ignore missing async jobs
:type ignore_missing: bool
:returns: ``True`` if the result was deleted successfully, ``False``
if the job was not found but **ignore_missing** was set to ``True``
:rtype: bool
:raises arango.exceptions.AsyncJobInvalidError: if the async job is
not valid
:raises arango.exceptions.AsyncJobNotFoundError: if the async job
cannot be found in the server
:raises arango.exceptions.AsyncJobClearError: if the result of
the async job cannot be removed from the server
:raises arango.exceptions.AsyncJobClearError: if the result of the
async job cannot be delete from the server
"""
_id = self._id
res = self._conn.delete('/_api/job/{}'.format(_id))
res = self._conn.delete('/_api/job/{}'.format(self._id))
if res.status_code in HTTP_OK:
return True
elif res.status_code == 400:
raise AsyncJobInvalidError(res, 'Job {} invalid'.format(_id))
elif res.status_code == 404:
if ignore_missing:
return False
raise AsyncJobNotFoundError(res, 'Job {} not found'.format(_id))
raise AsyncJobClearError(res, 'Failed to clear job {}'.format(_id))
raise AsyncJobClearError(res, 'Job {} missing'.format(self._id))
else:
raise AsyncJobClearError(res)
5 changes: 3 additions & 2 deletions arango/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def __init__(self, connection, return_result=True, commit_on_error=False):
username=connection.username,
password=connection.password,
http_client=connection.http_client,
database=connection.database
database=connection.database,
enable_logging=connection.has_logging
)
self._id = uuid4()
self._return_result = return_result
Expand All @@ -50,7 +51,7 @@ def __init__(self, connection, return_result=True, commit_on_error=False):
self._type = 'batch'

def __repr__(self):
return '<ArangoDB batch request {}>'.format(self._id)
return '<ArangoDB batch execution {}>'.format(self._id)

def __enter__(self):
return self
Expand Down

0 comments on commit a2fb431

Please sign in to comment.