8000 Add CheckConstraint.check fixer (#457) · adamchainz/django-upgrade@d0fc922 · GitHub
[go: up one dir, main page]

Skip to content

Commit d0fc922

Browse files
authored
Add CheckConstraint.check fixer (#457)
Fixes #455.
1 parent 585dbe0 commit d0fc922

File tree

4 files changed

+198
-1
lines changed

4 files changed

+198
-1
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Changelog
44

55
* Support Django 5.1 as a target version.
66

7+
* Add Django 5.1+ fixer to rewrite the ``check`` keyword argument of ``CheckConstraint`` to ``condition``.
8+
79
1.17.0 (2024-05-10)
810
-------------------
911

README.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,4 +1120,16 @@ Django 5.1
11201120

11211121
`Release Notes <https://docs.djangoproject.com/en/5.1/releases/5.1/>`__
11221122

1123-
No fixers yet.
1123+
``CheckConstraint`` ``condition`` argument
1124+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1125+
1126+
**Name:** ``check_constraint_condition``
1127+
1128+
Rewrites calls to ``CheckConstraint`` and built-in subclasses from the old ``check`` argument to the new name ``condition``.
1129+
1130+
Requires Python 3.9+ due to changes in ``ast.keyword``.
1131+
1132+
.. code-block:: diff
1133+
1134+
-CheckConstraint(check=Q(amount__gte=0))
1135+
+CheckConstraint(condition=Q(amount__gte=0))
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Rewrite CheckConstraint calls to use 'condition' argument instead of 'check':
3+
https://docs.djangoproject.com/en/5.1/releases/5.1/#id2
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import ast
9+
import sys
10+
from functools import partial
11+
from typing import Iterable
12+
13+
from tokenize_rt import Offset
14+
15+
from django_upgrade.ast import ast_start_offset
16+
from django_upgrade.data import Fixer
17+
from django_upgrade.data import State
18+
from django_upgrade.data import TokenFunc
19+
from django_upgrade.tokens import replace
20+
21+
fixer = Fixer(
22+
__name__,
23+
min_version=(5, 1),
24+
)
25+
26+
# Requires lineno/utf8_byte_offset on ast.keyword, added in Python 3.9
27+
if sys.version_info >= (3, 9):
28+
29+
@fixer.register(ast.Call)
30+
def visit_Call(
31+
state: State,
32+
node: ast.Call,
33+
parents: list[ast.AST],
34+
) -> Iterable[tuple[Offset, TokenFunc]]:
35+
if (
36+
(
37+
(
38+
isinstance(node.func, ast.Name)
39+
and node.func.id == "CheckConstraint"
40+
and "CheckConstraint" in state.from_imports["django.db.models"]
41+
)
42+
or (
43+
isinstance(node.func, ast.Attribute)
44+
and node.func.attr == "CheckConstraint"
45+
and isinstance(node.func.value, ast.Name)
46+
and node.func.value.id == "models"
47+
and "models" in state.from_imports["django.db"]
48+
)
49+
)
50+
and (kwarg_names := {k.arg for k in node.keywords})
51+
and "check" in kwarg_names
52+
and "condition" not in kwarg_names
53+
):
54+
check_kwarg = [k for k in node.keywords if k.arg == "check"][0]
55+
yield ast_start_offset(check_kwarg), partial(replace, src="condition")
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from functools import partial
5+
6+
import pytest
7+
8+
from django_upgrade.data import Settings
9+
from tests.fixers import tools
10+
11+
if sys.version_info < (3, 9):
12+
pytest.skip("Python 3.9+", allow_module_level=True)
13+
14+
settings = Settings(target_version=(5, 1))
15+
check_noop = partial(tools.check_noop, settings=settings)
16+
check_transformed = partial(tools.check_transformed, settings=settings)
17+
18+
19+
def test_name_no_import():
20+
check_noop(
21+
"""\
22+
CheckConstraint(check=Q(id=1))
23+
""",
24+
)
25+
26+
27+
def test_attr_multilevel():
28+
check_noop(
29+
"""\
30+
from django import db
31+
32+
db.models.CheckConstraint(check=db.models.Q(id=1))
33+
"""
34+
)
35+
36+
37+
def test_attr_not_models():
38+
check_noop(
39+
"""\
40+
from django.db import shmodels
41+
42+
shmodels.CheckConstraint(check=shmodels.Q(id=1))
43+
"""
44+
)
45+
46+
47+
def test_attr_no_import():
48+
check_noop(
49+
"""\
50+
models.CheckConstraint(check=models.Q(id=1))
51+
"""
52+
)
53+
54+
55+
def test_no_check_kwarg():
56+
check_noop(
57+
"""\
58+
from django.db.models import CheckConstraint
59+
60+
CheckConstraint(
61+
name="monomodel_id",
62+
)
63+
""",
64+
)
65+
66+
67+
def test_condition_present():
68+
check_noop(
69+
"""\
70+
from django.db.models import CheckConstraint
71+
72+
CheckConstraint(
73+
check=Q(id=1),
74+
condition=Q(id=1),
75+
)
76+
""",
77+
)
78+
79+
80+
def test_success_name():
81+
check_transformed(
82+
"""\
83+
from django.db.models import CheckConstraint
84+
85+
CheckConstraint(check=Q(id=1))
86+
""",
87+
"""\
88+
from django.db.models import CheckConstraint
89+
90+
CheckConstraint(condition=Q(id=1))
91+
""",
92+
)
93+
94+
95+
def test_success_attr():
96+
check_transformed(
97+
"""\
98+
from django.db import models
99+
100+
models.CheckConstraint(check=models.Q(id=1))
101+
""",
102+
"""\
103+
from django.db import models
104+
105+
models.CheckConstraint(condition=models.Q(id=1))
106+
""",
107+
)
108+
109+
110+
def test_success_other_args():
111+
check_transformed(
112+
"""\
113+
from django.db.models import CheckConstraint
114+
115+
CheckConstraint(
116+
name="monomodel_id",
117+
check=Q(id=1),
118+
)
119+
""",
120+
"""\
121+
from django.db.models import CheckConstraint
122+
123+
CheckConstraint(
124+
name="monomodel_id",
125+
condition=Q(id=1),
126+
)
127+
""",
128+
)

0 commit comments

Comments
 (0)
0