Skip to content

Commit

Permalink
Ignore Pydantic classes in RUF012
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jun 21, 2023
1 parent 1eccbbb commit 507fee5
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 1 deletion.
12 changes: 12 additions & 0 deletions crates/ruff/resources/test/fixtures/ruff/RUF012.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]] = []
9 changes: 9 additions & 0 deletions crates/ruff/src/rules/ruff/rules/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
})
})
}
14 changes: 13 additions & 1 deletion crates/ruff/src/rules/ruff/rules/mutable_class_default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -57,13 +59,23 @@ 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()));
}
}
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()));
Expand Down

0 comments on commit 507fee5

Please sign in to comment.