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
rakko/__init__.py
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
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
|
+
import importlib
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import warnings
|
|
11
|
+
from collections.abc import Callable, Coroutine, Iterable
|
|
12
|
+
from types import ModuleType
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from rakko.apps import Apps
|
|
16
|
+
from rakko.backends.base.client import BaseDBAsyncClient
|
|
17
|
+
from rakko.backends.base.config_generator import expand_db_url
|
|
18
|
+
from rakko.config import TortoiseConfig
|
|
19
|
+
from rakko.connection import connections, get_connection, get_connections
|
|
20
|
+
from rakko.exceptions import ConfigurationError
|
|
21
|
+
from rakko.expressions import Criterion, Query
|
|
22
|
+
from rakko.fields import Field
|
|
23
|
+
from rakko.fields.relational import (
|
|
24
|
+
BackwardFKRelation,
|
|
25
|
+
BackwardOneToOneRelation,
|
|
26
|
+
ForeignKeyFieldInstance,
|
|
27
|
+
ManyToManyFieldInstance,
|
|
28
|
+
OneToOneFieldInstance,
|
|
29
|
+
)
|
|
30
|
+
from rakko.log import logger
|
|
31
|
+
from rakko.models import MetaOption, Model, ModelMeta
|
|
32
|
+
from rakko.relations import reverse_relation
|
|
33
|
+
from rakko.timezone import _reset_timezone_cache
|
|
34
|
+
from rakko.utils import generate_schema_for_client
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from rakko.context import TortoiseContext
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class classproperty:
|
|
41
|
+
"""
|
|
42
|
+
Descriptor that acts like @property but works on classes.
|
|
43
|
+
|
|
44
|
+
This allows `Tortoise.apps` and `Tortoise._inited` to dynamically
|
|
45
|
+
resolve to the current context's state without using a metaclass.
|
|
46
|
+
|
|
47
|
+
Note: This only supports getters, not setters. Internal code must
|
|
48
|
+
work with context directly for mutations.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, func: Callable[..., Any]) -> None:
|
|
52
|
+
self.func = func
|
|
53
|
+
|
|
54
|
+
def __get__(self, obj: Any, objtype: type | None = None) -> Any:
|
|
55
|
+
return self.func(objtype)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _TortoiseMeta(type):
|
|
59
|
+
"""Metaclass that prevents accidental shadowing of classproperty descriptors.
|
|
60
|
+
|
|
61
|
+
Direct assignment like ``Tortoise.apps = None`` silently replaces the
|
|
62
|
+
classproperty descriptor with a plain attribute, breaking dynamic context
|
|
63
|
+
resolution for the rest of the process. This metaclass intercepts such
|
|
64
|
+
assignments and raises a clear error.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __setattr__(cls, name: str, value: Any) -> None:
|
|
68
|
+
if name in cls.__dict__ and isinstance(cls.__dict__[name], classproperty):
|
|
69
|
+
raise AttributeError(
|
|
70
|
+
f"Cannot assign to Tortoise.{name} — it is a classproperty that resolves "
|
|
71
|
+
f"from the current TortoiseContext. Modify the context directly instead "
|
|
72
|
+
f"(e.g. ctx._apps = None)."
|
|
73
|
+
)
|
|
74
|
+
super().__setattr__(name, value)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Tortoise(metaclass=_TortoiseMeta):
|
|
78
|
+
"""
|
|
79
|
+
Tortoise ORM main interface.
|
|
80
|
+
|
|
81
|
+
Provides static methods for initialization and access to ORM state.
|
|
82
|
+
All state is managed by TortoiseContext instances.
|
|
83
|
+
|
|
84
|
+
NOTE: No class-level state except table_name_generator for backward compat.
|
|
85
|
+
All runtime state lives in TortoiseContext.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
# Class-level for backward compatibility; also stored in TortoiseContext
|
|
89
|
+
table_name_generator: Callable[[type[Model]], str] | None = None
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def _get_context(cls) -> TortoiseContext | None:
|
|
93
|
+
"""Get the current context from context var."""
|
|
94
|
+
from rakko.context import get_current_context
|
|
95
|
+
|
|
96
|
+
return get_current_context()
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _require_context(cls) -> TortoiseContext:
|
|
100
|
+
"""Get the current context, raising if none exists."""
|
|
101
|
+
ctx = cls._get_context()
|
|
102
|
+
if ctx is None:
|
|
103
|
+
raise ConfigurationError(
|
|
104
|
+
"Tortoise ORM is not initialized. Call Tortoise.init() first "
|
|
105
|
+
"or use 'async with TortoiseContext()' for explicit context management."
|
|
106
|
+
)
|
|
107
|
+
return ctx
|
|
108
|
+
|
|
109
|
+
# BACKWARD COMPATIBLE: Class properties (no metaclass needed!)
|
|
110
|
+
@classproperty
|
|
111
|
+
def apps(cls) -> Apps | None:
|
|
112
|
+
"""
|
|
113
|
+
Get the Apps registry from current context.
|
|
114
|
+
|
|
115
|
+
Returns None if no context is active.
|
|
116
|
+
"""
|
|
117
|
+
ctx = cls._get_context()
|
|
118
|
+
return ctx.apps if ctx else None
|
|
119
|
+
|
|
120
|
+
@classproperty
|
|
121
|
+
def _inited(cls) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Check if Tortoise is initialized.
|
|
124
|
+
|
|
125
|
+
Returns False if no context is active.
|
|
126
|
+
"""
|
|
127
|
+
ctx = cls._get_context()
|
|
128
|
+
return ctx.inited if ctx else False
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def is_inited(cls) -> bool:
|
|
132
|
+
"""Check if Tortoise is initialized."""
|
|
133
|
+
ctx = cls._get_context()
|
|
134
|
+
return ctx.inited if ctx else False
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_connection(cls, connection_name: str) -> BaseDBAsyncClient:
|
|
138
|
+
"""
|
|
139
|
+
Returns the connection by name.
|
|
140
|
+
|
|
141
|
+
:raises ConfigurationError: If connection name does not exist.
|
|
142
|
+
|
|
143
|
+
.. warning::
|
|
144
|
+
This is deprecated and will be removed in a future release. Please use
|
|
145
|
+
:meth:`get_connection<tortoise.connection.get_connection>` instead.
|
|
146
|
+
"""
|
|
147
|
+
return get_connection(connection_name)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def describe_model(
|
|
151
|
+
cls, model: type[Model], serializable: bool = True
|
|
152
|
+
) -> dict[str, Any]: # pragma: nocoverage
|
|
153
|
+
"""
|
|
154
|
+
Describes the given list of models or ALL registered models.
|
|
155
|
+
|
|
156
|
+
:param model:
|
|
157
|
+
The Model to describe
|
|
158
|
+
|
|
159
|
+
:param serializable:
|
|
160
|
+
``False`` if you want raw python objects,
|
|
161
|
+
``True`` for JSON-serializable data. (Defaults to ``True``)
|
|
162
|
+
|
|
163
|
+
See :meth:`tortoise.models.Model.describe`
|
|
164
|
+
|
|
165
|
+
.. warning::
|
|
166
|
+
This is deprecated, please use :meth:`tortoise.models.Model.describe` instead
|
|
167
|
+
"""
|
|
168
|
+
warnings.warn(
|
|
169
|
+
"Tortoise.describe_model(<MODEL>) is deprecated, please use <MODEL>.describe() instead",
|
|
170
|
+
DeprecationWarning,
|
|
171
|
+
stacklevel=2,
|
|
172
|
+
)
|
|
173
|
+
return model.describe(serializable=serializable)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def describe_models(
|
|
177
|
+
cls, models: list[type[Model]] | None = None, serializable: bool = True
|
|
178
|
+
) -> dict[str, dict[str, Any]]:
|
|
179
|
+
"""
|
|
180
|
+
Describes the given list of models or ALL registered models.
|
|
181
|
+
|
|
182
|
+
:param models:
|
|
183
|
+
List of models to describe, if not provided then describes ALL registered models
|
|
184
|
+
|
|
185
|
+
:param serializable:
|
|
186
|
+
``False`` if you want raw python objects,
|
|
187
|
+
``True`` for JSON-serializable data. (Defaults to ``True``)
|
|
188
|
+
|
|
189
|
+
:return:
|
|
190
|
+
A dictionary containing the model qualifier as key,
|
|
191
|
+
and the same output as ``describe_model(...)`` as value:
|
|
192
|
+
|
|
193
|
+
.. code-block:: python3
|
|
194
|
+
|
|
195
|
+
{
|
|
196
|
+
"models.User": {...},
|
|
197
|
+
"models.Permission": {...}
|
|
198
|
+
}
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
if not models:
|
|
202
|
+
models = []
|
|
203
|
+
if cls.apps:
|
|
204
|
+
models = list(cls.apps.get_models_iterable())
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
f"{model._meta.app}.{model.__name__}": model.describe(serializable) for model in models
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def _init_relations(cls) -> None:
|
|
212
|
+
if not cls.apps:
|
|
213
|
+
return
|
|
214
|
+
cls.apps._init_relations()
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def init_models(
|
|
218
|
+
cls,
|
|
219
|
+
models_paths: Iterable[ModuleType | str],
|
|
220
|
+
app_label: str,
|
|
221
|
+
_init_relations: bool = True,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Early initialisation of Tortoise ORM Models.
|
|
225
|
+
|
|
226
|
+
Initialise the relationships between Models.
|
|
227
|
+
This does not initialise any database connection.
|
|
228
|
+
|
|
229
|
+
:param models_paths: Models paths to initialise
|
|
230
|
+
:param app_label: The app label, e.g. 'models'
|
|
231
|
+
:param _init_relations: Whether to init relations or not
|
|
232
|
+
|
|
233
|
+
:raises ConfigurationError: If models are invalid.
|
|
234
|
+
"""
|
|
235
|
+
cls.init_app(app_label, models_paths, _init_relations=_init_relations)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def init_app(
|
|
239
|
+
cls,
|
|
240
|
+
label: str,
|
|
241
|
+
model_paths: Iterable[ModuleType | str],
|
|
242
|
+
_init_relations: bool = True,
|
|
243
|
+
) -> dict[str, type[Model]]:
|
|
244
|
+
"""
|
|
245
|
+
Early initialization of Tortoise ORM Models for a single app.
|
|
246
|
+
|
|
247
|
+
:param label: The app label, e.g. 'models'
|
|
248
|
+
:param model_paths: Models paths to initialize
|
|
249
|
+
:param _init_relations: Whether to init relations or not
|
|
250
|
+
"""
|
|
251
|
+
from rakko.context import TortoiseContext, get_current_context
|
|
252
|
+
|
|
253
|
+
# Get or create context
|
|
254
|
+
ctx = get_current_context()
|
|
255
|
+
if ctx is None:
|
|
256
|
+
ctx = TortoiseContext()
|
|
257
|
+
ctx.__enter__()
|
|
258
|
+
|
|
259
|
+
# Create Apps if not exists
|
|
260
|
+
if ctx._apps is None:
|
|
261
|
+
ctx._apps = Apps({}, ctx.connections, cls.table_name_generator)
|
|
262
|
+
ctx._apps._table_name_generator = cls.table_name_generator
|
|
263
|
+
return ctx._apps.init_app(label, model_paths, _init_relations=_init_relations)
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def _init_apps(
|
|
267
|
+
cls, apps_config: dict[str, dict[str, Any]], *, validate_connections: bool = True
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Internal: Initialize Apps registry on current context."""
|
|
270
|
+
ctx = cls._require_context()
|
|
271
|
+
ctx._apps = Apps(
|
|
272
|
+
apps_config,
|
|
273
|
+
ctx.connections,
|
|
274
|
+
cls.table_name_generator,
|
|
275
|
+
validate_connections=validate_connections,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
@classmethod
|
|
279
|
+
def _build_initial_querysets(cls) -> None:
|
|
280
|
+
if cls.apps:
|
|
281
|
+
cls.apps._build_initial_querysets()
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
async def init(
|
|
285
|
+
cls,
|
|
286
|
+
config: dict[str, Any] | TortoiseConfig | None = None,
|
|
287
|
+
config_file: str | None = None,
|
|
288
|
+
_create_db: bool = False,
|
|
289
|
+
db_url: str | None = None,
|
|
290
|
+
modules: dict[str, Iterable[str | ModuleType]] | None = None,
|
|
291
|
+
use_tz: bool = True,
|
|
292
|
+
timezone: str = "UTC",
|
|
293
|
+
routers: list[str | type] | None = None,
|
|
294
|
+
table_name_generator: Callable[[type[Model]], str] | None = None,
|
|
295
|
+
init_connections: bool = True,
|
|
296
|
+
_enable_global_fallback: bool = False,
|
|
297
|
+
) -> TortoiseContext:
|
|
298
|
+
"""
|
|
299
|
+
Sets up Tortoise-ORM: loads apps and models, configures database connections but does not
|
|
300
|
+
connect to the database yet. The actual connection or connection pool is established
|
|
301
|
+
lazily on first query execution.
|
|
302
|
+
|
|
303
|
+
You can configure using only one of ``config``, ``config_file``
|
|
304
|
+
and ``(db_url, modules)``.
|
|
305
|
+
|
|
306
|
+
:param config:
|
|
307
|
+
Dict containing config or ``TortoiseConfig``:
|
|
308
|
+
|
|
309
|
+
.. admonition:: Example
|
|
310
|
+
|
|
311
|
+
.. code-block:: python3
|
|
312
|
+
|
|
313
|
+
{
|
|
314
|
+
'connections': {
|
|
315
|
+
# Dict format for connection
|
|
316
|
+
'default': {
|
|
317
|
+
'engine': 'tortoise.backends.asyncpg',
|
|
318
|
+
'credentials': {
|
|
319
|
+
'host': 'localhost',
|
|
320
|
+
'port': '5432',
|
|
321
|
+
'user': 'tortoise',
|
|
322
|
+
'password': 'qwerty123',
|
|
323
|
+
'database': 'test',
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
# Using a DB_URL string
|
|
327
|
+
'default': 'postgres://postgres:qwerty123@localhost:5432/test'
|
|
328
|
+
},
|
|
329
|
+
'apps': {
|
|
330
|
+
'my_app': {
|
|
331
|
+
'models': ['__main__'],
|
|
332
|
+
# If no default_connection specified, defaults to 'default'
|
|
333
|
+
'default_connection': 'default',
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
'routers': ['path.router1', 'path.router2'],
|
|
337
|
+
'use_tz': False,
|
|
338
|
+
'timezone': 'UTC'
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
:param config_file:
|
|
342
|
+
Path to .json or .yml (if PyYAML installed) file containing config with
|
|
343
|
+
same format as above.
|
|
344
|
+
:param db_url:
|
|
345
|
+
Use a DB_URL string. See :ref:`db_url`
|
|
346
|
+
:param modules:
|
|
347
|
+
Dictionary of ``key``: [``list_of_modules``] that defined "apps" and modules that
|
|
348
|
+
should be discovered for models.
|
|
349
|
+
:param _create_db:
|
|
350
|
+
If ``True`` tries to create database for specified connections,
|
|
351
|
+
could be used for testing purposes.
|
|
352
|
+
:param use_tz:
|
|
353
|
+
A boolean that specifies if datetime will be timezone-aware by default or not.
|
|
354
|
+
:param timezone:
|
|
355
|
+
Timezone to use, default is UTC.
|
|
356
|
+
:param routers:
|
|
357
|
+
A list of db routers str path or module.
|
|
358
|
+
:param table_name_generator:
|
|
359
|
+
A callable that generates table names. The model class will be passed as its argument.
|
|
360
|
+
If not provided, Tortoise will use the lowercase model name as the table name.
|
|
361
|
+
Example: ``lambda cls: f"prefix_{cls.__name__.lower()}"``
|
|
362
|
+
:param init_connections:
|
|
363
|
+
When ``False``, skips initializing connection clients while still loading apps
|
|
364
|
+
and validating connection names against the config.
|
|
365
|
+
:param _enable_global_fallback:
|
|
366
|
+
When ``True``, stores the context as a global fallback for cross-task access.
|
|
367
|
+
This is used by RegisterTortoise (FastAPI) where asgi-lifespan runs lifespan
|
|
368
|
+
in a background task. Default is ``False`` for pure context isolation.
|
|
369
|
+
|
|
370
|
+
:raises ConfigurationError: For any configuration error
|
|
371
|
+
|
|
372
|
+
:returns: The TortoiseContext that was initialized. For multiple asyncio.run()
|
|
373
|
+
calls, capture this and use 'with ctx:' to maintain context.
|
|
374
|
+
"""
|
|
375
|
+
from rakko.context import TortoiseContext, _current_context
|
|
376
|
+
|
|
377
|
+
# Get or create context - only use contextvar, not global fallback.
|
|
378
|
+
# Global fallback is for reading (queries), not for initialization.
|
|
379
|
+
# This allows multiple apps to initialize independently even if one
|
|
380
|
+
# has global fallback enabled.
|
|
381
|
+
ctx = _current_context.get()
|
|
382
|
+
if ctx is None:
|
|
383
|
+
ctx = TortoiseContext()
|
|
384
|
+
ctx.__enter__()
|
|
385
|
+
elif ctx.inited:
|
|
386
|
+
# Re-initializing existing context
|
|
387
|
+
await ctx.close_connections()
|
|
388
|
+
|
|
389
|
+
# Validate config source - must provide exactly one
|
|
390
|
+
if int(bool(config) + bool(config_file) + bool(db_url)) != 1:
|
|
391
|
+
raise ConfigurationError(
|
|
392
|
+
'You should init either from "config", "config_file" or "db_url"'
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Normalize config: handle config_file case
|
|
396
|
+
normalized_config: dict[str, Any] | TortoiseConfig | None = config
|
|
397
|
+
if config_file:
|
|
398
|
+
normalized_config = TortoiseConfig.from_config_file(config_file)
|
|
399
|
+
|
|
400
|
+
# Debug logging
|
|
401
|
+
if logger.isEnabledFor(logging.DEBUG) and normalized_config is not None:
|
|
402
|
+
if isinstance(normalized_config, TortoiseConfig):
|
|
403
|
+
config_dict = normalized_config.to_dict()
|
|
404
|
+
else:
|
|
405
|
+
config_dict = normalized_config
|
|
406
|
+
connections_config = config_dict.get("connections", {})
|
|
407
|
+
apps_config = config_dict.get("apps", {})
|
|
408
|
+
str_connection_config = cls.star_password(connections_config)
|
|
409
|
+
logger.debug(
|
|
410
|
+
"Tortoise-ORM startup\n connections: %s\n apps: %s",
|
|
411
|
+
str_connection_config,
|
|
412
|
+
str(apps_config),
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Store table_name_generator at class level for backward compatibility
|
|
416
|
+
cls.table_name_generator = table_name_generator
|
|
417
|
+
|
|
418
|
+
# Delegate to context init
|
|
419
|
+
await ctx.init(
|
|
420
|
+
config=normalized_config,
|
|
421
|
+
db_url=db_url,
|
|
422
|
+
modules=modules,
|
|
423
|
+
_create_db=_create_db,
|
|
424
|
+
use_tz=use_tz,
|
|
425
|
+
timezone=timezone,
|
|
426
|
+
routers=routers,
|
|
427
|
+
table_name_generator=table_name_generator,
|
|
428
|
+
init_connections=init_connections,
|
|
429
|
+
_enable_global_fallback=_enable_global_fallback,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return ctx
|
|
433
|
+
|
|
434
|
+
@staticmethod
|
|
435
|
+
def star_password(connections_config: dict) -> str:
|
|
436
|
+
# Mask passwords to hide sensitive information in logs output
|
|
437
|
+
passwords = []
|
|
438
|
+
for _name, info in connections_config.items():
|
|
439
|
+
if isinstance(info, str):
|
|
440
|
+
info = expand_db_url(info)
|
|
441
|
+
if (password := info.get("credentials", {}).get("password")) and isinstance(
|
|
442
|
+
password, str
|
|
443
|
+
):
|
|
444
|
+
passwords.append(password)
|
|
445
|
+
|
|
446
|
+
str_connection_config = str(connections_config)
|
|
447
|
+
for password in passwords:
|
|
448
|
+
str_connection_config = str_connection_config.replace(
|
|
449
|
+
password,
|
|
450
|
+
# Show one third of the password at beginning (may be better for debugging purposes)
|
|
451
|
+
f"{password[0 : len(password) // 3]}***",
|
|
452
|
+
)
|
|
453
|
+
return str_connection_config
|
|
454
|
+
|
|
455
|
+
@classmethod
|
|
456
|
+
def _init_routers(cls, routers: list[str | type] | None = None) -> None:
|
|
457
|
+
from rakko.router import router
|
|
458
|
+
|
|
459
|
+
routers = routers or []
|
|
460
|
+
router_cls = []
|
|
461
|
+
for r in routers:
|
|
462
|
+
if isinstance(r, str):
|
|
463
|
+
try:
|
|
464
|
+
module_name, class_name = r.rsplit(".", 1)
|
|
465
|
+
router_cls.append(getattr(importlib.import_module(module_name), class_name))
|
|
466
|
+
except Exception as e:
|
|
467
|
+
raise ConfigurationError(f"Can't import router from `{r}`") from e
|
|
468
|
+
elif isinstance(r, type):
|
|
469
|
+
router_cls.append(r)
|
|
470
|
+
else:
|
|
471
|
+
raise ConfigurationError("Router must be either str or type")
|
|
472
|
+
router.init_routers(router_cls)
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
async def close_connections(cls) -> None:
|
|
476
|
+
"""
|
|
477
|
+
Close all connections cleanly.
|
|
478
|
+
|
|
479
|
+
It is required for this to be called on exit,
|
|
480
|
+
else your event loop may never complete
|
|
481
|
+
as it is waiting for the connections to die.
|
|
482
|
+
"""
|
|
483
|
+
ctx = cls._get_context()
|
|
484
|
+
if ctx is not None:
|
|
485
|
+
await ctx.close_connections()
|
|
486
|
+
logger.info("Tortoise-ORM shutdown")
|
|
487
|
+
|
|
488
|
+
@classmethod
|
|
489
|
+
async def _reset_apps(cls) -> None:
|
|
490
|
+
"""Internal: Reset Apps registry on current context."""
|
|
491
|
+
ctx = cls._get_context()
|
|
492
|
+
if ctx is None or ctx._apps is None:
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
for model in ctx._apps.get_models_iterable():
|
|
496
|
+
if isinstance(model, ModelMeta):
|
|
497
|
+
model._meta.default_connection = None
|
|
498
|
+
ctx._apps.clear()
|
|
499
|
+
ctx._apps = None
|
|
500
|
+
|
|
501
|
+
@classmethod
|
|
502
|
+
async def generate_schemas(cls, safe: bool = True) -> None:
|
|
503
|
+
"""
|
|
504
|
+
Generate schemas according to models provided to ``.init()`` method.
|
|
505
|
+
Will fail if schemas already exists, so it's not recommended to be used as part
|
|
506
|
+
of application workflow
|
|
507
|
+
|
|
508
|
+
:param safe: When set to true, creates the table only when it does not already exist.
|
|
509
|
+
|
|
510
|
+
:raises ConfigurationError: When ``.init()`` has not been called.
|
|
511
|
+
"""
|
|
512
|
+
if not cls._inited:
|
|
513
|
+
raise ConfigurationError("You have to call .init() first before generating schemas")
|
|
514
|
+
for connection in get_connections().all():
|
|
515
|
+
await generate_schema_for_client(connection, safe)
|
|
516
|
+
|
|
517
|
+
@classmethod
|
|
518
|
+
async def _drop_databases(cls) -> None:
|
|
519
|
+
"""
|
|
520
|
+
Tries to drop all databases provided in config passed to ``.init()`` method.
|
|
521
|
+
Normally should be used only for testing purposes.
|
|
522
|
+
|
|
523
|
+
:raises ConfigurationError: When ``.init()`` has not been called.
|
|
524
|
+
"""
|
|
525
|
+
if not cls._inited:
|
|
526
|
+
raise ConfigurationError("You have to call .init() first before deleting schemas")
|
|
527
|
+
# this closes any existing connections/pool if any and clears
|
|
528
|
+
# the storage
|
|
529
|
+
conn_handler = get_connections()
|
|
530
|
+
await conn_handler.close_all(discard=False)
|
|
531
|
+
for conn in conn_handler.all():
|
|
532
|
+
await conn.db_delete()
|
|
533
|
+
conn_handler.discard(conn.connection_name)
|
|
534
|
+
|
|
535
|
+
await cls._reset_apps()
|
|
536
|
+
|
|
537
|
+
@classmethod
|
|
538
|
+
def _init_timezone(cls, use_tz: bool, timezone: str) -> None:
|
|
539
|
+
os.environ["USE_TZ"] = str(use_tz)
|
|
540
|
+
os.environ["TIMEZONE"] = timezone
|
|
541
|
+
_reset_timezone_cache()
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def run_async(coro: Coroutine, *, verbose: bool = False) -> None:
|
|
545
|
+
"""
|
|
546
|
+
Simple async runner that cleans up DB connections on exit.
|
|
547
|
+
This is meant for simple scripts.
|
|
548
|
+
|
|
549
|
+
:param coro: The coroutine to run.
|
|
550
|
+
:param verbose: If ``True``, set the ``tortoise`` logger to ``DEBUG``
|
|
551
|
+
so that generated SQL and runtime details are printed. If no
|
|
552
|
+
logging handler is configured, a basic one is added to the root
|
|
553
|
+
logger. Other loggers are left untouched.
|
|
554
|
+
|
|
555
|
+
Usage::
|
|
556
|
+
|
|
557
|
+
from tortoise import Tortoise, run_async
|
|
558
|
+
|
|
559
|
+
async def do_stuff():
|
|
560
|
+
await Tortoise.init(
|
|
561
|
+
db_url='sqlite://db.sqlite3',
|
|
562
|
+
models={'models': ['app.models']}
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
...
|
|
566
|
+
|
|
567
|
+
run_async(do_stuff())
|
|
568
|
+
"""
|
|
569
|
+
# Imports `anyio` inside function to improve the tortoise module startup performance
|
|
570
|
+
# (it's only used by this convenience helper).
|
|
571
|
+
from anyio import from_thread
|
|
572
|
+
|
|
573
|
+
from rakko.context import get_current_context
|
|
574
|
+
|
|
575
|
+
if verbose:
|
|
576
|
+
logging.basicConfig(
|
|
577
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
578
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
579
|
+
)
|
|
580
|
+
logger.setLevel(logging.DEBUG)
|
|
581
|
+
|
|
582
|
+
async def main() -> None:
|
|
583
|
+
try:
|
|
584
|
+
await coro
|
|
585
|
+
finally:
|
|
586
|
+
ctx = get_current_context()
|
|
587
|
+
if ctx is not None:
|
|
588
|
+
await ctx.connections.close_all(discard=True)
|
|
589
|
+
|
|
590
|
+
with from_thread.start_blocking_portal() as portal:
|
|
591
|
+
portal.call(main)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
__version__ = "1.0.0a0"
|
|
595
|
+
|
|
596
|
+
__all__ = [
|
|
597
|
+
"Criterion",
|
|
598
|
+
"BackwardFKRelation",
|
|
599
|
+
"BackwardOneToOneRelation",
|
|
600
|
+
"Model",
|
|
601
|
+
"MetaOption",
|
|
602
|
+
"Field",
|
|
603
|
+
"Query",
|
|
604
|
+
"reverse_relation",
|
|
605
|
+
"ForeignKeyFieldInstance",
|
|
606
|
+
"ManyToManyFieldInstance",
|
|
607
|
+
"OneToOneFieldInstance",
|
|
608
|
+
"Tortoise",
|
|
609
|
+
"BaseDBAsyncClient",
|
|
610
|
+
"TortoiseConfig",
|
|
611
|
+
"__version__",
|
|
612
|
+
"connections",
|
|
613
|
+
]
|
rakko/__main__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
"""Module entry point for `python -m tortoise`."""
|
|
6
|
+
|
|
7
|
+
from rakko.cli import main
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
main()
|