Skip to content

feat(customize): add required field for input questions - #1984

Closed
bearomorphism wants to merge 1 commit into
commitizen-tools:masterfrom
bearomorphism:feat/1231-customize-question-required
Closed

feat(customize): add required field for input questions#1984
bearomorphism wants to merge 1 commit into
commitizen-tools:masterfrom
bearomorphism:feat/1231-customize-question-required

Conversation

@bearomorphism

Copy link
Copy Markdown
Collaborator

Description

Closes #1231.

Adds a required field to cz_customize input questions so users can enforce non-empty answers without writing a custom Python validator.

Why

Users who configure commit rules entirely through pyproject.toml / .cz.json / .cz.yaml have no way to mark a free-text question as mandatory. They must either accept empty answers silently or write a Python class just to add a one-line validator. This PR provides a zero-code path for the common case.

What changed

File Change
commitizen/question.py Added validate and required optional fields to InputQuestion TypedDict
commitizen/cz/customize/customize.py questions() now shallow-copies each question dict; if type == "input" and required=True, pops required and injects a validate callable that rejects blank input
docs/customization/config_file.md Added required row to the questions field table; added required = true to the TOML example
tests/test_cz_customize.py Seven new unit tests covering the required validator behaviour

How it works

When questions() processes each question from the config:

  1. A shallow copy of the question dict is made (avoids mutating the parsed config on repeated calls).
  2. If the question has type = "input" and required = true, the required key is popped and replaced with validate = _required_validator.
  3. _required_validator returns True for non-blank strings and "This answer is required." for empty/whitespace-only input — the exact signature questionary expects.
  4. All other question types (list, confirm) pass through unchanged; required is meaningless there since both always return a value.

Backward compatibility

Fully backward-compatible. The required key defaults to False (absent), so existing configs are unaffected. No CLI flags, exit codes, or public APIs changed.

Checklist

Was generative AI tooling used to co-author this PR?

  • Yes (GitHub Copilot CLI)

Code Changes

  • Add test cases to all the changes you introduce
  • Run uv run poe all locally to ensure this change passes linter check and tests
  • Manually test the changes:
    • Verify the feature/bug fix works as expected in real-world scenarios
    • Test edge cases and error conditions
    • Ensure backward compatibility is maintained
    • Document any manual testing steps performed
  • Update the documentation for the changes

Expected Behavior

Scenario Before After
required = true on an input question KeyError / unknown key passed to questionary validate callable injected; empty input is rejected with "This answer is required."
required absent / false question passes through as-is question passes through as-is (no change)
list or confirm question with no required key unchanged unchanged
calling questions() twice on the same config N/A second call still works correctly (no mutation)

Steps to Test This Pull Request

# pyproject.toml
[tool.commitizen]
name = "cz_customize"

[tool.commitizen.customize]
message_template = "{{change_type}}: {{message}}"

[[tool.commitizen.customize.questions]]
type = "list"
name = "change_type"
message = "Type"
choices = [{value = "feat", name = "feat"}, {value = "fix", name = "fix"}]

[[tool.commitizen.customize.questions]]
type = "input"
name = "message"
message = "Describe the change (required):"
required = true

[[tool.commitizen.customize.questions]]
type = "input"
name = "note"
message = "Optional note:"
# 1. Reproduce original problem (empty answer silently accepted)
git checkout v4.15.1
cz commit  # pick a type, press Enter on empty message -> commit succeeds

# 2. Verify fix
git checkout feat/1231-customize-question-required
cz commit  # pick a type, press Enter on empty message -> "This answer is required." shown
           # type something -> commit proceeds normally

Additional Context

Related issue: #1231

Users can now mark a cz_customize input question as required by setting
required = true in their config. The loader converts this into a questionary
validate= callable that rejects empty/whitespace-only answers with the
message 'This answer is required.' The required key is stripped before the
question dict reaches questionary, so questionary never sees the unknown key.

Closes commitizen-tools#1231

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.61538% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.17%. Comparing base (4b93a50) to head (5c85feb).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
commitizen/question.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1984      +/-   ##
==========================================
- Coverage   98.23%   98.17%   -0.07%     
==========================================
  Files          61       61              
  Lines        2779     2791      +12     
==========================================
+ Hits         2730     2740      +10     
- Misses         49       51       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bearomorphism

Copy link
Copy Markdown
Collaborator Author

closing since we're not sure about this feature...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a required flag to cz_customize input questions so config-only users can require non-empty answers, implemented by injecting a questionary validate callable while avoiding mutation of the underlying parsed config.

Changes:

  • Extend InputQuestion typing to include optional required and validate.
  • Update CustomizeCommitsCz.questions() to shallow-copy question dicts and inject a required-field validator for type="input".
  • Document the new required flag and add unit tests for the injected validator behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
commitizen/cz/customize/customize.py Shallow-copies question dicts and injects a non-blank validator when required=true on input questions.
commitizen/question.py Updates InputQuestion TypedDict to allow required and validate.
docs/customization/config_file.md Documents the new required question field and updates examples.
tests/test_cz_customize.py Adds tests covering required-validator injection, behavior, and non-mutation across calls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

result: list[CzQuestion] = []
for raw in raw_questions:
q: dict[str, Any] = dict(raw)
if q.get("type") == "input" and q.pop("required", False):
Comment on lines +65 to +67
q: dict[str, Any] = dict(raw)
if q.get("type") == "input" and q.pop("required", False):
q["validate"] = _required_validator

assert validate("") is not True
assert validate(" ") is not True
assert isinstance(validate(""), str) # error message
@bearomorphism

Copy link
Copy Markdown
Collaborator Author

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 type: feature (or implicit equivalent) label means it's not on a "ready-to-implement" track yet.

The implementation itself is preserved on the branch (feat/1231-customize-question-required) — if a maintainer decides this is the direction they want, the PR can be re-opened in one click, or the work can serve as a starting point for a maintainer-led design.

This PR is being closed so that #1231 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants