From b15924ca0f22dc94299cf9feaa546bf4c223c9c7 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 89cc73a..0e5bb2a 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 6fe0edf..930d0ad 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 7317061..c0d3102 100644 --- a/src/django_migration_linter/management/commands/lintmigrations.py +++ b/src/django_migration_linter/management/commands/lintmigrations.py @@ -175,6 +175,7 @@ class Command(BaseCommand): 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 b842c0d..7edc8f1 100644 --- a/src/django_migration_linter/management/commands/makemigrations.py +++ b/src/django_migration_linter/management/commands/makemigrations.py @@ -52,6 +52,7 @@ class Command(MakeMigrationsCommand): 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 @@ class Command(MakeMigrationsCommand): 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 effc29d..7e039d4 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 92e3057..b9a5805 100644 --- a/src/django_migration_linter/migration_linter.py +++ b/src/django_migration_linter/migration_linter.py @@ -63,6 +63,7 @@ class MigrationLinter: 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 @@ class MigrationLinter: 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 @@ class MigrationLinter: 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 8c2b57b..6eec2b2 100644 --- a/tests/unit/test_linter.py +++ b/tests/unit/test_linter.py @@ -2,7 +2,9 @@ from __future__ import annotations 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 @@ class LinterFunctionsTestCase(unittest.TestCase): 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)