crochet-migration 0.1.0__py3-none-any.whl → 0.1.1__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.
- crochet/migrations/engine.py +5 -3
- crochet/migrations/template.py +200 -1
- {crochet_migration-0.1.0.dist-info → crochet_migration-0.1.1.dist-info}/METADATA +2 -2
- {crochet_migration-0.1.0.dist-info → crochet_migration-0.1.1.dist-info}/RECORD +7 -7
- {crochet_migration-0.1.0.dist-info → crochet_migration-0.1.1.dist-info}/WHEEL +0 -0
- {crochet_migration-0.1.0.dist-info → crochet_migration-0.1.1.dist-info}/entry_points.txt +0 -0
- {crochet_migration-0.1.0.dist-info → crochet_migration-0.1.1.dist-info}/licenses/LICENSE +0 -0
crochet/migrations/engine.py
CHANGED
|
@@ -153,6 +153,7 @@ class MigrationEngine:
|
|
|
153
153
|
# Compute schema hash and diff
|
|
154
154
|
schema_hash = ""
|
|
155
155
|
diff_summary = ""
|
|
156
|
+
diff_obj = None
|
|
156
157
|
if current_snapshot is not None:
|
|
157
158
|
current_snapshot = hash_snapshot(current_snapshot)
|
|
158
159
|
schema_hash = current_snapshot.schema_hash
|
|
@@ -166,9 +167,9 @@ class MigrationEngine:
|
|
|
166
167
|
prev_json = self._ledger.get_snapshot(prev_hash)
|
|
167
168
|
if prev_json:
|
|
168
169
|
prev_snapshot = SchemaSnapshot.from_json(prev_json)
|
|
169
|
-
|
|
170
|
-
if
|
|
171
|
-
diff_summary =
|
|
170
|
+
diff_obj = diff_snapshots(prev_snapshot, current_snapshot)
|
|
171
|
+
if diff_obj.has_changes:
|
|
172
|
+
diff_summary = diff_obj.summary()
|
|
172
173
|
|
|
173
174
|
content = render_migration(
|
|
174
175
|
revision_id=revision_id,
|
|
@@ -177,6 +178,7 @@ class MigrationEngine:
|
|
|
177
178
|
schema_hash=schema_hash,
|
|
178
179
|
rollback_safe=rollback_safe,
|
|
179
180
|
diff_summary=diff_summary,
|
|
181
|
+
diff=diff_obj,
|
|
180
182
|
)
|
|
181
183
|
|
|
182
184
|
return write_migration_file(
|
crochet/migrations/template.py
CHANGED
|
@@ -5,6 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from crochet.ir.diff import SchemaDiff
|
|
8
12
|
|
|
9
13
|
_MIGRATION_TEMPLATE = '''\
|
|
10
14
|
"""
|
|
@@ -52,6 +56,189 @@ def generate_revision_id(seq: int, description: str) -> str:
|
|
|
52
56
|
return f"{seq:04d}_{slug}"
|
|
53
57
|
|
|
54
58
|
|
|
59
|
+
def generate_operations_from_diff(diff: "SchemaDiff") -> tuple[str, str]:
|
|
60
|
+
"""Generate upgrade and downgrade operation code from a SchemaDiff.
|
|
61
|
+
|
|
62
|
+
Returns a tuple of (upgrade_code, downgrade_code) as strings.
|
|
63
|
+
"""
|
|
64
|
+
upgrade_lines: list[str] = []
|
|
65
|
+
downgrade_lines: list[str] = []
|
|
66
|
+
|
|
67
|
+
# Process node changes
|
|
68
|
+
for nc in diff.node_changes:
|
|
69
|
+
if nc.kind == "added":
|
|
70
|
+
# Node added - no automatic operations (user should handle data)
|
|
71
|
+
upgrade_lines.append(f"# TODO: Handle new node '{nc.new.label}' (kgid={nc.kgid})")
|
|
72
|
+
downgrade_lines.append(f"# TODO: Clean up node '{nc.new.label}' (kgid={nc.kgid})")
|
|
73
|
+
elif nc.kind == "removed":
|
|
74
|
+
# Node removed - no automatic operations (user should handle data)
|
|
75
|
+
upgrade_lines.append(f"# TODO: Handle removed node '{nc.old.label}' (kgid={nc.kgid})")
|
|
76
|
+
downgrade_lines.append(f"# TODO: Restore node '{nc.old.label}' (kgid={nc.kgid})")
|
|
77
|
+
elif nc.kind == "modified":
|
|
78
|
+
label = nc.new.label if nc.new else nc.old.label
|
|
79
|
+
|
|
80
|
+
# Handle label rename
|
|
81
|
+
if nc.label_renamed and nc.old and nc.new:
|
|
82
|
+
upgrade_lines.append(
|
|
83
|
+
f'ctx.rename_label("{nc.old.label}", "{nc.new.label}")'
|
|
84
|
+
)
|
|
85
|
+
downgrade_lines.append(
|
|
86
|
+
f'ctx.rename_label("{nc.new.label}", "{nc.old.label}")'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Handle property changes
|
|
90
|
+
for pc in nc.property_changes:
|
|
91
|
+
if pc.kind == "added":
|
|
92
|
+
# Property added
|
|
93
|
+
upgrade_lines.append(
|
|
94
|
+
f'ctx.add_node_property("{label}", "{pc.property_name}")'
|
|
95
|
+
)
|
|
96
|
+
downgrade_lines.append(
|
|
97
|
+
f'ctx.remove_node_property("{label}", "{pc.property_name}")'
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Handle constraints/indexes for new property
|
|
101
|
+
if pc.new:
|
|
102
|
+
if pc.new.unique_index:
|
|
103
|
+
upgrade_lines.append(
|
|
104
|
+
f'ctx.add_unique_constraint("{label}", "{pc.property_name}")'
|
|
105
|
+
)
|
|
106
|
+
downgrade_lines.append(
|
|
107
|
+
f'ctx.drop_unique_constraint("{label}", "{pc.property_name}")'
|
|
108
|
+
)
|
|
109
|
+
elif pc.new.index:
|
|
110
|
+
upgrade_lines.append(
|
|
111
|
+
f'ctx.add_index("{label}", "{pc.property_name}")'
|
|
112
|
+
)
|
|
113
|
+
downgrade_lines.append(
|
|
114
|
+
f'ctx.drop_index("{label}", "{pc.property_name}")'
|
|
115
|
+
)
|
|
116
|
+
if pc.new.required:
|
|
117
|
+
upgrade_lines.append(
|
|
118
|
+
f'ctx.add_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
119
|
+
)
|
|
120
|
+
downgrade_lines.append(
|
|
121
|
+
f'ctx.drop_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
elif pc.kind == "removed":
|
|
125
|
+
# Property removed
|
|
126
|
+
upgrade_lines.append(
|
|
127
|
+
f'ctx.remove_node_property("{label}", "{pc.property_name}")'
|
|
128
|
+
)
|
|
129
|
+
downgrade_lines.append(
|
|
130
|
+
f'ctx.add_node_property("{label}", "{pc.property_name}")'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Handle constraints/indexes for removed property
|
|
134
|
+
if pc.old:
|
|
135
|
+
if pc.old.unique_index:
|
|
136
|
+
upgrade_lines.append(
|
|
137
|
+
f'ctx.drop_unique_constraint("{label}", "{pc.property_name}")'
|
|
138
|
+
)
|
|
139
|
+
downgrade_lines.append(
|
|
140
|
+
f'ctx.add_unique_constraint("{label}", "{pc.property_name}")'
|
|
141
|
+
)
|
|
142
|
+
elif pc.old.index:
|
|
143
|
+
upgrade_lines.append(
|
|
144
|
+
f'ctx.drop_index("{label}", "{pc.property_name}")'
|
|
145
|
+
)
|
|
146
|
+
downgrade_lines.append(
|
|
147
|
+
f'ctx.add_index("{label}", "{pc.property_name}")'
|
|
148
|
+
)
|
|
149
|
+
if pc.old.required:
|
|
150
|
+
upgrade_lines.append(
|
|
151
|
+
f'ctx.drop_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
152
|
+
)
|
|
153
|
+
downgrade_lines.append(
|
|
154
|
+
f'ctx.add_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
elif pc.kind == "modified" and pc.old and pc.new:
|
|
158
|
+
# Property modified - handle constraint/index changes
|
|
159
|
+
if pc.old.unique_index != pc.new.unique_index:
|
|
160
|
+
if pc.new.unique_index:
|
|
161
|
+
upgrade_lines.append(
|
|
162
|
+
f'ctx.add_unique_constraint("{label}", "{pc.property_name}")'
|
|
163
|
+
)
|
|
164
|
+
downgrade_lines.append(
|
|
165
|
+
f'ctx.drop_unique_constraint("{label}", "{pc.property_name}")'
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
upgrade_lines.append(
|
|
169
|
+
f'ctx.drop_unique_constraint("{label}", "{pc.property_name}")'
|
|
170
|
+
)
|
|
171
|
+
downgrade_lines.append(
|
|
172
|
+
f'ctx.add_unique_constraint("{label}", "{pc.property_name}")'
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if pc.old.index != pc.new.index:
|
|
176
|
+
if pc.new.index:
|
|
177
|
+
upgrade_lines.append(
|
|
178
|
+
f'ctx.add_index("{label}", "{pc.property_name}")'
|
|
179
|
+
)
|
|
180
|
+
downgrade_lines.append(
|
|
181
|
+
f'ctx.drop_index("{label}", "{pc.property_name}")'
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
upgrade_lines.append(
|
|
185
|
+
f'ctx.drop_index("{label}", "{pc.property_name}")'
|
|
186
|
+
)
|
|
187
|
+
downgrade_lines.append(
|
|
188
|
+
f'ctx.add_index("{label}", "{pc.property_name}")'
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if pc.old.required != pc.new.required:
|
|
192
|
+
if pc.new.required:
|
|
193
|
+
upgrade_lines.append(
|
|
194
|
+
f'ctx.add_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
195
|
+
)
|
|
196
|
+
downgrade_lines.append(
|
|
197
|
+
f'ctx.drop_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
198
|
+
)
|
|
199
|
+
else:
|
|
200
|
+
upgrade_lines.append(
|
|
201
|
+
f'ctx.drop_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
202
|
+
)
|
|
203
|
+
downgrade_lines.append(
|
|
204
|
+
f'ctx.add_node_property_existence_constraint("{label}", "{pc.property_name}")'
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Process relationship changes
|
|
208
|
+
for rc in diff.relationship_changes:
|
|
209
|
+
if rc.kind == "added":
|
|
210
|
+
upgrade_lines.append(f"# TODO: Handle new relationship '{rc.new.rel_type}' (kgid={rc.kgid})")
|
|
211
|
+
downgrade_lines.append(f"# TODO: Clean up relationship '{rc.new.rel_type}' (kgid={rc.kgid})")
|
|
212
|
+
elif rc.kind == "removed":
|
|
213
|
+
upgrade_lines.append(f"# TODO: Handle removed relationship '{rc.old.rel_type}' (kgid={rc.kgid})")
|
|
214
|
+
downgrade_lines.append(f"# TODO: Restore relationship '{rc.old.rel_type}' (kgid={rc.kgid})")
|
|
215
|
+
elif rc.kind == "modified":
|
|
216
|
+
# Handle relationship property changes similarly to nodes
|
|
217
|
+
rel_type = rc.new.rel_type if rc.new else rc.old.rel_type
|
|
218
|
+
for pc in rc.property_changes:
|
|
219
|
+
if pc.kind == "added":
|
|
220
|
+
upgrade_lines.append(
|
|
221
|
+
f'# TODO: Add property "{pc.property_name}" to relationship {rel_type}'
|
|
222
|
+
)
|
|
223
|
+
elif pc.kind == "removed":
|
|
224
|
+
upgrade_lines.append(
|
|
225
|
+
f'# TODO: Remove property "{pc.property_name}" from relationship {rel_type}'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Format the code
|
|
229
|
+
if upgrade_lines:
|
|
230
|
+
upgrade_code = " " + "\n ".join(upgrade_lines)
|
|
231
|
+
else:
|
|
232
|
+
upgrade_code = " pass"
|
|
233
|
+
|
|
234
|
+
if downgrade_lines:
|
|
235
|
+
downgrade_code = " " + "\n ".join(downgrade_lines)
|
|
236
|
+
else:
|
|
237
|
+
downgrade_code = " pass"
|
|
238
|
+
|
|
239
|
+
return upgrade_code, downgrade_code
|
|
240
|
+
|
|
241
|
+
|
|
55
242
|
def render_migration(
|
|
56
243
|
revision_id: str,
|
|
57
244
|
parent_id: str | None,
|
|
@@ -59,11 +246,23 @@ def render_migration(
|
|
|
59
246
|
schema_hash: str,
|
|
60
247
|
rollback_safe: bool = True,
|
|
61
248
|
diff_summary: str = "",
|
|
249
|
+
diff: "SchemaDiff | None" = None,
|
|
62
250
|
) -> str:
|
|
63
251
|
"""Render a migration file from template."""
|
|
64
252
|
now = datetime.now(timezone.utc).isoformat()
|
|
65
253
|
|
|
66
|
-
if
|
|
254
|
+
# Generate operations from diff if available
|
|
255
|
+
if diff is not None and diff.has_changes:
|
|
256
|
+
upgrade_lines, downgrade_lines = generate_operations_from_diff(diff)
|
|
257
|
+
# Prepend comment header showing what was detected
|
|
258
|
+
if diff_summary:
|
|
259
|
+
comment_header = _DIFF_COMMENT_HEADER
|
|
260
|
+
for line in diff_summary.splitlines():
|
|
261
|
+
comment_header += f" # {line}\n"
|
|
262
|
+
upgrade_lines = comment_header + "\n" + upgrade_lines
|
|
263
|
+
downgrade_lines = comment_header + "\n" + downgrade_lines
|
|
264
|
+
elif diff_summary:
|
|
265
|
+
# Fallback to old behavior if only summary is provided
|
|
67
266
|
upgrade_lines = _DIFF_COMMENT_HEADER
|
|
68
267
|
for line in diff_summary.splitlines():
|
|
69
268
|
upgrade_lines += f" # {line}\n"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crochet-migration
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Versioned schema & data migrations for neomodel Neo4j graphs
|
|
5
5
|
Project-URL: Homepage, https://github.com/keshavd/crochet
|
|
6
6
|
Project-URL: Repository, https://github.com/keshavd/crochet
|
|
@@ -52,7 +52,7 @@ graph.
|
|
|
52
52
|
## Installation
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
pip install crochet
|
|
55
|
+
pip install crochet-migration
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
For development:
|
|
@@ -13,14 +13,14 @@ crochet/ir/schema.py,sha256=0MTOWGAmtWDVGCSfUEAdHXNM-lriyeT3gCUbsF3OsSc,6160
|
|
|
13
13
|
crochet/ledger/__init__.py,sha256=xXaClHCWAbo36NfYMo41VftPo3O7kPpIyW-wj4Lwaqw,133
|
|
14
14
|
crochet/ledger/sqlite.py,sha256=yGy4dDh5KPC5-N3HVaK1FpD7GYixoM2M5_IDRaEROvU,9421
|
|
15
15
|
crochet/migrations/__init__.py,sha256=-112mqAx8xTQzu33lA3mmiZAP6v_H0_tPlOpYRazVeE,204
|
|
16
|
-
crochet/migrations/engine.py,sha256=
|
|
16
|
+
crochet/migrations/engine.py,sha256=YmZ1OuaYmTWof5cp3HZbLKQVhUHARO1OScvMgePEt5w,9626
|
|
17
17
|
crochet/migrations/operations.py,sha256=iv2aFF5w-X5K2y5fqB-w8CuSNN15eEswDHyOJIiamrY,10360
|
|
18
|
-
crochet/migrations/template.py,sha256=
|
|
18
|
+
crochet/migrations/template.py,sha256=A_cZ_ECD5vybg_GaoB3uYydfail-Ygm8hzG915EBsGA,12637
|
|
19
19
|
crochet/scaffold/__init__.py,sha256=ZPyyQ3_s2uwFaw8kdpsYT7q4Br5TdU6KVCJbc0hhslo,236
|
|
20
20
|
crochet/scaffold/node.py,sha256=vXIqyYUj1Ot4qPUIwcwXsFKJsdcIbEh8Y71ASgWH4nY,1266
|
|
21
21
|
crochet/scaffold/relationship.py,sha256=eHKAt0olYcGXAA5TdnIl2DOZpN31GK9y-hJFutF2Tqg,1373
|
|
22
|
-
crochet_migration-0.1.
|
|
23
|
-
crochet_migration-0.1.
|
|
24
|
-
crochet_migration-0.1.
|
|
25
|
-
crochet_migration-0.1.
|
|
26
|
-
crochet_migration-0.1.
|
|
22
|
+
crochet_migration-0.1.1.dist-info/METADATA,sha256=OzS8_dvoN65VJAfMiiZn3B8-q9zb7Gy3O4_MRO_i4HU,8295
|
|
23
|
+
crochet_migration-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
crochet_migration-0.1.1.dist-info/entry_points.txt,sha256=wKTVp7ky8zuaFIMskw4FEVSppYDoKG-6CV1joyCKSC8,45
|
|
25
|
+
crochet_migration-0.1.1.dist-info/licenses/LICENSE,sha256=plFEmT-Ix7lZ5QZvnBsTTETSVDcBhM9sY8lWCxU6llg,1068
|
|
26
|
+
crochet_migration-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|