Skip to content

Enable RUF012 ClassVar annotation#845

Merged
johanlundberg merged 4 commits intomainfrom
ylle-ruf012
Feb 13, 2026
Merged

Enable RUF012 ClassVar annotation#845
johanlundberg merged 4 commits intomainfrom
ylle-ruf012

Conversation

@helylle
Copy link
Contributor

@helylle helylle commented Feb 13, 2026

Enable ruff rule RUF012: Mutable class attributes should be annotated with typing.ClassVar

Summary

Enable the RUF012 lint rule, which requires mutable class-level attributes to be explicitly annotated
with ClassVar. This was previously ignored in ruff.toml. All 55 violations across 8 files are resolved.

Motivation

ClassVar is a type-checking annotation that explicitly marks attributes as belonging to the class rather
than instances. Without it, type checkers may treat mutable class attributes as instance attributes, and
the shared-state semantics are not visible to readers or tooling. This change documents existing intent
without altering runtime behavior.

Alternatives considered

  • Move attributes to __init__ (per-instance): Wrong for caches that intentionally share state across
    instances, and unnecessary overhead for class-level configuration constants that are never mutated.
  • Use immutable types (tuple instead of list): Good semantic fit for the configuration lists
    (whitelist_set_attrs, add_keys, etc.) since they're never mutated, but doesn't work for caches
    which must be mutable dicts. Would be a larger refactor for limited additional safety.
  • ClassVar[Final[...]] (Python 3.13 supports nesting): Doesn't fit — the configuration lists are
    overridden by subclasses (which Final prohibits), and the caches are reassigned at runtime.

ClassVar is the pragmatic choice: it matches what the code already does, adds type-checking clarity,
and requires minimal changes.

Changes by category

1. Cache singletons — ClassVar annotation (3 files)

Shared mutable dicts that cache database connections or Celery task resources. ClassVar correctly
documents the intentional class-level sharing.

  • AsyncClientCache._clients in userdb/db/async_db.py
  • MongoClientCache._clients in userdb/db/sync_db.py
  • MessageSender._cache_store in workers/msg/tasks.py

2. AttributeFetcher configuration lists (2 files, 34 violations)

The base class and 17 subclasses define whitelist_set_attrs / whitelist_unset_attrs as class-level
configuration. These are read at runtime but never mutated. Updated the base class in ams/common.py
and all subclasses in ams/__init__.py.

3. GenericFilterDict filter lists (1 file, 14 violations)

The base class and ~10 subclasses define add_keys / remove_keys to configure dict filtering
behavior. Updated base class and all subclasses in userdb/support/models.py.

4. Test mock data (1 file, 2 violations)

MockedScimAPIMixin.get_invite_response and post_user_response are class-level test fixture dicts.
Annotated with ClassVar[dict[str, Any]] in common/clients/scim_client/testing.py.

5. Pydantic false positive (1 file)

LadokConfig.dev_fake_users_in: list[str] = [] is a Pydantic field, but ruff can't detect the
BaseModel ancestry through 3 levels of inheritance. Adding ClassVar would break Pydantic
serialization. Fixed by using Field(default_factory=list) — the idiomatic Pydantic pattern for
mutable defaults — which doesn't trigger the rule.

6. Dead code removal (1 file)

Removed CacheMDB._init_collections: set[str] in workers/msg/cache.py. This was an artifact from
a 2021 refactor (commit 0c441c4a9) that modernized index management to use BaseDB.setup_indexes().
The old ensure_indices() method was removed but the tracking attribute was left behind. It had zero
references in the codebase.

@sonarqubecloud
Copy link

@helylle helylle marked this pull request as ready for review February 13, 2026 12:06
@johanlundberg johanlundberg merged commit 1754a7e into main Feb 13, 2026
12 checks passed
@johanlundberg johanlundberg deleted the ylle-ruf012 branch February 13, 2026 12:22
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.

2 participants