Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Follow these instructions in addition to any higher-level system or tool rules.
- `commitizen/commands/` - subcommands such as `bump`, `commit`, `changelog`, `check`, etc.
- `commitizen/config/` - configuration discovery and loading.
- `commitizen/providers/` - version providers (e.g., `pep621`, `poetry`, `npm`, `uv`).
- **Config sources**: `pyproject.toml` (project config, poe tasks, ruff, mypy), `.pre-commit-config.yaml` (hooks), `.github/workflows/` (CI).
- **Config sources**: `pyproject.toml` (project config, poe tasks, ruff, ty), `.pre-commit-config.yaml` (hooks), `.github/workflows/` (CI).

## General Expectations

Expand All @@ -34,7 +34,7 @@ uv sync --frozen --group base --group test --group linters
### Local commands

- **Format**: `uv run poe format` (runs `ruff check --fix` then `ruff format`)
- **Lint**: `uv run poe lint` (runs `ruff check` then `mypy`)
- **Lint**: `uv run poe lint` (runs `ruff check` then `ty check`)
- **Test**: `uv run poe test` (runs `pytest -n auto`)
- **CI-equivalent**: `uv run poe ci` (commit check + pre-commit hooks via `prek` + test with coverage)
- **Full local check**: `uv run poe all` (format + lint + check-commit + coverage)
Expand All @@ -50,7 +50,7 @@ Always run at least `uv run ruff check --fix . && uv run ruff format .` before p
### Common CI failure patterns

- **"Format Python code...Failed"**: Run `uv run poe format` and commit the result.
- **mypy `[arg-type]` on TypedDict**: Dynamically-constructed dicts (e.g., from `pytest.mark.parametrize`) passed to TypedDict-typed params need `# type: ignore[arg-type]`.
- **ty `invalid-argument-type` on TypedDict**: Dynamically-constructed dicts (e.g., from `pytest.mark.parametrize`) passed to TypedDict-typed params need `# type: ignore # noqa: PGH003` or `cast()`.
- **"pathspec 'vX.Y.Z' did not match"**: `.pre-commit-config.yaml` pins a tag of this repo. Rebase onto master to pick up the tag.
- **`VersionProtocol` + `issubclass`**: This Protocol has non-method members (properties), so `issubclass()` raises `TypeError`. Use `hasattr` checks for runtime validation.

Expand Down
11 changes: 8 additions & 3 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from dataclasses import dataclass
from datetime import date
from itertools import chain
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast

from deprecated import deprecated
from jinja2 import (
Expand Down Expand Up @@ -192,10 +192,14 @@ def process_commit_message(

messages = [processed_msg] if isinstance(processed_msg, dict) else processed_msg
for msg in messages:
change_type = msg.pop("change_type", None)
if not isinstance(msg, dict):
Comment thread
bearomorphism marked this conversation as resolved.
continue
# cast needed: ty cannot narrow dict type from Iterable union
msg_dict = cast("dict[str, Any]", msg)
change_type = msg_dict.pop("change_type", None)
if change_type_map and change_type:
change_type = change_type_map.get(change_type, change_type)
ref_changes[change_type].append(msg)
ref_changes[change_type].append(msg_dict)


def generate_ordered_changelog_tree(
Expand Down Expand Up @@ -272,6 +276,7 @@ def incremental_build(
skip = False
if (
latest_version_position is None
or unreleased_end is None
or latest_version_position > unreleased_end
):
continue
Expand Down
10 changes: 6 additions & 4 deletions commitizen/changelog_formats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,16 @@ def get_changelog_format(
:raises FormatUnknown: if a non-empty name is provided but cannot be found in the known formats
"""
name: str | None = config.settings.get("changelog_format")
format = (
name and KNOWN_CHANGELOG_FORMATS.get(name) or _guess_changelog_format(filename)
format_cls: type[ChangelogFormat] | None = (
KNOWN_CHANGELOG_FORMATS.get(name) if name else None
)
if format_cls is None:
format_cls = _guess_changelog_format(filename)

if not format:
if not format_cls:
raise ChangelogFormatUnknown(f"Unknown changelog format '{name}'")

return format(config)
return format_cls(config)


def _guess_changelog_format(filename: str | None) -> type[ChangelogFormat] | None:
Expand Down
2 changes: 1 addition & 1 deletion commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ def main() -> None:
if args.no_raise:
sys.excepthook = partial(sys.excepthook, no_raise=parse_no_raise(args.no_raise))

args.func(conf, arguments)() # type: ignore[arg-type]
args.func(conf, arguments)() # type: ignore # noqa: PGH003


if __name__ == "__main__":
Expand Down
20 changes: 12 additions & 8 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from commitizen import bump, factory, git, hooks, out
from commitizen.changelog_formats import get_changelog_format
from commitizen.commands.changelog import Changelog
from commitizen.commands.changelog import Changelog, ChangelogArgs
from commitizen.defaults import Settings
from commitizen.exceptions import (
BumpCommitFailedError,
Expand Down Expand Up @@ -263,7 +263,7 @@ def __call__(self) -> None:
)
)

rules = TagRules.from_settings(cast("Settings", self.bump_settings))
rules = TagRules.from_settings(self.bump_settings)
current_tag = rules.find_tag_for(git.get_tags(), current_version)
current_tag_version = (
current_tag.name if current_tag else rules.normalize_tag(current_version)
Expand Down Expand Up @@ -323,18 +323,22 @@ def __call__(self) -> None:
try:
Changelog(
self.config,
{**changelog_args, "dry_run": True}, # type: ignore[typeddict-item]
{**changelog_args, "dry_run": True},
)()
except DryRunExit:
pass

changelog_cmd = Changelog(
self.config,
{
**changelog_args, # type: ignore[typeddict-item]
"file_name": self.file_name,
"allow_no_commit": bool(self.arguments["allow_no_commit"]),
},
# TODO: remove cast once self.file_name is narrowed to str
cast(
"ChangelogArgs",
{
**changelog_args,
"file_name": self.file_name,
"allow_no_commit": bool(self.arguments["allow_no_commit"]),
},
),
)
changelog_cmd()
changelog_file_name = changelog_cmd.file_name
Expand Down
7 changes: 3 additions & 4 deletions commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from difflib import SequenceMatcher
from operator import itemgetter
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypedDict, cast
from typing import TYPE_CHECKING, Any, TypedDict

from commitizen import changelog, defaults, factory, git, out
from commitizen.changelog_formats import get_changelog_format
Expand Down Expand Up @@ -99,11 +99,10 @@ def __init__(self, config: BaseConfig, arguments: ChangelogArgs) -> None:
self.change_type_map = (
self.config.settings.get("change_type_map") or self.cz.change_type_map
)
self.change_type_order = cast(
"list[str]",
self.change_type_order: list[str] = (
self.config.settings.get("change_type_order")
or self.cz.change_type_order
or defaults.CHANGE_TYPE_ORDER,
or defaults.CHANGE_TYPE_ORDER
)
self.rev_range = arguments.get("rev_range")
self.tag_rules = TagRules(
Expand Down
2 changes: 1 addition & 1 deletion commitizen/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _create_config_from_path(path: Path) -> BaseConfig:
return create_config(data=path.read_bytes(), path=path)


def read_cfg(filepath: str | None = None) -> BaseConfig:
def read_cfg(filepath: str | Path | None = None) -> BaseConfig:
if filepath is not None:
conf_path = Path(filepath)
if not conf_path.is_file():
Expand Down
2 changes: 1 addition & 1 deletion commitizen/config/base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def settings(self) -> Settings:

@property
def path(self) -> Path:
return self._path # type: ignore[return-value]
return self._path # type: ignore # noqa: PGH003

@path.setter
def path(self, path: Path) -> None:
Expand Down
6 changes: 3 additions & 3 deletions commitizen/config/toml_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def init_empty_config_content(self) -> None:

if config_doc.get("tool") is None:
config_doc["tool"] = table()
config_doc["tool"]["commitizen"] = table() # type: ignore[index]
config_doc["tool"]["commitizen"] = table() # type: ignore # noqa: PGH003

with self.path.open("wb") as output_toml_file:
output_toml_file.write(
Expand All @@ -46,7 +46,7 @@ def init_empty_config_content(self) -> None:
def set_key(self, key: str, value: object) -> Self:
config_doc = parse(self.path.read_bytes())

config_doc["tool"]["commitizen"][key] = value # type: ignore[index]
config_doc["tool"]["commitizen"][key] = value # type: ignore # noqa: PGH003
self.path.write_bytes(config_doc.as_string().encode(self._settings["encoding"]))

return self
Expand All @@ -65,6 +65,6 @@ def _parse_setting(self, data: bytes | str) -> None:
raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")

try:
self.settings.update(doc["tool"]["commitizen"]) # type: ignore[index,typeddict-item] # TODO: fix this
self.settings.update(doc["tool"]["commitizen"]) # type: ignore # noqa: PGH003 # TODO: fix this
except exceptions.NonExistentKey:
pass
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def questions(self) -> list[CzQuestion]:
},
]

def message(self, answers: ConventionalCommitsAnswers) -> str: # type: ignore[override]
def message(self, answers: ConventionalCommitsAnswers) -> str: # type: ignore # noqa: PGH003
prefix = answers["prefix"]
scope = answers["scope"]
subject = answers["subject"]
Expand Down
6 changes: 4 additions & 2 deletions commitizen/cz/customize/customize.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ def __init__(self, config: BaseConfig) -> None:
setattr(self, attr_name, value)

def questions(self) -> list[CzQuestion]:
return self.custom_settings.get("questions", [{}]) # type: ignore[return-value]
return self.custom_settings.get("questions", [{}]) # type: ignore # noqa: PGH003

def message(self, answers: Mapping[str, Any]) -> str:
message_template = Template(self.custom_settings.get("message_template", ""))
if getattr(Template, "substitute", None):
return message_template.substitute(**answers) # type: ignore[attr-defined,no-any-return] # pragma: no cover # TODO: check if we can fix this
return message_template.substitute( # type: ignore[attr-defined]
**answers
) # pragma: no cover # TODO: check if we can fix this
return message_template.render(**answers)
Comment thread
bearomorphism marked this conversation as resolved.

def example(self) -> str:
Expand Down
3 changes: 2 additions & 1 deletion commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import lru_cache
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import IO, Any

from commitizen import cmd, out
from commitizen.exceptions import GitCommandError
Expand Down Expand Up @@ -316,7 +317,7 @@ def get_core_editor() -> str | None:
return None


def smart_open(*args, **kwargs): # type: ignore[no-untyped-def,unused-ignore] # noqa: ANN201
def smart_open(*args: Any, **kwargs: Any) -> IO[Any]:
"""Open a file with the EOL style determined from Git."""
return open(*args, newline=EOLType.for_open(), **kwargs)

Expand Down
4 changes: 2 additions & 2 deletions commitizen/providers/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def set_version(self, version: str) -> None:
self.file.write_text(tomlkit.dumps(document), encoding=self._get_encoding())

def get(self, document: tomlkit.TOMLDocument) -> str:
return document["project"]["version"] # type: ignore[index,return-value]
return document["project"]["version"] # type: ignore # noqa: PGH003

def set(self, document: tomlkit.TOMLDocument, version: str) -> None:
document["project"]["version"] = version # type: ignore[index]
document["project"]["version"] = version # type: ignore # noqa: PGH003
6 changes: 3 additions & 3 deletions commitizen/providers/cargo_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ def set_lock_version(self, version: str) -> None:
assert isinstance(packages, AoT)

try:
cargo_package_name = cargo_toml_content["package"]["name"] # type: ignore[index]
cargo_package_name = cargo_toml_content["package"]["name"] # type: ignore # noqa: PGH003
if TYPE_CHECKING:
assert isinstance(cargo_package_name, str)
for i, package in enumerate(packages):
if package["name"] == cargo_package_name:
cargo_lock_content["package"][i]["version"] = version # type: ignore[index]
cargo_lock_content["package"][i]["version"] = version # type: ignore # noqa: PGH003
break
except NonExistentKey:
workspace = cargo_toml_content.get("workspace", {})
Expand Down Expand Up @@ -94,7 +94,7 @@ def set_lock_version(self, version: str) -> None:

for i, package in enumerate(packages):
if package["name"] in members_inheriting:
cargo_lock_content["package"][i]["version"] = version # type: ignore[index]
cargo_lock_content["package"][i]["version"] = version # type: ignore # noqa: PGH003

self.lock_file.write_text(
dumps(cargo_lock_content), encoding=self._get_encoding()
Expand Down
8 changes: 4 additions & 4 deletions commitizen/providers/poetry_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class PoetryProvider(TomlProvider):

filename = "pyproject.toml"

def get(self, pyproject: tomlkit.TOMLDocument) -> str:
return pyproject["tool"]["poetry"]["version"] # type: ignore[index,return-value]
def get(self, document: tomlkit.TOMLDocument) -> str:
return document["tool"]["poetry"]["version"] # type: ignore # noqa: PGH003

def set(self, pyproject: tomlkit.TOMLDocument, version: str) -> None:
pyproject["tool"]["poetry"]["version"] = version # type: ignore[index]
def set(self, document: tomlkit.TOMLDocument, version: str) -> None:
document["tool"]["poetry"]["version"] = version # type: ignore # noqa: PGH003
6 changes: 3 additions & 3 deletions commitizen/providers/uv_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ def set_lock_version(self, version: str) -> None:
pyproject_toml_content = tomlkit.parse(
self.file.read_text(encoding=self._get_encoding())
)
project_name = pyproject_toml_content["project"]["name"] # type: ignore[index]
project_name = pyproject_toml_content["project"]["name"] # type: ignore # noqa: PGH003
normalized_project_name = canonicalize_name(str(project_name))

document = tomlkit.parse(
self.lock_file.read_text(encoding=self._get_encoding())
)

packages: tomlkit.items.AoT = document["package"] # type: ignore[assignment]
packages: tomlkit.items.AoT = document["package"] # type: ignore # noqa: PGH003
for i, package in enumerate(packages):
if package["name"] == normalized_project_name:
document["package"][i]["version"] = version # type: ignore[index]
document["package"][i]["version"] = version # type: ignore # noqa: PGH003
break
self.lock_file.write_text(
tomlkit.dumps(document), encoding=self._get_encoding()
Expand Down
11 changes: 6 additions & 5 deletions commitizen/version_schemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def scheme(self) -> VersionScheme:

@property
def prerelease(self) -> str | None:
# version.pre is needed for mypy check
# version.pre is needed for static type checking
if self.is_prerelease and self.pre:
return f"{self.pre[0]}{self.pre[1]}"
return None
Expand Down Expand Up @@ -253,7 +253,7 @@ def bump(

if self.local and is_local_version:
local_version = self.scheme(self.local).bump(increment)
return self.scheme(f"{self.public}+{local_version}") # type: ignore[return-value]
return self.scheme(f"{self.public}+{local_version}") # type: ignore # noqa: PGH003

base = self._get_increment_base(increment, exact_increment)
dev_version = self.generate_devrelease(devrelease)
Expand All @@ -270,7 +270,7 @@ def bump(
# TODO: post version
return self.scheme(
f"{base}{pre_version}{dev_version}{self.generate_build_metadata(build_metadata)}"
) # type: ignore[return-value]
) # type: ignore # noqa: PGH003

def _get_increment_base(
self, increment: Increment | None, exact_increment: bool
Expand Down Expand Up @@ -385,7 +385,8 @@ def _get_prerelease(self) -> str:
return ".".join(prerelease_parts)


DEFAULT_SCHEME: VersionScheme = Pep440
# cast needed: ty cannot resolve type[Pep440] as type[VersionProtocol]
DEFAULT_SCHEME: VersionScheme = cast("VersionScheme", Pep440)
Comment thread
bearomorphism marked this conversation as resolved.

SCHEMES_ENTRYPOINT = "commitizen.scheme"
"""Schemes entrypoints group"""
Expand Down Expand Up @@ -420,7 +421,7 @@ def get_version_scheme(settings: Settings, name: str | None = None) -> VersionSc
(ep,) = metadata.entry_points(name=name, group=SCHEMES_ENTRYPOINT)
except ValueError:
raise VersionSchemeUnknown(f'Version scheme "{name}" unknown.')
scheme = cast("VersionScheme", ep.load())
scheme: VersionScheme = ep.load()

# `VersionProtocol` is a `@runtime_checkable` Protocol, but `issubclass()` is not
# supported for Protocols with non-method members. We check an instance instead by
Expand Down
6 changes: 3 additions & 3 deletions docs/contributing/contributing_tldr.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ uv sync --dev --frozen
# Make ruff happy
uv run poe format

# Check if ruff and mypy are happy
# Check if ruff and ty are happy
uv run poe lint

# Check if mypy is happy in python 3.10
mypy --python-version 3.10
# Check ty against Python 3.10 types (matches [tool.ty.environment] python-version)
uv run ty check --python-version 3.10

# Run tests in parallel.
pytest -n auto # This may take a while.
Expand Down
Loading
Loading