mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 21:40:51 -05:00
[ruff] Add exception for ctypes.Structure._fields_ (RUF012) (#22559)
Closes #22166 ## Summary Adds an exception for `ctypes.Structure._fields_` to the rule RUF012 as it has it's own way of enforcing immutability: > The fields class variable can only be set once. Later assignments will raise an [AttributeError](https://docs.python.org/3/library/exceptions.html#AttributeError). --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
@@ -138,3 +138,15 @@ class AWithQuotes:
|
||||
class P:
|
||||
class_variable: ClassVar[list] = [10, 20, 30, 40, 50]
|
||||
class_variable = [*class_variable[0::1], *class_variable[2::3]]
|
||||
|
||||
|
||||
import ctypes
|
||||
# Lint should trigger RUF012 only for the `test` field and not the `_fields_`
|
||||
class S(ctypes.Structure):
|
||||
test = [""]
|
||||
_fields_ = [
|
||||
("attr_set", ctypes.c_uint64),
|
||||
("attr_clr", ctypes.c_uint64),
|
||||
("propagation", ctypes.c_uint64),
|
||||
("userns_fd", ctypes.c_uint64),
|
||||
]
|
||||
|
||||
@@ -242,3 +242,23 @@ pub(super) fn is_descriptor_class(func: &Expr, semantic: &SemanticModel) -> bool
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the class has `ctypes.Structure` as a base
|
||||
/// and the first target is the `_fields_` attribute.
|
||||
pub(super) fn is_ctypes_structure_fields(
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
targets: &[Expr],
|
||||
) -> bool {
|
||||
let is_ctypes_structure =
|
||||
analyze::class::any_qualified_base_class(class_def, semantic, &|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["ctypes", "Structure"])
|
||||
});
|
||||
|
||||
let is_fields = matches!(
|
||||
targets.first(),
|
||||
Some(Expr::Name(ast::ExprName { id, .. })) if id == "_fields_"
|
||||
);
|
||||
|
||||
is_ctypes_structure && is_fields
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ use ruff_text_size::Ranged;
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::ruff::helpers::{
|
||||
dataclass_kind, has_default_copy_semantics, is_class_var_annotation, is_final_annotation,
|
||||
is_special_attribute,
|
||||
dataclass_kind, has_default_copy_semantics, is_class_var_annotation,
|
||||
is_ctypes_structure_fields, is_final_annotation, is_special_attribute,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
@@ -140,6 +140,14 @@ pub(crate) fn mutable_class_default(checker: &Checker, class_def: &ast::StmtClas
|
||||
.is_some_and(|name| class_var_targets.contains(&name.id))
|
||||
}) && is_mutable_expr(value, checker.semantic())
|
||||
{
|
||||
// The `_fields_` property of a `ctypes.Structure` base class has its
|
||||
// immutability enforced by the base class itself which will throw an error if
|
||||
// it's set a second time
|
||||
// See: https://docs.python.org/3/library/ctypes.html#ctypes.Structure._fields_
|
||||
if is_ctypes_structure_fields(class_def, checker.semantic(), targets) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid, e.g., Pydantic and msgspec models, which end up copying defaults on instance creation.
|
||||
if has_default_copy_semantics(class_def, checker.semantic()) {
|
||||
return;
|
||||
|
||||
@@ -117,3 +117,14 @@ RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
134 | final_variable_without_subscript: 'Final' = []
|
||||
| ^^
|
||||
|
|
||||
|
||||
RUF012 Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
--> RUF012.py:146:12
|
||||
|
|
||||
144 | # Lint should trigger RUF012 only for the `test` field and not the `_fields_`
|
||||
145 | class S(ctypes.Structure):
|
||||
146 | test = [""]
|
||||
| ^^^^
|
||||
147 | _fields_ = [
|
||||
148 | ("attr_set", ctypes.c_uint64),
|
||||
|
|
||||
|
||||
Reference in New Issue
Block a user