Metadata-Version: 2.4
Name: no-defaults
Version: 1.0.1
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Quality Assurance
License-File: LICENSE
Summary: A fast Python linter that forbids function and dataclass defaults.
Keywords: lint,defaults,dataclasses,pre-commit
Home-Page: https://github.com/adamtheturtle/no-defaults
Author-email: Adam Dangoor <adamdangoor@gmail.com>
License-Expression: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/adamtheturtle/no-defaults/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/adamtheturtle/no-defaults/issues
Project-URL: Source, https://github.com/adamtheturtle/no-defaults

[![CI](https://github.com/adamtheturtle/no-defaults/actions/workflows/ci.yml/badge.svg)](https://github.com/adamtheturtle/no-defaults/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/no-defaults.svg)](https://pypi.org/project/no-defaults/)

# no-defaults

A fast, standalone Python linter that forbids defaults in function signatures and dataclasses.
It is implemented in Rust and parses Python with Ruff's parser.

```python
from dataclasses import dataclass, field

def connect(timeout=30):  # NOD001
    pass

@dataclass
class Job:
    retries: int = 3  # NOD001
    tags: list[str] = field(default_factory=list)  # NOD001
```

## Installation

```console
uv tool install no-defaults
```

## Usage

```console
no-defaults .
no-defaults --fix .
no-defaults --diff .
no-defaults --private-only src tests
no-defaults --output-format json .
no-defaults --output-format github .
no-defaults --show-settings src/package/api.py
```

Exit status is `0` when clean, `1` when violations are found, and `2` for an operational error.
Directories are walked in parallel, respecting `.gitignore` and standard hidden-file filters.
Diagnostics use Ruff's concise format and include a summary:

```text
src/example.py:4:17: NOD001 parameter `timeout` of function `connect` has a default
Found 1 error.
```

Pass `--fix` to remove defaults automatically. Function parameters and ordinary dataclass assignments become required. For `field(...)`, only the positional default, `default=`, or `default_factory=` argument is removed; other metadata is preserved:

```python
retries: int = field(default=3, kw_only=True)
# becomes
retries: int = field(kw_only=True)
```

After a successful fix, the command exits with status `0` and prints a Ruff-style summary such as `Found 2 errors (2 fixed, 0 remaining).` Writes use an atomic same-directory replacement. `--diff` prints a unified diff, writes nothing, and exits with status `1` when changes are available.

The default `full` output includes source excerpts and carets. `concise` emits one diagnostic per line, `json` emits a machine-readable array, and `github` emits workflow commands for GitHub Actions annotations.

The linter detects defaults on positional-only, positional-or-keyword, and keyword-only parameters.
For classes decorated with `@dataclass` or `@dataclasses.dataclass`, it detects assigned defaults plus `field(default=...)` and `field(default_factory=...)` in the class body.
`ClassVar` assignments are ignored because they are not dataclass fields, and annotated assignments inside method bodies are ignored because they are locals.

Suppress an individual violation with either a blanket `# noqa` or the rule-specific `# noqa: NOD001` on the line containing the default:

```python
def compatible(timeout=30):  # noqa: NOD001
    pass
```

Suppress the rule for an entire file with `# ruff: noqa` or `# ruff: noqa: NOD001`.

## Configuration

Configuration lives in `pyproject.toml`:

```toml
[tool.no_defaults]
private_only = true

[tool.no_defaults.per_file_enforcement]
"tests/**" = "all"
"src/**" = "private"
```

Private means a name that starts with one underscore. In private-only mode, the rule applies to private modules and packages, private functions and methods, all members of private classes, and private dataclass fields. For example, all defaults in `_module.py` and `_package/module.py` are checked. Dunder names such as `__init__.py` are not considered private by themselves.

`per_file_enforcement` accepts Ruff-style glob patterns relative to the directory containing `pyproject.toml`. Use `"all"` to reject every default in matching files or `"private"` to reject defaults only in private scopes. Patterns without a slash match file names at any depth. An initial `!` negates a pattern. If multiple patterns match, the most specific pattern wins; equally specific patterns are resolved lexicographically so results never depend on TOML table order.

The `--private-only` CLI flag overrides the configuration for every checked file.

Like Ruff, `no-defaults` discovers the closest `pyproject.toml` containing `[tool.no_defaults]` separately for each file. This supports monorepos with nested configuration; files without a local table continue searching parent directories.

## Performance

An optimized `1.0.0` development build checked a pinned Typeshed checkout containing 5,368 Python and stub files (12.5 MiB) in a median 0.29 seconds across five warm runs on an Apple Silicon Mac, or roughly 18,000 files per second. It produced 50,974 diagnostics; an earlier full-output measurement used approximately 41 MiB maximum RSS.

CodSpeed runs parser-and-rule benchmarks for representative modules on every pull request and every push to `main`, providing stable comparisons against the default-branch baseline. The scheduled `Typeshed benchmark` remains as a real-project correctness and gross-regression check.

## pre-commit

```yaml
repos:
  - repo: https://github.com/adamtheturtle/no-defaults
    rev: v1.0.0
    hooks:
      - id: no-defaults
```

## License

MIT

See [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and [CHANGELOG.md](CHANGELOG.md).

