plain.models 0.49.2__py3-none-any.whl → 0.51.0__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.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +13 -4
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
plain/models/cli.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import subprocess
|
3
5
|
import sys
|
4
6
|
import time
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
5
8
|
|
6
9
|
import click
|
7
10
|
|
@@ -13,7 +16,8 @@ from plain.utils.text import Truncator
|
|
13
16
|
from . import migrations
|
14
17
|
from .backups.cli import cli as backups_cli
|
15
18
|
from .backups.cli import create_backup
|
16
|
-
from .db import OperationalError
|
19
|
+
from .db import OperationalError
|
20
|
+
from .db import db_connection as _db_connection
|
17
21
|
from .migrations.autodetector import MigrationAutodetector
|
18
22
|
from .migrations.executor import MigrationExecutor
|
19
23
|
from .migrations.loader import AmbiguityError, MigrationLoader
|
@@ -28,10 +32,17 @@ from .migrations.state import ModelState, ProjectState
|
|
28
32
|
from .migrations.writer import MigrationWriter
|
29
33
|
from .registry import models_registry
|
30
34
|
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from .backends.base.base import BaseDatabaseWrapper
|
37
|
+
|
38
|
+
db_connection = cast("BaseDatabaseWrapper", _db_connection)
|
39
|
+
else:
|
40
|
+
db_connection = _db_connection
|
41
|
+
|
31
42
|
|
32
43
|
@register_cli("models")
|
33
44
|
@click.group()
|
34
|
-
def cli():
|
45
|
+
def cli() -> None:
|
35
46
|
pass
|
36
47
|
|
37
48
|
|
@@ -40,10 +51,10 @@ cli.add_command(backups_cli)
|
|
40
51
|
|
41
52
|
@cli.command()
|
42
53
|
@click.argument("parameters", nargs=-1)
|
43
|
-
def db_shell(parameters):
|
54
|
+
def db_shell(parameters: tuple[str, ...]) -> None:
|
44
55
|
"""Runs the command-line client for specified database, or the default database if none is provided."""
|
45
56
|
try:
|
46
|
-
db_connection.client.runshell(parameters)
|
57
|
+
db_connection.client.runshell(list(parameters))
|
47
58
|
except FileNotFoundError:
|
48
59
|
# Note that we're assuming the FileNotFoundError relates to the
|
49
60
|
# command missing. It could be raised for some other reason, in
|
@@ -68,7 +79,7 @@ def db_shell(parameters):
|
|
68
79
|
|
69
80
|
|
70
81
|
@cli.command()
|
71
|
-
def db_wait():
|
82
|
+
def db_wait() -> None:
|
72
83
|
"""Wait for the database to be ready"""
|
73
84
|
attempts = 0
|
74
85
|
while True:
|
@@ -100,26 +111,26 @@ def db_wait():
|
|
100
111
|
is_flag=True,
|
101
112
|
help="Only show models from packages that start with 'app'.",
|
102
113
|
)
|
103
|
-
def list_models(package_labels, app_only):
|
114
|
+
def list_models(package_labels: tuple[str, ...], app_only: bool) -> None:
|
104
115
|
"""List installed models."""
|
105
116
|
|
106
117
|
packages = set(package_labels)
|
107
118
|
|
108
119
|
for model in sorted(
|
109
120
|
models_registry.get_models(),
|
110
|
-
key=lambda m: (m.
|
121
|
+
key=lambda m: (m.model_options.package_label, m.model_options.model_name),
|
111
122
|
):
|
112
|
-
pkg = model.
|
123
|
+
pkg = model.model_options.package_label
|
113
124
|
pkg_name = packages_registry.get_package_config(pkg).name
|
114
125
|
if app_only and not pkg_name.startswith("app"):
|
115
126
|
continue
|
116
127
|
if packages and pkg not in packages:
|
117
128
|
continue
|
118
|
-
fields = ", ".join(f.name for f in model.
|
129
|
+
fields = ", ".join(f.name for f in model._model_meta.get_fields())
|
119
130
|
click.echo(
|
120
131
|
f"{click.style(pkg, fg='cyan')}.{click.style(model.__name__, fg='blue')}"
|
121
132
|
)
|
122
|
-
click.echo(f" table: {model.
|
133
|
+
click.echo(f" table: {model.model_options.db_table}")
|
123
134
|
click.echo(f" fields: {fields}")
|
124
135
|
click.echo(f" package: {pkg_name}\n")
|
125
136
|
|
@@ -153,19 +164,30 @@ def list_models(package_labels, app_only):
|
|
153
164
|
default=1,
|
154
165
|
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
|
155
166
|
)
|
156
|
-
def makemigrations(
|
167
|
+
def makemigrations(
|
168
|
+
package_labels: tuple[str, ...],
|
169
|
+
dry_run: bool,
|
170
|
+
empty: bool,
|
171
|
+
no_input: bool,
|
172
|
+
name: str | None,
|
173
|
+
check: bool,
|
174
|
+
verbosity: int,
|
175
|
+
) -> None:
|
157
176
|
"""Creates new migration(s) for packages."""
|
158
177
|
|
159
|
-
written_files = []
|
178
|
+
written_files: list[str] = []
|
160
179
|
interactive = not no_input
|
161
180
|
migration_name = name
|
162
181
|
check_changes = check
|
163
182
|
|
164
|
-
def log(msg, level=1):
|
183
|
+
def log(msg: str, level: int = 1) -> None:
|
165
184
|
if verbosity >= level:
|
166
185
|
click.echo(msg)
|
167
186
|
|
168
|
-
def write_migration_files(
|
187
|
+
def write_migration_files(
|
188
|
+
changes: dict[str, list[Migration]],
|
189
|
+
update_previous_migration_paths: dict[str, str] | None = None,
|
190
|
+
) -> None:
|
169
191
|
"""Take a changes dict and write them out as migration files."""
|
170
192
|
directory_created = {}
|
171
193
|
for package_label, package_migrations in changes.items():
|
@@ -221,9 +243,9 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
221
243
|
log(writer.as_string(), level=3)
|
222
244
|
|
223
245
|
# Validate package labels
|
224
|
-
|
246
|
+
package_labels_set = set(package_labels)
|
225
247
|
has_bad_labels = False
|
226
|
-
for package_label in
|
248
|
+
for package_label in package_labels_set:
|
227
249
|
try:
|
228
250
|
packages_registry.get_package_config(package_label)
|
229
251
|
except LookupError as err:
|
@@ -241,11 +263,11 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
241
263
|
|
242
264
|
# Check for conflicts
|
243
265
|
conflicts = loader.detect_conflicts()
|
244
|
-
if
|
266
|
+
if package_labels_set:
|
245
267
|
conflicts = {
|
246
268
|
package_label: conflict
|
247
269
|
for package_label, conflict in conflicts.items()
|
248
|
-
if package_label in
|
270
|
+
if package_label in package_labels_set
|
249
271
|
}
|
250
272
|
|
251
273
|
if conflicts:
|
@@ -261,12 +283,12 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
261
283
|
# Set up questioner
|
262
284
|
if interactive:
|
263
285
|
questioner = InteractiveMigrationQuestioner(
|
264
|
-
specified_packages=
|
286
|
+
specified_packages=package_labels_set,
|
265
287
|
dry_run=dry_run,
|
266
288
|
)
|
267
289
|
else:
|
268
290
|
questioner = NonInteractiveMigrationQuestioner(
|
269
|
-
specified_packages=
|
291
|
+
specified_packages=package_labels_set,
|
270
292
|
dry_run=dry_run,
|
271
293
|
verbosity=verbosity,
|
272
294
|
)
|
@@ -280,12 +302,12 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
280
302
|
|
281
303
|
# Handle empty migrations if requested
|
282
304
|
if empty:
|
283
|
-
if not
|
305
|
+
if not package_labels_set:
|
284
306
|
raise click.ClickException(
|
285
307
|
"You must supply at least one package label when using --empty."
|
286
308
|
)
|
287
309
|
changes = {
|
288
|
-
package: [Migration("custom", package)] for package in
|
310
|
+
package: [Migration("custom", package)] for package in package_labels_set
|
289
311
|
}
|
290
312
|
changes = autodetector.arrange_for_graph(
|
291
313
|
changes=changes,
|
@@ -298,17 +320,17 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
298
320
|
# Detect changes
|
299
321
|
changes = autodetector.changes(
|
300
322
|
graph=loader.graph,
|
301
|
-
trim_to_packages=
|
302
|
-
convert_packages=
|
323
|
+
trim_to_packages=package_labels_set or None,
|
324
|
+
convert_packages=package_labels_set or None,
|
303
325
|
migration_name=migration_name,
|
304
326
|
)
|
305
327
|
|
306
328
|
if not changes:
|
307
329
|
log(
|
308
330
|
"No changes detected"
|
309
|
-
if not
|
310
|
-
else f"No changes detected in {'package' if len(
|
311
|
-
f"'{', '.join(
|
331
|
+
if not package_labels_set
|
332
|
+
else f"No changes detected in {'package' if len(package_labels_set) == 1 else 'packages'} "
|
333
|
+
f"'{', '.join(package_labels_set)}'",
|
312
334
|
level=1,
|
313
335
|
)
|
314
336
|
else:
|
@@ -368,20 +390,22 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
|
|
368
390
|
help="Run migrations in a single transaction (auto-detected by default)",
|
369
391
|
)
|
370
392
|
def migrate(
|
371
|
-
package_label,
|
372
|
-
migration_name,
|
373
|
-
fake,
|
374
|
-
plan,
|
375
|
-
check_unapplied,
|
376
|
-
backup,
|
377
|
-
prune,
|
378
|
-
no_input,
|
379
|
-
verbosity,
|
380
|
-
atomic_batch,
|
381
|
-
):
|
393
|
+
package_label: str | None,
|
394
|
+
migration_name: str | None,
|
395
|
+
fake: bool,
|
396
|
+
plan: bool,
|
397
|
+
check_unapplied: bool,
|
398
|
+
backup: bool | None,
|
399
|
+
prune: bool,
|
400
|
+
no_input: bool,
|
401
|
+
verbosity: int,
|
402
|
+
atomic_batch: bool | None,
|
403
|
+
) -> None:
|
382
404
|
"""Updates database schema. Manages both packages with migrations and those without."""
|
383
405
|
|
384
|
-
def migration_progress_callback(
|
406
|
+
def migration_progress_callback(
|
407
|
+
action: str, migration: Migration | None = None, fake: bool = False
|
408
|
+
) -> None:
|
385
409
|
if verbosity >= 1:
|
386
410
|
if action == "apply_start":
|
387
411
|
click.echo(f" Applying {migration}...", nl=False)
|
@@ -395,7 +419,7 @@ def migrate(
|
|
395
419
|
elif action == "render_success":
|
396
420
|
click.echo(click.style(" DONE", fg="green"))
|
397
421
|
|
398
|
-
def describe_operation(operation):
|
422
|
+
def describe_operation(operation: Any) -> tuple[str, bool]:
|
399
423
|
"""Return a string that describes a migration operation for --plan."""
|
400
424
|
prefix = ""
|
401
425
|
is_error = False
|
@@ -438,6 +462,7 @@ def migrate(
|
|
438
462
|
|
439
463
|
# If they supplied command line arguments, work out what they mean.
|
440
464
|
target_package_labels_only = True
|
465
|
+
targets: list[tuple[str, str]]
|
441
466
|
if package_label:
|
442
467
|
try:
|
443
468
|
packages_registry.get_package_config(package_label)
|
@@ -463,13 +488,13 @@ def migrate(
|
|
463
488
|
raise click.ClickException(
|
464
489
|
f"Cannot find a migration matching '{migration_name}' from package '{package_label}'."
|
465
490
|
)
|
466
|
-
target = (package_label, migration.name)
|
491
|
+
target: tuple[str, str] = (package_label, migration.name)
|
467
492
|
if (
|
468
493
|
target not in executor.loader.graph.nodes
|
469
494
|
and target in executor.loader.replacements
|
470
495
|
):
|
471
496
|
incomplete_migration = executor.loader.replacements[target]
|
472
|
-
target = incomplete_migration.replaces[-1]
|
497
|
+
target = incomplete_migration.replaces[-1] # type: ignore[assignment]
|
473
498
|
targets = [target]
|
474
499
|
target_package_labels_only = False
|
475
500
|
elif package_label:
|
@@ -477,7 +502,7 @@ def migrate(
|
|
477
502
|
key for key in executor.loader.graph.leaf_nodes() if key[0] == package_label
|
478
503
|
]
|
479
504
|
else:
|
480
|
-
targets = executor.loader.graph.leaf_nodes()
|
505
|
+
targets = list(executor.loader.graph.leaf_nodes())
|
481
506
|
|
482
507
|
if prune:
|
483
508
|
if not package_label:
|
@@ -486,8 +511,8 @@ def migrate(
|
|
486
511
|
)
|
487
512
|
if verbosity > 0:
|
488
513
|
click.secho("Pruning migrations:", fg="cyan")
|
489
|
-
to_prune = set(executor.loader.applied_migrations) - set(
|
490
|
-
executor.loader.disk_migrations
|
514
|
+
to_prune = set(executor.loader.applied_migrations) - set( # type: ignore[arg-type]
|
515
|
+
executor.loader.disk_migrations # type: ignore[arg-type]
|
491
516
|
)
|
492
517
|
squashed_migrations_with_deleted_replaced_migrations = [
|
493
518
|
migration_key
|
@@ -729,10 +754,12 @@ def migrate(
|
|
729
754
|
default=1,
|
730
755
|
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
|
731
756
|
)
|
732
|
-
def show_migrations(
|
757
|
+
def show_migrations(
|
758
|
+
package_labels: tuple[str, ...], format: str, verbosity: int
|
759
|
+
) -> None:
|
733
760
|
"""Shows all available migrations for the current project"""
|
734
761
|
|
735
|
-
def _validate_package_names(package_names):
|
762
|
+
def _validate_package_names(package_names: tuple[str, ...]) -> None:
|
736
763
|
has_bad_names = False
|
737
764
|
for package_name in package_names:
|
738
765
|
try:
|
@@ -743,7 +770,7 @@ def show_migrations(package_labels, format, verbosity):
|
|
743
770
|
if has_bad_names:
|
744
771
|
sys.exit(2)
|
745
772
|
|
746
|
-
def show_list(db_connection, package_names):
|
773
|
+
def show_list(db_connection: Any, package_names: tuple[str, ...]) -> None:
|
747
774
|
"""
|
748
775
|
Show a list of all migrations on the system, or only those of
|
749
776
|
some named packages.
|
@@ -755,14 +782,16 @@ def show_migrations(package_labels, format, verbosity):
|
|
755
782
|
|
756
783
|
graph = loader.graph
|
757
784
|
# If we were passed a list of packages, validate it
|
785
|
+
package_names_list: list[str]
|
758
786
|
if package_names:
|
759
787
|
_validate_package_names(package_names)
|
788
|
+
package_names_list = list(package_names)
|
760
789
|
# Otherwise, show all packages in alphabetic order
|
761
790
|
else:
|
762
|
-
|
791
|
+
package_names_list = sorted(loader.migrated_packages)
|
763
792
|
# For each app, print its migrations in order from oldest (roots) to
|
764
793
|
# newest (leaves).
|
765
|
-
for package_name in
|
794
|
+
for package_name in package_names_list:
|
766
795
|
click.secho(package_name, fg="cyan", bold=True)
|
767
796
|
shown = set()
|
768
797
|
for node in graph.leaf_nodes(package_name):
|
@@ -770,9 +799,9 @@ def show_migrations(package_labels, format, verbosity):
|
|
770
799
|
if plan_node not in shown and plan_node[0] == package_name:
|
771
800
|
# Give it a nice title if it's a squashed one
|
772
801
|
title = plan_node[1]
|
773
|
-
if graph.nodes[plan_node].replaces:
|
774
|
-
title += f" ({len(graph.nodes[plan_node].replaces)} squashed migrations)"
|
775
|
-
applied_migration = loader.applied_migrations.get(plan_node)
|
802
|
+
if graph.nodes[plan_node].replaces: # type: ignore[union-attr]
|
803
|
+
title += f" ({len(graph.nodes[plan_node].replaces)} squashed migrations)" # type: ignore[union-attr]
|
804
|
+
applied_migration = loader.applied_migrations.get(plan_node) # type: ignore[union-attr]
|
776
805
|
# Mark it as applied/unapplied
|
777
806
|
if applied_migration:
|
778
807
|
if plan_node in recorded_migrations:
|
@@ -795,8 +824,8 @@ def show_migrations(package_labels, format, verbosity):
|
|
795
824
|
migration
|
796
825
|
for migration in recorded_migrations
|
797
826
|
if (
|
798
|
-
migration not in loader.disk_migrations
|
799
|
-
and (not
|
827
|
+
migration not in loader.disk_migrations # type: ignore[operator]
|
828
|
+
and (not package_names_list or migration[0] in package_names_list)
|
800
829
|
)
|
801
830
|
]
|
802
831
|
|
@@ -819,7 +848,7 @@ def show_migrations(package_labels, format, verbosity):
|
|
819
848
|
for name in sorted(prunable_by_package[package]):
|
820
849
|
click.echo(f" - {name}")
|
821
850
|
|
822
|
-
def show_plan(db_connection, package_names):
|
851
|
+
def show_plan(db_connection: Any, package_names: tuple[str, ...]) -> None:
|
823
852
|
"""
|
824
853
|
Show all known migrations (or only those of the specified package_names)
|
825
854
|
in the order they will be applied.
|
@@ -844,7 +873,7 @@ def show_migrations(package_labels, format, verbosity):
|
|
844
873
|
seen.add(migration)
|
845
874
|
|
846
875
|
# Output
|
847
|
-
def print_deps(node):
|
876
|
+
def print_deps(node: Any) -> str:
|
848
877
|
out = []
|
849
878
|
for parent in sorted(node.parents):
|
850
879
|
out.append(f"{parent.key[0]}.{parent.key[1]}")
|
@@ -856,7 +885,7 @@ def show_migrations(package_labels, format, verbosity):
|
|
856
885
|
deps = ""
|
857
886
|
if verbosity >= 2:
|
858
887
|
deps = print_deps(node)
|
859
|
-
if node.key in loader.applied_migrations:
|
888
|
+
if node.key in loader.applied_migrations: # type: ignore[operator]
|
860
889
|
click.echo(f"[X] {node.key[0]}.{node.key[1]}{deps}")
|
861
890
|
else:
|
862
891
|
click.echo(f"[ ] {node.key[0]}.{node.key[1]}{deps}")
|
@@ -896,20 +925,22 @@ def show_migrations(package_labels, format, verbosity):
|
|
896
925
|
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
|
897
926
|
)
|
898
927
|
def squash_migrations(
|
899
|
-
package_label,
|
900
|
-
start_migration_name,
|
901
|
-
migration_name,
|
902
|
-
no_optimize,
|
903
|
-
no_input,
|
904
|
-
squashed_name,
|
905
|
-
verbosity,
|
906
|
-
):
|
928
|
+
package_label: str,
|
929
|
+
start_migration_name: str | None,
|
930
|
+
migration_name: str,
|
931
|
+
no_optimize: bool,
|
932
|
+
no_input: bool,
|
933
|
+
squashed_name: str | None,
|
934
|
+
verbosity: int,
|
935
|
+
) -> None:
|
907
936
|
"""
|
908
937
|
Squashes an existing set of migrations (from first until specified) into a single new one.
|
909
938
|
"""
|
910
939
|
interactive = not no_input
|
911
940
|
|
912
|
-
def find_migration(
|
941
|
+
def find_migration(
|
942
|
+
loader: MigrationLoader, package_label: str, name: str
|
943
|
+
) -> Migration:
|
913
944
|
try:
|
914
945
|
return loader.get_migration_by_prefix(package_label, name)
|
915
946
|
except AmbiguityError:
|
plain/models/config.py
CHANGED
@@ -9,7 +9,7 @@ from .registry import models_registry
|
|
9
9
|
|
10
10
|
@register_config
|
11
11
|
class Config(PackageConfig):
|
12
|
-
def ready(self):
|
12
|
+
def ready(self) -> None:
|
13
13
|
# Trigger register calls to fire by importing the modules
|
14
14
|
packages_registry.autodiscover_modules("models", include_app=False)
|
15
15
|
|
plain/models/connections.py
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from importlib import import_module
|
2
4
|
from threading import local
|
3
|
-
from typing import Any, TypedDict
|
5
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
4
6
|
|
5
7
|
from plain.runtime import settings as plain_settings
|
6
8
|
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
11
|
+
|
7
12
|
|
8
13
|
class DatabaseConfig(TypedDict, total=False):
|
9
14
|
AUTOCOMMIT: bool
|
@@ -26,7 +31,7 @@ class DatabaseConnection:
|
|
26
31
|
|
27
32
|
__slots__ = ("_settings", "_local")
|
28
33
|
|
29
|
-
def __init__(self):
|
34
|
+
def __init__(self) -> None:
|
30
35
|
self._settings: DatabaseConfig = {}
|
31
36
|
self._local = local()
|
32
37
|
|
@@ -53,21 +58,32 @@ class DatabaseConnection:
|
|
53
58
|
|
54
59
|
return database
|
55
60
|
|
56
|
-
def create_connection(self):
|
61
|
+
def create_connection(self) -> BaseDatabaseWrapper:
|
57
62
|
database_config = self.configure_settings()
|
58
63
|
backend = import_module(f"{database_config['ENGINE']}.base")
|
59
|
-
return backend.DatabaseWrapper(database_config)
|
60
64
|
|
61
|
-
|
65
|
+
# Map vendor to wrapper class name
|
66
|
+
vendor_map = {
|
67
|
+
"plain.models.backends.sqlite3": "SQLiteDatabaseWrapper",
|
68
|
+
"plain.models.backends.mysql": "MySQLDatabaseWrapper",
|
69
|
+
"plain.models.backends.postgresql": "PostgreSQLDatabaseWrapper",
|
70
|
+
}
|
71
|
+
wrapper_class_name = vendor_map.get(
|
72
|
+
database_config["ENGINE"], "DatabaseWrapper"
|
73
|
+
)
|
74
|
+
wrapper_class = getattr(backend, wrapper_class_name)
|
75
|
+
return wrapper_class(database_config)
|
76
|
+
|
77
|
+
def has_connection(self) -> bool:
|
62
78
|
return hasattr(self._local, "conn")
|
63
79
|
|
64
|
-
def __getattr__(self, attr):
|
80
|
+
def __getattr__(self, attr: str) -> Any:
|
65
81
|
if not self.has_connection():
|
66
82
|
self._local.conn = self.create_connection()
|
67
83
|
|
68
84
|
return getattr(self._local.conn, attr)
|
69
85
|
|
70
|
-
def __setattr__(self, name, value):
|
86
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
71
87
|
if name.startswith("_"):
|
72
88
|
super().__setattr__(name, value)
|
73
89
|
else:
|