Skip to content

Commit 080f3f0

Browse files
Develop (#24)
Almost done, now for some documentation
1 parent 739df04 commit 080f3f0

File tree

9 files changed

+421
-33
lines changed

9 files changed

+421
-33
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
<!-- towncrier release notes start -->
44

5+
## [0.1.0](https://github.com/Nagidal/hatch-semver/tree/0.1.0) - 2022-11-13
6+
7+
8+
### New Features
9+
10+
- Added inline options for prerelease and build tokens [#23](https://github.com/Nagidal/hatch-semver/issues/23)
11+
12+
13+
### Bugfixes
14+
15+
- Fixed undefined ValeError [#16](https://github.com/Nagidal/hatch-semver/issues/16)
16+
- Can bump prereleases now [#17](https://github.com/Nagidal/hatch-semver/issues/17)
17+
- Can bump builds now [#18](https://github.com/Nagidal/hatch-semver/issues/18)
18+
- Can update version to release [#22](https://github.com/Nagidal/hatch-semver/issues/22)
19+
20+
21+
### Development Details
22+
23+
- Created tests [#19](https://github.com/Nagidal/hatch-semver/issues/19)
24+
25+
526
## [0.0.8](https://github.com/Nagidal/hatch-semver/tree/0.0.8) - 2022-11-10
627

728

pyproject.toml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ readme = "README.md"
1111
license = "MIT"
1212
requires-python = ">=3.9"
1313
authors = [
14-
{ name = "Sven Siegmund", email = "sven.siegmund@iav.de" },
14+
{ name = "Sven Siegmund", email = "sven.siegmund@gmail.com" },
1515
]
1616

1717
keywords = [
@@ -40,7 +40,7 @@ classifiers = [
4040

4141
dependencies = [
4242
"hatchling",
43-
"semver",
43+
"semver ~= 2.13.0",
4444
]
4545

4646
dynamic = [
@@ -67,8 +67,15 @@ dependencies = [
6767
"towncrier",
6868
]
6969

70+
[tool.hatch.envs.test]
71+
dependencies = [
72+
"pytest",
73+
"pytest-cov",
74+
"hatch",
75+
]
76+
7077
[tool.hatch.envs.default.scripts]
71-
cov = "pytest -v --cov-report=term-missing --cov-config=pyproject.toml --cov=src/hatch_semver --cov=tests"
78+
cov = "pytest -vx --cov-report=term-missing --cov-config=pyproject.toml --cov=src/hatch_semver --cov=tests"
7279
no-cov = "cov --no-cov"
7380

7481
[tool.hatch.envs.style]
@@ -93,7 +100,7 @@ build = "pdoc --html --output-dir docs hatch-semver"
93100
serve = "pdoc --http : hatch-semver"
94101

95102
[[tool.hatch.envs.test.matrix]]
96-
python = ["39", "310"]
103+
python = ["310"]
97104

98105
[tool.coverage.run]
99106
branch = true
@@ -144,6 +151,6 @@ showcontent = true
144151
name = "Bugfixes"
145152
showcontent = true
146153

147-
[tool.towncrier.fragment.detail]
154+
[tool.towncrier.fragment.unimportant]
148155
name = "Development Details"
149156
showcontent = true

src/hatch_semver/__about__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
__author_email__ = "[email protected]"
88
__maintainer__ = __author__
99
__maintainer_email__ = __author_email__
10-
__release_date__ = date(year=2022, month=11, day=10)
11-
__version__ = "0.0.8"
10+
__release_date__ = date(year=2022, month=11, day=13)
11+
__version__ = "0.1.0"
1212

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python
2+
3+
from dataclasses import dataclass, field, InitVar
4+
from typing import Optional, ClassVar
5+
6+
7+
@dataclass
8+
class BumpInstruction:
9+
sep: ClassVar[str] = "="
10+
acceptable_version_parts: ClassVar[tuple[str]] = (
11+
"major",
12+
"minor",
13+
"patch",
14+
"prerelease",
15+
"build",
16+
"release",
17+
)
18+
version_part: str = field(init=False)
19+
token: Optional[str] = field(init=False)
20+
is_specific: bool = field(init=False)
21+
instruction: InitVar[str]
22+
23+
def __post_init__(self, instruction) -> None:
24+
if self.sep not in instruction:
25+
raw_version_part = instruction
26+
raw_token = None
27+
else:
28+
raw_version_part, raw_token = instruction.split(self.sep, maxsplit=1)
29+
self.version_part, self.token, self.is_specific = self.normalize_version_part(
30+
raw_version_part, raw_token
31+
)
32+
33+
@classmethod
34+
def normalize_version_part(cls, part: str, token: str) -> tuple[str, str, bool]:
35+
if part in ("pre", "prerelease", "pre-release", "rc"):
36+
part = "prerelease"
37+
elif part in ("micro", "fix"):
38+
part = "patch"
39+
elif part in ("alpha", "beta"):
40+
if token:
41+
raise ValueError(
42+
" ".join(
43+
(
44+
f"{part} version cannot be set to {token} directly.",
45+
f"Use 'prerelease={part}' instead",
46+
)
47+
)
48+
)
49+
token = part
50+
part = "prerelease"
51+
elif part in ("dev",):
52+
if token:
53+
raise ValueError(
54+
f"{part} version cannot be set to {token} directly. Use 'build={part}' instead"
55+
)
56+
token = part
57+
part = "build"
58+
if token:
59+
if part in ("major", "minor", "patch", "release"):
60+
raise ValueError(
61+
f"{part} version cannot be set to {token} specifically. Use {part} alone"
62+
)
63+
if part in cls.acceptable_version_parts:
64+
specific = False
65+
else:
66+
specific = True
67+
return part, token, specific

src/hatch_semver/semver_scheme.py

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#!/usr/bin/env python
22

33
from hatchling.version.scheme.plugin.interface import VersionSchemeInterface
4+
from typing import Mapping
5+
from semver import VersionInfo
6+
from operator import ge, gt
7+
from copy import deepcopy
8+
from .bump_instruction import BumpInstruction
49

510

611
class SemverScheme(VersionSchemeInterface):
@@ -12,33 +17,55 @@ class SemverScheme(VersionSchemeInterface):
1217
"""
1318

1419
PLUGIN_NAME = "semver"
20+
INSTRUCTION_SEPARATOR = ","
1521

16-
def update(self, desired_version, original_version, version_data) -> str:
17-
from semver import VersionInfo
18-
19-
original = VersionInfo.parse(original_version)
20-
parts = desired_version.replace("micro", "patch").replace("fix", "patch").split(",")
21-
22-
for part in parts:
23-
if part in ("major", "minor", "patch"):
24-
next_version = getattr(original, "bump_" + part)()
25-
original = next_version
26-
elif part in ("post", "rev", "r"):
27-
raise ValeError(f"Semver has no concept of a post-release. Use 'build' instead")
28-
elif part == "dev":
29-
raise ValeError(f"Semver has no concept of a dev-release. Use 'build' instead")
22+
def update(self, desired_version: str, original_version: str, version_data: Mapping) -> str:
23+
if not desired_version:
24+
return original_version
25+
original_version = VersionInfo.parse(original_version)
26+
current_version = deepcopy(original_version)
27+
instructions: str = desired_version
28+
validate = self.config.get("validate-bump", True)
29+
for instruction in instructions.split(self.INSTRUCTION_SEPARATOR):
30+
bi = BumpInstruction(instruction)
31+
last_bump_was_build = False
32+
if bi.version_part == "build":
33+
current_version = current_version.bump_build(token=bi.token)
34+
last_bump_was_build = True
35+
elif bi.version_part == "release":
36+
current_version = current_version.finalize_version()
37+
elif bi.is_specific:
38+
current_version = VersionInfo.parse(bi.version_part)
3039
else:
31-
if len(parts) > 1:
32-
raise ValueError(
33-
"Cannot specify multiple update operations with an explicit version"
34-
)
40+
current_version = current_version.next_version(
41+
bi.version_part, prerelease_token=bi.token
42+
)
43+
if validate:
44+
self.validate_bump(current_version, original_version, bumped_build=last_bump_was_build)
45+
return current_version
3546

36-
next_version = VersionInfo.parse(part)
37-
if self.config.get("validate-bump", True) and next_version <= original:
38-
raise ValueError(
39-
f"Version `{part}` is not higher than the original version `{original_version}`"
47+
def validate_bump(
48+
self, current_version: VersionInfo, original_version: VersionInfo, bumped_build: bool
49+
) -> None:
50+
"""
51+
In Semver spec, all builds are equally ranked.
52+
So for build bumps we validate only whether the current version is equal to the original one.
53+
For other bumps we validate if the current version is higher than the original.
54+
"""
55+
if bumped_build:
56+
comparator = ge
57+
relation = "at least as high as"
58+
else:
59+
comparator = gt
60+
relation = "higher than"
61+
if comparator(current_version, original_version):
62+
return
63+
else:
64+
raise ValueError(
65+
" ".join(
66+
(
67+
f"Version `{current_version}` is not {relation}",
68+
f"the original version `{original_version}`",
4069
)
41-
else:
42-
return str(next_version)
43-
44-
return str(original)
70+
)
71+
)

tests/conftest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python
2+
3+
from typing import Generator
4+
import os
5+
import pytest
6+
from hatch.config.constants import AppEnvVars, ConfigEnvVars, PublishEnvVars
7+
from hatch.utils.fs import Path, temp_directory
8+
9+
10+
collect_ignore = [
11+
# "test_semver_scheme.py",
12+
# "test_bump_instruction.py",
13+
]
14+
15+
@pytest.fixture(scope='session', autouse=True)
16+
def isolation() -> Generator[Path, None, None]:
17+
with temp_directory() as d:
18+
data_dir = d / 'data'
19+
data_dir.mkdir()
20+
cache_dir = d / 'cache'
21+
cache_dir.mkdir()
22+
23+
default_env_vars = {
24+
AppEnvVars.NO_COLOR: '1',
25+
ConfigEnvVars.DATA: str(data_dir),
26+
ConfigEnvVars.CACHE: str(cache_dir),
27+
PublishEnvVars.REPO: 'dev',
28+
'HATCH_SELF_TESTING': 'true',
29+
'GIT_AUTHOR_NAME': 'Foo Bar',
30+
'GIT_AUTHOR_EMAIL': '[email protected]',
31+
'COLUMNS': '80',
32+
'LINES': '24',
33+
}
34+
with d.as_cwd(default_env_vars):
35+
os.environ.pop(AppEnvVars.ENV_ACTIVE, None)
36+
yield d
37+
38+

0 commit comments

Comments
 (0)