memgraph-toolbox 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/PKG-INFO +1 -1
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/pyproject.toml +1 -1
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/api/memgraph.py +34 -0
- memgraph_toolbox-0.2.2/src/memgraph_toolbox/tests/test_merge_with_retry.py +69 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/.gitignore +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/LICENSE +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/README.md +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/api/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/api/tool.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/api/toolbox.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/client/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/client/mcp_prompt.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/evals/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/evals/coherence.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/memgraph_toolbox.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/py.typed +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_async_memgraph.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_embedding.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_eval_coherence.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_mcp_prompt.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_memgraph_config.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_toolbox.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_tools.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tools/__init__.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tools/cypher.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tools/schema.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/utils/embedding.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/utils/logger.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/utils/serialization.py +0 -0
- {memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: memgraph-toolbox
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Memgraph toolbox library for Memgraph AI tools and utilities
|
|
5
5
|
Project-URL: Homepage, https://github.com/memgraph/ai-toolkit
|
|
6
6
|
Project-URL: Source, https://github.com/memgraph/ai-toolkit
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import time
|
|
2
3
|
from collections.abc import Mapping
|
|
3
4
|
from typing import Any, cast
|
|
4
5
|
|
|
@@ -78,6 +79,39 @@ def memgraph_env(
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
|
|
82
|
+
def merge_with_retry(db: Any, query: str, params: dict | None = None, *, attempts: int = 3) -> list[dict[str, Any]]:
|
|
83
|
+
"""Run *query*, a MERGE of a uniqueness-constrained node, through *db*, surviving a concurrent create.
|
|
84
|
+
|
|
85
|
+
Two transactions that MERGE the same new node both find it missing and
|
|
86
|
+
both create it; the later commit fails the uniqueness constraint. Run
|
|
87
|
+
again, its MERGE matches the node the other one committed. Writers that
|
|
88
|
+
race this way are typically one process per event (agent hooks).
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
db: Anything with ``query(query, params)``, normally a :class:`Memgraph`.
|
|
92
|
+
query: The Cypher to run; it must be safe to run more than once.
|
|
93
|
+
params: Its parameters.
|
|
94
|
+
attempts: Runs before giving up, the first included.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The rows of the run that committed.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
neo4j.exceptions.ClientError: a uniqueness violation on the last
|
|
101
|
+
attempt, or any other client error at once.
|
|
102
|
+
"""
|
|
103
|
+
from neo4j.exceptions import ClientError
|
|
104
|
+
|
|
105
|
+
for attempt in range(attempts):
|
|
106
|
+
try:
|
|
107
|
+
return db.query(query, params=params)
|
|
108
|
+
except ClientError as exc:
|
|
109
|
+
if "unique constraint" not in str(exc) or attempt == attempts - 1:
|
|
110
|
+
raise
|
|
111
|
+
time.sleep(0.05 * (attempt + 1))
|
|
112
|
+
raise ValueError(f"attempts must be at least 1, got {attempts}")
|
|
113
|
+
|
|
114
|
+
|
|
81
115
|
class Memgraph:
|
|
82
116
|
"""
|
|
83
117
|
Base Memgraph client for interacting with Memgraph database.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Tests for merge_with_retry against a live Memgraph; they skip when none is reachable."""
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from neo4j.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from ..api.memgraph import Memgraph, merge_with_retry
|
|
9
|
+
|
|
10
|
+
_LABEL = "MergeWithRetryProbe"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def db():
|
|
15
|
+
try:
|
|
16
|
+
client = Memgraph()
|
|
17
|
+
except Exception as exc:
|
|
18
|
+
pytest.skip(f"No Memgraph server reachable at the configured MEMGRAPH_URL: {exc}")
|
|
19
|
+
client.query(f"MATCH (n:{_LABEL}) DETACH DELETE n")
|
|
20
|
+
client.query(f"CREATE CONSTRAINT ON (n:{_LABEL}) ASSERT n.key IS UNIQUE;")
|
|
21
|
+
yield client
|
|
22
|
+
client.query(f"MATCH (n:{_LABEL}) DETACH DELETE n")
|
|
23
|
+
client.query(f"DROP CONSTRAINT ON (n:{_LABEL}) ASSERT n.key IS UNIQUE;")
|
|
24
|
+
client.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_concurrent_merges_of_new_nodes_converge_on_one_node_each(db):
|
|
28
|
+
keys = [f"k{i}" for i in range(10)]
|
|
29
|
+
|
|
30
|
+
def merge(key: str) -> None:
|
|
31
|
+
merge_with_retry(db, f"MERGE (n:{_LABEL} {{key: $key}})", {"key": key})
|
|
32
|
+
|
|
33
|
+
with ThreadPoolExecutor(max_workers=16) as pool:
|
|
34
|
+
list(pool.map(merge, keys * 16))
|
|
35
|
+
|
|
36
|
+
rows = db.query(f"MATCH (n:{_LABEL}) RETURN n.key AS key, count(*) AS c ORDER BY key")
|
|
37
|
+
assert rows == [{"key": key, "c": 1} for key in sorted(keys)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_returns_the_rows_of_the_run_that_committed(db):
|
|
41
|
+
rows = merge_with_retry(db, f"MERGE (n:{_LABEL} {{key: 'a'}}) RETURN n.key AS key")
|
|
42
|
+
assert rows == [{"key": "a"}]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_any_other_client_error_is_raised_without_retrying(db):
|
|
46
|
+
calls = []
|
|
47
|
+
|
|
48
|
+
class Counting:
|
|
49
|
+
def query(self, query, params=None):
|
|
50
|
+
calls.append(query)
|
|
51
|
+
return db.query(query, params)
|
|
52
|
+
|
|
53
|
+
with pytest.raises(ClientError):
|
|
54
|
+
merge_with_retry(Counting(), "MERGE (n:")
|
|
55
|
+
assert len(calls) == 1
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_a_conflict_on_every_attempt_is_raised_after_the_last():
|
|
59
|
+
"""A conflict that never clears can't be produced on demand by a real server."""
|
|
60
|
+
calls = []
|
|
61
|
+
|
|
62
|
+
class AlwaysConflicting:
|
|
63
|
+
def query(self, query, params=None):
|
|
64
|
+
calls.append(query)
|
|
65
|
+
raise ClientError("Unable to commit due to unique constraint violation on :X(key)")
|
|
66
|
+
|
|
67
|
+
with pytest.raises(ClientError, match="unique constraint"):
|
|
68
|
+
merge_with_retry(AlwaysConflicting(), "MERGE (n:X {key: 1})", attempts=2)
|
|
69
|
+
assert len(calls) == 2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_async_memgraph.py
RENAMED
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_embedding.py
RENAMED
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_eval_coherence.py
RENAMED
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_mcp_prompt.py
RENAMED
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_memgraph_config.py
RENAMED
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/tests/test_toolbox.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memgraph_toolbox-0.2.1 → memgraph_toolbox-0.2.2}/src/memgraph_toolbox/utils/serialization.py
RENAMED
|
File without changes
|
|
File without changes
|