A version provider tells Commitizen where to read and write the project's version (e.g., pyproject.toml for PEP 621, Cargo.toml for Cargo, package.json for npm). End-user documentation: Version Provider.
Architectural context: Architecture § Extension points.
- "Add support for
<ecosystem>version files." - "Read the version from
<file>instead of asking the user." - Issue or feature request mentions a packaging system that is not in the list of built-ins (
cargo,commitizen,composer,npm,pep621,poetry,scm,uv).
commitizen/providers/__init__.py— registration helperget_provider, entry-point groupcommitizen.provider, default provider name.commitizen/providers/base_provider.py—VersionProvider,FileProvider,JsonProvider,TomlProviderbase classes.- An existing provider that resembles your target:
- Simplest JSON case (single file, top-level
versionkey):commitizen/providers/composer_provider.py— only setsfilenameandindent. - JSON with multi-file updates (package + lockfile + shrinkwrap):
commitizen/providers/npm_provider.py— overridesget_version/set_version. - TOML with multi-file updates (
pyproject.toml+uv.lock):commitizen/providers/uv_provider.py. - SCM tag-based, no file write:
commitizen/providers/scm_provider.py.
- Simplest JSON case (single file, top-level
- Test for the closest existing provider:
tests/providers/test_<name>_provider.py. commitizen/config/base_config.py:BaseConfig— what your provider's__init__(config)will receive.
-
Create the provider module at
commitizen/providers/<name>_provider.py. Subclass the closest base:TomlProviderif your file is TOML and[project].versionis sufficient — override onlyget/setif the version lives at a different path.JsonProviderfor JSON files; same override pattern.FileProviderdirectly when the format is neither TOML nor JSON.VersionProviderwhen there is no file (e.g., SCM-tag-based).
-
Honor the configured encoding for every file read and write — call
self._get_encoding()(provided byFileProvider) rather than relying on system defaults. Seecommitizen/providers/base_provider.pyfor examples. -
Register the built-in by adding one line to
pyproject.tomlunder[project.entry-points."commitizen.provider"]:<name> = "commitizen.providers:<NameProvider>" -
Export the class from
commitizen/providers/__init__.py: import it and add it to__all__. -
Add tests at
tests/providers/test_<name>_provider.py. The existing tests demonstrate the patterns — most usepytest-regressionsfor file snapshots andpytest-mockto substitute the working directory. -
Update user docs at
docs/config/version_provider.md— add a row to the providers table and an example block if the configuration is non-trivial. -
Re-run the editable install so the new entry point is picked up:
uv sync --frozen --group base --group test --group linters
uv run pytest tests/providers/test_<name>_provider.py tests/test_factory.py -n auto
uv run poe lint
uv run poe doc:build # if docs/config/version_provider.md changed
uv run poe all # final pre-push (PR template requirement)- Forgetting
pyproject.tomlregistration — the provider class will exist butcz bumpwill raiseVersionProviderUnknownbecauseget_providerlooks it up by entry point, not by import path. - Hardcoded
open(path)— drops the user's configured encoding. Useself._get_encoding()andPath.read_text(encoding=...). - Mutating files outside
set_version— providers should be idempotent and side-effect-free onget_version. Multi-file updates (likeUvProviderupdating bothpyproject.tomlanduv.lock) belong insideset_version. - Not testing the missing-file path —
cz bump --get-nextrunsget_versionon a fresh checkout. Make sure your provider returns a reasonable default or raises a clear exception when its file is absent. - Cross-platform line endings — write with
Path.write_text(...)and add a trailing newline; do not assume\n.
- The packaging ecosystem requires authentication to discover the version (e.g., reading from a registry). Network-dependent providers are out of scope for built-ins; suggest packaging it as a third-party plugin.
- The version is split across two unrelated files with no clear "primary" source.
- The setting would require a new key in the
SettingsTypedDict (commitizen/defaults.py) — that is a config schema change, surface it in the issue.