cypherast 0.1.0__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.
- cypherast-0.1.0/.agents/rules/architecture.md +29 -0
- cypherast-0.1.0/.agents/rules/graph-native-naming.md +32 -0
- cypherast-0.1.0/.agents/rules/testing.md +22 -0
- cypherast-0.1.0/.github/copilot-instructions.md +74 -0
- cypherast-0.1.0/.github/instructions/python.instructions.md +12 -0
- cypherast-0.1.0/.github/instructions/review.instructions.md +24 -0
- cypherast-0.1.0/.github/workflows/ci.yml +87 -0
- cypherast-0.1.0/.github/workflows/release.yml +64 -0
- cypherast-0.1.0/.gitignore +45 -0
- cypherast-0.1.0/.python-version +1 -0
- cypherast-0.1.0/AGENTS.md +102 -0
- cypherast-0.1.0/CONTRIBUTING.md +46 -0
- cypherast-0.1.0/HOWTOAI.md +60 -0
- cypherast-0.1.0/LICENSE +21 -0
- cypherast-0.1.0/Makefile +115 -0
- cypherast-0.1.0/PKG-INFO +230 -0
- cypherast-0.1.0/README.md +201 -0
- cypherast-0.1.0/cypherast/__init__.py +214 -0
- cypherast-0.1.0/cypherast/ast.py +604 -0
- cypherast-0.1.0/cypherast/cli.py +62 -0
- cypherast-0.1.0/cypherast/dialects/__init__.py +23 -0
- cypherast-0.1.0/cypherast/dialects/capabilities.py +35 -0
- cypherast-0.1.0/cypherast/dialects/constraints.py +425 -0
- cypherast-0.1.0/cypherast/dialects/dialect.py +142 -0
- cypherast-0.1.0/cypherast/dialects/memgraph.py +34 -0
- cypherast-0.1.0/cypherast/dialects/neo4j.py +30 -0
- cypherast-0.1.0/cypherast/dialects/opencypher.py +25 -0
- cypherast-0.1.0/cypherast/dialects/puppygraph.py +54 -0
- cypherast-0.1.0/cypherast/errors.py +158 -0
- cypherast-0.1.0/cypherast/executor/__init__.py +6 -0
- cypherast-0.1.0/cypherast/executor/engine.py +680 -0
- cypherast-0.1.0/cypherast/executor/env.py +308 -0
- cypherast-0.1.0/cypherast/executor/graph.py +112 -0
- cypherast-0.1.0/cypherast/lexer.py +467 -0
- cypherast-0.1.0/cypherast/lineage.py +186 -0
- cypherast-0.1.0/cypherast/optimizer/__init__.py +67 -0
- cypherast-0.1.0/cypherast/optimizer/catalog.py +96 -0
- cypherast-0.1.0/cypherast/optimizer/engine.py +126 -0
- cypherast-0.1.0/cypherast/parser.py +880 -0
- cypherast-0.1.0/cypherast/planner/__init__.py +30 -0
- cypherast-0.1.0/cypherast/planner/cache.py +32 -0
- cypherast-0.1.0/cypherast/planner/cost.py +36 -0
- cypherast-0.1.0/cypherast/planner/enumeration.py +122 -0
- cypherast-0.1.0/cypherast/planner/physical_ops.py +162 -0
- cypherast-0.1.0/cypherast/planner/plan.py +162 -0
- cypherast-0.1.0/cypherast/py.typed +0 -0
- cypherast-0.1.0/cypherast/renderer.py +431 -0
- cypherast-0.1.0/cypherast/rewriter/__init__.py +62 -0
- cypherast-0.1.0/cypherast/rewriter/annotate_types.py +39 -0
- cypherast-0.1.0/cypherast/rewriter/canonicalize_patterns.py +21 -0
- cypherast-0.1.0/cypherast/rewriter/merge_match_chains.py +63 -0
- cypherast-0.1.0/cypherast/rewriter/pushdown_predicates.py +77 -0
- cypherast-0.1.0/cypherast/rewriter/qualify.py +40 -0
- cypherast-0.1.0/cypherast/rewriter/simplify.py +84 -0
- cypherast-0.1.0/cypherast/schema.py +105 -0
- cypherast-0.1.0/cypherast/scope.py +113 -0
- cypherast-0.1.0/docs/api.md +174 -0
- cypherast-0.1.0/pyproject.toml +86 -0
- cypherast-0.1.0/tests/__init__.py +0 -0
- cypherast-0.1.0/tests/tck/__init__.py +0 -0
- cypherast-0.1.0/tests/tck/features/CreateAcceptance.feature +53 -0
- cypherast-0.1.0/tests/tck/features/ExpressionAcceptance.feature +70 -0
- cypherast-0.1.0/tests/tck/features/MatchAcceptance.feature +44 -0
- cypherast-0.1.0/tests/tck/features/ReturnAcceptance.feature +51 -0
- cypherast-0.1.0/tests/tck/runner.py +161 -0
- cypherast-0.1.0/tests/tck/test_tck_scoreboard.py +24 -0
- cypherast-0.1.0/tests/tck/test_tck_smoke.py +19 -0
- cypherast-0.1.0/tests/test_executor.py +59 -0
- cypherast-0.1.0/tests/test_hardening.py +82 -0
- cypherast-0.1.0/tests/test_lexer.py +32 -0
- cypherast-0.1.0/tests/test_optimizer_rules.py +90 -0
- cypherast-0.1.0/tests/test_parse.py +62 -0
- cypherast-0.1.0/tests/test_planner_lineage.py +24 -0
- cypherast-0.1.0/tests/test_puppygraph_dialect.py +117 -0
- cypherast-0.1.0/tests/test_rewriter.py +36 -0
- cypherast-0.1.0/uv.lock +354 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Pipeline layers and module boundaries for cypherast
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Architecture
|
|
7
|
+
|
|
8
|
+
Pipeline order (do not skip layers or import backwards):
|
|
9
|
+
|
|
10
|
+
1. **Lexer / parser** (`lexer.py`, `parser.py`) → AST (`ast.py`)
|
|
11
|
+
2. **Dialect** (`dialects/`) — parse/render hooks, `capabilities`, constraint rewrites
|
|
12
|
+
3. **Rewriter passes** (`rewriter/`) — pure AST transforms
|
|
13
|
+
4. **Optimizer** (`optimizer/`) — named `Rule` / `RuleSet`, catalogs, `only`/`disable`
|
|
14
|
+
5. **Planner** (`planner/`) — explain / cost / physical ops
|
|
15
|
+
6. **Executor** (`executor/`) — in-memory `Graph` + engine
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
- Public surface stays in `cypherast/__init__.py` (+ CLI). New user-facing entry points go there.
|
|
20
|
+
- Dialect-specific engine limits belong in `DialectCapabilities` + `constraints.py` / `constraint_rules`, not hard-coded label/rel names from one customer graph.
|
|
21
|
+
- Rewrite implementations live in `rewriter/`; registration/order/names live in `optimizer/catalog.py`.
|
|
22
|
+
- Rendering goes through `Renderer` / `AstNode.cypher(...)` — keep dialect emit differences in dialect renderer subclasses when needed.
|
|
23
|
+
- Avoid circular imports: `rewriter/__init__.py` must not import `optimizer` at module top level (optimizer catalog imports rewriter pass modules).
|
|
24
|
+
|
|
25
|
+
## Anti-patterns
|
|
26
|
+
|
|
27
|
+
- SQL vocabulary or SQL-toolchain brand names in APIs/docs
|
|
28
|
+
- Putting PuppyGraph (or any engine) hacks into shared openCypher paths without a capability flag
|
|
29
|
+
- Enabling `merge_match_chains` by default
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Keep cypherast graph-native — no SQL-clone framing or foreign brand names
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Graph-native naming
|
|
7
|
+
|
|
8
|
+
cypherast is a **Cypher/GQL** library. Do not frame it as a SQL toolchain clone in code, comments, docs, tests, READMEs, commit messages, or user-facing copy. Do not cite third-party SQL library brand names.
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Describe features in graph/Cypher terms: named rewrite rules, dialects, `only`/`disable`, capability constraints.
|
|
13
|
+
- Use graph-native vocabulary (MATCH, pattern, binding, hop).
|
|
14
|
+
|
|
15
|
+
## Don't
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
# BAD
|
|
19
|
+
"SQL-style optimizer like <sql library>"
|
|
20
|
+
"transpile_sql" / AstNode.sql()
|
|
21
|
+
customer- or employer-specific product codenames in public docs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
# GOOD
|
|
26
|
+
"named rule filters"
|
|
27
|
+
"enable/disable by rule name"
|
|
28
|
+
AstNode.cypher()
|
|
29
|
+
generic engine capability limits
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Keep the idea; drop foreign brand names and internal org jargon from the public repo.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Pytest conventions for cypherast
|
|
3
|
+
globs: tests/**/*.py
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
- Framework: **pytest** (`tests/`, `testpaths` in `pyproject.toml`).
|
|
10
|
+
- Name files `tests/test_*.py`. Prefer focused cases over giant fixtures.
|
|
11
|
+
- Public API tests go through `import cypherast` (`parse_one`, `optimize`, `translate`, `validate`).
|
|
12
|
+
- Dialect capability tests: `tests/test_puppygraph_dialect.py`.
|
|
13
|
+
- Named rule filters: `tests/test_optimizer_rules.py`.
|
|
14
|
+
- Parser/hardening: `tests/test_parse.py`, `tests/test_hardening.py`.
|
|
15
|
+
- TCK parse-rate: `tests/tck/` via `make test-tck` — do not treat sample `.feature` files as a full openCypher TCK.
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
- Assert on rendered Cypher (`.cypher(...)`) or structured AST — not private rewriter helpers unless unit-testing that helper.
|
|
20
|
+
- When testing `only`/`disable`/`constraint_disable`, name the rule exactly as in `RULES.names` / `constraint_rule_set().names`.
|
|
21
|
+
- After dialect constraint changes, keep `make test-puppy` green.
|
|
22
|
+
- No network / live graph engines in unit tests — use in-memory `Graph` or AST-only checks.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# GitHub Copilot — repository-wide instructions (chat, coding agent, code review)
|
|
2
|
+
|
|
3
|
+
## What this repo is
|
|
4
|
+
|
|
5
|
+
**cypherast** is a Cypher/GQL library (lexer → parser → AST → named rewrite rules → planner → in-memory executor).
|
|
6
|
+
Python 3.13+, zero runtime dependencies, MIT. Public API lives in `cypherast/__init__.py`.
|
|
7
|
+
|
|
8
|
+
This is a **graph query** toolchain. Use Cypher/GQL vocabulary (MATCH, pattern, binding, hop, dialect). Do not frame APIs or reviews in SQL terms.
|
|
9
|
+
|
|
10
|
+
For deeper agent context see root `AGENTS.md`. Prefer that file over inventing architecture.
|
|
11
|
+
|
|
12
|
+
## When reviewing a PR
|
|
13
|
+
|
|
14
|
+
Flag as blockers:
|
|
15
|
+
|
|
16
|
+
1. New runtime dependencies without clear justification
|
|
17
|
+
2. SQL-shaped public names (`sql()`, `transpile_sql`, etc.) or docs that describe the library as a SQL clone
|
|
18
|
+
3. Enabling `merge_match_chains` in default `RULES` (Cartesian risk on some engines)
|
|
19
|
+
4. Hard-coding customer-specific graph labels/rel types into dialect constraint code — use `DialectCapabilities` / generic rules
|
|
20
|
+
5. Qualifying anonymous variables inside pattern predicates (engines reject new bindings there)
|
|
21
|
+
6. Breaking `AstNode.cypher(...)` / `cypher=` public naming
|
|
22
|
+
7. Skipping tests for parser, rewriter, optimizer rule, or dialect capability changes
|
|
23
|
+
8. Lowering coverage gate or silencing CI (`|| true` on mypy/tests) without discussion
|
|
24
|
+
|
|
25
|
+
Nits (non-blocking unless pervasive):
|
|
26
|
+
|
|
27
|
+
- Missing type hints on new public functions
|
|
28
|
+
- Rewriter pass registered in the wrong order vs `optimizer/catalog.py`
|
|
29
|
+
- Docs/README not updated when public API changed
|
|
30
|
+
|
|
31
|
+
## Architecture map (for understanding diffs)
|
|
32
|
+
|
|
33
|
+
| Path | Owns |
|
|
34
|
+
|------|------|
|
|
35
|
+
| `cypherast/lexer.py`, `parser.py`, `ast.py` | Tokenize / parse / IR |
|
|
36
|
+
| `cypherast/dialects/` | Dialect registry, capabilities, constraint rewrites |
|
|
37
|
+
| `cypherast/rewriter/` | Individual AST rewrite passes |
|
|
38
|
+
| `cypherast/optimizer/` | Named `Rule` / `RuleSet`, catalogs, `only`/`disable` |
|
|
39
|
+
| `cypherast/planner/` | Explain / cost / plans |
|
|
40
|
+
| `cypherast/executor/` | In-memory `Graph` + execute |
|
|
41
|
+
| `cypherast/renderer.py` | AST → Cypher text |
|
|
42
|
+
| `tests/` | pytest; `tests/tck/` = sample parse-rate features only |
|
|
43
|
+
|
|
44
|
+
Pipeline order: parse → (optional) optimize/constraints → render / plan / run.
|
|
45
|
+
|
|
46
|
+
## Dialects & optimize
|
|
47
|
+
|
|
48
|
+
- Dialects: `opencypher`, `neo4j`, `memgraph`, `puppygraph`
|
|
49
|
+
- `optimize(..., write=)` applies canonicalizer rules then write-dialect constraints
|
|
50
|
+
- Filters: `only` / `disable` (canonicalizer), `constraint_only` / `constraint_disable`
|
|
51
|
+
- Default rules: `qualify` → `canonicalize_patterns` → `simplify` → `pushdown_predicates` → `annotate_types`
|
|
52
|
+
- Opt-in: `OPTIONAL_RULES` includes `merge_match_chains`
|
|
53
|
+
|
|
54
|
+
## How to verify locally (tell authors)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv sync --group dev
|
|
58
|
+
make check # ruff + mypy + pytest
|
|
59
|
+
make test-cov # coverage XML + term
|
|
60
|
+
make test-puppy # PuppyGraph dialect tests
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
CI workflows:
|
|
64
|
+
|
|
65
|
+
- `.github/workflows/ci.yml` — ruff, mypy (strict), pytest with `--cov-fail-under=60`, uploads `coverage.xml`
|
|
66
|
+
- `.github/workflows/release.yml` — tag `v*` → verify version → build → PyPI (OIDC) → GitHub Release
|
|
67
|
+
|
|
68
|
+
## Coding conventions for suggestions
|
|
69
|
+
|
|
70
|
+
- Match existing style: ruff line-length 100, mypy strict, Python 3.13
|
|
71
|
+
- Prefer small, test-backed changes over large refactors
|
|
72
|
+
- New rewrite logic → implement under `rewriter/`, register name in `optimizer/catalog.py`
|
|
73
|
+
- New engine limits → `DialectCapabilities` + named constraint rule, not one-off string hacks
|
|
74
|
+
- Keep comments/docs free of SQL-toolchain brand names
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.py"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Python review focus (cypherast)
|
|
6
|
+
|
|
7
|
+
- Prefer graph/Cypher naming in identifiers and user-facing strings.
|
|
8
|
+
- Public API changes must update `cypherast/__init__.py` exports and usually `docs/api.md` / `README.md`.
|
|
9
|
+
- Avoid circular imports: do not import `cypherast.optimizer` at the top of `cypherast/rewriter/__init__.py`.
|
|
10
|
+
- Dialect constraint callables should accept `(tree, schema=None)` so `RuleSet.apply` can pass kwargs.
|
|
11
|
+
- Tests: use `cypherast.parse_one` / `optimize` / `translate` / `validate`; assert via `.cypher(...)` when checking emit.
|
|
12
|
+
- Typecheck is strict (`mypy` on `cypherast` package) — new public functions need annotations.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: ".github/workflows/**/*.yml,cypherast/dialects/**/*.py,cypherast/optimizer/**/*.py,cypherast/rewriter/**/*.py"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# High-risk paths — Copilot code review
|
|
6
|
+
|
|
7
|
+
When reviewing changes under dialects, optimizer, rewriter, or CI workflows:
|
|
8
|
+
|
|
9
|
+
## Dialects / constraints
|
|
10
|
+
|
|
11
|
+
- Confirm capability flags match rewrite behavior (`require_limit_on_row_return`, `allow_cartesian_match_paths`, var-length bounds).
|
|
12
|
+
- Do not reintroduce default MATCH-chain merge or `exists()` emit for PuppyGraph unless capabilities explicitly allow it.
|
|
13
|
+
- Unbounded `*` var-length must remain allowed when `allow_unbounded_var_length=True` and `max_var_length_hops is None`.
|
|
14
|
+
|
|
15
|
+
## Optimizer / rewriter
|
|
16
|
+
|
|
17
|
+
- Rule names in `only`/`disable` must match catalog names exactly.
|
|
18
|
+
- Keep `merge_match_chains` in `OPTIONAL_RULES` only unless the PR deliberately changes that policy and updates docs/tests.
|
|
19
|
+
|
|
20
|
+
## CI
|
|
21
|
+
|
|
22
|
+
- Do not drop coverage fail-under without calling it out in the PR.
|
|
23
|
+
- Do not make mypy or pytest non-blocking again.
|
|
24
|
+
- Coverage runs in the CI `test` job and must still produce `coverage.xml` artifact on failure when possible.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# CI — lint, types, tests + coverage
|
|
2
|
+
|
|
3
|
+
name: CI
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
name: Lint (ruff)
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: astral-sh/setup-uv@v5
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
- name: Sync deps
|
|
24
|
+
run: uv sync --group dev
|
|
25
|
+
- name: Ruff
|
|
26
|
+
run: uv run ruff check cypherast tests
|
|
27
|
+
|
|
28
|
+
typecheck:
|
|
29
|
+
name: Typecheck (mypy)
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v5
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
- name: Sync deps
|
|
37
|
+
run: uv sync --group dev
|
|
38
|
+
- name: Mypy (strict)
|
|
39
|
+
run: uv run mypy cypherast
|
|
40
|
+
|
|
41
|
+
test:
|
|
42
|
+
name: Tests + coverage
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
strategy:
|
|
45
|
+
fail-fast: false
|
|
46
|
+
matrix:
|
|
47
|
+
python-version: ["3.13"]
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: astral-sh/setup-uv@v5
|
|
51
|
+
with:
|
|
52
|
+
enable-cache: true
|
|
53
|
+
python-version: ${{ matrix.python-version }}
|
|
54
|
+
- name: Sync deps
|
|
55
|
+
run: uv sync --group dev
|
|
56
|
+
- name: Pytest + coverage
|
|
57
|
+
run: >
|
|
58
|
+
uv run pytest
|
|
59
|
+
--cov=cypherast
|
|
60
|
+
--cov-report=term-missing:skip-covered
|
|
61
|
+
--cov-report=xml:coverage.xml
|
|
62
|
+
--cov-fail-under=60
|
|
63
|
+
- name: PuppyGraph dialect suite
|
|
64
|
+
run: uv run pytest tests/test_puppygraph_dialect.py -q
|
|
65
|
+
- name: Upload coverage.xml
|
|
66
|
+
if: always()
|
|
67
|
+
uses: actions/upload-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: coverage-xml
|
|
70
|
+
path: coverage.xml
|
|
71
|
+
if-no-files-found: warn
|
|
72
|
+
retention-days: 14
|
|
73
|
+
- name: Coverage summary (PR)
|
|
74
|
+
if: github.event_name == 'pull_request'
|
|
75
|
+
run: |
|
|
76
|
+
python - <<'PY'
|
|
77
|
+
import xml.etree.ElementTree as ET
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
p = Path("coverage.xml")
|
|
80
|
+
if not p.exists():
|
|
81
|
+
print("coverage.xml missing")
|
|
82
|
+
raise SystemExit(0)
|
|
83
|
+
root = ET.parse(p).getroot()
|
|
84
|
+
line_rate = float(root.attrib.get("line-rate", "0"))
|
|
85
|
+
pct = round(line_rate * 100, 1)
|
|
86
|
+
print(f"Line coverage: {pct}% (fail-under 60%)")
|
|
87
|
+
PY
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Release — build + publish to PyPI on version tags (v*)
|
|
2
|
+
|
|
3
|
+
name: Release
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write # GitHub Release
|
|
12
|
+
id-token: write # PyPI trusted publishing (OIDC)
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release:
|
|
20
|
+
name: Build, verify, publish
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
environment: pypi
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: astral-sh/setup-uv@v5
|
|
27
|
+
with:
|
|
28
|
+
enable-cache: true
|
|
29
|
+
python-version: "3.13"
|
|
30
|
+
|
|
31
|
+
- name: Sync deps
|
|
32
|
+
run: uv sync --group dev
|
|
33
|
+
|
|
34
|
+
- name: Tag must match pyproject version
|
|
35
|
+
run: |
|
|
36
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
37
|
+
PKG=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
38
|
+
echo "tag=$TAG pyproject=$PKG"
|
|
39
|
+
test "$TAG" = "$PKG" || {
|
|
40
|
+
echo "::error::Tag v$TAG does not match project.version=$PKG in pyproject.toml"
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- name: Quality gate
|
|
45
|
+
run: |
|
|
46
|
+
uv run ruff check cypherast tests
|
|
47
|
+
uv run mypy cypherast
|
|
48
|
+
uv run pytest --cov=cypherast --cov-fail-under=60 -q
|
|
49
|
+
|
|
50
|
+
- name: Build sdist + wheel
|
|
51
|
+
run: uv build
|
|
52
|
+
|
|
53
|
+
- name: Check dist
|
|
54
|
+
run: uvx twine check dist/*
|
|
55
|
+
|
|
56
|
+
- name: Publish to PyPI
|
|
57
|
+
run: uv publish
|
|
58
|
+
|
|
59
|
+
- name: GitHub Release
|
|
60
|
+
uses: softprops/action-gh-release@v2
|
|
61
|
+
with:
|
|
62
|
+
files: dist/*
|
|
63
|
+
generate_release_notes: true
|
|
64
|
+
fail_on_unmatched_files: true
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python / tooling
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.eggs/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
coverage.xml
|
|
15
|
+
*.cover
|
|
16
|
+
.hypothesis/
|
|
17
|
+
.tox/
|
|
18
|
+
.nox/
|
|
19
|
+
|
|
20
|
+
# Secrets / credentials — never commit
|
|
21
|
+
.env
|
|
22
|
+
.env.*
|
|
23
|
+
!.env.example
|
|
24
|
+
*.pem
|
|
25
|
+
*.key
|
|
26
|
+
*.p12
|
|
27
|
+
*.pfx
|
|
28
|
+
id_rsa
|
|
29
|
+
id_rsa.*
|
|
30
|
+
id_ed25519
|
|
31
|
+
id_ed25519.*
|
|
32
|
+
*_secret*
|
|
33
|
+
*credentials*
|
|
34
|
+
*.token
|
|
35
|
+
.secrets/
|
|
36
|
+
secrets/
|
|
37
|
+
|
|
38
|
+
# Editor / OS / local tool state (not shared)
|
|
39
|
+
.DS_Store
|
|
40
|
+
.idea/
|
|
41
|
+
.vscode/
|
|
42
|
+
*.swp
|
|
43
|
+
*~
|
|
44
|
+
.claude/
|
|
45
|
+
.cursor/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# cypherast — agent instructions
|
|
2
|
+
|
|
3
|
+
Cypher/GQL library: lexer → parser → AST → named rewrite rules → planner → in-memory executor.
|
|
4
|
+
Zero runtime deps. Python **3.13+**. Package version **0.1.0** (`pyproject.toml`). MIT.
|
|
5
|
+
|
|
6
|
+
Canonical AI instructions live here. Topic rules live under `.agents/rules/` only (no tool-specific stub/rule trees in-repo).
|
|
7
|
+
|
|
8
|
+
## Non-negotiables
|
|
9
|
+
|
|
10
|
+
- Graph-native naming only (MATCH, pattern, binding, hop). No SQL vocabulary in public APIs or docs.
|
|
11
|
+
- Never frame the library as a SQL toolchain clone — see `.agents/rules/graph-native-naming.md`.
|
|
12
|
+
- Prefer `AstNode.cypher(...)` for render; public params use `cypher=`, not SQL-shaped names.
|
|
13
|
+
- Do not add runtime dependencies without an explicit request.
|
|
14
|
+
- Do not invent dialect capability limits — read `DialectCapabilities` / `cypherast/dialects/constraints.py`.
|
|
15
|
+
- Do not hard-code customer-specific graph labels/rel types into dialect code.
|
|
16
|
+
## Public API (`cypherast/__init__.py`)
|
|
17
|
+
|
|
18
|
+
| Function | Role |
|
|
19
|
+
|----------|------|
|
|
20
|
+
| `parse` / `parse_one` | Cypher → AST (`read=` dialect) |
|
|
21
|
+
| `optimize` | Canonicalizer + optional write-dialect constraints (`only` / `disable` / `constraint_*`) |
|
|
22
|
+
| `translate` / `transpile` | Parse `from_` → render `to_`; `optimize=` applies target constraints |
|
|
23
|
+
| `validate` | List capability `ConstraintIssue`s for a dialect |
|
|
24
|
+
| `explain` / `profile` / `run` | Plan / profile / execute on in-memory `Graph` |
|
|
25
|
+
| `lineage` | Binding provenance |
|
|
26
|
+
|
|
27
|
+
CLI entry: `cypherast.cli:main` (`uv run cypherast …`).
|
|
28
|
+
|
|
29
|
+
## Dialects
|
|
30
|
+
|
|
31
|
+
Registered under `cypherast/dialects/`: `opencypher`, `neo4j`, `memgraph`, `puppygraph`.
|
|
32
|
+
`Dialect.optimize` runs `cypherast.optimizer` `RULES` then `constraint_rules(capabilities)`.
|
|
33
|
+
PuppyGraph caps are generic engine limits (LIMIT, no Cartesian multi-path MATCH, etc.) — keep dialect code free of domain-specific label/rel names.
|
|
34
|
+
|
|
35
|
+
## Optimizer rules
|
|
36
|
+
|
|
37
|
+
- Default `RULES`: `qualify` → `canonicalize_patterns` → `simplify` → `pushdown_predicates` → `annotate_types`
|
|
38
|
+
- Opt-in: `OPTIONAL_RULES` (`merge_match_chains`) — not default (Cartesian risk on some engines)
|
|
39
|
+
- Constraints built by `constraint_rules(caps)` — e.g. `ensure_row_limit`, `split_multi_path_match`
|
|
40
|
+
|
|
41
|
+
## Dev commands (verified in `Makefile` / `pyproject.toml`)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv sync --group dev
|
|
45
|
+
make check # ruff + mypy + pytest
|
|
46
|
+
make test
|
|
47
|
+
make test-puppy
|
|
48
|
+
make optimize Q="MATCH (n:Person) RETURN n.name" WRITE=puppygraph
|
|
49
|
+
make optimize Q="..." DISABLE=qualify CONSTRAINT_DISABLE=ensure_row_limit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
CI:
|
|
53
|
+
|
|
54
|
+
- `.github/workflows/ci.yml` — ruff, mypy (strict), pytest + coverage (fail-under 60%, uploads `coverage.xml`)
|
|
55
|
+
- `.github/workflows/release.yml` — on `v*` tags: verify tag==version → check → `uv build` → `uv publish` (OIDC) → GitHub Release
|
|
56
|
+
|
|
57
|
+
Copilot: `.github/copilot-instructions.md` (+ `.github/instructions/*.instructions.md`).
|
|
58
|
+
|
|
59
|
+
## Project Structure
|
|
60
|
+
|
|
61
|
+
Single Python package (uv + hatchling):
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
cypherast/
|
|
65
|
+
├── AGENTS.md # this file (canonical AI instructions)
|
|
66
|
+
├── HOWTOAI.md
|
|
67
|
+
├── CONTRIBUTING.md
|
|
68
|
+
├── README.md
|
|
69
|
+
├── Makefile
|
|
70
|
+
├── pyproject.toml
|
|
71
|
+
├── LICENSE
|
|
72
|
+
├── .agents/rules/ # agent topic rules (canonical)
|
|
73
|
+
│ ├── graph-native-naming.md
|
|
74
|
+
│ ├── architecture.md
|
|
75
|
+
│ └── testing.md
|
|
76
|
+
├── .github/
|
|
77
|
+
│ ├── copilot-instructions.md
|
|
78
|
+
│ ├── instructions/ # path-scoped Copilot review rules
|
|
79
|
+
│ └── workflows/
|
|
80
|
+
│ ├── ci.yml # lint + mypy + pytest/coverage
|
|
81
|
+
│ └── release.yml # tag v* → PyPI + GitHub Release
|
|
82
|
+
├── docs/api.md
|
|
83
|
+
├── cypherast/
|
|
84
|
+
│ ├── __init__.py # public API
|
|
85
|
+
│ ├── ast.py
|
|
86
|
+
│ ├── lexer.py
|
|
87
|
+
│ ├── parser.py
|
|
88
|
+
│ ├── renderer.py
|
|
89
|
+
│ ├── cli.py
|
|
90
|
+
│ ├── errors.py
|
|
91
|
+
│ ├── schema.py
|
|
92
|
+
│ ├── scope.py
|
|
93
|
+
│ ├── lineage.py
|
|
94
|
+
│ ├── dialects/ # Dialect + capabilities + constraints
|
|
95
|
+
│ ├── rewriter/ # rewrite pass implementations
|
|
96
|
+
│ ├── optimizer/ # Rule / RuleSet / RULES catalog
|
|
97
|
+
│ ├── planner/
|
|
98
|
+
│ └── executor/ # in-memory Graph + engine
|
|
99
|
+
└── tests/
|
|
100
|
+
├── test_*.py
|
|
101
|
+
└── tck/ # sample .feature parse-rate suite
|
|
102
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Contributing to cypherast
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync --group dev
|
|
7
|
+
make check
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Requires Python 3.13+ (`requires-python` in `pyproject.toml`).
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Keep changes scoped — match existing module boundaries (`dialects`, `rewriter`, `optimizer`, `planner`, `executor`).
|
|
15
|
+
2. Add or update tests under `tests/` for behavior changes.
|
|
16
|
+
3. Run `make check` (ruff, mypy, pytest) before opening a PR.
|
|
17
|
+
4. Prefer graph-native naming; see `AGENTS.md` and `.agents/rules/`.
|
|
18
|
+
|
|
19
|
+
## Code style
|
|
20
|
+
|
|
21
|
+
- Ruff: line length 100, target `py313` (`pyproject.toml`).
|
|
22
|
+
- Mypy: `strict = true` on the `cypherast` package.
|
|
23
|
+
- Zero runtime dependencies unless explicitly agreed.
|
|
24
|
+
|
|
25
|
+
## Tests
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
make test
|
|
29
|
+
make test-cov
|
|
30
|
+
make test-puppy
|
|
31
|
+
make test-tck
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
CI runs ruff, mypy (strict), and pytest with coverage (fail-under 60%) in one workflow.
|
|
35
|
+
|
|
36
|
+
## Release / PyPI
|
|
37
|
+
|
|
38
|
+
1. Bump `version` in `pyproject.toml`.
|
|
39
|
+
2. Tag `vX.Y.Z` matching that version and push the tag.
|
|
40
|
+
3. `.github/workflows/release.yml` builds, publishes via PyPI trusted publishing, opens a GitHub Release.
|
|
41
|
+
|
|
42
|
+
See README **Publishing to PyPI** for one-time publisher setup.
|
|
43
|
+
|
|
44
|
+
## Docs
|
|
45
|
+
|
|
46
|
+
Update `README.md` / `docs/api.md` when the public API changes. Keep AI instructions in `AGENTS.md` (not duplicated into tool stubs).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# How to Use AI with cypherast
|
|
2
|
+
|
|
3
|
+
Practical guide for contributing with AI coding assistants.
|
|
4
|
+
|
|
5
|
+
## Core principles
|
|
6
|
+
|
|
7
|
+
- Human owns the PR. Understand every line before merge.
|
|
8
|
+
- Match existing graph-native style: Cypher terms, zero runtime deps, typed Python 3.13.
|
|
9
|
+
- Read `AGENTS.md` and `.agents/rules/` before large changes.
|
|
10
|
+
- Never invent dialect limits — open `cypherast/dialects/capabilities.py` and `constraints.py`.
|
|
11
|
+
|
|
12
|
+
## Best workflow
|
|
13
|
+
|
|
14
|
+
1. Sync env: `uv sync --group dev`
|
|
15
|
+
2. Reproduce with a minimal Cypher string via `parse_one` / `optimize` / `translate`
|
|
16
|
+
3. Prefer TDD for parser/rewriter/dialect behavior (`tests/test_*.py`)
|
|
17
|
+
4. Run `make check` (ruff + mypy + pytest) before claiming done
|
|
18
|
+
5. For PuppyGraph emit path: also `make test-puppy` and `make validate Q="..."`
|
|
19
|
+
|
|
20
|
+
### Useful Make targets
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
make help
|
|
24
|
+
make optimize Q="MATCH (n:Person) WHERE n.status = 'ACTIVE' RETURN n.name"
|
|
25
|
+
make optimize Q="..." WRITE=puppygraph CONSTRAINT_DISABLE=ensure_row_limit
|
|
26
|
+
make translate Q="..." FROM=opencypher TO=puppygraph OPT=1
|
|
27
|
+
make validate Q="MATCH (n) RETURN n" DIALECT=puppygraph
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Named optimizer rules
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import cypherast
|
|
34
|
+
from cypherast.optimizer import RULES, OPTIONAL_RULES
|
|
35
|
+
|
|
36
|
+
cypherast.optimize(q, disable=["qualify"])
|
|
37
|
+
cypherast.optimize(q, write="puppygraph", constraint_disable=["ensure_row_limit"])
|
|
38
|
+
cypherast.optimize(q, rules=RULES + OPTIONAL_RULES) # opt-in merge_match_chains
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What to watch
|
|
42
|
+
|
|
43
|
+
- Do not merge MATCH chains into comma paths by default — PuppyGraph rejects Cartesian multi-path MATCH.
|
|
44
|
+
- Pattern predicates must not introduce new bindings; do not qualify anon vars inside them.
|
|
45
|
+
- `AstNode.cypher(dialect=...)` for render checks after optimize/translate.
|
|
46
|
+
- Keep `merge_match_chains` out of default `RULES` unless explicitly opting in.
|
|
47
|
+
|
|
48
|
+
## Review bar for AI-authored PRs
|
|
49
|
+
|
|
50
|
+
- Tests cover the behavior change (parser, rewriter, dialect, or API).
|
|
51
|
+
- Docs/README/api only updated when the public surface changed.
|
|
52
|
+
- No SQL-clone framing in comments or docs (see `.agents/rules/graph-native-naming.md`).
|
|
53
|
+
- `make check` green locally.
|
|
54
|
+
|
|
55
|
+
## Tools
|
|
56
|
+
|
|
57
|
+
- Canonical instructions: `AGENTS.md`
|
|
58
|
+
- Shared rules: `.agents/rules/` only
|
|
59
|
+
- Copilot (optional): `.github/copilot-instructions.md`
|
|
60
|
+
- Full API samples: `docs/api.md`
|
cypherast-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gaurav Sagar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|