Skip to content

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

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

feat(customize): add required field for input questions#1984
bearomorphism wants to merge 1 commit intocommitizen-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
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

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...

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add required parameter to Detailed questions content

2 participants