Skip to content

Commit 7910a06

Browse files
committed
Split models cli in to db and migrations
1 parent d7f7053 commit 7910a06

File tree

6 files changed

+127
-128
lines changed

6 files changed

+127
-128
lines changed

plain-code/plain/code/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def maybe_exit(return_code: int) -> None:
120120

121121
@common_command
122122
@without_runtime_setup
123-
@register_cli("fix", shortcut_for="code")
123+
@register_cli("fix", shortcut_for="code fix")
124124
@cli.command()
125125
@click.pass_context
126126
@click.argument("path", default=".")

plain-models/plain/models/backends/base/creation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def create_test_db(self, verbosity: int = 1, prefix: str = "") -> str:
3737
If prefix is provided, it will be prepended to the database name
3838
to isolate it from other test databases.
3939
"""
40-
from plain.models.cli import migrate
40+
from plain.models.cli.migrations import apply
4141

4242
test_database_name = self._get_test_db_name(prefix)
4343

@@ -52,7 +52,7 @@ def create_test_db(self, verbosity: int = 1, prefix: str = "") -> str:
5252
settings.DATABASE["NAME"] = test_database_name
5353
self.connection.settings_dict["NAME"] = test_database_name
5454

55-
migrate.callback(
55+
apply.callback(
5656
package_label=None,
5757
migration_name=None,
5858
fake=False,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from . import db, migrations
2+
3+
__all__ = [
4+
"db",
5+
"migrations",
6+
]
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import sys
5+
import time
6+
from typing import TYPE_CHECKING, cast
7+
8+
import click
9+
10+
from plain.cli import register_cli
11+
12+
from ..backups.cli import cli as backups_cli
13+
from ..db import OperationalError
14+
from ..db import db_connection as _db_connection
15+
16+
if TYPE_CHECKING:
17+
from ..backends.base.base import BaseDatabaseWrapper
18+
19+
db_connection = cast("BaseDatabaseWrapper", _db_connection)
20+
else:
21+
db_connection = _db_connection
22+
23+
24+
@register_cli("db")
25+
@click.group()
26+
def cli() -> None:
27+
"""Database operations"""
28+
29+
30+
cli.add_command(backups_cli)
31+
32+
33+
@cli.command()
34+
@click.argument("parameters", nargs=-1)
35+
def shell(parameters: tuple[str, ...]) -> None:
36+
"""Open an interactive database shell"""
37+
try:
38+
db_connection.client.runshell(list(parameters))
39+
except FileNotFoundError:
40+
# Note that we're assuming the FileNotFoundError relates to the
41+
# command missing. It could be raised for some other reason, in
42+
# which case this error message would be inaccurate. Still, this
43+
# message catches the common case.
44+
click.secho(
45+
f"You appear not to have the {db_connection.client.executable_name!r} program installed or on your path.",
46+
fg="red",
47+
err=True,
48+
)
49+
sys.exit(1)
50+
except subprocess.CalledProcessError as e:
51+
click.secho(
52+
'"{}" returned non-zero exit status {}.'.format(
53+
" ".join(e.cmd),
54+
e.returncode,
55+
),
56+
fg="red",
57+
err=True,
58+
)
59+
sys.exit(e.returncode)
60+
61+
62+
@cli.command()
63+
def wait() -> None:
64+
"""Wait for the database to be ready"""
65+
attempts = 0
66+
while True:
67+
attempts += 1
68+
waiting_for = False
69+
70+
try:
71+
db_connection.ensure_connection()
72+
except OperationalError:
73+
waiting_for = True
74+
75+
if waiting_for:
76+
if attempts > 1:
77+
# After the first attempt, start printing them
78+
click.secho(
79+
f"Waiting for database (attempt {attempts})",
80+
fg="yellow",
81+
)
82+
time.sleep(1.5)
83+
else:
84+
click.secho("✔ Database ready", fg="green")
85+
break

plain-models/plain/models/cli.py renamed to plain-models/plain/models/cli/migrations.py

Lines changed: 32 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import os
4-
import subprocess
54
import sys
65
import time
76
from typing import TYPE_CHECKING, Any, cast
@@ -14,132 +13,41 @@
1413
from plain.runtime import settings
1514
from plain.utils.text import Truncator
1615

17-
from . import migrations
18-
from .backups.cli import cli as backups_cli
19-
from .backups.core import DatabaseBackups
20-
from .db import OperationalError
21-
from .db import db_connection as _db_connection
22-
from .migrations.autodetector import MigrationAutodetector
23-
from .migrations.executor import MigrationExecutor
24-
from .migrations.loader import AmbiguityError, MigrationLoader
25-
from .migrations.migration import Migration, SettingsTuple
26-
from .migrations.optimizer import MigrationOptimizer
27-
from .migrations.questioner import (
16+
from .. import migrations
17+
from ..backups.core import DatabaseBackups
18+
from ..db import db_connection as _db_connection
19+
from ..migrations.autodetector import MigrationAutodetector
20+
from ..migrations.executor import MigrationExecutor
21+
from ..migrations.loader import AmbiguityError, MigrationLoader
22+
from ..migrations.migration import Migration, SettingsTuple
23+
from ..migrations.optimizer import MigrationOptimizer
24+
from ..migrations.questioner import (
2825
InteractiveMigrationQuestioner,
2926
NonInteractiveMigrationQuestioner,
3027
)
31-
from .migrations.recorder import MigrationRecorder
32-
from .migrations.state import ModelState, ProjectState
33-
from .migrations.writer import MigrationWriter
34-
from .registry import models_registry
28+
from ..migrations.recorder import MigrationRecorder
29+
from ..migrations.state import ModelState, ProjectState
30+
from ..migrations.writer import MigrationWriter
31+
from ..registry import models_registry
3532

3633
if TYPE_CHECKING:
37-
from .backends.base.base import BaseDatabaseWrapper
38-
from .migrations.operations.base import Operation
34+
from ..backends.base.base import BaseDatabaseWrapper
35+
from ..migrations.operations.base import Operation
3936

4037
db_connection = cast("BaseDatabaseWrapper", _db_connection)
4138
else:
4239
db_connection = _db_connection
4340

4441

45-
@register_cli("models")
42+
@register_cli("migrations")
4643
@click.group()
4744
def cli() -> None:
48-
"""Database model management"""
49-
50-
51-
cli.add_command(backups_cli)
52-
53-
54-
@cli.command()
55-
@click.argument("parameters", nargs=-1)
56-
def db_shell(parameters: tuple[str, ...]) -> None:
57-
"""Open an interactive database shell"""
58-
try:
59-
db_connection.client.runshell(list(parameters))
60-
except FileNotFoundError:
61-
# Note that we're assuming the FileNotFoundError relates to the
62-
# command missing. It could be raised for some other reason, in
63-
# which case this error message would be inaccurate. Still, this
64-
# message catches the common case.
65-
click.secho(
66-
f"You appear not to have the {db_connection.client.executable_name!r} program installed or on your path.",
67-
fg="red",
68-
err=True,
69-
)
70-
sys.exit(1)
71-
except subprocess.CalledProcessError as e:
72-
click.secho(
73-
'"{}" returned non-zero exit status {}.'.format(
74-
" ".join(e.cmd),
75-
e.returncode,
76-
),
77-
fg="red",
78-
err=True,
79-
)
80-
sys.exit(e.returncode)
81-
82-
83-
@cli.command()
84-
def db_wait() -> None:
85-
"""Wait for the database to be ready"""
86-
attempts = 0
87-
while True:
88-
attempts += 1
89-
waiting_for = False
90-
91-
try:
92-
db_connection.ensure_connection()
93-
except OperationalError:
94-
waiting_for = True
95-
96-
if waiting_for:
97-
if attempts > 1:
98-
# After the first attempt, start printing them
99-
click.secho(
100-
f"Waiting for database (attempt {attempts})",
101-
fg="yellow",
102-
)
103-
time.sleep(1.5)
104-
else:
105-
click.secho("✔ Database ready", fg="green")
106-
break
107-
108-
109-
@cli.command(name="list")
110-
@click.argument("package_labels", nargs=-1)
111-
@click.option(
112-
"--app-only",
113-
is_flag=True,
114-
help="Only show models from packages that start with 'app'.",
115-
)
116-
def list_models(package_labels: tuple[str, ...], app_only: bool) -> None:
117-
"""List all installed models"""
118-
119-
packages = set(package_labels)
120-
121-
for model in sorted(
122-
models_registry.get_models(),
123-
key=lambda m: (m.model_options.package_label, m.model_options.model_name),
124-
):
125-
pkg = model.model_options.package_label
126-
pkg_name = packages_registry.get_package_config(pkg).name
127-
if app_only and not pkg_name.startswith("app"):
128-
continue
129-
if packages and pkg not in packages:
130-
continue
131-
fields = ", ".join(f.name for f in model._model_meta.get_fields())
132-
click.echo(
133-
f"{click.style(pkg, fg='cyan')}.{click.style(model.__name__, fg='blue')}"
134-
)
135-
click.echo(f" table: {model.model_options.db_table}")
136-
click.echo(f" fields: {fields}")
137-
click.echo(f" package: {pkg_name}\n")
45+
"""Database migration management"""
13846

13947

14048
@common_command
141-
@register_cli("makemigrations", shortcut_for="models")
142-
@cli.command()
49+
@register_cli("makemigrations", shortcut_for="migrations make")
50+
@cli.command("make")
14351
@click.argument("package_labels", nargs=-1)
14452
@click.option(
14553
"--dry-run",
@@ -167,7 +75,7 @@ def list_models(package_labels: tuple[str, ...], app_only: bool) -> None:
16775
default=1,
16876
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
16977
)
170-
def makemigrations(
78+
def make(
17179
package_labels: tuple[str, ...],
17280
dry_run: bool,
17381
empty: bool,
@@ -344,8 +252,8 @@ def write_migration_files(
344252

345253

346254
@common_command
347-
@register_cli("migrate", shortcut_for="models")
348-
@cli.command()
255+
@register_cli("migrate", shortcut_for="migrations apply")
256+
@cli.command("apply")
349257
@click.argument("package_label", required=False)
350258
@click.argument("migration_name", required=False)
351259
@click.option(
@@ -386,7 +294,7 @@ def write_migration_files(
386294
is_flag=True,
387295
help="Suppress migration output (used for test database creation).",
388296
)
389-
def migrate(
297+
def apply(
390298
package_label: str | None,
391299
migration_name: str | None,
392300
fake: bool,
@@ -695,7 +603,7 @@ def describe_operation(operation: Any) -> tuple[str, bool]:
695603
)
696604

697605

698-
@cli.command()
606+
@cli.command("list")
699607
@click.argument("package_labels", nargs=-1)
700608
@click.option(
701609
"--format",
@@ -710,10 +618,10 @@ def describe_operation(operation: Any) -> tuple[str, bool]:
710618
default=1,
711619
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
712620
)
713-
def show_migrations(
621+
def list_migrations(
714622
package_labels: tuple[str, ...], format: str, verbosity: int
715623
) -> None:
716-
"""Show all available migrations"""
624+
"""Show all migrations"""
717625

718626
def _validate_package_names(package_names: tuple[str, ...]) -> None:
719627
has_bad_names = False
@@ -827,14 +735,14 @@ def print_deps(node: Any) -> str:
827735
show_list(db_connection, package_labels)
828736

829737

830-
@cli.command()
738+
@cli.command("prune")
831739
@click.option(
832740
"--yes",
833741
is_flag=True,
834742
help="Skip confirmation prompt (for non-interactive use).",
835743
)
836-
def prune_migrations(yes: bool) -> None:
837-
"""Prune stale migration records"""
744+
def prune(yes: bool) -> None:
745+
"""Remove stale migration records from the database"""
838746
# Load migrations from disk and database
839747
loader = MigrationLoader(db_connection, ignore_no_migrations=True)
840748
recorder = MigrationRecorder(db_connection)
@@ -929,7 +837,7 @@ def prune_migrations(yes: bool) -> None:
929837
)
930838

931839

932-
@cli.command()
840+
@cli.command("squash")
933841
@click.argument("package_label")
934842
@click.argument("start_migration_name", required=False)
935843
@click.argument("migration_name")
@@ -953,7 +861,7 @@ def prune_migrations(yes: bool) -> None:
953861
default=1,
954862
help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
955863
)
956-
def squash_migrations(
864+
def squash(
957865
package_label: str,
958866
start_migration_name: str | None,
959867
migration_name: str,
@@ -1016,7 +924,7 @@ def find_migration(
1016924
f"The migration '{start_migration}' cannot be found. Maybe it comes after "
1017925
f"the migration '{migration}'?\n"
1018926
f"Have a look at:\n"
1019-
f" plain models show-migrations {package_label}\n"
927+
f" plain migrations list {package_label}\n"
1020928
f"to debug this issue."
1021929
)
1022930

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
def setup() -> None:
22
# This package isn't an installed app,
33
# so we need to trigger our own import and cli registration.
4-
from .cli import cli # noqa
4+
from . import cli # noqa

0 commit comments

Comments
 (0)