Skip to content

Commit

Permalink
Add rule for duplicate field names (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
Viicos committed Mar 14, 2024
1 parent 3715aec commit 315ab61
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/flake8_pydantic/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class PYD005(Error):
message = "Field name overrides annotation"


class PYD006(Error):
error_code = "PYD005"
message = "Duplicate field name"


class PYD010(Error):
error_code = "PYD010"
message = "Usage of __pydantic_config__"
14 changes: 13 additions & 1 deletion src/flake8_pydantic/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from ._compat import TypeAlias
from ._utils import extract_annotations, is_dataclass, is_function, is_name, is_pydantic_model
from .errors import PYD001, PYD002, PYD003, PYD004, PYD005, PYD010, Error
from .errors import PYD001, PYD002, PYD003, PYD004, PYD005, PYD006, PYD010, Error

ClassType: TypeAlias = Literal["pydantic_model", "dataclass", "other_class"]

Expand Down Expand Up @@ -95,6 +95,17 @@ def _check_pyd_005(self, node: ast.ClassDef) -> None:
if previous_targets & extract_annotations(stmt.annotation):
self.errors.append(PYD005.from_node(stmt))

def _check_pyd_006(self, node: ast.ClassDef) -> None:
if self.current_class in {"pydantic_model", "dataclass"}:
previous_targets: set[str] = set()

for stmt in node.body:
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
if stmt.target.id in previous_targets:
self.errors.append(PYD006.from_node(stmt))

previous_targets.add(stmt.target.id)

def _check_pyd_010(self, node: ast.ClassDef) -> None:
if self.current_class == "other_class":
for stmt in node.body:
Expand All @@ -115,6 +126,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
self.enter_class(node)
self._check_pyd_002(node)
self._check_pyd_005(node)
self._check_pyd_006(node)
self._check_pyd_010(node)
self.generic_visit(node)
self.leave_class()
Expand Down
35 changes: 35 additions & 0 deletions tests/rules/test_pyd006.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations

import ast

import pytest

from flake8_pydantic.errors import PYD006, Error
from flake8_pydantic.visitor import Visitor

PYD006_1 = """
class Model(BaseModel):
x: int
x: str = "1"
"""

PYD006_2 = """
class Model(BaseModel):
x: int
y: int
"""


@pytest.mark.parametrize(
["source", "expected"],
[
(PYD006_1, [PYD006(4, 4)]),
(PYD006_2, []),
],
)
def test_pyd006(source: str, expected: list[Error]) -> None:
module = ast.parse(source)
visitor = Visitor()
visitor.visit(module)

assert visitor.errors == expected

0 comments on commit 315ab61

Please sign in to comment.