mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:35 +00:00
Ignore Pydantic classes when evaluating mutable-class-default
(RUF012
) (#5273)
Closes https://github.com/astral-sh/ruff/issues/5272.
This commit is contained in:
parent
ccf34aae8c
commit
1229600e1d
3 changed files with 34 additions and 1 deletions
|
@ -31,3 +31,15 @@ class C:
|
|||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class D(BaseModel):
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
|
|
|
@ -28,3 +28,12 @@ pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticMod
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the given class is a Pydantic `BaseModel`.
|
||||
pub(super) fn is_pydantic_model(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||
class_def.bases.iter().any(|expr| {
|
||||
semantic.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pydantic", "BaseModel"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ use ruff_macros::{derive_message_formats, violation};
|
|||
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass};
|
||||
use crate::rules::ruff::rules::helpers::{
|
||||
is_class_var_annotation, is_dataclass, is_pydantic_model,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for mutable default values in class attributes.
|
||||
|
@ -57,6 +59,11 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
|
|||
&& !is_immutable_annotation(annotation, checker.semantic())
|
||||
&& !is_dataclass(class_def, checker.semantic())
|
||||
{
|
||||
// Avoid Pydantic models, which end up copying defaults on instance creation.
|
||||
if is_pydantic_model(class_def, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MutableClassDefault, value.range()));
|
||||
|
@ -64,6 +71,11 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt
|
|||
}
|
||||
Stmt::Assign(ast::StmtAssign { value, .. }) => {
|
||||
if is_mutable_expr(value, checker.semantic()) {
|
||||
// Avoid Pydantic models, which end up copying defaults on instance creation.
|
||||
if is_pydantic_model(class_def, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(MutableClassDefault, value.range()));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue