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.
Files changed (143) hide show
  1. rakko/__init__.py +613 -0
  2. rakko/__main__.py +10 -0
  3. rakko/apps.py +356 -0
  4. rakko/backends/__init__.py +4 -0
  5. rakko/backends/asyncpg/__init__.py +7 -0
  6. rakko/backends/asyncpg/client.py +223 -0
  7. rakko/backends/asyncpg/executor.py +20 -0
  8. rakko/backends/asyncpg/schema_generator.py +17 -0
  9. rakko/backends/base/__init__.py +4 -0
  10. rakko/backends/base/client.py +438 -0
  11. rakko/backends/base/config_generator.py +249 -0
  12. rakko/backends/base/executor.py +764 -0
  13. rakko/backends/base/schema_generator.py +580 -0
  14. rakko/backends/base_postgres/__init__.py +4 -0
  15. rakko/backends/base_postgres/client.py +170 -0
  16. rakko/backends/base_postgres/executor.py +149 -0
  17. rakko/backends/base_postgres/schema_generator.py +84 -0
  18. rakko/backends/mssql/__init__.py +7 -0
  19. rakko/backends/mssql/client.py +127 -0
  20. rakko/backends/mssql/executor.py +17 -0
  21. rakko/backends/mssql/schema_generator.py +125 -0
  22. rakko/backends/mysql/__init__.py +7 -0
  23. rakko/backends/mysql/client.py +312 -0
  24. rakko/backends/mysql/executor.py +154 -0
  25. rakko/backends/mysql/schema_generator.py +101 -0
  26. rakko/backends/odbc/__init__.py +4 -0
  27. rakko/backends/odbc/client.py +221 -0
  28. rakko/backends/odbc/executor.py +20 -0
  29. rakko/backends/oracle/__init__.py +7 -0
  30. rakko/backends/oracle/client.py +136 -0
  31. rakko/backends/oracle/executor.py +27 -0
  32. rakko/backends/oracle/schema_generator.py +120 -0
  33. rakko/backends/psycopg/__init__.py +7 -0
  34. rakko/backends/psycopg/client.py +288 -0
  35. rakko/backends/psycopg/executor.py +14 -0
  36. rakko/backends/psycopg/schema_generator.py +17 -0
  37. rakko/backends/sqlite/__init__.py +14 -0
  38. rakko/backends/sqlite/client.py +299 -0
  39. rakko/backends/sqlite/executor.py +88 -0
  40. rakko/backends/sqlite/schema_generator.py +32 -0
  41. rakko/cli/__init__.py +13 -0
  42. rakko/cli/cli.py +878 -0
  43. rakko/cli/utils.py +181 -0
  44. rakko/config.py +315 -0
  45. rakko/connection.py +345 -0
  46. rakko/context.py +571 -0
  47. rakko/contrib/__init__.py +4 -0
  48. rakko/contrib/aiohttp/__init__.py +99 -0
  49. rakko/contrib/blacksheep/__init__.py +121 -0
  50. rakko/contrib/fastapi/__init__.py +301 -0
  51. rakko/contrib/mysql/__init__.py +4 -0
  52. rakko/contrib/mysql/fields.py +63 -0
  53. rakko/contrib/mysql/functions.py +19 -0
  54. rakko/contrib/mysql/indexes.py +28 -0
  55. rakko/contrib/mysql/json_functions.py +80 -0
  56. rakko/contrib/mysql/search.py +49 -0
  57. rakko/contrib/postgres/__init__.py +4 -0
  58. rakko/contrib/postgres/array_functions.py +35 -0
  59. rakko/contrib/postgres/fields.py +105 -0
  60. rakko/contrib/postgres/functions.py +43 -0
  61. rakko/contrib/postgres/indexes.py +33 -0
  62. rakko/contrib/postgres/json_functions.py +113 -0
  63. rakko/contrib/postgres/regex.py +31 -0
  64. rakko/contrib/postgres/search.py +483 -0
  65. rakko/contrib/pydantic/__init__.py +16 -0
  66. rakko/contrib/pydantic/base.py +147 -0
  67. rakko/contrib/pydantic/creator.py +681 -0
  68. rakko/contrib/pydantic/descriptions.py +207 -0
  69. rakko/contrib/pydantic/utils.py +38 -0
  70. rakko/contrib/pylint/__init__.py +158 -0
  71. rakko/contrib/quart/__init__.py +117 -0
  72. rakko/contrib/sanic/__init__.py +116 -0
  73. rakko/contrib/sqlite/__init__.py +4 -0
  74. rakko/contrib/sqlite/functions.py +16 -0
  75. rakko/contrib/sqlite/regex.py +46 -0
  76. rakko/contrib/starlette/__init__.py +180 -0
  77. rakko/contrib/test/__init__.py +336 -0
  78. rakko/contrib/test/condition.py +42 -0
  79. rakko/converters.py +160 -0
  80. rakko/exceptions.py +130 -0
  81. rakko/expressions.py +962 -0
  82. rakko/fields/__init__.py +111 -0
  83. rakko/fields/base.py +685 -0
  84. rakko/fields/data.py +1308 -0
  85. rakko/fields/db_defaults.py +105 -0
  86. rakko/fields/relational.py +703 -0
  87. rakko/filters.py +639 -0
  88. rakko/functions.py +142 -0
  89. rakko/indexes.py +165 -0
  90. rakko/log.py +8 -0
  91. rakko/manager.py +29 -0
  92. rakko/migrations/__init__.py +41 -0
  93. rakko/migrations/api/__init__.py +16 -0
  94. rakko/migrations/api/migrate.py +77 -0
  95. rakko/migrations/api/plan.py +76 -0
  96. rakko/migrations/api/sqlmigrate.py +69 -0
  97. rakko/migrations/autodetector.py +203 -0
  98. rakko/migrations/constraints.py +31 -0
  99. rakko/migrations/exceptions.py +10 -0
  100. rakko/migrations/executor.py +368 -0
  101. rakko/migrations/expressions.py +13 -0
  102. rakko/migrations/graph.py +158 -0
  103. rakko/migrations/loader.py +127 -0
  104. rakko/migrations/migration.py +189 -0
  105. rakko/migrations/operations.py +1288 -0
  106. rakko/migrations/recorder.py +104 -0
  107. rakko/migrations/schema_editor/__init__.py +25 -0
  108. rakko/migrations/schema_editor/asyncpg.py +11 -0
  109. rakko/migrations/schema_editor/base.py +945 -0
  110. rakko/migrations/schema_editor/base_postgres.py +171 -0
  111. rakko/migrations/schema_editor/data.py +18 -0
  112. rakko/migrations/schema_editor/mssql.py +339 -0
  113. rakko/migrations/schema_editor/mysql.py +408 -0
  114. rakko/migrations/schema_editor/oracle.py +259 -0
  115. rakko/migrations/schema_editor/psycopg.py +11 -0
  116. rakko/migrations/schema_editor/sqlite.py +424 -0
  117. rakko/migrations/schema_generator/__init__.py +5 -0
  118. rakko/migrations/schema_generator/operation_generator.py +163 -0
  119. rakko/migrations/schema_generator/state.py +224 -0
  120. rakko/migrations/schema_generator/state_apps.py +165 -0
  121. rakko/migrations/schema_generator/state_diff.py +419 -0
  122. rakko/migrations/writer.py +515 -0
  123. rakko/models.py +1854 -0
  124. rakko/py.typed +0 -0
  125. rakko/query_api.py +134 -0
  126. rakko/query_utils.py +238 -0
  127. rakko/queryset.py +2743 -0
  128. rakko/relations.py +85 -0
  129. rakko/router.py +55 -0
  130. rakko/schema_quoting.py +62 -0
  131. rakko/signals.py +73 -0
  132. rakko/timezone.py +173 -0
  133. rakko/transactions.py +69 -0
  134. rakko/utils.py +50 -0
  135. rakko/validators.py +412 -0
  136. rakko/warnings.py +22 -0
  137. rakko_orm-1.0.0a0.dist-info/METADATA +114 -0
  138. rakko_orm-1.0.0a0.dist-info/RECORD +143 -0
  139. rakko_orm-1.0.0a0.dist-info/WHEEL +4 -0
  140. rakko_orm-1.0.0a0.dist-info/entry_points.txt +5 -0
  141. rakko_orm-1.0.0a0.dist-info/licenses/CONTRIBUTORS.rst +86 -0
  142. rakko_orm-1.0.0a0.dist-info/licenses/LICENSE.txt +201 -0
  143. 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