superseed 0.2.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.
@@ -0,0 +1,11 @@
1
+ .env
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ dist/
10
+ *.egg-info/
11
+ .superseed/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SuperSeed contributors
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.
@@ -0,0 +1,460 @@
1
+ Metadata-Version: 2.4
2
+ Name: superseed
3
+ Version: 0.2.0
4
+ Summary: Declarative Neo4j fixtures for pytest
5
+ Project-URL: Documentation, https://github.com/angelmota/superseed#readme
6
+ Project-URL: Source, https://github.com/angelmota/superseed
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: neo4j>=5
11
+ Requires-Dist: pyyaml>=6
12
+ Requires-Dist: typer>=0.12
13
+ Provides-Extra: dev
14
+ Requires-Dist: mypy>=1.13; extra == 'dev'
15
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
16
+ Requires-Dist: pytest>=8; extra == 'dev'
17
+ Requires-Dist: ruff>=0.8; extra == 'dev'
18
+ Requires-Dist: testcontainers[neo4j]>=4; extra == 'dev'
19
+ Requires-Dist: types-pyyaml>=6; extra == 'dev'
20
+ Provides-Extra: neo4j
21
+ Requires-Dist: neo4j>=5; extra == 'neo4j'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # SuperSeed
25
+
26
+ Declarative Neo4j fixtures for pytest. Seed **real graph data** before each test, run your services against it, then tear it down automatically.
27
+
28
+ > **This is not mocking.** SuperSeed does not patch repositories or stub the driver. It loads YAML scenarios, compiles them to Cypher, `MERGE`s nodes and relationships into Neo4j, and deletes everything tagged with a per-run ID when the test finishes.
29
+
30
+ **Requirements:** Python 3.11+, Neo4j 5+, pytest
31
+
32
+ ---
33
+
34
+ ## Table of contents
35
+
36
+ 1. [How it works](#how-it-works)
37
+ 2. [Quick start — PyPI install](#quick-start--pypi-install)
38
+ 3. [Quick start — clone the monorepo demo](#quick-start--clone-the-monorepo-demo)
39
+ 4. [Writing tests with `@super_seed`](#writing-tests-with-super_seed)
40
+ 5. [`superseed.yaml` reference](#superseedyaml-reference)
41
+ 6. [`*.schema.yaml` reference](#schemayaml-reference)
42
+ 7. [Cleanup model (`testRunId`)](#cleanup-model-testrunid)
43
+ 8. [CLI](#cli)
44
+ 9. [Full walkthrough repo](#full-walkthrough-repo)
45
+ 10. [Contributing](#contributing)
46
+
47
+ ---
48
+
49
+ ## How it works
50
+
51
+ ```mermaid
52
+ sequenceDiagram
53
+ autonumber
54
+ participant Dev as Your test
55
+ participant Plugin as SuperSeed plugin
56
+ participant YAML as superseed.yaml
57
+ participant Compiler as compiler
58
+ participant Neo4j as Neo4j
59
+ participant App as Service / repository
60
+
61
+ Dev->>Plugin: @super_seed("matrix_cast")
62
+ Plugin->>YAML: find config + load scenario
63
+ Plugin->>Compiler: compile_scenario(run_id)
64
+ Compiler-->>Plugin: CypherSeedPlan (MERGE statements)
65
+ Plugin->>Neo4j: seed — MERGE nodes & relationships
66
+ Note over Neo4j: every node tagged with testRunId
67
+ Plugin->>Dev: run test body
68
+ Dev->>App: call business logic
69
+ App->>Neo4j: real driver queries
70
+ Neo4j-->>App: graph results
71
+ App-->>Dev: assertions pass / fail
72
+ Plugin->>Neo4j: cleanup(testRunId)
73
+ Note over Neo4j: DETACH DELETE seeded nodes
74
+ ```
75
+
76
+ ```text
77
+ superseed.yaml scenario
78
+
79
+
80
+ compile to Cypher (MERGE nodes + relationships)
81
+
82
+
83
+ seed Neo4j (tag every node with testRunId)
84
+
85
+
86
+ your pytest runs (real service / repository code)
87
+
88
+
89
+ cleanup (DELETE all nodes with that testRunId)
90
+ ```
91
+
92
+ 1. You decorate a test with `@super_seed("scenario_name")`.
93
+ 2. The pytest plugin finds `superseed.yaml` (searches upward from the test file).
94
+ 3. It compiles the named scenario to parameterized Cypher statements.
95
+ 4. Before the test body runs, it seeds Neo4j and tags created nodes with a unique `testRunId`.
96
+ 5. After the test (pass or fail), it deletes every node with that `testRunId`.
97
+
98
+ Your tests talk to **real** Neo4j through your **real** application code — the same code you run in production.
99
+
100
+ ---
101
+
102
+ ## Quick start — PyPI install
103
+
104
+ Use this path when SuperSeed is a dependency in your own project.
105
+
106
+ ### 1. Install
107
+
108
+ ```bash
109
+ pip install superseed
110
+ # or
111
+ uv add superseed
112
+ ```
113
+
114
+ You also need a running Neo4j instance. Minimal Docker setup:
115
+
116
+ ```bash
117
+ docker run -d \
118
+ --name neo4j \
119
+ -p 7474:7474 -p 7687:7687 \
120
+ -e NEO4J_AUTH=neo4j/your-password \
121
+ neo4j:5
122
+ ```
123
+
124
+ ### 2. Add `superseed.yaml` beside your tests
125
+
126
+ ```yaml
127
+ neo4j:
128
+ uri: ${NEO4J_URI:-bolt://localhost:7687}
129
+ user: neo4j
130
+ password: ${NEO4J_PASSWORD:-your-password}
131
+
132
+ defaults:
133
+ Movie:
134
+ released: 1999
135
+
136
+ scenarios:
137
+ matrix_cast:
138
+ description: "The Matrix with Neo and Trinity"
139
+ parameters:
140
+ movie_title: "The Matrix"
141
+ nodes:
142
+ - label: Movie
143
+ key: {title: "${movie_title}"}
144
+ props: {released: 1999}
145
+ - label: Actor
146
+ key: {name: "Keanu Reeves"}
147
+ - label: Actor
148
+ key: {name: "Carrie-Anne Moss"}
149
+ props: {born: 1967}
150
+ relationships:
151
+ - type: ACTED_IN
152
+ from: {label: Actor, key: {name: "Keanu Reeves"}}
153
+ to: {label: Movie, key: {title: "${movie_title}"}}
154
+ props: {role: "Neo"}
155
+ - type: ACTED_IN
156
+ from: {label: Actor, key: {name: "Carrie-Anne Moss"}}
157
+ to: {label: Movie, key: {title: "${movie_title}"}}
158
+ props: {role: "Trinity"}
159
+ ```
160
+
161
+ Set environment variables for your Neo4j connection:
162
+
163
+ ```bash
164
+ export NEO4J_URI=bolt://localhost:7687
165
+ export NEO4J_PASSWORD=your-password
166
+ ```
167
+
168
+ ### 3. Write a test
169
+
170
+ ```python
171
+ from superseed import super_seed
172
+
173
+ @super_seed("matrix_cast")
174
+ def test_get_cast(movie_service):
175
+ cast = movie_service.get_cast_for_movie("The Matrix")
176
+ assert len(cast) == 2
177
+ ```
178
+
179
+ The plugin loads automatically when pytest discovers SuperSeed (via the `pytest11` entry point). No `pytest_plugins` line required if `superseed` is installed.
180
+
181
+ ### 4. Run
182
+
183
+ ```bash
184
+ pytest tests/ -v
185
+ superseed validate -c tests/superseed.yaml # optional CI check
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Quick start — clone the monorepo demo
191
+
192
+ Use this path to explore the reference app and dogfood the library locally.
193
+
194
+ ```bash
195
+ git clone <repo-url> superseed && cd superseed
196
+ cp .env.example .env
197
+ docker compose up --build -d # Neo4j + movie-api
198
+
199
+ # Package dev setup
200
+ uv sync --extra dev
201
+
202
+ # Run the demo integration tests (Neo4j only — movie-api container optional)
203
+ export NEO4J_URI=bolt://localhost:7687
204
+ export NEO4J_PASSWORD=superseed-dev
205
+ cd examples/movie-api && uv sync --extra dev && uv run pytest -v
206
+ ```
207
+
208
+ | URL | What |
209
+ |-----|------|
210
+ | http://localhost:7474 | Neo4j Browser (`neo4j` / `superseed-dev`) |
211
+ | http://localhost:8000/docs | Movie API (FastAPI demo) |
212
+ | `bolt://localhost:7687` | Host pytest / CLI |
213
+
214
+ After running a seeded test you can inspect the graph in Neo4j Browser or hit the API:
215
+
216
+ ```bash
217
+ uv run pytest -k matrix -v
218
+ curl http://localhost:8000/movies/The%20Matrix/actors
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Writing tests with `@super_seed`
224
+
225
+ ### Basic usage
226
+
227
+ ```python
228
+ from superseed import super_seed
229
+
230
+ @super_seed("matrix_cast")
231
+ def test_get_cast_for_matrix(movie_service):
232
+ cast = movie_service.get_cast_for_movie("The Matrix")
233
+ assert len(cast) == 2
234
+ ```
235
+
236
+ ### Parameter overrides
237
+
238
+ Override scenario parameters per test without duplicating YAML:
239
+
240
+ ```python
241
+ @super_seed("matrix_cast", movie_title="Inception")
242
+ def test_custom_title(movie_service):
243
+ ...
244
+ ```
245
+
246
+ In YAML, reference parameters with lowercase placeholders: `${movie_title}`.
247
+
248
+ ### Config discovery
249
+
250
+ By default the plugin searches upward from the test file for `superseed.yaml`. Override with:
251
+
252
+ ```bash
253
+ pytest --superseed-config path/to/superseed.yaml
254
+ ```
255
+
256
+ Optional schema validation:
257
+
258
+ ```bash
259
+ pytest --superseed-schema path/to/movies.schema.yaml
260
+ ```
261
+
262
+ If omitted, the plugin looks for `movies.schema.yaml` or `superseed.schema.yaml` next to the config file.
263
+
264
+ ### What you should **not** do
265
+
266
+ ```python
267
+ # Before SuperSeed — don't do this anymore
268
+ def test_old_way(neo4j_session, movie_service):
269
+ neo4j_session.run("CREATE (m:Movie ...)") # hand-written seed
270
+ ...
271
+ neo4j_session.run("MATCH (n) DETACH DELETE n") # manual cleanup
272
+ ```
273
+
274
+ SuperSeed replaces inline `CREATE` blocks and manual teardown.
275
+
276
+ ---
277
+
278
+ ## `superseed.yaml` reference
279
+
280
+ Top-level keys:
281
+
282
+ | Key | Required | Description |
283
+ |-----|----------|-------------|
284
+ | `neo4j` | yes | Connection settings (`uri`, `user`, `password`) |
285
+ | `defaults` | no | Default properties per label, merged into every node |
286
+ | `scenarios` | yes | Named scenario definitions |
287
+
288
+ ### Environment substitution
289
+
290
+ Use uppercase env vars in config values:
291
+
292
+ ```yaml
293
+ neo4j:
294
+ uri: ${NEO4J_URI:-bolt://localhost:7687}
295
+ password: ${NEO4J_PASSWORD:-superseed-dev}
296
+ ```
297
+
298
+ Syntax: `${VAR}` or `${VAR:-default}`.
299
+
300
+ ### Scenario fields
301
+
302
+ | Field | Required | Description |
303
+ |-------|----------|-------------|
304
+ | `description` | no | Human-readable summary (shown by `superseed list`) |
305
+ | `linked_repository` | no | Dotted repo method path for future auto-detection, e.g. `MovieRepository.find_actors_for_movie` |
306
+ | `parameters` | no | Default parameter values; overridable via `@super_seed(..., param=value)` |
307
+ | `required_labels` | no | Documentation / scan metadata |
308
+ | `required_relationships` | no | Documentation / scan metadata |
309
+ | `nodes` | yes | Nodes to `MERGE` |
310
+ | `relationships` | no | Relationships to `MERGE` between matched nodes |
311
+
312
+ ### Node entry
313
+
314
+ ```yaml
315
+ - label: Movie
316
+ key: {title: "${movie_title}"} # MERGE identity — must be unique in the graph
317
+ props: {released: 1999} # additional SET properties
318
+ ```
319
+
320
+ - **`key`** — business identity used in `MERGE (n:Label {key...})`
321
+ - **`props`** — extra properties merged after defaults and schema defaults
322
+ - **`${parameter}`** — lowercase placeholders resolved at compile time from `parameters` + decorator overrides
323
+
324
+ ### Relationship entry
325
+
326
+ ```yaml
327
+ - type: ACTED_IN
328
+ from: {label: Actor, key: {name: "Keanu Reeves"}}
329
+ to: {label: Movie, key: {title: "${movie_title}"}}
330
+ props: {role: "Neo"}
331
+ ```
332
+
333
+ The compiler `MATCH`es both endpoints by key, then `MERGE`s the relationship.
334
+
335
+ ---
336
+
337
+ ## `*.schema.yaml` reference
338
+
339
+ Optional label contract validated before compile. Place beside `superseed.yaml` (e.g. `movies.schema.yaml`).
340
+
341
+ ```yaml
342
+ Movie:
343
+ required: [title]
344
+ optional: [released, id]
345
+ defaults:
346
+ released: 1999
347
+
348
+ Actor:
349
+ required: [name]
350
+ optional: [born, id]
351
+ defaults:
352
+ born: 1964
353
+ ```
354
+
355
+ | Key | Description |
356
+ |-----|-------------|
357
+ | `required` | Properties that must appear in `key` + `props` (after defaults) |
358
+ | `optional` | Allowed extra properties |
359
+ | `defaults` | Applied before scenario `props` |
360
+
361
+ Run `superseed validate -c tests/superseed.yaml` to catch schema violations in CI.
362
+
363
+ ---
364
+
365
+ ## Cleanup model (`testRunId`)
366
+
367
+ Every seeded node gets a property:
368
+
369
+ ```text
370
+ testRunId = "<uuid per test run>"
371
+ ```
372
+
373
+ Cleanup query (run automatically after each `@super_seed` test):
374
+
375
+ ```cypher
376
+ MATCH (n {testRunId: $run_id}) DETACH DELETE n
377
+ ```
378
+
379
+ **Why this works:**
380
+
381
+ - Each test run gets a fresh UUID — no collisions between parallel or sequential tests.
382
+ - `DETACH DELETE` removes relationships automatically when nodes are deleted.
383
+ - Tests are isolated: one test's graph never leaks into the next.
384
+ - Failed tests still clean up (teardown runs regardless of pass/fail).
385
+
386
+ Relationships are not tagged; they disappear when their endpoint nodes are deleted.
387
+
388
+ ---
389
+
390
+ ## CLI
391
+
392
+ ```bash
393
+ superseed validate -c path/to/superseed.yaml # parse YAML + schema; exit 1 on error
394
+ superseed list -c path/to/superseed.yaml # print scenario names + descriptions
395
+ superseed scan path/to/repositories # suggest scenario stubs from repo Cypher
396
+ ```
397
+
398
+ ### `superseed scan`
399
+
400
+ Walks `*repository*.py` files, extracts Cypher via AST, and writes:
401
+
402
+ ```text
403
+ .superseed/
404
+ ├── suggested/
405
+ │ ├── find_actors_for_movie.yaml # merge into superseed.yaml
406
+ │ └── ...
407
+ └── repo_index.json # machine-readable index for tooling
408
+ ```
409
+
410
+ Each suggested stub includes `linked_repository`, detected labels, and relationship types. Edit the stub, copy the scenario into `superseed.yaml`, and run `superseed validate`.
411
+
412
+ Example:
413
+
414
+ ```bash
415
+ superseed scan app/repositories -o .superseed
416
+ superseed validate -c tests/superseed.yaml
417
+ ```
418
+
419
+ ---
420
+
421
+ ## Full walkthrough repo
422
+
423
+ The git monorepo includes a complete reference app that dogfoods SuperSeed:
424
+
425
+ ```text
426
+ examples/movie-api/
427
+ ├── app/
428
+ │ ├── repositories/movie_repository.py # real Cypher strings (scan targets these)
429
+ │ ├── services/movie_service.py
430
+ │ └── main.py # FastAPI demo
431
+ └── tests/
432
+ ├── superseed.yaml # scenarios
433
+ ├── movies.schema.yaml # label contract
434
+ └── test_movie_service.py # @super_seed tests — no inline CREATE
435
+ ```
436
+
437
+ See [`examples/movie-api/tests/test_movie_service.py`](examples/movie-api/tests/test_movie_service.py) and [`examples/movie-api/tests/superseed.yaml`](examples/movie-api/tests/superseed.yaml) for a working end-to-end example.
438
+
439
+ ---
440
+
441
+ ## Contributing
442
+
443
+ SuperSeed is developed in the open. Planning docs:
444
+
445
+ - [Future work (v0.3+ auto-detection)](docs/FUTURE.md)
446
+
447
+ Local development:
448
+
449
+ ```bash
450
+ uv sync --extra dev
451
+ uv run pytest tests/ -v
452
+ uv run ruff check src tests
453
+ uv run mypy src
454
+ ```
455
+
456
+ ---
457
+
458
+ ## License
459
+
460
+ MIT — see [LICENSE](LICENSE).