Ensure value of setattr satisfy type of a its target attribute
Project description
mypy-setattr
mypy-setattr is a mypy plugin for literal-name assignments via object.__setattr__ (and setattr).
It checks that the target attribute exists and that the assigned value matches the declared type.
It reports mypy errors for unknown attributes and incompatible value types.
The main use case is __post_init__ in frozen dataclasses.
# example.py
from dataclasses import dataclass, field
@dataclass(frozen=True)
class User:
name: str
slug: str = field(init=False)
def __post_init__(self) -> None:
object.__setattr__(self, "slug", self.name.lower()) # OK
object.__setattr__(self, "slgu", self.name.lower()) # typo: unknown attribute
object.__setattr__(self, "slug", 123) # wrong type: expected str
$ uv run mypy --config-file pyproject.toml example.py
example.py:10: error: attribute "slgu" does not exist on example.User [misc]
example.py:11: error: value of type "Literal[123]?" is not assignable to attribute "slug" on example.User; expected "builtins.str" [misc]
Found 2 errors in 1 file (checked 1 source file)
Motivation
The standard mypy type checker cannot reason about dynamic attribute names, so object.__setattr__
and setattr calls silently bypass attribute existence checks and value type validation. Projects that
lean on frozen dataclasses, __setattr__ overrides, or metaprogramming end up trading
runtime correctness for static flexibility. This plugin restores that safety by enforcing
lookups when the attribute name is a literal string: assignments to Any-typed attributes
remain permissive, while every other annotated type (whether int, dict[str, str], or
Literal["aaa", "bbb"]) is validated. As a result, teams can keep expressive initialisation
patterns without losing coverage from their type checker or relying on fragile helper wrappers.
Whether you call object.__setattr__ or use plain setattr, the same literal-name guarantees
apply—with the object variant remaining the main monitored surface.
Usage
-
Install the package in the environment that runs mypy.
-
Enable the plugin in your
mypy.iniorpyproject.toml:# mypy.ini [mypy] plugins = mypy_setattr.plugin
# pyproject.toml [tool.mypy] plugins = ["mypy_setattr.plugin"]
-
Call
object.__setattr__inside frozen dataclasses whenever you want literal-name type checking—the plugin adds the safety checks automatically. Plainsetattrenjoys the same enforcement if you happen to use it.
Example: frozen dataclass
from dataclasses import dataclass, field
@dataclass(frozen=True)
class User:
name: str
slug: str = field(init=False)
def __post_init__(self) -> None:
object.__setattr__(self, "slug", self.name.lower())
def make_user(raw_name: str) -> User:
return User(raw_name)
When mypy runs with the plugin enabled, it verifies the "slug" assignment is allowed and that the value type
matches the annotated attribute.
Development
- Formatting:
./scripts/format.sh - Format check (CI-safe):
./scripts/format-dry-run.sh - Linting:
./scripts/lint.sh - Tests:
./scripts/test.sh - Type checks:
./scripts/typecheck.sh
Guix environment helpers
manifest.scmlists the packages required to work on this repository via GNU Guix.channels.scmpins the Guix channel revisions used for reproducible environments.guix-shell.shdrops you into a development shell defined bymanifest.scm. This is fast but it is not reproducible, because guix channel is not pinned.# Add extra manifest files or packages ./guix-shell.sh -m path/to/extra-manifest.scm python # Run a single command inside the environment ./guix-shell.sh -- ./scripts/typecheck.shguix-time-machine-shell.shdoes the same, but first time-travels to the pinned channels fromchannels.scm. This command is too late to run frequently, but completely reproducible.# Time-machine shell with additional dependencies ./guix-time-machine-shell.sh -m aux-manifest.scm mypy # One-shot execution against the pinned channels ./guix-time-machine-shell.sh -- ./scripts/typecheck.sh
License
This project is released under the GNU Affero General Public License v3.0. Using mypy-setattr
as a development-only dependency—for example, to improve type checking in CI—does not infect your product or
runtime artifacts with AGPL obligations; the copyleft terms apply to the plugin itself and any modifications
you make to it. If you embed the plugin directly into a distributed product, ensure that distribution complies
with AGPL-3.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mypy_setattr-0.1.1.tar.gz.
File metadata
- Download URL: mypy_setattr-0.1.1.tar.gz
- Upload date:
- Size: 8.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5cc4a64d620e69ab74636db681275cd4ee63fb9b7b95881da8e777e2d78e006
|
|
| MD5 |
778e61937491f449d9ca990796c47603
|
|
| BLAKE2b-256 |
6002608f3a1218f74b262c80b827bc5d2c39bf85fb21eed44bacfa346841a3e2
|
Provenance
The following attestation bundles were made for mypy_setattr-0.1.1.tar.gz:
Publisher:
tagpr.yml on ROCKTAKEY/mypy-setattr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mypy_setattr-0.1.1.tar.gz -
Subject digest:
f5cc4a64d620e69ab74636db681275cd4ee63fb9b7b95881da8e777e2d78e006 - Sigstore transparency entry: 947945693
- Sigstore integration time:
-
Permalink:
ROCKTAKEY/mypy-setattr@c9d1d910ae339083ae7c36ac5046b75dbcd0dd81 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ROCKTAKEY
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
tagpr.yml@c9d1d910ae339083ae7c36ac5046b75dbcd0dd81 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mypy_setattr-0.1.1-py3-none-any.whl.
File metadata
- Download URL: mypy_setattr-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd3fd9456e0afaae1e99cd072c89d42b0212601b887b8c56db945e49e88e5926
|
|
| MD5 |
4fa1100742801f9974cc7cc78a974647
|
|
| BLAKE2b-256 |
465b904ff833172758a60b498cb9f0fd000c5b15589a73f3b66c02801863ca99
|
Provenance
The following attestation bundles were made for mypy_setattr-0.1.1-py3-none-any.whl:
Publisher:
tagpr.yml on ROCKTAKEY/mypy-setattr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mypy_setattr-0.1.1-py3-none-any.whl -
Subject digest:
cd3fd9456e0afaae1e99cd072c89d42b0212601b887b8c56db945e49e88e5926 - Sigstore transparency entry: 947945746
- Sigstore integration time:
-
Permalink:
ROCKTAKEY/mypy-setattr@c9d1d910ae339083ae7c36ac5046b75dbcd0dd81 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ROCKTAKEY
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
tagpr.yml@c9d1d910ae339083ae7c36ac5046b75dbcd0dd81 -
Trigger Event:
push
-
Statement type: