Skip to main content

CI PyPI

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: 2026.9.1  # pin to a release tag
    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.rst. Semver releases through 2.3.0 are recorded in CHANGELOG.md.

Release files for no-defaults 2026.9.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for no-defaults 2026.9.1
File Size Uploaded
no_defaults-2026.9.1.tar.gz 350.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for no-defaults 2026.9.1
File Interpreter ABI Platform
no_defaults-2026.9.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
no_defaults-2026.9.1-py3-none-manylinux_2_39_x86_64.whl Python 3 none Linux glibc 2.39+ x86-64 Details
no_defaults-2026.9.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 8.5 MB

Release files / no_defaults-2026.9.1.tar.gz

Download URL no_defaults-2026.9.1.tar.gz
Size 350.7 kB
Tags Source
SHA-256 checksum
How to use checksums
4896a7c80e6b4aeb746b6cf01f19dec5920a42e2f95763913227ffb27b598fe1
BLAKE2b-256 checksum
How to use checksums
7fe41d6ae2ca92de1c81f13c33f9288dba02c016e3e43b794e9017feea8dac26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / no_defaults-2026.9.1-py3-none-win_amd64.whl

Download URL no_defaults-2026.9.1-py3-none-win_amd64.whl
Size 2.6 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
7ac612f2d54a157e3888ff9cdbd3e8e8d8a3901212a6e7efe32a3ae6870654a1
BLAKE2b-256 checksum
How to use checksums
12fa3486fe771b5e76cfca6a7a3c1ccb9c81c08881cfae5aea8f766aff6af05b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / no_defaults-2026.9.1-py3-none-manylinux_2_39_x86_64.whl

Download URL no_defaults-2026.9.1-py3-none-manylinux_2_39_x86_64.whl
Size 2.9 MB
Tags Linux glibc 2.39+ x86-64 Python 3
SHA-256 checksum
How to use checksums
898c2fbf07992438b1a50f21f42d4cf6109cd2a2887d975b3bb36c1d2aa3f5c4
BLAKE2b-256 checksum
How to use checksums
c7af4e98e63a8fd6ce61a21493b094078d675d678447049686868b5877edc726
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / no_defaults-2026.9.1-py3-none-macosx_11_0_arm64.whl

Download URL no_defaults-2026.9.1-py3-none-macosx_11_0_arm64.whl
Size 2.7 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
84807575eca53f72547a0d03ab518d8ef34541698ccc9c3114e8671469f07a49
BLAKE2b-256 checksum
How to use checksums
ee7fd167db7f3500793bbaa730710dea7eceab47e0ddb748c040aaa7b3892e36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2026.9.1 This release

4 release files

2.3.0

4 release files

2.1.0

4 release files

2.0.0

4 release files

1.1.0

4 release files

1.0.1

4 release files

1.0.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page