rakko-orm 1.0.0a0__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.
- rakko/__init__.py +613 -0
- rakko/__main__.py +10 -0
- rakko/apps.py +356 -0
- rakko/backends/__init__.py +4 -0
- rakko/backends/asyncpg/__init__.py +7 -0
- rakko/backends/asyncpg/client.py +223 -0
- rakko/backends/asyncpg/executor.py +20 -0
- rakko/backends/asyncpg/schema_generator.py +17 -0
- rakko/backends/base/__init__.py +4 -0
- rakko/backends/base/client.py +438 -0
- rakko/backends/base/config_generator.py +249 -0
- rakko/backends/base/executor.py +764 -0
- rakko/backends/base/schema_generator.py +580 -0
- rakko/backends/base_postgres/__init__.py +4 -0
- rakko/backends/base_postgres/client.py +170 -0
- rakko/backends/base_postgres/executor.py +149 -0
- rakko/backends/base_postgres/schema_generator.py +84 -0
- rakko/backends/mssql/__init__.py +7 -0
- rakko/backends/mssql/client.py +127 -0
- rakko/backends/mssql/executor.py +17 -0
- rakko/backends/mssql/schema_generator.py +125 -0
- rakko/backends/mysql/__init__.py +7 -0
- rakko/backends/mysql/client.py +312 -0
- rakko/backends/mysql/executor.py +154 -0
- rakko/backends/mysql/schema_generator.py +101 -0
- rakko/backends/odbc/__init__.py +4 -0
- rakko/backends/odbc/client.py +221 -0
- rakko/backends/odbc/executor.py +20 -0
- rakko/backends/oracle/__init__.py +7 -0
- rakko/backends/oracle/client.py +136 -0
- rakko/backends/oracle/executor.py +27 -0
- rakko/backends/oracle/schema_generator.py +120 -0
- rakko/backends/psycopg/__init__.py +7 -0
- rakko/backends/psycopg/client.py +288 -0
- rakko/backends/psycopg/executor.py +14 -0
- rakko/backends/psycopg/schema_generator.py +17 -0
- rakko/backends/sqlite/__init__.py +14 -0
- rakko/backends/sqlite/client.py +299 -0
- rakko/backends/sqlite/executor.py +88 -0
- rakko/backends/sqlite/schema_generator.py +32 -0
- rakko/cli/__init__.py +13 -0
- rakko/cli/cli.py +878 -0
- rakko/cli/utils.py +181 -0
- rakko/config.py +315 -0
- rakko/connection.py +345 -0
- rakko/context.py +571 -0
- rakko/contrib/__init__.py +4 -0
- rakko/contrib/aiohttp/__init__.py +99 -0
- rakko/contrib/blacksheep/__init__.py +121 -0
- rakko/contrib/fastapi/__init__.py +301 -0
- rakko/contrib/mysql/__init__.py +4 -0
- rakko/contrib/mysql/fields.py +63 -0
- rakko/contrib/mysql/functions.py +19 -0
- rakko/contrib/mysql/indexes.py +28 -0
- rakko/contrib/mysql/json_functions.py +80 -0
- rakko/contrib/mysql/search.py +49 -0
- rakko/contrib/postgres/__init__.py +4 -0
- rakko/contrib/postgres/array_functions.py +35 -0
- rakko/contrib/postgres/fields.py +105 -0
- rakko/contrib/postgres/functions.py +43 -0
- rakko/contrib/postgres/indexes.py +33 -0
- rakko/contrib/postgres/json_functions.py +113 -0
- rakko/contrib/postgres/regex.py +31 -0
- rakko/contrib/postgres/search.py +483 -0
- rakko/contrib/pydantic/__init__.py +16 -0
- rakko/contrib/pydantic/base.py +147 -0
- rakko/contrib/pydantic/creator.py +681 -0
- rakko/contrib/pydantic/descriptions.py +207 -0
- rakko/contrib/pydantic/utils.py +38 -0
- rakko/contrib/pylint/__init__.py +158 -0
- rakko/contrib/quart/__init__.py +117 -0
- rakko/contrib/sanic/__init__.py +116 -0
- rakko/contrib/sqlite/__init__.py +4 -0
- rakko/contrib/sqlite/functions.py +16 -0
- rakko/contrib/sqlite/regex.py +46 -0
- rakko/contrib/starlette/__init__.py +180 -0
- rakko/contrib/test/__init__.py +336 -0
- rakko/contrib/test/condition.py +42 -0
- rakko/converters.py +160 -0
- rakko/exceptions.py +130 -0
- rakko/expressions.py +962 -0
- rakko/fields/__init__.py +111 -0
- rakko/fields/base.py +685 -0
- rakko/fields/data.py +1308 -0
- rakko/fields/db_defaults.py +105 -0
- rakko/fields/relational.py +703 -0
- rakko/filters.py +639 -0
- rakko/functions.py +142 -0
- rakko/indexes.py +165 -0
- rakko/log.py +8 -0
- rakko/manager.py +29 -0
- rakko/migrations/__init__.py +41 -0
- rakko/migrations/api/__init__.py +16 -0
- rakko/migrations/api/migrate.py +77 -0
- rakko/migrations/api/plan.py +76 -0
- rakko/migrations/api/sqlmigrate.py +69 -0
- rakko/migrations/autodetector.py +203 -0
- rakko/migrations/constraints.py +31 -0
- rakko/migrations/exceptions.py +10 -0
- rakko/migrations/executor.py +368 -0
- rakko/migrations/expressions.py +13 -0
- rakko/migrations/graph.py +158 -0
- rakko/migrations/loader.py +127 -0
- rakko/migrations/migration.py +189 -0
- rakko/migrations/operations.py +1288 -0
- rakko/migrations/recorder.py +104 -0
- rakko/migrations/schema_editor/__init__.py +25 -0
- rakko/migrations/schema_editor/asyncpg.py +11 -0
- rakko/migrations/schema_editor/base.py +945 -0
- rakko/migrations/schema_editor/base_postgres.py +171 -0
- rakko/migrations/schema_editor/data.py +18 -0
- rakko/migrations/schema_editor/mssql.py +339 -0
- rakko/migrations/schema_editor/mysql.py +408 -0
- rakko/migrations/schema_editor/oracle.py +259 -0
- rakko/migrations/schema_editor/psycopg.py +11 -0
- rakko/migrations/schema_editor/sqlite.py +424 -0
- rakko/migrations/schema_generator/__init__.py +5 -0
- rakko/migrations/schema_generator/operation_generator.py +163 -0
- rakko/migrations/schema_generator/state.py +224 -0
- rakko/migrations/schema_generator/state_apps.py +165 -0
- rakko/migrations/schema_generator/state_diff.py +419 -0
- rakko/migrations/writer.py +515 -0
- rakko/models.py +1854 -0
- rakko/py.typed +0 -0
- rakko/query_api.py +134 -0
- rakko/query_utils.py +238 -0
- rakko/queryset.py +2743 -0
- rakko/relations.py +85 -0
- rakko/router.py +55 -0
- rakko/schema_quoting.py +62 -0
- rakko/signals.py +73 -0
- rakko/timezone.py +173 -0
- rakko/transactions.py +69 -0
- rakko/utils.py +50 -0
- rakko/validators.py +412 -0
- rakko/warnings.py +22 -0
- rakko_orm-1.0.0a0.dist-info/METADATA +114 -0
- rakko_orm-1.0.0a0.dist-info/RECORD +143 -0
- rakko_orm-1.0.0a0.dist-info/WHEEL +4 -0
- rakko_orm-1.0.0a0.dist-info/entry_points.txt +5 -0
- rakko_orm-1.0.0a0.dist-info/licenses/CONTRIBUTORS.rst +86 -0
- rakko_orm-1.0.0a0.dist-info/licenses/LICENSE.txt +201 -0
- rakko_orm-1.0.0a0.dist-info/licenses/NOTICE.txt +22 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# Copyright (c) 2026 ropmyung (Rakko ORM modifications and additions).
|
|
2
|
+
# Derived from Tortoise ORM; maintained and modified for Rakko ORM.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from collections.abc import Iterable
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from rakko.fields.relational import (
|
|
11
|
+
ForeignKeyFieldInstance,
|
|
12
|
+
ManyToManyFieldInstance,
|
|
13
|
+
OneToOneFieldInstance,
|
|
14
|
+
)
|
|
15
|
+
from rakko.indexes import Index
|
|
16
|
+
from rakko.migrations.constraints import CheckConstraint, UniqueConstraint
|
|
17
|
+
from rakko.migrations.operations import (
|
|
18
|
+
AddConstraint,
|
|
19
|
+
AddField,
|
|
20
|
+
AddIndex,
|
|
21
|
+
AlterField,
|
|
22
|
+
AlterModelOptions,
|
|
23
|
+
RemoveConstraint,
|
|
24
|
+
RemoveField,
|
|
25
|
+
RemoveIndex,
|
|
26
|
+
RenameConstraint,
|
|
27
|
+
RenameField,
|
|
28
|
+
RenameIndex,
|
|
29
|
+
TortoiseOperation,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from rakko.fields.base import Field
|
|
34
|
+
from rakko.migrations.schema_generator.state import ModelState
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Shared normalisation helpers
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _normalize_indexes(value: object) -> list[Index]:
|
|
43
|
+
"""Normalise raw index tuples/Index objects into a flat list of Index instances."""
|
|
44
|
+
if not value or not isinstance(value, Iterable):
|
|
45
|
+
return []
|
|
46
|
+
return [item if isinstance(item, Index) else Index(fields=tuple(item)) for item in value]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _index_signature(index: Index) -> tuple:
|
|
50
|
+
"""Return a hashable identity tuple for an Index (ignoring its name)."""
|
|
51
|
+
return (tuple(index.field_names), index.INDEX_TYPE, index.extra)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _normalize_unique_together(value: object) -> list[tuple[str, ...]]:
|
|
55
|
+
if not value or not isinstance(value, Iterable):
|
|
56
|
+
return []
|
|
57
|
+
return [tuple(fields) for fields in value]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _normalize_constraints(value: object) -> list[UniqueConstraint | CheckConstraint]:
|
|
61
|
+
if not value or not isinstance(value, Iterable):
|
|
62
|
+
return []
|
|
63
|
+
return [c for c in value if isinstance(c, (UniqueConstraint, CheckConstraint))]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
RELATION_FIELDS = (ForeignKeyFieldInstance, OneToOneFieldInstance, ManyToManyFieldInstance)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _field_signature(field: Field) -> dict[str, object]:
|
|
70
|
+
desc = field.describe(serializable=True)
|
|
71
|
+
if getattr(field, "source_field", None) is None:
|
|
72
|
+
desc.pop("db_column", None)
|
|
73
|
+
for key in ("name", "docstring", "default", "python_type"):
|
|
74
|
+
desc.pop(key, None)
|
|
75
|
+
return desc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _field_signature_for_rename(field: Field) -> dict[str, object]:
|
|
79
|
+
desc = _field_signature(field)
|
|
80
|
+
desc.pop("source_field", None)
|
|
81
|
+
desc.pop("db_column", None)
|
|
82
|
+
return desc
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _model_options_for_compare(options: dict[str, object]) -> dict[str, object]:
|
|
86
|
+
return {
|
|
87
|
+
key: value
|
|
88
|
+
for key, value in options.items()
|
|
89
|
+
if key not in AlterModelOptions.UNMANAGED_OPTION_KEYS
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _base_signature(bases: Iterable[type]) -> list[str]:
|
|
94
|
+
return [f"{base.__module__}.{base.__name__}" for base in bases]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _model_signature(model_state: ModelState) -> dict[str, object]:
|
|
98
|
+
fields = {
|
|
99
|
+
name: _field_signature(field)
|
|
100
|
+
for name, field in model_state.fields.items()
|
|
101
|
+
if not isinstance(field, RELATION_FIELDS)
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
"fields": fields,
|
|
105
|
+
"options": _model_options_for_compare(model_state.options),
|
|
106
|
+
"bases": _base_signature(model_state.bases),
|
|
107
|
+
"pk_field_name": model_state.pk_field_name,
|
|
108
|
+
"abstract": model_state.abstract,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class StateModelDiff:
|
|
113
|
+
def __init__(self, old_state: ModelState, new_state: ModelState) -> None:
|
|
114
|
+
self.old_state = old_state
|
|
115
|
+
self.new_state = new_state
|
|
116
|
+
|
|
117
|
+
def generate_operations(self) -> list[TortoiseOperation]:
|
|
118
|
+
if self.old_state is self.new_state:
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
operations: list[TortoiseOperation] = []
|
|
122
|
+
operations.extend(self._generate_index_operations())
|
|
123
|
+
operations.extend(self._generate_constraint_operations())
|
|
124
|
+
old_options = _model_options_for_compare(self.old_state.options)
|
|
125
|
+
new_options = _model_options_for_compare(self.new_state.options)
|
|
126
|
+
if old_options != new_options:
|
|
127
|
+
operations.append(
|
|
128
|
+
AlterModelOptions(
|
|
129
|
+
name=self.new_state.name,
|
|
130
|
+
options=self.new_state.options,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
operations.extend(StateFieldDiff(self.old_state, self.new_state).generate_operations())
|
|
135
|
+
|
|
136
|
+
# Classes that must always be last
|
|
137
|
+
always_last = (AddIndex, AddConstraint)
|
|
138
|
+
operations = sorted(operations, key=lambda op: isinstance(op, always_last))
|
|
139
|
+
return operations
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _normalize_indexes_with_explicit(value: object) -> list[tuple[Index, bool]]:
|
|
143
|
+
"""Like _normalize_indexes but tracks whether each item was an explicit Index."""
|
|
144
|
+
if not value or not isinstance(value, Iterable):
|
|
145
|
+
return []
|
|
146
|
+
return [
|
|
147
|
+
(item, True) if isinstance(item, Index) else (Index(fields=tuple(item)), False)
|
|
148
|
+
for item in value
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
def _generate_index_operations(self) -> list[TortoiseOperation]:
|
|
152
|
+
operations: list[TortoiseOperation] = []
|
|
153
|
+
old_indexes = self._normalize_indexes_with_explicit(
|
|
154
|
+
self.old_state.options.get("indexes", ())
|
|
155
|
+
)
|
|
156
|
+
new_indexes = self._normalize_indexes_with_explicit(
|
|
157
|
+
self.new_state.options.get("indexes", ())
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
matched_old_indexes: set[int] = set()
|
|
161
|
+
matched_new_indexes: set[int] = set()
|
|
162
|
+
|
|
163
|
+
for new_idx, (new_index, new_explicit) in enumerate(new_indexes):
|
|
164
|
+
if not new_explicit or not new_index.name:
|
|
165
|
+
continue
|
|
166
|
+
new_sig = _index_signature(new_index)
|
|
167
|
+
for old_idx, (old_index, old_explicit) in enumerate(old_indexes):
|
|
168
|
+
if old_idx in matched_old_indexes or not old_explicit or not old_index.name:
|
|
169
|
+
continue
|
|
170
|
+
if new_sig == _index_signature(old_index) and new_index.name != old_index.name:
|
|
171
|
+
operations.append(
|
|
172
|
+
RenameIndex(
|
|
173
|
+
model_name=self.new_state.name,
|
|
174
|
+
old_name=old_index.name,
|
|
175
|
+
new_name=new_index.name,
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
matched_old_indexes.add(old_idx)
|
|
179
|
+
matched_new_indexes.add(new_idx)
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
for old_idx, (old_index, _explicit) in enumerate(old_indexes):
|
|
183
|
+
if old_idx in matched_old_indexes:
|
|
184
|
+
continue
|
|
185
|
+
if any(
|
|
186
|
+
_index_signature(old_index) == _index_signature(new_index)
|
|
187
|
+
for new_index, _ in new_indexes
|
|
188
|
+
):
|
|
189
|
+
continue
|
|
190
|
+
operations.append(
|
|
191
|
+
RemoveIndex(
|
|
192
|
+
model_name=self.old_state.name,
|
|
193
|
+
name=old_index.name,
|
|
194
|
+
fields=list(old_index.field_names) if not old_index.name else None,
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
for new_idx, (new_index, _explicit) in enumerate(new_indexes):
|
|
199
|
+
if new_idx in matched_new_indexes:
|
|
200
|
+
continue
|
|
201
|
+
if any(
|
|
202
|
+
_index_signature(new_index) == _index_signature(old_index)
|
|
203
|
+
for old_index, _ in old_indexes
|
|
204
|
+
):
|
|
205
|
+
continue
|
|
206
|
+
operations.append(AddIndex(model_name=self.new_state.name, index=new_index))
|
|
207
|
+
|
|
208
|
+
return operations
|
|
209
|
+
|
|
210
|
+
def _generate_constraint_operations(self) -> list[TortoiseOperation]:
|
|
211
|
+
operations: list[TortoiseOperation] = []
|
|
212
|
+
old_unique = _normalize_unique_together(self.old_state.options.get("unique_together", ()))
|
|
213
|
+
new_unique = _normalize_unique_together(self.new_state.options.get("unique_together", ()))
|
|
214
|
+
|
|
215
|
+
for fields in old_unique:
|
|
216
|
+
if fields not in new_unique:
|
|
217
|
+
operations.append(
|
|
218
|
+
RemoveConstraint(
|
|
219
|
+
model_name=self.old_state.name,
|
|
220
|
+
fields=list(fields),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
for fields in new_unique:
|
|
225
|
+
if fields not in old_unique:
|
|
226
|
+
operations.append(
|
|
227
|
+
AddConstraint(
|
|
228
|
+
model_name=self.new_state.name,
|
|
229
|
+
constraint=UniqueConstraint(fields=tuple(fields)),
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
old_constraints = _normalize_constraints(self.old_state.options.get("constraints", ()))
|
|
234
|
+
new_constraints = _normalize_constraints(self.new_state.options.get("constraints", ()))
|
|
235
|
+
|
|
236
|
+
# Rename detection for UniqueConstraint (by matching fields)
|
|
237
|
+
old_by_fields = {
|
|
238
|
+
tuple(constraint.fields): constraint
|
|
239
|
+
for constraint in old_constraints
|
|
240
|
+
if isinstance(constraint, UniqueConstraint) and constraint.name
|
|
241
|
+
}
|
|
242
|
+
new_by_fields = {
|
|
243
|
+
tuple(constraint.fields): constraint
|
|
244
|
+
for constraint in new_constraints
|
|
245
|
+
if isinstance(constraint, UniqueConstraint) and constraint.name
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for fields, new_constraint in new_by_fields.items():
|
|
249
|
+
old_constraint = old_by_fields.get(fields)
|
|
250
|
+
if (
|
|
251
|
+
old_constraint
|
|
252
|
+
and old_constraint.name is not None
|
|
253
|
+
and new_constraint.name is not None
|
|
254
|
+
and old_constraint.name != new_constraint.name
|
|
255
|
+
):
|
|
256
|
+
operations.append(
|
|
257
|
+
RenameConstraint(
|
|
258
|
+
model_name=self.new_state.name,
|
|
259
|
+
old_name=old_constraint.name,
|
|
260
|
+
new_name=new_constraint.name,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Rename detection for CheckConstraint (by matching check expression)
|
|
265
|
+
old_by_check: dict[str, CheckConstraint] = {
|
|
266
|
+
constraint.check: constraint
|
|
267
|
+
for constraint in old_constraints
|
|
268
|
+
if isinstance(constraint, CheckConstraint)
|
|
269
|
+
}
|
|
270
|
+
new_by_check: dict[str, CheckConstraint] = {
|
|
271
|
+
constraint.check: constraint
|
|
272
|
+
for constraint in new_constraints
|
|
273
|
+
if isinstance(constraint, CheckConstraint)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for check_expr, new_ck in new_by_check.items():
|
|
277
|
+
old_ck = old_by_check.get(check_expr)
|
|
278
|
+
if old_ck and old_ck.name != new_ck.name:
|
|
279
|
+
operations.append(
|
|
280
|
+
RenameConstraint(
|
|
281
|
+
model_name=self.new_state.name,
|
|
282
|
+
old_name=old_ck.name,
|
|
283
|
+
new_name=new_ck.name,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
for constraint in old_constraints:
|
|
288
|
+
if constraint.name and any(
|
|
289
|
+
op
|
|
290
|
+
for op in operations
|
|
291
|
+
if isinstance(op, RenameConstraint) and op.old_name == constraint.name
|
|
292
|
+
):
|
|
293
|
+
continue
|
|
294
|
+
if constraint not in new_constraints:
|
|
295
|
+
operations.append(
|
|
296
|
+
RemoveConstraint(
|
|
297
|
+
model_name=self.old_state.name,
|
|
298
|
+
name=constraint.name,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
for constraint in new_constraints:
|
|
303
|
+
if constraint.name and any(
|
|
304
|
+
op
|
|
305
|
+
for op in operations
|
|
306
|
+
if isinstance(op, RenameConstraint) and op.new_name == constraint.name
|
|
307
|
+
):
|
|
308
|
+
continue
|
|
309
|
+
if constraint not in old_constraints:
|
|
310
|
+
operations.append(
|
|
311
|
+
AddConstraint(
|
|
312
|
+
model_name=self.new_state.name,
|
|
313
|
+
constraint=constraint,
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return operations
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class StateFieldDiff:
|
|
321
|
+
def __init__(self, old_state: ModelState, new_state: ModelState) -> None:
|
|
322
|
+
self.old_state = old_state
|
|
323
|
+
self.new_state = new_state
|
|
324
|
+
|
|
325
|
+
def _generated_field_recreate_ops(
|
|
326
|
+
self, field_name: str, field: Field
|
|
327
|
+
) -> list[TortoiseOperation]:
|
|
328
|
+
operations: list[TortoiseOperation] = []
|
|
329
|
+
if getattr(field, "index", False):
|
|
330
|
+
operations.append(
|
|
331
|
+
AddIndex(model_name=self.new_state.name, index=Index(fields=(field_name,)))
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
old_indexes = _normalize_indexes(self.old_state.options.get("indexes", ()))
|
|
335
|
+
new_indexes = _normalize_indexes(self.new_state.options.get("indexes", ()))
|
|
336
|
+
old_index_sigs = {_index_signature(index) for index in old_indexes}
|
|
337
|
+
for index in new_indexes:
|
|
338
|
+
if _index_signature(index) not in old_index_sigs:
|
|
339
|
+
continue
|
|
340
|
+
if index.fields and field_name in index.fields:
|
|
341
|
+
operations.append(AddIndex(model_name=self.new_state.name, index=index))
|
|
342
|
+
|
|
343
|
+
old_unique = set(
|
|
344
|
+
_normalize_unique_together(self.old_state.options.get("unique_together", ()))
|
|
345
|
+
)
|
|
346
|
+
new_unique = set(
|
|
347
|
+
_normalize_unique_together(self.new_state.options.get("unique_together", ()))
|
|
348
|
+
)
|
|
349
|
+
for fields in new_unique & old_unique:
|
|
350
|
+
if field_name not in fields:
|
|
351
|
+
continue
|
|
352
|
+
operations.append(
|
|
353
|
+
AddConstraint(
|
|
354
|
+
model_name=self.new_state.name,
|
|
355
|
+
constraint=UniqueConstraint(fields=tuple(fields)),
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
old_constraints = _normalize_constraints(self.old_state.options.get("constraints", ()))
|
|
360
|
+
new_constraints = _normalize_constraints(self.new_state.options.get("constraints", ()))
|
|
361
|
+
old_constraints_set = set(old_constraints)
|
|
362
|
+
for constraint in new_constraints:
|
|
363
|
+
if constraint not in old_constraints_set:
|
|
364
|
+
continue
|
|
365
|
+
if isinstance(constraint, UniqueConstraint) and field_name in constraint.fields:
|
|
366
|
+
operations.append(
|
|
367
|
+
AddConstraint(model_name=self.new_state.name, constraint=constraint)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return operations
|
|
371
|
+
|
|
372
|
+
def generate_operations(self) -> list[TortoiseOperation]:
|
|
373
|
+
operations: list[TortoiseOperation] = []
|
|
374
|
+
old_fields = self.old_state.fields
|
|
375
|
+
new_fields = self.new_state.fields
|
|
376
|
+
added_fields = set(new_fields) - set(old_fields)
|
|
377
|
+
removed_fields = set(old_fields) - set(new_fields)
|
|
378
|
+
|
|
379
|
+
for new_name in sorted(added_fields):
|
|
380
|
+
new_sig = _field_signature_for_rename(new_fields[new_name])
|
|
381
|
+
for old_name in sorted(removed_fields):
|
|
382
|
+
if new_sig == _field_signature_for_rename(old_fields[old_name]):
|
|
383
|
+
operations.append(
|
|
384
|
+
RenameField(
|
|
385
|
+
model_name=self.new_state.name,
|
|
386
|
+
old_name=old_name,
|
|
387
|
+
new_name=new_name,
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
removed_fields.remove(old_name)
|
|
391
|
+
added_fields.remove(new_name)
|
|
392
|
+
break
|
|
393
|
+
|
|
394
|
+
for name in sorted(set(old_fields) & set(new_fields)):
|
|
395
|
+
old_sig = _field_signature(old_fields[name])
|
|
396
|
+
new_sig = _field_signature(new_fields[name])
|
|
397
|
+
if old_sig != new_sig:
|
|
398
|
+
old_field = old_fields[name]
|
|
399
|
+
new_field = new_fields[name]
|
|
400
|
+
if old_field.generated or new_field.generated:
|
|
401
|
+
operations.append(RemoveField(model_name=self.new_state.name, name=name))
|
|
402
|
+
operations.append(
|
|
403
|
+
AddField(model_name=self.new_state.name, name=name, field=new_field)
|
|
404
|
+
)
|
|
405
|
+
operations.extend(self._generated_field_recreate_ops(name, new_field))
|
|
406
|
+
else:
|
|
407
|
+
operations.append(
|
|
408
|
+
AlterField(model_name=self.new_state.name, name=name, field=new_field)
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
for name in sorted(added_fields):
|
|
412
|
+
operations.append(
|
|
413
|
+
AddField(model_name=self.new_state.name, name=name, field=new_fields[name])
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
for name in sorted(removed_fields):
|
|
417
|
+
operations.append(RemoveField(model_name=self.new_state.name, name=name))
|
|
418
|
+
|
|
419
|
+
return operations
|