no-defaults
A fast, standalone Python linter that forbids defaults in function signatures, dataclasses, and pydantic models — and removes them for you, updating the call sites in the files you checked so the code still runs. It is implemented in Rust and parses Python with Ruff's parser.
from dataclasses import dataclass, field
from pydantic import BaseModel, Field
def connect(timeout=30): # NOD001
pass
@dataclass
class Job:
retries: int = 3 # NOD001
tags: list[str] = field(default_factory=list) # NOD001
class Request(BaseModel):
method: str = "GET" # NOD001
headers: dict[str, str] = Field(default_factory=dict) # NOD001
url: str = Field(..., description="required, so no default to remove")
Installation
uv tool install no-defaults
The Python package installs ty alongside
no-defaults. If you install the Rust binary with Cargo instead, install ty
separately and make it available on PATH; cross-package callback resolution
uses the supported ty server interface and fails explicitly when it is absent.
Usage
no-defaults . # check
no-defaults --fix . # remove defaults, update call sites
no-defaults --diff . # preview the same edits, write nothing
no-defaults --private-only src tests # only underscore-prefixed names
no-defaults --private-only --respect-reexports src # ... minus ones re-exported publicly
no-defaults --output-format json . # also: full, concise, github
no-defaults --show-settings src/package/api.py # the settings that apply to a file
Exit status is 0 when clean, 1 when violations are found, and 2 for an operational error — including a path named on the command line that is not a .py or .pyi file, so a mistyped path fails rather than reporting a clean run over nothing. Directories are walked in parallel for Python files, respecting .gitignore and hidden-file filters.
src/example.py:4:21: NOD001 parameter `timeout` of function `connect` has a default
Found 1 error.
| code | meaning |
|---|---|
NOD000 |
a file the parser rejected. The run carries on so one bad file does not hide the rest, and --fix leaves that file alone and exits 1 |
NOD001 |
a default |
NOD002 |
a # noqa naming NOD001 that suppresses nothing. --fix removes it |
--fix
--fix removes the default and then passes it explicitly at every call it can resolve in the files you asked it to check, so the argument that just became required is still supplied:
def connect(host, timeout=30): # becomes def connect(host, timeout):
...
connect("example.com") # becomes connect("example.com", timeout=30)
connect("example.com", 5) # already supplies it, so it is left alone
Dataclass and model fields become required at construction the same way, and field(...) keeps its other metadata: field(default=3, kw_only=True) becomes field(kw_only=True).
Nothing is guessed. A call is left alone, and named in a warning, when it cannot be tied to the definition that changed, when the removed default is not a literal, or in any of the other cases listed in the reference. Defaults in .pyi stubs are reported but never removed, since a stub describes a signature rather than supplying one.
Two things --fix cannot reach in general: callers outside the files you checked, and calls made dynamically. It reads imported Python packages from PYTHONPATH and the active virtual environment and asks ty to resolve statically visible framework callbacks: a default omitted by one of those dependency calls is retained, while the dependency is never imported, diagnosed, or edited. A warning after fixing covers callers it still cannot see, and your test suite is what confirms the result. Run it over the whole project at once, and prefer private_only with respect_reexports, where the symbols it touches have no callers outside the project.
Suppressing
# noqa: NOD001 — or a blanket # noqa — on the line holding the default suppresses it. On a def or class line it covers that whole signature or class body, so a multi-line signature needs one directive rather than one per parameter. # ruff: noqa: NOD001 on its own line covers a whole file. See the reference for the exact scoping rules.
NOD001 is not a Ruff rule, so Ruff reports every such comment as RUF102 Invalid rule code and ruff check --fix deletes it, leaving the violation behind. Register the prefix so Ruff leaves the suppressions alone:
[tool.ruff]
lint.external = [ "NOD" ]
Configuration
Configuration lives in pyproject.toml. Like Ruff, no-defaults finds the closest one containing [tool.no_defaults] separately for each file, so nested configuration in a monorepo works. An unrecognised key is an error, so a misspelled option fails the run rather than silently leaving the defaults in place.
[tool.no_defaults]
# Check only private names: private modules and packages, private functions
# and methods, all members of private classes, and private fields.
private_only = true
# ... but treat what a package's `__init__.py` re-exports as public API.
respect_reexports = true
# Bases whose annotated assignments are fields, as pydantic's are. Setting this
# replaces the default rather than adding to it.
field_base_classes = [ "pydantic.BaseModel" ]
# Ruff-style globs, relative to this file. "all", "private", or "none".
[tool.no_defaults.per_file_enforcement]
"tests/**" = "all"
"src/**" = "private"
The reference covers what counts as private, what counts as a re-export and the limits of detecting one, and how overlapping per_file_enforcement patterns are resolved.
pre-commit
repos:
- repo: https://github.com/adamtheturtle/no-defaults
rev: v2.3.0
hooks:
- id: no-defaults
pre-commit passes only the changed files, so a call in a file that did not change is not updated. Run no-defaults --fix . by hand when you are removing a default that is called from elsewhere.
License
MIT
See docs/reference.md for the full behaviour, and CONTRIBUTING.md, SECURITY.md, and CHANGELOG.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 no_defaults-2.3.0.tar.gz.
File metadata
- Download URL: no_defaults-2.3.0.tar.gz
- Upload date:
- Size: 318.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
402c8eb9c874b774b6300252800c004fa61b0a4ac036973eafa875e78d0c4bb2
|
|
| MD5 |
d206630e2ef86caea19612cb8e5fc9c9
|
|
| BLAKE2b-256 |
fbc2b614a65ae8a75aff88141eba3e1966edf3953ecdeb0d265b717d014a64a2
|
Provenance
The following attestation bundles were made for no_defaults-2.3.0.tar.gz:
Publisher:
release.yml on adamtheturtle/no-defaults
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
no_defaults-2.3.0.tar.gz -
Subject digest:
402c8eb9c874b774b6300252800c004fa61b0a4ac036973eafa875e78d0c4bb2 - Sigstore transparency entry: 2661394838
- Sigstore integration time:
-
Permalink:
adamtheturtle/no-defaults@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/adamtheturtle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file no_defaults-2.3.0-py3-none-win_amd64.whl.
File metadata
- Download URL: no_defaults-2.3.0-py3-none-win_amd64.whl
- Upload date:
- Size: 2.6 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1c30cb8bc7bff71f575ca3607f3b92abd643bcc333df2ff4f9f299a32815967
|
|
| MD5 |
b8f2313e87d398fb603394ce9352217c
|
|
| BLAKE2b-256 |
23fa8c9f40e59cf7159ab4fb19a64f079c3800f6425b2d8752085601ac665916
|
Provenance
The following attestation bundles were made for no_defaults-2.3.0-py3-none-win_amd64.whl:
Publisher:
release.yml on adamtheturtle/no-defaults
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
no_defaults-2.3.0-py3-none-win_amd64.whl -
Subject digest:
f1c30cb8bc7bff71f575ca3607f3b92abd643bcc333df2ff4f9f299a32815967 - Sigstore transparency entry: 2661394925
- Sigstore integration time:
-
Permalink:
adamtheturtle/no-defaults@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/adamtheturtle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file no_defaults-2.3.0-py3-none-manylinux_2_39_x86_64.whl.
File metadata
- Download URL: no_defaults-2.3.0-py3-none-manylinux_2_39_x86_64.whl
- Upload date:
- Size: 2.9 MB
- Tags: Python 3, manylinux: glibc 2.39+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7e94852c4c5855470b460854908be0ff662538c896fae5844d13a54baacf21c
|
|
| MD5 |
444b8c4320ab7dabe97964a16c9f2de4
|
|
| BLAKE2b-256 |
1b5bf5a558b182221d3ed382d4180e5824dac43b7705c98e9b0efc3907c95a51
|
Provenance
The following attestation bundles were made for no_defaults-2.3.0-py3-none-manylinux_2_39_x86_64.whl:
Publisher:
release.yml on adamtheturtle/no-defaults
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
no_defaults-2.3.0-py3-none-manylinux_2_39_x86_64.whl -
Subject digest:
b7e94852c4c5855470b460854908be0ff662538c896fae5844d13a54baacf21c - Sigstore transparency entry: 2661394887
- Sigstore integration time:
-
Permalink:
adamtheturtle/no-defaults@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/adamtheturtle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file no_defaults-2.3.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: no_defaults-2.3.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.7 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce5fd1412edb6e77f26196520d03d7ff25e93fcd4cfa13457a7032d68bbd3283
|
|
| MD5 |
28ece7e41b5738c14fc2f3c44296cc63
|
|
| BLAKE2b-256 |
38b4ccc2bb556fa5f15310f889dee6e85b117b5582ad53795189a7d2d52f2cd8
|
Provenance
The following attestation bundles were made for no_defaults-2.3.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on adamtheturtle/no-defaults
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
no_defaults-2.3.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
ce5fd1412edb6e77f26196520d03d7ff25e93fcd4cfa13457a7032d68bbd3283 - Sigstore transparency entry: 2661394974
- Sigstore integration time:
-
Permalink:
adamtheturtle/no-defaults@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Branch / Tag:
refs/tags/v2.3.0 - Owner: https://github.com/adamtheturtle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e30c7dc739c91a8357c090b399d16fd6aba17a9 -
Trigger Event:
push
-
Statement type: