common-python-tasks 0.3.0__py3-none-any.whl → 0.3.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.
- common_python_tasks/data/generic/cliff.toml +5 -0
- common_python_tasks/git.py +7 -23
- common_python_tasks/tasks.py +14 -4
- common_python_tasks/utils.py +70 -23
- {common_python_tasks-0.3.0.dist-info → common_python_tasks-0.3.1.dist-info}/METADATA +9 -9
- {common_python_tasks-0.3.0.dist-info → common_python_tasks-0.3.1.dist-info}/RECORD +8 -7
- {common_python_tasks-0.3.0.dist-info → common_python_tasks-0.3.1.dist-info}/WHEEL +1 -1
- {common_python_tasks-0.3.0.dist-info → common_python_tasks-0.3.1.dist-info}/licenses/LICENSE +0 -0
common_python_tasks/git.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
import shutil
|
|
4
|
-
from enum import
|
|
4
|
+
from enum import StrEnum, auto
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, NamedTuple
|
|
7
7
|
|
|
@@ -27,14 +27,6 @@ class ReleaseComponent(StrEnum):
|
|
|
27
27
|
PATCH = auto()
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class ReleaseComponentBump(IntEnum):
|
|
31
|
-
"""Numeric bump positions expected by `Version.bump`."""
|
|
32
|
-
|
|
33
|
-
MAJOR = 0
|
|
34
|
-
MINOR = 1
|
|
35
|
-
PATCH = 2
|
|
36
|
-
|
|
37
|
-
|
|
38
30
|
class ReleaseStage(StrEnum):
|
|
39
31
|
"""Supported pre-release stages."""
|
|
40
32
|
|
|
@@ -105,18 +97,12 @@ def compute_next_release_version(
|
|
|
105
97
|
A `NextReleaseVersion` with the serialized new version and normalized component.
|
|
106
98
|
"""
|
|
107
99
|
current_version_text = project.get_project_version_from_poetry()
|
|
108
|
-
release_component =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if re.match(r"^0\.", str(Version.parse(current_version_text).base)):
|
|
112
|
-
LOGGER.info(
|
|
113
|
-
"Current version %s is pre-production; using patch bump.",
|
|
114
|
-
current_version_text,
|
|
115
|
-
)
|
|
116
|
-
release_component = ReleaseComponent.PATCH
|
|
100
|
+
release_component = (
|
|
101
|
+
infer_bump_component_from_git_cliff() if component is None else component
|
|
102
|
+
)
|
|
117
103
|
|
|
118
104
|
bumped = Version.parse(Version.parse(current_version_text).base).bump(
|
|
119
|
-
|
|
105
|
+
list(ReleaseComponent).index(release_component)
|
|
120
106
|
)
|
|
121
107
|
if stage is not None:
|
|
122
108
|
bumped.stage = stage.value
|
|
@@ -179,10 +165,8 @@ def infer_bump_component_from_git_cliff() -> ReleaseComponent:
|
|
|
179
165
|
"Install git-cliff or pass one of: major, minor, patch."
|
|
180
166
|
)
|
|
181
167
|
|
|
182
|
-
result = utils.
|
|
183
|
-
["
|
|
184
|
-
capture_output=True,
|
|
185
|
-
acceptable_returncodes={0},
|
|
168
|
+
result = utils.run_git_cliff(
|
|
169
|
+
["--bumped-version", "--unreleased"], capture_output=True
|
|
186
170
|
)
|
|
187
171
|
version_text = result.stdout.strip()
|
|
188
172
|
if not version_text:
|
common_python_tasks/tasks.py
CHANGED
|
@@ -166,10 +166,20 @@ def _resolve_allow_dirty_release(dirty_files: list[Path]) -> bool:
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
def _update_release_changelog(release_tag: str, dry_run: bool = False) -> None:
|
|
169
|
-
from .utils import
|
|
169
|
+
from .utils import (
|
|
170
|
+
get_prepended_changelog_contents,
|
|
171
|
+
log_dry_run,
|
|
172
|
+
prepend_changelog,
|
|
173
|
+
run_command,
|
|
174
|
+
)
|
|
170
175
|
|
|
171
176
|
changelog_path = Path("CHANGELOG.md")
|
|
172
177
|
if dry_run:
|
|
178
|
+
LOGGER.debug(
|
|
179
|
+
"Dry-run rendered %s contents:\n%s",
|
|
180
|
+
changelog_path,
|
|
181
|
+
get_prepended_changelog_contents(release_tag, changelog_path),
|
|
182
|
+
)
|
|
173
183
|
log_dry_run(
|
|
174
184
|
"Would prepend generated git-cliff release notes for %s to %s",
|
|
175
185
|
release_tag,
|
|
@@ -985,7 +995,7 @@ def changelog() -> None:
|
|
|
985
995
|
|
|
986
996
|
|
|
987
997
|
def _run_release_flow(
|
|
988
|
-
component: str = "
|
|
998
|
+
component: str = "auto",
|
|
989
999
|
*,
|
|
990
1000
|
stage: str | None = None,
|
|
991
1001
|
dry_run: bool = False,
|
|
@@ -1105,7 +1115,7 @@ def _run_release_flow(
|
|
|
1105
1115
|
|
|
1106
1116
|
@tasks.script(task_name="release", tags=["packaging", "release", "containers"])
|
|
1107
1117
|
def release(
|
|
1108
|
-
component: str = "
|
|
1118
|
+
component: str = "auto",
|
|
1109
1119
|
*,
|
|
1110
1120
|
stage: str | None = None,
|
|
1111
1121
|
dry_run: bool = False,
|
|
@@ -1159,7 +1169,7 @@ def release(
|
|
|
1159
1169
|
|
|
1160
1170
|
@tasks.script(task_name="release", tags=["packaging", "release", "common"])
|
|
1161
1171
|
def release_without_containers(
|
|
1162
|
-
component: str = "
|
|
1172
|
+
component: str = "auto",
|
|
1163
1173
|
*,
|
|
1164
1174
|
stage: str | None = None,
|
|
1165
1175
|
dry_run: bool = False,
|
common_python_tasks/utils.py
CHANGED
|
@@ -10,7 +10,7 @@ from getpass import getuser
|
|
|
10
10
|
from importlib.resources import files
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from shlex import quote
|
|
13
|
-
from typing import Callable, Sequence
|
|
13
|
+
from typing import Callable, Collection, Sequence
|
|
14
14
|
|
|
15
15
|
from jinja2 import Template
|
|
16
16
|
|
|
@@ -120,7 +120,7 @@ def remove_path(path: Path) -> None:
|
|
|
120
120
|
def run_command(
|
|
121
121
|
command: Sequence[str | Path | None],
|
|
122
122
|
capture_output: bool = False,
|
|
123
|
-
acceptable_returncodes:
|
|
123
|
+
acceptable_returncodes: Collection[int] | None = None,
|
|
124
124
|
env: dict[str, str] | None = None,
|
|
125
125
|
always_show_command: bool = False,
|
|
126
126
|
dry_run: bool = False,
|
|
@@ -143,10 +143,9 @@ def run_command(
|
|
|
143
143
|
The `CompletedProcess` instance from the subprocess run, or `None` for dry-run
|
|
144
144
|
invocations.
|
|
145
145
|
"""
|
|
146
|
-
import subprocess
|
|
147
|
-
|
|
148
146
|
if acceptable_returncodes is None:
|
|
149
147
|
acceptable_returncodes = {0}
|
|
148
|
+
acceptable_returncodes = set(acceptable_returncodes)
|
|
150
149
|
|
|
151
150
|
command = [str(c) for c in command if c is not None]
|
|
152
151
|
|
|
@@ -208,7 +207,18 @@ def load_data_file(
|
|
|
208
207
|
try:
|
|
209
208
|
data_files = files("common_python_tasks") / "data" / type_identifier
|
|
210
209
|
data_file = data_files / file_name
|
|
211
|
-
|
|
210
|
+
text = data_file.read_text()
|
|
211
|
+
# Try to provide a real filesystem Path when possible (packages in wheels
|
|
212
|
+
# may store resources inside archives). `as_file()` yields a real path
|
|
213
|
+
# via a context manager when supported by the Traversable.
|
|
214
|
+
try:
|
|
215
|
+
with data_file.as_file() as real_path:
|
|
216
|
+
return (Path(real_path), text)
|
|
217
|
+
except (AttributeError, OSError):
|
|
218
|
+
# Fall back to a Path constructed from the Traversable string
|
|
219
|
+
# representation so existing callers that index [0] still receive
|
|
220
|
+
# a Path-like object.
|
|
221
|
+
return (Path(str(data_file)), text)
|
|
212
222
|
except FileNotFoundError as e:
|
|
213
223
|
if fatal_on_missing:
|
|
214
224
|
fatal(f"Data file not found: {file_name} ({e})")
|
|
@@ -239,7 +249,7 @@ def get_container_registry_url() -> str:
|
|
|
239
249
|
|
|
240
250
|
|
|
241
251
|
@lru_cache
|
|
242
|
-
def get_package_name(use_underscores: bool = False) -> str:
|
|
252
|
+
def get_package_name(use_underscores: bool = False) -> str | None:
|
|
243
253
|
"""Return the package name from environment or pyproject configuration.
|
|
244
254
|
|
|
245
255
|
Args:
|
|
@@ -250,9 +260,17 @@ def get_package_name(use_underscores: bool = False) -> str:
|
|
|
250
260
|
"""
|
|
251
261
|
import tomllib
|
|
252
262
|
|
|
253
|
-
name = os.getenv("PACKAGE_NAME")
|
|
254
|
-
|
|
255
|
-
|
|
263
|
+
name = os.getenv("PACKAGE_NAME")
|
|
264
|
+
if not name:
|
|
265
|
+
try:
|
|
266
|
+
pyproject = Path("pyproject.toml")
|
|
267
|
+
if pyproject.exists():
|
|
268
|
+
name = (
|
|
269
|
+
tomllib.loads(pyproject.read_text()).get("project", {}).get("name")
|
|
270
|
+
)
|
|
271
|
+
except Exception as e: # pragma: no cover - defensive logging
|
|
272
|
+
LOGGER.debug("Unable to read pyproject.toml: %s", e)
|
|
273
|
+
|
|
256
274
|
if use_underscores and name:
|
|
257
275
|
name = package_name_to_underscore(name)
|
|
258
276
|
return name
|
|
@@ -340,13 +358,47 @@ def run_git_cliff(
|
|
|
340
358
|
Returns:
|
|
341
359
|
The `CompletedProcess` result, or `None` for dry-run invocations.
|
|
342
360
|
"""
|
|
361
|
+
config_path = get_config_path(
|
|
362
|
+
"GIT_CLIFF_CONFIG",
|
|
363
|
+
"cliff.toml",
|
|
364
|
+
"cliff.toml",
|
|
365
|
+
)
|
|
343
366
|
return run_command(
|
|
344
|
-
["git-cliff", *args],
|
|
367
|
+
["git-cliff", "--config", config_path if config_path else None, *args],
|
|
345
368
|
capture_output=capture_output,
|
|
346
369
|
acceptable_returncodes={0},
|
|
347
370
|
)
|
|
348
371
|
|
|
349
372
|
|
|
373
|
+
def get_prepended_changelog_contents(
|
|
374
|
+
tag_name: str,
|
|
375
|
+
changelog_path: Path = Path("CHANGELOG.md"),
|
|
376
|
+
) -> str:
|
|
377
|
+
"""Return changelog content with release notes prepended for the given tag.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
tag_name: The release tag to generate changelog entries for.
|
|
381
|
+
changelog_path: The changelog file to read as the base content.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
The full changelog text as it would look after prepending release notes.
|
|
385
|
+
"""
|
|
386
|
+
existing_changelog = (
|
|
387
|
+
changelog_path.read_text(encoding="utf-8")
|
|
388
|
+
if changelog_path.exists()
|
|
389
|
+
else "# Changelog\n\n## [Unreleased]\n"
|
|
390
|
+
)
|
|
391
|
+
res = run_git_cliff(["--unreleased", "--tag", tag_name], capture_output=True)
|
|
392
|
+
release_notes = res.stdout if (res and res.stdout) else ""
|
|
393
|
+
if release_notes.strip():
|
|
394
|
+
# Ensure the prepended section is separated from existing content.
|
|
395
|
+
release_notes = release_notes.rstrip("\n") + "\n\n"
|
|
396
|
+
else:
|
|
397
|
+
release_notes = ""
|
|
398
|
+
|
|
399
|
+
return release_notes + existing_changelog
|
|
400
|
+
|
|
401
|
+
|
|
350
402
|
def prepend_changelog(
|
|
351
403
|
tag_name: str,
|
|
352
404
|
changelog_path: Path = Path("CHANGELOG.md"),
|
|
@@ -360,25 +412,20 @@ def prepend_changelog(
|
|
|
360
412
|
tag_name: The release tag to generate changelog entries for.
|
|
361
413
|
changelog_path: The path to the changelog file to prepend to.
|
|
362
414
|
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
).stdout
|
|
368
|
-
if release_notes and not release_notes.endswith("\n"):
|
|
369
|
-
release_notes += "\n"
|
|
370
|
-
|
|
371
|
-
existing_changelog = changelog_path.read_text(encoding="utf-8")
|
|
372
|
-
changelog_path.write_text(release_notes + existing_changelog, encoding="utf-8")
|
|
415
|
+
changelog_path.write_text(
|
|
416
|
+
get_prepended_changelog_contents(tag_name, changelog_path),
|
|
417
|
+
encoding="utf-8",
|
|
418
|
+
)
|
|
373
419
|
|
|
374
420
|
|
|
375
421
|
def changelog() -> str | None:
|
|
376
422
|
"""Generate release notes from git-cliff for unreleased commits."""
|
|
377
|
-
|
|
378
|
-
if not
|
|
423
|
+
res = run_git_cliff(["--unreleased"], capture_output=True)
|
|
424
|
+
if not res or not res.stdout:
|
|
379
425
|
LOGGER.warning("git-cliff produced no release notes for unreleased commits")
|
|
380
|
-
return
|
|
426
|
+
return None
|
|
381
427
|
|
|
428
|
+
result = res.stdout
|
|
382
429
|
return re.sub(
|
|
383
430
|
r"(?im)\A##\s*\[?unreleased\]?\s*\n?",
|
|
384
431
|
"",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: common-python-tasks
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Opinionated Poe the Poet tasks for Python package development.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -50,7 +50,7 @@ curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents
|
|
|
50
50
|
To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
|
|
51
51
|
|
|
52
52
|
```shell
|
|
53
|
-
COMMON_PYTHON_TASKS_VERSION=0.3.
|
|
53
|
+
COMMON_PYTHON_TASKS_VERSION=0.3.1 \
|
|
54
54
|
sh -c "$(curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add-common-python-tasks.sh | jq -r '.content' | base64 -d)"
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -71,9 +71,9 @@ There's no real reason to run the automated script; I just like automating every
|
|
|
71
71
|
```toml
|
|
72
72
|
[project]
|
|
73
73
|
name = "my-awesome-project"
|
|
74
|
-
version = "0.3.
|
|
74
|
+
version = "0.3.1"
|
|
75
75
|
dependencies = [
|
|
76
|
-
"common-python-tasks==0.3.
|
|
76
|
+
"common-python-tasks==0.3.1", # Always pin to a specific version
|
|
77
77
|
]
|
|
78
78
|
|
|
79
79
|
[tool.poe]
|
|
@@ -287,8 +287,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
|
|
|
287
287
|
```toml
|
|
288
288
|
[project]
|
|
289
289
|
name = "simple-cli-tool"
|
|
290
|
-
version = "0.3.
|
|
291
|
-
dependencies = ["common-python-tasks==0.3.
|
|
290
|
+
version = "0.3.1"
|
|
291
|
+
dependencies = ["common-python-tasks==0.3.1"]
|
|
292
292
|
|
|
293
293
|
[tool.poe]
|
|
294
294
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -301,8 +301,8 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
|
|
|
301
301
|
```toml
|
|
302
302
|
[project]
|
|
303
303
|
name = "containerized-app"
|
|
304
|
-
version = "0.3.
|
|
305
|
-
dependencies = ["common-python-tasks==0.3.
|
|
304
|
+
version = "0.3.1"
|
|
305
|
+
dependencies = ["common-python-tasks==0.3.1"]
|
|
306
306
|
|
|
307
307
|
[tool.poe]
|
|
308
308
|
include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
|
|
@@ -319,7 +319,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
|
|
|
319
319
|
```toml
|
|
320
320
|
[project]
|
|
321
321
|
name = "custom-test-setup"
|
|
322
|
-
dependencies = ["common-python-tasks==0.3.
|
|
322
|
+
dependencies = ["common-python-tasks==0.3.1"]
|
|
323
323
|
dynamic = ["version"]
|
|
324
324
|
|
|
325
325
|
[tool.poe]
|
|
@@ -12,16 +12,17 @@ common_python_tasks/data/generic/.flake8,sha256=PzH98ak-AKcENaBCzOhI2eHvLxrneklm
|
|
|
12
12
|
common_python_tasks/data/generic/.isort.cfg,sha256=P_hdco_n95VeXJjlUcXkChqNdct0SNDuk_n9ZaHzXPE,71
|
|
13
13
|
common_python_tasks/data/generic/Dockerfile.deps.j2,sha256=rCGsBwLHQIRsGFzDbiRi_nWc-SZ3MAFZ_BeZJFV28Ck,432
|
|
14
14
|
common_python_tasks/data/generic/Dockerfile.j2,sha256=QBWryTMqEPLpPehUyQHAiLCbKFlrC9VjkE4Sv56t1bk,5467
|
|
15
|
+
common_python_tasks/data/generic/cliff.toml,sha256=po1MnEGEEgrpyBz3XLWr0h7emXb6ASX63lrU2r6o240,77
|
|
15
16
|
common_python_tasks/data/generic/pytest.ini,sha256=RHak5QrLzKHvUBjFmaaiOYFrd_8YBRQubXEOT0NJfco,134
|
|
16
17
|
common_python_tasks/docker.py,sha256=lS22ecY1rVCkYGPc7hDgXNM-e-TjIW3ce9Um9FOiW7A,17515
|
|
17
18
|
common_python_tasks/docker_compose.py,sha256=DN4cap0lGxBSCYwEXXSytbq4V5NPVv35e8KyCPTsMhM,20828
|
|
18
19
|
common_python_tasks/env.py,sha256=G4CF2c3vpUe6qs_WNW41b4IqUnAMBwAuTnUyd8PwExk,13671
|
|
19
|
-
common_python_tasks/git.py,sha256=
|
|
20
|
+
common_python_tasks/git.py,sha256=PCkx6cvWRqarEhDV3aJCPUrjcVxxaZq03ZWY8JqD9Yo,10642
|
|
20
21
|
common_python_tasks/github.py,sha256=32u4LshDOJpyah8I00c1IKeopNJCWAM8M8Ci2SOdxhQ,18983
|
|
21
22
|
common_python_tasks/project.py,sha256=SIZRaj0wvPXQbfJOa9jCJJ6F6M_p2QeYjZUMVRsCPjI,10062
|
|
22
|
-
common_python_tasks/tasks.py,sha256=
|
|
23
|
-
common_python_tasks/utils.py,sha256=
|
|
24
|
-
common_python_tasks-0.3.
|
|
25
|
-
common_python_tasks-0.3.
|
|
26
|
-
common_python_tasks-0.3.
|
|
27
|
-
common_python_tasks-0.3.
|
|
23
|
+
common_python_tasks/tasks.py,sha256=nIIf4HaQoAUSy89ybg1zMNZwxKL1xIYwiCXHnY52uJo,50140
|
|
24
|
+
common_python_tasks/utils.py,sha256=RTVMeNyoIVvnJYsg92yXOVelEXlsLumO9Bgdqyx6BHw,14001
|
|
25
|
+
common_python_tasks-0.3.1.dist-info/METADATA,sha256=VvvfJdJJYUowwh8jm1t704ThsjIdk_Hm_6Z-9xCEUQc,21396
|
|
26
|
+
common_python_tasks-0.3.1.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
27
|
+
common_python_tasks-0.3.1.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
|
|
28
|
+
common_python_tasks-0.3.1.dist-info/RECORD,,
|
{common_python_tasks-0.3.0.dist-info → common_python_tasks-0.3.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|