hatch-calvar-sample 2026.1.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hatch_calvar_sample/__about__.py +34 -0
- hatch_calvar_sample/__init__.py +8 -0
- hatch_calvar_sample/cli.py +313 -0
- hatch_calvar_sample-2026.1.19.1.dist-info/METADATA +421 -0
- hatch_calvar_sample-2026.1.19.1.dist-info/RECORD +8 -0
- hatch_calvar_sample-2026.1.19.1.dist-info/WHEEL +4 -0
- hatch_calvar_sample-2026.1.19.1.dist-info/entry_points.txt +2 -0
- hatch_calvar_sample-2026.1.19.1.dist-info/licenses/LICENSE.txt +9 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026-present QAToolist <qatoolist@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Version metadata for hatch_calvar_sample package."""
|
|
5
|
+
|
|
6
|
+
# Import version function with fallback for Python < 3.8
|
|
7
|
+
try:
|
|
8
|
+
from importlib.metadata import version as _version_func
|
|
9
|
+
except ImportError:
|
|
10
|
+
# Python < 3.8
|
|
11
|
+
from importlib_metadata import (
|
|
12
|
+
version as _version_func, # type: ignore[no-untyped-call]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_package_version(package_name: str) -> str:
|
|
17
|
+
"""Get version from package metadata."""
|
|
18
|
+
return _version_func(package_name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Try to get version from package metadata (when installed)
|
|
22
|
+
try:
|
|
23
|
+
__version__ = get_package_version("hatch-calvar-sample")
|
|
24
|
+
except Exception:
|
|
25
|
+
# Fallback: try reading from VERSION file (for development builds)
|
|
26
|
+
import os
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
version_file = Path(__file__).parent / "VERSION"
|
|
30
|
+
if version_file.exists():
|
|
31
|
+
__version__ = version_file.read_text().strip()
|
|
32
|
+
else:
|
|
33
|
+
# Last resort: read from environment variable
|
|
34
|
+
__version__ = os.environ.get("HATCH_CALVER_VERSION", "0.0.0.dev0")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026-present QAToolist <qatoolist@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Sample hatch project demonstrating CalVer (YYYY.MM.DD.MICRO) with hatch."""
|
|
5
|
+
|
|
6
|
+
from hatch_calvar_sample.__about__ import __version__
|
|
7
|
+
|
|
8
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""CLI tool for CalVer version management."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
# Import version function with fallback for Python < 3.8
|
|
10
|
+
try:
|
|
11
|
+
from importlib.metadata import version as _version_func
|
|
12
|
+
except ImportError:
|
|
13
|
+
# Python < 3.8
|
|
14
|
+
from importlib_metadata import (
|
|
15
|
+
version as _version_func, # type: ignore[no-untyped-call]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Import version calculation functions from script
|
|
20
|
+
# We'll need to make these importable or duplicate the logic
|
|
21
|
+
# For now, we'll import from the script directory
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts"))
|
|
23
|
+
try:
|
|
24
|
+
from calc_version import (
|
|
25
|
+
calculate_next_version,
|
|
26
|
+
check_pep440_compliance,
|
|
27
|
+
get_git_tags,
|
|
28
|
+
parse_calver_tag,
|
|
29
|
+
validate_version_format,
|
|
30
|
+
)
|
|
31
|
+
except ImportError:
|
|
32
|
+
# Fallback if script not available
|
|
33
|
+
def calculate_next_version():
|
|
34
|
+
return "0.0.0.0" # nosec B104
|
|
35
|
+
|
|
36
|
+
def validate_version_format(version: str) -> bool:
|
|
37
|
+
return bool(version)
|
|
38
|
+
|
|
39
|
+
def check_pep440_compliance(version: str) -> bool:
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
def parse_calver_tag(tag: str):
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def get_git_tags():
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_package_version_from_metadata() -> Optional[str]:
|
|
50
|
+
"""Get version from installed package metadata.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Version string or None if not available
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
return _version_func("hatch-calvar-sample")
|
|
57
|
+
except Exception:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def version_calc(args: argparse.Namespace) -> int:
|
|
62
|
+
"""Calculate next version.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
args: Parsed command-line arguments
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Exit code (0 for success, 1 for error)
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
version = calculate_next_version()
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f"Error calculating version: {e}", file=sys.stderr)
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
if args.json:
|
|
77
|
+
output = {"version": version}
|
|
78
|
+
print(json.dumps(output))
|
|
79
|
+
else:
|
|
80
|
+
print(version)
|
|
81
|
+
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def version_check(args: argparse.Namespace) -> int:
|
|
86
|
+
"""Check current version from different sources.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
args: Parsed command-line arguments
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Exit code (0 for success, 1 for error)
|
|
93
|
+
"""
|
|
94
|
+
versions = {}
|
|
95
|
+
|
|
96
|
+
# Check package metadata
|
|
97
|
+
pkg_version = get_package_version_from_metadata()
|
|
98
|
+
if pkg_version:
|
|
99
|
+
versions["package"] = pkg_version
|
|
100
|
+
|
|
101
|
+
# Check git tags
|
|
102
|
+
git_tags = get_git_tags()
|
|
103
|
+
calver_tags = [tag for tag in git_tags if parse_calver_tag(tag)]
|
|
104
|
+
if calver_tags:
|
|
105
|
+
# Get latest CalVer tag
|
|
106
|
+
parsed_tags = [(tag, parse_calver_tag(tag)) for tag in calver_tags]
|
|
107
|
+
parsed_tags.sort(key=lambda x: x[1], reverse=True) # Sort by date/micro
|
|
108
|
+
latest_tag = parsed_tags[0][0]
|
|
109
|
+
# Remove 'v' prefix if present
|
|
110
|
+
if latest_tag.startswith("v"):
|
|
111
|
+
versions["git_tag"] = latest_tag[1:]
|
|
112
|
+
else:
|
|
113
|
+
versions["git_tag"] = latest_tag
|
|
114
|
+
|
|
115
|
+
# Check VERSION file
|
|
116
|
+
version_file = (
|
|
117
|
+
Path(__file__).parent.parent.parent / "src" / "hatch_calvar_sample" / "VERSION"
|
|
118
|
+
)
|
|
119
|
+
if version_file.exists():
|
|
120
|
+
versions["file"] = version_file.read_text().strip()
|
|
121
|
+
|
|
122
|
+
if args.json:
|
|
123
|
+
output = {"versions": versions}
|
|
124
|
+
print(json.dumps(output, indent=2))
|
|
125
|
+
else:
|
|
126
|
+
if versions:
|
|
127
|
+
print("Current versions:")
|
|
128
|
+
for source, ver in versions.items():
|
|
129
|
+
print(f" {source}: {ver}")
|
|
130
|
+
else:
|
|
131
|
+
print("No version information found", file=sys.stderr)
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def version_validate(args: argparse.Namespace) -> int:
|
|
138
|
+
"""Validate version format.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
args: Parsed command-line arguments
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Exit code (0 for valid, 1 for invalid)
|
|
145
|
+
"""
|
|
146
|
+
if not args.version:
|
|
147
|
+
print("Error: version argument required", file=sys.stderr)
|
|
148
|
+
return 1
|
|
149
|
+
|
|
150
|
+
version = args.version
|
|
151
|
+
|
|
152
|
+
# Validate format
|
|
153
|
+
is_valid_format = validate_version_format(version)
|
|
154
|
+
|
|
155
|
+
# Check PEP 440 compliance
|
|
156
|
+
is_pep440 = False
|
|
157
|
+
if is_valid_format:
|
|
158
|
+
is_pep440 = check_pep440_compliance(version)
|
|
159
|
+
|
|
160
|
+
if args.json:
|
|
161
|
+
output = {
|
|
162
|
+
"version": version,
|
|
163
|
+
"valid_format": is_valid_format,
|
|
164
|
+
"pep440_compliant": is_pep440,
|
|
165
|
+
}
|
|
166
|
+
print(json.dumps(output))
|
|
167
|
+
return 0 if (is_valid_format and is_pep440) else 1
|
|
168
|
+
else:
|
|
169
|
+
if not is_valid_format:
|
|
170
|
+
print(f"Invalid CalVer format: {version}", file=sys.stderr)
|
|
171
|
+
return 1
|
|
172
|
+
|
|
173
|
+
if not is_pep440:
|
|
174
|
+
print(f"Version not PEP 440 compliant: {version}", file=sys.stderr)
|
|
175
|
+
return 1
|
|
176
|
+
|
|
177
|
+
print(f"Version '{version}' is valid and PEP 440 compliant")
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def version_compare(args: argparse.Namespace) -> int:
|
|
182
|
+
"""Compare two versions.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
args: Parsed command-line arguments
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Exit code (0 for success, 1 for error)
|
|
189
|
+
"""
|
|
190
|
+
if len(args.versions) != 2:
|
|
191
|
+
print("Error: exactly two versions required for comparison", file=sys.stderr)
|
|
192
|
+
return 1
|
|
193
|
+
|
|
194
|
+
v1_str, v2_str = args.versions
|
|
195
|
+
|
|
196
|
+
# Parse versions
|
|
197
|
+
v1 = parse_calver_tag(v1_str)
|
|
198
|
+
v2 = parse_calver_tag(v2_str)
|
|
199
|
+
|
|
200
|
+
if not v1:
|
|
201
|
+
print(f"Error: invalid version format: {v1_str}", file=sys.stderr)
|
|
202
|
+
return 1
|
|
203
|
+
|
|
204
|
+
if not v2:
|
|
205
|
+
print(f"Error: invalid version format: {v2_str}", file=sys.stderr)
|
|
206
|
+
return 1
|
|
207
|
+
|
|
208
|
+
# Compare (year, month, day, micro)
|
|
209
|
+
if v1 < v2:
|
|
210
|
+
result = "<"
|
|
211
|
+
elif v1 > v2:
|
|
212
|
+
result = ">"
|
|
213
|
+
else:
|
|
214
|
+
result = "=="
|
|
215
|
+
|
|
216
|
+
if args.json:
|
|
217
|
+
output = {
|
|
218
|
+
"version1": v1_str,
|
|
219
|
+
"version2": v2_str,
|
|
220
|
+
"comparison": result,
|
|
221
|
+
}
|
|
222
|
+
print(json.dumps(output))
|
|
223
|
+
else:
|
|
224
|
+
print(f"{v1_str} {result} {v2_str}")
|
|
225
|
+
|
|
226
|
+
return 0
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def version_info(args: argparse.Namespace) -> int:
|
|
230
|
+
"""Show version information.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
args: Parsed command-line arguments
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Exit code (0 for success, 1 for error)
|
|
237
|
+
"""
|
|
238
|
+
info = {}
|
|
239
|
+
|
|
240
|
+
# Get next version
|
|
241
|
+
try:
|
|
242
|
+
next_version = calculate_next_version()
|
|
243
|
+
parsed = parse_calver_tag(next_version)
|
|
244
|
+
if parsed:
|
|
245
|
+
year, month, day, micro = parsed
|
|
246
|
+
info["next_version"] = next_version
|
|
247
|
+
info["date"] = f"{year:04d}-{month:02d}-{day:02d}"
|
|
248
|
+
info["micro"] = micro
|
|
249
|
+
except Exception as e:
|
|
250
|
+
info["next_version_error"] = str(e)
|
|
251
|
+
|
|
252
|
+
# Get current package version
|
|
253
|
+
pkg_version = get_package_version_from_metadata()
|
|
254
|
+
if pkg_version:
|
|
255
|
+
info["current_package_version"] = pkg_version
|
|
256
|
+
|
|
257
|
+
if args.json:
|
|
258
|
+
print(json.dumps(info, indent=2))
|
|
259
|
+
else:
|
|
260
|
+
print("Version Information:")
|
|
261
|
+
for key, value in info.items():
|
|
262
|
+
print(f" {key}: {value}")
|
|
263
|
+
|
|
264
|
+
return 0
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def main():
|
|
268
|
+
"""Main CLI entry point."""
|
|
269
|
+
parser = argparse.ArgumentParser(
|
|
270
|
+
description="CalVer version management CLI", prog="calver-check"
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument("--json", action="store_true", help="Output in JSON format")
|
|
273
|
+
|
|
274
|
+
subparsers = parser.add_subparsers(dest="command", help="Command")
|
|
275
|
+
|
|
276
|
+
# calc command
|
|
277
|
+
calc_parser = subparsers.add_parser("calc", help="Calculate next CalVer version")
|
|
278
|
+
calc_parser.set_defaults(func=version_calc)
|
|
279
|
+
|
|
280
|
+
# check command
|
|
281
|
+
check_parser = subparsers.add_parser(
|
|
282
|
+
"check", help="Check current version from different sources"
|
|
283
|
+
)
|
|
284
|
+
check_parser.set_defaults(func=version_check)
|
|
285
|
+
|
|
286
|
+
# validate command
|
|
287
|
+
validate_parser = subparsers.add_parser(
|
|
288
|
+
"validate", help="Validate version format and PEP 440 compliance"
|
|
289
|
+
)
|
|
290
|
+
validate_parser.add_argument("version", help="Version string to validate")
|
|
291
|
+
validate_parser.set_defaults(func=version_validate)
|
|
292
|
+
|
|
293
|
+
# compare command
|
|
294
|
+
compare_parser = subparsers.add_parser("compare", help="Compare two versions")
|
|
295
|
+
compare_parser.add_argument("versions", nargs=2, help="Two versions to compare")
|
|
296
|
+
compare_parser.set_defaults(func=version_compare)
|
|
297
|
+
|
|
298
|
+
# info command
|
|
299
|
+
info_parser = subparsers.add_parser("info", help="Show version information")
|
|
300
|
+
info_parser.set_defaults(func=version_info)
|
|
301
|
+
|
|
302
|
+
args = parser.parse_args()
|
|
303
|
+
|
|
304
|
+
if not args.command:
|
|
305
|
+
parser.print_help()
|
|
306
|
+
return 1
|
|
307
|
+
|
|
308
|
+
# Call the appropriate function
|
|
309
|
+
return args.func(args)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
sys.exit(main())
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hatch-calvar-sample
|
|
3
|
+
Version: 2026.1.19.1
|
|
4
|
+
Summary: Sample project demonstrating CalVer (YYYY.MM.DD.MICRO) with hatch
|
|
5
|
+
Project-URL: Documentation, https://github.com/QAToolist/hatch-calvar-sample#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/QAToolist/hatch-calvar-sample/issues
|
|
7
|
+
Project-URL: Source, https://github.com/QAToolist/hatch-calvar-sample
|
|
8
|
+
Author-email: QAToolist <qatoolist@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: black>=24.1.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: isort>=5.13.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
30
|
+
Provides-Extra: lint
|
|
31
|
+
Requires-Dist: bandit>=1.7.0; extra == 'lint'
|
|
32
|
+
Requires-Dist: black>=24.1.0; extra == 'lint'
|
|
33
|
+
Requires-Dist: isort>=5.13.0; extra == 'lint'
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == 'lint'
|
|
35
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'lint'
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == 'lint'
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# hatch-calvar-sample
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/hatch-calvar-sample)
|
|
45
|
+
[](https://pypi.org/project/hatch-calvar-sample)
|
|
46
|
+
|
|
47
|
+
Sample hatch-based Python project demonstrating **Calendar Versioning (CalVer)** with format `YYYY.MM.DD.MICRO` and automated PyPI release workflows.
|
|
48
|
+
|
|
49
|
+
## Overview
|
|
50
|
+
|
|
51
|
+
This project serves as a proof-of-concept for implementing CalVer versioning with the hatch build system. It demonstrates:
|
|
52
|
+
|
|
53
|
+
- **Calendar Versioning (YYYY.MM.DD.MICRO)** calculated from git tags
|
|
54
|
+
- **Dynamic versioning** with hatch build system
|
|
55
|
+
- **Version checking CLI tool** with multiple commands
|
|
56
|
+
- **Automated PyPI release** via GitHub Actions
|
|
57
|
+
- **Complete release workflow** automation
|
|
58
|
+
|
|
59
|
+
### Version Format
|
|
60
|
+
|
|
61
|
+
The project uses CalVer format: `YYYY.MM.DD.MICRO`
|
|
62
|
+
|
|
63
|
+
- `YYYY` - 4-digit year (e.g., 2024)
|
|
64
|
+
- `MM` - 2-digit month (01-12)
|
|
65
|
+
- `DD` - 2-digit day (01-31)
|
|
66
|
+
- `MICRO` - Sequential number for releases on the same day (1, 2, 3, ...)
|
|
67
|
+
|
|
68
|
+
Examples: `2024.01.18.1`, `2024.01.18.2`, `2024.03.15.1`
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```console
|
|
73
|
+
pip install hatch-calvar-sample
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
### Version Calculation
|
|
79
|
+
|
|
80
|
+
The project includes a script that automatically calculates the next CalVer version based on:
|
|
81
|
+
- Current UTC date
|
|
82
|
+
- Existing git tags matching the CalVer pattern
|
|
83
|
+
- Automatic MICRO increment for same-day releases
|
|
84
|
+
|
|
85
|
+
### CLI Tool
|
|
86
|
+
|
|
87
|
+
The `calver-check` CLI provides multiple commands for version management:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Calculate next version
|
|
91
|
+
calver-check calc
|
|
92
|
+
|
|
93
|
+
# Check current version from different sources
|
|
94
|
+
calver-check check
|
|
95
|
+
|
|
96
|
+
# Validate version format
|
|
97
|
+
calver-check validate 2024.01.18.1
|
|
98
|
+
|
|
99
|
+
# Compare two versions
|
|
100
|
+
calver-check compare 2024.01.18.1 2024.01.18.2
|
|
101
|
+
|
|
102
|
+
# Show version information
|
|
103
|
+
calver-check info
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All commands support `--json` flag for machine-readable output:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
calver-check calc --json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Usage Examples
|
|
113
|
+
|
|
114
|
+
### Calculating Next Version
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Using the script directly
|
|
118
|
+
python scripts/calc_version.py
|
|
119
|
+
|
|
120
|
+
# Using the CLI tool
|
|
121
|
+
calver-check calc
|
|
122
|
+
|
|
123
|
+
# With validation
|
|
124
|
+
python scripts/calc_version.py --validate --pep440
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Checking Current Version
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Check version from package metadata
|
|
131
|
+
calver-check check
|
|
132
|
+
|
|
133
|
+
# Check with JSON output
|
|
134
|
+
calver-check check --json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Validating Version Format
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Validate a version string
|
|
141
|
+
calver-check validate 2024.01.18.1
|
|
142
|
+
|
|
143
|
+
# Invalid version will exit with error
|
|
144
|
+
calver-check validate 2024.1.18.1
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Comparing Versions
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Compare two versions
|
|
151
|
+
calver-check compare 2024.01.18.1 2024.01.18.2
|
|
152
|
+
# Output: 2024.01.18.1 < 2024.01.18.2
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Release Process
|
|
156
|
+
|
|
157
|
+
### Fully Automated Release Workflow
|
|
158
|
+
|
|
159
|
+
The project uses GitHub Actions for **fully automated** PyPI releases. **No manual tagging required!** The workflow consists of two stages:
|
|
160
|
+
|
|
161
|
+
#### Stage 1: Auto-tag on PR Merge
|
|
162
|
+
|
|
163
|
+
When a pull request is merged to `main` or `master`:
|
|
164
|
+
|
|
165
|
+
1. Automatically calculates next CalVer version using `scripts/calc_version.py`
|
|
166
|
+
2. Creates a git tag with format `vYYYY.MM.DD.MICRO`
|
|
167
|
+
3. Pushes the tag to the repository
|
|
168
|
+
|
|
169
|
+
#### Stage 2: Build and Publish
|
|
170
|
+
|
|
171
|
+
When a git tag matching `v*` is pushed:
|
|
172
|
+
|
|
173
|
+
1. Extracts version from tag (strips `v` prefix)
|
|
174
|
+
2. Validates CalVer format
|
|
175
|
+
3. Builds package with hatch
|
|
176
|
+
4. Validates distributions with twine
|
|
177
|
+
5. Publishes to PyPI using Trusted Publishing
|
|
178
|
+
|
|
179
|
+
### Automated Release Steps
|
|
180
|
+
|
|
181
|
+
1. **Open a pull request** with your changes
|
|
182
|
+
2. **Merge the pull request** to `main`/`master`
|
|
183
|
+
3. **GitHub Actions automatically:**
|
|
184
|
+
- Calculates next CalVer version (e.g., `2024.01.18.1`)
|
|
185
|
+
- Creates tag `v2024.01.18.1`
|
|
186
|
+
- Builds the package
|
|
187
|
+
- Validates version format
|
|
188
|
+
- Publishes to PyPI
|
|
189
|
+
|
|
190
|
+
No manual tagging required! The release happens automatically when PRs are merged.
|
|
191
|
+
|
|
192
|
+
### Manual Release (Optional)
|
|
193
|
+
|
|
194
|
+
If you need to manually create a release tag (for hotfixes, etc.):
|
|
195
|
+
|
|
196
|
+
1. **Calculate next version:**
|
|
197
|
+
```bash
|
|
198
|
+
calver-check calc
|
|
199
|
+
# Output: 2024.01.18.1
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
2. **Create and push git tag:**
|
|
203
|
+
```bash
|
|
204
|
+
git tag v2024.01.18.1 -m "Release 2024.01.18.1"
|
|
205
|
+
git push origin v2024.01.18.1
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The tag push will trigger the same build and publish workflow.
|
|
209
|
+
|
|
210
|
+
### Using Makefile
|
|
211
|
+
|
|
212
|
+
For convenience, use the Makefile targets:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Calculate next version
|
|
216
|
+
make version-calc
|
|
217
|
+
|
|
218
|
+
# Check current version
|
|
219
|
+
make version-check
|
|
220
|
+
|
|
221
|
+
# Validate version format
|
|
222
|
+
make version-validate VERSION=2024.01.18.1
|
|
223
|
+
|
|
224
|
+
# Create and push release tag
|
|
225
|
+
make release-tag
|
|
226
|
+
|
|
227
|
+
# Build package for testing
|
|
228
|
+
make build-test
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Project Structure
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
hatch-calvar-sample/
|
|
235
|
+
├── pyproject.toml # Hatch configuration with dynamic versioning
|
|
236
|
+
├── README.md # This file
|
|
237
|
+
├── CHANGELOG.md # Changelog with CalVer format
|
|
238
|
+
├── LICENSE.txt # MIT license
|
|
239
|
+
├── Makefile # Convenient make targets
|
|
240
|
+
├── .github/
|
|
241
|
+
│ └── workflows/
|
|
242
|
+
│ ├── auto-tag.yml # Auto-create tag on PR merge
|
|
243
|
+
│ └── release.yml # Automated PyPI release workflow
|
|
244
|
+
├── scripts/
|
|
245
|
+
│ └── calc_version.py # Version calculation script
|
|
246
|
+
├── src/
|
|
247
|
+
│ └── hatch_calvar_sample/
|
|
248
|
+
│ ├── __init__.py # Package with __version__
|
|
249
|
+
│ ├── __about__.py # Version metadata
|
|
250
|
+
│ ├── VERSION # Version file (generated during build)
|
|
251
|
+
│ └── cli.py # Version checking CLI implementation
|
|
252
|
+
└── tests/
|
|
253
|
+
├── test_version_calc.py # Tests for version calculation
|
|
254
|
+
└── test_version_cli.py # Tests for CLI tool
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Configuration
|
|
258
|
+
|
|
259
|
+
### Dynamic Versioning
|
|
260
|
+
|
|
261
|
+
The project uses hatch's dynamic versioning feature configured in `pyproject.toml`:
|
|
262
|
+
|
|
263
|
+
```toml
|
|
264
|
+
[tool.hatch.version]
|
|
265
|
+
path = "src/hatch_calvar_sample/VERSION"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The VERSION file is created during the release workflow with the version extracted from the git tag.
|
|
269
|
+
|
|
270
|
+
### Version Metadata in Code
|
|
271
|
+
|
|
272
|
+
The package reads version from `importlib.metadata` when installed, with fallbacks:
|
|
273
|
+
|
|
274
|
+
1. Package metadata (when installed)
|
|
275
|
+
2. VERSION file (for development builds)
|
|
276
|
+
3. Environment variable `HATCH_CALVER_VERSION`
|
|
277
|
+
|
|
278
|
+
Access version programmatically:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from hatch_calvar_sample import __version__
|
|
282
|
+
|
|
283
|
+
print(__version__) # Output: 2024.01.18.1
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
### Setting Up Development Environment
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Clone the repository
|
|
292
|
+
git clone https://github.com/QAToolist/hatch-calvar-sample.git
|
|
293
|
+
cd hatch-calvar-sample
|
|
294
|
+
|
|
295
|
+
# Install in development mode
|
|
296
|
+
pip install -e .
|
|
297
|
+
|
|
298
|
+
# Install development dependencies
|
|
299
|
+
pip install pytest
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Running Tests
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# Run all tests
|
|
306
|
+
pytest
|
|
307
|
+
|
|
308
|
+
# Run specific test file
|
|
309
|
+
pytest tests/test_version_calc.py
|
|
310
|
+
|
|
311
|
+
# Run with coverage
|
|
312
|
+
pytest --cov=hatch_calvar_sample --cov=scripts
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Local Testing
|
|
316
|
+
|
|
317
|
+
1. **Test version calculation:**
|
|
318
|
+
```bash
|
|
319
|
+
python scripts/calc_version.py
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
2. **Test build process:**
|
|
323
|
+
```bash
|
|
324
|
+
# Create VERSION file manually
|
|
325
|
+
echo "2024.01.18.1" > src/hatch_calvar_sample/VERSION
|
|
326
|
+
|
|
327
|
+
# Build package
|
|
328
|
+
hatch build
|
|
329
|
+
|
|
330
|
+
# Check built package
|
|
331
|
+
twine check dist/*
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
3. **Test installation:**
|
|
335
|
+
```bash
|
|
336
|
+
pip install -e .
|
|
337
|
+
python -c "import hatch_calvar_sample; print(hatch_calvar_sample.__version__)"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Version Calculation Logic
|
|
341
|
+
|
|
342
|
+
The version calculation script:
|
|
343
|
+
|
|
344
|
+
1. Gets current UTC date → `YYYY.MM.DD`
|
|
345
|
+
2. Fetches all git tags (`git fetch --tags`)
|
|
346
|
+
3. Parses tags matching CalVer pattern (`YYYY.MM.DD.MICRO` or `vYYYY.MM.DD.MICRO`)
|
|
347
|
+
4. Filters tags with the same date
|
|
348
|
+
5. Extracts MICRO numbers
|
|
349
|
+
6. Calculates next MICRO = `max(existing) + 1` or `1` if none exist
|
|
350
|
+
7. Returns: `YYYY.MM.DD.MICRO`
|
|
351
|
+
|
|
352
|
+
### Edge Cases Handled
|
|
353
|
+
|
|
354
|
+
- No tags → Returns `YYYY.MM.DD.1`
|
|
355
|
+
- Multiple tags same date → Increments MICRO correctly
|
|
356
|
+
- Date boundary crossing → Resets MICRO to 1 for new date
|
|
357
|
+
- Invalid tag formats → Skipped gracefully
|
|
358
|
+
- Timezone handling → Uses UTC for consistency
|
|
359
|
+
|
|
360
|
+
## PEP 440 Compliance
|
|
361
|
+
|
|
362
|
+
CalVer format `YYYY.MM.DD.MICRO` is PEP 440 compliant as a release segment. The format:
|
|
363
|
+
|
|
364
|
+
- Uses numeric components
|
|
365
|
+
- Follows semantic ordering (newer dates > older dates)
|
|
366
|
+
- Valid for PyPI distribution
|
|
367
|
+
|
|
368
|
+
Validate PEP 440 compliance:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
calver-check validate 2024.01.18.1
|
|
372
|
+
python scripts/calc_version.py --pep440
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Troubleshooting
|
|
376
|
+
|
|
377
|
+
### Version Not Found
|
|
378
|
+
|
|
379
|
+
If `__version__` is not available:
|
|
380
|
+
|
|
381
|
+
1. Ensure package is installed: `pip install -e .`
|
|
382
|
+
2. Check VERSION file exists: `ls src/hatch_calvar_sample/VERSION`
|
|
383
|
+
3. Verify git tags: `git tag`
|
|
384
|
+
|
|
385
|
+
### Build Errors
|
|
386
|
+
|
|
387
|
+
If build fails:
|
|
388
|
+
|
|
389
|
+
1. Verify VERSION file exists with valid format
|
|
390
|
+
2. Check `pyproject.toml` dynamic version configuration
|
|
391
|
+
3. Ensure hatch is installed: `pip install hatchling`
|
|
392
|
+
|
|
393
|
+
### CLI Not Found
|
|
394
|
+
|
|
395
|
+
If `calver-check` command is not available:
|
|
396
|
+
|
|
397
|
+
1. Reinstall package: `pip install -e .`
|
|
398
|
+
2. Check entry point in `pyproject.toml`
|
|
399
|
+
3. Verify PATH includes Python scripts directory
|
|
400
|
+
|
|
401
|
+
## Contributing
|
|
402
|
+
|
|
403
|
+
This is a sample project for demonstration purposes. If you find it useful or want to adapt it for your project:
|
|
404
|
+
|
|
405
|
+
1. Review the implementation details in scripts and source code
|
|
406
|
+
2. Check the GitHub Actions workflow for CI/CD patterns
|
|
407
|
+
3. Adapt the configuration for your project structure
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
`hatch-calvar-sample` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
|
412
|
+
|
|
413
|
+
## References
|
|
414
|
+
|
|
415
|
+
- [Hatch Documentation](https://hatch.pypa.io/)
|
|
416
|
+
- [Calendar Versioning (CalVer)](https://calver.org/)
|
|
417
|
+
- [PEP 440 - Version Identification](https://peps.python.org/pep-0440/)
|
|
418
|
+
- [GitHub Actions](https://docs.github.com/en/actions)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
## Demo Change
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
hatch_calvar_sample/__about__.py,sha256=xlD9nPpC2pVTXj7SnqL2D4Ii8Hx5PdoszDaQ-scX_m8,1107
|
|
2
|
+
hatch_calvar_sample/__init__.py,sha256=Zo7rNhiPjw6aA00bIGyizV2ke8xv24cjV3C2K6MfPDI,265
|
|
3
|
+
hatch_calvar_sample/cli.py,sha256=J3wnxje3vnx1Ep2YlPyBlkTBo4eUmBb8zesCHbZFj_k,8394
|
|
4
|
+
hatch_calvar_sample-2026.1.19.1.dist-info/METADATA,sha256=STWKCFQb3Hn8T9vfDZym1L2vs7ZuGzGgXTgoNXmscE8,11396
|
|
5
|
+
hatch_calvar_sample-2026.1.19.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
hatch_calvar_sample-2026.1.19.1.dist-info/entry_points.txt,sha256=vXoWTt7vuY493G-1mbwHnSSVt_ixdRvCZl2pHxszDjE,62
|
|
7
|
+
hatch_calvar_sample-2026.1.19.1.dist-info/licenses/LICENSE.txt,sha256=mTqa2fQvFlr6XRReIRjiWIxWJ43FPXEV2mhd8o_HL4k,1096
|
|
8
|
+
hatch_calvar_sample-2026.1.19.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present QAToolist <qatoolist@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|