-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make squashmigrations update max_migrations.txt
- Loading branch information
1 parent
b57d62b
commit 473ae9c
Showing
8 changed files
with
284 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/django_linear_migrations/management/commands/squashmigrations.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
from django.core.management.commands import squashmigrations | ||
from django.core.management.commands.squashmigrations import Command as BaseCommand | ||
from django.db.migrations import Migration | ||
from django.db.migrations.writer import MigrationWriter | ||
|
||
from django_linear_migrations.apps import MigrationDetails | ||
from django_linear_migrations.apps import first_party_app_configs | ||
|
||
|
||
class Command(BaseCommand): | ||
def handle(self, **options: Any) -> None: | ||
# Temporarily wrap the call to MigrationWriter.__init__ to capture its first | ||
# argument, the generated migration instance. | ||
captured_migration = None | ||
|
||
def wrapper(migration: Migration, *args: Any, **kwargs: Any) -> MigrationWriter: | ||
nonlocal captured_migration | ||
captured_migration = migration | ||
return MigrationWriter(migration, *args, **kwargs) | ||
|
||
squashmigrations.MigrationWriter = wrapper # type: ignore[attr-defined] | ||
|
||
try: | ||
super().handle(**options) | ||
finally: | ||
squashmigrations.MigrationWriter = MigrationWriter # type: ignore[attr-defined] | ||
|
||
if captured_migration is not None and any( | ||
captured_migration.app_label == app_config.label | ||
for app_config in first_party_app_configs() | ||
): | ||
# A squash migration was generated, update max_migration.txt. | ||
migration_details = MigrationDetails(captured_migration.app_label) | ||
max_migration_txt = migration_details.dir / "max_migration.txt" | ||
max_migration_txt.write_text(f"{captured_migration.name}\n") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from __future__ import annotations | ||
|
||
import sys | ||
import unittest | ||
from collections.abc import Callable | ||
from contextlib import AbstractContextManager | ||
from typing import Any | ||
from typing import TypeVar | ||
|
||
# TestCase.enterContext() backport, source: | ||
# https://adamj.eu/tech/2022/11/14/unittest-context-methods-python-3-11-backports/ | ||
|
||
_T = TypeVar("_T") | ||
|
||
if sys.version_info < (3, 11): | ||
|
||
def _enter_context(cm: Any, addcleanup: Callable[..., None]) -> Any: | ||
# We look up the special methods on the type to match the with | ||
# statement. | ||
cls = type(cm) | ||
try: | ||
enter = cls.__enter__ | ||
exit = cls.__exit__ | ||
except AttributeError: | ||
raise TypeError( | ||
f"'{cls.__module__}.{cls.__qualname__}' object does " | ||
f"not support the context manager protocol" | ||
) from None | ||
result = enter(cm) | ||
addcleanup(exit, cm, None, None, None) | ||
return result | ||
|
||
|
||
class EnterContextMixin(unittest.TestCase): | ||
if sys.version_info < (3, 11): | ||
|
||
def enterContext(self, cm: AbstractContextManager[_T]) -> _T: | ||
result: _T = _enter_context(cm, self.addCleanup) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
from __future__ import annotations | ||
|
||
from functools import partial | ||
from textwrap import dedent | ||
|
||
import pytest | ||
from django.core.management import CommandError | ||
from django.test import TestCase | ||
from django.test import override_settings | ||
|
||
from tests.compat import EnterContextMixin | ||
from tests.utils import run_command | ||
from tests.utils import temp_migrations_module | ||
|
||
|
||
class SquashMigrationsTests(EnterContextMixin, TestCase): | ||
def setUp(self): | ||
self.migrations_dir = self.enterContext(temp_migrations_module()) | ||
|
||
call_command = partial(run_command, "squashmigrations") | ||
|
||
def test_fail_already_squashed_migration(self): | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0001_already_squashed.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
replaces = [ | ||
('testapp', '0001_initial'), | ||
('testapp', '0002_second'), | ||
] | ||
dependencies = [] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0002_new_branch.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
('testapp', '0001_already_squashed'), | ||
] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
max_migration_txt = self.migrations_dir / "max_migration.txt" | ||
max_migration_txt.write_text("0002_new_branch\n") | ||
|
||
with pytest.raises(CommandError) as excinfo: | ||
self.call_command("testapp", "0002", "--no-input") | ||
|
||
assert excinfo.value.args[0].startswith( | ||
"You cannot squash squashed migrations!" | ||
) | ||
assert max_migration_txt.read_text() == "0002_new_branch\n" | ||
|
||
def test_success(self): | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0001_initial.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
intial = True | ||
dependencies = [] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0002_second.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
('testapp', '0001_initial'), | ||
] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
max_migration_txt = self.migrations_dir / "max_migration.txt" | ||
max_migration_txt.write_text("0002_second\n") | ||
|
||
out, err, returncode = self.call_command("testapp", "0002", "--no-input") | ||
|
||
assert returncode == 0 | ||
assert max_migration_txt.read_text() == "0001_squashed_0002_second\n" | ||
|
||
@override_settings(FIRST_PARTY_APPS=[]) | ||
def test_skip_non_first_party_app(self): | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0001_initial.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
intial = True | ||
dependencies = [] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
(self.migrations_dir / "__init__.py").touch() | ||
(self.migrations_dir / "0002_second.py").write_text( | ||
dedent( | ||
"""\ | ||
from django.db import migrations, models | ||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
('testapp', '0001_initial'), | ||
] | ||
operations = [] | ||
""" | ||
) | ||
) | ||
max_migration_txt = self.migrations_dir / "max_migration.txt" | ||
max_migration_txt.write_text("0002_second\n") | ||
|
||
out, err, returncode = self.call_command("testapp", "0002", "--no-input") | ||
|
||
assert returncode == 0 | ||
assert max_migration_txt.read_text() == "0002_second\n" |
Oops, something went wrong.