Skip to content

reassigned-constants

A SCREAMING_CASE name promises a constant, so a module-level one that is reassigned contradicts its own casing.

surfaces a module-level SCREAMING_CASE binding only when it is reassigned (an assignment_count above one or an augmented assignment recorded against the name), leaving a write-once constant silent whatever its value. The fix renames the variable to lowercase or stops reassigning it, work the lint leaves to a future migration pass that picks up its output.

The rule weighs module-level SCREAMING_CASE assignments and annotated assignments, firing only on the reassigned ones. Names on the configurable allow list stay quiet. Dunder-style names (__version__, __all__) fall outside SCREAMING_CASE because they lead with an underscore. Typing constructs from the standard library (TypeVar, ParamSpec, NewType, TypeAliasType) and any binding declared inside an if TYPE_CHECKING: block also stay quiet, since both carry their own semantics distinct from runtime configuration. In-place mutation through a method call or a subscript store stays out of scope, since the binding table records those as reads. The lint is non-rewriting, so the diagnostic surfaces without touching the source.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueToggle the rule on or off
allowlist of names[]Module-level names exempted from the lint

The allow list holds bare names. An entry never produces a lint, even when its shape would otherwise match.

The Canonical Case

A SCREAMING_CASE name reassigned at module scope breaks the immutability its casing promises, so each write surfaces the lint where a write-once constant stays silent.

python
RETRIES = 3
RETRIES = 5

More Examples

A reassigned SCREAMING_CASE annotated assignment draws the diagnostic whether or not a value follows the annotation. The bare FLAG: bool and the valued MAX: int = 1 shapes both feed the annotated-assignment path ahead of the reassignment gate.

A name listed in [tool.prose.rules.reassigned-constants].allow passes through silently even when reassigned, so a blessed LOG_LEVEL publishes at the module root where an unlisted CACHE_DIR reassignment still draws the diagnostic.

No Change

A # fmt: off block carries the same suppression weight against lint diagnostics as against edits, so a reassigned constant inside the block escapes the rule's emission path entirely.

No Change

A clean file with only an allowed __version__ dunder passes through unchanged on every pass, pinning the lint-only shape where the rule emits no edits and no diagnostic.

No Change

Chained assignment (A = B = 1), tuple unpacking (A, B = 1, 2), and attribute targets (FOO.bar = 1) all fall outside the single-target name shape the rule flags, so each line passes through silently.

No Change

The attribute-qualified typing.TYPE_CHECKING guard skips its block exactly as the bare TYPE_CHECKING name does, so a constant inside draws no diagnostic.

No Change

Dunder names such as __version__, __all__, and __author__ are module-level by convention, so the rule leaves them untouched and emits no diagnostic for any of them.

No Change

A SCREAMING_CASE name assigned exactly once is a genuine constant, so the rule stays silent whatever its value shape.

For per-line opt-outs, the Suppression chapter covers the # prose: ignore[reassigned-constants] directive.