Skip to content

Commit 3508bfb

Browse files
authored
🐛 Do not duplicate comments on validator replacement (#43)
1 parent 1571fd3 commit 3508bfb

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

bump_pydantic/codemods/validator.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def __init__(self, context: CodemodContext) -> None:
6666
self._import_pydantic_validator = self._import_pydantic_root_validator = False
6767
self._already_modified = False
6868
self._should_add_comment = False
69+
self._has_comment = False
6970
self._args: List[cst.Arg] = []
7071

7172
@m.visit(IMPORT_VALIDATOR)
@@ -104,15 +105,17 @@ def visit_validator_decorator(self, node: cst.Decorator) -> None:
104105

105106
@m.visit(VALIDATOR_FUNCTION)
106107
def visit_validator_func(self, node: cst.FunctionDef) -> None:
108+
for line in node.leading_lines:
109+
if m.matches(line, m.EmptyLine(comment=m.Comment(value=CHECK_LINK_COMMENT))):
110+
self._has_comment = True
107111
# We are only able to refactor the `@validator` when the function has only `cls` and `v` as arguments.
108112
if len(node.params.params) > 2:
109113
self._should_add_comment = True
110114

111115
@m.leave(ROOT_VALIDATOR_DECORATOR)
112116
def leave_root_validator_func(self, original_node: cst.Decorator, updated_node: cst.Decorator) -> cst.Decorator:
113-
for line in updated_node.leading_lines:
114-
if m.matches(line, m.EmptyLine(comment=m.Comment(value=CHECK_LINK_COMMENT))):
115-
return updated_node
117+
if self._has_comment:
118+
return updated_node
116119

117120
if self._should_add_comment:
118121
return self._decorator_with_leading_comment(updated_node, ROOT_VALIDATOR_COMMENT)
@@ -121,9 +124,8 @@ def leave_root_validator_func(self, original_node: cst.Decorator, updated_node:
121124

122125
@m.leave(VALIDATOR_DECORATOR)
123126
def leave_validator_decorator(self, original_node: cst.Decorator, updated_node: cst.Decorator) -> cst.Decorator:
124-
for line in updated_node.leading_lines:
125-
if m.matches(line, m.EmptyLine(comment=m.Comment(value=CHECK_LINK_COMMENT))):
126-
return updated_node
127+
if self._has_comment:
128+
return updated_node
127129

128130
if self._should_add_comment:
129131
return self._decorator_with_leading_comment(updated_node, VALIDATOR_COMMENT)
@@ -133,9 +135,11 @@ def leave_validator_decorator(self, original_node: cst.Decorator, updated_node:
133135
@m.leave(VALIDATOR_FUNCTION | ROOT_VALIDATOR_FUNCTION)
134136
def leave_validator_func(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.FunctionDef:
135137
self._args = []
138+
self._has_comment = False
136139
if self._should_add_comment:
137140
self._should_add_comment = False
138141
return updated_node
142+
139143
classmethod_decorator = cst.Decorator(decorator=cst.Name("classmethod"))
140144
return updated_node.with_changes(decorators=[*updated_node.decorators, classmethod_decorator])
141145

tests/unit/test_validator.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,24 @@ def _normalize_fields(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
350350
return values
351351
"""
352352
self.assertCodemod(before, after)
353+
354+
def test_noop_comment(self) -> None:
355+
code = """
356+
import typing as t
357+
358+
from pydantic import BaseModel, validator
359+
360+
361+
class Potato(BaseModel):
362+
name: str
363+
dialect: str
364+
365+
# TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
366+
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
367+
@validator("name", "dialect")
368+
def _string_validator(cls, v: t.Any, values: t.Dict[str, t.Any], **kwargs) -> t.Optional[str]:
369+
if isinstance(v, exp.Expression):
370+
return v.name.lower()
371+
return str(v).lower() if v is not None else None
372+
"""
373+
self.assertCodemod(code, code)

0 commit comments

Comments
 (0)