feat(init): discover third-party version providers via entry points#1971
feat(init): discover third-party version providers via entry points#1971bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
Conversation
Closes commitizen-tools#1258. `cz init` previously presented a hard-coded tuple of eight built-in version providers (`commitizen/commands/init.py:_VERSION_PROVIDER_CHOICES`). Third-party providers registered under the `commitizen.provider` entry-point group — already loaded at runtime by `get_provider()` in `commitizen/providers/__init__.py` — were therefore invisible during `cz init`, forcing users to edit the config by hand. This change: - Renames the tuple to `_BUILTIN_VERSION_PROVIDER_OPTIONS` (data only, no `questionary.Choice` objects). - Adds `_construct_version_provider_choices()`, which builds the picker list at call time: built-in providers first (curated descriptions), followed by any third-party providers discovered via `metadata.entry_points(group=PROVIDER_ENTRYPOINT)` that are not already listed as built-ins. - Wires `_ask_version_provider()` to call the new constructor. - Documents the auto-discovery in `docs/config/version_provider.md` so custom-provider authors know `cz init` will surface their plugin once installed. Adds two unit tests: - `test_construct_version_provider_choices_includes_builtins` — asserts all eight built-ins appear with curated titles and no `third-party` suffix. - `test_construct_version_provider_choices_discovers_third_party` — patches `metadata.entry_points` to add a fake plugin and asserts it is appended (and that built-ins registered in the entry-point group are not duplicated under the generic suffix). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1971 +/- ##
=======================================
Coverage 98.23% 98.24%
=======================================
Files 61 61
Lines 2779 2792 +13
=======================================
+ Hits 2730 2743 +13
Misses 49 49 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Pull request overview
This PR improves cz init by dynamically discovering installed version providers registered under the commitizen.provider entry-point group, so third-party providers become selectable during interactive initialization (while keeping built-ins first with curated descriptions).
Changes:
- Replace the hard-coded version provider
questionary.Choicetuple with a built-in options tuple and a call-time choice constructor. - Discover and append third-party provider entry points (deduped against built-ins) to the
cz initversion provider picker. - Add tests and a short documentation note describing the auto-discovery behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
commitizen/commands/init.py |
Builds the version-provider selection list at runtime, appending third-party entry points after built-ins. |
tests/commands/test_init_command.py |
Adds unit tests for built-in ordering/labels and third-party discovery via patched entry points. |
docs/config/version_provider.md |
Documents that third-party providers will appear in cz init once installed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| third_party_choices = [ | ||
| questionary.Choice( | ||
| title=f"{ep.name}: third-party version provider", | ||
| value=ep.name, | ||
| ) | ||
| for ep in metadata.entry_points(group=PROVIDER_ENTRYPOINT) | ||
| if ep.name not in builtin_names |
| def test_construct_version_provider_choices_discovers_third_party( | ||
| mocker: MockFixture, | ||
| ): | ||
| """Third-party providers registered under `commitizen.provider` are appended.""" | ||
| from commitizen.commands.init import _construct_version_provider_choices | ||
| from commitizen.providers import PROVIDER_ENTRYPOINT | ||
|
|
Address GitHub Copilot review feedback on PR commitizen-tools#1971: when two distributions register a third-party provider under the same name in the `commitizen.provider` entry-point group, the user previously saw two ambiguous choices in `cz init`. Track names in a `seen_names` set (seeded with the built-in names) and skip subsequent matches so each name appears at most once. The conflict is not silenced — `commitizen.providers.get_provider` already raises `VersionProviderUnknown` if more than one entry point shares the name, so the user gets a clear error if they pick the duplicated provider, instead of a confusing UI showing two identical choices that resolve the same way. Add `test_construct_version_provider_choices_dedupes_duplicate_third_party` which simulates two `EntryPoint(name="duplicated", ...)` values and asserts only one appears in the choice list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Closing this PR per maintainer-triage policy: feature-request issues should sit with the maintainers for design / scope review before any implementation lands. The issue's The implementation itself is preserved on the branch ( This PR is being closed so that #1258 reverts to "awaiting maintainer triage / decision" rather than "PR pending review", which is the correct state for a feature request. Closed via the round-2 triage cleanup in #1965. |
Description
Closes #1258.
Why
cz initinteractively asks the user to pick aversion_provider. The picker's options were hard-coded to the eight built-in providers (commitizen,cargo,composer,npm,pep621,poetry,uv,scm), even though commitizen already supports third-party providers via thecommitizen.providerentry-point group at runtime (commitizen/providers/__init__.py:get_provider). The mismatch meant a user who installed e.g.commitizen-deno-providerhad to either know the provider name from memory and hand-edit the config, or skipcz initentirely. The original reporter offered the implementation sketch.What changed
commitizen/commands/init.py_VERSION_PROVIDER_CHOICES(a tuple ofquestionary.Choice) is replaced by_BUILTIN_VERSION_PROVIDER_OPTIONS(data-only) and a new_construct_version_provider_choices()helper that builds the picker list at call time._ask_version_provider()now invokes that helper.tests/commands/test_init_command.pydocs/config/version_provider.mdcz initnow surfaces the plugin once installed.How it works
_BUILTIN_VERSION_PROVIDER_OPTIONStuple keeps the human-readable labels (e.g."pep621: Get and set version from pyproject.toml:project.version field") instead of falling back to a generic suffix.ep.nameis read;ep.load()is not called, so installing a maliciouscz_*plugin can't execute its module just because the user openedcz init.[project.entry-points."commitizen.provider"]inpyproject.toml:73-81. A naive listing would yield each one twice. We seedseen_nameswith the built-in set and skip any entry-point name already in it.cz_jira). After the first occurrence is added we mark the nameseenand skip the rest. We deliberately don't try to merge the duplicates:commitizen.providers.get_provider()raisesVersionProviderUnknownwhen more than one entry point matches, so the user picking the duplicated name gets a clear error instead of the picker silently choosing whichever distribution loaded first.cz initinvocation re-reads the entry-point group, so a provider installed mid-shell-session is picked up immediately.Backward compatibility
_VERSION_PROVIDER_CHOICESwas a leading-underscore module global, never exported).Checklist
Was generative AI tooling used to co-author this PR?
Generated-by: Claude following the guidelines
Code Changes
uv run poe alllocally to ensure this change passes linter check and tests (poe lintclean; 33/33 init tests pass)Documentation Changes
docs/config/version_provider.mduv run poe doclocally to ensure the documentation pages render correctlyExpected Behavior
cz initwith no third-party providers installedcz initwith a third-party plugin installed (e.g.commitizen-deno-providerexposingcommitizen.provider->deno)deno: third-party version provider. Selecting it writesversion_provider = "deno"topyproject.toml.commitizen.provider->dupdupappears once in the picker. Selecting it surfaces the existingVersionProviderUnknownerror fromget_provider()so the conflict is loud, not silent.Steps to Test This Pull Request
Additional Context
Surfaced while triaging open issues in #1965. The implementation follows the original reporter's sketch but uses a single discover-on-call function and dedupes against the built-in list rather than building a separate registry. The duplicate-name handling was added in fixup commit
1bad9047after a GitHub Copilot review pass flagged it as a potential UX gap.