Skip to content

Commit b9e1380

Browse files
committed
feat(providers): add support for some TOML-based versions (PEP621, Poetry, Cargo)
1 parent dfa354b commit b9e1380

File tree

5 files changed

+180
-18
lines changed

5 files changed

+180
-18
lines changed

commitizen/providers.py

+66-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
from abc import ABC, abstractmethod
4-
from typing import cast
4+
from pathlib import Path
5+
from typing import ClassVar, cast
56

67
import importlib_metadata as metadata
8+
import tomlkit
79

810
from commitizen.config.base_config import BaseConfig
911
from commitizen.exceptions import VersionProviderUnknown
@@ -51,6 +53,69 @@ def set_version(self, version: str):
5153
self.config.set_key("version", version)
5254

5355

56+
class FileProvider(VersionProvider):
57+
"""
58+
Base class for file-based version providers
59+
"""
60+
61+
filename: ClassVar[str]
62+
63+
@property
64+
def file(self) -> Path:
65+
return Path() / self.filename
66+
67+
68+
class TomlProvider(FileProvider):
69+
"""
70+
Base class for TOML-based version providers
71+
"""
72+
73+
def get_version(self) -> str:
74+
document = tomlkit.parse(self.file.read_text())
75+
return self.get(document)
76+
77+
def set_version(self, version: str):
78+
document = tomlkit.parse(self.file.read_text())
79+
self.set(document, version)
80+
self.file.write_text(tomlkit.dumps(document))
81+
82+
def get(self, document: tomlkit.TOMLDocument) -> str:
83+
return document["project"]["version"] # type: ignore
84+
85+
def set(self, document: tomlkit.TOMLDocument, version: str):
86+
document["project"]["version"] = version # type: ignore
87+
88+
89+
class Pep621Provider(TomlProvider):
90+
"""
91+
PEP-621 version management
92+
"""
93+
94+
filename = "pyproject.toml"
95+
96+
97+
class PoetryProvider(TomlProvider):
98+
"""
99+
Poetry version management
100+
"""
101+
102+
filename = "pyproject.toml"
103+
104+
def get(self, pyproject: tomlkit.TOMLDocument) -> str:
105+
return pyproject["tool"]["poetry"]["version"] # type: ignore
106+
107+
def set(self, pyproject: tomlkit.TOMLDocument, version: str):
108+
pyproject["tool"]["poetry"]["version"] = version # type: ignore
109+
110+
111+
class CargoProvider(TomlProvider):
112+
"""
113+
Cargo version management
114+
"""
115+
116+
filename = "Cargo.toml"
117+
118+
54119
def get_provider(config: BaseConfig) -> VersionProvider:
55120
"""
56121
Get the version provider as defined in the configuration

docs/config.md

+9
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ Commitizen can read and write version from different sources.
118118
By default, it use the `commitizen` one which is using the `version` field from the commitizen settings.
119119
But you can use any `commitizen.provider` entrypoint as value for `version_provider`.
120120

121+
Commitizen provides some version providers for some well known formats:
122+
123+
| name | description |
124+
| ---- | ----------- |
125+
| `commitizen` | Default version provider: Fetch and set version in commitizen config. |
126+
| `pep621` | Get and set version from `pyproject.toml` `project.version` field |
127+
| `poetry` | Get and set version from `pyproject.toml` `tool.poetry.version` field |
128+
| `cargo` | Get and set version from `Cargo.toml` `project.version` field |
129+
121130
### Custom version provider
122131

123132
You can add you own version provider by extending `VersionProvider` and exposing it on the `commitizen.provider` entrypoint.

docs/faq.md

+8-15
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,19 @@ PEP621 establishes a `[project]` definition inside `pyproject.toml`
55
```toml
66
[project]
77
name = "spam"
8-
version = "2020.0.0"
8+
version = "42.0.1"
99
```
1010

11-
Commitizen **won't** use the `project.version` as a source of truth because it's a
12-
tool aimed for any kind of project.
13-
14-
If we were to use it, it would increase the complexity of the tool. Also why
15-
wouldn't we support other project files like `cargo.toml` or `package.json`?
16-
17-
Instead of supporting all the different project files, you can use `version_files`
18-
inside `[tool.commitizen]`, and it will cheaply keep any of these project files in sync
11+
Commitizen provides a [`pep621` version provider](config.md#version-providers) to get and set version from this field.
12+
You just need to set the proper `version_provider` setting:
1913

2014
```toml
15+
[project]
16+
name = "spam"
17+
version = "42.0.1"
18+
2119
[tool.commitizen]
22-
version = "2.5.1"
23-
version_files = [
24-
"pyproject.toml:^version",
25-
"cargo.toml:^version",
26-
"package.json:\"version\":"
27-
]
20+
version_provider = "pep621"
2821
```
2922

3023
## Why are `revert` and `chore` valid types in the check pattern of cz conventional_commits but not types we can select?

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz"
105105
cz_customize = "commitizen.cz.customize:CustomizeCommitsCz"
106106

107107
[tool.poetry.plugins."commitizen.provider"]
108+
cargo = "commitizen.providers:CargoProvider"
108109
commitizen = "commitizen.providers:CommitizenProvider"
110+
pep621 = "commitizen.providers:Pep621Provider"
111+
poetry = "commitizen.providers:PoetryProvider"
109112

110113
[tool.isort]
111114
profile = "black"

tests/test_version_providers.py

+94-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
import os
4+
from pathlib import Path
5+
from textwrap import dedent
6+
from typing import TYPE_CHECKING, Iterator
47

58
import pytest
69

710
from commitizen.config.base_config import BaseConfig
811
from commitizen.exceptions import VersionProviderUnknown
9-
from commitizen.providers import CommitizenProvider, get_provider
12+
from commitizen.providers import (
13+
CargoProvider,
14+
CommitizenProvider,
15+
Pep621Provider,
16+
PoetryProvider,
17+
get_provider,
18+
)
1019

1120
if TYPE_CHECKING:
1221
from pytest_mock import MockerFixture
1322

1423

24+
@pytest.fixture
25+
def chdir(tmp_path: Path) -> Iterator[Path]:
26+
cwd = Path()
27+
os.chdir(tmp_path)
28+
yield tmp_path
29+
os.chdir(cwd)
30+
31+
1532
def test_default_version_provider_is_commitizen_config(config: BaseConfig):
1633
provider = get_provider(config)
1734

@@ -33,3 +50,78 @@ def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture):
3350

3451
provider.set_version("43.1")
3552
mock.assert_called_once_with("version", "43.1")
53+
54+
55+
def test_pep621_provider(config: BaseConfig, chdir: Path):
56+
pyproject_toml = chdir / "pyproject.toml"
57+
pyproject_toml.write_text(
58+
dedent(
59+
"""\
60+
[project]
61+
version = "0.1.0"
62+
"""
63+
)
64+
)
65+
66+
provider = Pep621Provider(config)
67+
68+
assert provider.get_version() == "0.1.0"
69+
70+
provider.set_version("43.1")
71+
72+
assert pyproject_toml.read_text() == dedent(
73+
"""\
74+
[project]
75+
version = "43.1"
76+
"""
77+
)
78+
79+
80+
def test_poetry_provider(config: BaseConfig, chdir: Path):
81+
pyproject_toml = chdir / "pyproject.toml"
82+
pyproject_toml.write_text(
83+
dedent(
84+
"""\
85+
[tool.poetry]
86+
version = "0.1.0"
87+
"""
88+
)
89+
)
90+
config.settings["version_provider"] = "poetry"
91+
92+
provider = get_provider(config)
93+
assert isinstance(provider, PoetryProvider)
94+
assert provider.get_version() == "0.1.0"
95+
96+
provider.set_version("43.1")
97+
assert pyproject_toml.read_text() == dedent(
98+
"""\
99+
[tool.poetry]
100+
version = "43.1"
101+
"""
102+
)
103+
104+
105+
def test_cargo_provider(config: BaseConfig, chdir: Path):
106+
cargo_toml = chdir / "Cargo.toml"
107+
cargo_toml.write_text(
108+
dedent(
109+
"""\
110+
[project]
111+
version = "0.1.0"
112+
"""
113+
)
114+
)
115+
config.settings["version_provider"] = "cargo"
116+
117+
provider = get_provider(config)
118+
assert isinstance(provider, CargoProvider)
119+
assert provider.get_version() == "0.1.0"
120+
121+
provider.set_version("43.1")
122+
assert cargo_toml.read_text() == dedent(
123+
"""\
124+
[project]
125+
version = "43.1"
126+
"""
127+
)

0 commit comments

Comments
 (0)