Skip to content

ndcorder/pyproject-lint

Repository files navigation

pyproject-lint

A dedicated linter and auto-fixer for pyproject.toml files.

Validates your pyproject.toml against PEP 517, 518, 621, and 735 standards, checks backend-specific schemas (setuptools, Poetry, Hatch, uv), audits dependencies, and validates classifiers — all in under 100ms.

Why?

pyproject.toml is now the universal Python project config, yet no dedicated tool validates it against PEP standards. Developers discover misconfigurations only at build time or when switching between backends. Common mistakes include deprecated keys, missing required fields, invalid classifiers, and backend-specific schema violations.

Approach Catches PEP violations Backend schemas Auto-fix Classifier validation
Manual review Sometimes No No No
Build-time errors Some Partial No No
pyproject-lint Yes Yes Yes Yes

Installation

pip install pyproject-lint

Or with uv:

uv add --dev pyproject-lint

Quick Start

# Validate your pyproject.toml
pyproject-lint check

# Auto-fix common issues
pyproject-lint fix

# Preview fixes without writing
pyproject-lint fix --dry-run

# Machine-readable output
pyproject-lint check --format json

CLI Reference

pyproject-lint check

Validate a pyproject.toml file.

pyproject-lint check [PATH] [OPTIONS]

Arguments:
  PATH    Path to pyproject.toml (default: pyproject.toml)

Options:
  --backend [setuptools|poetry|hatch|uv|auto]
            Force a specific backend for validation (default: auto)
  --format [text|json]
            Output format (default: text)

Exit code 1 if errors are found, 0 otherwise.

pyproject-lint fix

Auto-fix common issues in a pyproject.toml file.

pyproject-lint fix [PATH] [OPTIONS]

Arguments:
  PATH    Path to pyproject.toml (default: pyproject.toml)

Options:
  --dry-run   Show what would change without writing
  --backend [setuptools|poetry|hatch|uv|auto]
              Force a specific backend for validation (default: auto)

What It Checks

PEP Validation

  • PEP 517/518: [build-system] table — requires, build-backend
  • PEP 621: [project] metadata — name, version, authors, dependencies, dynamic fields
  • PEP 735: [dependency-groups] — structure, include-group references

Backend-Specific Rules

  • setuptools: Valid [tool.setuptools] keys, deprecated namespace-packages
  • Poetry: Valid [tool.poetry] keys, missing section detection
  • Hatch: Valid [tool.hatch] keys
  • uv: Valid [tool.uv] keys, deprecated dev-dependencies

Classifier Validation

  • Validates against the PyPI trove classifier database
  • Detects typos and suggests corrections
  • Flags duplicate classifiers

Dependency Audit

  • Flags exact-pinned versions (==) without flexibility
  • Detects duplicate dependencies
  • Reports unsorted dependency lists (with auto-fix)
  • Checks optional-dependencies for duplicates

Pre-commit Integration

Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/your-org/pyproject-lint
    rev: v0.1.0
    hooks:
      - id: pyproject-lint

JSON Output

pyproject-lint check --format json
{
  "diagnostics": [
    {
      "rule_id": "pep621-metadata",
      "severity": "error",
      "message": "[project] must have a 'name' field (PEP 621).",
      "key_path": "project.name"
    }
  ],
  "summary": {
    "errors": 1,
    "warnings": 0,
    "fixable": 0
  }
}

Contributing

  1. Clone the repository
  2. Install dependencies: uv sync
  3. Run tests: uv run pytest
  4. Run linter: uv run ruff check .

Project Structure

src/pyproject_lint/
  cli.py          # Click CLI entry point
  models.py       # Diagnostic models (Severity, Diagnostic, LintResult)
  parser.py       # TOML parsing (tomli/tomllib)
  registry.py     # Rule registry and runner
  fixer.py        # Auto-fix engine
  serializer.py   # TOML serializer for writing fixes
  rules/
    pep.py        # PEP 517/518/621/735 rules
    backend.py    # Backend-specific rules
    classifiers.py # Classifier validation
    dependencies.py # Dependency audit

Adding Rules

Rules are functions decorated with @register_rule:

from pyproject_lint.models import Diagnostic, Severity
from pyproject_lint.registry import register_rule

@register_rule("my-custom-rule")
def check_something(data: dict) -> list[Diagnostic]:
    diagnostics = []
    if "project" in data and "name" not in data["project"]:
        diagnostics.append(
            Diagnostic(
                rule_id="my-custom-rule",
                severity=Severity.ERROR,
                message="Missing project name.",
                key_path="project.name",
            )
        )
    return diagnostics

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages