Ignore Pydantic classes when evaluating mutable-class-default (RUF012) (#5273)

Closes https://github.com/astral-sh/ruff/issues/5272.
This commit is contained in:
Charlie Marsh 2023-06-21 19:59:44 -04:00 committed by GitHub
parent ccf34aae8c
commit 1229600e1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 1 deletions

View file

@ -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]] = []

View file

@ -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"])
})
})
}

View file

@ -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()));