From d2986350d52b6dabc87f995f08f4908b7cc491c9 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Sun, 27 Sep 2026 20:43:42 +0300 Subject: [PATCH] docs: add a Python API page commit_check.api had no page on the site. The core README carried it until the README pass (commit-check/commit-check#587) cut it to one example, on the understanding that the site would pick it up. guides/python-api.md covers all six functions -- including validate_tag and validate_push, which the old README never listed -- what each reads from git when a value is left out, the result shape and what pass, fail, skip and warn mean, and that the API does not read cchk.toml: config is a dict merged over the defaults, so a repository's file has to be loaded and passed in, and inherit_from is not followed. Every Python sample was run against commit-check 2.18.1 and its output pasted as printed. The page is in the nav under Guides and is linked from Where to run it and from the end of Reading the JSON. --- docs/example.md | 3 + docs/guides/integrations.md | 6 ++ docs/guides/python-api.md | 166 ++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 176 insertions(+) create mode 100644 docs/guides/python-api.md diff --git a/docs/example.md b/docs/example.md index 098fd15..423ef97 100644 --- a/docs/example.md +++ b/docs/example.md @@ -317,3 +317,6 @@ $ commit-check -m -b --format json | jq '{status, warnings}' "warnings": 1 } ``` + +From Python, [`commit_check.api`](guides/python-api.md) returns the same +result as a `dict`, without a subprocess. diff --git a/docs/guides/integrations.md b/docs/guides/integrations.md index 6e85448..e0bdf48 100644 --- a/docs/guides/integrations.md +++ b/docs/guides/integrations.md @@ -11,6 +11,7 @@ developer sees locally and what is enforced on the pull request. | [GitHub App](github-app.md) | On every push and pull request, hosted | No | Install once for the organization | | [Command line](../example.md) | Wherever you call it: a range of commits, a CI you write yourself | — | `pip install commit-check` | | [MCP server](mcp.md) | Before the commit exists, inside an AI coding agent | — | One entry in the agent's MCP config | +| [Python API](python-api.md) | Wherever your own code runs: a bot, a server-side hook, an agent you build | — | `pip install commit-check`, then `import commit_check.api` | | [Organization config](organization.md) | Everywhere at once | — | One shared `cchk.toml`, whichever of the above runs it | ## Which one @@ -68,6 +69,11 @@ push and pull request, with no workflow file and no CI minutes. The same rules as tools for an AI coding agent, so the message is right before the commit exists. One entry in the agent's MCP settings. [Set it up →](mcp.md) +## Python API + +The same checks from your own code, with no subprocess: each call returns the +JSON result as a `dict`. [Read the API →](python-api.md) + ## Across an organization { #across-an-organization } One `cchk.toml` in the organization's `.github` repository; each repository diff --git a/docs/guides/python-api.md b/docs/guides/python-api.md new file mode 100644 index 0000000..6d1d662 --- /dev/null +++ b/docs/guides/python-api.md @@ -0,0 +1,166 @@ +# Python API + +`commit_check.api` runs the same checks as the command line from Python code — +a bot, a server-side hook, an agent you are building — without starting a +subprocess. Each function takes the value to check and returns a plain `dict` +in the shape `commit-check --format json` prints, ready to branch on, log or +hand to a model. It ships with the package: `pip install commit-check` is all +it needs. + +```python +from commit_check.api import validate_message + +result = validate_message("Fix: add streaming support") +for check in result["checks"]: + if check["status"] == "fail": + print(check["rule_id"], check["suggest"]) + print("fix:", check["fix"]) +``` + +```text +CC001 Use "fix: add streaming support" +fix: fix: add streaming support +``` + +## Functions + +| Function | Checks | When the value is left out | +|---|---|---| +| `validate_message(message, *, config=None)` | The commit message: Conventional Commits, subject length, case and mood, body, sign-off, AI attribution — whichever rules the config enables | Required | +| `validate_branch(branch=None, *, config=None)` | The branch name, and its rebase target when one is configured | The current branch, from `git branch --show-current` | +| `validate_author(name=None, email=None, *, config=None)` | The author's name and email | Both come from `git config` | +| `validate_tag(tag=None, *, config=None)` | Tag names, one per line | The tags pointing at `HEAD`; with none, the result is `skip` | +| `validate_push(push_refs=None, *, config=None)` | That a push is not a force push; the check is always on here | Pass the pre-push lines, ` `, one per line — without them there is nothing to compare | +| `validate_all(message=None, branch=None, author_name=None, author_email=None, *, config=None)` | Message, branch and author together, in one result | Whatever is left out is not checked, and nothing is read from git | + +The git lookups run in the current working directory, as the CLI's do. + +```python +from commit_check.api import validate_all + +result = validate_all(message="feat: implement new feature", branch="user-login") +print(result["status"]) +for check in result["checks"]: + print(check["rule_id"], check["check"], check["status"]) +``` + +```text +fail +CC001 message pass +CC004 subject_max_length pass +CC005 subject_min_length pass +CC201 branch fail +``` + +## The result + +Every function returns the same shape: + +```python +{ + "status": "pass" | "fail" | "skip", + "warnings": 0, # how many checks have status "warn" + "checks": [ + { + "rule_id": "CC001", + "check": "message", + "status": "pass" | "fail" | "warn" | "skip", + "value": "...", # what was checked + "error": "...", # why it failed + "suggest": "...", # advice for a person + "fix": "...", # the corrected value, or "" when it takes judgment + "docs_url": "https://commit-check.com/rules/#cc001", + }, + ], +} +``` + +- **Only `fail` is a rejection.** Code that branches on `status == "fail"` + keeps working whatever else the result holds. +- **`skip` is not `pass`.** A check skips when it never ran — the author is in + `ignore_authors`, or there was nothing to check — and the top-level `status` + is `skip` only when every check skipped. A skipped run validated nothing, so + do not read it as approval. +- **`warn`** is a rule listed under the config's `warn`: reported in full, + never a failure. The top-level `status` stays `pass`, and `warnings` counts + them. +- **`fix`** is non-empty only when the correction is mechanical, so it can be + applied as it stands; otherwise follow `suggest`. + [Reading the JSON](../example.md#reading-the-json) lists the cases. + +## Configuration + +The API does not read `cchk.toml`. It starts from the built-in defaults and +merges the `config` you pass, a `dict` shaped like the TOML file: + +```python +from commit_check.api import validate_message + +result = validate_message( + "docs: update readme", + config={"commit": {"allow_commit_types": ["feat", "fix"]}}, +) +print(result["status"]) +print(result["checks"][0]["suggest"]) +``` + +```text +fail +Use (): , where is one of: feat, fix +``` + +A top-level `warn` works as it does in the file: + +```python +from commit_check.api import validate_message + +result = validate_message("add streaming support", config={"warn": ["message"]}) +print(result["status"], result["warnings"]) +``` + +```text +pass 1 +``` + +To apply a repository's own policy, load its file and pass it along: + +```python +import tomllib # on Python 3.10: import tomli as tomllib + +from commit_check.api import validate_message + +with open("cchk.toml", "rb") as f: + config = tomllib.load(f) + +result = validate_message("feat: add streaming support", config=config) +``` + +On Python 3.10, `tomli` is already installed: commit-check depends on it +there. `inherit_from` is not followed this way; only the file's own keys +apply. Every key is in the [configuration reference](../configuration.md). + +## Fixing until it passes + +An agent can apply `fix` and check again — the loop the +[MCP server](mcp.md) teaches its clients: + +```python +from commit_check.api import validate_message + +message = "Fix: add streaming support" +for _ in range(3): + result = validate_message(message) + fixes = [c["fix"] for c in result["checks"] if c["status"] == "fail" and c["fix"]] + if result["status"] != "fail" or not fixes: + break + message = fixes[0] + +print(result["status"], message) +``` + +```text +pass fix: add streaming support +``` + +When `fix` is empty the correction takes judgment — choosing a type for a bare +subject, shortening a long one — and `suggest` says what is needed. diff --git a/mkdocs.yml b/mkdocs.yml index 5c73338..e22b598 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -175,6 +175,7 @@ nav: - GitHub App: guides/github-app.md - Across an organization: guides/organization.md - MCP server: guides/mcp.md + - Python API: guides/python-api.md - Policy guides: guides/policies.md - Command-line recipes: example.md - Troubleshooting: troubleshoot.md