Changelog
pycmdcheck 0.2.0
This release raises fewer false alarms and explains every finding.
New features
Every finding now has a rule code, such as
license.missing, and a one-sentence suggestion that says what to do about it.--verboseshows the suggestions,--list-ruleslists every rule, and the JSON report holds both underfindingsin each result.--format markdownwrites a report made for a pull request or issue comment, and--output FILEsaves any report to a file.pycmdcheck --profile triage --format markdown --output report.mdNew
test_suitecheck says whether a project has tests without running them, so thetriageprofile can now report a missing test suite.New
--config FILEoption takes the profile, the checks and their options from a settings file kept outside the checked project. It is for bots and organizations that check repositories they do not own. The[tool.pycmdcheck]table of the checked project is then not read at all.--profile triagenow gives the same results whatever the checked project’s[tool.pycmdcheck.checks]table says. Its checks only read files, and the profile sets every option they use.Skipped checks now say why. Each one has a
skip_reason, such astool-missing,not-configuredornot-applicable, and never fails a run.New Agent Skill teaches an AI assistant to run pycmdcheck and to fix each finding by its rule code and suggestion.
pycmdcheck --install-skillcopies it into.claude/skills, and the repository also works as a Claude Code plugin.--checkand--skipnow take several names separated by commas, as in--check tests,linting.--list-profilesnow shows the checks and options of each profile, and--list,--list-profilesand--list-rulestake--json.The JSON report gains
schema_version,pycmdcheck_version,profile,config_file,fail_on,failed,requested_checksandskipped_checks, and every result gainsfindingsandskip_reason. A program that compares the exact set of keys needs an update.Exit code 3 now marks a failure of pycmdcheck itself, so a caller can tell it from a report of findings.
The Python API gains
pycmdcheck.Finding,BaseCheck.rules,BaseCheck.hintsandCheckResult.skip()for custom checks that report findings, and aprofile=argument forcheck()andrun_checks(). A custom check that reports no findings still works.
Breaking changes
Python 3.10 is no longer supported. pycmdcheck needs Python 3.11 or later.
Many results change status. A check with nothing to look at is now skipped and no longer gives a note, a warning or an error, and each cause is reported by one check. A project with no tests, for example, now gets its warning from
test_suiteand a skippedtests.lintingandtypingnow run only when the project has configured a tool, and are skipped otherwise. To run one anyway, name the tool, as inlinting = { tool = "ruff" }under[tool.pycmdcheck.checks].formattingno longer takes a[tool.ruff]table alone as the sign of a ruff formatter, so such a project is now skipped. Add a[tool.ruff.format]table, which may be empty, to keep the check.--fail-onnow names a threshold, so--fail-on warningalso fails on an error.fail_onin[tool.pycmdcheck]now takes effect when--fail-onis not given, under every profile, so a project that setsfail_on = ["warning"]can now get exit code 1. Pass--fail-onor use--configwhen you check repositories you do not own.doctestsnow gives an error for a failed docstring example, and the run exits with code 1. Fix the example, or mark it with# doctest: +SKIP.dependenciesnow warns about an undeclared import also when the module is installed, and when the project declares no dependencies at all. Add the package todependenciesin the[project]table.--skipnow gives a usage error with exit code 2 when it names an unknown check, holds an empty name, or leaves no check to run. So does aPATHthat is a file.--profile defaultand theimportscheck are deprecated. Use--profile core, which runs the same checks, and--check dependencies, which now does the work ofimports. Takeimportsout of--skip.--profile strictnow runs ruff and mypy whatever tool the project names in[tool.pycmdcheck.checks], such as flake8 or pyright. Use--profile pyopenscito keep the project’s choice.The
allextra now installs thebuildpackage, as the newbuildextra does, so thebuildcheck builds the project where it was skipped. Pass--skip buildto leave it out.Report text is reworded. The closing line starts with
Failed:after a failed run, many messages and detail lines changed, and advice moved from the detail lines to the suggestion. A program should read the JSON report and match thecodeof a finding.A check that raises an exception no longer prints its traceback. Run with
--debugto see it.The
devanddocsextras are gone. Both are now dependency groups for people who work on pycmdcheck.check()andrun_checks()now raiseFileNotFoundErrororNotADirectoryErrorfor a path that is not a directory, andValueErrorfor an unknown profile or an unknown name inchecks=.A plugin can no longer replace a built-in check by registering under its name.
For authors of custom checks, the class attributes
CHANGELOG_FILENAMES,CITATION_FILENAMES,CONTRIBUTING_FILENAMESandCODE_OF_CONDUCT_FILENAMESgive way toCHANGELOG_STEMS,CITATION_EXTENSIONS,CONTRIBUTING_STEMSandCODE_OF_CONDUCT_STEMS. Several undocumented helpers ofpycmdcheck.package_layoutare gone too.
Minor improvements and fixes
--profile pyopensciand--profile strictnow set the minimum docstring coverage to 50% and 80%, so missing docstrings give a note only below that coverage.buildnow builds a project that has only asetup.py, and abuild.pyorbuild/in the project no longer gets in its way.changelog,ci,citationandcommunitynow accept many more file names and services, such asRELEASE_NOTES, a directory of news fragments, Forgejo Actions,CITATION.rst,codemeta.jsonandCONDUCT.changelog,ci,citation,communityanddocsnow match file names in any letter case, so results agree between macOS and Linux.dependenciesno longer flags guarded imports, such as those underif TYPE_CHECKINGor in atrythat catchesImportError. It also leaves out the standard library, the project’s own modules, and code in tests and documentation.docsgains acheck_docs_siteoption, which gives a note when a project has no documentation beyond its README. Thetriage,pyopensciandstrictprofiles turn it on.docsnow finds a README in.github/ordocs/, or one declared inpyproject.toml.doctestsnow names each failed example and gives the line and the reason, as inline 29: expected 10, got 9.doctestsnow collects only the project’s own packages, and still runs the other examples when one file cannot be imported.licensenow gives a note when the declared license expression names no license approved by the Open Source Initiative. The approved licenses come from the SPDX license list.licensenow acceptsLICENSE/andLICENSES/directories,UNLICENSE, and files that are not UTF-8. It no longer labels MPL, LGPL, AGPL or EPL texts as GPL.lintingno longer rewrites files when the project’s ruff settings turnfixon, and lists each finding aspath:row:col CODE message.lintingandformattingnow find ruff and black when they are installed next to pycmdcheck, which is wherepipxanduv toolput them.metadatanow flags values that cannot be right. An emptynameorversiongives an error, and a setuptools project that lists aLicense ::classifier beside a license expression gets a warning, because setuptools 77 and later refuses to build it.metadatanow gives a warning, not an error, whensetup.pyorsetup.cfgholds the metadata beside apyproject.tomlthat has no[project]table.py_typednow suggests thepy.typedmarker only for a package that is clearly typed, and counts.pyistub files.python_versionsnow readspython_requiresfromsetup.py, and knows end-of-life dates through Python 3.15.structurenow finds a package besidesrc/, namespace packages, stub-only packages and packages with underscore names. It no longer warns about Rust, C or front-end directories undersrc/, and it recognizes native build tools named under[build-system] requires, such as setuptools-rust, Cython, pybind11 and cffi.testsnow finds tests inside the package, undertestpaths, and in*_test.pyfiles. It says “Tests could not be collected” when a test module cannot be imported.typingnow gives ok when mypy prints only notes, counts the warnings of pyright apart from its errors, and no longer passes--strictto pyright.urlsno longer contacts local or private hosts, and follows a redirect only to anotherhttporhttpsaddress.urlsnow gives a note, not a warning, for an address it could not verify, such as one that answers HTTP 403 or times out. Only 404, 410 and an unknown host give a warning.versionnow treats1.0and1.0.0as the same version, as PEP 440 does.A
pyproject.tomlthat cannot be parsed no longer ends the run with a traceback.metadatareports the file, and the checks that need it are skipped.A tool that is installed but cannot run now gives a warning that names it, such as “ruff could not run”, not an error.
Files in the checked repository are now read only when they are regular files, and only up to a size limit.
python -m pycmdchecknow removes the current directory from the module search path, so a repository checked from its own root cannot register checks of its own.The documentation gains pages on the rules, on triage and on working with an AI assistant, and is published at https://py-pkg.thecoatlessprofessor.com/pycmdcheck/.
pycmdcheck 0.1.1
--exit-zeroalways exits with code 0, whatever the checks find, so the exit code says whether pycmdcheck ran. It is for automation that reads the results itself, for example from--json.Project URLs now point to the repository at
github.com/coatless-py-pkg/pycmdcheck.Installation instructions now use
pip install pycmdcheck.Docstrings of the Python API now match what the functions return, and their examples run under
pytest --doctest-modules.
pycmdcheck 0.1.0
First release on PyPI.
check()and thepycmdcheckcommand check the quality of a Python package, with a text report for the terminal and a JSON report for automation.Built-in checks
metadata,structure,tests,linting,typing,imports,license,docs,dependencies,build,formatting,version,py_typed,community,ci,changelog,citation,python_versions,urlsanddoctests.--profileruns a set of checks, and--list-profileslists the sets, which areminimal,triage,default,pyopensciandstrict. Thetriageprofile only reads files, so it can check a repository that is not installed.[tool.pycmdcheck]inpyproject.tomlconfigures the checks, and custom checks plug in through entry points.metadata,version,python_versionsanddependenciesread legacy Poetry projects, which have a[tool.poetry]table and no[project]table.structurerecognizes namespace packages, single-file modules, and the native build backends maturin and scikit-build.formattingfinds the configured formatter, ruff or black, and is skipped when none is configured.The Python API exports
CheckResult,CheckStatus,ReportandBaseCheckfor custom checks.