cypherast 0.1.0__py3-none-any.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.
Files changed (44) hide show
  1. cypherast/__init__.py +214 -0
  2. cypherast/ast.py +604 -0
  3. cypherast/cli.py +62 -0
  4. cypherast/dialects/__init__.py +23 -0
  5. cypherast/dialects/capabilities.py +35 -0
  6. cypherast/dialects/constraints.py +425 -0
  7. cypherast/dialects/dialect.py +142 -0
  8. cypherast/dialects/memgraph.py +34 -0
  9. cypherast/dialects/neo4j.py +30 -0
  10. cypherast/dialects/opencypher.py +25 -0
  11. cypherast/dialects/puppygraph.py +54 -0
  12. cypherast/errors.py +158 -0
  13. cypherast/executor/__init__.py +6 -0
  14. cypherast/executor/engine.py +680 -0
  15. cypherast/executor/env.py +308 -0
  16. cypherast/executor/graph.py +112 -0
  17. cypherast/lexer.py +467 -0
  18. cypherast/lineage.py +186 -0
  19. cypherast/optimizer/__init__.py +67 -0
  20. cypherast/optimizer/catalog.py +96 -0
  21. cypherast/optimizer/engine.py +126 -0
  22. cypherast/parser.py +880 -0
  23. cypherast/planner/__init__.py +30 -0
  24. cypherast/planner/cache.py +32 -0
  25. cypherast/planner/cost.py +36 -0
  26. cypherast/planner/enumeration.py +122 -0
  27. cypherast/planner/physical_ops.py +162 -0
  28. cypherast/planner/plan.py +162 -0
  29. cypherast/py.typed +0 -0
  30. cypherast/renderer.py +431 -0
  31. cypherast/rewriter/__init__.py +62 -0
  32. cypherast/rewriter/annotate_types.py +39 -0
  33. cypherast/rewriter/canonicalize_patterns.py +21 -0
  34. cypherast/rewriter/merge_match_chains.py +63 -0
  35. cypherast/rewriter/pushdown_predicates.py +77 -0
  36. cypherast/rewriter/qualify.py +40 -0
  37. cypherast/rewriter/simplify.py +84 -0
  38. cypherast/schema.py +105 -0
  39. cypherast/scope.py +113 -0
  40. cypherast-0.1.0.dist-info/METADATA +230 -0
  41. cypherast-0.1.0.dist-info/RECORD +44 -0
  42. cypherast-0.1.0.dist-info/WHEEL +4 -0
  43. cypherast-0.1.0.dist-info/entry_points.txt +2 -0
  44. cypherast-0.1.0.dist-info/licenses/LICENSE +21 -0
cypherast/__init__.py ADDED
@@ -0,0 +1,214 @@
1
+ """cypherast — Cypher/GQL transpiler, rewriter, planner, executor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from cypherast import ast
6
+ from cypherast.dialects import dialect_names, get_dialect
7
+ from cypherast.errors import (
8
+ CompatibilityError,
9
+ CypherastError,
10
+ ExecuteError,
11
+ OptimizeError,
12
+ ParseError,
13
+ PlanError,
14
+ SchemaError,
15
+ TokenizeError,
16
+ ValidationError,
17
+ )
18
+ from cypherast.lexer import Lexer, Token, TokenKind
19
+ from cypherast.parser import Parser
20
+
21
+ __version__ = "0.1.0"
22
+
23
+
24
+ def parse(cypher: str, read: str | None = None) -> list[ast.AstNode]:
25
+ """Parse one or more Cypher statements. Returns list of top-level AstNodes."""
26
+ dialect = get_dialect(read)
27
+ tree = dialect.parser(cypher).parse()
28
+ return [tree]
29
+
30
+
31
+ def parse_one(cypher: str, read: str | None = None) -> ast.AstNode:
32
+ """Parse a single Cypher statement into an AstNode (usually ``Cypher``)."""
33
+ dialect = get_dialect(read)
34
+ return dialect.parser(cypher).parse()
35
+
36
+
37
+ def optimize(
38
+ cypher: str | ast.AstNode,
39
+ schema: object | None = None,
40
+ read: str | None = None,
41
+ write: str | None = None,
42
+ *,
43
+ strict: bool = False,
44
+ only: list[str] | tuple[str, ...] | None = None,
45
+ disable: list[str] | tuple[str, ...] | None = None,
46
+ constraint_only: list[str] | tuple[str, ...] | None = None,
47
+ constraint_disable: list[str] | tuple[str, ...] | None = None,
48
+ ) -> ast.AstNode:
49
+ """Canonicalize + apply target-dialect capability constraints.
50
+
51
+ ``read`` selects the parser dialect; ``write`` (default ``read``) selects
52
+ capability rewrites (e.g. puppygraph LIMIT / no Cartesian MATCH).
53
+
54
+ Rules are named. Filter with ``only`` / ``disable`` for canonicalizer rules,
55
+ and ``constraint_only`` / ``constraint_disable`` for dialect constraints
56
+ (e.g. ``ensure_row_limit``, ``split_multi_path_match``).
57
+ """
58
+ from cypherast.dialects.dialect import get_dialect_cls
59
+
60
+ tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=read)
61
+ target = get_dialect_cls(write or read)
62
+ return target.optimize(
63
+ tree,
64
+ schema=schema,
65
+ strict=strict,
66
+ only=only,
67
+ disable=disable,
68
+ constraint_only=constraint_only,
69
+ constraint_disable=constraint_disable,
70
+ )
71
+
72
+
73
+ def translate(
74
+ cypher: str,
75
+ *,
76
+ from_: str | None = None,
77
+ to_: str | None = None,
78
+ pretty: bool = False,
79
+ optimize: bool = False,
80
+ strict: bool = False,
81
+ only: list[str] | tuple[str, ...] | None = None,
82
+ disable: list[str] | tuple[str, ...] | None = None,
83
+ constraint_only: list[str] | tuple[str, ...] | None = None,
84
+ constraint_disable: list[str] | tuple[str, ...] | None = None,
85
+ ) -> str:
86
+ """Parse with ``from_`` dialect and render with ``to_`` dialect (transpile).
87
+
88
+ When ``optimize=True``, runs shared rewriter + target capability constraints
89
+ before render (useful for puppygraph emit). Rule filters pass through.
90
+ """
91
+ from cypherast.dialects.constraints import raise_if_invalid
92
+ from cypherast.dialects.dialect import get_dialect_cls
93
+
94
+ tree = parse_one(cypher, read=from_)
95
+ target_cls = get_dialect_cls(to_ or from_)
96
+ if optimize:
97
+ tree = target_cls.optimize(
98
+ tree,
99
+ strict=strict,
100
+ only=only,
101
+ disable=disable,
102
+ constraint_only=constraint_only,
103
+ constraint_disable=constraint_disable,
104
+ )
105
+ else:
106
+ tree = target_cls.apply_constraints(
107
+ tree, only=constraint_only, disable=constraint_disable
108
+ )
109
+ if strict:
110
+ raise_if_invalid(tree, target_cls.capabilities)
111
+ return target_cls.renderer().generate(tree, pretty=pretty)
112
+
113
+
114
+ # Alias for translate
115
+ transpile = translate
116
+
117
+
118
+ def validate(
119
+ cypher: str | ast.AstNode,
120
+ *,
121
+ read: str | None = None,
122
+ dialect: str | None = None,
123
+ ) -> list[object]:
124
+ """Return capability ``ConstraintIssue`` list for ``dialect`` (default ``read``)."""
125
+ from cypherast.dialects.dialect import get_dialect_cls
126
+
127
+ tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=read)
128
+ return list(get_dialect_cls(dialect or read).validate(tree))
129
+
130
+
131
+
132
+ def explain(cypher: str, schema: object | None = None, read: str | None = None) -> str:
133
+ """Return textual EXPLAIN plan."""
134
+ from cypherast.planner import explain as _explain
135
+
136
+ tree = parse_one(cypher, read=read)
137
+ return _explain(tree, schema=schema)
138
+
139
+
140
+ def profile(
141
+ cypher: str,
142
+ *,
143
+ schema: object | None = None,
144
+ graph: object | None = None,
145
+ read: str | None = None,
146
+ ) -> str:
147
+ """Run with profiling and return plan + row counts."""
148
+ from cypherast.planner import profile as _profile
149
+
150
+ tree = parse_one(cypher, read=read)
151
+ return _profile(tree, schema=schema, graph=graph)
152
+
153
+
154
+ def run(
155
+ cypher: str,
156
+ *,
157
+ graph: object | None = None,
158
+ schema: object | None = None,
159
+ read: str | None = None,
160
+ ) -> object:
161
+ """Execute Cypher against an in-memory Graph."""
162
+ from cypherast.executor import execute
163
+ from cypherast.executor.graph import Graph
164
+
165
+ tree = parse_one(cypher, read=read)
166
+ g = graph if isinstance(graph, Graph) or graph is None else None
167
+ if graph is not None and not isinstance(graph, Graph):
168
+ raise TypeError("graph must be a cypherast.executor.Graph")
169
+ return execute(tree, graph=g, schema=schema)
170
+
171
+
172
+ def lineage(
173
+ cypher: str | ast.AstNode,
174
+ binding: str | None = None,
175
+ *,
176
+ schema: object | None = None,
177
+ from_: str | None = None,
178
+ ) -> object:
179
+ """Binding-level provenance graph for a Cypher query."""
180
+ from cypherast.lineage import lineage as _lineage
181
+
182
+ tree = cypher if isinstance(cypher, ast.AstNode) else parse_one(cypher, read=from_)
183
+ return _lineage(tree, binding=binding, schema=schema)
184
+
185
+
186
+ __all__ = [
187
+ "CompatibilityError",
188
+ "CypherastError",
189
+ "ExecuteError",
190
+ "Lexer",
191
+ "OptimizeError",
192
+ "ParseError",
193
+ "Parser",
194
+ "PlanError",
195
+ "SchemaError",
196
+ "Token",
197
+ "TokenKind",
198
+ "TokenizeError",
199
+ "ValidationError",
200
+ "ast",
201
+ "dialect_names",
202
+ "explain",
203
+ "get_dialect",
204
+ "lineage",
205
+ "optimize",
206
+ "parse",
207
+ "parse_one",
208
+ "profile",
209
+ "run",
210
+ "transpile",
211
+ "translate",
212
+ "validate",
213
+ "__version__",
214
+ ]