diff --git a/README.md b/README.md index 6a0b79b..5df1db4 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,14 @@ If `aerich` guesses you are renaming a column, it will ask `Rename {old_column} `True` to rename column without column drop, or choose `False` to drop the column then create. Note that the latter may lose data. +If you need to manually write migration, you could generate empty file: + +```shell +> aerich migrate --name add_index --empty + +Success migrate 1_202326122220101229_add_index.py +``` + ### Upgrade to latest version ```shell diff --git a/aerich/__init__.py b/aerich/__init__.py index 3ad1e91..f3e045c 100644 --- a/aerich/__init__.py +++ b/aerich/__init__.py @@ -123,8 +123,8 @@ class Command: inspect = cls(connection, tables) return await inspect.inspect() - async def migrate(self, name: str = "update"): - return await Migrate.migrate(name) + async def migrate(self, name: str = "update", empty: bool = False) -> str: + return await Migrate.migrate(name, empty) async def init_db(self, safe: bool): location = self.location diff --git a/aerich/cli.py b/aerich/cli.py index 3d1f08b..924f044 100644 --- a/aerich/cli.py +++ b/aerich/cli.py @@ -79,6 +79,7 @@ async def cli(ctx: Context, config, app): @cli.command(help="Generate migrate changes file.") @click.option("--name", default="update", show_default=True, help="Migrate name.") +@click.option("--empty", default=False, is_flag=True, help="Generate empty migration file.") @click.pass_context @coro async def migrate(ctx: Context, name): diff --git a/aerich/migrate.py b/aerich/migrate.py index 0d691d5..27fba58 100644 --- a/aerich/migrate.py +++ b/aerich/migrate.py @@ -120,22 +120,23 @@ class Migrate: os.unlink(Path(cls.migrate_location, version_file)) version_file = Path(cls.migrate_location, version) - content = MIGRATE_TEMPLATE.format( - upgrade_sql=";\n ".join(cls.upgrade_operators) + ";", - downgrade_sql=";\n ".join(cls.downgrade_operators) + ";", - ) + content = cls._get_diff_file_content() with open(version_file, "w", encoding="utf-8") as f: f.write(content) return version @classmethod - async def migrate(cls, name) -> str: + async def migrate(cls, name: str, empty: bool) -> str: """ diff old models and new models to generate diff content - :param name: + :param name: str name for migration + :param empty: bool if True generates empty migration :return: """ + if empty: + return await cls._generate_diff_py(name) + new_version_content = get_models_describe(cls.app) cls.diff_models(cls._last_version_content, new_version_content) cls.diff_models(new_version_content, cls._last_version_content, False) @@ -147,6 +148,21 @@ class Migrate: return await cls._generate_diff_py(name) + @classmethod + def _get_diff_file_content(cls) -> str: + """ + builds content for diff file from template + """ + def join_lines(lines: List[str]) -> str: + if not lines: + return "" + return ";\n ".join(lines) + ";" + + return MIGRATE_TEMPLATE.format( + upgrade_sql=join_lines(cls.upgrade_operators), + downgrade_sql=join_lines(cls.downgrade_operators) + ) + @classmethod def _add_operator(cls, operator: str, upgrade=True, fk_m2m_index=False): """ diff --git a/tests/test_migrate.py b/tests/test_migrate.py index 94353d3..a94d73f 100644 --- a/tests/test_migrate.py +++ b/tests/test_migrate.py @@ -1,3 +1,6 @@ +import tempfile +from pathlib import Path + import pytest from pytest_mock import MockerFixture @@ -5,7 +8,7 @@ from aerich.ddl.mysql import MysqlDDL from aerich.ddl.postgres import PostgresDDL from aerich.ddl.sqlite import SqliteDDL from aerich.exceptions import NotSupportError -from aerich.migrate import Migrate +from aerich.migrate import MIGRATE_TEMPLATE, Migrate from aerich.utils import get_models_describe old_models_describe = { @@ -966,3 +969,15 @@ def test_sort_all_version_files(mocker): "10_datetime_update.py", "11_datetime_update.py", ] + +async def test_empty_migration(mocker) -> None: + mocker.patch("os.listdir", return_value=[]) + Migrate.app = "foo" + expected_content = MIGRATE_TEMPLATE.format(upgrade_sql="", downgrade_sql="") + with tempfile.TemporaryDirectory() as temp_dir: + Migrate.migrate_location = temp_dir + + migration_file = await Migrate.migrate("update", True) + + with open(Path(temp_dir, migration_file), "r") as f: + assert f.read() == expected_content