diff --git a/arango/client.py b/arango/client.py index db5a0606..5bd44da7 100644 --- a/arango/client.py +++ b/arango/client.py @@ -393,7 +393,7 @@ def run_tests(self, tests): # pragma: no cover raise ServerRunTestsError(res) return res.body - def execute(self, program): + def execute(self, program): # pragma: no cover """Execute a Javascript program on the server. :param program: the body of the Javascript program to execute. @@ -875,21 +875,28 @@ def delete_user(self, username, ignore_missing=False): return False raise UserDeleteError(res) - def user_access(self, username): - """Return the database access details of a user. + def user_access(self, username, full=False): + """Return a user's access details for databases (and collections). - :param username: the name of the user + :param username: The name of the user. :type username: str | unicode - :returns: the names of the databases the user can access - :rtype: [str] - :raises: arango.exceptions.UserAccessError: if the retrieval fails + :param full: Return the full set of access levels for all databases and + collections for the user. + :type full: bool + :returns: The names of the databases (and collections) the user has + access to. + :rtype: [str] | [unicode] + :raises: arango.exceptions.UserAccessError: If the retrieval fails. .. note:: Only the root user can access this method. For non-root users, use :func:`arango.database.Database.user_access` (via a database the users have access to) instead. """ - res = self._conn.get('/_api/user/{}/database'.format(username)) + res = self._conn.get( + '/_api/user/{}/database'.format(username), + params={'full': full} + ) if res.status_code in HTTP_OK: return list(res.body['result']) raise UserAccessError(res) @@ -897,13 +904,13 @@ def user_access(self, username): def grant_user_access(self, username, database): """Grant user access to a database. - :param username: the name of the user + :param username: The name of the user. :type username: str | unicode - :param database: the name of the database + :param database: The name of the database. :type database: str | unicode - :returns: whether the operation was successful + :returns: Whether the operation was successful or not. :rtype: bool - :raises arango.exceptions.UserGrantAccessError: if the operation fails + :raises arango.exceptions.UserGrantAccessError: If the operation fails. .. note:: Only the root user can access this method. For non-root users, @@ -921,22 +928,21 @@ def grant_user_access(self, username, database): def revoke_user_access(self, username, database): """Revoke user access to a database. - :param username: the name of the user + :param username: The name of the user. :type username: str | unicode - :param database: the name of the database + :param database: The name of the database. :type database: str | unicode | unicode - :returns: whether the operation was successful + :returns: Whether the operation was successful or not. :rtype: bool - :raises arango.exceptions.UserRevokeAccessError: if the operation fails + :raises arango.exceptions.UserRevokeAccessError: If the operation fails. .. note:: Only the root user can access this method. For non-root users, use :func:`arango.database.Database.revoke_user_access` (via a database the users have access to) instead. """ - res = self._conn.put( - '/_api/user/{}/database/{}'.format(username, database), - data={'grant': 'none'} + res = self._conn.delete( + '/_api/user/{}/database/{}'.format(username, database) ) if res.status_code in HTTP_OK: return True diff --git a/arango/collections/base.py b/arango/collections/base.py index d997273f..7f853813 100644 --- a/arango/collections/base.py +++ b/arango/collections/base.py @@ -123,6 +123,15 @@ def name(self): """ return self._name + @property + def database(self): + """Return the name of the database the collection belongs to. + + :returns: The name of the database. + :rtype: str | unicode + """ + return self._conn.database + @api_method def rename(self, new_name): """Rename the collection. @@ -930,6 +939,7 @@ def indexes(self): :rtype: [dict] :raises arango.exceptions.IndexListError: if the list of indexes cannot be retrieved + """ request = Request( method='get', @@ -988,7 +998,11 @@ def handler(res): return request, handler @api_method - def add_hash_index(self, fields, unique=None, sparse=None): + def add_hash_index(self, + fields, + unique=None, + sparse=None, + deduplicate=None): """Create a new hash index in the collection. :param fields: the document fields to index @@ -997,6 +1011,14 @@ def add_hash_index(self, fields, unique=None, sparse=None): :type unique: bool :param sparse: index ``None``'s :type sparse: bool + :param deduplicate: Controls whether inserting duplicate index values + from the same document into a unique array index leads to a unique + constraint error or not. If set to ``True`` (default), only a + single instance of each non-unique index values is inserted into + the index per document. Trying to insert a value into the index + that already exists will always fail, regardless of the value of + this field. + :param deduplicate: bool :returns: the details on the new index :rtype: dict :raises arango.exceptions.IndexCreateError: if the hash index cannot @@ -1007,10 +1029,16 @@ def add_hash_index(self, fields, unique=None, sparse=None): data['unique'] = unique if sparse is not None: data['sparse'] = sparse + if deduplicate is not None: + data['deduplicate'] = deduplicate return self._add_index(data) @api_method - def add_skiplist_index(self, fields, unique=None, sparse=None): + def add_skiplist_index(self, + fields, + unique=None, + sparse=None, + deduplicate=None): """Create a new skiplist index in the collection. A skiplist index is used to find the ranges of documents (e.g. time). @@ -1021,6 +1049,14 @@ def add_skiplist_index(self, fields, unique=None, sparse=None): :type unique: bool :param sparse: index ``None``'s :type sparse: bool + :param deduplicate: Controls whether inserting duplicate index values + from the same document into a unique array index leads to a unique + constraint error or not. If set to ``True`` (default), only a + single instance of each non-unique index values is inserted into + the index per document. Trying to insert a value into the index + that already exists will always fail, regardless of the value of + this field. + :param deduplicate: bool :returns: the details on the new index :rtype: dict :raises arango.exceptions.IndexCreateError: if the skiplist index @@ -1031,6 +1067,8 @@ def add_skiplist_index(self, fields, unique=None, sparse=None): data['unique'] = unique if sparse is not None: data['sparse'] = sparse + if deduplicate is not None: + data['deduplicate'] = deduplicate return self._add_index(data) @api_method @@ -1138,3 +1176,83 @@ def handler(res): return not res.body['error'] return request, handler + + @api_method + 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 in HTTP_OK: + result = res.body['result'].lower() + return None if result == 'none' else result + raise UserAccessError(res) + + return request, handler + + @api_method + 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 request, handler + + @api_method + def revoke_user_access(self, username): + """Revoke 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.UserRevokeAccessError: If the operation fails. + """ + request = Request( + method='delete', + endpoint='/_api/user/{}/database/{}/{}'.format( + username, self.database, self.name + ) + ) + + def handler(res): + if res.status_code in HTTP_OK: + return True + raise UserRevokeAccessError(res) + + return request, handler diff --git a/arango/connection.py b/arango/connection.py index 1464f673..ad654863 100644 --- a/arango/connection.py +++ b/arango/connection.py @@ -142,7 +142,6 @@ def type(self): def handle_request(self, request, handler): # from arango.async import AsyncExecution # from arango.exceptions import ArangoError - # # async = AsyncExecution(self, return_result=True) # response = async.handle_request(request, handler) # while response.status() != 'done': diff --git a/arango/database.py b/arango/database.py index 7f2cef42..c51d3645 100644 --- a/arango/database.py +++ b/arango/database.py @@ -241,7 +241,7 @@ def run_tests(self, tests): # pragma: no cover raise ServerRunTestsError(res) return res.body - def execute(self, program): + def execute(self, program): # pragma: no cover """Execute a Javascript program on the server. :param program: the body of the Javascript program to execute. @@ -449,9 +449,9 @@ def cluster(self, shard_id, transaction_id=None, timeout=None, sync=None): """Return the cluster round-trip test object. :param shard_id: the ID of the shard to which the request is sent - :type shard_id: str | unicode + :type shard_id: str | unicode | int :param transaction_id: the transaction ID for the request - :type transaction_id: str | unicode + :type transaction_id: str | unicode | int :param timeout: the timeout in seconds for the cluster operation, where an error is returned if the response does not arrive within the given limit (default: 24 hrs) @@ -776,11 +776,11 @@ def create_graph(self, } for definition in edge_definitions] if orphan_collections is not None: data['orphanCollections'] = orphan_collections - if smart is not None: + if smart is not None: # pragma: no cover data['isSmart'] = smart - if smart_field is not None: + if smart_field is not None: # pragma: no cover data['smartGraphAttribute'] = smart_field - if shard_count is not None: + if shard_count is not None: # pragma: no cover data['numberOfShards'] = shard_count res = self._conn.post('/_api/gharial', data=data) @@ -1076,30 +1076,41 @@ def delete_user(self, username, ignore_missing=False): raise UserDeleteError(res) def user_access(self, username): - """Return the database access details of a user. + """Return a user's access details for the database. - :param username: the name of the user + Appropriate permissions are required in order to execute this method. + + :param username: The name of the user. :type username: str | unicode - :returns: the names of the databases the user can access - :rtype: [str] - :raises: arango.exceptions.UserAccessError: if the retrieval fails + :returns: The access details (e.g. ``"rw"``, ``None``) + :rtype: str | unicode | None + :raises: arango.exceptions.UserAccessError: If the retrieval fails. """ - res = self._conn.get('/_api/user/{}/database'.format(username)) + res = self._conn.get( + '/_api/user/{}/database/{}'.format(username, self.name), + ) if res.status_code in HTTP_OK: - return list(res.body['result']) + result = res.body['result'].lower() + return None if result == 'none' else result raise UserAccessError(res) - def grant_user_access(self, username, database): - """Grant user access to a database. + def grant_user_access(self, username, database=None): + """Grant user access to the database. - :param username: the name of the user + Appropriate permissions are required in order to execute this method. + + :param username: The name of the user. :type username: str | unicode - :param database: the name of the database + :param database: The name of the database. If a name is not specified, + the name of the current database is used. :type database: str | unicode - :returns: whether the operation was successful + :returns: Whether the operation was successful or not. :rtype: bool - :raises arango.exceptions.UserGrantAccessError: if the operation fails + :raises arango.exceptions.UserGrantAccessError: If the operation fails. """ + if database is None: + database = self.name + res = self._conn.put( '/_api/user/{}/database/{}'.format(username, database), data={'grant': 'rw'} @@ -1108,20 +1119,25 @@ def grant_user_access(self, username, database): return True raise UserGrantAccessError(res) - def revoke_user_access(self, username, database): - """Revoke user access to a database. + def revoke_user_access(self, username, database=None): + """Revoke user access to the database. - :param username: the name of the user + Appropriate permissions are required in order to execute this method. + + :param username: The name of the user. :type username: str | unicode - :param database: the name of the database + :param database: The name of the database. If a name is not specified, + the name of the current database is used. :type database: str | unicode | unicode - :returns: whether the operation was successful + :returns: Whether the operation was successful or not. :rtype: bool - :raises arango.exceptions.UserRevokeAccessError: if the operation fails + :raises arango.exceptions.UserRevokeAccessError: If the operation fails. """ - res = self._conn.put( - '/_api/user/{}/database/{}'.format(username, database), - data={'grant': 'none'} + if database is None: + database = self.name + + res = self._conn.delete( + '/_api/user/{}/database/{}'.format(username, database) ) if res.status_code in HTTP_OK: return True diff --git a/arango/utils.py b/arango/utils.py index 1979c558..89fa6034 100644 --- a/arango/utils.py +++ b/arango/utils.py @@ -6,6 +6,7 @@ # Set of HTTP OK status codes HTTP_OK = {200, 201, 202, 203, 204, 205, 206} +HTTP_AUTH_ERR = {401, 403} def sanitize(data): diff --git a/arango/version.py b/arango/version.py index d7df69c0..8a37b19c 100644 --- a/arango/version.py +++ b/arango/version.py @@ -1 +1 @@ -VERSION = '3.9.0' +VERSION = '3.10.0' diff --git a/docs/user.rst b/docs/user.rst index a076da3b..dfc6bffe 100644 --- a/docs/user.rst +++ b/docs/user.rst @@ -1,9 +1,10 @@ .. _user-page: -User Management ---------------- +User and Access Management +-------------------------- -Python-arango provides operations for managing users and database access. +Python-arango provides operations for managing users and database/collection +access. Example: @@ -39,8 +40,8 @@ Example: username='johndoe@gmail.com', database='my_database' ) - # Get database access details of an existing user - client.user_access('johndoe@gmail.com') + # Get full database and collection access details of an existing user + client.user_access('johndoe@gmail.com', full=True) # Revoke database access from an existing user client.revoke_user_access( @@ -90,22 +91,33 @@ database they have access to instead. For example: extra={'team': 'frontend', 'title': 'architect'} ) # Grant database access to an existing user - db.grant_user_access( - username='johndoe@gmail.com', - database='database-the-user-has-access-to' - ) + db.grant_user_access('johndoe@gmail.com') + # Get database access details of an existing user db.user_access('johndoe@gmail.com') # Revoke database access from an existing user - db.revoke_user_access( - username='johndoe@gmail.com', - database='database-the-user-has-access-to' - ) + db.revoke_user_access('johndoe@gmail.com') # Delete an existing user client.delete_user(username='johndoe@gmail.com') +Collection-specific user access management is also possible: + +.. code-block:: python + + col = db.collection('some-collection') + + # Grant collection access to an existing user + col.grant_user_access('johndoe@gmail.com') + + # Get collection access details of an existing user + col.user_access('johndoe@gmail.com') + + # Revoke collection access from an existing user + col.revoke_user_access('johndoe@gmail.com') + -Refer to classes :class:`arango.client.ArangoClient` and -:ref::class:`arango.database.Database` for more details on the methods. +Refer to classes :class:`arango.client.ArangoClient`, +:class:`arango.database.Database`, and :class:`arango.collections.Collection` +classes for more details. diff --git a/tests/test_aql.py b/tests/test_aql.py index 1ef254b8..d3f13a3d 100644 --- a/tests/test_aql.py +++ b/tests/test_aql.py @@ -4,17 +4,7 @@ from arango import ArangoClient from arango.aql import AQL -from arango.exceptions import ( - AQLQueryExecuteError, - AQLQueryExplainError, - AQLQueryValidateError, - AQLFunctionListError, - AQLFunctionCreateError, - AQLFunctionDeleteError, - AQLCacheClearError, - AQLCacheConfigureError, - AQLCachePropertiesError, -) +from arango.exceptions import * from .utils import ( generate_db_name, @@ -25,11 +15,11 @@ arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() db.create_collection(col_name) -username = generate_user_name(arango_client) +username = generate_user_name() user = arango_client.create_user(username, 'password') func_name = '' func_body = '' @@ -198,17 +188,29 @@ def test_clear_query_cache(): @pytest.mark.order10 def test_aql_errors(): - bad_db_name = generate_db_name(arango_client) + bad_db_name = generate_db_name() bad_aql = arango_client.database(bad_db_name).aql - with pytest.raises(AQLFunctionListError): + with pytest.raises(ArangoError) as err: bad_aql.functions() + assert isinstance(err.value, AQLFunctionListError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) - with pytest.raises(AQLCachePropertiesError): + with pytest.raises(ArangoError) as err: bad_aql.cache.properties() + assert isinstance(err.value, AQLCachePropertiesError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) - with pytest.raises(AQLCacheConfigureError): + with pytest.raises(ArangoError) as err: bad_aql.cache.configure(mode='on') + assert isinstance(err.value, AQLCacheConfigureError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) - with pytest.raises(AQLCacheClearError): + with pytest.raises(ArangoError) as err: bad_aql.cache.clear() + assert isinstance(err.value, AQLCacheClearError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) diff --git a/tests/test_async.py b/tests/test_async.py index 9c06d612..51f89a45 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -25,9 +25,9 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) col.add_fulltext_index(fields=['val']) diff --git a/tests/test_async_new.py b/tests/test_async_new.py index 2abf57df..f4a60709 100644 --- a/tests/test_async_new.py +++ b/tests/test_async_new.py @@ -25,9 +25,9 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) col.add_fulltext_index(fields=['val']) diff --git a/tests/test_batch.py b/tests/test_batch.py index a41b881a..62986dfb 100644 --- a/tests/test_batch.py +++ b/tests/test_batch.py @@ -20,11 +20,11 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -bad_db_name = generate_db_name(arango_client) +bad_db_name = generate_db_name() bad_db = arango_client.db(bad_db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) diff --git a/tests/test_client.py b/tests/test_client.py index 72ea17ad..45b0d118 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -15,7 +15,7 @@ http_client = DefaultHTTPClient(use_session=False) arango_client = ArangoClient(http_client=http_client) bad_arango_client = ArangoClient(username='root', password='incorrect') -db_name = generate_db_name(arango_client) +db_name = generate_db_name() def teardown_module(*_): diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 2685f37a..84e97654 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -16,13 +16,13 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) -graph_name = generate_graph_name(db) +graph_name = generate_graph_name() graph = db.create_graph(graph_name) -vcol_name = generate_col_name(db) +vcol_name = generate_col_name() graph.create_vertex_collection(vcol_name) diff --git a/tests/test_collection.py b/tests/test_collection.py index c388fcac..9d56f13c 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -13,11 +13,11 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) -bad_col_name = generate_col_name(db) +bad_col_name = generate_col_name() bad_col = db.collection(bad_col_name) @@ -80,9 +80,9 @@ def test_configure(): def test_rename(): assert col.name == col_name - new_name = generate_col_name(db) + new_name = generate_col_name() while new_name == bad_col_name: - new_name = generate_col_name(db) + new_name = generate_col_name() # Test rename collection result = col.rename(new_name) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index d9b2ce79..bd2d0eb6 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -15,9 +15,9 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) cursor = None diff --git a/tests/test_database.py b/tests/test_database.py index 5110002d..b43fef85 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -18,14 +18,14 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -bad_db_name = generate_db_name(arango_client) +bad_db_name = generate_db_name() bad_db = arango_client.db(bad_db_name) -col_name_1 = generate_col_name(db) +col_name_1 = generate_col_name() col_name_2 = '' db.create_collection(col_name_1) -graph_name = generate_graph_name(db) +graph_name = generate_graph_name() db.create_graph(graph_name) @@ -76,7 +76,7 @@ def test_create_collection(): db.create_collection(col_name_1) # Test create collection with parameters - col_name_2 = generate_col_name(db) + col_name_2 = generate_col_name() col = db.create_collection( name=col_name_2, sync=True, @@ -176,7 +176,7 @@ def test_create_graph(): with pytest.raises(GraphCreateError): db.create_graph(graph_name) - new_graph_name = generate_graph_name(db) + new_graph_name = generate_graph_name() db.create_graph(new_graph_name) assert new_graph_name in [g['name'] for g in db.graphs()] @@ -198,15 +198,15 @@ def test_delete_graph(): major, minor = arango_version(arango_client) - if not (major == 3 and minor >= 1): + if major == 3 and minor >= 1: # Create a graph with vertex and edge collections and delete them all - new_graph_name = generate_graph_name(db) + new_graph_name = generate_graph_name() graph = db.create_graph(new_graph_name) - vcol_name_1 = generate_col_name(db) + vcol_name_1 = generate_col_name() graph.create_vertex_collection(vcol_name_1) - vcol_name_2 = generate_col_name(db) + vcol_name_2 = generate_col_name() graph.create_vertex_collection(vcol_name_2) - ecol_name = generate_col_name(db) + ecol_name = generate_col_name() graph.create_edge_definition( name=ecol_name, from_collections=[vcol_name_1], diff --git a/tests/test_document.py b/tests/test_document.py index 07a82955..98ef8928 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -14,14 +14,15 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) -edge_col_name = generate_col_name(db) +edge_col_name = generate_col_name() edge_col = db.create_collection(edge_col_name, edge=True) geo_index = col.add_geo_index(['coordinates']) -bad_col_name = generate_col_name(db) +bad_db = arango_client.database(db_name, password='invalid') +bad_col_name = generate_col_name() bad_col = db.collection(bad_col_name) doc1 = {'_key': '1', 'val': 100, 'text': 'foo', 'coordinates': [1, 1]} @@ -804,10 +805,15 @@ def test_delete(): # Test delete with missing collection with pytest.raises(ArangoError): bad_col.delete(doc5) - with pytest.raises(ArangoError): bad_col.delete(doc5['_key']) + # Test delete with wrong user credentials + with pytest.raises(ArangoError) as err: + bad_db.collection(col_name).delete(doc5) + assert isinstance(err.value, DocumentDeleteError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) def test_delete_many(): # Set up test documents diff --git a/tests/test_edge.py b/tests/test_edge.py index 3e352f71..ace0b56a 100644 --- a/tests/test_edge.py +++ b/tests/test_edge.py @@ -12,14 +12,14 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -ecol_name = generate_col_name(db) +ecol_name = generate_col_name() ecol = db.create_collection(ecol_name, edge=True) ecol.add_geo_index(['coordinates']) # Set up test collection and edges -col_name = generate_col_name(db) +col_name = generate_col_name() db.create_collection(col_name).import_bulk([ {'_key': '1'}, {'_key': '2'}, {'_key': '3'}, {'_key': '4'}, {'_key': '5'} ]) @@ -232,7 +232,7 @@ def test_update_match(): assert 'foo' not in ecol['2'] # Test update matching documents in missing collection - bad_ecol_name = generate_col_name(db) + bad_ecol_name = generate_col_name() with pytest.raises(DocumentUpdateError): bad_ecol = db.collection(bad_ecol_name) bad_ecol.update_match({'_key': '1'}, {'foo': 100}) @@ -336,7 +336,7 @@ def test_replace_match(): assert 'foo' not in ecol['2'] # Test replace matching documents in missing collection - bad_ecol_name = generate_col_name(db) + bad_ecol_name = generate_col_name() with pytest.raises(DocumentReplaceError): bad_ecol = db.collection(bad_ecol_name) bad_ecol.replace_match({'_key': '1'}, {'foo': 100}) @@ -403,11 +403,11 @@ def test_delete(): assert len(ecol) == 1 # Test delete with missing edge collection - bad_col = generate_col_name(db) + bad_col = generate_col_name() with pytest.raises(DocumentDeleteError): db.collection(bad_col).delete(edge5) - bad_col = generate_col_name(db) + bad_col = generate_col_name() with pytest.raises(DocumentDeleteError): db.collection(bad_col).delete(edge5['_key']) @@ -490,11 +490,11 @@ def test_delete_many(): assert len(ecol) == 0 # Test delete_many with missing edge collection - bad_ecol = generate_col_name(db) + bad_ecol = generate_col_name() with pytest.raises(DocumentDeleteError): db.collection(bad_ecol).delete_many(edges) - bad_ecol = generate_col_name(db) + bad_ecol = generate_col_name() with pytest.raises(DocumentDeleteError): db.collection(bad_ecol).delete_many(test_edge_keys) diff --git a/tests/test_graph.py b/tests/test_graph.py index 4eb9305b..33f5577a 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -17,15 +17,15 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) -graph_name = generate_graph_name(db) +graph_name = generate_graph_name() graph = db.create_graph(graph_name) -bad_graph_name = generate_graph_name(db) +bad_graph_name = generate_graph_name() bad_graph = db.graph(bad_graph_name) -bad_col_name = generate_col_name(db) +bad_col_name = generate_col_name() bad_vcol = bad_graph.vertex_collection(bad_col_name) bad_ecol = bad_graph.edge_collection(bad_col_name) @@ -79,7 +79,7 @@ def test_properties(): with pytest.raises(GraphPropertiesError): bad_graph.properties() - new_graph_name = generate_graph_name(db) + new_graph_name = generate_graph_name() new_graph = db.create_graph( new_graph_name, # TODO only possible with enterprise edition @@ -374,7 +374,7 @@ def test_delete_edge_definition(): @pytest.mark.order9 def test_create_graph_with_vertices_ane_edges(): - new_graph_name = generate_graph_name(db) + new_graph_name = generate_graph_name() edge_definitions = [ { 'name': 'ecol1', diff --git a/tests/test_index.py b/tests/test_index.py index 0e03ba48..c096894a 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -15,11 +15,11 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) -bad_col_name = generate_col_name(db) +bad_col_name = generate_col_name() bad_col = db.collection(bad_col_name) col.add_geo_index(['coordinates']) @@ -54,7 +54,8 @@ def test_add_hash_index(): result = col.add_hash_index( fields=fields, unique=True, - sparse=True + sparse=True, + deduplicate=True ) expected_index = { @@ -63,6 +64,7 @@ def test_add_hash_index(): 'type': 'hash', 'fields': ['attr1', 'attr2'], 'unique': True, + 'deduplicate': True } for key, value in expected_index.items(): assert result[key] == value @@ -76,14 +78,16 @@ def test_add_skiplist_index(): result = col.add_skiplist_index( fields=fields, unique=True, - sparse=True + sparse=True, + deduplicate=True ) expected_index = { 'sparse': True, 'type': 'skiplist', 'fields': ['attr1', 'attr2'], - 'unique': True + 'unique': True, + 'deduplicate': True } for key, value in expected_index.items(): assert result[key] == value diff --git a/tests/test_task.py b/tests/test_task.py index 1df0716e..12b2d0b4 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -13,9 +13,9 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -bad_db_name = generate_db_name(arango_client) +bad_db_name = generate_db_name() bad_db = arango_client.db(bad_db_name) test_cmd = "require('@arangodb').print(params);" @@ -48,12 +48,12 @@ def test_get_task(): # Test get missing task with pytest.raises(TaskGetError): - db.task(generate_task_id(db)) + db.task(generate_task_id()) def test_create_task(): # Test create task with random ID - task_name = generate_task_name(db) + task_name = generate_task_name() new_task = db.create_task( name=task_name, command=test_cmd, @@ -69,8 +69,8 @@ def test_create_task(): assert db.task(new_task['id']) == new_task # Test create task with specific ID - task_name = generate_task_name(db) - task_id = generate_task_id(db) + task_name = generate_task_name() + task_id = generate_task_id() new_task = db.create_task( name=task_name, command=test_cmd, @@ -99,8 +99,8 @@ def test_create_task(): def test_delete_task(): # Set up a test task to delete - task_name = generate_task_name(db) - task_id = generate_task_id(db) + task_name = generate_task_name() + task_id = generate_task_id() db.create_task( name=task_name, command=test_cmd, diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 5f0e3d46..ea75ac32 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -12,9 +12,9 @@ ) arango_client = ArangoClient() -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) -col_name = generate_col_name(db) +col_name = generate_col_name() col = db.create_collection(col_name) doc1 = {'_key': '1', 'data': {'val': 100}} @@ -88,7 +88,7 @@ def test_execute_with_params(): def test_execute_with_errors(): txn = db.transaction(write=col_name) - bad_col_name = generate_col_name(db) + bad_col_name = generate_col_name() with pytest.raises(TransactionError): txn.execute( command=''' diff --git a/tests/test_user.py b/tests/test_user.py index 1f226873..2f022573 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -4,6 +4,7 @@ import pytest from arango import ArangoClient +from arango.utils import HTTP_AUTH_ERR from arango.exceptions import * from .utils import ( @@ -14,10 +15,10 @@ arango_client = ArangoClient() bad_client = ArangoClient(password='incorrect') -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) bad_db = bad_client.db(db_name) -another_db_name = generate_db_name(arango_client) +another_db_name = generate_db_name() def teardown_module(*_): @@ -36,7 +37,7 @@ def test_list_users(): with pytest.raises(UserListError) as err: bad_client.users() - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR def test_get_user(): @@ -45,7 +46,7 @@ def test_get_user(): assert arango_client.user(user['username']) == user # Get a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserGetError) as err: arango_client.user(bad_username) assert err.value.http_code == 404 @@ -53,7 +54,7 @@ def test_get_user(): def test_create_user(): # Create a new user - username = generate_user_name(arango_client) + username = generate_user_name() new_user = arango_client.create_user( username=username, password='password', @@ -72,7 +73,7 @@ def test_create_user(): def test_update_user(): - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user( username=username, password='password', @@ -93,7 +94,7 @@ def test_update_user(): assert arango_client.user(username) == new_user # Update a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserUpdateError) as err: arango_client.update_user( username=bad_username, @@ -103,7 +104,7 @@ def test_update_user(): def test_replace_user(): - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user( username=username, password='password', @@ -124,7 +125,7 @@ def test_replace_user(): assert arango_client.user(username) == new_user # Replace a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserReplaceError) as err: arango_client.replace_user( username=bad_username, @@ -134,7 +135,7 @@ def test_replace_user(): def test_delete_user(): - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user( username=username, password='password' @@ -154,7 +155,7 @@ def test_delete_user(): def test_grant_user_access(): # Create a test user and login as that user - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user(username=username, password='password') user_db = arango_client.database( name=db_name, @@ -163,10 +164,10 @@ def test_grant_user_access(): ) # Create a collection with the user (should have no access) - col_name = generate_col_name(db) + col_name = generate_col_name() with pytest.raises(CollectionCreateError) as err: user_db.create_collection(col_name) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR assert col_name not in set(col['name'] for col in db.collections()) # Grant the user access and try again @@ -175,7 +176,7 @@ def test_grant_user_access(): assert col_name in set(col['name'] for col in db.collections()) # Grant access to a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserGrantAccessError) as err: arango_client.grant_user_access(bad_username, db_name) assert err.value.http_code == 404 @@ -183,7 +184,7 @@ def test_grant_user_access(): def test_revoke_user_access(): # Create a test user with access and login as that user - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user(username=username, password='password') arango_client.grant_user_access(username, db_name) user_db = arango_client.database( @@ -193,7 +194,7 @@ def test_revoke_user_access(): ) # Test user access by creating a collection - col_name = generate_col_name(db) + col_name = generate_col_name() user_db.create_collection(col_name) assert col_name in set(col['name'] for col in db.collections()) @@ -201,10 +202,10 @@ def test_revoke_user_access(): arango_client.revoke_user_access(username, db_name) with pytest.raises(CollectionDeleteError) as err: user_db.delete_collection(col_name) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR # Test revoke access to missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserRevokeAccessError) as err: arango_client.revoke_user_access(bad_username, db_name) assert err.value.http_code == 404 @@ -212,7 +213,7 @@ def test_revoke_user_access(): def test_get_user_access(): # Create a test user - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user(username=username, password='password') # Get user access (should be empty initially) @@ -223,17 +224,17 @@ def test_get_user_access(): assert arango_client.user_access(username) == [db_name] # Get access of a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() assert arango_client.user_access(bad_username) == [] # Get access of a user from a bad client (incorrect password) with pytest.raises(UserAccessError) as err: bad_client.user_access(username) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR def test_change_password(): - username = generate_user_name(arango_client) + username = generate_user_name() arango_client.create_user(username=username, password='password1') arango_client.grant_user_access(username, db_name) @@ -246,7 +247,7 @@ def test_change_password(): # Ensure that the user cannot make requests with bad credentials with pytest.raises(DatabasePropertiesError) as err: db2.properties() - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR # Update the user password and test again arango_client.update_user(username=username, password='password2') @@ -257,20 +258,20 @@ def test_change_password(): # db1.create_collection('test1') # with pytest.raises(DatabasePropertiesError) as err: # db1.create_collection('test') - # assert err.value.http_code == 401 + # assert err.value.http_code in HTTP_AUTH_ERR # # # Replace the user password and test again # arango_client.update_user(username=username, password='password1') # db1.properties() # with pytest.raises(DatabasePropertiesError) as err: # db2.properties() - # assert err.value.http_code == 401 + # assert err.value.http_code in HTTP_AUTH_ERR def test_create_user_with_database(): - username1 = generate_user_name(arango_client) - username2 = generate_user_name(arango_client, {username1}) - username3 = generate_user_name(arango_client, {username1, username2}) + username1 = generate_user_name() + username2 = generate_user_name() + username3 = generate_user_name() user_db = arango_client.create_database( name=another_db_name, users=[ @@ -303,7 +304,7 @@ def test_create_user_with_database(): assert user_db.connection.password == 'password3' with pytest.raises(DatabasePropertiesError) as err: user_db.properties() - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR def test_list_users_db_level(): for user in db.users(): @@ -313,7 +314,7 @@ def test_list_users_db_level(): with pytest.raises(UserListError) as err: bad_db.users() - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR def test_get_user_db_level(): @@ -322,7 +323,7 @@ def test_get_user_db_level(): assert db.user(user['username']) == user # Get a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserGetError) as err: db.user(bad_username) assert err.value.http_code == 404 @@ -330,7 +331,7 @@ def test_get_user_db_level(): def test_create_user_db_level(): # Create a new user - username = generate_user_name(arango_client) + username = generate_user_name() new_user = db.create_user( username=username, password='password', @@ -349,7 +350,7 @@ def test_create_user_db_level(): def test_update_user_db_level(): - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user( username=username, password='password', @@ -370,7 +371,7 @@ def test_update_user_db_level(): assert db.user(username) == new_user # Update a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserUpdateError) as err: db.update_user( username=bad_username, @@ -380,7 +381,7 @@ def test_update_user_db_level(): def test_replace_user_db_level(): - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user( username=username, password='password', @@ -401,7 +402,7 @@ def test_replace_user_db_level(): assert db.user(username) == new_user # Replace a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserReplaceError) as err: db.replace_user( username=bad_username, @@ -411,7 +412,7 @@ def test_replace_user_db_level(): def test_delete_user_db_level(): - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user( username=username, password='password' @@ -431,7 +432,7 @@ def test_delete_user_db_level(): def test_grant_user_access_db_level(): # Create a test user and login as that user - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user(username=username, password='password') user_db = arango_client.database( name=db_name, @@ -440,27 +441,27 @@ def test_grant_user_access_db_level(): ) # Create a collection with the user (should have no access) - col_name = generate_col_name(db) + col_name = generate_col_name() with pytest.raises(CollectionCreateError) as err: user_db.create_collection(col_name) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR assert col_name not in set(col['name'] for col in db.collections()) # Grant the user access and try again - db.grant_user_access(username, db_name) + db.grant_user_access(username) db.create_collection(col_name) assert col_name in set(col['name'] for col in db.collections()) # Grant access to a missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserGrantAccessError) as err: - db.grant_user_access(bad_username, db_name) + db.grant_user_access(bad_username) assert err.value.http_code == 404 def test_revoke_user_access_db_level(): # Create a test user with access and login as that user - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user(username=username, password='password') db.grant_user_access(username, db_name) user_db = arango_client.database( @@ -470,40 +471,145 @@ def test_revoke_user_access_db_level(): ) # Test user access by creating a collection - col_name = generate_col_name(db) + col_name = generate_col_name() user_db.create_collection(col_name) assert col_name in set(col['name'] for col in db.collections()) # Revoke access from the user - db.revoke_user_access(username, db_name) + db.revoke_user_access(username) with pytest.raises(CollectionDeleteError) as err: user_db.delete_collection(col_name) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR # Test revoke access to missing user - bad_username = generate_user_name(arango_client) + bad_username = generate_user_name() with pytest.raises(UserRevokeAccessError) as err: - db.revoke_user_access(bad_username, db_name) + db.revoke_user_access(bad_username) assert err.value.http_code == 404 def test_get_user_access_db_level(): # Create a test user - username = generate_user_name(arango_client) + username = generate_user_name() db.create_user(username=username, password='password') - # Get user access (should be empty initially) - assert db.user_access(username) == [] + # Get user access (should be none initially) + assert db.user_access(username) is None # Grant user access to the database and check again - db.grant_user_access(username, db_name) - assert db.user_access(username) == [db_name] + db.grant_user_access(username) + assert db.user_access(username) == 'rw' # Get access of a missing user - bad_username = generate_user_name(arango_client) - assert db.user_access(bad_username) == [] + bad_username = generate_user_name() + assert db.user_access(bad_username) is None # Get user access from a bad database (incorrect password) with pytest.raises(UserAccessError) as err: bad_db.user_access(bad_username) - assert err.value.http_code == 401 + assert err.value.http_code in HTTP_AUTH_ERR + + +def test_grant_user_access_collection_level(): + # Create a new test user + username = generate_user_name() + db.create_user(username=username, password='password') + + # Sign in as the user + user_db = arango_client.database( + name=db_name, + username=username, + password='password' + ) + + # Create a new collection + col_name = generate_col_name() + col = db.create_collection(col_name) + + # The new user should have no access to the collection + with pytest.raises(ArangoError) as err: + user_db.collection(col_name).count() + assert err.value.http_code in HTTP_AUTH_ERR + assert isinstance(err.value, DocumentCountError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) + + # After granting access to the collection it should work + col.grant_user_access(username) + assert user_db.collection(col_name).count() == 0 + + # Grant access from a bad database (missing user) + with pytest.raises(ArangoError) as err: + bad_db.collection(col_name).grant_user_access(generate_user_name()) + assert err.value.http_code in HTTP_AUTH_ERR + assert isinstance(err.value, UserGrantAccessError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) + + +def test_revoke_user_access_collection_level(): + # Create a new test user + username = generate_user_name() + db.create_user(username=username, password='password') + user_db = arango_client.database( + name=db_name, + username=username, + password='password' + ) + col_name = generate_col_name() + col = db.create_collection(col_name) + + # The new user should have no access to the collection + with pytest.raises(ArangoError) as err: + user_db.collection(col_name).count() + assert err.value.http_code in HTTP_AUTH_ERR + + # After granting access to the collection it should work + col.grant_user_access(username) + assert user_db.collection(col_name).count() == 0 + + # Revoke the access again to see that 401 is back + col.revoke_user_access(username) + with pytest.raises(ArangoError) as err: + user_db.collection(col_name).count() + assert err.value.http_code in HTTP_AUTH_ERR + + # Grant access from a bad database (missing user) + with pytest.raises(ArangoError) as err: + bad_db.collection(col_name).revoke_user_access(generate_user_name()) + assert err.value.http_code in HTTP_AUTH_ERR + assert isinstance(err.value, UserRevokeAccessError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) + + +def test_get_user_access_collection_level(): + # Create a new test user + username = generate_user_name() + db.create_user(username=username, password='password') + user_db = arango_client.database( + name=db_name, + username=username, + password='password' + ) + col_name = generate_col_name() + col = db.create_collection(col_name) + + # The new user should have no access to the collection + assert col.user_access(username) is None + + # After granting access to the collection it should work + col.grant_user_access(username) + assert col.user_access(username) == 'rw' + + # Revoke the access again to see that 401 is back + col.revoke_user_access(username) + assert col.user_access(username) is None + + # Grant access from a bad database (missing user) + with pytest.raises(ArangoError) as err: + bad_db.collection(col_name).user_access(generate_user_name()) + assert err.value.http_code in HTTP_AUTH_ERR + assert isinstance(err.value, UserAccessError) \ + or isinstance(err.value, AsyncExecuteError) \ + or isinstance(err.value, BatchExecuteError) \ No newline at end of file diff --git a/tests/test_wal.py b/tests/test_wal.py index 1a3a0882..50586b06 100644 --- a/tests/test_wal.py +++ b/tests/test_wal.py @@ -13,9 +13,9 @@ from .utils import generate_user_name, generate_db_name arango_client = ArangoClient() -username = generate_user_name(arango_client) +username = generate_user_name() user = arango_client.create_user(username, 'password') -db_name = generate_db_name(arango_client) +db_name = generate_db_name() db = arango_client.create_database(db_name) diff --git a/tests/utils.py b/tests/utils.py index 5c8b08e9..e9c1af4e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,132 +1,73 @@ from __future__ import absolute_import, unicode_literals from random import randint +from uuid import uuid4 def arango_version(client): """Return the major and minor version of ArangoDB. - :param client: ArangoDB client + :param client: The ArangoDB client. :type client: arango.ArangoClient - :return: the major and minor version numbers - :rtype: tuple + :return: The major and minor version numbers. + :rtype: (int, int) """ version_nums = client.version().split('.') return map(int, version_nums[:2]) -def generate_db_name(client, exclude=None): - """Generate and return the next available database name. +def generate_db_name(): + """Generate and return a random database name. - :param client: ArangoDB client - :type client: arango.ArangoClient - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available database name + :returns: A random database name. :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(client.databases()) - if exclude is not None: - existing |= set(exclude) - while 'test_database_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_database_{num:06d}'.format(num=num) - - -def generate_col_name(database, exclude=None): - """Generate and return the next available collection name. - - :param database: ArangoDB database - :type database: arango.database.ArangoDatabase - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available collection name + return 'test_database_{}'.format(uuid4().hex) + + +def generate_col_name(): + """Generate and return a random collection name. + + :returns: A random collection name. :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(col['name'] for col in database.collections()) - if exclude is not None: - existing |= set(exclude) - while 'test_collection_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_collection_{num:06d}'.format(num=num) - - -def generate_graph_name(database, exclude=None): - """Generate and return the next available collection name. - - :param database: ArangoDB database - :type database: arango.database.ArangoDatabase - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available graph name + return 'test_collection_{}'.format(uuid4().hex) + + +def generate_graph_name(): + """Generate and return a random graph name. + + :returns: A random graph name. :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(g['name'] for g in database.graphs()) - if exclude is not None: - existing |= set(exclude) - while 'test_graph_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_graph_{num:06d}'.format(num=num) - - -def generate_task_name(database, exclude=None): - """Generate and return the next available task name. - - :param database: ArangoDB database - :type database: arango.database.ArangoDatabase - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available task name + return 'test_graph_{}'.format(uuid4().hex) + + +def generate_task_name(): + """Generate and return a random task name. + + :returns: A random task name. :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(task['name'] for task in database.tasks()) - if exclude is not None: - existing |= set(exclude) - while 'test_task_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_task_{num:06d}'.format(num=num) - - -def generate_task_id(database, exclude=None): - """Generate and return the next available task ID. - - :param database: ArangoDB database - :type database: arango.database.ArangoDatabase - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available task ID + return 'test_task_{}'.format(uuid4().hex) + + +def generate_task_id(): + """Generate and return a random task ID. + + :returns: A random task ID :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(task['id'] for task in database.tasks()) - if exclude is not None: - existing |= set(exclude) - while 'test_task_id_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_task_id_{num:06d}'.format(num=num) + return 'test_task_id_{}'.format(uuid4().hex) -def generate_user_name(client, exclude=None): - """Generate and return the next available user name. +def generate_user_name(): + """Generate and return a random username. - :param client: ArangoDB client - :type client: arango.ArangoClient - :param exclude: set of names to exclude - :type exclude: set - :returns: the next available database name + :returns: A random username. :rtype: str | unicode """ - num = randint(100000, 999999) - existing = set(user['username'] for user in client.users()) - if exclude is not None: - existing |= set(exclude) - while 'test_user_{num:06d}'.format(num=num) in existing: - num = randint(100000, 999999) - return 'test_user_{num:06d}'.format(num=num) + return 'test_user_{}'.format(uuid4().hex) def clean_keys(obj):