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
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()