8000 Edge collection management · arangodb/python-arango-async@9ccbe69 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9ccbe69

Browse files
committed
Edge collection management
1 parent 526b135 commit 9ccbe69

File tree

4 files changed

+290
-2
lines changed

4 files changed

+290
-2
lines changed

arangoasync/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ class DocumentUpdateError(ArangoServerError):
263263
"""Failed to update document."""
264264

265265

266+
class EdgeCollectionListError(ArangoServerError):
267+
"""Failed to retrieve edge collections."""
268+
269+
266270
class EdgeDefinitionListError(ArangoServerError):
267271
"""Failed to retrieve edge definitions."""
268272

arangoasync/graph.py

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
__all__ = ["Graph"]
22

33

4-
from typing import Generic, List, Optional, Sequence, TypeVar
4+
from typing import Generic, List, Optional, Sequence, TypeVar, cast
55

66
from arangoasync.collection import EdgeCollection, VertexCollection
77
from arangoasync.exceptions import (
8+
EdgeCollectionListError,
89
EdgeDefinitionCreateError,
10+
EdgeDefinitionDeleteError,
11+
EdgeDefinitionListError,
12+
EdgeDefinitionReplaceError,
913
GraphPropertiesError,
1014
VertexCollectionCreateError,
1115
VertexCollectionDeleteError,
@@ -21,6 +25,7 @@
2125
GraphProperties,
2226
Json,
2327
Jsons,
28+
Params,
2429
VertexCollectionOptions,
2530
)
2631

@@ -129,7 +134,7 @@ def response_handler(resp: Response) -> List[str]:
129134
if not resp.is_success:
130135
raise VertexCollectionListError(resp, request)
131136
body = self.deserializer.loads(resp.raw_body)
132-
return list(sorted(set(body["collections"])))
137+
return list(sorted(body["collections"]))
133138

134139
return await self._executor.execute(request, response_handler)
135140

@@ -245,6 +250,76 @@ def edge_collection(self, name: str) -> EdgeCollection[T, U, V]:
245250
doc_deserializer=self._doc_deserializer,
246251
)
247252

253+
async def edge_definitions(self) -> Result[Jsons]:
254+
"""Return the edge definitions from the graph.
255+
256+
Returns:
257+
list: List of edge definitions.
258+
259+
Raises:
260+
EdgeDefinitionListError: If the operation fails.
261+
"""
262+
request = Request(method=Method.GET, endpoint=f"/_api/gharial/{self._name}")
263+
264+
def response_handler(resp: Response) -> Jsons:
265+
if not resp.is_success:
266+
raise EdgeDefinitionListError(resp, request)
267+
body = self.deserializer.loads(resp.raw_body)
268+
properties = GraphProperties(body["graph"])
269+
edge_definitions = properties.format(
270+
GraphProperties.compatibility_formatter
271+
)["edge_definitions"]
272+
return cast(Jsons, edge_definitions)
273+
274+
return await self._executor.execute(request, response_handler)
275+
276+
async def has_edge_definition(self, name: str) -> Result[bool]:
277+
"""Check if the graph has the given edge definition.
278+
279+
Returns:
280+
bool: `True` if the graph has the edge definitions, `False` otherwise.
281+
282+
Raises:
283+
EdgeDefinitionListError: If the operation fails.
284+
"""
285+
request = Request(method=Method.GET, endpoint=f"/_api/gharial/{self._name}")
286+
287+
def response_handler(resp: Response) -> bool:
288+
if not resp.is_success:
289+
raise EdgeDefinitionListError(resp, request)
290+
body = self.deserializer.loads(resp.raw_body)
291+
return any(
292+
edge_definition["collection"] == name
293+
for edge_definition in body["graph"]["edgeDefinitions"]
294+
)
295+
296+
return await self._executor.execute(request, response_handler)
297+
298+
async def edge_collections(self) -> Result[List[str]]:
299+
"""Get the names of all edge collections in the graph.
300+
301+
Returns:
302+
list: List of edge collection names.
303+
304+
Raises:
305+
EdgeCollectionListError: If the operation fails.
306+
307+
References:
308+
- `list-edge-collections <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#list-edge-collections>`__
309+
""" # noqa: E501
310+
request = Request(
311+
method=Method.GET,
312+
endpoint=f"/_api/gharial/{self._name}/edge",
313+
)
314+
315+
def response_handler(resp: Response) -> List[str]:
316+
if not resp.is_success:
317+
raise EdgeCollectionListError(resp, request)
318+
body = self.deserializer.loads(resp.raw_body)
319+
return list(sorted(body["collections"]))
320+
321+
return await self._executor.execute(request, response_handler)
322+
248323
async def create_edge_definition(
249324
self,
250325
edge_collection: str,
@@ -307,3 +382,104 @@ def response_handler(resp: Response) -> EdgeCollection[T, U, V]:
307382
return self.edge_collection(edge_collection)
308383

309384
return await self._executor.execute(request, response_handler)
385+
386+
async def replace_edge_definition(
387+
self,
388+
edge_collection: str,
389+
from_vertex_collections: Sequence[str],
390+
to_vertex_collections: Sequence[str],
391+
options: Optional[EdgeDefinitionOptions | Json] = None,
392+
wait_for_sync: Optional[bool] = None,
393+
drop_collections: Optional[bool] = None,
394+
) -> Result[EdgeCollection[T, U, V]]:
395+
"""Replace an edge definition.
396+
397+
Args:
398+
edge_collection (str): Edge collection name.
399+
from_vertex_collections (list): Names of "from" vertex collections.
400+
to_vertex_collections (list): Names of "to" vertex collections.
401+
options (dict | EdgeDefinitionOptions | None): Extra options for
402+
modifying collections withing this edge definition.
403+
wait_for_sync (bool | None): If set to `True`, the operation waits for
404+
data to be synced to disk before returning.
405+
drop_collections (bool | None): Drop the edge collection in addition to
406+
removing it from the graph. The collection is only dropped if it is
407+
not used in other graphs.
408+
409+
Returns:
410+
EdgeCollection: API wrapper.
411+
412+
Raises:
413+
EdgeDefinitionReplaceError: If the operation fails.
414+
415+
References:
416+
- `replace-an-edge-definition <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#replace-an-edge-definition>`__
417+
""" # noqa: E501
418+
data: Json = {
419+
"collection": edge_collection,
420+
"from": from_vertex_collections,
421+
"to": to_vertex_collections,
422+
}
423+
if options is not None:
424+
if isinstance(options, VertexCollectionOptions):
425+
data["options"] = options.to_dict()
426+
else:
427+
data["options"] = options
428+
429+
params: Params = {}
430+
if wait_for_sync is not None:
431+
params["waitForSync"] = wait_for_sync
432+
if drop_collections is not None:
433+
params["dropCollections"] = drop_collections
434+
435+
request = Request(
436+
method=Method.PUT,
437+
endpoint=f"/_api/gharial/{self._name}/edge/{edge_collection}",
438+
data=self.serializer.dumps(data),
439+
params=params,
440+
)
441+
442+
def response_handler(resp: Response) -> EdgeCollection[T, U, V]:
443+
if resp.is_success:
444+
return self.edge_collection(edge_collection)
445+
raise EdgeDefinitionReplaceError(resp, request)
446+
447+
return await self._executor.execute(request, response_handler)
448+
449+
async def delete_edge_definition(
450+
self,
451+
name: str,
452+
purge: bool = False,
453+
wait_for_sync: Optional[bool] = None,
454+
) -> None:
455+
"""Delete an edge definition from the graph.
456+
457+
Args:
458+
name (str): Edge collection name.
459+
purge (bool): If set to `True`, the edge definition is not just removed
460+
from the graph but the edge collection is also deleted completely
461+
from the database.
462+
wait_for_sync (bool | None): If set to `True`, the operation waits for
463+
changes to be synced to disk before returning.
464+
465+
Raises:
466+
EdgeDefinitionDeleteError: If the operation fails.
467+
468+
References:
469+
- `remove-an-edge-definition <https://docs.arangodb.com/stable/develop/http-api/graphs/named-graphs/#remove-an-edge-definition>`__
470+
""" # noqa: E501
471+
params: Params = {"dropCollections": purge}
472+
if wait_for_sync is not None:
473+
params["waitForSync"] = wait_for_sync
474+
475+
request = Request(
476+
method=Method.DELETE,
477+
< 1241 span class=pl-s1>endpoint=f"/_api/gharial/{self._name}/edge/{name}",
478+
params=params,
479+
)
480+
481+
def response_handler(resp: Response) -> None:
482+
if not resp.is_success:
483+
raise EdgeDefinitionDeleteError(resp, request)
484+
485+
await self._executor.execute(request, response_handler)

arangoasync/typings.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,47 @@ def edge_definitions(self) -> Jsons:
17261726
def orphan_collections(self) -> List[str]:
17271727
return cast(List[str], self._data.get("orphanCollections", list()))
17281728

1729+
@staticmethod
1730+
def compatibility_formatter(data: Json) -> Json:
1731+
result: Json = {}
1732+
1733+
if "_id" in data:
1734+
result["id"] = data["_id"]
1735+
if "_key" in data:
1736+
result["key"] = data["_key"]
1737+
if "name" in data:
1738+
result["name"] = data["name"]
1739+
if "_rev" in data:
1740+
result["revision"] = data["_rev"]
1741+
if "orphanCollections" in data:
1742+
result["orphan_collection"] = data["orphanCollections"]
1743+
if "edgeDefinitions" in data:
1744+
result["edge_definitions"] = [
1745+
{
1746+
"edge_collection": edge_definition["collection"],
1747+
"from_vertex_collections": edge_definition["from"],
1748+
"to_vertex_collections": edge_definition["to"],
1749+
}
1750+
for edge_definition in data["edgeDefinitions"]
1751+
]
1752+
if "isSmart" in data:
1753+
result["smart"] = data["isSmart"]
1754+
if "isDisjoint" in data:
1755+
result["disjoint"] = data["isDisjoint"]
1756+
if "isSatellite" in data:
1757+
result["is_satellite"] = data["isSatellite"]
1758+
if "smartGraphAttribute" in data:
1759+
result["smart_field"] = data["smartGraphAttribute"]
1760+
if "numberOfShards" in data:
1761+
result["shard_count"] = data["numberOfShards"]
1762+
if "replicationFactor" in data:
1763+
result["replication_factor"] = data["replicationFactor"]
1764+
if "minReplicationFactor" in data:
1765+
result["min_replication_factor"] = data["minReplicationFactor"]
1766+
if "writeConcern" in data:
1767+
result["write_concern"] = data["writeConcern"]
1768+
return result
1769+
17291770

17301771
class GraphOptions(JsonWrapper):
17311772
"""Special options for graph creation.

tests/test_graph.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import pytest
22

33
from arangoasync.exceptions import (
4+
EdgeCollectionListError,
5+
EdgeDefinitionDeleteError,
6+
EdgeDefinitionListError,
7+
EdgeDefinitionReplaceError,
48
GraphCreateError,
59
GraphDeleteError,
610
GraphListError,
@@ -126,3 +130,66 @@ async def test_vertex_collections(db, bad_graph):
126130
# Delete collections
127131
await graph.delete_vertex_collection(names[0])
128132
assert await graph.has_vertex_collection(names[0]) is False
133+
134+
135+
async def test_edge_collections(db, bad_graph):
136+
# Test errors
137+
with pytest.raises(EdgeDefinitionListError):
138+
await bad_graph.edge_definitions()
139+
with pytest.raises(EdgeDefinitionListError):
140+
await bad_graph.has_edge_definition("bad_col")
141+
with pytest.raises(EdgeCollectionListError):
142+
await bad_graph.edge_collections()
143+
with pytest.raises(EdgeDefinitionReplaceError):
144+
await bad_graph.replace_edge_definition("foo", ["bar1"], ["bar2"])
145+
with pytest.raises(EdgeDefinitionDeleteError):
146+
await bad_graph.delete_edge_definition("foo")
147+
148+
# Create full graph
149+
name = generate_graph_name()
150+
graph = await db.create_graph(name)
151+
vcol_name = generate_col_name()
152+
await graph.create_vertex_collection(vcol_name)
153+
vcol2_name = generate_col_name()
154+
await graph.create_vertex_collection(vcol2_name)
155+
edge_name = generate_col_name()
156+
edge_col = await graph.create_edge_definition(
157+
edge_name,
158+
from_vertex_collections=[vcol_name],
159+
to_vertex_collections=[vcol2_name],
160+
)
161+
assert edge_col.name == edge_name
162+
163+
# List edge definitions
164+
edge_definitions = await graph.edge_definitions()
165+
assert len(edge_definitions) == 1
166+
assert "edge_collection" in edge_definitions[0]
167+
assert "from_vertex_collections" in edge_definitions[0]
168+
assert "to_vertex_collections" in edge_definitions[0]
169+
assert await graph.has_edge_definition(edge_name) is True
170+
assert await graph.has_edge_definition("bad_edge") is False
171+
172+
edge_cols = await graph.edge_collections()
173+
assert len(edge_cols) == 1
174+
assert edge_name in edge_cols
175+
176+
# Replace the edge definition
177+
new_from_collections = [vcol2_name]
178+
new_to_collections = [vcol_name]
179+
replaced_edge_col = await graph.replace_edge_definition(
180+
edge_name,
181+
from_vertex_collections=new_from_collections,
182+
to_vertex_collections=new_to_collections,
183+
)
184+
assert replaced_edge_col.name == edge_name
185+
186+
# Verify the updated edge definition
187+
edge_definitions = await graph.edge_definitions()
188+
assert len(edge_definitions) == 1
189+
assert edge_definitions[0]["edge_collection"] == edge_name
190+
assert edge_definitions[0]["from_vertex_collections"] == new_from_collections
191+
assert edge_definitions[0]["to_vertex_collections"] == new_to_collections
192+
193+
# Delete the edge definition
194+
await graph.delete_edge_definition(edge_name)
195+
assert await graph.has_edge_definition(edge_name) is False

0 commit comments

Comments
 (0)
0