Skip to content

Commit

Permalink
Refs kiwitcms/Kiwi#1774 New linter: warn about missing backwards migr…
Browse files Browse the repository at this point in the history
…ations
  • Loading branch information
brymut authored and atodorov committed Jul 12, 2020
1 parent 4d606f6 commit 5f0fef4
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 9 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
* [psrb](https://github.com/psrb)
* [WayneLambert](https://github.com/WayneLambert)
* [alejandro-angulo](https://github.com/alejandro-angulo)
* [brymut](https://github.com/brymut)

45 changes: 36 additions & 9 deletions pylint_django/checkers/db_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pylint.checkers import utils
from pylint_django.__pkginfo__ import BASE_ID
from pylint_django import compat
from pylint_django.utils import is_migrations_module


def _is_addfield_with_default(call):
Expand All @@ -37,13 +38,6 @@ def _is_addfield_with_default(call):
return False


def _is_migrations_module(node):
if not isinstance(node, astroid.Module):
return False

return 'migrations' in node.path[0] and not node.path[0].endswith('__init__.py')


class NewDbFieldWithDefaultChecker(checkers.BaseChecker):
"""
Looks for migrations which add new model fields and these fields have a
Expand All @@ -69,7 +63,7 @@ class NewDbFieldWithDefaultChecker(checkers.BaseChecker):
_possible_offences = {}

def visit_module(self, node):
if _is_migrations_module(node):
if is_migrations_module(node):
self._migration_modules.append(node)

def visit_call(self, node):
Expand All @@ -78,7 +72,7 @@ def visit_call(self, node):
except: # noqa: E722, pylint: disable=bare-except
return

if not _is_migrations_module(module):
if not is_migrations_module(module):
return

if _is_addfield_with_default(node):
Expand Down Expand Up @@ -114,6 +108,38 @@ def _path(node):
self.add_message('new-db-field-with-default', args=module.name, node=node)


class MissingBackwardsMigrationChecker(checkers.BaseChecker):
__implements__ = (interfaces.IAstroidChecker,)

name = 'missing-backwards-migration-callable'

msgs = {'W%s05' % BASE_ID: ('%s Always include backwards migration callable',
'missing-backwards-migration-callable',
'Always include a backwards/reverse callable counterpart'
' so that the migration is not irreversable.')}

@utils.check_messages('missing-backwards-migration-callable')
def visit_call(self, node):
try:
module = node.frame().parent
except: # noqa: E722, pylint: disable=bare-except
return

if not is_migrations_module(module):
return

if node.func.as_string().endswith('RunPython') and len(node.args) < 2:
if node.keywords:
for keyword in node.keywords:
if keyword.arg == 'reverse_code':
return
self.add_message('missing-backwards-migration-callable',
args=module.name, node=node)
else:
self.add_message('missing-backwards-migration-callable',
args=module.name, node=node)


def load_configuration(linter):
# don't blacklist migrations for this checker
new_black_list = list(linter.config.black_list)
Expand All @@ -125,5 +151,6 @@ def load_configuration(linter):
def register(linter):
"""Required method to auto register this checker."""
linter.register_checker(NewDbFieldWithDefaultChecker(linter))
linter.register_checker(MissingBackwardsMigrationChecker(linter))
if not compat.LOAD_CONFIGURATION_SUPPORTED:
load_configuration(linter)
20 changes: 20 additions & 0 deletions pylint_django/tests/input/migrations/0003_without_backwards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# pylint: disable=missing-docstring, invalid-name
from django.db import migrations


# pylint: disable=unused-argument
def forwards_test(apps, schema_editor):
pass


class Migration(migrations.Migration):

operations = [
migrations.RunPython(), # [missing-backwards-migration-callable]
migrations.RunPython( # [missing-backwards-migration-callable]
forwards_test),
migrations.RunPython( # [missing-backwards-migration-callable]
code=forwards_test),
migrations.RunPython( # [missing-backwards-migration-callable]
code=forwards_test, atomic=False)
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
missing-backwards-migration-callable:13:Migration:pylint_django.tests.input.migrations.0003_without_backwards Always include backwards migration callable
missing-backwards-migration-callable:14:Migration:pylint_django.tests.input.migrations.0003_without_backwards Always include backwards migration callable
missing-backwards-migration-callable:16:Migration:pylint_django.tests.input.migrations.0003_without_backwards Always include backwards migration callable
missing-backwards-migration-callable:18:Migration:pylint_django.tests.input.migrations.0003_without_backwards Always include backwards migration callable
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# pylint: disable=missing-docstring, invalid-name
from django.db import migrations


# pylint: disable=unused-argument
def forwards_test(apps, schema_editor):
pass


# pylint: disable=unused-argument
def backwards_test(apps, schema_editor):
pass


class Migration(migrations.Migration):

operations = [
migrations.RunPython(forwards_test, backwards_test),
migrations.RunPython(forwards_test, reverse_code=backwards_test),
migrations.RunPython(code=forwards_test, reverse_code=backwards_test)
]
8 changes: 8 additions & 0 deletions pylint_django/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Utils."""
import sys
import astroid

from astroid.bases import Instance
from astroid.exceptions import InferenceError
Expand Down Expand Up @@ -30,3 +31,10 @@ def node_is_subclass(cls, *subclass_names):
continue

return False


def is_migrations_module(node):
if not isinstance(node, astroid.Module):
return False

return 'migrations' in node.path[0] and not node.path[0].endswith('__init__.py')

0 comments on commit 5f0fef4

Please sign in to comment.