Skip to content

Commit 745f6e6

Browse files
authored
Adding KeyOptions object (#21)
1 parent 8c8b237 commit 745f6e6

File tree

4 files changed

+127
-22
lines changed

4 files changed

+127
-22
lines changed

arangoasync/collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ["Collection", "Collection", "StandardCollection"]
1+
__all__ = ["Collection", "CollectionType", "StandardCollection"]
22

33

44
from enum import Enum

arangoasync/database.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from arangoasync.response import Response
2121
from arangoasync.serialization import Deserializer, Serializer
2222
from arangoasync.typings import Json, Jsons, Params, Result
23-
from arangoasync.wrapper import ServerStatusInformation
23+
from arangoasync.wrapper import KeyOptions, ServerStatusInformation
2424

2525
T = TypeVar("T")
2626
U = TypeVar("U")
@@ -140,7 +140,7 @@ async def create_collection(
140140
computed_values: Optional[Jsons] = None,
141141
distribute_shards_like: Optional[str] = None,
142142
is_system: Optional[bool] = False,
143-
key_options: Optional[Json] = None,
143+
key_options: Optional[KeyOptions | Json] = None,
144144
schema: Optional[Json] = None,
145145
shard_keys: Optional[Sequence[str]] = None,
146146
sharding_strategy: Optional[str] = None,
@@ -179,7 +179,10 @@ async def create_collection(
179179
way as the shards of the other collection.
180180
is_system (bool | None): If `True`, create a system collection.
181181
In this case, the collection name should start with an underscore.
182-
key_options (dict | None): Additional options for key generation.
182+
key_options (KeyOptions | dict | None): Additional options for key
183+
generation. You may use a :class:`KeyOptions
184+
<arangoasync.wrapper.KeyOptions>` object for easier configuration,
185+
or pass a dictionary directly.
183186
schema (dict | None): Optional object that specifies the collection
184187
level schema for documents.
185188
shard_keys (list | None): In a cluster, this attribute determines which
@@ -204,6 +207,7 @@ async def create_collection(
204207
StandardCollection: Collection API wrapper.
205208
206209
Raises:
210+
ValueError: If parameters are invalid.
207211
CollectionCreateError: If the operation fails.
208212
"""
209213
data: Json = {"name": name}
@@ -226,7 +230,10 @@ async def create_collection(
226230
if is_system is not None:
227231
data["isSystem"] = is_system
228232
if key_options is not None:
229-
data["keyOptions"] = key_options
233+
if isinstance(key_options, dict):
234+
key_options = KeyOptions(key_options)
235+
key_options.validate()
236+
data["keyOptions"] = key_options.to_dict()
230237
if schema is not None:
231238
data["schema"] = schema
232239
if shard_keys is not None:
@@ -304,9 +311,8 @@ def response_handler(resp: Response) -> bool:
304311
nonlocal ignore_missing
305312
if resp.is_success:
306313
return True
307-
if resp.error_code == HTTP_NOT_FOUND:
308-
if ignore_missing:
309-
return False
314+
if resp.error_code == HTTP_NOT_FOUND and ignore_missing:
315+
return False
310316
raise CollectionDeleteError(resp, request)
311317

312318
return await self._executor.execute(request, response_handler)

arangoasync/wrapper.py

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from typing import Any, Dict, Iterator, Optional, Tuple
1+
from typing import Any, Iterator, Optional, Tuple
22

3+
from arangoasync.typings import Json
34

4-
class Wrapper:
5-
"""Wrapper over server response objects."""
65

7-
def __init__(self, data: Dict[str, Any]) -> None:
6+
class JsonWrapper:
7+
"""Wrapper over server request/response objects."""
8+
9+
def __init__(self, data: Json) -> None:
810
self._data = data
911

1012
def __getitem__(self, key: str) -> Any:
@@ -42,9 +44,88 @@ def items(self) -> Iterator[Tuple[str, Any]]:
4244
"""Return an iterator over the dictionary’s key-value pairs."""
4345
return iter(self._data.items())
4446

47+
def to_dict(self) -> Json:
48+
"""Return the dictionary."""
49+
return self._data
50+
51+
52+
class KeyOptions(JsonWrapper):
53+
"""Additional options for key generation, used on collections.
54+
55+
https://docs.arangodb.com/stable/develop/http-api/collections/#create-a-collection_body_keyOptions
56+
57+
Example:
58+
.. code-block:: json
59+
60+
"keyOptions": {
61+
"type": "autoincrement",
62+
"increment": 5,
63+
"allowUserKeys": true
64+
}
4565
46-
class ServerStatusInformation(Wrapper):
66+
Args:
67+
data (dict | None): Key options. If this parameter is specified, the
68+
other parameters are ignored.
69+
allow_user_keys (bool): If set to `True`, then you are allowed to supply own
70+
key values in the `_key` attribute of documents. If set to `False`, then
71+
the key generator is solely responsible for generating keys and an error
72+
is raised if you supply own key values in the `_key` attribute of
73+
documents.
74+
generator_type (str): Specifies the type of the key generator. The currently
75+
available generators are "traditional", "autoincrement", "uuid" and
76+
"padded".
77+
increment (int | None): The increment value for the "autoincrement" key
78+
generator. Not allowed for other key generator types.
79+
offset (int | None): The initial offset value for the "autoincrement" key
80+
generator. Not allowed for other key generator types.
4781
"""
82+
83+
def __init__(
84+
self,
85+
data: Optional[Json] = None,
86+
allow_user_keys: bool = True,
87+
generator_type: str = "traditional",
88+
increment: Optional[int] = None,
89+
offset: Optional[int] = None,
90+
) -> None:
91+
if data is None:
92+
data = {
93+
"allowUserKeys": allow_user_keys,
94+
"type": generator_type,
95+
}
96+
if increment is not None:
97+
data["increment"] = increment
98+
if offset is not None:
99+
data["offset"] = offset
100+
super().__init__(data)
101+
102+
def validate(self) -> None:
103+
"""Validate key options."""
104+
if "type" not in self:
105+
raise ValueError('"type" value is required for key options')
106+
if "allowUserKeys" not in self:
107+
raise ValueError('"allowUserKeys" value is required for key options')
108+
109+
allowed_types = {"autoincrement", "uuid", "padded", "traditional"}
110+
if self["type"] not in allowed_types:
111+
raise ValueError(
112+
f"Invalid key generator type '{self['type']}', "
113+
f"expected one of {allowed_types}"
114+
)
115+
116+
if self.get("increment") is not None and self["type"] != "autoincrement":
117+
raise ValueError(
118+
'"increment" value is only allowed for "autoincrement" ' "key generator"
119+
)
120+
if self.get("offset") is not None and self["type"] != "autoincrement":
121+
raise ValueError(
122+
'"offset" value is only allowed for "autoincrement" ' "key generator"
123+
)
124+
125+
126+
class ServerStatusInformation(JsonWrapper):
127+
"""Status information about the server.
128+
48129
https://docs.arangodb.com/stable/develop/http-api/administration/#get-server-status-information
49130
50131
Example:
@@ -92,7 +173,7 @@ class ServerStatusInformation(Wrapper):
92173
}
93174
"""
94175

95-
def __init__(self, data: Dict[str, Any]) -> None:
176+
def __init__(self, data: Json) -> None:
96177
super().__init__(data)
97178

98179
@property
@@ -132,13 +213,13 @@ def hostname(self) -> Optional[str]:
132213
return self._data.get("hostname")
133214

134215
@property
135-
def server_info(self) -> Optional[Dict[str, Any]]:
216+
def server_info(self) -> Optional[Json]:
136217
return self._data.get("serverInfo")
137218

138219
@property
139-
def coordinator(self) -> Optional[Dict[str, Any]]:
220+
def coordinator(self) -> Optional[Json]:
140221
return self._data.get("coordinator")
141222

142223
@property
143-
def agency(self) -> Optional[Dict[str, Any]]:
224+
def agency(self) -> Optional[Json]:
144225
return self._data.get("agency")

tests/test_wrapper.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from arangoasync.wrapper import Wrapper
1+
import pytest
2+
3+
from arangoasync.wrapper import JsonWrapper, KeyOptions
24

35

46
def test_basic_wrapper():
5-
wrapper = Wrapper({"a": 1, "b": 2})
7+
wrapper = JsonWrapper({"a": 1, "b": 2})
68
assert wrapper["a"] == 1
79
assert wrapper["b"] == 2
810

@@ -12,16 +14,16 @@ def test_basic_wrapper():
1214
del wrapper["a"]
1315
assert "a" not in wrapper
1416

15-
wrapper = Wrapper({"a": 1, "b": 2})
17+
wrapper = JsonWrapper({"a": 1, "b": 2})
1618
keys = list(iter(wrapper))
1719
assert keys == ["a", "b"]
1820
assert len(wrapper) == 2
1921

2022
assert "a" in wrapper
2123
assert "c" not in wrapper
2224

23-
assert repr(wrapper) == "Wrapper({'a': 1, 'b': 2})"
24-
wrapper = Wrapper({"a": 1, "b": 2})
25+
assert repr(wrapper) == "JsonWrapper({'a': 1, 'b': 2})"
26+
wrapper = JsonWrapper({"a": 1, "b": 2})
2527
assert str(wrapper) == "{'a': 1, 'b': 2}"
2628
assert wrapper == {"a": 1, "b": 2}
2729

@@ -30,3 +32,19 @@ def test_basic_wrapper():
3032

3133
items = list(wrapper.items())
3234
assert items == [("a", 1), ("b", 2)]
35+
assert wrapper.to_dict() == {"a": 1, "b": 2}
36+
37+
38+
def test_KeyOptions():
39+
options = KeyOptions(generator_type="autoincrement")
40+
options.validate()
41+
with pytest.raises(ValueError, match="Invalid key generator type 'invalid_type'"):
42+
KeyOptions(generator_type="invalid_type").validate()
43+
with pytest.raises(ValueError, match='"increment" value'):
44+
KeyOptions(generator_type="uuid", increment=5).validate()
45+
with pytest.raises(ValueError, match='"offset" value'):
46+
KeyOptions(generator_type="uuid", offset=5).validate()
47+
with pytest.raises(ValueError, match='"type" value'):
48+
KeyOptions(data={"allowUserKeys": True}).validate()
49+
with pytest.raises(ValueError, match='"allowUserKeys" value'):
50+
KeyOptions(data={"type": "autoincrement"}).validate()

0 commit comments

Comments
 (0)