python-arango-async 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arangoasync/__init__.py +5 -0
- arangoasync/aql.py +760 -0
- arangoasync/auth.py +121 -0
- arangoasync/client.py +239 -0
- arangoasync/collection.py +1688 -0
- arangoasync/compression.py +139 -0
- arangoasync/connection.py +515 -0
- arangoasync/cursor.py +262 -0
- arangoasync/database.py +1533 -0
- arangoasync/errno.py +1168 -0
- arangoasync/exceptions.py +379 -0
- arangoasync/executor.py +168 -0
- arangoasync/http.py +182 -0
- arangoasync/job.py +214 -0
- arangoasync/logger.py +3 -0
- arangoasync/request.py +107 -0
- arangoasync/resolver.py +119 -0
- arangoasync/response.py +65 -0
- arangoasync/result.py +9 -0
- arangoasync/serialization.py +111 -0
- arangoasync/typings.py +1646 -0
- arangoasync/version.py +1 -0
- python_arango_async-0.0.1.dist-info/METADATA +142 -0
- python_arango_async-0.0.1.dist-info/RECORD +27 -0
- python_arango_async-0.0.1.dist-info/WHEEL +5 -0
- python_arango_async-0.0.1.dist-info/licenses/LICENSE +21 -0
- python_arango_async-0.0.1.dist-info/top_level.txt +1 -0
arangoasync/database.py
ADDED
|
@@ -0,0 +1,1533 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"Database",
|
|
3
|
+
"StandardDatabase",
|
|
4
|
+
"TransactionDatabase",
|
|
5
|
+
"AsyncDatabase",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import Any, List, Optional, Sequence, TypeVar, cast
|
|
10
|
+
from warnings import warn
|
|
11
|
+
|
|
12
|
+
from arangoasync.aql import AQL
|
|
13
|
+
from arangoasync.collection import StandardCollection
|
|
14
|
+
from arangoasync.connection import Connection
|
|
15
|
+
from arangoasync.errno import HTTP_FORBIDDEN, HTTP_NOT_FOUND
|
|
16
|
+
from arangoasync.exceptions import (
|
|
17
|
+
AsyncJobClearError,
|
|
18
|
+
AsyncJobListError,
|
|
19
|
+
CollectionCreateError,
|
|
20
|
+
CollectionDeleteError,
|
|
21
|
+
CollectionListError,
|
|
22
|
+
DatabaseCreateError,
|
|
23
|
+
DatabaseDeleteError,
|
|
24
|
+
DatabaseListError,
|
|
25
|
+
DatabasePropertiesError,
|
|
26
|
+
JWTSecretListError,
|
|
27
|
+
JWTSecretReloadError,
|
|
28
|
+
PermissionGetError,
|
|
29
|
+
PermissionListError,
|
|
30
|
+
PermissionResetError,
|
|
31
|
+
PermissionUpdateError,
|
|
32
|
+
ServerStatusError,
|
|
33
|
+
ServerVersionError,
|
|
34
|
+
TransactionAbortError,
|
|
35
|
+
TransactionCommitError,
|
|
36
|
+
TransactionExecuteError,
|
|
37
|
+
TransactionInitError,
|
|
38
|
+
TransactionListError,
|
|
39
|
+
TransactionStatusError,
|
|
40
|
+
UserCreateError,
|
|
41
|
+
UserDeleteError,
|
|
42
|
+
UserGetError,
|
|
43
|
+
UserListError,
|
|
44
|
+
UserReplaceError,
|
|
45
|
+
UserUpdateError,
|
|
46
|
+
)
|
|
47
|
+
from arangoasync.executor import (
|
|
48
|
+
ApiExecutor,
|
|
49
|
+
AsyncApiExecutor,
|
|
50
|
+
DefaultApiExecutor,
|
|
51
|
+
TransactionApiExecutor,
|
|
52
|
+
)
|
|
53
|
+
from arangoasync.request import Method, Request
|
|
54
|
+
from arangoasync.response import Response
|
|
55
|
+
from arangoasync.result import Result
|
|
56
|
+
from arangoasync.serialization import Deserializer, Serializer
|
|
57
|
+
from arangoasync.typings import (
|
|
58
|
+
CollectionInfo,
|
|
59
|
+
CollectionType,
|
|
60
|
+
DatabaseProperties,
|
|
61
|
+
Json,
|
|
62
|
+
Jsons,
|
|
63
|
+
KeyOptions,
|
|
64
|
+
Params,
|
|
65
|
+
ServerStatusInformation,
|
|
66
|
+
UserInfo,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
T = TypeVar("T")
|
|
70
|
+
U = TypeVar("U")
|
|
71
|
+
V = TypeVar("V")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Database:
|
|
75
|
+
"""Database API.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
executor: API executor.
|
|
79
|
+
Responsible for executing requests and handling responses.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, executor: ApiExecutor) -> None:
|
|
83
|
+
self._executor = executor
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def connection(self) -> Connection:
|
|
87
|
+
"""Return the HTTP connection."""
|
|
88
|
+
return self._executor.connection
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def name(self) -> str:
|
|
92
|
+
"""Return the name of the current database."""
|
|
93
|
+
return self._executor.db_name
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def serializer(self) -> Serializer[Json]:
|
|
97
|
+
"""Return the serializer."""
|
|
98
|
+
return self._executor.serializer
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def deserializer(self) -> Deserializer[Json, Jsons]:
|
|
102
|
+
"""Return the deserializer."""
|
|
103
|
+
return self._executor.deserializer
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def context(self) -> str:
|
|
107
|
+
"""Return the API execution context.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
str: API execution context. Possible values are "default", "transaction".
|
|
111
|
+
"""
|
|
112
|
+
return self._executor.context
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def aql(self) -> AQL:
|
|
116
|
+
"""Return the AQL API wrapper.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
arangoasync.aql.AQL: AQL API wrapper.
|
|
120
|
+
"""
|
|
121
|
+
return AQL(self._executor)
|
|
122
|
+
|
|
123
|
+
async def properties(self) -> Result[DatabaseProperties]:
|
|
124
|
+
"""Return database properties.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
DatabaseProperties: Properties of the current database.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
DatabasePropertiesError: If retrieval fails.
|
|
131
|
+
|
|
132
|
+
References:
|
|
133
|
+
- `get-information-about-the-current-database <https://docs.arangodb.com/stable/develop/http-api/databases/#get-information-about-the-current-database>`__
|
|
134
|
+
""" # noqa: E501
|
|
135
|
+
request = Request(method=Method.GET, endpoint="/_api/database/current")
|
|
136
|
+
|
|
137
|
+
def response_handler(resp: Response) -> DatabaseProperties:
|
|
138
|
+
if not resp.is_success:
|
|
139
|
+
raise DatabasePropertiesError(resp, request)
|
|
140
|
+
return DatabaseProperties(
|
|
141
|
+
self.deserializer.loads(resp.raw_body), strip_result=True
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return await self._executor.execute(request, response_handler)
|
|
145
|
+
|
|
146
|
+
async def status(self) -> Result[ServerStatusInformation]:
|
|
147
|
+
"""Query the server status.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
ServerStatusInformation: Server status.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ServerSatusError: If retrieval fails.
|
|
154
|
+
|
|
155
|
+
References:
|
|
156
|
+
- `get-server-status-information <https://docs.arangodb.com/stable/develop/http-api/administration/#get-server-status-information>`__
|
|
157
|
+
""" # noqa: E501
|
|
158
|
+
request = Request(method=Method.GET, endpoint="/_admin/status")
|
|
159
|
+
|
|
160
|
+
def response_handler(resp: Response) -> ServerStatusInformation:
|
|
161
|
+
if not resp.is_success:
|
|
162
|
+
raise ServerStatusError(resp, request)
|
|
163
|
+
return ServerStatusInformation(self.deserializer.loads(resp.raw_body))
|
|
164
|
+
|
|
165
|
+
return await self._executor.execute(request, response_handler)
|
|
166
|
+
|
|
167
|
+
async def databases(self) -> Result[List[str]]:
|
|
168
|
+
"""Return the names of all databases.
|
|
169
|
+
|
|
170
|
+
Note:
|
|
171
|
+
This method can only be executed in the **_system** database.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
list: Database names.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
DatabaseListError: If retrieval fails.
|
|
178
|
+
|
|
179
|
+
References:
|
|
180
|
+
- `list-all-databases <https://docs.arangodb.com/stable/develop/http-api/databases/#list-all-databases>`__
|
|
181
|
+
""" # noqa: E501
|
|
182
|
+
request = Request(method=Method.GET, endpoint="/_api/database")
|
|
183
|
+
|
|
184
|
+
def response_handler(resp: Response) -> List[str]:
|
|
185
|
+
if resp.is_success:
|
|
186
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
187
|
+
return cast(List[str], body["result"])
|
|
188
|
+
msg: Optional[str] = None
|
|
189
|
+
if resp.status_code == HTTP_FORBIDDEN:
|
|
190
|
+
msg = "This request can only be executed in the _system database."
|
|
191
|
+
raise DatabaseListError(resp, request, msg)
|
|
192
|
+
|
|
193
|
+
return await self._executor.execute(request, response_handler)
|
|
194
|
+
|
|
195
|
+
async def databases_accessible_to_user(self) -> Result[List[str]]:
|
|
196
|
+
"""Return the names of all databases accessible to the current user.
|
|
197
|
+
|
|
198
|
+
Note:
|
|
199
|
+
This method can only be executed in the **_system** database.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
list: Database names.
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
DatabaseListError: If retrieval fails.
|
|
206
|
+
|
|
207
|
+
References:
|
|
208
|
+
- `list-the-accessible-databases <https://docs.arangodb.com/stable/develop/http-api/databases/#list-the-accessible-databases>`__
|
|
209
|
+
""" # noqa: E501
|
|
210
|
+
request = Request(method=Method.GET, endpoint="/_api/database/user")
|
|
211
|
+
|
|
212
|
+
def response_handler(resp: Response) -> List[str]:
|
|
213
|
+
if resp.is_success:
|
|
214
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
215
|
+
return cast(List[str], body["result"])
|
|
216
|
+
raise DatabaseListError(resp, request)
|
|
217
|
+
|
|
218
|
+
return await self._executor.execute(request, response_handler)
|
|
219
|
+
|
|
220
|
+
async def has_database(self, name: str) -> Result[bool]:
|
|
221
|
+
"""Check if a database exists.
|
|
222
|
+
|
|
223
|
+
Note:
|
|
224
|
+
This method can only be executed from within the **_system** database.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
name (str): Database name.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
bool: `True` if the database exists, `False` otherwise.
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
DatabaseListError: If retrieval fails.
|
|
234
|
+
"""
|
|
235
|
+
request = Request(method=Method.GET, endpoint="/_api/database")
|
|
236
|
+
|
|
237
|
+
def response_handler(resp: Response) -> bool:
|
|
238
|
+
if resp.is_success:
|
|
239
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
240
|
+
return name in body["result"]
|
|
241
|
+
msg: Optional[str] = None
|
|
242
|
+
if resp.status_code == HTTP_FORBIDDEN:
|
|
243
|
+
msg = "This request can only be executed in the _system database."
|
|
244
|
+
raise DatabaseListError(resp, request, msg)
|
|
245
|
+
|
|
246
|
+
return await self._executor.execute(request, response_handler)
|
|
247
|
+
|
|
248
|
+
async def create_database(
|
|
249
|
+
self,
|
|
250
|
+
name: str,
|
|
251
|
+
users: Optional[Sequence[Json | UserInfo]] = None,
|
|
252
|
+
replication_factor: Optional[int | str] = None,
|
|
253
|
+
write_concern: Optional[int] = None,
|
|
254
|
+
sharding: Optional[bool] = None,
|
|
255
|
+
) -> Result[bool]:
|
|
256
|
+
"""Create a new database.
|
|
257
|
+
|
|
258
|
+
Note:
|
|
259
|
+
This method can only be executed from within the **_system** database.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
name (str): Database name.
|
|
263
|
+
users (list | None): Optional list of users with access to the new
|
|
264
|
+
database, where each user is of :class:`User
|
|
265
|
+
<arangoasync.wrapper.UserInfo>` type, or a dictionary with fields
|
|
266
|
+
"username", "password" and "active". If not set, the default user
|
|
267
|
+
**root** will be used to ensure that the new database will be
|
|
268
|
+
accessible after it is created.
|
|
269
|
+
replication_factor (int | str | None): Default replication factor for new
|
|
270
|
+
collections created in this database. Special values include
|
|
271
|
+
“satellite”, which will replicate the collection to every DB-Server
|
|
272
|
+
(Enterprise Edition only), and 1, which disables replication. Used
|
|
273
|
+
for clusters only.
|
|
274
|
+
write_concern (int | None): Default write concern for collections created
|
|
275
|
+
in this database. Determines how many copies of each shard are required
|
|
276
|
+
to be in sync on different DB-Servers. If there are less than these many
|
|
277
|
+
copies in the cluster a shard will refuse to write. Writes to shards with
|
|
278
|
+
enough up-to-date copies will succeed at the same time, however. Value of
|
|
279
|
+
this parameter can not be larger than the value of **replication_factor**.
|
|
280
|
+
Used for clusters only.
|
|
281
|
+
sharding (str | None): Sharding method used for new collections in this
|
|
282
|
+
database. Allowed values are: "", "flexible" and "single". The first
|
|
283
|
+
two are equivalent. Used for clusters only.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
bool: True if the database was created successfully.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
DatabaseCreateError: If creation fails.
|
|
290
|
+
|
|
291
|
+
References:
|
|
292
|
+
- `create-a-database <https://docs.arangodb.com/stable/develop/http-api/databases/#create-a-database>`__
|
|
293
|
+
""" # noqa: E501
|
|
294
|
+
data: Json = {"name": name}
|
|
295
|
+
|
|
296
|
+
options: Json = {}
|
|
297
|
+
if replication_factor is not None:
|
|
298
|
+
options["replicationFactor"] = replication_factor
|
|
299
|
+
if write_concern is not None:
|
|
300
|
+
options["writeConcern"] = write_concern
|
|
301
|
+
if sharding is not None:
|
|
302
|
+
options["sharding"] = sharding
|
|
303
|
+
if options:
|
|
304
|
+
data["options"] = options
|
|
305
|
+
|
|
306
|
+
if users is not None:
|
|
307
|
+
data["users"] = [
|
|
308
|
+
{
|
|
309
|
+
"username": user["user"] if "user" in user else user["username"],
|
|
310
|
+
"passwd": user["password"],
|
|
311
|
+
"active": user.get("active", True),
|
|
312
|
+
"extra": user.get("extra", {}),
|
|
313
|
+
}
|
|
314
|
+
for user in users
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
request = Request(
|
|
318
|
+
method=Method.POST,
|
|
319
|
+
endpoint="/_api/database",
|
|
320
|
+
data=self.serializer.dumps(data),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def response_handler(resp: Response) -> bool:
|
|
324
|
+
if resp.is_success:
|
|
325
|
+
return True
|
|
326
|
+
msg: Optional[str] = None
|
|
327
|
+
if resp.status_code == HTTP_FORBIDDEN:
|
|
328
|
+
msg = "This request can only be executed in the _system database."
|
|
329
|
+
raise DatabaseCreateError(resp, request, msg)
|
|
330
|
+
|
|
331
|
+
return await self._executor.execute(request, response_handler)
|
|
332
|
+
|
|
333
|
+
async def delete_database(
|
|
334
|
+
self, name: str, ignore_missing: bool = False
|
|
335
|
+
) -> Result[bool]:
|
|
336
|
+
"""Delete a database.
|
|
337
|
+
|
|
338
|
+
Note:
|
|
339
|
+
This method can only be executed from within the **_system** database.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
name (str): Database name.
|
|
343
|
+
ignore_missing (bool): Do not raise an exception on missing database.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
bool: True if the database was deleted successfully, `False` if the
|
|
347
|
+
database was not found but **ignore_missing** was set to `True`.
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
DatabaseDeleteError: If deletion fails.
|
|
351
|
+
|
|
352
|
+
References:
|
|
353
|
+
- `drop-a-database <https://docs.arangodb.com/stable/develop/http-api/databases/#drop-a-database>`__
|
|
354
|
+
""" # noqa: E501
|
|
355
|
+
request = Request(method=Method.DELETE, endpoint=f"/_api/database/{name}")
|
|
356
|
+
|
|
357
|
+
def response_handler(resp: Response) -> bool:
|
|
358
|
+
if resp.is_success:
|
|
359
|
+
return True
|
|
360
|
+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
|
|
361
|
+
return False
|
|
362
|
+
msg: Optional[str] = None
|
|
363
|
+
if resp.status_code == HTTP_FORBIDDEN:
|
|
364
|
+
msg = "This request can only be executed in the _system database."
|
|
365
|
+
raise DatabaseDeleteError(resp, request, msg)
|
|
366
|
+
|
|
367
|
+
return await self._executor.execute(request, response_handler)
|
|
368
|
+
|
|
369
|
+
def collection(
|
|
370
|
+
self,
|
|
371
|
+
name: str,
|
|
372
|
+
doc_serializer: Optional[Serializer[T]] = None,
|
|
373
|
+
doc_deserializer: Optional[Deserializer[U, V]] = None,
|
|
374
|
+
) -> StandardCollection[T, U, V]:
|
|
375
|
+
"""Return the collection API wrapper.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
name (str): Collection name.
|
|
379
|
+
doc_serializer (Serializer): Custom document serializer.
|
|
380
|
+
This will be used only for document operations.
|
|
381
|
+
doc_deserializer (Deserializer): Custom document deserializer.
|
|
382
|
+
This will be used only for document operations.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
StandardCollection: Collection API wrapper.
|
|
386
|
+
"""
|
|
387
|
+
if doc_serializer is None:
|
|
388
|
+
serializer = cast(Serializer[T], self.serializer)
|
|
389
|
+
else:
|
|
390
|
+
serializer = doc_serializer
|
|
391
|
+
if doc_deserializer is None:
|
|
392
|
+
deserializer = cast(Deserializer[U, V], self.deserializer)
|
|
393
|
+
else:
|
|
394
|
+
deserializer = doc_deserializer
|
|
395
|
+
|
|
396
|
+
return StandardCollection[T, U, V](
|
|
397
|
+
self._executor, name, serializer, deserializer
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
async def collections(
|
|
401
|
+
self,
|
|
402
|
+
exclude_system: Optional[bool] = None,
|
|
403
|
+
) -> Result[List[CollectionInfo]]:
|
|
404
|
+
"""Returns basic information for all collections in the current database,
|
|
405
|
+
optionally excluding system collections.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
list: Collection names.
|
|
409
|
+
|
|
410
|
+
Raises:
|
|
411
|
+
CollectionListError: If retrieval fails.
|
|
412
|
+
|
|
413
|
+
References:
|
|
414
|
+
- `list-all-collections <https://docs.arangodb.com/stable/develop/http-api/collections/#list-all-collections>`__
|
|
415
|
+
""" # noqa: E501
|
|
416
|
+
params: Params = {}
|
|
417
|
+
if exclude_system is not None:
|
|
418
|
+
params["excludeSystem"] = exclude_system
|
|
419
|
+
|
|
420
|
+
request = Request(
|
|
421
|
+
method=Method.GET,
|
|
422
|
+
endpoint="/_api/collection",
|
|
423
|
+
params=params,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def response_handler(resp: Response) -> List[CollectionInfo]:
|
|
427
|
+
if not resp.is_success:
|
|
428
|
+
raise CollectionListError(resp, request)
|
|
429
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
430
|
+
return [CollectionInfo(c) for c in body["result"]]
|
|
431
|
+
|
|
432
|
+
return await self._executor.execute(request, response_handler)
|
|
433
|
+
|
|
434
|
+
async def has_collection(self, name: str) -> Result[bool]:
|
|
435
|
+
"""Check if a collection exists in the database.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
name (str): Collection name.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
bool: True if the collection exists, False otherwise.
|
|
442
|
+
|
|
443
|
+
Raises:
|
|
444
|
+
CollectionListError: If retrieval fails.
|
|
445
|
+
"""
|
|
446
|
+
request = Request(method=Method.GET, endpoint=f"/_api/collection/{name}")
|
|
447
|
+
|
|
448
|
+
def response_handler(resp: Response) -> bool:
|
|
449
|
+
if resp.is_success:
|
|
450
|
+
return True
|
|
451
|
+
if resp.status_code == HTTP_NOT_FOUND:
|
|
452
|
+
return False
|
|
453
|
+
raise CollectionListError(resp, request)
|
|
454
|
+
|
|
455
|
+
return await self._executor.execute(request, response_handler)
|
|
456
|
+
|
|
457
|
+
async def create_collection(
|
|
458
|
+
self,
|
|
459
|
+
name: str,
|
|
460
|
+
doc_serializer: Optional[Serializer[T]] = None,
|
|
461
|
+
doc_deserializer: Optional[Deserializer[U, V]] = None,
|
|
462
|
+
col_type: Optional[CollectionType] = None,
|
|
463
|
+
write_concern: Optional[int] = None,
|
|
464
|
+
wait_for_sync: Optional[bool] = None,
|
|
465
|
+
number_of_shards: Optional[int] = None,
|
|
466
|
+
replication_factor: Optional[int] = None,
|
|
467
|
+
cache_enabled: Optional[bool] = None,
|
|
468
|
+
computed_values: Optional[Jsons] = None,
|
|
469
|
+
distribute_shards_like: Optional[str] = None,
|
|
470
|
+
is_system: Optional[bool] = False,
|
|
471
|
+
key_options: Optional[KeyOptions | Json] = None,
|
|
472
|
+
schema: Optional[Json] = None,
|
|
473
|
+
shard_keys: Optional[Sequence[str]] = None,
|
|
474
|
+
sharding_strategy: Optional[str] = None,
|
|
475
|
+
smart_graph_attribute: Optional[str] = None,
|
|
476
|
+
smart_join_attribute: Optional[str] = None,
|
|
477
|
+
wait_for_sync_replication: Optional[bool] = None,
|
|
478
|
+
enforce_replication_factor: Optional[bool] = None,
|
|
479
|
+
) -> Result[StandardCollection[T, U, V]]:
|
|
480
|
+
"""Create a new collection.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
name (str): Collection name.
|
|
484
|
+
doc_serializer (Serializer): Custom document serializer.
|
|
485
|
+
This will be used only for document operations.
|
|
486
|
+
doc_deserializer (Deserializer): Custom document deserializer.
|
|
487
|
+
This will be used only for document operations.
|
|
488
|
+
col_type (CollectionType | int | str | None): Collection type.
|
|
489
|
+
write_concern (int | None): Determines how many copies of each shard are
|
|
490
|
+
required to be in sync on the different DB-Servers.
|
|
491
|
+
wait_for_sync (bool | None): If `True`, the data is synchronised to disk
|
|
492
|
+
before returning from a document create, update, replace or removal
|
|
493
|
+
operation.
|
|
494
|
+
number_of_shards (int | None): In a cluster, this value determines the
|
|
495
|
+
number of shards to create for the collection.
|
|
496
|
+
replication_factor (int | None): In a cluster, this attribute determines
|
|
497
|
+
how many copies of each shard are kept on different DB-Servers.
|
|
498
|
+
cache_enabled (bool | None): Whether the in-memory hash cache for
|
|
499
|
+
documents should be enabled for this collection.
|
|
500
|
+
computed_values (Jsons | None): An optional list of objects, each
|
|
501
|
+
representing a computed value.
|
|
502
|
+
distribute_shards_like (str | None): The name of another collection.
|
|
503
|
+
If this property is set in a cluster, the collection copies the
|
|
504
|
+
replicationFactor, numberOfShards and shardingStrategy properties
|
|
505
|
+
from the specified collection (referred to as the prototype
|
|
506
|
+
collection) and distributes the shards of this collection in the same
|
|
507
|
+
way as the shards of the other collection.
|
|
508
|
+
is_system (bool | None): If `True`, create a system collection.
|
|
509
|
+
In this case, the collection name should start with an underscore.
|
|
510
|
+
key_options (KeyOptions | dict | None): Additional options for key
|
|
511
|
+
generation. You may use a :class:`KeyOptions
|
|
512
|
+
<arangoasync.wrapper.KeyOptions>` object for easier configuration,
|
|
513
|
+
or pass a dictionary directly.
|
|
514
|
+
schema (dict | None): Optional object that specifies the collection
|
|
515
|
+
level schema for documents.
|
|
516
|
+
shard_keys (list | None): In a cluster, this attribute determines which
|
|
517
|
+
document attributes are used to determine the target shard for
|
|
518
|
+
documents.
|
|
519
|
+
sharding_strategy (str | None): Name of the sharding strategy.
|
|
520
|
+
smart_graph_attribute: (str | None): The attribute that is used for
|
|
521
|
+
sharding: vertices with the same value of this attribute are placed
|
|
522
|
+
in the same shard.
|
|
523
|
+
smart_join_attribute: (str | None): Determines an attribute of the
|
|
524
|
+
collection that must contain the shard key value of the referred-to
|
|
525
|
+
SmartJoin collection.
|
|
526
|
+
wait_for_sync_replication (bool | None): If `True`, the server only
|
|
527
|
+
reports success back to the client when all replicas have created
|
|
528
|
+
the collection. Set it to `False` if you want faster server
|
|
529
|
+
responses and don’t care about full replication.
|
|
530
|
+
enforce_replication_factor (bool | None): If `True`, the server checks
|
|
531
|
+
if there are enough replicas available at creation time and bail out
|
|
532
|
+
otherwise. Set it to `False` to disable this extra check.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
StandardCollection: Collection API wrapper.
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
ValueError: If parameters are invalid.
|
|
539
|
+
CollectionCreateError: If the operation fails.
|
|
540
|
+
|
|
541
|
+
References:
|
|
542
|
+
- `create-a-collection <https://docs.arangodb.com/stable/develop/http-api/collections/#create-a-collection>`__
|
|
543
|
+
""" # noqa: E501
|
|
544
|
+
data: Json = {"name": name}
|
|
545
|
+
if col_type is not None:
|
|
546
|
+
if isinstance(col_type, int):
|
|
547
|
+
col_type = CollectionType.from_int(col_type)
|
|
548
|
+
elif isinstance(col_type, str):
|
|
549
|
+
col_type = CollectionType.from_str(col_type)
|
|
550
|
+
elif not isinstance(col_type, CollectionType):
|
|
551
|
+
raise ValueError("Invalid collection type")
|
|
552
|
+
data["type"] = col_type.value
|
|
553
|
+
if write_concern is not None:
|
|
554
|
+
data["writeConcern"] = write_concern
|
|
555
|
+
if wait_for_sync is not None:
|
|
556
|
+
data["waitForSync"] = wait_for_sync
|
|
557
|
+
if number_of_shards is not None:
|
|
558
|
+
data["numberOfShards"] = number_of_shards
|
|
559
|
+
if replication_factor is not None:
|
|
560
|
+
data["replicationFactor"] = replication_factor
|
|
561
|
+
if cache_enabled is not None:
|
|
562
|
+
data["cacheEnabled"] = cache_enabled
|
|
563
|
+
if computed_values is not None:
|
|
564
|
+
data["computedValues"] = computed_values
|
|
565
|
+
if distribute_shards_like is not None:
|
|
566
|
+
data["distributeShardsLike"] = distribute_shards_like
|
|
567
|
+
if is_system is not None:
|
|
568
|
+
data["isSystem"] = is_system
|
|
569
|
+
if key_options is not None:
|
|
570
|
+
if isinstance(key_options, dict):
|
|
571
|
+
key_options = KeyOptions(data=key_options)
|
|
572
|
+
key_options.validate()
|
|
573
|
+
data["keyOptions"] = key_options.to_dict()
|
|
574
|
+
if schema is not None:
|
|
575
|
+
data["schema"] = schema
|
|
576
|
+
if shard_keys is not None:
|
|
577
|
+
data["shardKeys"] = shard_keys
|
|
578
|
+
if sharding_strategy is not None:
|
|
579
|
+
data["shardingStrategy"] = sharding_strategy
|
|
580
|
+
if smart_graph_attribute is not None:
|
|
581
|
+
data["smartGraphAttribute"] = smart_graph_attribute
|
|
582
|
+
if smart_join_attribute is not None:
|
|
583
|
+
data["smartJoinAttribute"] = smart_join_attribute
|
|
584
|
+
|
|
585
|
+
params: Params = {}
|
|
586
|
+
if wait_for_sync_replication is not None:
|
|
587
|
+
params["waitForSyncReplication"] = wait_for_sync_replication
|
|
588
|
+
if enforce_replication_factor is not None:
|
|
589
|
+
params["enforceReplicationFactor"] = enforce_replication_factor
|
|
590
|
+
|
|
591
|
+
request = Request(
|
|
592
|
+
method=Method.POST,
|
|
593
|
+
endpoint="/_api/collection",
|
|
594
|
+
data=self.serializer.dumps(data),
|
|
595
|
+
params=params,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def response_handler(resp: Response) -> StandardCollection[T, U, V]:
|
|
599
|
+
nonlocal doc_serializer, doc_deserializer
|
|
600
|
+
if not resp.is_success:
|
|
601
|
+
raise CollectionCreateError(resp, request)
|
|
602
|
+
if doc_serializer is None:
|
|
603
|
+
serializer = cast(Serializer[T], self.serializer)
|
|
604
|
+
else:
|
|
605
|
+
serializer = doc_serializer
|
|
606
|
+
if doc_deserializer is None:
|
|
607
|
+
deserializer = cast(Deserializer[U, V], self.deserializer)
|
|
608
|
+
else:
|
|
609
|
+
deserializer = doc_deserializer
|
|
610
|
+
return StandardCollection[T, U, V](
|
|
611
|
+
self._executor, name, serializer, deserializer
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
return await self._executor.execute(request, response_handler)
|
|
615
|
+
|
|
616
|
+
async def delete_collection(
|
|
617
|
+
self,
|
|
618
|
+
name: str,
|
|
619
|
+
ignore_missing: bool = False,
|
|
620
|
+
is_system: Optional[bool] = None,
|
|
621
|
+
) -> Result[bool]:
|
|
622
|
+
"""Delete a collection.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
name (str): Collection name.
|
|
626
|
+
ignore_missing (bool): Do not raise an exception on missing collection.
|
|
627
|
+
is_system (bool | None): Whether to drop a system collection. This parameter
|
|
628
|
+
must be set to `True` in order to drop a system collection.
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
bool: True if the collection was deleted successfully, `False` if the
|
|
632
|
+
collection was not found but **ignore_missing** was set to `True`.
|
|
633
|
+
|
|
634
|
+
Raises:
|
|
635
|
+
CollectionDeleteError: If the operation fails.
|
|
636
|
+
|
|
637
|
+
References:
|
|
638
|
+
- `drop-a-collection <https://docs.arangodb.com/stable/develop/http-api/collections/#drop-a-collection>`__
|
|
639
|
+
""" # noqa: E501
|
|
640
|
+
params: Params = {}
|
|
641
|
+
if is_system is not None:
|
|
642
|
+
params["isSystem"] = is_system
|
|
643
|
+
|
|
644
|
+
request = Request(
|
|
645
|
+
method=Method.DELETE,
|
|
646
|
+
endpoint=f"/_api/collection/{name}",
|
|
647
|
+
params=params,
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
def response_handler(resp: Response) -> bool:
|
|
651
|
+
nonlocal ignore_missing
|
|
652
|
+
if resp.is_success:
|
|
653
|
+
return True
|
|
654
|
+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
|
|
655
|
+
return False
|
|
656
|
+
raise CollectionDeleteError(resp, request)
|
|
657
|
+
|
|
658
|
+
return await self._executor.execute(request, response_handler)
|
|
659
|
+
|
|
660
|
+
async def has_user(self, username: str) -> Result[bool]:
|
|
661
|
+
"""Check if a user exists.
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
username (str): Username.
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
bool: True if the user exists, False otherwise.
|
|
668
|
+
|
|
669
|
+
Raises:
|
|
670
|
+
UserListError: If the operation fails.
|
|
671
|
+
"""
|
|
672
|
+
request = Request(method=Method.GET, endpoint=f"/_api/user/{username}")
|
|
673
|
+
|
|
674
|
+
def response_handler(resp: Response) -> bool:
|
|
675
|
+
if resp.is_success:
|
|
676
|
+
return True
|
|
677
|
+
if resp.status_code == HTTP_NOT_FOUND:
|
|
678
|
+
return False
|
|
679
|
+
raise UserListError(resp, request)
|
|
680
|
+
|
|
681
|
+
return await self._executor.execute(request, response_handler)
|
|
682
|
+
|
|
683
|
+
async def user(self, username: str) -> Result[UserInfo]:
|
|
684
|
+
"""Fetches data about a user.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
username (str): Username.
|
|
688
|
+
|
|
689
|
+
Returns:
|
|
690
|
+
UserInfo: User details.
|
|
691
|
+
|
|
692
|
+
Raises:
|
|
693
|
+
UserGetError: If the operation fails.
|
|
694
|
+
|
|
695
|
+
References:
|
|
696
|
+
- `get-a-user` <https://docs.arangodb.com/stable/develop/http-api/users/#get-a-user>`__
|
|
697
|
+
""" # noqa: E501
|
|
698
|
+
request = Request(method=Method.GET, endpoint=f"/_api/user/{username}")
|
|
699
|
+
|
|
700
|
+
def response_handler(resp: Response) -> UserInfo:
|
|
701
|
+
if not resp.is_success:
|
|
702
|
+
raise UserGetError(resp, request)
|
|
703
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
704
|
+
return UserInfo(
|
|
705
|
+
user=body["user"],
|
|
706
|
+
active=cast(bool, body.get("active")),
|
|
707
|
+
extra=body.get("extra"),
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
return await self._executor.execute(request, response_handler)
|
|
711
|
+
|
|
712
|
+
async def users(self) -> Result[Sequence[UserInfo]]:
|
|
713
|
+
"""Fetches data about all users.
|
|
714
|
+
|
|
715
|
+
Without the necessary permissions, you might only get data about the
|
|
716
|
+
current user.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
list: User information.
|
|
720
|
+
|
|
721
|
+
Raises:
|
|
722
|
+
UserListError: If the operation fails.
|
|
723
|
+
|
|
724
|
+
References:
|
|
725
|
+
- `list-available-users <https://docs.arangodb.com/stable/develop/http-api/users/#list-available-users>`__
|
|
726
|
+
""" # noqa: E501
|
|
727
|
+
request = Request(method=Method.GET, endpoint="/_api/user")
|
|
728
|
+
|
|
729
|
+
def response_handler(resp: Response) -> Sequence[UserInfo]:
|
|
730
|
+
if not resp.is_success:
|
|
731
|
+
raise UserListError(resp, request)
|
|
732
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
733
|
+
return [
|
|
734
|
+
UserInfo(user=u["user"], active=u.get("active"), extra=u.get("extra"))
|
|
735
|
+
for u in body["result"]
|
|
736
|
+
]
|
|
737
|
+
|
|
738
|
+
return await self._executor.execute(request, response_handler)
|
|
739
|
+
|
|
740
|
+
async def create_user(self, user: UserInfo | Json) -> Result[UserInfo]:
|
|
741
|
+
"""Create a new user.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
user (UserInfo | dict): User information.
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
UserInfo: New user details.
|
|
748
|
+
|
|
749
|
+
Raises:
|
|
750
|
+
ValueError: If the username is missing.
|
|
751
|
+
UserCreateError: If the operation fails.
|
|
752
|
+
|
|
753
|
+
Example:
|
|
754
|
+
.. code-block:: python
|
|
755
|
+
|
|
756
|
+
await db.create_user(UserInfo(user="john", password="secret"))
|
|
757
|
+
await db.create_user({user="john", password="secret"})
|
|
758
|
+
|
|
759
|
+
References:
|
|
760
|
+
- `create-a-user <https://docs.arangodb.com/stable/develop/http-api/users/#create-a-user>`__
|
|
761
|
+
""" # noqa: E501
|
|
762
|
+
if isinstance(user, dict):
|
|
763
|
+
user = UserInfo(**user)
|
|
764
|
+
if not user.user:
|
|
765
|
+
raise ValueError("Username is required.")
|
|
766
|
+
|
|
767
|
+
data: Json = user.format(UserInfo.user_management_formatter)
|
|
768
|
+
request = Request(
|
|
769
|
+
method=Method.POST,
|
|
770
|
+
endpoint="/_api/user",
|
|
771
|
+
data=self.serializer.dumps(data),
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
def response_handler(resp: Response) -> UserInfo:
|
|
775
|
+
if not resp.is_success:
|
|
776
|
+
raise UserCreateError(resp, request)
|
|
777
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
778
|
+
return UserInfo(
|
|
779
|
+
user=body["user"],
|
|
780
|
+
active=cast(bool, body.get("active")),
|
|
781
|
+
extra=body.get("extra"),
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
return await self._executor.execute(request, response_handler)
|
|
785
|
+
|
|
786
|
+
async def replace_user(self, user: UserInfo | Json) -> Result[UserInfo]:
|
|
787
|
+
"""Replace the data of an existing user.
|
|
788
|
+
|
|
789
|
+
Args:
|
|
790
|
+
user (UserInfo | dict): New user information.
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
UserInfo: New user details.
|
|
794
|
+
|
|
795
|
+
Raises:
|
|
796
|
+
ValueError: If the username is missing.
|
|
797
|
+
UserReplaceError: If the operation fails.
|
|
798
|
+
|
|
799
|
+
References:
|
|
800
|
+
- `replace-a-user <https://docs.arangodb.com/stable/develop/http-api/users/#replace-a-user>`__
|
|
801
|
+
""" # noqa: E501
|
|
802
|
+
if isinstance(user, dict):
|
|
803
|
+
user = UserInfo(**user)
|
|
804
|
+
if not user.user:
|
|
805
|
+
raise ValueError("Username is required.")
|
|
806
|
+
|
|
807
|
+
data: Json = user.format(UserInfo.user_management_formatter)
|
|
808
|
+
request = Request(
|
|
809
|
+
method=Method.PUT,
|
|
810
|
+
endpoint=f"/_api/user/{user.user}",
|
|
811
|
+
data=self.serializer.dumps(data),
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
def response_handler(resp: Response) -> UserInfo:
|
|
815
|
+
if not resp.is_success:
|
|
816
|
+
raise UserReplaceError(resp, request)
|
|
817
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
818
|
+
return UserInfo(
|
|
819
|
+
user=body["user"],
|
|
820
|
+
active=cast(bool, body.get("active")),
|
|
821
|
+
extra=body.get("extra"),
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
return await self._executor.execute(request, response_handler)
|
|
825
|
+
|
|
826
|
+
async def update_user(self, user: UserInfo | Json) -> Result[UserInfo]:
|
|
827
|
+
"""Partially modifies the data of an existing user.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
user (UserInfo | dict): User information.
|
|
831
|
+
|
|
832
|
+
Returns:
|
|
833
|
+
UserInfo: Updated user details.
|
|
834
|
+
|
|
835
|
+
Raises:
|
|
836
|
+
ValueError: If the username is missing.
|
|
837
|
+
UserUpdateError: If the operation fails.
|
|
838
|
+
|
|
839
|
+
References:
|
|
840
|
+
- `update-a-user <https://docs.arangodb.com/stable/develop/http-api/users/#update-a-user>`__
|
|
841
|
+
""" # noqa: E501
|
|
842
|
+
if isinstance(user, dict):
|
|
843
|
+
user = UserInfo(**user)
|
|
844
|
+
if not user.user:
|
|
845
|
+
raise ValueError("Username is required.")
|
|
846
|
+
|
|
847
|
+
data: Json = user.format(UserInfo.user_management_formatter)
|
|
848
|
+
request = Request(
|
|
849
|
+
method=Method.PATCH,
|
|
850
|
+
endpoint=f"/_api/user/{user.user}",
|
|
851
|
+
data=self.serializer.dumps(data),
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
def response_handler(resp: Response) -> UserInfo:
|
|
855
|
+
if not resp.is_success:
|
|
856
|
+
raise UserUpdateError(resp, request)
|
|
857
|
+
body = self.deserializer.loads(resp.raw_body)
|
|
858
|
+
return UserInfo(
|
|
859
|
+
user=body["user"],
|
|
860
|
+
active=cast(bool, body.get("active")),
|
|
861
|
+
extra=body.get("extra"),
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
return await self._executor.execute(request, response_handler)
|
|
865
|
+
|
|
866
|
+
async def delete_user(
|
|
867
|
+
self,
|
|
868
|
+
username: str,
|
|
869
|
+
ignore_missing: bool = False,
|
|
870
|
+
) -> Result[bool]:
|
|
871
|
+
"""Delete a user.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
username (str): Username.
|
|
875
|
+
ignore_missing (bool): Do not raise an exception on missing user.
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
bool: True if the user was deleted successfully, `False` if the user was
|
|
879
|
+
not found but **ignore_missing** was set to `True`.
|
|
880
|
+
|
|
881
|
+
Raises:
|
|
882
|
+
UserDeleteError: If the operation fails.
|
|
883
|
+
|
|
884
|
+
References:
|
|
885
|
+
- `remove-a-user <https://docs.arangodb.com/stable/develop/http-api/users/#remove-a-user>`__
|
|
886
|
+
""" # noqa: E501
|
|
887
|
+
request = Request(method=Method.DELETE, endpoint=f"/_api/user/{username}")
|
|
888
|
+
|
|
889
|
+
def response_handler(resp: Response) -> bool:
|
|
890
|
+
if resp.is_success:
|
|
891
|
+
return True
|
|
892
|
+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
|
|
893
|
+
return False
|
|
894
|
+
raise UserDeleteError(resp, request)
|
|
895
|
+
|
|
896
|
+
return await self._executor.execute(request, response_handler)
|
|
897
|
+
|
|
898
|
+
async def permissions(self, username: str, full: bool = True) -> Result[Json]:
|
|
899
|
+
"""Return user permissions for all databases and collections.
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
username (str): Username.
|
|
903
|
+
full (bool): If `True`, the result will contain the permissions for the
|
|
904
|
+
databases as well as the permissions for the collections.
|
|
905
|
+
|
|
906
|
+
Returns:
|
|
907
|
+
dict: User permissions for all databases and (optionally) collections.
|
|
908
|
+
|
|
909
|
+
Raises:
|
|
910
|
+
PermissionListError: If the operation fails.
|
|
911
|
+
|
|
912
|
+
References:
|
|
913
|
+
- `list-a-users-accessible-databases <https://docs.arangodb.com/stable/develop/http-api/users/#list-a-users-accessible-databases>`__
|
|
914
|
+
""" # noqa: 501
|
|
915
|
+
request = Request(
|
|
916
|
+
method=Method.GET,
|
|
917
|
+
endpoint=f"/_api/user/{username}/database",
|
|
918
|
+
params={"full": full},
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
def response_handler(resp: Response) -> Json:
|
|
922
|
+
if resp.is_success:
|
|
923
|
+
result: Json = self.deserializer.loads(resp.raw_body)["result"]
|
|
924
|
+
return result
|
|
925
|
+
raise PermissionListError(resp, request)
|
|
926
|
+
|
|
927
|
+
return await self._executor.execute(request, response_handler)
|
|
928
|
+
|
|
929
|
+
async def permission(
|
|
930
|
+
self,
|
|
931
|
+
username: str,
|
|
932
|
+
database: str,
|
|
933
|
+
collection: Optional[str] = None,
|
|
934
|
+
) -> Result[str]:
|
|
935
|
+
"""Return user permission for a specific database or collection.
|
|
936
|
+
|
|
937
|
+
Args:
|
|
938
|
+
username (str): Username.
|
|
939
|
+
database (str): Database name.
|
|
940
|
+
collection (str | None): Collection name.
|
|
941
|
+
|
|
942
|
+
Returns:
|
|
943
|
+
str: User access level.
|
|
944
|
+
|
|
945
|
+
Raises:
|
|
946
|
+
PermissionGetError: If the operation fails.
|
|
947
|
+
|
|
948
|
+
References:
|
|
949
|
+
- `get-a-users-database-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#get-a-users-database-access-level>`__
|
|
950
|
+
- `get-a-users-collection-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#get-a-users-collection-access-level>`__
|
|
951
|
+
""" # noqa: 501
|
|
952
|
+
endpoint = f"/_api/user/{username}/database/{database}"
|
|
953
|
+
if collection is not None:
|
|
954
|
+
endpoint += f"/{collection}"
|
|
955
|
+
request = Request(method=Method.GET, endpoint=endpoint)
|
|
956
|
+
|
|
957
|
+
def response_handler(resp: Response) -> str:
|
|
958
|
+
if resp.is_success:
|
|
959
|
+
return cast(str, self.deserializer.loads(resp.raw_body)["result"])
|
|
960
|
+
raise PermissionGetError(resp, request)
|
|
961
|
+
|
|
962
|
+
return await self._executor.execute(request, response_handler)
|
|
963
|
+
|
|
964
|
+
async def update_permission(
|
|
965
|
+
self,
|
|
966
|
+
username: str,
|
|
967
|
+
permission: str,
|
|
968
|
+
database: str,
|
|
969
|
+
collection: Optional[str] = None,
|
|
970
|
+
ignore_failure: bool = False,
|
|
971
|
+
) -> Result[bool]:
|
|
972
|
+
"""Update user permissions for a specific database or collection.
|
|
973
|
+
|
|
974
|
+
Args:
|
|
975
|
+
username (str): Username.
|
|
976
|
+
permission (str): Allowed values are "rw" (administrate),
|
|
977
|
+
"ro" (access) and "none" (no access).
|
|
978
|
+
database (str): Database to set the access level for.
|
|
979
|
+
collection (str | None): Collection to set the access level for.
|
|
980
|
+
ignore_failure (bool): Do not raise an exception on failure.
|
|
981
|
+
|
|
982
|
+
Returns:
|
|
983
|
+
bool: `True` if the operation was successful.
|
|
984
|
+
|
|
985
|
+
Raises:
|
|
986
|
+
PermissionUpdateError: If the operation fails and `ignore_failure`
|
|
987
|
+
is `False`.
|
|
988
|
+
|
|
989
|
+
References:
|
|
990
|
+
- `set-a-users-database-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#set-a-users-database-access-level>`__
|
|
991
|
+
- `set-a-users-collection-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#set-a-users-collection-access-level>`__
|
|
992
|
+
""" # noqa: E501
|
|
993
|
+
endpoint = f"/_api/user/{username}/database/{database}"
|
|
994
|
+
if collection is not None:
|
|
995
|
+
endpoint += f"/{collection}"
|
|
996
|
+
|
|
997
|
+
request = Request(
|
|
998
|
+
method=Method.PUT,
|
|
999
|
+
endpoint=endpoint,
|
|
1000
|
+
data=self.serializer.dumps({"grant": permission}),
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
def response_handler(resp: Response) -> bool:
|
|
1004
|
+
nonlocal ignore_failure
|
|
1005
|
+
if resp.is_success:
|
|
1006
|
+
return True
|
|
1007
|
+
if ignore_failure:
|
|
1008
|
+
return False
|
|
1009
|
+
raise PermissionUpdateError(resp, request)
|
|
1010
|
+
|
|
1011
|
+
return await self._executor.execute(request, response_handler)
|
|
1012
|
+
|
|
1013
|
+
async def reset_permission(
|
|
1014
|
+
self,
|
|
1015
|
+
username: str,
|
|
1016
|
+
database: str,
|
|
1017
|
+
collection: Optional[str] = None,
|
|
1018
|
+
ignore_failure: bool = False,
|
|
1019
|
+
) -> Result[bool]:
|
|
1020
|
+
"""Reset user permission for a specific database or collection.
|
|
1021
|
+
|
|
1022
|
+
Args:
|
|
1023
|
+
username (str): Username.
|
|
1024
|
+
database (str): Database to reset the access level for.
|
|
1025
|
+
collection (str | None): Collection to reset the access level for.
|
|
1026
|
+
ignore_failure (bool): Do not raise an exception on failure.
|
|
1027
|
+
|
|
1028
|
+
Returns:
|
|
1029
|
+
bool: `True` if the operation was successful.
|
|
1030
|
+
|
|
1031
|
+
Raises:
|
|
1032
|
+
PermissionResetError: If the operation fails and `ignore_failure`
|
|
1033
|
+
is `False`.
|
|
1034
|
+
|
|
1035
|
+
References:
|
|
1036
|
+
- `clear-a-users-database-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#clear-a-users-database-access-level>`__
|
|
1037
|
+
- `clear-a-users-collection-access-level <https://docs.arangodb.com/stable/develop/http-api/users/#clear-a-users-collection-access-level>`__
|
|
1038
|
+
""" # noqa: E501
|
|
1039
|
+
endpoint = f"/_api/user/{username}/database/{database}"
|
|
1040
|
+
if collection is not None:
|
|
1041
|
+
endpoint += f"/{collection}"
|
|
1042
|
+
|
|
1043
|
+
request = Request(
|
|
1044
|
+
method=Method.DELETE,
|
|
1045
|
+
endpoint=endpoint,
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
def response_handler(resp: Response) -> bool:
|
|
1049
|
+
nonlocal ignore_failure
|
|
1050
|
+
if resp.is_success:
|
|
1051
|
+
return True
|
|
1052
|
+
if ignore_failure:
|
|
1053
|
+
return False
|
|
1054
|
+
raise PermissionResetError(resp, request)
|
|
1055
|
+
|
|
1056
|
+
return await self._executor.execute(request, response_handler)
|
|
1057
|
+
|
|
1058
|
+
async def jwt_secrets(self) -> Result[Json]:
|
|
1059
|
+
"""Return information on currently loaded JWT secrets.
|
|
1060
|
+
|
|
1061
|
+
Returns:
|
|
1062
|
+
dict: JWT secrets.
|
|
1063
|
+
|
|
1064
|
+
Raises:
|
|
1065
|
+
JWTSecretListError: If the operation fails.
|
|
1066
|
+
|
|
1067
|
+
References:
|
|
1068
|
+
- `get-information-about-the-loaded-jwt-secrets <https://docs.arangodb.com/stable/develop/http-api/authentication/#get-information-about-the-loaded-jwt-secrets>`__
|
|
1069
|
+
""" # noqa: 501
|
|
1070
|
+
request = Request(method=Method.GET, endpoint="/_admin/server/jwt")
|
|
1071
|
+
|
|
1072
|
+
def response_handler(resp: Response) -> Json:
|
|
1073
|
+
if not resp.is_success:
|
|
1074
|
+
raise JWTSecretListError(resp, request)
|
|
1075
|
+
result: Json = self.deserializer.loads(resp.raw_body)
|
|
1076
|
+
return result
|
|
1077
|
+
|
|
1078
|
+
return await self._executor.execute(request, response_handler)
|
|
1079
|
+
|
|
1080
|
+
async def reload_jwt_secrets(self) -> Result[Json]:
|
|
1081
|
+
"""Hot_reload JWT secrets from disk.
|
|
1082
|
+
|
|
1083
|
+
Returns:
|
|
1084
|
+
dict: Information on reloaded JWT secrets.
|
|
1085
|
+
|
|
1086
|
+
Raises:
|
|
1087
|
+
JWTSecretReloadError: If the operation fails.
|
|
1088
|
+
|
|
1089
|
+
References:
|
|
1090
|
+
- `hot-reload-the-jwt-secrets-from-disk <https://docs.arangodb.com/stable/develop/http-api/authentication/#hot-reload-the-jwt-secrets-from-disk>`__
|
|
1091
|
+
""" # noqa: 501
|
|
1092
|
+
request = Request(method=Method.POST, endpoint="/_admin/server/jwt")
|
|
1093
|
+
|
|
1094
|
+
def response_handler(resp: Response) -> Json:
|
|
1095
|
+
if not resp.is_success:
|
|
1096
|
+
raise JWTSecretReloadError(resp, request)
|
|
1097
|
+
result: Json = self.deserializer.loads(resp.raw_body)
|
|
1098
|
+
return result
|
|
1099
|
+
|
|
1100
|
+
return await self._executor.execute(request, response_handler)
|
|
1101
|
+
|
|
1102
|
+
async def list_transactions(self) -> Result[Jsons]:
|
|
1103
|
+
"""List all currently running stream transactions.
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
list: List of transactions, with each transaction containing
|
|
1107
|
+
an "id" and a "state" field.
|
|
1108
|
+
|
|
1109
|
+
Raises:
|
|
1110
|
+
TransactionListError: If the operation fails on the server side.
|
|
1111
|
+
"""
|
|
1112
|
+
request = Request(method=Method.GET, endpoint="/_api/transaction")
|
|
1113
|
+
|
|
1114
|
+
def response_handler(resp: Response) -> Jsons:
|
|
1115
|
+
if not resp.is_success:
|
|
1116
|
+
raise TransactionListError(resp, request)
|
|
1117
|
+
result: Json = self.deserializer.loads(resp.raw_body)
|
|
1118
|
+
return cast(Jsons, result["transactions"])
|
|
1119
|
+
|
|
1120
|
+
return await self._executor.execute(request, response_handler)
|
|
1121
|
+
|
|
1122
|
+
async def execute_transaction(
|
|
1123
|
+
self,
|
|
1124
|
+
command: str,
|
|
1125
|
+
params: Optional[Json] = None,
|
|
1126
|
+
read: Optional[str | Sequence[str]] = None,
|
|
1127
|
+
write: Optional[str | Sequence[str]] = None,
|
|
1128
|
+
exclusive: Optional[str | Sequence[str]] = None,
|
|
1129
|
+
allow_implicit: Optional[bool] = None,
|
|
1130
|
+
wait_for_sync: Optional[bool] = None,
|
|
1131
|
+
lock_timeout: Optional[int] = None,
|
|
1132
|
+
max_transaction_size: Optional[int] = None,
|
|
1133
|
+
) -> Result[Any]:
|
|
1134
|
+
"""Execute a JavaScript Transaction.
|
|
1135
|
+
|
|
1136
|
+
Warning:
|
|
1137
|
+
JavaScript Transactions are deprecated from ArangoDB v3.12.0 onward and
|
|
1138
|
+
will be removed in a future version.
|
|
1139
|
+
|
|
1140
|
+
Args:
|
|
1141
|
+
command (str): The actual transaction operations to be executed, in the
|
|
1142
|
+
form of stringified JavaScript code.
|
|
1143
|
+
params (dict): Optional parameters passed into the JavaScript command.
|
|
1144
|
+
read (str | list | None): Name(s) of collections read during transaction.
|
|
1145
|
+
write (str | list | None): Name(s) of collections written to during
|
|
1146
|
+
transaction with shared access.
|
|
1147
|
+
exclusive (str | list | None): Name(s) of collections written to during
|
|
1148
|
+
transaction with exclusive access.
|
|
1149
|
+
allow_implicit (bool | None): Allow reading from undeclared collections.
|
|
1150
|
+
wait_for_sync (bool | None): If `True`, will force the transaction to write
|
|
1151
|
+
all data to disk before returning.
|
|
1152
|
+
lock_timeout (int | None): Timeout for waiting on collection locks. Setting
|
|
1153
|
+
it to 0 will prevent ArangoDB from timing out while waiting for a lock.
|
|
1154
|
+
max_transaction_size (int | None): Transaction size limit in bytes.
|
|
1155
|
+
|
|
1156
|
+
Returns:
|
|
1157
|
+
Any: Result of the transaction.
|
|
1158
|
+
|
|
1159
|
+
Raises:
|
|
1160
|
+
TransactionExecuteError: If the operation fails on the server side.
|
|
1161
|
+
|
|
1162
|
+
References:
|
|
1163
|
+
- `execute-a-javascript-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/javascript-transactions/#execute-a-javascript-transaction>`__
|
|
1164
|
+
""" # noqa: 501
|
|
1165
|
+
m = "JavaScript Transactions are deprecated from ArangoDB v3.12.0 onward and will be removed in a future version." # noqa: E501
|
|
1166
|
+
warn(m, DeprecationWarning, stacklevel=2)
|
|
1167
|
+
|
|
1168
|
+
collections = dict()
|
|
1169
|
+
if read is not None:
|
|
1170
|
+
collections["read"] = read
|
|
1171
|
+
if write is not None:
|
|
1172
|
+
collections["write"] = write
|
|
1173
|
+
if exclusive is not None:
|
|
1174
|
+
collections["exclusive"] = exclusive
|
|
1175
|
+
|
|
1176
|
+
data: Json = dict(collections=collections, action=command)
|
|
1177
|
+
if params is not None:
|
|
1178
|
+
data["params"] = params
|
|
1179
|
+
if wait_for_sync is not None:
|
|
1180
|
+
data["waitForSync"] = wait_for_sync
|
|
1181
|
+
if allow_implicit is not None:
|
|
1182
|
+
data["allowImplicit"] = allow_implicit
|
|
1183
|
+
if lock_timeout is not None:
|
|
1184
|
+
data["lockTimeout"] = lock_timeout
|
|
1185
|
+
if max_transaction_size is not None:
|
|
1186
|
+
data["maxTransactionSize"] = max_transaction_size
|
|
1187
|
+
|
|
1188
|
+
request = Request(
|
|
1189
|
+
method=Method.POST,
|
|
1190
|
+
endpoint="/_api/transaction",
|
|
1191
|
+
data=self.serializer.dumps(data),
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
def response_handler(resp: Response) -> Any:
|
|
1195
|
+
if not resp.is_success:
|
|
1196
|
+
raise TransactionExecuteError(resp, request)
|
|
1197
|
+
return self.deserializer.loads(resp.raw_body)["result"]
|
|
1198
|
+
|
|
1199
|
+
return await self._executor.execute(request, response_handler)
|
|
1200
|
+
|
|
1201
|
+
async def version(self, details: bool = False) -> Result[Json]:
|
|
1202
|
+
"""Return the server version information.
|
|
1203
|
+
|
|
1204
|
+
Args:
|
|
1205
|
+
details (bool): If `True`, return detailed version information.
|
|
1206
|
+
|
|
1207
|
+
Returns:
|
|
1208
|
+
dict: Server version information.
|
|
1209
|
+
|
|
1210
|
+
Raises:
|
|
1211
|
+
ServerVersionError: If the operation fails on the server side.
|
|
1212
|
+
|
|
1213
|
+
References:
|
|
1214
|
+
- `get-the-server-version <https://docs.arangodb.com/stable/develop/http-api/administration/#get-the-server-version>`__
|
|
1215
|
+
""" # noqa: E501
|
|
1216
|
+
request = Request(
|
|
1217
|
+
method=Method.GET, endpoint="/_api/version", params={"details": details}
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
def response_handler(resp: Response) -> Json:
|
|
1221
|
+
if not resp.is_success:
|
|
1222
|
+
raise ServerVersionError(resp, request)
|
|
1223
|
+
return self.deserializer.loads(resp.raw_body)
|
|
1224
|
+
|
|
1225
|
+
return await self._executor.execute(request, response_handler)
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
class StandardDatabase(Database):
|
|
1229
|
+
"""Standard database API wrapper.
|
|
1230
|
+
|
|
1231
|
+
Args:
|
|
1232
|
+
connection (Connection): Connection object to be used by the API executor.
|
|
1233
|
+
"""
|
|
1234
|
+
|
|
1235
|
+
def __init__(self, connection: Connection) -> None:
|
|
1236
|
+
super().__init__(DefaultApiExecutor(connection))
|
|
1237
|
+
|
|
1238
|
+
def __repr__(self) -> str:
|
|
1239
|
+
return f"<StandardDatabase {self.name}>"
|
|
1240
|
+
|
|
1241
|
+
async def begin_transaction(
|
|
1242
|
+
self,
|
|
1243
|
+
read: Optional[str | Sequence[str]] = None,
|
|
1244
|
+
write: Optional[str | Sequence[str]] = None,
|
|
1245
|
+
exclusive: Optional[str | Sequence[str]] = None,
|
|
1246
|
+
wait_for_sync: Optional[bool] = None,
|
|
1247
|
+
allow_implicit: Optional[bool] = None,
|
|
1248
|
+
lock_timeout: Optional[int] = None,
|
|
1249
|
+
max_transaction_size: Optional[int] = None,
|
|
1250
|
+
allow_dirty_read: Optional[bool] = None,
|
|
1251
|
+
skip_fast_lock_round: Optional[bool] = None,
|
|
1252
|
+
) -> "TransactionDatabase":
|
|
1253
|
+
"""Begin a Stream Transaction.
|
|
1254
|
+
|
|
1255
|
+
Args:
|
|
1256
|
+
read (str | list | None): Name(s) of collections read during transaction.
|
|
1257
|
+
Read-only collections are added lazily but should be declared if
|
|
1258
|
+
possible to avoid deadlocks.
|
|
1259
|
+
write (str | list | None): Name(s) of collections written to during
|
|
1260
|
+
transaction with shared access.
|
|
1261
|
+
exclusive (str | list | None): Name(s) of collections written to during
|
|
1262
|
+
transaction with exclusive access.
|
|
1263
|
+
wait_for_sync (bool | None): If `True`, will force the transaction to write
|
|
1264
|
+
all data to disk before returning
|
|
1265
|
+
allow_implicit (bool | None): Allow reading from undeclared collections.
|
|
1266
|
+
lock_timeout (int | None): Timeout for waiting on collection locks. Setting
|
|
1267
|
+
it to 0 will prevent ArangoDB from timing out while waiting for a lock.
|
|
1268
|
+
max_transaction_size (int | None): Transaction size limit in bytes.
|
|
1269
|
+
allow_dirty_read (bool | None): If `True`, allows the Coordinator to ask any
|
|
1270
|
+
shard replica for the data, not only the shard leader. This may result
|
|
1271
|
+
in “dirty reads”. This setting decides about dirty reads for the entire
|
|
1272
|
+
transaction. Individual read operations, that are performed as part of
|
|
1273
|
+
the transaction, cannot override it.
|
|
1274
|
+
skip_fast_lock_round (bool | None): Whether to disable fast locking for
|
|
1275
|
+
write operations.
|
|
1276
|
+
|
|
1277
|
+
Returns:
|
|
1278
|
+
TransactionDatabase: Database API wrapper specifically tailored for
|
|
1279
|
+
transactions.
|
|
1280
|
+
|
|
1281
|
+
Raises:
|
|
1282
|
+
TransactionInitError: If the operation fails on the server side.
|
|
1283
|
+
|
|
1284
|
+
References:
|
|
1285
|
+
- `begin-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#begin-a-stream-transaction>`__
|
|
1286
|
+
""" # noqa: E501
|
|
1287
|
+
collections = dict()
|
|
1288
|
+
if read is not None:
|
|
1289
|
+
collections["read"] = read
|
|
1290
|
+
if write is not None:
|
|
1291
|
+
collections["write"] = write
|
|
1292
|
+
if exclusive is not None:
|
|
1293
|
+
collections["exclusive"] = exclusive
|
|
1294
|
+
|
|
1295
|
+
data: Json = dict(collections=collections)
|
|
1296
|
+
if wait_for_sync is not None:
|
|
1297
|
+
data["waitForSync"] = wait_for_sync
|
|
1298
|
+
if allow_implicit is not None:
|
|
1299
|
+
data["allowImplicit"] = allow_implicit
|
|
1300
|
+
if lock_timeout is not None:
|
|
1301
|
+
data["lockTimeout"] = lock_timeout
|
|
1302
|
+
if max_transaction_size is not None:
|
|
1303
|
+
data["maxTransactionSize"] = max_transaction_size
|
|
1304
|
+
if skip_fast_lock_round is not None:
|
|
1305
|
+
data["skipFastLockRound"] = skip_fast_lock_round
|
|
1306
|
+
|
|
1307
|
+
headers = dict()
|
|
1308
|
+
if allow_dirty_read is not None:
|
|
1309
|
+
headers["x-arango-allow-dirty-read"] = str(allow_dirty_read).lower()
|
|
1310
|
+
|
|
1311
|
+
request = Request(
|
|
1312
|
+
method=Method.POST,
|
|
1313
|
+
endpoint="/_api/transaction/begin",
|
|
1314
|
+
data=self.serializer.dumps(data),
|
|
1315
|
+
headers=headers,
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
def response_handler(resp: Response) -> str:
|
|
1319
|
+
if not resp.is_success:
|
|
1320
|
+
raise TransactionInitError(resp, request)
|
|
1321
|
+
result: Json = self.deserializer.loads(resp.raw_body)["result"]
|
|
1322
|
+
return cast(str, result["id"])
|
|
1323
|
+
|
|
1324
|
+
transaction_id = await self._executor.execute(request, response_handler)
|
|
1325
|
+
return TransactionDatabase(self.connection, cast(str, transaction_id))
|
|
1326
|
+
|
|
1327
|
+
def fetch_transaction(self, transaction_id: str) -> "TransactionDatabase":
|
|
1328
|
+
"""Fetch an existing transaction.
|
|
1329
|
+
|
|
1330
|
+
Args:
|
|
1331
|
+
transaction_id (str): Transaction ID.
|
|
1332
|
+
|
|
1333
|
+
Returns:
|
|
1334
|
+
TransactionDatabase: Database API wrapper specifically tailored for
|
|
1335
|
+
transactions.
|
|
1336
|
+
"""
|
|
1337
|
+
return TransactionDatabase(self.connection, transaction_id)
|
|
1338
|
+
|
|
1339
|
+
def begin_async_execution(self, return_result: bool = True) -> "AsyncDatabase":
|
|
1340
|
+
"""Begin async execution.
|
|
1341
|
+
|
|
1342
|
+
Args:
|
|
1343
|
+
return_result (bool): If set to `True`, API executions return instances of
|
|
1344
|
+
`arangoasync.job.AsyncJob`, which you can be used to retrieve
|
|
1345
|
+
results from server once available. Otherwise, API executions
|
|
1346
|
+
return `None` and no results are stored on server.
|
|
1347
|
+
|
|
1348
|
+
Returns:
|
|
1349
|
+
AsyncDatabase: Database API wrapper tailored for async execution.
|
|
1350
|
+
"""
|
|
1351
|
+
return AsyncDatabase(self.connection, return_result)
|
|
1352
|
+
|
|
1353
|
+
async def async_jobs(
|
|
1354
|
+
self, status: str, count: Optional[int] = None
|
|
1355
|
+
) -> Result[List[str]]:
|
|
1356
|
+
"""Return IDs of async jobs with given status.
|
|
1357
|
+
|
|
1358
|
+
Args:
|
|
1359
|
+
status (str): Job status (e.g. "pending", "done").
|
|
1360
|
+
count (int | None): Max number of job IDs to return.
|
|
1361
|
+
|
|
1362
|
+
Returns:
|
|
1363
|
+
list: List of job IDs.
|
|
1364
|
+
|
|
1365
|
+
Raises:
|
|
1366
|
+
AsyncJobListError: If retrieval fails.
|
|
1367
|
+
|
|
1368
|
+
References:
|
|
1369
|
+
- `list-async-jobs-by-status-or-get-the-status-of-specific-job <https://docs.arangodb.com/stable/develop/http-api/jobs/#list-async-jobs-by-status-or-get-the-status-of-specific-job>`__
|
|
1370
|
+
""" # noqa: E501
|
|
1371
|
+
params: Params = {}
|
|
1372
|
+
if count is not None:
|
|
1373
|
+
params["count"] = count
|
|
1374
|
+
|
|
1375
|
+
request = Request(
|
|
1376
|
+
method=Method.GET, endpoint=f"/_api/job/{status}", params=params
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
def response_handler(resp: Response) -> List[str]:
|
|
1380
|
+
if resp.is_success:
|
|
1381
|
+
return cast(List[str], self.deserializer.loads(resp.raw_body))
|
|
1382
|
+
raise AsyncJobListError(resp, request)
|
|
1383
|
+
|
|
1384
|
+
return await self._executor.execute(request, response_handler)
|
|
1385
|
+
|
|
1386
|
+
async def clear_async_jobs(self, threshold: Optional[float] = None) -> None:
|
|
1387
|
+
"""Clear async job results from the server.
|
|
1388
|
+
|
|
1389
|
+
Async jobs that are still queued or running are not stopped.
|
|
1390
|
+
Clients can use this method to perform an eventual garbage
|
|
1391
|
+
collection of job results.
|
|
1392
|
+
|
|
1393
|
+
Args:
|
|
1394
|
+
threshold (float | None): If specified, only the job results created
|
|
1395
|
+
prior to the threshold (a Unix timestamp) are deleted. Otherwise,
|
|
1396
|
+
all job results are deleted.
|
|
1397
|
+
|
|
1398
|
+
Raises:
|
|
1399
|
+
AsyncJobClearError: If the operation fails.
|
|
1400
|
+
|
|
1401
|
+
References:
|
|
1402
|
+
- `delete-async-job-results <https://docs.arangodb.com/stable/develop/http-api/jobs/#delete-async-job-results>`__
|
|
1403
|
+
""" # noqa: E501
|
|
1404
|
+
if threshold is None:
|
|
1405
|
+
request = Request(method=Method.DELETE, endpoint="/_api/job/all")
|
|
1406
|
+
else:
|
|
1407
|
+
request = Request(
|
|
1408
|
+
method=Method.DELETE,
|
|
1409
|
+
endpoint="/_api/job/expired",
|
|
1410
|
+
params={"stamp": threshold},
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
def response_handler(resp: Response) -> None:
|
|
1414
|
+
if not resp.is_success:
|
|
1415
|
+
raise AsyncJobClearError(resp, request)
|
|
1416
|
+
|
|
1417
|
+
await self._executor.execute(request, response_handler)
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
class TransactionDatabase(Database):
|
|
1421
|
+
"""Database API tailored specifically for
|
|
1422
|
+
`Stream Transactions <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/>`__.
|
|
1423
|
+
|
|
1424
|
+
It allows you start a transaction, run multiple operations (eg. AQL queries) over a short period of time,
|
|
1425
|
+
and then commit or abort the transaction.
|
|
1426
|
+
|
|
1427
|
+
See :func:`arangoasync.database.StandardDatabase.begin_transaction`.
|
|
1428
|
+
|
|
1429
|
+
Args:
|
|
1430
|
+
connection (Connection): Connection object to be used by the API executor.
|
|
1431
|
+
transaction_id (str): Transaction ID.
|
|
1432
|
+
""" # noqa: E501
|
|
1433
|
+
|
|
1434
|
+
def __init__(self, connection: Connection, transaction_id: str) -> None:
|
|
1435
|
+
super().__init__(TransactionApiExecutor(connection, transaction_id))
|
|
1436
|
+
self._standard_executor = DefaultApiExecutor(connection)
|
|
1437
|
+
self._transaction_id = transaction_id
|
|
1438
|
+
|
|
1439
|
+
def __repr__(self) -> str:
|
|
1440
|
+
return f"<TransactionDatabase {self.name}>"
|
|
1441
|
+
|
|
1442
|
+
@property
|
|
1443
|
+
def transaction_id(self) -> str:
|
|
1444
|
+
"""Transaction ID."""
|
|
1445
|
+
return self._transaction_id
|
|
1446
|
+
|
|
1447
|
+
async def transaction_status(self) -> str:
|
|
1448
|
+
"""Get the status of the transaction.
|
|
1449
|
+
|
|
1450
|
+
Returns:
|
|
1451
|
+
str: Transaction status: one of "running", "committed" or "aborted".
|
|
1452
|
+
|
|
1453
|
+
Raises:
|
|
1454
|
+
TransactionStatusError: If the transaction is not found.
|
|
1455
|
+
|
|
1456
|
+
References:
|
|
1457
|
+
- `get-the-status-of-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#get-the-status-of-a-stream-transaction>`__
|
|
1458
|
+
""" # noqa: E501
|
|
1459
|
+
request = Request(
|
|
1460
|
+
method=Method.GET,
|
|
1461
|
+
endpoint=f"/_api/transaction/{self.transaction_id}",
|
|
1462
|
+
)
|
|
1463
|
+
|
|
1464
|
+
def response_handler(resp: Response) -> str:
|
|
1465
|
+
if not resp.is_success:
|
|
1466
|
+
raise TransactionStatusError(resp, request)
|
|
1467
|
+
result: Json = self.deserializer.loads(resp.raw_body)["result"]
|
|
1468
|
+
return cast(str, result["status"])
|
|
1469
|
+
|
|
1470
|
+
return await self._standard_executor.execute(request, response_handler)
|
|
1471
|
+
|
|
1472
|
+
async def commit_transaction(self) -> None:
|
|
1473
|
+
"""Commit the transaction.
|
|
1474
|
+
|
|
1475
|
+
Raises:
|
|
1476
|
+
TransactionCommitError: If the operation fails on the server side.
|
|
1477
|
+
|
|
1478
|
+
References:
|
|
1479
|
+
- `commit-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#commit-a-stream-transaction>`__
|
|
1480
|
+
""" # noqa: E501
|
|
1481
|
+
request = Request(
|
|
1482
|
+
method=Method.PUT,
|
|
1483
|
+
endpoint=f"/_api/transaction/{self.transaction_id}",
|
|
1484
|
+
)
|
|
1485
|
+
|
|
1486
|
+
def response_handler(resp: Response) -> None:
|
|
1487
|
+
if not resp.is_success:
|
|
1488
|
+
raise TransactionCommitError(resp, request)
|
|
1489
|
+
|
|
1490
|
+
await self._standard_executor.execute(request, response_handler)
|
|
1491
|
+
|
|
1492
|
+
async def abort_transaction(self) -> None:
|
|
1493
|
+
"""Abort the transaction.
|
|
1494
|
+
|
|
1495
|
+
Raises:
|
|
1496
|
+
TransactionAbortError: If the operation fails on the server side.
|
|
1497
|
+
|
|
1498
|
+
References:
|
|
1499
|
+
- `abort-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#abort-a-stream-transaction>`__
|
|
1500
|
+
""" # noqa: E501
|
|
1501
|
+
request = Request(
|
|
1502
|
+
method=Method.DELETE,
|
|
1503
|
+
endpoint=f"/_api/transaction/{self.transaction_id}",
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
def response_handler(resp: Response) -> None:
|
|
1507
|
+
if not resp.is_success:
|
|
1508
|
+
raise TransactionAbortError(resp, request)
|
|
1509
|
+
|
|
1510
|
+
await self._standard_executor.execute(request, response_handler)
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
class AsyncDatabase(Database):
|
|
1514
|
+
"""Database API wrapper tailored specifically for async execution.
|
|
1515
|
+
|
|
1516
|
+
See :func:`arangoasync.database.StandardDatabase.begin_async_execution`.
|
|
1517
|
+
|
|
1518
|
+
Args:
|
|
1519
|
+
connection (Connection): HTTP connection.
|
|
1520
|
+
return_result (bool): If set to `True`, API executions return instances of
|
|
1521
|
+
:class:`arangoasync.job.AsyncJob`, which you can use to retrieve results
|
|
1522
|
+
from server once available. If set to `False`, API executions return `None`
|
|
1523
|
+
and no results are stored on server.
|
|
1524
|
+
|
|
1525
|
+
References:
|
|
1526
|
+
- `jobs <https://docs.arangodb.com/stable/develop/http-api/jobs/>`__
|
|
1527
|
+
""" # noqa: E501
|
|
1528
|
+
|
|
1529
|
+
def __init__(self, connection: Connection, return_result: bool) -> None:
|
|
1530
|
+
super().__init__(executor=AsyncApiExecutor(connection, return_result))
|
|
1531
|
+
|
|
1532
|
+
def __repr__(self) -> str:
|
|
1533
|
+
return f"<AsyncDatabase {self.name}>"
|