Skip to main content

pycmdcheck

A Python package quality checker inspired by R's R CMD check.

Installation

pip install pycmdcheck

Or with uv:

uv add pycmdcheck

Optional dependencies

# For mypy type checking
pip install pycmdcheck[typing-mypy]

# For pyright type checking
pip install pycmdcheck[typing-pyright]

# For ruff linting
pip install pycmdcheck[linting]

# All optional dependencies
pip install pycmdcheck[all]

Usage

Command Line

# Check current directory
pycmdcheck

# Check a specific directory
pycmdcheck /path/to/package

# Run only specific checks
pycmdcheck -c metadata -c tests

# Skip specific checks
pycmdcheck -s typing -s linting

# Fail on warnings too
pycmdcheck --fail-on error --fail-on warning

# List available checks
pycmdcheck --list

# JSON output
pycmdcheck --json

# Stop after first error (fail fast)
pycmdcheck -x
pycmdcheck --fail-fast

# Verbose output with details
pycmdcheck -v

# Use a check profile
pycmdcheck --profile pyopensci
pycmdcheck --profile minimal
pycmdcheck --profile strict

# List available profiles
pycmdcheck --list-profiles

With uv

uv run pycmdcheck
uv run pycmdcheck -c tests -c linting

Python API

from pycmdcheck import check, CheckStatus

# Check current directory
report = check(".")

# Check specific directory
report = check("/path/to/package")

# Run only specific checks
report = check(".", checks=["metadata", "tests"])

# Skip specific checks
report = check(".", skip=["linting", "typing"])

# Check results
if report.passed:
    print("All checks passed!")
else:
    for result in report.results:
        print(f"{result.status}: {result.name} - {result.message}")

# Get counts by status
counts = report.count_by_status()
print(f"OK: {counts[CheckStatus.OK]}, Errors: {counts[CheckStatus.ERROR]}")

# JSON output
import json
print(json.dumps(report.to_dict(), indent=2))

Built-in Checks

Core checks

Check Description
build Verifies the package builds successfully (wheel/sdist)
dependencies Audits declared vs. actual imports, warns on unbounded versions
docs Checks for README (content sections, word count) and docstrings
formatting Checks code formatting (ruff format/black)
imports Validates all imports can be resolved
license Checks for LICENSE file and OSI-approved license validation
linting Runs ruff, flake8, or pylint
metadata Validates pyproject.toml fields (required, recommended, extended)
py_typed Checks for PEP 561 py.typed marker
structure Checks package directory structure (src or flat layout)
tests Runs pytest or unittest and reports results
typing Runs mypy or pyright type checker
version Verifies version consistency between pyproject.toml and code

pyOpenSci / community checks

Check Description
changelog Checks for CHANGELOG, NEWS, or HISTORY file
ci Checks for CI configuration (GitHub Actions, CircleCI, etc.)
citation Checks for CITATION.cff or CITATION.bib file
community Checks for CONTRIBUTING.md and CODE_OF_CONDUCT.md
doctests Runs doctests via pytest --doctest-modules
python_versions Checks requires-python excludes EOL Python versions
urls Validates project URLs in metadata are reachable

Profiles

Profiles bundle checks for common use cases:

Profile Description Checks
minimal Quick sanity checks metadata, structure, license
triage Static checks only — no install, external tools, or network (for CI triage of arbitrary repos) All static checks (excludes tests, imports, dependencies, build, linting, typing, formatting, doctests, urls)
default Standard quality checks All original core checks
pyopensci pyOpenSci onboarding All checks with stricter docs settings
strict Maximum strictness All checks with strict typing
# Run pyOpenSci onboarding checks
pycmdcheck --profile pyopensci

# Static-only triage (safe to run on an un-installed repo in CI)
pycmdcheck --profile triage

# Quick check before committing
pycmdcheck --profile minimal

Profiles can be combined with --skip to exclude specific checks:

pycmdcheck --profile pyopensci -s urls -s doctests

Configuration

Configure pycmdcheck in your pyproject.toml:

[tool.pycmdcheck]
# Fail on these statuses (default: ["error"])
fail_on = ["error", "warning"]

[tool.pycmdcheck.checks]
# Enable/disable checks with boolean
metadata = true
structure = true
imports = true
license = true

# Configure checks with options
tests = { enabled = true, runner = "pytest" }
linting = { enabled = true, tool = "ruff" }
typing = { enabled = true, tool = "mypy", strict = false }
# formatting "tool" defaults to "auto": detects ruff vs black from
# [tool.ruff]/ruff.toml or [tool.black]; skips if neither is configured.
formatting = { enabled = true, tool = "auto" }
docs = { enabled = true, require_readme = true, check_docstrings = false, check_readme_sections = false }

# New checks (disabled by default in "default" profile)
community = true
ci = true
changelog = true
citation = true
python_versions = true
urls = { enabled = true, timeout = 10 }
doctests = true

Linting tools

Supported linting tools:

  • ruff (default, recommended)
  • flake8
  • pylint
[tool.pycmdcheck.checks.linting]
tool = "ruff"
args = ["--select=E,F,W"]

Build options

[tool.pycmdcheck.checks.build]
timeout = 300  # seconds (default: 120)
args = ["--wheel"]

Type checking tools

Supported type checkers:

  • mypy (default)
  • pyright
[tool.pycmdcheck.checks.typing]
tool = "pyright"
strict = true

Custom Checks

Create custom checks by implementing the Check protocol:

from pathlib import Path
from typing import Any
from pycmdcheck.checks.base import BaseCheck
from pycmdcheck.results import CheckResult, CheckStatus

class MyCustomCheck(BaseCheck):
    name = "my_custom_check"
    description = "My custom quality check"

    def run(self, package_path: Path, config: dict[str, Any]) -> CheckResult:
        # Your check logic here
        issues = []  # ... populate with any issues found
        if not issues:
            return CheckResult(
                name=self.name,
                status=CheckStatus.OK,
                message="All good!",
            )
        return CheckResult(
            name=self.name,
            status=CheckStatus.ERROR,
            message="Something is wrong",
            details=issues,
        )

Register your check via entry points in pyproject.toml:

[project.entry-points."pycmdcheck.checks"]
my_custom_check = "my_package.checks:MyCustomCheck"

Check Statuses

Status Symbol Description
OK ✓ Check passed
NOTE ℹ Minor observation, not a problem
WARNING ⚠ Potential issue, should be reviewed
ERROR ✗ Check failed
SKIPPED ○ Check was skipped (e.g., tool not installed)

License

MIT

Metadata

Release files for pycmdcheck 0.1.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 pycmdcheck 0.1.1
File Size Uploaded
pycmdcheck-0.1.1.tar.gz 828.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pycmdcheck 0.1.1
File Interpreter ABI Platform
pycmdcheck-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 902.7 kB

Release files / pycmdcheck-0.1.1.tar.gz

Download URL pycmdcheck-0.1.1.tar.gz
Size 828.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0b6c6dc454ef51fe023b76b2d339e1e2a906b02dc10df01b05cb75ed059268f3
BLAKE2b-256 checksum
How to use checksums
42c5237f56a4831bb08826e54ed1d080e5508913c294e4bbde4d5691b1923ba0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jun 26, 2026.

Transparency log

Release files / pycmdcheck-0.1.1-py3-none-any.whl

Download URL pycmdcheck-0.1.1-py3-none-any.whl
Size 74.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c19aadfae5b644b568daf9270691a12f12116926a7d69ad53c5dd35dba2e999c
BLAKE2b-256 checksum
How to use checksums
0a144e8ab931bc822c2e538ca55aa8dfecf82f1ac6ce44ca8eb07da30dc2dcd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jun 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 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