runic-py 0.3.0__tar.gz → 0.3.3__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.
- {runic_py-0.3.0 → runic_py-0.3.3}/.gitignore +11 -4
- {runic_py-0.3.0 → runic_py-0.3.3}/PKG-INFO +19 -15
- {runic_py-0.3.0 → runic_py-0.3.3}/README.md +6 -5
- {runic_py-0.3.0 → runic_py-0.3.3}/pyproject.toml +36 -24
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/__init__.py +3 -1
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/_base.py +33 -1
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/arcadedb.py +8 -60
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/falkordb.py +6 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/memgraph.py +12 -59
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/neo4j.py +8 -57
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/cli.py +12 -14
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/context.py +37 -4
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/operations.py +21 -25
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/schema.py +61 -72
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/script.py +3 -3
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/core/descriptors.py +9 -29
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/core/models.py +11 -22
- runic_py-0.3.3/runic/orm/query/_compiler.py +210 -0
- runic_py-0.3.3/runic/orm/query/_decoder.py +115 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/query/builder.py +4 -252
- runic_py-0.3.3/runic/orm/repository/_base.py +49 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/repository/async_repository.py +6 -33
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/repository/repository.py +5 -35
- runic_py-0.3.3/runic/orm/session/_base.py +213 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/session/async_session.py +15 -161
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/session/session.py +17 -182
- {runic_py-0.3.0 → runic_py-0.3.3}/LICENSE.md +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/adapters/age.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/autogen.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/checksum.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/config.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/exceptions.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/introspect.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/manifest.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/service.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/templates/env.py.mako +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/templates/script.py.mako +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/testing.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/migrate/version.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/core/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/core/metadata.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/core/types.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/age.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/arcadedb.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/bolt.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/factory.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/falkordb.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/memgraph.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/driver/neo4j.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/exceptions.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/mapper/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/mapper/mapper.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/mapper/relationship_loader.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/mapper/relationship_writer.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/operations.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/query/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/query/expressions.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/query/specialised.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/query/traversal.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/repository/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/repository/cypher.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/repository/protocol.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/schema/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/schema/index_manager.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/session/__init__.py +0 -0
- {runic_py-0.3.0 → runic_py-0.3.3}/runic/orm/session/connection_pool.py +0 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
__pycache__/
|
|
2
2
|
__pypackages__/
|
|
3
3
|
.cache
|
|
4
|
+
.claude/ralph-loop.local.md
|
|
4
5
|
.coverage
|
|
5
6
|
.coverage.*
|
|
7
|
+
.deepeval_telemetry.txt
|
|
8
|
+
.deepeval-cache.json
|
|
9
|
+
.deepeval/
|
|
6
10
|
.dmypy.json
|
|
7
11
|
.DS_Store
|
|
8
12
|
.eggs/
|
|
@@ -13,6 +17,8 @@ __pypackages__/
|
|
|
13
17
|
.idea/
|
|
14
18
|
.installed.cfg
|
|
15
19
|
.ipynb_checkpoints
|
|
20
|
+
.latest_run_full.json
|
|
21
|
+
.latest_test_run.json
|
|
16
22
|
.mypy_cache/
|
|
17
23
|
.nox/
|
|
18
24
|
.pdm.toml
|
|
@@ -23,10 +29,12 @@ __pypackages__/
|
|
|
23
29
|
.python_packages
|
|
24
30
|
.pytype/
|
|
25
31
|
.ropeproject
|
|
32
|
+
.runic
|
|
26
33
|
.scrapy
|
|
27
34
|
.spyderproject
|
|
28
35
|
.spyproject
|
|
29
36
|
.states
|
|
37
|
+
.task/
|
|
30
38
|
.tox/
|
|
31
39
|
.venv
|
|
32
40
|
.venv.mac
|
|
@@ -57,11 +65,13 @@ __pypackages__/
|
|
|
57
65
|
*$py.class
|
|
58
66
|
/site
|
|
59
67
|
/vectorstore/
|
|
68
|
+
# DeepEval run caches (tests/evals suite runs from an isolated venv)
|
|
60
69
|
aila-storage/
|
|
61
70
|
assets/external/
|
|
62
71
|
build/
|
|
63
72
|
celerybeat-schedule
|
|
64
73
|
celerybeat.pid
|
|
74
|
+
CLAUDE.local.md
|
|
65
75
|
configuration/config.abaz009.yaml
|
|
66
76
|
configuration/config.bubb001.yaml
|
|
67
77
|
configuration/config.stie104.yaml
|
|
@@ -98,6 +108,7 @@ pip-delete-this-directory.txt
|
|
|
98
108
|
pip-log.txt
|
|
99
109
|
Pipfile
|
|
100
110
|
profile_default/
|
|
111
|
+
runic_test/
|
|
101
112
|
sdist/
|
|
102
113
|
share/python-wheels/
|
|
103
114
|
sketchpad/
|
|
@@ -112,7 +123,3 @@ var/
|
|
|
112
123
|
venv.bak/
|
|
113
124
|
venv/
|
|
114
125
|
wheels/
|
|
115
|
-
.task/
|
|
116
|
-
.runic
|
|
117
|
-
runic_test/
|
|
118
|
-
.claude/ralph-loop.local.md
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runic-py
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.3
|
|
4
|
+
Summary: Graph schema migrations and ORM for Cypher-based graph databases.
|
|
5
5
|
Project-URL: Homepage, https://runic-py.readthedocs.io/en/latest/
|
|
6
6
|
Project-URL: Repository, https://github.com/jenreh/runic
|
|
7
7
|
Author: Jens Rehpöhler
|
|
@@ -11,25 +11,28 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Topic ::
|
|
14
|
+
Classifier: Topic :: Database
|
|
15
15
|
Requires-Python: >=3.14
|
|
16
16
|
Requires-Dist: mako<2.0.0,>=1.3.0
|
|
17
|
-
Requires-Dist: rich<16.0.0,>=
|
|
17
|
+
Requires-Dist: rich<16.0.0,>=14.0.0
|
|
18
|
+
Requires-Dist: shellingham<2.0.0,>=1.5.0
|
|
18
19
|
Requires-Dist: typer<1.0.0,>=0.26.0
|
|
19
20
|
Provides-Extra: age
|
|
20
|
-
Requires-Dist: psycopg
|
|
21
|
+
Requires-Dist: psycopg<4.0.0,>=3.3.4; extra == 'age'
|
|
22
|
+
Provides-Extra: age-binary
|
|
23
|
+
Requires-Dist: psycopg[binary]<4.0.0,>=3.3.4; extra == 'age-binary'
|
|
21
24
|
Provides-Extra: all
|
|
22
25
|
Requires-Dist: falkordb<2.0.0,>=1.6.0; extra == 'all'
|
|
23
|
-
Requires-Dist: neo4j
|
|
24
|
-
Requires-Dist: psycopg[binary]
|
|
26
|
+
Requires-Dist: neo4j<7.0.0,>=6.2.0; extra == 'all'
|
|
27
|
+
Requires-Dist: psycopg[binary]<4.0.0,>=3.3.4; extra == 'all'
|
|
25
28
|
Provides-Extra: arcadedb
|
|
26
|
-
Requires-Dist: neo4j
|
|
29
|
+
Requires-Dist: neo4j<7.0.0,>=6.2.0; extra == 'arcadedb'
|
|
27
30
|
Provides-Extra: falkordb
|
|
28
31
|
Requires-Dist: falkordb<2.0.0,>=1.6.0; extra == 'falkordb'
|
|
29
32
|
Provides-Extra: memgraph
|
|
30
|
-
Requires-Dist: neo4j
|
|
33
|
+
Requires-Dist: neo4j<7.0.0,>=6.2.0; extra == 'memgraph'
|
|
31
34
|
Provides-Extra: neo4j
|
|
32
|
-
Requires-Dist: neo4j
|
|
35
|
+
Requires-Dist: neo4j<7.0.0,>=6.2.0; extra == 'neo4j'
|
|
33
36
|
Description-Content-Type: text/markdown
|
|
34
37
|
|
|
35
38
|
<div align="center">
|
|
@@ -39,7 +42,8 @@ Description-Content-Type: text/markdown
|
|
|
39
42
|
|
|
40
43
|
**Graph schema migrations and ORM for Cypher-based graph databases.**
|
|
41
44
|
|
|
42
|
-

|
|
46
|
+
[](https://pypi.org/project/runic-py/)
|
|
43
47
|
[](https://www.python.org)
|
|
44
48
|
[](LICENSE.md)
|
|
45
49
|
|
|
@@ -294,9 +298,9 @@ if min_age > 0:
|
|
|
294
298
|
stmt = stmt.where(User.age >= min_age)
|
|
295
299
|
|
|
296
300
|
with Session(driver) as session:
|
|
297
|
-
users: list[User]
|
|
298
|
-
user:
|
|
299
|
-
n:
|
|
301
|
+
users: list[User] = session.scalars(stmt) # list[User]
|
|
302
|
+
user: User | None = session.scalar(stmt) # User | None
|
|
303
|
+
n: int = session.count(stmt) # int
|
|
300
304
|
```
|
|
301
305
|
|
|
302
306
|
The same `stmt` is reusable — pass it to multiple sessions or execute it
|
|
@@ -354,4 +358,4 @@ class Article(Node, labels=["Article"]):
|
|
|
354
358
|
|
|
355
359
|
## Documentation
|
|
356
360
|
|
|
357
|
-
Full conceptual overview, async usage, advanced CLI flags, and API reference at the complete [Runic Documentation](https://runic-py.readthedocs.io/latest
|
|
361
|
+
Full conceptual overview, async usage, advanced CLI flags, and API reference at the complete [Runic Documentation](https://runic-py.readthedocs.io/en/latest).
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
**Graph schema migrations and ORM for Cypher-based graph databases.**
|
|
7
7
|
|
|
8
|
-

|
|
9
|
+
[](https://pypi.org/project/runic-py/)
|
|
9
10
|
[](https://www.python.org)
|
|
10
11
|
[](LICENSE.md)
|
|
11
12
|
|
|
@@ -260,9 +261,9 @@ if min_age > 0:
|
|
|
260
261
|
stmt = stmt.where(User.age >= min_age)
|
|
261
262
|
|
|
262
263
|
with Session(driver) as session:
|
|
263
|
-
users: list[User]
|
|
264
|
-
user:
|
|
265
|
-
n:
|
|
264
|
+
users: list[User] = session.scalars(stmt) # list[User]
|
|
265
|
+
user: User | None = session.scalar(stmt) # User | None
|
|
266
|
+
n: int = session.count(stmt) # int
|
|
266
267
|
```
|
|
267
268
|
|
|
268
269
|
The same `stmt` is reusable — pass it to multiple sessions or execute it
|
|
@@ -320,4 +321,4 @@ class Article(Node, labels=["Article"]):
|
|
|
320
321
|
|
|
321
322
|
## Documentation
|
|
322
323
|
|
|
323
|
-
Full conceptual overview, async usage, advanced CLI flags, and API reference at the complete [Runic Documentation](https://runic-py.readthedocs.io/latest
|
|
324
|
+
Full conceptual overview, async usage, advanced CLI flags, and API reference at the complete [Runic Documentation](https://runic-py.readthedocs.io/en/latest).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "runic-py"
|
|
3
|
-
version = "0.3.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.3.3"
|
|
4
|
+
description = "Graph schema migrations and ORM for Cypher-based graph databases."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
license = { text = "MIT" }
|
|
@@ -12,47 +12,49 @@ classifiers = [
|
|
|
12
12
|
"License :: OSI Approved :: MIT License",
|
|
13
13
|
"Programming Language :: Python :: 3",
|
|
14
14
|
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
-
"Topic ::
|
|
15
|
+
"Topic :: Database",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
dependencies = [
|
|
19
19
|
"typer>=0.26.0,<1.0.0",
|
|
20
|
-
"rich>=
|
|
20
|
+
"rich>=14.0.0,<16.0.0",
|
|
21
21
|
"mako>=1.3.0,<2.0.0",
|
|
22
|
+
"shellingham>=1.5.0,<2.0.0",
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[project.optional-dependencies]
|
|
25
26
|
falkordb = ["falkordb>=1.6.0,<2.0.0"]
|
|
26
|
-
neo4j = ["neo4j>=6.2.0"]
|
|
27
|
-
memgraph = ["neo4j>=6.2.0"]
|
|
28
|
-
arcadedb = ["neo4j>=6.2.0"]
|
|
29
|
-
age = ["psycopg
|
|
27
|
+
neo4j = ["neo4j>=6.2.0,<7.0.0"]
|
|
28
|
+
memgraph = ["neo4j>=6.2.0,<7.0.0"]
|
|
29
|
+
arcadedb = ["neo4j>=6.2.0,<7.0.0"]
|
|
30
|
+
age = ["psycopg>=3.3.4,<4.0.0"]
|
|
31
|
+
age-binary = ["psycopg[binary]>=3.3.4,<4.0.0"]
|
|
30
32
|
all = [
|
|
31
33
|
"falkordb>=1.6.0,<2.0.0",
|
|
32
|
-
"neo4j>=6.2.0",
|
|
33
|
-
"psycopg[binary]>=3.3.4",
|
|
34
|
+
"neo4j>=6.2.0,<7.0.0",
|
|
35
|
+
"psycopg[binary]>=3.3.4,<4.0.0",
|
|
34
36
|
]
|
|
35
|
-
|
|
36
37
|
[dependency-groups]
|
|
37
38
|
dev = [
|
|
38
39
|
"bump-my-version==1.3.0",
|
|
40
|
+
"deepeval==4.0.5",
|
|
39
41
|
"detect-secrets==1.5.0",
|
|
42
|
+
"falkordb>=1.6.1",
|
|
43
|
+
"falkordblite==0.10.0",
|
|
44
|
+
"furo==2025.12.19",
|
|
45
|
+
"neo4j==6.2.0",
|
|
40
46
|
"pre-commit==4.6.0",
|
|
41
|
-
"
|
|
47
|
+
"psycopg[binary]==3.3.4",
|
|
42
48
|
"pytest-asyncio==1.4.0",
|
|
43
49
|
"pytest-cov==7.1.0",
|
|
44
50
|
"pytest-httpserver==1.1.5",
|
|
51
|
+
"pytest==9.0.3",
|
|
52
|
+
"python-dotenv==1.2.2",
|
|
45
53
|
"ruff==0.15.16",
|
|
46
|
-
"falkordblite==0.10.0",
|
|
47
|
-
"sphinx==9.1.0",
|
|
48
|
-
"furo==2025.12.19",
|
|
49
54
|
"sphinx-copybutton==0.5.2",
|
|
50
55
|
"sphinx-design==0.7.0",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"falkordb>=1.6.0,<2.0.0",
|
|
54
|
-
"neo4j>=6.2.0",
|
|
55
|
-
"psycopg[binary]>=3.3.4",
|
|
56
|
+
"sphinx==9.1.0",
|
|
57
|
+
"ty==0.0.44",
|
|
56
58
|
]
|
|
57
59
|
|
|
58
60
|
[project.scripts]
|
|
@@ -91,7 +93,7 @@ exclude = [
|
|
|
91
93
|
|
|
92
94
|
[tool.bumpversion]
|
|
93
95
|
commit = false
|
|
94
|
-
current_version = "0.3.
|
|
96
|
+
current_version = "0.3.3"
|
|
95
97
|
tag = false
|
|
96
98
|
allow-dirty = false
|
|
97
99
|
|
|
@@ -107,8 +109,13 @@ replace = ''
|
|
|
107
109
|
|
|
108
110
|
[[tool.bumpversion.files]]
|
|
109
111
|
filename = "docs/source/index.rst"
|
|
110
|
-
search = '
|
|
111
|
-
replace = '
|
|
112
|
+
search = '.. |Github| image:: https://img.shields.io/badge/release-{current_version}-yellow?logo=github'
|
|
113
|
+
replace = '.. |Github| image:: https://img.shields.io/badge/release-{new_version}-yellow?logo=github'
|
|
114
|
+
|
|
115
|
+
[[tool.bumpversion.files]]
|
|
116
|
+
filename = "docs/source/index.rst"
|
|
117
|
+
search = ' :target: https://github.com/jenreh/runic/tree/v{current_version}'
|
|
118
|
+
replace = ' :target: https://github.com/jenreh/runic/tree/v{new_version}'
|
|
112
119
|
|
|
113
120
|
[[tool.bumpversion.files]]
|
|
114
121
|
filename = "docs/source/conf.py"
|
|
@@ -243,6 +250,9 @@ known-local-folder = ["sonos"]
|
|
|
243
250
|
[tool.pytest.ini_options]
|
|
244
251
|
cache_dir = ".cache/pytest"
|
|
245
252
|
testpaths = ["tests"]
|
|
253
|
+
# Evals (tests/evals) are excluded from the default suite; run explicitly with
|
|
254
|
+
# `uv run pytest tests/evals`.
|
|
255
|
+
addopts = "--ignore=tests/evals"
|
|
246
256
|
asyncio_mode = "auto"
|
|
247
257
|
markers = [
|
|
248
258
|
"integration: requires a live graph backend (embedded FalkorDB via redislite by default; other backends via RUNIC_TEST_BACKENDS env var + docker-compose.test.yml)",
|
|
@@ -262,6 +272,8 @@ exclude = [
|
|
|
262
272
|
"build",
|
|
263
273
|
"dist",
|
|
264
274
|
"examples",
|
|
275
|
+
"skill",
|
|
276
|
+
"tests/evals",
|
|
265
277
|
]
|
|
266
278
|
|
|
267
279
|
[tool.ty.rules]
|
|
@@ -273,7 +285,7 @@ cache_dir = ".cache/mypy"
|
|
|
273
285
|
|
|
274
286
|
[tool.coverage.run]
|
|
275
287
|
branch = true
|
|
276
|
-
source = ["
|
|
288
|
+
source = ["runic"]
|
|
277
289
|
omit = ["runic/adapters/*"]
|
|
278
290
|
|
|
279
291
|
[tool.coverage.report]
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Protocol, runtime_checkable
|
|
4
4
|
|
|
5
|
-
from runic.migrate.introspect import LiveSchema
|
|
5
|
+
from runic.migrate.introspect import LiveSchema, SchemaSnapshot
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@runtime_checkable
|
|
@@ -37,6 +37,7 @@ class GraphAdapter(Protocol):
|
|
|
37
37
|
|
|
38
38
|
# Schema introspection
|
|
39
39
|
def read_live_schema(self) -> LiveSchema: ...
|
|
40
|
+
def introspect_schema(self) -> SchemaSnapshot: ...
|
|
40
41
|
|
|
41
42
|
# Schema DDL
|
|
42
43
|
def create_range_index(
|
|
@@ -74,6 +75,7 @@ class GraphAdapter(Protocol):
|
|
|
74
75
|
def delete_graph(self) -> None: ...
|
|
75
76
|
|
|
76
77
|
# Snapshots
|
|
78
|
+
def supports_snapshots(self) -> bool: ...
|
|
77
79
|
def snapshot(self, snap_name: str) -> None: ...
|
|
78
80
|
def restore_snapshot(self, snap_name: str) -> None: ...
|
|
79
81
|
def snapshot_exists(self, snap_name: str) -> bool: ...
|
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
from abc import ABC, abstractmethod
|
|
12
12
|
from typing import Any, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
|
-
from runic.migrate.introspect import LiveSchema
|
|
14
|
+
from runic.migrate.introspect import LiveSchema, SchemaSnapshot
|
|
15
15
|
from runic.orm.schema.index_manager import IndexSpec
|
|
16
16
|
|
|
17
17
|
log = logging.getLogger(__name__)
|
|
@@ -124,6 +124,19 @@ class GraphAdapterBase(ABC):
|
|
|
124
124
|
"""Execute a read-only Cypher query and return the result."""
|
|
125
125
|
...
|
|
126
126
|
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# DDL execution
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def _execute_ddl(self, cypher: str) -> Any:
|
|
132
|
+
"""Log and execute a single DDL statement, propagating any error.
|
|
133
|
+
|
|
134
|
+
Shared by the Bolt-based adapters so a failed schema operation aborts
|
|
135
|
+
the migration rather than leaving the schema silently incomplete.
|
|
136
|
+
"""
|
|
137
|
+
log.info("%s DDL: %s", self._backend_name, cypher)
|
|
138
|
+
return self.run_query(cypher)
|
|
139
|
+
|
|
127
140
|
# ------------------------------------------------------------------
|
|
128
141
|
# Version tracking
|
|
129
142
|
# ------------------------------------------------------------------
|
|
@@ -154,6 +167,17 @@ class GraphAdapterBase(ABC):
|
|
|
154
167
|
constraints=[],
|
|
155
168
|
)
|
|
156
169
|
|
|
170
|
+
def introspect_schema(self) -> SchemaSnapshot:
|
|
171
|
+
"""Snapshot the live schema for baseline / autogenerate.
|
|
172
|
+
|
|
173
|
+
Defaults to raising; backends with full index/constraint introspection
|
|
174
|
+
(currently FalkorDB) override this.
|
|
175
|
+
"""
|
|
176
|
+
raise NotImplementedError(
|
|
177
|
+
f"{self._backend_name} does not support schema introspection for "
|
|
178
|
+
"baseline/autogenerate."
|
|
179
|
+
)
|
|
180
|
+
|
|
157
181
|
def get_existing_specs(self) -> set[IndexSpec]:
|
|
158
182
|
return set()
|
|
159
183
|
|
|
@@ -169,6 +193,14 @@ class GraphAdapterBase(ABC):
|
|
|
169
193
|
)
|
|
170
194
|
self.run_query("MATCH (n) DETACH DELETE n")
|
|
171
195
|
|
|
196
|
+
def supports_snapshots(self) -> bool:
|
|
197
|
+
"""Whether this adapter can take and restore pre-migration snapshots.
|
|
198
|
+
|
|
199
|
+
Defaults to ``False``; backends with native graph-copy support (e.g.
|
|
200
|
+
FalkorDB) override this to ``True``.
|
|
201
|
+
"""
|
|
202
|
+
return False
|
|
203
|
+
|
|
172
204
|
def snapshot(self, snap_name: str) -> None:
|
|
173
205
|
raise NotImplementedError(
|
|
174
206
|
f"{self._backend_name} snapshots are not supported via runic migrate."
|
|
@@ -84,44 +84,20 @@ class ArcadeDBAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
84
84
|
# ------------------------------------------------------------------
|
|
85
85
|
|
|
86
86
|
def create_vertex_type(self, label: str) -> None:
|
|
87
|
-
|
|
88
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
89
|
-
try:
|
|
90
|
-
self.run_query(cypher)
|
|
91
|
-
except Exception as exc:
|
|
92
|
-
log.warning("ArcadeDB create_vertex_type failed for %s: %s", label, exc)
|
|
87
|
+
self._execute_ddl(f"CREATE VERTEX TYPE `{label}` IF NOT EXISTS")
|
|
93
88
|
|
|
94
89
|
def create_edge_type(self, type_name: str) -> None:
|
|
95
|
-
|
|
96
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
97
|
-
try:
|
|
98
|
-
self.run_query(cypher)
|
|
99
|
-
except Exception as exc:
|
|
100
|
-
log.warning("ArcadeDB create_edge_type failed for %s: %s", type_name, exc)
|
|
90
|
+
self._execute_ddl(f"CREATE EDGE TYPE `{type_name}` IF NOT EXISTS")
|
|
101
91
|
|
|
102
92
|
# ------------------------------------------------------------------
|
|
103
93
|
# DDL — indexes
|
|
104
94
|
# ------------------------------------------------------------------
|
|
105
95
|
|
|
106
96
|
def create_range_index(self, label: str, prop: str, *, rel: bool = False) -> None: # noqa: ARG002
|
|
107
|
-
|
|
108
|
-
log.info("ArcadeDB: %s", cypher)
|
|
109
|
-
try:
|
|
110
|
-
self.run_query(cypher)
|
|
111
|
-
except Exception as exc:
|
|
112
|
-
log.warning(
|
|
113
|
-
"ArcadeDB create_range_index failed for %s.%s: %s", label, prop, exc
|
|
114
|
-
)
|
|
97
|
+
self._execute_ddl(f"CREATE INDEX ON `{label}` ({prop})")
|
|
115
98
|
|
|
116
99
|
def drop_range_index(self, label: str, prop: str, *, rel: bool = False) -> None: # noqa: ARG002
|
|
117
|
-
|
|
118
|
-
log.info("ArcadeDB: %s", cypher)
|
|
119
|
-
try:
|
|
120
|
-
self.run_query(cypher)
|
|
121
|
-
except Exception as exc:
|
|
122
|
-
log.warning(
|
|
123
|
-
"ArcadeDB drop_range_index failed for %s.%s: %s", label, prop, exc
|
|
124
|
-
)
|
|
100
|
+
self._execute_ddl(f"DROP INDEX ON `{label}` ({prop})")
|
|
125
101
|
|
|
126
102
|
def get_existing_specs(self) -> set[IndexSpec]:
|
|
127
103
|
return set()
|
|
@@ -134,25 +110,11 @@ class ArcadeDBAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
134
110
|
stopwords: list[str] | None = None, # noqa: ARG002
|
|
135
111
|
) -> None:
|
|
136
112
|
props_str = ", ".join(props)
|
|
137
|
-
|
|
138
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
139
|
-
try:
|
|
140
|
-
self.run_query(cypher)
|
|
141
|
-
except Exception as exc:
|
|
142
|
-
log.warning(
|
|
143
|
-
"ArcadeDB create_fulltext_index failed for %s %s: %s", label, props, exc
|
|
144
|
-
)
|
|
113
|
+
self._execute_ddl(f"CREATE FULLTEXT INDEX ON `{label}` ({props_str})")
|
|
145
114
|
|
|
146
115
|
def drop_fulltext_index(self, label: str, *props: str) -> None:
|
|
147
116
|
props_str = ", ".join(props)
|
|
148
|
-
|
|
149
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
150
|
-
try:
|
|
151
|
-
self.run_query(cypher)
|
|
152
|
-
except Exception as exc:
|
|
153
|
-
log.warning(
|
|
154
|
-
"ArcadeDB drop_fulltext_index failed for %s %s: %s", label, props, exc
|
|
155
|
-
)
|
|
117
|
+
self._execute_ddl(f"DROP INDEX ON `{label}` ({props_str})")
|
|
156
118
|
|
|
157
119
|
def create_vector_index(
|
|
158
120
|
self,
|
|
@@ -189,14 +151,7 @@ class ArcadeDBAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
189
151
|
) -> None:
|
|
190
152
|
if kind == "UNIQUE" and entity == "NODE" and len(props) == 1:
|
|
191
153
|
prop = props[0]
|
|
192
|
-
|
|
193
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
194
|
-
try:
|
|
195
|
-
self.run_query(cypher)
|
|
196
|
-
except Exception as exc:
|
|
197
|
-
log.warning(
|
|
198
|
-
"ArcadeDB create_constraint failed for %s.%s: %s", label, prop, exc
|
|
199
|
-
)
|
|
154
|
+
self._execute_ddl(f"CREATE INDEX ON `{label}` ({prop}) UNIQUE")
|
|
200
155
|
else:
|
|
201
156
|
log.warning(
|
|
202
157
|
"ArcadeDB create_constraint: unsupported kind=%s entity=%s label=%s props=%s",
|
|
@@ -211,14 +166,7 @@ class ArcadeDBAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
211
166
|
) -> None:
|
|
212
167
|
if kind == "UNIQUE" and entity == "NODE" and len(props) == 1:
|
|
213
168
|
prop = props[0]
|
|
214
|
-
|
|
215
|
-
log.info("ArcadeDB DDL: %s", cypher)
|
|
216
|
-
try:
|
|
217
|
-
self.run_query(cypher)
|
|
218
|
-
except Exception as exc:
|
|
219
|
-
log.warning(
|
|
220
|
-
"ArcadeDB drop_constraint failed for %s.%s: %s", label, prop, exc
|
|
221
|
-
)
|
|
169
|
+
self._execute_ddl(f"DROP INDEX ON `{label}` ({prop}) UNIQUE")
|
|
222
170
|
else:
|
|
223
171
|
log.warning(
|
|
224
172
|
"ArcadeDB drop_constraint: unsupported kind=%s entity=%s label=%s props=%s",
|
|
@@ -160,6 +160,9 @@ class FalkorDBAdapter(GraphAdapter):
|
|
|
160
160
|
def read_live_schema(self) -> LiveSchema:
|
|
161
161
|
return introspect.read_live_schema(self._graph)
|
|
162
162
|
|
|
163
|
+
def introspect_schema(self) -> introspect.SchemaSnapshot:
|
|
164
|
+
return introspect.introspect_graph(self._graph)
|
|
165
|
+
|
|
163
166
|
def get_existing_specs(self) -> set[IndexSpec]:
|
|
164
167
|
try:
|
|
165
168
|
schema = self.read_live_schema()
|
|
@@ -328,6 +331,9 @@ class FalkorDBAdapter(GraphAdapter):
|
|
|
328
331
|
# Snapshots
|
|
329
332
|
# ------------------------------------------------------------------
|
|
330
333
|
|
|
334
|
+
def supports_snapshots(self) -> bool:
|
|
335
|
+
return True
|
|
336
|
+
|
|
331
337
|
def snapshot(self, snap_name: str) -> None:
|
|
332
338
|
# GRAPH.COPY fails on an empty key; initialize graph if it doesn't exist yet
|
|
333
339
|
if self._graph.name not in self._db.list_graphs():
|
|
@@ -95,24 +95,10 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
95
95
|
# ------------------------------------------------------------------
|
|
96
96
|
|
|
97
97
|
def create_range_index(self, label: str, prop: str, *, rel: bool = False) -> None: # noqa: ARG002
|
|
98
|
-
|
|
99
|
-
log.info("Memgraph DDL: %s", cypher)
|
|
100
|
-
try:
|
|
101
|
-
self.run_query(cypher)
|
|
102
|
-
except Exception as exc:
|
|
103
|
-
log.warning(
|
|
104
|
-
"Memgraph create_range_index failed for %s.%s: %s", label, prop, exc
|
|
105
|
-
)
|
|
98
|
+
self._execute_ddl(f"CREATE INDEX ON :{label}({prop})")
|
|
106
99
|
|
|
107
100
|
def drop_range_index(self, label: str, prop: str, *, rel: bool = False) -> None: # noqa: ARG002
|
|
108
|
-
|
|
109
|
-
log.info("Memgraph DDL: %s", cypher)
|
|
110
|
-
try:
|
|
111
|
-
self.run_query(cypher)
|
|
112
|
-
except Exception as exc:
|
|
113
|
-
log.warning(
|
|
114
|
-
"Memgraph drop_range_index failed for %s.%s: %s", label, prop, exc
|
|
115
|
-
)
|
|
101
|
+
self._execute_ddl(f"DROP INDEX ON :{label}({prop})")
|
|
116
102
|
|
|
117
103
|
def create_fulltext_index(
|
|
118
104
|
self,
|
|
@@ -122,8 +108,6 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
122
108
|
stopwords: list[str] | None = None, # noqa: ARG002
|
|
123
109
|
) -> None:
|
|
124
110
|
# Memgraph TEXT INDEX is whole-label; index name = label (matches MemgraphDialect)
|
|
125
|
-
cypher = f"CREATE TEXT INDEX {label} ON :{label}"
|
|
126
|
-
log.info("Memgraph DDL: %s", cypher)
|
|
127
111
|
if len(props) > 1:
|
|
128
112
|
log.warning(
|
|
129
113
|
"Memgraph text indexes cover the full label — "
|
|
@@ -131,18 +115,10 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
131
115
|
props,
|
|
132
116
|
label,
|
|
133
117
|
)
|
|
134
|
-
|
|
135
|
-
self.run_query(cypher)
|
|
136
|
-
except Exception as exc:
|
|
137
|
-
log.warning("Memgraph create_fulltext_index failed for %s: %s", label, exc)
|
|
118
|
+
self._execute_ddl(f"CREATE TEXT INDEX {label} ON :{label}")
|
|
138
119
|
|
|
139
120
|
def drop_fulltext_index(self, label: str, *props: str) -> None: # noqa: ARG002
|
|
140
|
-
|
|
141
|
-
log.info("Memgraph DDL: %s", cypher)
|
|
142
|
-
try:
|
|
143
|
-
self.run_query(cypher)
|
|
144
|
-
except Exception as exc:
|
|
145
|
-
log.warning("Memgraph drop_fulltext_index failed for %s: %s", label, exc)
|
|
121
|
+
self._execute_ddl(f"DROP TEXT INDEX {label}")
|
|
146
122
|
|
|
147
123
|
def create_vector_index(
|
|
148
124
|
self,
|
|
@@ -168,23 +144,10 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
168
144
|
f'{{"dimension": {dimension}, "capacity": 1000, "metric": "{similarity}", '
|
|
169
145
|
f'"resize_coefficient": 2, "m": {m}, "ef_construction": {ef_construction}}}'
|
|
170
146
|
)
|
|
171
|
-
|
|
172
|
-
try:
|
|
173
|
-
self.run_query(cypher)
|
|
174
|
-
except Exception as exc:
|
|
175
|
-
log.warning(
|
|
176
|
-
"Memgraph create_vector_index failed for %s.%s: %s", label, prop, exc
|
|
177
|
-
)
|
|
147
|
+
self._execute_ddl(cypher)
|
|
178
148
|
|
|
179
149
|
def drop_vector_index(self, label: str, prop: str) -> None:
|
|
180
|
-
|
|
181
|
-
log.info("Memgraph DDL: %s", cypher)
|
|
182
|
-
try:
|
|
183
|
-
self.run_query(cypher)
|
|
184
|
-
except Exception as exc:
|
|
185
|
-
log.warning(
|
|
186
|
-
"Memgraph drop_vector_index failed for %s.%s: %s", label, prop, exc
|
|
187
|
-
)
|
|
150
|
+
self._execute_ddl(f"DROP VECTOR INDEX {label}_{prop}")
|
|
188
151
|
|
|
189
152
|
# ------------------------------------------------------------------
|
|
190
153
|
# DDL — constraints
|
|
@@ -195,14 +158,9 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
195
158
|
) -> None:
|
|
196
159
|
if kind == "UNIQUE" and entity == "NODE" and len(props) == 1:
|
|
197
160
|
prop = props[0]
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
self.run_query(cypher)
|
|
202
|
-
except Exception as exc:
|
|
203
|
-
log.warning(
|
|
204
|
-
"Memgraph create_constraint failed for %s.%s: %s", label, prop, exc
|
|
205
|
-
)
|
|
161
|
+
self._execute_ddl(
|
|
162
|
+
f"CREATE CONSTRAINT ON (n:{label}) ASSERT n.{prop} IS UNIQUE"
|
|
163
|
+
)
|
|
206
164
|
else:
|
|
207
165
|
log.warning(
|
|
208
166
|
"Memgraph create_constraint: unsupported kind=%s entity=%s label=%s props=%s",
|
|
@@ -217,14 +175,9 @@ class MemgraphAdapter(GraphAdapterBase, GraphAdapter):
|
|
|
217
175
|
) -> None:
|
|
218
176
|
if kind == "UNIQUE" and entity == "NODE" and len(props) == 1:
|
|
219
177
|
prop = props[0]
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
self.run_query(cypher)
|
|
224
|
-
except Exception as exc:
|
|
225
|
-
log.warning(
|
|
226
|
-
"Memgraph drop_constraint failed for %s.%s: %s", label, prop, exc
|
|
227
|
-
)
|
|
178
|
+
self._execute_ddl(
|
|
179
|
+
f"DROP CONSTRAINT ON (n:{label}) ASSERT n.{prop} IS UNIQUE"
|
|
180
|
+
)
|
|
228
181
|
else:
|
|
229
182
|
log.warning(
|
|
230
183
|
"Memgraph drop_constraint: unsupported kind=%s entity=%s label=%s props=%s",
|