Skip to content

docs: add a Constants section to the Python code conventions - #75

Merged
eirkkr merged 6 commits into
mainfrom
docs/constants-convention
Aug 25, 2026
Merged

eirkkr merged 6 commits into
mainfrom
docs/constants-convention

Conversation

@eirkkr

@eirkkr eirkkr commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Module-level constants were everywhere in consumer code with no rule saying so, which left "should these be in a class, or an enum?" open every time it came up. This records the answer.

  • Two cases, both plain modules. A constant used by one module gets a leading underscore at the top of it; a group used by several gets a module of its own, imported by name so callers read coll_names.HTTP_LOG. Wrapping either in a class or a frozen dataclass buys nothing — a module is already a namespace, which is how errno.ENOENT and string.punctuation work.
  • Three tests for reaching past that to an Enum. Something must handle every value and fail to type-check when one is added; something loops over them or asks whether a value is one of them; or an argument should be typed as one of them. Absent all three it is work for nothing, and a frozenset is enough for a pure membership test.
  • One trap no container fixes. A value written on both sides of a language boundary — SAVE = "save" in Python, name="save" in a template — has nothing connecting the two copies, so a rename means Python silently stops finding the button. Hand the constants to the template engine instead.

The section states only rules that rely on review, per the file's own preamble: an earlier draft argued that a function-local should not wear UPPER_SNAKE, which is ruff's N806 under select = ["ALL"], and that came back out.

Test plan

  • awk 'length>80' over the file is clean — prose wraps at 80
  • Every example checked against the real module it names, rather than written from memory: signal.SIGTERM turned out to be a Signals IntEnum member, so it was swapped for string.punctuation to keep all three examples plain constants
  • Section placed between Member ordering and Enum values, and the file's intro line lists it

eirkkr added 6 commits August 25, 2026 21:03
Module-level constants were everywhere in consumer code without a rule
saying so, which left "should these be in a class or an enum?" open every
time it came up. Record the two shapes that cover it — private to one
module, and a namespace module holding a shared vocabulary — and the
three properties that earn an Enum instead: exhaustive branching,
iteration or membership, and a parameter typed "one of these".

Absent all three an enum is churn, and a frozenset is the right container
for a pure membership test.

Also record the case the container choice does not fix: a constant spelled
in two languages, where the duplicate fails silently.
The section restated a rule ruff already enforces - an UPPER_SNAKE local
in a function is N806, and consumers select ALL - in a file whose own
preamble says it holds the rules that rely on review. Drop it, and
tighten the rest: the enum test reads as one sentence rather than a
numbered list, and the two shapes lose their second explanations.
Three bare module names read as if they might be the repo's own, so the
precedent they were carrying did not land. Say where they come from, and
name a member of each rather than the module, which also shows the dotted
access the bullet is about.

Swap signal for string while here. signal.SIGTERM is a Signals IntEnum
member, not a plain int - it is iterated and mapped back from number to
name, so it is an example of the enum test two paragraphs down rather
than of a plain namespace module.
The section was written in shorthand - shapes, shared vocabulary,
authored-value surface, branched on exhaustively, churn - which a reader
has to decode before they reach the rule underneath. Say the same three
rules in ordinary words, and turn the enum test back into a list, since
it is three separate questions and reads as one run-on sentence
otherwise.

No rule changed. The examples, the two cases and the cross-language
warning are the same as before.
The two examples were shortened when the section was written, which
disguised nothing - the names never carried an identity - and left them
reading as module names nobody would write. Use the real ones, matching
the Imports section above, which quotes its examples as they are.
The paragraph opened on the abstraction - a problem no container solves,
a value written in two languages - and reached the actual case in a
subclause, so a reader who had not already met the problem could not tell
what it was describing. Put the submit button first, with both halves of
the duplicate spelled out, and keep the rule for the end.

Split the fix into its own paragraph while here, since it is the part a
reader acts on.
@eirkkr
eirkkr merged commit 3088046 into main Aug 25, 2026
1 check passed
@eirkkr
eirkkr deleted the docs/constants-convention branch August 25, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant