From 72ccde49b2ff41423b2a2fe3ab048bd5de52d3f4 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Thu, 14 Mar 2024 12:41:51 +0300 Subject: [PATCH] Allow ignoring sqlmigrate errors. --- CHANGELOG.md | 1 + docs/usage.md | 1 + .../management/commands/lintmigrations.py | 1 + .../management/commands/makemigrations.py | 2 ++ .../management/utils.py | 6 +++++ .../migration_linter.py | 27 ++++++++++++++----- tests/unit/test_linter.py | 24 +++++++++++++++++ 7 files changed, 55 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cc73a9..0e5bb2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Feature: - Support Django 5.0 `db_default` attribute (issue #275) +- Allow ignoring the failures of `sqlmigrate` commands, with `--ignore-sqlmigrate-errors` option (issue #274) Bug: - Don't detect 'IS NOT NULL' as backward incompatible changes (issue #263) diff --git a/docs/usage.md b/docs/usage.md index 6fe0edf8..930d0ad6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -47,6 +47,7 @@ If you are using a config file, replace any dashes (`-`) with an underscore (`_` | `--quiet or -q {ok,ignore,warning,error}` | Suppress certain output messages, instead of writing them to stdout. | | `--warnings-as-errors [MIGRATION_TEST_CODE [...]]` | Handle warnings as errors and therefore return an error status code if we should. Optionally specify migration test codes to handle as errors. When no test code specified, all warnings are handled as errors. | | `--sql-analyser` | Specify the SQL analyser that should be used. Allowed values: 'sqlite', 'mysql', 'postgresql'. | +| `--ignore-sqlmigrate-errors` | Ignore failures of sqlmigrate commands. | ## Django settings configuration diff --git a/src/django_migration_linter/management/commands/lintmigrations.py b/src/django_migration_linter/management/commands/lintmigrations.py index 73170616..c0d3102f 100644 --- a/src/django_migration_linter/management/commands/lintmigrations.py +++ b/src/django_migration_linter/management/commands/lintmigrations.py @@ -175,6 +175,7 @@ def handle(self, *args, **options): all_warnings_as_errors=all_warnings_as_errors, no_output=options["verbosity"] == 0, analyser_string=options["sql_analyser"], + ignore_sqlmigrate_errors=options["ignore_sqlmigrate_errors"], ) linter.lint_all_migrations( app_label=options["app_label"], diff --git a/src/django_migration_linter/management/commands/makemigrations.py b/src/django_migration_linter/management/commands/makemigrations.py index b842c0d2..7edc8f1d 100644 --- a/src/django_migration_linter/management/commands/makemigrations.py +++ b/src/django_migration_linter/management/commands/makemigrations.py @@ -52,6 +52,7 @@ def handle(self, *app_labels, **options): self.exclude_migrations_tests = options["exclude_migration_tests"] self.warnings_as_errors = options["warnings_as_errors"] self.sql_analyser = options["sql_analyser"] + self.ignore_sqlmigrate_errors = options["ignore_sqlmigrate_errors"] configure_logging(options["verbosity"]) return super().handle(*app_labels, **options) @@ -92,6 +93,7 @@ def write_migration_files(self, changes: dict[str, list[Migration]]) -> None: warnings_as_errors_tests=warnings_as_errors_tests, all_warnings_as_errors=all_warnings_as_errors, analyser_string=self.sql_analyser, + ignore_sqlmigrate_errors=self.ignore_sqlmigrate_errors, ) for app_label, app_migrations in changes.items(): diff --git a/src/django_migration_linter/management/utils.py b/src/django_migration_linter/management/utils.py index effc29d3..7e039d41 100644 --- a/src/django_migration_linter/management/utils.py +++ b/src/django_migration_linter/management/utils.py @@ -40,6 +40,12 @@ def register_linting_configuration_options(parser: CommandParser) -> None: help="select the SQL analyser", ) + parser.add_argument( + "--ignore-sqlmigrate-errors", + action="store_true", + help="ignore failures of sqlmigrate command", + ) + def configure_logging(verbosity: int) -> None: logger = logging.getLogger("django_migration_linter") diff --git a/src/django_migration_linter/migration_linter.py b/src/django_migration_linter/migration_linter.py index 92e30572..b9a5805e 100644 --- a/src/django_migration_linter/migration_linter.py +++ b/src/django_migration_linter/migration_linter.py @@ -63,6 +63,7 @@ def __init__( all_warnings_as_errors: bool = False, no_output: bool = False, analyser_string: str | None = None, + ignore_sqlmigrate_errors: bool = False, ): # Store parameters and options self.django_path = path @@ -86,6 +87,7 @@ def __init__( settings.DATABASES[self.database]["ENGINE"], analyser_string=analyser_string, ) + self.ignore_sqlmigrate_errors = ignore_sqlmigrate_errors # Initialise counters self.reset_counters() @@ -321,13 +323,24 @@ def get_sql(self, app_label: str, migration_name: str) -> list[str]: database=self.database, stdout=dev_null, ) - except (ValueError, ProgrammingError): - logger.warning( - "Error while executing sqlmigrate on (%s, %s).", - app_label, - migration_name, - ) - raise + except (ValueError, ProgrammingError) as err: + if self.ignore_sqlmigrate_errors: + logger.warning( + "Error while executing sqlmigrate on (%s, %s) with exception: %s. " + "Continuing execution with empty SQL.", + app_label, + migration_name, + str(err), + ) + sql_statement = "" + else: + logger.warning( + "Error while executing sqlmigrate on (%s, %s) with exception: %s.", + app_label, + migration_name, + str(err), + ) + raise return sql_statement.splitlines() @staticmethod diff --git a/tests/unit/test_linter.py b/tests/unit/test_linter.py index 8c2b57bd..6eec2b20 100644 --- a/tests/unit/test_linter.py +++ b/tests/unit/test_linter.py @@ -2,7 +2,9 @@ import tempfile import unittest +from unittest.mock import patch +from django.db import ProgrammingError from django.db.migrations import Migration from django_migration_linter import MigrationLinter @@ -109,6 +111,28 @@ def test_read_migrations_unknown_file(self): with self.assertRaises(Exception): MigrationLinter.read_migrations_list(file_path) + @patch( + "django_migration_linter.migration_linter.call_command", + side_effect=ProgrammingError, + ) + def test_raise_exception_on_sqlmigrate_error(self, call_command_mock): + linter = MigrationLinter( + exclude_migration_tests=[], database="mysql", ignore_sqlmigrate_errors=False + ) + with self.assertRaises(ProgrammingError): + linter.get_sql("app_correct", "0002_foo") + + @patch( + "django_migration_linter.migration_linter.call_command", + side_effect=ProgrammingError, + ) + def test_ignore_exception_on_sqlmigrate_error(self, call_command_mock): + linter = MigrationLinter( + exclude_migration_tests=[], database="mysql", ignore_sqlmigrate_errors=True + ) + sql_result = linter.get_sql("app_correct", "0002_foo") + self.assertEqual([], sql_result) + def test_read_migrations_no_file(self): migration_list = MigrationLinter.read_migrations_list(None) self.assertIsNone(migration_list)