crochet-migration 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.
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: crochet-migration
3
+ Version: 0.1.0
4
+ Summary: Versioned schema & data migrations for neomodel Neo4j graphs
5
+ Project-URL: Homepage, https://github.com/keshavd/crochet
6
+ Project-URL: Repository, https://github.com/keshavd/crochet
7
+ Project-URL: Issues, https://github.com/keshavd/crochet/issues
8
+ Author-email: Keshav Dial <y.inuyasha@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: database,graph,migrations,neo4j,neomodel,schema
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Database
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: click>=8.0
24
+ Requires-Dist: neomodel>=5.0
25
+ Requires-Dist: toml>=0.10
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy; extra == 'dev'
28
+ Requires-Dist: pytest-cov; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: ruff; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Crochet
34
+
35
+ Versioned schema & data migrations for [neomodel](https://github.com/neo4j-contrib/neomodel) Neo4j graphs.
36
+
37
+ Crochet is a Git-backed, migration-driven framework that makes neomodel-defined
38
+ Neo4j graphs evolvable, auditable, and rollback-safe without relying on database
39
+ introspection.
40
+
41
+ ## Problem It Solves
42
+
43
+ - neomodel has no native schema diff or migration system
44
+ - Neo4j is schemaless, so schema drift is silent
45
+ - Data loading and schema evolution are often intertwined but unmanaged
46
+ - Rollbacks are usually impossible or unsafe
47
+ - Git history and database state frequently diverge
48
+
49
+ Crochet enforces alignment between neomodel code, data ingests, and the live
50
+ graph.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install crochet
56
+ ```
57
+
58
+ For development:
59
+
60
+ ```bash
61
+ pip install -e ".[dev]"
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ### 1. Initialize a project
67
+
68
+ ```bash
69
+ crochet new-project --name my-graph
70
+ ```
71
+
72
+ This creates:
73
+
74
+ ```
75
+ my-graph/
76
+ crochet.toml # project config
77
+ models/ # neomodel definitions
78
+ migrations/ # migration files
79
+ .crochet/ledger.db # SQLite ledger
80
+ ```
81
+
82
+ ### 2. Create node and relationship models
83
+
84
+ ```bash
85
+ crochet create-node Person
86
+ crochet create-relationship Friendship --rel-type FRIENDS_WITH
87
+ ```
88
+
89
+ Each model gets an immutable `__kgid__` identifier. Models can be renamed or
90
+ moved across files without losing identity, because the `__kgid__` is what
91
+ Crochet tracks — not class names or file paths.
92
+
93
+ ```python
94
+ # models/person.py
95
+ from neomodel import StructuredNode, StringProperty, IntegerProperty
96
+
97
+ class Person(StructuredNode):
98
+ __kgid__ = "person_v1"
99
+ name = StringProperty(required=True, unique_index=True)
100
+ age = IntegerProperty(index=True)
101
+ ```
102
+
103
+ ### 3. Create a migration
104
+
105
+ ```bash
106
+ crochet create-migration "add person node"
107
+ ```
108
+
109
+ Crochet snapshots the current schema IR, diffs it against the previous
110
+ snapshot, and scaffolds a migration file with detected changes as comments:
111
+
112
+ ```python
113
+ # migrations/0001_add_person_node.py
114
+
115
+ revision_id = "0001_add_person_node"
116
+ parent_id = None
117
+ schema_hash = "a1b2c3..."
118
+ rollback_safe = True
119
+
120
+ def upgrade(ctx):
121
+ ctx.add_unique_constraint("Person", "name")
122
+ ctx.add_index("Person", "age")
123
+
124
+ def downgrade(ctx):
125
+ ctx.drop_index("Person", "age")
126
+ ctx.drop_unique_constraint("Person", "name")
127
+ ```
128
+
129
+ ### 4. Apply migrations
130
+
131
+ ```bash
132
+ crochet upgrade # apply all pending
133
+ crochet upgrade --dry-run # preview without executing
134
+ crochet upgrade --target 0001_add_person_node # apply up to a specific revision
135
+ ```
136
+
137
+ ### 5. Revert migrations
138
+
139
+ ```bash
140
+ crochet downgrade # revert the most recent migration
141
+ crochet downgrade --target 0001_add_person_node # revert down to a target
142
+ ```
143
+
144
+ Rollback-unsafe migrations will refuse to downgrade and raise an error.
145
+
146
+ ### 6. Check status and verify
147
+
148
+ ```bash
149
+ crochet status # show applied/pending migrations, head, batches
150
+ crochet verify # check ledger chain, file presence, schema hash consistency
151
+ ```
152
+
153
+ ## Core Concepts
154
+
155
+ ### Intermediate Representation (IR)
156
+
157
+ neomodel files are parsed into an intermediate schema representation. IR
158
+ snapshots can be hashed, serialized, and diffed. No Neo4j connection is
159
+ required for schema comparison.
160
+
161
+ ### Hash-Chained Migrations
162
+
163
+ Migrations are ordered by a parent chain (Alembic-style). Each migration
164
+ records the schema hash at the time it was created, so drift between code and
165
+ migrations is detectable.
166
+
167
+ ### SQLite Ledger
168
+
169
+ A local SQLite database (`.crochet/ledger.db`) is the authoritative record of:
170
+
171
+ - Applied migrations and their order
172
+ - Dataset batches with file checksums and loader versions
173
+ - Schema snapshots for diffing
174
+
175
+ ### Deterministic Data Ingest
176
+
177
+ Data loading is a first-class migration operation. The `MigrationContext`
178
+ provides helpers for batch-tracked ingests:
179
+
180
+ ```python
181
+ def upgrade(ctx):
182
+ batch_id = ctx.begin_batch()
183
+ ctx.create_nodes("Person", [
184
+ {"name": "Alice", "age": 30},
185
+ {"name": "Bob", "age": 25},
186
+ ])
187
+ ```
188
+
189
+ Every node and relationship created through a batch is tagged with
190
+ `_crochet_batch`, enabling delete-by-batch rollback.
191
+
192
+ ### Rollback Semantics
193
+
194
+ Rollbacks are explicitly declared, not assumed:
195
+
196
+ - Append-only ingests support `delete_nodes_by_batch` / `delete_relationships_by_batch`
197
+ - Destructive transforms must set `rollback_safe = False`
198
+ - Unsafe downgrades are prevented by policy
199
+
200
+ ## Migration Context Operations
201
+
202
+ The `MigrationContext` passed to `upgrade()` and `downgrade()` provides:
203
+
204
+ | Operation | Description |
205
+ |-----------|-------------|
206
+ | `add_unique_constraint(label, prop)` | Create a uniqueness constraint |
207
+ | `drop_unique_constraint(label, prop)` | Drop a uniqueness constraint |
208
+ | `add_node_property_existence_constraint(label, prop)` | Create a NOT NULL constraint |
209
+ | `drop_node_property_existence_constraint(label, prop)` | Drop a NOT NULL constraint |
210
+ | `add_index(label, prop)` | Create an index |
211
+ | `drop_index(label, prop)` | Drop an index |
212
+ | `rename_label(old, new)` | Rename a node label |
213
+ | `rename_relationship_type(old, new)` | Rename a relationship type |
214
+ | `add_node_property(label, prop, default)` | Add a property with optional default |
215
+ | `remove_node_property(label, prop)` | Remove a property |
216
+ | `rename_node_property(label, old, new)` | Rename a property |
217
+ | `run_cypher(cypher, params)` | Execute raw Cypher |
218
+ | `begin_batch(batch_id)` | Start a tracked data batch |
219
+ | `create_nodes(label, data)` | Batch-create nodes |
220
+ | `create_relationships(src, tgt, type, data)` | Batch-create relationships |
221
+ | `delete_nodes_by_batch(label, batch_id)` | Delete nodes by batch |
222
+ | `delete_relationships_by_batch(type, batch_id)` | Delete relationships by batch |
223
+
224
+ ## Configuration
225
+
226
+ `crochet.toml`:
227
+
228
+ ```toml
229
+ [project]
230
+ name = "my-graph"
231
+ models_path = "models"
232
+ migrations_path = "migrations"
233
+
234
+ [neo4j]
235
+ uri = "bolt://localhost:7687"
236
+ username = "neo4j"
237
+
238
+ [ledger]
239
+ path = ".crochet/ledger.db"
240
+ ```
241
+
242
+ Neo4j credentials can be overridden with environment variables:
243
+
244
+ - `CROCHET_NEO4J_URI`
245
+ - `CROCHET_NEO4J_USERNAME`
246
+ - `CROCHET_NEO4J_PASSWORD`
247
+
248
+ ## CLI Reference
249
+
250
+ | Command | Description |
251
+ |---------|-------------|
252
+ | `crochet new-project` | Initialize a new Crochet project |
253
+ | `crochet create-node NAME` | Scaffold a StructuredNode model |
254
+ | `crochet create-relationship NAME` | Scaffold a StructuredRel model |
255
+ | `crochet create-migration DESC` | Create a new migration file |
256
+ | `crochet upgrade` | Apply pending migrations |
257
+ | `crochet downgrade` | Revert the most recent migration |
258
+ | `crochet status` | Show migration status |
259
+ | `crochet verify` | Run verification checks |
260
+
261
+ ## Design Principles
262
+
263
+ - **No hidden magic** — all changes are explicit migration files
264
+ - **Code > database state** — neomodel files are the source of truth
265
+ - **Determinism over convenience** — schema IR is hashed and diffed
266
+ - **Rollback is a contract, not a guess** — explicitly declared per migration
267
+ - **Git history and graph state must agree** — ledger + hash chains enforce this
268
+
269
+ ## Development
270
+
271
+ ```bash
272
+ pip install -e ".[dev]"
273
+ pytest
274
+ ```
275
+
276
+ ## License
277
+
278
+ MIT
@@ -0,0 +1,26 @@
1
+ crochet/__init__.py,sha256=1y0jxq3KELfoanb8IVQa5bYbdV-d0NoQVP4Zyjpx5lk,103
2
+ crochet/cli.py,sha256=Ts1tZWpntGaBp_aJ2ZJZZB-ueiaLcA3r6wRgGKgY-Jc,10713
3
+ crochet/config.py,sha256=n6zWkn_p1ilj5s0IRDKFsrPLVdi5wGDWgavPY_rVN04,3611
4
+ crochet/errors.py,sha256=IEzKDJrK2cjAda-SUNLxanPijJ_sdcklAGZ9d71VcgI,2078
5
+ crochet/verify.py,sha256=OmBX0cTS2fQFz5-QVcCyVaPfjC3zBipzYH4gwiLrbRs,4415
6
+ crochet/ingest/__init__.py,sha256=eTwqsyxM21iDDtcrQzsFgzPJftGwKh47ZySKhLX-mgw,163
7
+ crochet/ingest/batch.py,sha256=C0nLIgiYy5zzz36l8Ywsgz8IlK7AmSwaSk3AMv_4Ogw,2014
8
+ crochet/ir/__init__.py,sha256=EIxtO4lQ242aVO7KOWcznnT_oNcQhRK47eBZkhIQACA,525
9
+ crochet/ir/diff.py,sha256=PYPbUX4W86RivpUBh1zda_B29uWsazSu51zjG1QH7Bg,7318
10
+ crochet/ir/hash.py,sha256=adUa5VOd8r3SotKDqXIHqDw88C4tfq54lwxyV1eQ4ZY,1137
11
+ crochet/ir/parser.py,sha256=Khahfvs1i_-kCTxFPrRDMiHuVQjg67IP1x5HRvqA9vg,7880
12
+ crochet/ir/schema.py,sha256=0MTOWGAmtWDVGCSfUEAdHXNM-lriyeT3gCUbsF3OsSc,6160
13
+ crochet/ledger/__init__.py,sha256=xXaClHCWAbo36NfYMo41VftPo3O7kPpIyW-wj4Lwaqw,133
14
+ crochet/ledger/sqlite.py,sha256=yGy4dDh5KPC5-N3HVaK1FpD7GYixoM2M5_IDRaEROvU,9421
15
+ crochet/migrations/__init__.py,sha256=-112mqAx8xTQzu33lA3mmiZAP6v_H0_tPlOpYRazVeE,204
16
+ crochet/migrations/engine.py,sha256=CyqtjWJbFPuIRfgMZ30ng1D7RUJGR0-i8Z1lxq5Z6sc,9563
17
+ crochet/migrations/operations.py,sha256=iv2aFF5w-X5K2y5fqB-w8CuSNN15eEswDHyOJIiamrY,10360
18
+ crochet/migrations/template.py,sha256=1vca5mDkQKt6B4M5mLn3NZErSZAannfkUuf1LPjdU3U,2673
19
+ crochet/scaffold/__init__.py,sha256=ZPyyQ3_s2uwFaw8kdpsYT7q4Br5TdU6KVCJbc0hhslo,236
20
+ crochet/scaffold/node.py,sha256=vXIqyYUj1Ot4qPUIwcwXsFKJsdcIbEh8Y71ASgWH4nY,1266
21
+ crochet/scaffold/relationship.py,sha256=eHKAt0olYcGXAA5TdnIl2DOZpN31GK9y-hJFutF2Tqg,1373
22
+ crochet_migration-0.1.0.dist-info/METADATA,sha256=tGQyC-qLHV0VbIW-u77FU10-EB6_ZXc3hPKa-hW2Tvc,8285
23
+ crochet_migration-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
+ crochet_migration-0.1.0.dist-info/entry_points.txt,sha256=wKTVp7ky8zuaFIMskw4FEVSppYDoKG-6CV1joyCKSC8,45
25
+ crochet_migration-0.1.0.dist-info/licenses/LICENSE,sha256=plFEmT-Ix7lZ5QZvnBsTTETSVDcBhM9sY8lWCxU6llg,1068
26
+ crochet_migration-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ crochet = crochet.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keshav Dial
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.