Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cf6a7ec

Browse files
refactor(question): strict type with pydantic for questions
1 parent a3919c9 commit cf6a7ec

File tree

11 files changed

+94
-29
lines changed

11 files changed

+94
-29
lines changed

‎commitizen/commands/commit.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
NothingToCommitError,
2525
)
2626
from commitizen.git import smart_open
27+
from commitizen.question import ListQuestion
2728

2829

2930
class Commit:
@@ -52,10 +53,12 @@ def prompt_commit_questions(self) -> str:
5253
# Prompt user for the commit message
5354
cz = self.cz
5455
questions = cz.questions()
55-
for question in filter(lambdaq: q["type"] =="list", questions):
56-
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
56+
for question in (qforqinquestionsifisinstance(q, ListQuestion)):
57+
question.use_shortcuts = self.config.settings["use_shortcuts"]
5758
try:
58-
answers = questionary.prompt(questions, style=cz.style)
59+
answers = questionary.prompt(
60+
(q.model_dump() for q in questions), style=cz.style
61+
)
5962
except ValueError as err:
6063
root_err = err.__context__
6164
if isinstance(root_err, CzException):

‎commitizen/cz/base.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from commitizen import git
1111
from commitizen.config.base_config import BaseConfig
12-
from commitizen.defaults import Questions
12+
from commitizen.question import CzQuestion
1313

1414

1515
class MessageBuilderHook(Protocol):
@@ -68,7 +68,7 @@ def __init__(self, config: BaseConfig) -> None:
6868
self.config.settings.update({"style": BaseCommitizen.default_style_config})
6969

7070
@abstractmethod
71-
def questions(self) -> Questions:
71+
def questions(self) -> list[CzQuestion]:
7272
"""Questions regarding the commit message."""
7373

7474
@abstractmethod

‎commitizen/cz/conventional_commits/conventional_commits.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from commitizen import defaults
55
from commitizen.cz.base import BaseCommitizen
66
from commitizen.cz.utils import multiple_line_breaker, required_validator
7-
from commitizen.defaults import Questions
7+
from commitizen.question import CzQuestion, CzQuestionModel
88

99
__all__ = ["ConventionalCommitsCz"]
1010

@@ -40,8 +40,8 @@ class ConventionalCommitsCz(BaseCommitizen):
4040
}
4141
changelog_pattern = defaults.BUMP_PATTERN
4242

43-
def questions(self) -> Questions:
44-
return [
43+
def questions(self) -> list[CzQuestion]:
44+
questions= [
4545
{
4646
"type": "list",
4747
"name": "prefix",
@@ -146,6 +146,9 @@ def questions(self) -> Questions:
146146
),
147147
},
148148
]
149+
return [
150+
CzQuestionModel.model_validate({"question": q}).question for q in questions
151+
]
149152

150153
def message(self, answers: dict) -> str:
151154
prefix = answers["prefix"]

‎commitizen/cz/customize/customize.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import TYPE_CHECKING
44

5+
from commitizen.question import CzQuestion, CzQuestionModel
6+
57
if TYPE_CHECKING:
68
from jinja2 import Template
79
else:
@@ -14,7 +16,6 @@
1416
from commitizen import defaults
1517
from commitizen.config import BaseConfig
1618
from commitizen.cz.base import BaseCommitizen
17-
from commitizen.defaults import Questions
1819
from commitizen.exceptions import MissingCzCustomizeConfigError
1920

2021
__all__ = ["CustomizeCommitsCz"]
@@ -45,8 +46,11 @@ def __init__(self, config: BaseConfig):
4546
if value := self.custom_settings.get(attr_name):
4647
setattr(self, attr_name, value)
4748

48-
def questions(self) -> Questions:
49-
return self.custom_settings.get("questions", [{}])
49+
def questions(self) -> list[CzQuestion]:
50+
questions = self.custom_settings.get("questions", [{}])
51+
return [
52+
CzQuestionModel.model_validate({"question": q}).question for q in questions
53+
]
5054

5155
def message(self, answers: dict) -> str:
5256
message_template = Template(self.custom_settings.get("message_template", ""))

‎commitizen/cz/jira/jira.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import os
22

33
from commitizen.cz.base import BaseCommitizen
4-
from commitizen.defaults import Questions
4+
from commitizen.question import CzQuestion, CzQuestionModel
55

66
__all__ = ["JiraSmartCz"]
77

88

99
class JiraSmartCz(BaseCommitizen):
10-
def questions(self) -> Questions:
11-
return [
10+
def questions(self) -> list[CzQuestion]:
11+
questions= [
1212
{
1313
"type": "input",
1414
"name": "message",
@@ -42,6 +42,9 @@ def questions(self) -> Questions:
4242
"filter": lambda x: "#comment " + x if x else "",
4343
},
4444
]
45+
return [
46+
CzQuestionModel.model_validate({"question": q}).question for q in questions
47+
]
4548

4649
def message(self, answers: dict) -> str:
4750
return " ".join(

‎commitizen/question.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from typing import Callable, Literal, Optional, Union
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class CzModel(BaseModel):
7+
def model_dump(self, **kwargs):
8+
return super().model_dump(exclude_unset=True, **kwargs)
9+
10+
11+
class Choice(CzModel):
12+
value: str
13+
name: str
14+
key: Optional[str] = None
15+
16+
17+
class QuestionBase(CzModel):
18+
name: str
19+
message: str
20+
21+
22+
class ListQuestion(QuestionBase):
23+
type: Literal["list"]
24+
choices: list[Choice]
25+
use_shortcuts: Optional[bool] = None
26+
27+
28+
class SelectQuestion(QuestionBase):
29+
type: Literal["select"]
30+
choices: list[Choice]
31+
use_search_filter: Optional[bool] = None # TODO: confirm type
32+
use_jk_keys: Optional[bool] = None
33+
34+
35+
class InputQuestion(QuestionBase):
36+
type: Literal["input"]
37+
filter: Optional[Callable[[str], str]] = None
38+
39+
40+
class ConfirmQuestion(QuestionBase):
41+
type: Literal["confirm"]
42+
default: Optional[bool] = None
43+
44+
45+
CzQuestion = Union[ListQuestion, SelectQuestion, InputQuestion, ConfirmQuestion]
46+
47+
48+
class CzQuestionModel(CzModel):
49+
question: CzQuestion = Field(discriminator="type")

‎tests/conftest.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from commitizen.config import BaseConfig
1818
from commitizen.cz import registry
1919
from commitizen.cz.base import BaseCommitizen
20+
from commitizen.question import CzQuestion
2021
from tests.utils import create_file_and_commit
2122

2223
SIGNER = "GitHub Action"
@@ -222,7 +223,7 @@ def use_cz_semver(mocker):
222223

223224

224225
class MockPlugin(BaseCommitizen):
225-
def questions(self) -> defaults.Questions:
226+
def questions(self) -> list[CzQuestion]:
226227
return []
227228

228229
def message(self, answers: dict) -> str:

‎tests/test_cz_conventional_commits.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
parse_subject,
77
)
88
from commitizen.cz.exceptions import AnswerRequiredError
9+
from commitizen.question import ListQuestion
910

1011
valid_scopes = ["", "simple", "dash-separated", "camelCaseUPPERCASE"]
1112

@@ -50,16 +51,15 @@ def test_questions(config):
5051
conventional_commits = ConventionalCommitsCz(config)
5152
questions = conventional_commits.questions()
5253
assert isinstance(questions, list)
53-
assert isinstance(questions[0], dict)
5454

5555

5656
def test_choices_all_have_keyboard_shortcuts(config):
5757
conventional_commits = ConventionalCommitsCz(config)
5858
questions = conventional_commits.questions()
5959

60-
list_questions = (q for q in questions if q["type"] =="list")
60+
list_questions = (q for q in questions if isinstance(q, ListQuestion))
6161
for select in list_questions:
62-
assert all("key"inchoicefor choice in select["choices"])
62+
assert all(choice.keyisnotNonefor choice in select.choices)
6363

6464

6565
def test_small_answer(config):

‎tests/test_cz_customize.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def test_questions(config):
443443
"message": "Do you want to add body message in commit?",
444444
},
445445
]
446-
assert list(questions) == expected_questions
446+
assert [q.model_dump() forqinquestions] == expected_questions
447447

448448

449449
def test_questions_unicode(config_with_unicode):
@@ -466,7 +466,7 @@ def test_questions_unicode(config_with_unicode):
466466
"message": "Do you want to add body message in commit?",
467467
},
468468
]
469-
assert list(questions) == expected_questions
469+
assert [q.model_dump() forqinquestions] == expected_questions
470470

471471

472472
def test_answer(config):

‎tests/test_cz_jira.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ def test_questions(config):
55
cz = JiraSmartCz(config)
66
questions = cz.questions()
77
assert isinstance(questions, list)
8-
assert isinstance(questions[0], dict)
98

109

1110
def test_answer(config):

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /