grafeo 0.2.4__cp312-cp312-win32.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.
- grafeo/__init__.py +32 -0
- grafeo/cli.py +320 -0
- grafeo/grafeo.cp312-win32.pyd +0 -0
- grafeo/py.typed +0 -0
- grafeo-0.2.4.dist-info/METADATA +68 -0
- grafeo-0.2.4.dist-info/RECORD +8 -0
- grafeo-0.2.4.dist-info/WHEEL +4 -0
- grafeo-0.2.4.dist-info/entry_points.txt +2 -0
grafeo/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Grafeo - A high-performance, embeddable graph database.
|
|
3
|
+
|
|
4
|
+
This module provides Python bindings for the Grafeo graph database,
|
|
5
|
+
offering a Pythonic interface for graph operations and GQL queries.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from grafeo import GrafeoDB
|
|
9
|
+
>>> db = GrafeoDB()
|
|
10
|
+
>>> node = db.create_node(["Person"], {"name": "Alice", "age": 30})
|
|
11
|
+
>>> result = db.execute("MATCH (n:Person) RETURN n")
|
|
12
|
+
>>> for row in result:
|
|
13
|
+
... print(row)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from grafeo.grafeo import (
|
|
17
|
+
GrafeoDB,
|
|
18
|
+
Node,
|
|
19
|
+
Edge,
|
|
20
|
+
QueryResult,
|
|
21
|
+
Value,
|
|
22
|
+
__version__,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"GrafeoDB",
|
|
27
|
+
"Node",
|
|
28
|
+
"Edge",
|
|
29
|
+
"QueryResult",
|
|
30
|
+
"Value",
|
|
31
|
+
"__version__",
|
|
32
|
+
]
|
grafeo/cli.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Grafeo CLI - Command-line interface for Grafeo graph databases.
|
|
3
|
+
|
|
4
|
+
This module provides a Python CLI wrapper for the Grafeo admin functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import click
|
|
14
|
+
except ImportError:
|
|
15
|
+
print("Error: click is required for the CLI. Install with: uv add grafeo[cli]")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
from grafeo import GrafeoDB
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def format_bytes(bytes_val: int) -> str:
|
|
22
|
+
"""Format bytes as human-readable string."""
|
|
23
|
+
if bytes_val >= 1024**3:
|
|
24
|
+
return f"{bytes_val / (1024**3):.2f} GB"
|
|
25
|
+
elif bytes_val >= 1024**2:
|
|
26
|
+
return f"{bytes_val / (1024**2):.2f} MB"
|
|
27
|
+
elif bytes_val >= 1024:
|
|
28
|
+
return f"{bytes_val / 1024:.2f} KB"
|
|
29
|
+
else:
|
|
30
|
+
return f"{bytes_val} bytes"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_table(headers: list[str], rows: list[list[str]]) -> None:
|
|
34
|
+
"""Print a simple table."""
|
|
35
|
+
if not rows:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Calculate column widths
|
|
39
|
+
widths = [len(h) for h in headers]
|
|
40
|
+
for row in rows:
|
|
41
|
+
for i, cell in enumerate(row):
|
|
42
|
+
if i < len(widths):
|
|
43
|
+
widths[i] = max(widths[i], len(str(cell)))
|
|
44
|
+
|
|
45
|
+
# Print header
|
|
46
|
+
header_line = " | ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
|
|
47
|
+
print(header_line)
|
|
48
|
+
print("-" * len(header_line))
|
|
49
|
+
|
|
50
|
+
# Print rows
|
|
51
|
+
for row in rows:
|
|
52
|
+
row_line = " | ".join(str(c).ljust(widths[i]) for i, c in enumerate(row))
|
|
53
|
+
print(row_line)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def print_key_value(items: list[tuple[str, str]], as_json: bool = False) -> None:
|
|
57
|
+
"""Print key-value pairs."""
|
|
58
|
+
if as_json:
|
|
59
|
+
print(json.dumps(dict(items), indent=2))
|
|
60
|
+
else:
|
|
61
|
+
max_key = max(len(k) for k, _ in items)
|
|
62
|
+
for key, value in items:
|
|
63
|
+
print(f"{key.ljust(max_key)} : {value}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@click.group()
|
|
67
|
+
@click.option("--format", "-f", type=click.Choice(["table", "json"]), default="table", help="Output format")
|
|
68
|
+
@click.option("--quiet", "-q", is_flag=True, help="Suppress progress messages")
|
|
69
|
+
@click.version_option()
|
|
70
|
+
@click.pass_context
|
|
71
|
+
def cli(ctx: click.Context, format: str, quiet: bool) -> None:
|
|
72
|
+
"""Grafeo database administration tool."""
|
|
73
|
+
ctx.ensure_object(dict)
|
|
74
|
+
ctx.obj["format"] = format
|
|
75
|
+
ctx.obj["quiet"] = quiet
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@cli.command()
|
|
79
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
80
|
+
@click.pass_context
|
|
81
|
+
def info(ctx: click.Context, path: str) -> None:
|
|
82
|
+
"""Display database information."""
|
|
83
|
+
db = GrafeoDB.open(path)
|
|
84
|
+
info_dict = db.info()
|
|
85
|
+
|
|
86
|
+
as_json = ctx.obj["format"] == "json"
|
|
87
|
+
if as_json:
|
|
88
|
+
print(json.dumps(info_dict, indent=2, default=str))
|
|
89
|
+
else:
|
|
90
|
+
items = [
|
|
91
|
+
("Mode", str(info_dict.get("mode", "unknown"))),
|
|
92
|
+
("Nodes", str(info_dict.get("node_count", 0))),
|
|
93
|
+
("Edges", str(info_dict.get("edge_count", 0))),
|
|
94
|
+
("Persistent", str(info_dict.get("is_persistent", False))),
|
|
95
|
+
("Path", str(info_dict.get("path") or "(in-memory)")),
|
|
96
|
+
("WAL Enabled", str(info_dict.get("wal_enabled", False))),
|
|
97
|
+
("Version", str(info_dict.get("version", "unknown"))),
|
|
98
|
+
]
|
|
99
|
+
print_key_value(items)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@cli.command()
|
|
103
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
104
|
+
@click.pass_context
|
|
105
|
+
def stats(ctx: click.Context, path: str) -> None:
|
|
106
|
+
"""Show detailed statistics."""
|
|
107
|
+
db = GrafeoDB.open(path)
|
|
108
|
+
stats_dict = db.detailed_stats()
|
|
109
|
+
|
|
110
|
+
as_json = ctx.obj["format"] == "json"
|
|
111
|
+
if as_json:
|
|
112
|
+
print(json.dumps(stats_dict, indent=2, default=str))
|
|
113
|
+
else:
|
|
114
|
+
memory = stats_dict.get("memory_bytes", 0)
|
|
115
|
+
disk = stats_dict.get("disk_bytes")
|
|
116
|
+
items = [
|
|
117
|
+
("Nodes", str(stats_dict.get("node_count", 0))),
|
|
118
|
+
("Edges", str(stats_dict.get("edge_count", 0))),
|
|
119
|
+
("Labels", str(stats_dict.get("label_count", 0))),
|
|
120
|
+
("Edge Types", str(stats_dict.get("edge_type_count", 0))),
|
|
121
|
+
("Property Keys", str(stats_dict.get("property_key_count", 0))),
|
|
122
|
+
("Indexes", str(stats_dict.get("index_count", 0))),
|
|
123
|
+
("Memory Usage", format_bytes(memory)),
|
|
124
|
+
("Disk Usage", format_bytes(disk) if disk else "N/A"),
|
|
125
|
+
]
|
|
126
|
+
print_key_value(items)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@cli.command()
|
|
130
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
131
|
+
@click.pass_context
|
|
132
|
+
def schema(ctx: click.Context, path: str) -> None:
|
|
133
|
+
"""Display schema information."""
|
|
134
|
+
db = GrafeoDB.open(path)
|
|
135
|
+
schema_dict = db.schema()
|
|
136
|
+
|
|
137
|
+
as_json = ctx.obj["format"] == "json"
|
|
138
|
+
if as_json:
|
|
139
|
+
print(json.dumps(schema_dict, indent=2, default=str))
|
|
140
|
+
else:
|
|
141
|
+
mode = schema_dict.get("mode", "lpg")
|
|
142
|
+
print(f"Mode: {mode.upper()}\n")
|
|
143
|
+
|
|
144
|
+
if mode == "lpg":
|
|
145
|
+
labels = schema_dict.get("labels", [])
|
|
146
|
+
if labels:
|
|
147
|
+
print("Labels:")
|
|
148
|
+
print_table(["Name", "Count"], [[l["name"], str(l["count"])] for l in labels])
|
|
149
|
+
print()
|
|
150
|
+
|
|
151
|
+
edge_types = schema_dict.get("edge_types", [])
|
|
152
|
+
if edge_types:
|
|
153
|
+
print("Edge Types:")
|
|
154
|
+
print_table(["Name", "Count"], [[e["name"], str(e["count"])] for e in edge_types])
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
prop_keys = schema_dict.get("property_keys", [])
|
|
158
|
+
if prop_keys:
|
|
159
|
+
print("Property Keys:")
|
|
160
|
+
for key in prop_keys:
|
|
161
|
+
print(f" - {key}")
|
|
162
|
+
else:
|
|
163
|
+
predicates = schema_dict.get("predicates", [])
|
|
164
|
+
if predicates:
|
|
165
|
+
print("Predicates:")
|
|
166
|
+
print_table(["IRI", "Count"], [[p["iri"], str(p["count"])] for p in predicates])
|
|
167
|
+
print()
|
|
168
|
+
|
|
169
|
+
graphs = schema_dict.get("named_graphs", [])
|
|
170
|
+
if graphs:
|
|
171
|
+
print("Named Graphs:")
|
|
172
|
+
for g in graphs:
|
|
173
|
+
print(f" - {g}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@cli.command()
|
|
177
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
178
|
+
@click.pass_context
|
|
179
|
+
def validate(ctx: click.Context, path: str) -> None:
|
|
180
|
+
"""Validate database integrity."""
|
|
181
|
+
db = GrafeoDB.open(path)
|
|
182
|
+
result = db.validate()
|
|
183
|
+
|
|
184
|
+
as_json = ctx.obj["format"] == "json"
|
|
185
|
+
if as_json:
|
|
186
|
+
print(json.dumps(result, indent=2, default=str))
|
|
187
|
+
else:
|
|
188
|
+
errors = result.get("errors", [])
|
|
189
|
+
warnings = result.get("warnings", [])
|
|
190
|
+
|
|
191
|
+
if not errors:
|
|
192
|
+
click.secho("Database is valid", fg="green")
|
|
193
|
+
else:
|
|
194
|
+
click.secho("Database has errors", fg="red")
|
|
195
|
+
|
|
196
|
+
print(f"\nErrors: {len(errors)}, Warnings: {len(warnings)}")
|
|
197
|
+
|
|
198
|
+
if errors:
|
|
199
|
+
print("\nErrors:")
|
|
200
|
+
print_table(
|
|
201
|
+
["Code", "Message", "Context"],
|
|
202
|
+
[[e["code"], e["message"], e.get("context", "-")] for e in errors],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if warnings:
|
|
206
|
+
print("\nWarnings:")
|
|
207
|
+
print_table(
|
|
208
|
+
["Code", "Message", "Context"],
|
|
209
|
+
[[w["code"], w["message"], w.get("context", "-")] for w in warnings],
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if errors:
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@cli.group()
|
|
217
|
+
def wal() -> None:
|
|
218
|
+
"""Manage Write-Ahead Log."""
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@wal.command("status")
|
|
223
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
224
|
+
@click.pass_context
|
|
225
|
+
def wal_status(ctx: click.Context, path: str) -> None:
|
|
226
|
+
"""Show WAL status."""
|
|
227
|
+
db = GrafeoDB.open(path)
|
|
228
|
+
status = db.wal_status()
|
|
229
|
+
|
|
230
|
+
as_json = ctx.obj["format"] == "json"
|
|
231
|
+
if as_json:
|
|
232
|
+
print(json.dumps(status, indent=2, default=str))
|
|
233
|
+
else:
|
|
234
|
+
items = [
|
|
235
|
+
("Enabled", str(status.get("enabled", False))),
|
|
236
|
+
("Path", str(status.get("path") or "N/A")),
|
|
237
|
+
("Size", format_bytes(status.get("size_bytes", 0))),
|
|
238
|
+
("Records", str(status.get("record_count", 0))),
|
|
239
|
+
("Last Checkpoint", str(status.get("last_checkpoint") or "Never")),
|
|
240
|
+
("Current Epoch", str(status.get("current_epoch", 0))),
|
|
241
|
+
]
|
|
242
|
+
print_key_value(items)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@wal.command("checkpoint")
|
|
246
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
247
|
+
@click.pass_context
|
|
248
|
+
def wal_checkpoint(ctx: click.Context, path: str) -> None:
|
|
249
|
+
"""Force a WAL checkpoint."""
|
|
250
|
+
quiet = ctx.obj["quiet"]
|
|
251
|
+
if not quiet:
|
|
252
|
+
click.echo("Forcing WAL checkpoint...")
|
|
253
|
+
|
|
254
|
+
db = GrafeoDB.open(path)
|
|
255
|
+
db.wal_checkpoint()
|
|
256
|
+
|
|
257
|
+
if not quiet:
|
|
258
|
+
click.secho("WAL checkpoint completed", fg="green")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@cli.group()
|
|
262
|
+
def backup() -> None:
|
|
263
|
+
"""Manage backups."""
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@backup.command("create")
|
|
268
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
269
|
+
@click.option("--output", "-o", required=True, type=click.Path(), help="Output backup path")
|
|
270
|
+
@click.pass_context
|
|
271
|
+
def backup_create(ctx: click.Context, path: str, output: str) -> None:
|
|
272
|
+
"""Create a database backup."""
|
|
273
|
+
quiet = ctx.obj["quiet"]
|
|
274
|
+
if not quiet:
|
|
275
|
+
click.echo(f"Creating backup of {path}...")
|
|
276
|
+
|
|
277
|
+
db = GrafeoDB.open(path)
|
|
278
|
+
db.save(output)
|
|
279
|
+
|
|
280
|
+
if not quiet:
|
|
281
|
+
click.secho(f"Backup created at {output}", fg="green")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@backup.command("restore")
|
|
285
|
+
@click.argument("backup_path", type=click.Path(exists=True))
|
|
286
|
+
@click.argument("target_path", type=click.Path())
|
|
287
|
+
@click.option("--force", is_flag=True, help="Overwrite existing target")
|
|
288
|
+
@click.pass_context
|
|
289
|
+
def backup_restore(ctx: click.Context, backup_path: str, target_path: str, force: bool) -> None:
|
|
290
|
+
"""Restore from a backup."""
|
|
291
|
+
quiet = ctx.obj["quiet"]
|
|
292
|
+
target = Path(target_path)
|
|
293
|
+
|
|
294
|
+
if target.exists() and not force:
|
|
295
|
+
click.echo(f"Error: Target {target_path} already exists. Use --force to overwrite.", err=True)
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
|
|
298
|
+
if target.exists() and force:
|
|
299
|
+
if not quiet:
|
|
300
|
+
click.echo(f"Removing existing database at {target_path}...")
|
|
301
|
+
import shutil
|
|
302
|
+
shutil.rmtree(target_path)
|
|
303
|
+
|
|
304
|
+
if not quiet:
|
|
305
|
+
click.echo(f"Restoring from {backup_path}...")
|
|
306
|
+
|
|
307
|
+
db = GrafeoDB.open(backup_path)
|
|
308
|
+
db.save(target_path)
|
|
309
|
+
|
|
310
|
+
if not quiet:
|
|
311
|
+
click.secho(f"Database restored to {target_path}", fg="green")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def main() -> None:
|
|
315
|
+
"""Entry point for the CLI."""
|
|
316
|
+
cli(obj={})
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
if __name__ == "__main__":
|
|
320
|
+
main()
|
|
Binary file
|
grafeo/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grafeo
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Intended Audience :: Science/Research
|
|
7
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Classifier: Topic :: Database
|
|
15
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Dist: solvor>=0.6.1
|
|
19
|
+
Requires-Dist: click>=8.3.1 ; extra == 'cli'
|
|
20
|
+
Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-asyncio>=1.3.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: ty>=0.0.14 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.14.14 ; extra == 'dev'
|
|
24
|
+
Provides-Extra: cli
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Summary: A high-performance, embeddable graph database with Python bindings
|
|
27
|
+
Keywords: graph,database,gql,knowledge-graph,embedded
|
|
28
|
+
Author: S.T. Grond
|
|
29
|
+
License: Apache-2.0
|
|
30
|
+
Requires-Python: >=3.12
|
|
31
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
32
|
+
Project-URL: Documentation, https://grafeo.dev/user-guide/
|
|
33
|
+
Project-URL: Homepage, https://grafeo.dev
|
|
34
|
+
Project-URL: Issues, https://github.com/GrafeoDB/grafeo/issues
|
|
35
|
+
Project-URL: Repository, https://github.com/GrafeoDB/grafeo
|
|
36
|
+
|
|
37
|
+
# grafeo
|
|
38
|
+
|
|
39
|
+
Python bindings for Grafeo, a pure-Rust, high-performance, embeddable graph database.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv add grafeo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import grafeo
|
|
51
|
+
|
|
52
|
+
# Create an in-memory database
|
|
53
|
+
db = grafeo.GrafeoDB()
|
|
54
|
+
|
|
55
|
+
# Create nodes using GQL
|
|
56
|
+
db.execute("INSERT (:Person {name: 'Alice', age: 30})")
|
|
57
|
+
db.execute("INSERT (:Person {name: 'Bob', age: 25})")
|
|
58
|
+
|
|
59
|
+
# Query the graph
|
|
60
|
+
result = db.execute("MATCH (p:Person) RETURN p.name, p.age")
|
|
61
|
+
for row in result:
|
|
62
|
+
print(row)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
Apache-2.0
|
|
68
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
grafeo\__init__.py,sha256=HjIOQVzybwDT4MRuQB-NuJ3Pwwn1LOvLJCaIl3V6iVg,698
|
|
2
|
+
grafeo\cli.py,sha256=_x3wgb8AuQl8IJ89HGApMbK7eZ4dvstf1KWt_4BIW4I,10256
|
|
3
|
+
grafeo\grafeo.cp312-win32.pyd,sha256=38kRJZBbWaemwgzIjoHktSvuPZGtIk3_OSSln0DdeiU,4622336
|
|
4
|
+
grafeo\py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
grafeo-0.2.4.dist-info\METADATA,sha256=aIUEtTWKAVhX0E1DkyiTSsugMHo7EgN_e01WvUCvQNM,2056
|
|
6
|
+
grafeo-0.2.4.dist-info\WHEEL,sha256=akdtKPhgZFKWiFB2PSXRQPXzLs0y-XAkg8eqWX78MDg,93
|
|
7
|
+
grafeo-0.2.4.dist-info\entry_points.txt,sha256=kNAJ9wDtkgCY-nfE5IXQaf-bJi1kKRplXKdZr6xavDs,41
|
|
8
|
+
grafeo-0.2.4.dist-info\RECORD,,
|