diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py index 2317e6608d..eb27b31726 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py @@ -81,28 +81,42 @@ class H(BaseModel): final_variable: Final[list[int]] = [] +from pydantic.v1 import BaseModel as V1BaseModel + + +class I(V1BaseModel): + mutable_default: list[int] = [] + + +from pydantic.v1.generics import GenericModel + + +class J(GenericModel): + mutable_default: list[int] = [] + + def sqlmodel_import_checker(): from sqlmodel.main import SQLModel - class I(SQLModel): + class K(SQLModel): id: int mutable_default: list[int] = [] from sqlmodel import SQLModel -class J(SQLModel): +class L(SQLModel): id: int name: str -class K(SQLModel): +class M(SQLModel): id: int i_s: list[J] = [] -class L(SQLModel): +class N(SQLModel): id: int - i_j: list[K] = list() + i_j: list[L] = list() # Lint should account for deferred annotations # See https://github.com/astral-sh/ruff/issues/15857 diff --git a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs index 19894bfa44..b3c813186a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs @@ -165,7 +165,7 @@ pub(super) fn dataclass_kind<'a>( /// Returns `true` if the given class has "default copy" semantics. /// -/// For example, Pydantic `BaseModel` and `BaseSettings` subclassses copy attribute defaults on +/// For example, Pydantic `BaseModel` and `BaseSettings` subclasses copy attribute defaults on /// instance creation. As such, the use of mutable default values is safe for such classes. pub(super) fn has_default_copy_semantics( class_def: &ast::StmtClassDef, @@ -174,7 +174,16 @@ pub(super) fn has_default_copy_semantics( analyze::class::any_qualified_base_class(class_def, semantic, &|qualified_name| { matches!( qualified_name.segments(), - ["pydantic", "BaseModel" | "BaseSettings" | "BaseConfig"] + [ + "pydantic", + "BaseModel" | "RootModel" | "BaseSettings" | "BaseConfig" + ] | ["pydantic", "generics", "GenericModel"] + | [ + "pydantic", + "v1", + "BaseModel" | "BaseSettings" | "BaseConfig" + ] + | ["pydantic", "v1", "generics", "GenericModel"] | ["pydantic_settings", "BaseSettings"] | ["msgspec", "Struct"] | ["sqlmodel", "SQLModel"] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF012_RUF012.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF012_RUF012.py.snap index 829a3b4cd7..c83fb86785 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF012_RUF012.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF012_RUF012.py.snap @@ -31,78 +31,78 @@ RUF012.py:25:26: RUF012 Mutable class attributes should be annotated with `typin 27 | class_variable: ClassVar[list[int]] = [] | -RUF012.py:89:38: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` - | -87 | class I(SQLModel): -88 | id: int -89 | mutable_default: list[int] = [] - | ^^ RUF012 -90 | -91 | from sqlmodel import SQLModel - | - -RUF012.py:114:36: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:103:38: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -112 | } -113 | -114 | mutable_default: 'list[int]' = [] +101 | class K(SQLModel): +102 | id: int +103 | mutable_default: list[int] = [] + | ^^ RUF012 +104 | +105 | from sqlmodel import SQLModel + | + +RUF012.py:128:36: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` + | +126 | } +127 | +128 | mutable_default: 'list[int]' = [] | ^^ RUF012 -115 | immutable_annotation: 'Sequence[int]'= [] -116 | without_annotation = [] +129 | immutable_annotation: 'Sequence[int]'= [] +130 | without_annotation = [] | -RUF012.py:115:44: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:129:44: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -114 | mutable_default: 'list[int]' = [] -115 | immutable_annotation: 'Sequence[int]'= [] +128 | mutable_default: 'list[int]' = [] +129 | immutable_annotation: 'Sequence[int]'= [] | ^^ RUF012 -116 | without_annotation = [] -117 | class_variable: 'ClassVar[list[int]]' = [] +130 | without_annotation = [] +131 | class_variable: 'ClassVar[list[int]]' = [] | -RUF012.py:116:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:130:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -114 | mutable_default: 'list[int]' = [] -115 | immutable_annotation: 'Sequence[int]'= [] -116 | without_annotation = [] +128 | mutable_default: 'list[int]' = [] +129 | immutable_annotation: 'Sequence[int]'= [] +130 | without_annotation = [] | ^^ RUF012 -117 | class_variable: 'ClassVar[list[int]]' = [] -118 | final_variable: 'Final[list[int]]' = [] +131 | class_variable: 'ClassVar[list[int]]' = [] +132 | final_variable: 'Final[list[int]]' = [] | -RUF012.py:117:45: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:131:45: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -115 | immutable_annotation: 'Sequence[int]'= [] -116 | without_annotation = [] -117 | class_variable: 'ClassVar[list[int]]' = [] +129 | immutable_annotation: 'Sequence[int]'= [] +130 | without_annotation = [] +131 | class_variable: 'ClassVar[list[int]]' = [] | ^^ RUF012 -118 | final_variable: 'Final[list[int]]' = [] -119 | class_variable_without_subscript: 'ClassVar' = [] +132 | final_variable: 'Final[list[int]]' = [] +133 | class_variable_without_subscript: 'ClassVar' = [] | -RUF012.py:118:42: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:132:42: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -116 | without_annotation = [] -117 | class_variable: 'ClassVar[list[int]]' = [] -118 | final_variable: 'Final[list[int]]' = [] +130 | without_annotation = [] +131 | class_variable: 'ClassVar[list[int]]' = [] +132 | final_variable: 'Final[list[int]]' = [] | ^^ RUF012 -119 | class_variable_without_subscript: 'ClassVar' = [] -120 | final_variable_without_subscript: 'Final' = [] +133 | class_variable_without_subscript: 'ClassVar' = [] +134 | final_variable_without_subscript: 'Final' = [] | -RUF012.py:119:52: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:133:52: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -117 | class_variable: 'ClassVar[list[int]]' = [] -118 | final_variable: 'Final[list[int]]' = [] -119 | class_variable_without_subscript: 'ClassVar' = [] +131 | class_variable: 'ClassVar[list[int]]' = [] +132 | final_variable: 'Final[list[int]]' = [] +133 | class_variable_without_subscript: 'ClassVar' = [] | ^^ RUF012 -120 | final_variable_without_subscript: 'Final' = [] +134 | final_variable_without_subscript: 'Final' = [] | -RUF012.py:120:49: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:134:49: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -118 | final_variable: 'Final[list[int]]' = [] -119 | class_variable_without_subscript: 'ClassVar' = [] -120 | final_variable_without_subscript: 'Final' = [] +132 | final_variable: 'Final[list[int]]' = [] +133 | class_variable_without_subscript: 'ClassVar' = [] +134 | final_variable_without_subscript: 'Final' = [] | ^^ RUF012 |