Skip to content
Sarj Code Standards

prefer-immutable-module-constant

Module collection constants should use native immutable containers or declare their bindings as Final.

Why

Tuple and frozenset protect collection membership. Final makes a collection's constant binding explicit without changing its runtime type, identity, serialization, or concrete collection API.

Fix

When the concrete list or set API is not part of the contract, use a tuple for ordered values or a frozenset for membership. Preserve a concrete list or set contract with Final[list[T]] or Final[set[T]], and a dictionary contract with Final[dict[K, V]]. Final prevents reassignment in type-checked code but does not freeze contents. Use MappingProxyType when runtime mapping write protection is required.

Examples

Before — flagged Conditional dictionary factory needs constant intent
settings.py
if voice_enabled:
TOOL_LABELS = dict.fromkeys(("call", "transfer"), "Voice")
After — preferred Conditional dictionary factory declares a Final binding
settings.py
from typing import Final
if voice_enabled:
TOOL_LABELS: Final[dict[str, str]] = dict.fromkeys(("call", "transfer"), "Voice")
Before — flagged Mutable collection exposed as a module constant
settings.py
ROLE_NAMES = ["admin", "member"]
After — preferred Immutable tuple used for a module constant
settings.py
ROLE_NAMES = ("admin", "member")
Before — flagged Dictionary exposed as a module constant
settings.py
ROLE_LABELS = {"admin": "Administrator"}
After — preferred Final dictionary binding used for keyed values
settings.py
from typing import Final
ROLE_LABELS: Final[dict[str, str]] = {"admin": "Administrator"}