def configure(self, oversized_ops=None, log_size=None, historic_logs=None, reserve_logs=None, throttle_wait=None, throttle_limit=None): """Configure the parameters of the write-ahead log. :param oversized_ops: execute and store ops bigger than a log file :type oversized_ops: bool :param log_size: the size of each write-ahead log file :type log_size: int :param historic_logs: the number of historic log files to keep :type historic_logs: int :param reserve_logs: the number of reserve log files to allocate :type reserve_logs: int :param throttle_wait: wait time before aborting when throttled (in ms) :type throttle_wait: int :param throttle_limit: number of pending gc ops before write-throttling :type throttle_limit: int :returns: the new configuration of the write-ahead log :rtype: dict :raises arango.exceptions.WALPropertiesError: if the WAL properties cannot be modified """ data = {} if oversized_ops is not None: data['allowOversizeEntries'] = oversized_ops if log_size is not None: data['logfileSize'] = log_size if historic_logs is not None: data['historicLogfiles'] = historic_logs if reserve_logs is not None: data['reserveLogfiles'] = reserve_logs if throttle_wait is not None: data['throttleWait'] = throttle_wait if throttle_limit is not None: data['throttleWhenPending'] = throttle_limit request = Request(method='put', endpoint='/_admin/wal/properties', data=data) def handler(res): if res.status_code not in HTTP_OK: raise WALConfigureError(res) return { 'oversized_ops': res.body.get('allowOversizeEntries'), 'log_size': res.body.get('logfileSize'), 'historic_logs': res.body.get('historicLogfiles'), 'reserve_logs': res.body.get('reserveLogfiles'), 'sync_interval': res.body.get('syncInterval'), 'throttle_wait': res.body.get('throttleWait'), 'throttle_limit': res.body.get('throttleWhenPending') } response = self.handle_request(request, handler) return response
def properties(self): """Return the configuration of the write-ahead log. :returns: the configuration of the write-ahead log :rtype: dict :raises arango.exceptions.WALPropertiesError: if the WAL properties cannot be retrieved from the server """ request = Request(method='get', endpoint='/_admin/wal/properties') def handler(res): if res.status_code not in HTTP_OK: raise WALPropertiesError(res) return { 'oversized_ops': res.body.get('allowOversizeEntries'), 'log_size': res.body.get('logfileSize'), 'historic_logs': res.body.get('historicLogfiles'), 'reserve_logs': res.body.get('reserveLogfiles'), 'sync_interval': res.body.get('syncInterval'), 'throttle_wait': res.body.get('throttleWait'), 'throttle_limit': res.body.get('throttleWhenPending') } response = self.handle_request(request, handler) return response
def _add_index(self, data): """Helper method for creating a new index.""" request = Request( method='post', endpoint='/_api/index', data=data, params={'collection': self._name} ) def handler(res): if res.status_code not in HTTP_OK: raise IndexCreateError(res) details = res.body details['id'] = details['id'].split('/', 1)[1] details.pop('error', None) details.pop('code', None) if 'minLength' in details: details['min_length'] = details.pop('minLength') if 'geoJson' in details: details['geo_json'] = details.pop('geoJson') if 'ignoreNull' in details: details['ignore_none'] = details.pop('ignoreNull') if 'selectivityEstimate' in details: details['selectivity'] = details.pop('selectivityEstimate') if 'isNewlyCreated' in details: details['new'] = details.pop('isNewlyCreated') return details return self.handle_request(request, handler)
def insert(self, document, sync=None): """Insert a new document into the vertex collection. If the ``"_key"`` field is present in **document**, its value is used as the key of the new document. Otherwise, the key is auto-generated. :param document: the document body :type document: dict :param sync: wait for the operation to sync to disk :type sync: bool | None :returns: the ID, revision and key of the document :rtype: dict :raises arango.exceptions.DocumentInsertError: if the document cannot be inserted into the collection """ params = {} if sync is not None: params['waitForSync'] = sync request = Request(method='post', endpoint='/_api/gharial/{}/vertex/{}'.format( self._graph_name, self._name), data=document, params=params) def handler(res): if res.status_code not in HTTP_OK: raise DocumentInsertError(res) return res.body['vertex'] return self.handle_request(request, handler)
def configure(self, mode=None, limit=None): """Configure the AQL query cache. :param mode: the operation mode (``"off"``, ``"on"`` or ``"demand"``) :type mode: str | unicode :param limit: the maximum number of results to be stored :type limit: int :returns: the result of the operation :rtype: dict :raises arango.exceptions.AQLCacheConfigureError: if the cache properties cannot be updated """ data = {} if mode is not None: data['mode'] = mode if limit is not None: data['maxResults'] = limit request = Request(method='put', endpoint='/_api/query-cache/properties', data=data) def handler(res): if res.status_code not in HTTP_OK: raise AQLCacheConfigureError(res) return {'mode': res.body['mode'], 'limit': res.body['maxResults']} return self.handle_request(request, handler)
def delete_index(self, index_id, ignore_missing=False): """Delete an index from the collection. :param index_id: the ID of the index to delete :type index_id: str | unicode :param ignore_missing: ignore missing indexes :type ignore_missing: bool :returns: whether the index was deleted successfully :rtype: bool :raises arango.exceptions.IndexDeleteError: if the specified index cannot be deleted from the collection """ request = Request( method='delete', endpoint='/_api/index/{}/{}'.format(self._name, index_id) ) def handler(res): if res.status_code == 404 and res.error_code == 1212: if ignore_missing: return False raise IndexDeleteError(res) if res.status_code not in HTTP_OK: raise IndexDeleteError(res) return not res.body['error'] return self.handle_request(request, handler)
def transactions(self): """Return details on currently running transactions. Fields in the returned dictionary: - *last_collected*: the ID of the last collected log file (at the \ start of each running transaction) or ``None`` if no transactions are \ running - *last_sealed*: the ID of the last sealed log file (at the start \ of each running transaction) or ``None`` if no transactions are \ running - *count*: the number of current running transactions :returns: the information about the currently running transactions :rtype: dict :raises arango.exceptions.WALTransactionListError: if the details on the transactions cannot be retrieved """ request = Request(method='get', endpoint='/_admin/wal/transactions') def handler(res): if res.status_code not in HTTP_OK: raise WALTransactionListError(res) return { 'last_collected': res.body['minLastCollected'], 'last_sealed': res.body['minLastSealed'], 'count': res.body['runningTransactions'] } response = self.handle_request(request, handler) return response
def __contains__(self, key): """Check if a document exists in the collection by its key. :param key: the document key :type key: dict | str | unicode :returns: whether the document exists :rtype: bool :raises arango.exceptions.DocumentInError: if the check cannot be executed """ request = Request( method='get', endpoint='/_api/document/{}/{}'.format(self._name, key) ) def handler(res): if res.status_code not in HTTP_OK: if res.status_code == 404 and res.error_code == 1202: return False raise DocumentInError(res) return True job = self.handle_request(request, handler, job_class=BaseJob) return job.result(raise_errors=True)
def rename(self, new_name): """Rename the collection. :param new_name: the new name for the collection :type new_name: str | unicode :returns: the new collection details :rtype: dict :raises arango.exceptions.CollectionRenameError: if the collection name cannot be changed """ request = Request( method='put', endpoint='/_api/collection/{}/rename'.format(self._name), data={'name': new_name} ) def handler(res): if res.status_code not in HTTP_OK: raise CollectionRenameError(res) self._name = new_name return { 'id': res.body['id'], 'is_system': res.body['isSystem'], 'name': res.body['name'], 'status': self._status(res.body['status']), 'type': self.TYPES[res.body['type']] } return self.handle_request(request, handler)
def next(self): """Read the next result from the cursor. :returns: the next item in the cursor :rtype: dict :raises: StopIteration, CursorNextError """ if len(self.batch()) == 0: if not self.has_more(): raise StopIteration request = Request(method='put', endpoint='/_api/{}/{}'.format( self._cursor_type, self.id)) def handler(res): if res.status_code not in HTTP_OK: raise CursorNextError(res) return res.body job = self.handle_request(request, handler, job_class=BaseJob, use_underlying=True) result = job.result(raise_errors=True) self._data = result return self.batch().pop(0)
def grant_user_access(self, username): """Grant user access to the collection. Appropriate permissions are required in order to execute this method. :param username: The name of the user. :type username: str | unicode :returns: Whether the operation was successful or not. :rtype: bool :raises arango.exceptions.UserGrantAccessError: If the operation fails. """ request = Request( method='put', endpoint='/_api/user/{}/database/{}/{}'.format( username, self.database, self.name ), data={'grant': 'rw'} ) def handler(res): if res.status_code in HTTP_OK: return True raise UserGrantAccessError(res) return self.handle_request(request, handler)
def find(self, filters, offset=None, limit=None): """Return all documents that match the given filters. :param filters: the document filters :type filters: dict :param offset: the number of documents to skip initially :type offset: int :param limit: the max number of documents to return :type limit: int :returns: the document cursor :rtype: arango.cursor.Cursor :raises arango.exceptions.DocumentGetError: if the document cannot be fetched from the collection """ data = {'collection': self._name, 'example': filters} if offset is not None: data['skip'] = offset if limit is not None: data['limit'] = limit request = Request( method='put', endpoint='/_api/simple/by-example', data=data ) def handler(res): if res.status_code not in HTTP_OK: raise DocumentGetError(res) return Cursor(self._conn, res.body) return self.handle_request(request, handler)
def create_function(self, name, code): """Create a new AQL function. :param name: the name of the new AQL function to create :type name: str | unicode :param code: the definition of the function in Javascript :type code: str | unicode :returns: whether the AQL function was created successfully :rtype: bool :raises arango.exceptions.AQLFunctionCreateError: if the AQL function cannot be created """ request = Request(method='post', endpoint='/_api/aqlfunction', data={ 'name': name, 'code': code }) def handler(res): if res.status_code not in (200, 201): raise AQLFunctionCreateError(res) return not res.body['error'] return self.handle_request(request, handler)
def indexes(self): """Return the collection indexes. :returns: the collection indexes :rtype: [dict] :raises arango.exceptions.IndexListError: if the list of indexes cannot be retrieved """ request = Request( method='get', endpoint='/_api/index', params={'collection': self._name} ) def handler(res): if res.status_code not in HTTP_OK: raise IndexListError(res) indexes = [] for index in res.body['indexes']: index['id'] = index['id'].split('/', 1)[1] if 'minLength' in index: index['min_length'] = index.pop('minLength') if 'geoJson' in index: index['geo_json'] = index.pop('geoJson') if 'ignoreNull' in index: index['ignore_none'] = index.pop('ignoreNull') if 'selectivityEstimate' in index: index['selectivity'] = index.pop('selectivityEstimate') indexes.append(index) return indexes return self.handle_request(request, handler)
def truncate(self): """Truncate the collection. :returns: the collection details :rtype: dict :raises arango.exceptions.CollectionTruncateError: if the collection cannot be truncated """ request = Request( method='put', endpoint='/_api/collection/{}/truncate'.format(self._name), command='db.{}.truncate()'.format(self._name) ) def handler(res): if res.status_code not in HTTP_OK: raise CollectionTruncateError(res) return { 'id': res.body['id'], 'is_system': res.body['isSystem'], 'name': res.body['name'], 'status': self._status(res.body['status']), 'type': self.TYPES[res.body['type']] } return self.handle_request(request, handler)
def clear(self, ignore_missing=False): """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.AsyncJobClearError: if the result of the async job cannot be delete from the server """ request = Request(method='delete', endpoint='/_api/job/{}'.format(self.id)) def handler(res): if res.status_code in HTTP_OK: return True elif res.status_code == 404: if ignore_missing: return False raise AsyncJobClearError(res, 'Job {} missing'.format(self.id)) else: raise AsyncJobClearError(res) response = self._conn.underlying.handle_request(request, handler, job_class=BaseJob) return response.result(raise_errors=True)
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 finished or raised an exception), or `"cancelled"` (the job was cancelled before completion) :rtype: str | unicode :raises arango.exceptions.AsyncJobStatusError: if the status of the async job cannot be retrieved from the server """ request = Request(method='get', endpoint='/_api/job/{}'.format(self.id)) def handler(res): if res.status_code == 204: self.update('pending') elif res.status_code in HTTP_OK: self.update('done') elif res.status_code == 404: raise AsyncJobStatusError(res, 'Job {} missing'.format(self.id)) else: raise AsyncJobStatusError(res) return self._status response = self._conn.underlying.handle_request(request, handler, job_class=BaseJob) return response.result(raise_errors=True)
def delete_database(self, name, ignore_missing=False): """Delete the database of the specified name. :param name: the name of the database to delete :type name: str | unicode :param ignore_missing: ignore missing databases :type ignore_missing: bool :returns: whether the database was deleted successfully :rtype: bool :raises arango.exceptions.DatabaseDeleteError: if the delete fails .. note:: Root privileges (i.e. access to the ``_system`` database) are required to use this method. """ request = Request(method='delete', endpoint='/_api/database/{}'.format(name)) def handler(res): if res.status_code not in HTTP_OK: if not (res.status_code == 404 and ignore_missing): raise DatabaseDeleteError(res) return not res.body['error'] return self.handle_request(request, handler)
def get(self, key, rev=None): """Fetch a document by key from the edge collection. :param key: the document key :type key: str | unicode :param rev: the document revision :type rev: str | unicode | None :returns: the vertex document or ``None`` if not found :rtype: dict | None :raises arango.exceptions.DocumentRevisionError: if the given revision does not match the revision of the target document :raises arango.exceptions.DocumentGetError: if the document cannot be fetched from the collection """ headers = {} if rev is not None: headers['If-Match'] = rev request = Request(method='get', endpoint='/_api/gharial/{}/edge/{}/{}'.format( self._graph_name, self._name, key), headers=headers) def handler(res): if res.status_code == 412: raise DocumentRevisionError(res) elif res.status_code == 404 and res.error_code == 1202: return None elif res.status_code not in HTTP_OK: raise DocumentGetError(res) return res.body['edge'] return self.handle_request(request, handler)
def close(self, ignore_missing=True): """Close the cursor and free the resources tied to it. :returns: whether the cursor was closed successfully :rtype: bool :param ignore_missing: ignore missing cursors :type ignore_missing: bool :raises: CursorCloseError """ if not self.id: return False request = Request(method='delete', endpoint='/_api/{}/{}'.format( self._cursor_type, self.id)) def handler(res): if res.status_code not in HTTP_OK: if res.status_code == 404 and ignore_missing: return False raise CursorCloseError(res) return True return self.handle_request( request, handler, job_class=BaseJob, use_underlying=True).result(raise_errors=True)
def statistics(self): """Return the collection statistics. :returns: the collection statistics :rtype: dict :raises arango.exceptions.CollectionStatisticsError: if the collection statistics cannot be retrieved """ request = Request( method='get', endpoint='/_api/collection/{}/figures'.format(self._name) ) def handler(res): if res.status_code not in HTTP_OK: raise CollectionStatisticsError(res) stats = res.body['figures'] stats['compaction_status'] = stats.pop('compactionStatus', None) stats['document_refs'] = stats.pop('documentReferences', None) stats['last_tick'] = stats.pop('lastTick', None) stats['waiting_for'] = stats.pop('waitingFor', None) stats['uncollected_logfile_entries'] = stats.pop( 'uncollectedLogfileEntries', None ) return stats return self.handle_request(request, handler)
def flush(self, sync=True, garbage_collect=True): """Flush the write-ahead log to collection journals and data files. :param sync: block until data is synced to disk :type sync: bool :param garbage_collect: block until flushed data is garbage collected :type garbage_collect: bool :returns: whether the write-ahead log was flushed successfully :rtype: bool :raises arango.exceptions.WALFlushError: it the WAL cannot be flushed """ data = {'waitForSync': sync, 'waitForCollector': garbage_collect} request = Request(method='put', endpoint='/_admin/wal/flush', data=data) def handler(res): if res.status_code not in HTTP_OK: raise WALFlushError(res) return not res.body.get('error') response = self.handle_request(request, handler) return response
def checksum(self, with_rev=False, with_data=False): """Return the collection checksum. :param with_rev: include the document revisions in the checksum calculation :type with_rev: bool :param with_data: include the document data in the checksum calculation :type with_data: bool :returns: the collection checksum :rtype: int :raises arango.exceptions.CollectionChecksumError: if the collection checksum cannot be retrieved """ request = Request( method='get', endpoint='/_api/collection/{}/checksum'.format(self._name), params={'withRevision': with_rev, 'withData': with_data} ) def handler(res): if res.status_code not in HTTP_OK: raise CollectionChecksumError(res) return int(res.body['checksum']) return self.handle_request(request, handler)
def __getitem__(self, key): """Return a document by its key from the collection. :param key: the document key :type key: str | unicode :returns: the document :rtype: dict :raises arango.exceptions.DocumentGetError: if the document cannot be fetched from the collection """ request = Request( method='get', endpoint='/_api/document/{}/{}'.format(self._name, key) ) def handler(res): if res.status_code == 404 and res.error_code == 1202: return None elif res.status_code not in HTTP_OK: raise DocumentGetError(res) return res.body job = self.handle_request(request, handler, job_class=BaseJob) return job.result(raise_errors=True)
def all(self, skip=None, limit=None): """Return all documents in the collection using a server cursor. :param skip: the number of documents to skip :type skip: int :param limit: the max number of documents fetched by the cursor :type limit: int :returns: the document cursor :rtype: arango.cursor.Cursor :raises arango.exceptions.DocumentGetError: if the documents in the collection cannot be retrieved """ data = {'collection': self._name} if skip is not None: data['skip'] = skip if limit is not None: data['limit'] = limit request = Request( method='put', endpoint='/_api/simple/all', data=data ) def handler(res): if res.status_code not in HTTP_OK: raise DocumentGetError(res) return Cursor(self._conn, res.body) return self.handle_request(request, handler)
def user_access(self, username): """Return a user's access details for the collection. Appropriate permissions are required in order to execute this method. :param username: The name of the user. :type username: str | unicode :returns: The access details (e.g. ``"rw"``, ``None``) :rtype: str | unicode | None :raises: arango.exceptions.UserAccessError: If the retrieval fails. """ request = Request( method='get', endpoint='/_api/user/{}/database/{}/{}'.format( username, self.database, self.name ) ) def handler(res): if res.status_code not in HTTP_OK: raise UserAccessError(res) result = res.body['result'].lower() if result == 'none': return None else: return result return self.handle_request(request, handler)
def update_match(self, filters, body, limit=None, keep_none=True, sync=None): """Update matching documents in the collection. :param filters: the filters :type filters: dict :param body: the document body :type body: dict :param limit: the max number of documents to return :type limit: int :param keep_none: if ``True``, the fields with value ``None`` are retained in the document, otherwise the fields are removed from the document completely :type keep_none: bool :param sync: wait for the operation to sync to disk :type sync: bool :returns: the number of documents updated :rtype: int :raises arango.exceptions.DocumentUpdateError: if the documents cannot be updated """ data = { 'collection': self._name, 'example': filters, 'newValue': body, 'keepNull': keep_none, } if limit is not None: data['limit'] = limit if sync is not None: data['waitForSync'] = sync if self._conn.type != 'transaction': command = None else: command = 'db.{}.updateByExample({},{},{})'.format( self._name, dumps(filters), dumps(body), dumps(data) ) request = Request( method='put', endpoint='/_api/simple/update-by-example', data=data, command=command ) def handler(res): if res.status_code not in HTTP_OK: raise DocumentUpdateError(res) return res.body['updated'] return self.handle_request(request, handler)
def execute(self, query, count=False, batch_size=None, ttl=None, bind_vars=None, full_count=None, max_plans=None, optimizer_rules=None): """Execute the query and return the result cursor. :param query: the AQL query to execute :type query: str | unicode :param count: whether the document count should be returned :type count: bool :param batch_size: maximum number of documents in one round trip :type batch_size: int :param ttl: time-to-live for the cursor (in seconds) :type ttl: int :param bind_vars: key-value pairs of bind parameters :type bind_vars: dict :param full_count: include count before last LIMIT :param max_plans: maximum number of plans the optimizer generates :type max_plans: int :param optimizer_rules: list of optimizer rules :type optimizer_rules: list :returns: document cursor :rtype: arango.cursor.Cursor :raises arango.exceptions.AQLQueryExecuteError: if the query cannot be executed :raises arango.exceptions.CursorCloseError: if the cursor cannot be closed properly """ options = {} if full_count is not None: options['fullCount'] = full_count if max_plans is not None: options['maxNumberOfPlans'] = max_plans if optimizer_rules is not None: options['optimizer'] = {'rules': optimizer_rules} data = {'query': query, 'count': count} if batch_size is not None: data['batchSize'] = batch_size if ttl is not None: data['ttl'] = ttl if bind_vars is not None: data['bindVars'] = bind_vars if options: data['options'] = options request = Request(method='post', endpoint='/_api/cursor', data=data) def handler(res): if res.status_code not in HTTP_OK: raise AQLQueryExecuteError(res) return Cursor(self._conn, res.body) return self.handle_request(request, handler)
def find_in_box(self, latitude1, longitude1, latitude2, longitude2, skip=None, limit=None, geo_field=None): """Return all documents in an rectangular area. :param latitude1: the first latitude :type latitude1: int :param longitude1: the first longitude :type longitude1: int :param latitude2: the second latitude :type latitude2: int :param longitude2: the second longitude :type longitude2: int :param skip: the number of documents to skip :type skip: int :param limit: the max number of documents to return (if 0 is given all documents are returned) :type limit: int :param geo_field: the field to use for geo index :type geo_field: str | unicode :returns: the document cursor :rtype: arango.cursor.Cursor :raises arango.exceptions.DocumentGetError: if the documents cannot be fetched from the collection """ data = { 'collection': self._name, 'latitude1': latitude1, 'longitude1': longitude1, 'latitude2': latitude2, 'longitude2': longitude2, } if skip is not None: data['skip'] = skip if limit is not None: data['limit'] = limit if geo_field is not None: data['geo'] = '/'.join([self._name, geo_field]) request = Request( method='put', endpoint='/_api/simple/within-rectangle', data=data ) def handler(res): if res.status_code not in HTTP_OK: raise DocumentGetError(res) return Cursor(self._conn, res.body) return self.handle_request(request, handler)
def create_database(self, name, users=None, username=None, password=None): """Create a new database. :param name: the name of the new database :type name: str | unicode :param users: the list of users with access to the new database, where each user is a dictionary with keys ``"username"``, ``"password"``, ``"active"`` and ``"extra"``. :type users: [dict] :param username: the username for authentication (if set, overrides the username specified during the client initialization) :type username: str | unicode :param password: the password for authentication (if set, overrides the password specified during the client initialization :type password: str | unicode :returns: the database object :rtype: arango.database.Database :raises arango.exceptions.DatabaseCreateError: if the create fails .. note:: Here is an example entry in **users**: .. code-block:: python { 'username': '******', 'password': '******', 'active': True, 'extra': {'Department': 'IT'} } If **users** is not set, only the root and the current user are granted access to the new database by default. """ data = { 'name': name, } if users is not None: data['users'] = [{ 'username': user['username'], 'passwd': user['password'], 'active': user.get('active', True), 'extra': user.get('extra', {}) } for user in users] request = Request(method='post', endpoint='/_api/database', data=data) def handler(res): if res.status_code not in HTTP_OK: raise DatabaseCreateError(res) return self.db(name, username, password) return self.handle_request(request, handler)