common-python-tasks 0.8.0__py3-none-any.whl → 0.9.0__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/fastapi/compose-base.yml.j2 +1 -2
- common_python_tasks/data/fastapi/compose-db.yml.j2 +1 -2
- common_python_tasks/data/fastapi/compose-debug.yml.j2 +1 -1
- common_python_tasks/data/generic/.dockerignore +1 -1
- common_python_tasks/data/generic/Dockerfile.j2 +1 -34
- common_python_tasks/data/generic/ruff.toml +1 -1
- common_python_tasks/docker.py +1 -21
- common_python_tasks/docker_compose.py +4 -7
- common_python_tasks/github.py +20 -1
- common_python_tasks/project.py +124 -295
- common_python_tasks/tasks.py +7 -14
- {common_python_tasks-0.8.0.dist-info → common_python_tasks-0.9.0.dist-info}/METADATA +33 -25
- common_python_tasks-0.9.0.dist-info/RECORD +27 -0
- common_python_tasks-0.8.0.dist-info/RECORD +0 -27
- {common_python_tasks-0.8.0.dist-info → common_python_tasks-0.9.0.dist-info}/WHEEL +0 -0
- {common_python_tasks-0.8.0.dist-info → common_python_tasks-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,8 +9,7 @@ x-common-environment: &common-environment
|
|
|
9
9
|
x-common-build-args: &common-build-args
|
|
10
10
|
PACKAGE_NAME: ${PACKAGE_NAME}
|
|
11
11
|
PYTHON_VERSION: ${PYTHON_VERSION}
|
|
12
|
-
|
|
13
|
-
PACKAGE_MANAGER_VERSION: ${PACKAGE_MANAGER_VERSION}
|
|
12
|
+
UV_VERSION: ${UV_VERSION}
|
|
14
13
|
|
|
15
14
|
services:
|
|
16
15
|
api:
|
|
@@ -8,8 +8,7 @@ x-common-environment: &common-environment
|
|
|
8
8
|
x-common-build-args: &common-build-args
|
|
9
9
|
PACKAGE_NAME: ${PACKAGE_NAME}
|
|
10
10
|
PYTHON_VERSION: ${PYTHON_VERSION}
|
|
11
|
-
|
|
12
|
-
PACKAGE_MANAGER_VERSION: ${PACKAGE_MANAGER_VERSION}
|
|
11
|
+
UV_VERSION: ${UV_VERSION}
|
|
13
12
|
WORKDIR_PATH: ${WORKDIR_PATH}
|
|
14
13
|
|
|
15
14
|
services:
|
|
@@ -7,33 +7,10 @@ ARG PYTHON_VERSION=3
|
|
|
7
7
|
# See https://hub.docker.com/_/python for available variants
|
|
8
8
|
ARG PYTHON_VARIANT=slim
|
|
9
9
|
ARG WORKDIR_PATH=/workspace
|
|
10
|
-
{% set package_manager = PACKAGE_MANAGER|default('poetry') %}
|
|
11
10
|
|
|
12
11
|
FROM python:${PYTHON_VERSION} AS builder
|
|
13
12
|
|
|
14
13
|
ARG PACKAGE_VERSION
|
|
15
|
-
|
|
16
|
-
{% if package_manager == 'poetry' %}
|
|
17
|
-
ARG POETRY_VERSION
|
|
18
|
-
ARG POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION
|
|
19
|
-
ARG POETRY_PLUGIN_EXPORT_VERSION
|
|
20
|
-
|
|
21
|
-
# Bypass VCS-based version detection: use the host-computed version directly.
|
|
22
|
-
# This avoids requiring a .git directory in the build context and prevents
|
|
23
|
-
# false dirty-version detection from a partial working tree.
|
|
24
|
-
ENV POETRY_DYNAMIC_VERSIONING_BYPASS=${PACKAGE_VERSION}
|
|
25
|
-
|
|
26
|
-
# Install Poetry and required Poetry plugins for package build
|
|
27
|
-
RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} \
|
|
28
|
-
sh -c 'set -eu; \
|
|
29
|
-
poetry_spec="poetry"; \
|
|
30
|
-
if [ -n "${POETRY_VERSION:-}" ]; then poetry_spec="poetry==$POETRY_VERSION"; fi; \
|
|
31
|
-
dynamic_spec="poetry-dynamic-versioning[plugin]"; \
|
|
32
|
-
if [ -n "${POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION:-}" ]; then dynamic_spec="poetry-dynamic-versioning[plugin]==$POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION"; fi; \
|
|
33
|
-
export_spec="poetry-plugin-export"; \
|
|
34
|
-
if [ -n "${POETRY_PLUGIN_EXPORT_VERSION:-}" ]; then export_spec="poetry-plugin-export==$POETRY_PLUGIN_EXPORT_VERSION"; fi; \
|
|
35
|
-
pip install --root-user-action=ignore "$poetry_spec" "$dynamic_spec" "$export_spec"'
|
|
36
|
-
{% else %}
|
|
37
14
|
ARG UV_VERSION
|
|
38
15
|
|
|
39
16
|
# Bypass VCS-based version detection: use the host-computed version directly.
|
|
@@ -48,7 +25,6 @@ RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }}
|
|
|
48
25
|
else \
|
|
49
26
|
pip install --root-user-action=ignore uv; \
|
|
50
27
|
fi'
|
|
51
|
-
{% endif %}
|
|
52
28
|
|
|
53
29
|
# Build package
|
|
54
30
|
WORKDIR /tmp/build
|
|
@@ -56,24 +32,15 @@ COPY . /tmp/build/
|
|
|
56
32
|
|
|
57
33
|
{% if HAS_DEBUG_DEPS %}
|
|
58
34
|
# Export debug requirements when the project defines a debug dependency group
|
|
59
|
-
{% if package_manager == 'poetry' %}
|
|
60
|
-
RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} \
|
|
61
|
-
poetry export --only debug --without-hashes -f requirements.txt --output requirements-debug.txt
|
|
62
|
-
{% else %}
|
|
63
35
|
RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache{{ CACHE_ID_SUFFIX }} \
|
|
64
36
|
uv export --group debug --no-hashes --format requirements-txt --output-file requirements-debug.txt
|
|
65
37
|
{% endif %}
|
|
66
|
-
{% endif %}
|
|
67
38
|
|
|
68
|
-
# Build the wheel, caching the
|
|
39
|
+
# Build the wheel, caching the uv cache directory to speed up subsequent builds.
|
|
69
40
|
# If running this in a CI system, you must be using a persistent builder to take
|
|
70
41
|
# advantage of the cache mount, and you should configure your CI to persist the
|
|
71
42
|
# cache directory between builds
|
|
72
|
-
{% if package_manager == 'poetry' %}
|
|
73
|
-
RUN --mount=type=cache,target=/root/.cache/pypoetry,id=pypoetry-cache{{ CACHE_ID_SUFFIX }} poetry build --format=wheel
|
|
74
|
-
{% else %}
|
|
75
43
|
RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache{{ CACHE_ID_SUFFIX }} uv build --wheel
|
|
76
|
-
{% endif %}
|
|
77
44
|
|
|
78
45
|
FROM python:${PYTHON_VERSION}-${PYTHON_VARIANT} AS runtime
|
|
79
46
|
ARG WORKDIR_PATH=/workspace
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
line-length = 88
|
|
2
|
-
extend-exclude = [".dist", "
|
|
2
|
+
extend-exclude = [".dist", "build"]
|
common_python_tasks/docker.py
CHANGED
|
@@ -289,37 +289,17 @@ def render_build_image(
|
|
|
289
289
|
commit_tag = version_tag
|
|
290
290
|
|
|
291
291
|
python_version = platform.python_version()
|
|
292
|
-
package_manager = project.get_package_manager()
|
|
293
|
-
package_manager_version = project.get_package_manager_version(package_manager)
|
|
294
|
-
|
|
295
|
-
manager_build_args: dict[str, str | None]
|
|
296
|
-
if package_manager == project.PackageManager.POETRY:
|
|
297
|
-
manager_build_args = {
|
|
298
|
-
"POETRY_VERSION": package_manager_version,
|
|
299
|
-
"POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION": project.get_installed_requirement_version(
|
|
300
|
-
"poetry-dynamic-versioning[plugin]"
|
|
301
|
-
),
|
|
302
|
-
"POETRY_PLUGIN_EXPORT_VERSION": project.get_installed_requirement_version(
|
|
303
|
-
"poetry-plugin-export"
|
|
304
|
-
),
|
|
305
|
-
}
|
|
306
|
-
else:
|
|
307
|
-
manager_build_args = {
|
|
308
|
-
"UV_VERSION": package_manager_version,
|
|
309
|
-
}
|
|
310
292
|
|
|
311
293
|
build_args = _merge_build_args(
|
|
312
294
|
{
|
|
313
295
|
"PYTHON_VERSION": python_version,
|
|
314
|
-
"
|
|
315
|
-
"PACKAGE_MANAGER_VERSION": package_manager_version,
|
|
296
|
+
"UV_VERSION": project.get_uv_version(),
|
|
316
297
|
"PACKAGE_VERSION": package_version,
|
|
317
298
|
"PACKAGE_NAME": utils.get_package_name(use_underscores=True),
|
|
318
299
|
"AUTHORS": ",".join(
|
|
319
300
|
[f"{name} <{email}>" for name, email in project.get_authors()]
|
|
320
301
|
),
|
|
321
302
|
"GIT_COMMIT": commit_tag,
|
|
322
|
-
**manager_build_args,
|
|
323
303
|
},
|
|
324
304
|
extra_build_args,
|
|
325
305
|
)
|
|
@@ -14,7 +14,7 @@ from poethepoet_tasks import TaskCollection
|
|
|
14
14
|
|
|
15
15
|
from . import utils
|
|
16
16
|
from .env import get_workdir_path
|
|
17
|
-
from .project import
|
|
17
|
+
from .project import get_uv_version
|
|
18
18
|
|
|
19
19
|
LOGGER = logging.getLogger(__name__)
|
|
20
20
|
|
|
@@ -239,8 +239,7 @@ _COMPOSE_VAR_REQUIREMENTS: dict[str, dict[str, set[str]]] = {
|
|
|
239
239
|
"ENVIRONMENT",
|
|
240
240
|
"IMAGE_TAG",
|
|
241
241
|
"PYTHON_VERSION",
|
|
242
|
-
"
|
|
243
|
-
"PACKAGE_MANAGER_VERSION",
|
|
242
|
+
"UV_VERSION",
|
|
244
243
|
},
|
|
245
244
|
"compose-db": {
|
|
246
245
|
"PACKAGE_NAME",
|
|
@@ -250,8 +249,7 @@ _COMPOSE_VAR_REQUIREMENTS: dict[str, dict[str, set[str]]] = {
|
|
|
250
249
|
"DB_PORT",
|
|
251
250
|
"IMAGE_TAG",
|
|
252
251
|
"PYTHON_VERSION",
|
|
253
|
-
"
|
|
254
|
-
"PACKAGE_MANAGER_VERSION",
|
|
252
|
+
"UV_VERSION",
|
|
255
253
|
"POSTGRES_VERSION",
|
|
256
254
|
"WORKDIR_PATH",
|
|
257
255
|
},
|
|
@@ -337,13 +335,12 @@ def get_compose_env(
|
|
|
337
335
|
"ENVIRONMENT": os.getenv("ENVIRONMENT", "production"),
|
|
338
336
|
"IMAGE_TAG": image_tag or "latest",
|
|
339
337
|
"PACKAGE_NAME": package_name,
|
|
340
|
-
"PACKAGE_MANAGER": get_package_manager(),
|
|
341
|
-
"PACKAGE_MANAGER_VERSION": get_package_manager_version(),
|
|
342
338
|
"PACKAGE_UNDERSCORE_NAME": utils.package_name_to_underscore(package_name),
|
|
343
339
|
"WORKDIR_PATH": get_workdir_path(),
|
|
344
340
|
"POSTGRES_VERSION": os.getenv("POSTGRES_VERSION", "17"),
|
|
345
341
|
"PYTHON_VERSION": platform.python_version(),
|
|
346
342
|
"SECRET_KEY": os.getenv("SECRET_KEY", ""),
|
|
343
|
+
"UV_VERSION": get_uv_version(),
|
|
347
344
|
}
|
|
348
345
|
|
|
349
346
|
if compose_type and compose_files:
|
common_python_tasks/github.py
CHANGED
|
@@ -28,6 +28,18 @@ def _is_unreleased_placeholder(notes: str) -> bool:
|
|
|
28
28
|
return normalized in {"[unreleased]", "## [unreleased]", "## unreleased"}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _pattern_targets_hidden_paths(pattern: str) -> bool:
|
|
32
|
+
return any(
|
|
33
|
+
part.startswith(".")
|
|
34
|
+
for part in pattern.split("/")
|
|
35
|
+
if part and part not in {".", ".."}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _path_has_hidden_segment(path: Path) -> bool:
|
|
40
|
+
return any(part.startswith(".") for part in path.parts if part not in {".", ".."})
|
|
41
|
+
|
|
42
|
+
|
|
31
43
|
def _latest_tagged_changelog() -> str | None:
|
|
32
44
|
result = utils.run_git_cliff(["--latest"], capture_output=True)
|
|
33
45
|
changelog = result.stdout.strip()
|
|
@@ -538,6 +550,8 @@ def get_github_release_asset_paths(
|
|
|
538
550
|
interpreted as colon-separated paths or glob patterns.
|
|
539
551
|
If not provided, the default value is `dist/*`. An empty list
|
|
540
552
|
explicitly disables asset selection.
|
|
553
|
+
Wildcard patterns only include hidden files when the pattern itself
|
|
554
|
+
explicitly targets hidden paths (for example, `dist/.*`).
|
|
541
555
|
|
|
542
556
|
Returns:
|
|
543
557
|
A sorted list of existing asset paths.
|
|
@@ -561,9 +575,14 @@ def get_github_release_asset_paths(
|
|
|
561
575
|
asset_paths = []
|
|
562
576
|
for part in source_items:
|
|
563
577
|
if any(char in part for char in "*?[]"):
|
|
578
|
+
matches = [path for path in Path(".").glob(part) if path.is_file()]
|
|
579
|
+
if not _pattern_targets_hidden_paths(part):
|
|
580
|
+
matches = [
|
|
581
|
+
path for path in matches if not _path_has_hidden_segment(path)
|
|
582
|
+
]
|
|
564
583
|
asset_paths.extend(
|
|
565
584
|
sorted(
|
|
566
|
-
|
|
585
|
+
matches,
|
|
567
586
|
key=lambda p: str(p),
|
|
568
587
|
)
|
|
569
588
|
)
|
common_python_tasks/project.py
CHANGED
|
@@ -2,9 +2,7 @@ import ast
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
-
import shutil
|
|
6
5
|
import tomllib
|
|
7
|
-
from enum import StrEnum, auto
|
|
8
6
|
from functools import lru_cache
|
|
9
7
|
from importlib import metadata
|
|
10
8
|
from pathlib import Path
|
|
@@ -17,20 +15,16 @@ from . import utils
|
|
|
17
15
|
|
|
18
16
|
LOGGER = logging.getLogger(__name__)
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
18
|
+
PUBLISH_REPOSITORY_ENV_VAR = "COMMON_PYTHON_TASKS_PUBLISH_REPOSITORY"
|
|
19
|
+
PUBLISH_REPOSITORY_URL_ENV_VAR = "COMMON_PYTHON_TASKS_PUBLISH_URL"
|
|
20
|
+
PUBLISH_REPOSITORY_ENV_VARS = (
|
|
21
|
+
PUBLISH_REPOSITORY_ENV_VAR,
|
|
22
|
+
"UV_PUBLISH_INDEX",
|
|
23
|
+
)
|
|
24
|
+
PUBLISH_URL_ENV_VARS = (
|
|
25
|
+
PUBLISH_REPOSITORY_URL_ENV_VAR,
|
|
26
|
+
"UV_PUBLISH_URL",
|
|
25
27
|
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class PackageManager(StrEnum):
|
|
29
|
-
"""Supported package-manager backends for build and publish workflows."""
|
|
30
|
-
|
|
31
|
-
POETRY = auto()
|
|
32
|
-
UV = auto()
|
|
33
|
-
AUTO = auto()
|
|
34
28
|
|
|
35
29
|
|
|
36
30
|
def _parse_tool_version_output(output: str, tool_name: str) -> str:
|
|
@@ -49,116 +43,88 @@ def _parse_tool_version_output(output: str, tool_name: str) -> str:
|
|
|
49
43
|
utils.fatal(f"Unable to parse {tool_name} version output: {output!r}")
|
|
50
44
|
|
|
51
45
|
|
|
52
|
-
def
|
|
53
|
-
for env_var in
|
|
46
|
+
def _find_non_empty_env_override(env_vars: Sequence[str]) -> tuple[str, str] | None:
|
|
47
|
+
for env_var in env_vars:
|
|
54
48
|
value = os.getenv(env_var)
|
|
55
49
|
if value is not None and value.strip():
|
|
56
|
-
return env_var, value.strip()
|
|
50
|
+
return env_var, value.strip()
|
|
57
51
|
return None
|
|
58
52
|
|
|
59
53
|
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _detect_declared_package_manager(
|
|
68
|
-
pyproject_data: dict[str, Any],
|
|
69
|
-
) -> PackageManager | None:
|
|
70
|
-
build_backend = (
|
|
71
|
-
pyproject_data.get("build-system", {}).get("build-backend", "") or ""
|
|
72
|
-
)
|
|
73
|
-
if isinstance(build_backend, str) and "poetry" in build_backend.lower():
|
|
74
|
-
return PackageManager.POETRY
|
|
75
|
-
|
|
76
|
-
if pyproject_data.get("tool", {}).get("poetry") is not None:
|
|
77
|
-
return PackageManager.POETRY
|
|
78
|
-
|
|
79
|
-
if pyproject_data.get("tool", {}).get("uv") is not None:
|
|
80
|
-
return PackageManager.UV
|
|
81
|
-
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _resolve_package_manager_auto() -> tuple[PackageManager, str]:
|
|
86
|
-
has_poetry = shutil.which("poetry") is not None
|
|
87
|
-
has_uv = shutil.which("uv") is not None
|
|
54
|
+
def _resolve_uv_publish_repository_from_config() -> str | None:
|
|
55
|
+
pyproject_data = read_pyproject_toml()
|
|
56
|
+
uv_config = pyproject_data.get("tool", {}).get("uv", {})
|
|
57
|
+
raw_indexes = uv_config.get("index")
|
|
58
|
+
if raw_indexes is None:
|
|
59
|
+
return None
|
|
88
60
|
|
|
89
|
-
if
|
|
90
|
-
|
|
61
|
+
if not isinstance(raw_indexes, list):
|
|
62
|
+
LOGGER.warning(
|
|
63
|
+
"Ignoring malformed [tool.uv.index] configuration: expected a list of tables"
|
|
64
|
+
)
|
|
65
|
+
return None
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
67
|
+
publishable_indexes = [
|
|
68
|
+
entry
|
|
69
|
+
for entry in raw_indexes
|
|
70
|
+
if isinstance(entry, dict)
|
|
71
|
+
and isinstance(entry.get("name"), str)
|
|
72
|
+
and entry.get("name", "").strip()
|
|
73
|
+
and isinstance(entry.get("publish-url"), str)
|
|
74
|
+
and entry.get("publish-url", "").strip()
|
|
75
|
+
]
|
|
76
|
+
if not publishable_indexes:
|
|
77
|
+
return None
|
|
94
78
|
|
|
95
|
-
|
|
79
|
+
default_publishable_names = [
|
|
80
|
+
entry["name"].strip()
|
|
81
|
+
for entry in publishable_indexes
|
|
82
|
+
if entry.get("default") is True
|
|
83
|
+
]
|
|
84
|
+
if len(default_publishable_names) > 1:
|
|
96
85
|
utils.fatal(
|
|
97
|
-
"
|
|
98
|
-
"or
|
|
86
|
+
"Multiple publishable `[[tool.uv.index]]` entries are marked `default = true`. "
|
|
87
|
+
"Set one default publishable index or pass an explicit publish repository."
|
|
99
88
|
)
|
|
89
|
+
if len(default_publishable_names) == 1:
|
|
90
|
+
return default_publishable_names[0]
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return declared_manager, "project metadata"
|
|
105
|
-
|
|
106
|
-
return PackageManager.POETRY, "default fallback"
|
|
92
|
+
publishable_names = [entry["name"].strip() for entry in publishable_indexes]
|
|
93
|
+
if len(publishable_names) == 1:
|
|
94
|
+
return publishable_names[0]
|
|
107
95
|
|
|
96
|
+
utils.fatal(
|
|
97
|
+
"Multiple publishable `[[tool.uv.index]]` entries were found. "
|
|
98
|
+
"Set one as `default = true` or pass an explicit publish repository."
|
|
99
|
+
)
|
|
108
100
|
|
|
109
|
-
@lru_cache
|
|
110
|
-
def get_package_manager() -> PackageManager:
|
|
111
|
-
"""Resolve the package manager backend used for build and publish commands.
|
|
112
|
-
|
|
113
|
-
Resolution order:
|
|
114
|
-
1. Environment overrides via `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`,
|
|
115
|
-
`COMMON_PYTHON_TASKS_BUILD_TOOL`, or `PACKAGE_MANAGER`, in that order.
|
|
116
|
-
2. Auto-discovery from installed executables and project metadata.
|
|
117
101
|
|
|
118
|
-
|
|
102
|
+
def _resolve_default_publish_target() -> tuple[str | None, str | None]:
|
|
103
|
+
repository_override = _find_non_empty_env_override(PUBLISH_REPOSITORY_ENV_VARS)
|
|
104
|
+
if repository_override is not None:
|
|
105
|
+
env_var, repository_name = repository_override
|
|
106
|
+
LOGGER.debug("Using uv publish index %s from %s", repository_name, env_var)
|
|
107
|
+
return repository_name, None
|
|
119
108
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"""
|
|
123
|
-
override = _find_package_manager_override()
|
|
124
|
-
if override is None:
|
|
125
|
-
package_manager, reason = _resolve_package_manager_auto()
|
|
109
|
+
config_repository_name = _resolve_uv_publish_repository_from_config()
|
|
110
|
+
if config_repository_name is not None:
|
|
126
111
|
LOGGER.debug(
|
|
127
|
-
"Using
|
|
128
|
-
|
|
129
|
-
reason,
|
|
112
|
+
"Using uv publish index %s from [[tool.uv.index]] configuration",
|
|
113
|
+
config_repository_name,
|
|
130
114
|
)
|
|
131
|
-
return
|
|
115
|
+
return config_repository_name, None
|
|
132
116
|
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
f"Invalid {env_var} value {raw_value!r}. Use one of: poetry, uv, auto."
|
|
137
|
-
)
|
|
117
|
+
repository_url_override = _find_non_empty_env_override(PUBLISH_URL_ENV_VARS)
|
|
118
|
+
if repository_url_override is None:
|
|
119
|
+
return None, None
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
"Using package manager backend: %s (%s via %s=%s)",
|
|
143
|
-
package_manager,
|
|
144
|
-
reason,
|
|
145
|
-
env_var,
|
|
146
|
-
raw_value,
|
|
147
|
-
)
|
|
148
|
-
return package_manager
|
|
149
|
-
|
|
150
|
-
package_manager = PackageManager(raw_value)
|
|
151
|
-
_ensure_package_manager_available(package_manager)
|
|
152
|
-
LOGGER.debug(
|
|
153
|
-
"Using package manager backend: %s (from %s)",
|
|
154
|
-
package_manager,
|
|
155
|
-
env_var,
|
|
156
|
-
)
|
|
157
|
-
return package_manager
|
|
121
|
+
env_var, repository_url = repository_url_override
|
|
122
|
+
LOGGER.debug("Using uv publish URL %s from %s", repository_url, env_var)
|
|
123
|
+
return None, repository_url
|
|
158
124
|
|
|
159
125
|
|
|
160
|
-
def
|
|
161
|
-
"""Return the build command
|
|
126
|
+
def get_build_command(wheel_only: bool = False) -> list[str]:
|
|
127
|
+
"""Return the uv package build command.
|
|
162
128
|
|
|
163
129
|
Args:
|
|
164
130
|
wheel_only: Whether to build only a wheel artifact.
|
|
@@ -166,23 +132,16 @@ def get_package_manager_build_command(wheel_only: bool = False) -> list[str]:
|
|
|
166
132
|
Returns:
|
|
167
133
|
Command tokens for the package build command.
|
|
168
134
|
"""
|
|
169
|
-
|
|
170
|
-
if package_manager == PackageManager.UV:
|
|
171
|
-
command = ["uv", "build"]
|
|
172
|
-
if wheel_only:
|
|
173
|
-
command.append("--wheel")
|
|
174
|
-
return command
|
|
175
|
-
|
|
176
|
-
command = ["poetry", "build"]
|
|
135
|
+
command = ["uv", "build"]
|
|
177
136
|
if wheel_only:
|
|
178
|
-
command
|
|
137
|
+
command.append("--wheel")
|
|
179
138
|
return command
|
|
180
139
|
|
|
181
140
|
|
|
182
|
-
def
|
|
141
|
+
def get_dependency_update_command(
|
|
183
142
|
dependencies: Sequence[str] | None = None,
|
|
184
143
|
) -> list[str]:
|
|
185
|
-
"""Return the dependency update command
|
|
144
|
+
"""Return the uv dependency update command.
|
|
186
145
|
|
|
187
146
|
Args:
|
|
188
147
|
dependencies: Optional dependency names to update. When omitted, all
|
|
@@ -194,21 +153,17 @@ def get_package_manager_update_command(
|
|
|
194
153
|
if dependencies is None:
|
|
195
154
|
dependencies = []
|
|
196
155
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
command
|
|
200
|
-
|
|
201
|
-
command.extend(["--upgrade-package", dependency])
|
|
202
|
-
return command
|
|
203
|
-
|
|
204
|
-
return ["poetry", "update", *dependencies]
|
|
156
|
+
command = ["uv", "sync", "--upgrade"]
|
|
157
|
+
for dependency in dependencies:
|
|
158
|
+
command.extend(["--upgrade-package", dependency])
|
|
159
|
+
return command
|
|
205
160
|
|
|
206
161
|
|
|
207
|
-
def
|
|
162
|
+
def get_publish_command(
|
|
208
163
|
repository: str | None = None,
|
|
209
164
|
repository_url: str | None = None,
|
|
210
165
|
) -> list[str]:
|
|
211
|
-
"""Return the publish command
|
|
166
|
+
"""Return the uv package publish command.
|
|
212
167
|
|
|
213
168
|
Args:
|
|
214
169
|
repository: Optional configured repository name to publish to.
|
|
@@ -220,20 +175,14 @@ def get_package_manager_publish_command(
|
|
|
220
175
|
if repository is not None and repository_url is not None:
|
|
221
176
|
utils.fatal("Specify either `repository` or `repository_url`, not both.")
|
|
222
177
|
|
|
223
|
-
|
|
224
|
-
|
|
178
|
+
if repository is None and repository_url is None:
|
|
179
|
+
repository, repository_url = _resolve_default_publish_target()
|
|
180
|
+
|
|
181
|
+
command = ["uv", "publish"]
|
|
225
182
|
if repository_url is not None:
|
|
226
|
-
if package_manager == PackageManager.POETRY:
|
|
227
|
-
utils.fatal(
|
|
228
|
-
"Poetry publish only supports configured repository names. "
|
|
229
|
-
"Pass `repository` instead of `repository_url`."
|
|
230
|
-
)
|
|
231
183
|
command += ["--publish-url", repository_url]
|
|
232
184
|
elif repository is not None:
|
|
233
|
-
|
|
234
|
-
command += ["--index", repository]
|
|
235
|
-
else:
|
|
236
|
-
command += ["-r", repository]
|
|
185
|
+
command += ["--index", repository]
|
|
237
186
|
return command
|
|
238
187
|
|
|
239
188
|
|
|
@@ -251,79 +200,45 @@ def get_authors() -> list[tuple[str, str]]:
|
|
|
251
200
|
]
|
|
252
201
|
|
|
253
202
|
|
|
254
|
-
def get_local_poetry_plugin_version(package_name: str) -> str | None:
|
|
255
|
-
"""Return a version from the local Poetry plugin installation if present.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
package_name: The name of the package to check.
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
The version of the local Poetry plugin if present, None otherwise.
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
plugins_dir = Path(".poetry/plugins")
|
|
265
|
-
if not plugins_dir.is_dir():
|
|
266
|
-
return None
|
|
267
|
-
|
|
268
|
-
candidates = {
|
|
269
|
-
package_name,
|
|
270
|
-
utils.package_name_to_underscore(package_name),
|
|
271
|
-
package_name.replace("_", "-"),
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
for metadata_path in plugins_dir.glob("*.dist-info/METADATA"):
|
|
275
|
-
try:
|
|
276
|
-
metadata_text = metadata_path.read_text(encoding="utf-8")
|
|
277
|
-
except OSError:
|
|
278
|
-
continue
|
|
279
|
-
|
|
280
|
-
name = None
|
|
281
|
-
version = None
|
|
282
|
-
for line in metadata_text.splitlines():
|
|
283
|
-
if line.startswith("Name:"):
|
|
284
|
-
name = line.split(":", 1)[1].strip()
|
|
285
|
-
elif line.startswith("Version:"):
|
|
286
|
-
version = line.split(":", 1)[1].strip()
|
|
287
|
-
if name is not None and version is not None:
|
|
288
|
-
break
|
|
289
|
-
|
|
290
|
-
if name and version and name in candidates:
|
|
291
|
-
return version
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
|
|
295
203
|
@lru_cache
|
|
296
|
-
def
|
|
297
|
-
"""Return the installed
|
|
204
|
+
def get_uv_version() -> str:
|
|
205
|
+
"""Return the installed uv version string.
|
|
298
206
|
|
|
299
207
|
Returns:
|
|
300
|
-
The
|
|
208
|
+
The uv version parsed from the output of `uv --version`.
|
|
301
209
|
"""
|
|
302
210
|
return _parse_tool_version_output(
|
|
303
|
-
utils.run_command(["
|
|
304
|
-
tool_name="
|
|
211
|
+
utils.run_command(["uv", "--version"], capture_output=True).stdout,
|
|
212
|
+
tool_name="uv",
|
|
305
213
|
)
|
|
306
214
|
|
|
307
215
|
|
|
308
|
-
def
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
216
|
+
def _query_uv_runtime_version(candidate: str) -> str | None:
|
|
217
|
+
result = utils.run_command(
|
|
218
|
+
[
|
|
219
|
+
"uv",
|
|
220
|
+
"run",
|
|
221
|
+
"python",
|
|
222
|
+
"-c",
|
|
223
|
+
"import importlib.metadata, sys; print(importlib.metadata.version(sys.argv[1]))",
|
|
224
|
+
candidate,
|
|
225
|
+
],
|
|
226
|
+
capture_output=True,
|
|
227
|
+
acceptable_returncodes={0, 1},
|
|
228
|
+
)
|
|
229
|
+
if result is not None and result.returncode == 0:
|
|
230
|
+
return result.stdout.strip() or None
|
|
231
|
+
return None
|
|
313
232
|
|
|
314
|
-
Returns:
|
|
315
|
-
Parsed package-manager version.
|
|
316
|
-
"""
|
|
317
|
-
selected_manager = PackageManager(package_manager or get_package_manager())
|
|
318
|
-
if selected_manager == PackageManager.POETRY:
|
|
319
|
-
return get_poetry_version()
|
|
320
233
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
234
|
+
def _parse_package_show_version(output: str) -> str | None:
|
|
235
|
+
for line in output.splitlines():
|
|
236
|
+
if ":" not in line:
|
|
237
|
+
continue
|
|
238
|
+
key, value = line.split(":", 1)
|
|
239
|
+
if key.strip().lower() == "version":
|
|
240
|
+
return value.strip()
|
|
241
|
+
return None
|
|
327
242
|
|
|
328
243
|
|
|
329
244
|
@lru_cache
|
|
@@ -336,8 +251,7 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
|
|
|
336
251
|
|
|
337
252
|
It normalizes extras like `package[extra]` and tries common name variants
|
|
338
253
|
with hyphens and underscores. If the package is not available to
|
|
339
|
-
importlib.metadata, it falls back to
|
|
340
|
-
listing commands.
|
|
254
|
+
importlib.metadata, it falls back to uv runtime and package listing commands.
|
|
341
255
|
|
|
342
256
|
Args:
|
|
343
257
|
requirement_name: The requirement name to lookup.
|
|
@@ -358,64 +272,18 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
|
|
|
358
272
|
except metadata.PackageNotFoundError:
|
|
359
273
|
pass
|
|
360
274
|
|
|
361
|
-
package_manager = PackageManager(get_package_manager())
|
|
362
|
-
|
|
363
|
-
def _query_runtime_version(candidate: str) -> str | None:
|
|
364
|
-
result = utils.run_command(
|
|
365
|
-
[
|
|
366
|
-
package_manager,
|
|
367
|
-
"run",
|
|
368
|
-
"python",
|
|
369
|
-
"-c",
|
|
370
|
-
"import importlib.metadata, sys; print(importlib.metadata.version(sys.argv[1]))",
|
|
371
|
-
candidate,
|
|
372
|
-
],
|
|
373
|
-
capture_output=True,
|
|
374
|
-
acceptable_returncodes={0, 1},
|
|
375
|
-
)
|
|
376
|
-
if result is None:
|
|
377
|
-
return None
|
|
378
|
-
if result.returncode == 0:
|
|
379
|
-
version = result.stdout.strip()
|
|
380
|
-
if version:
|
|
381
|
-
return version
|
|
382
|
-
return None
|
|
383
|
-
|
|
384
|
-
if package_manager == PackageManager.POETRY:
|
|
385
|
-
version = get_local_poetry_plugin_version(package_name)
|
|
386
|
-
if version:
|
|
387
|
-
return version
|
|
388
|
-
|
|
389
275
|
for candidate in candidates:
|
|
390
|
-
version =
|
|
276
|
+
version = _query_uv_runtime_version(candidate)
|
|
391
277
|
if version:
|
|
392
278
|
return version
|
|
393
279
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
key, value = line.split(":", 1)
|
|
399
|
-
if key.strip().lower() == "version":
|
|
400
|
-
return value.strip()
|
|
401
|
-
return None
|
|
402
|
-
|
|
403
|
-
show_commands = (
|
|
404
|
-
(["poetry", "show", package_name], ["poetry", "self", "show", package_name])
|
|
405
|
-
if package_manager == PackageManager.POETRY
|
|
406
|
-
else (["uv", "pip", "show", package_name],)
|
|
280
|
+
result = utils.run_command(
|
|
281
|
+
["uv", "pip", "show", package_name],
|
|
282
|
+
capture_output=True,
|
|
283
|
+
acceptable_returncodes={0, 1},
|
|
407
284
|
)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
result = utils.run_command(
|
|
411
|
-
list(cmd),
|
|
412
|
-
capture_output=True,
|
|
413
|
-
acceptable_returncodes={0, 1},
|
|
414
|
-
)
|
|
415
|
-
if result.returncode == 0:
|
|
416
|
-
version = _parse_package_show_version(result.stdout)
|
|
417
|
-
if version:
|
|
418
|
-
return version
|
|
285
|
+
if result.returncode == 0:
|
|
286
|
+
return _parse_package_show_version(result.stdout)
|
|
419
287
|
return None
|
|
420
288
|
|
|
421
289
|
|
|
@@ -623,28 +491,6 @@ def is_task_tag_included(tag: str) -> bool:
|
|
|
623
491
|
return True
|
|
624
492
|
|
|
625
493
|
|
|
626
|
-
def get_project_version_from_poetry() -> str:
|
|
627
|
-
"""Return the project version parsed from `poetry version` output.
|
|
628
|
-
|
|
629
|
-
Returns:
|
|
630
|
-
The parsed project version string.
|
|
631
|
-
"""
|
|
632
|
-
poetry_version_output = utils.run_command(
|
|
633
|
-
["poetry", "version"],
|
|
634
|
-
capture_output=True,
|
|
635
|
-
acceptable_returncodes={0},
|
|
636
|
-
).stdout.strip()
|
|
637
|
-
if not poetry_version_output:
|
|
638
|
-
utils.fatal("Unable to determine project version from poetry version")
|
|
639
|
-
|
|
640
|
-
parts = poetry_version_output.rsplit(" ", 1)
|
|
641
|
-
if len(parts) != 2 or not parts[1].strip():
|
|
642
|
-
utils.fatal(
|
|
643
|
-
f"Unexpected poetry version output format: {poetry_version_output!r}"
|
|
644
|
-
)
|
|
645
|
-
return parts[1].strip()
|
|
646
|
-
|
|
647
|
-
|
|
648
494
|
def _get_project_version_from_pyproject() -> str | None:
|
|
649
495
|
"""Return `project.version` from pyproject metadata when present.
|
|
650
496
|
|
|
@@ -674,17 +520,12 @@ def get_project_version_from_vcs() -> str | None:
|
|
|
674
520
|
def get_project_version() -> str:
|
|
675
521
|
"""Return the current project version for release and tagging workflows.
|
|
676
522
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
`project.version` and falls back to VCS-derived versioning.
|
|
523
|
+
Static `project.version` metadata takes precedence over a VCS-derived version.
|
|
524
|
+
Placeholder static versions fall back to VCS metadata when available.
|
|
680
525
|
|
|
681
526
|
Returns:
|
|
682
527
|
The resolved current project version.
|
|
683
528
|
"""
|
|
684
|
-
package_manager = PackageManager(get_package_manager())
|
|
685
|
-
if package_manager == PackageManager.POETRY:
|
|
686
|
-
return get_project_version_from_poetry()
|
|
687
|
-
|
|
688
529
|
pyproject_version = _get_project_version_from_pyproject()
|
|
689
530
|
if pyproject_version and pyproject_version not in {"0.0.0", "0.0.0.dev0"}:
|
|
690
531
|
return pyproject_version
|
|
@@ -710,15 +551,3 @@ def get_release_tag_from_project_version() -> str:
|
|
|
710
551
|
Release tag prefixed with `v`.
|
|
711
552
|
"""
|
|
712
553
|
return f"v{get_project_version()}"
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
def get_release_tag_from_poetry_version() -> str:
|
|
716
|
-
"""Return release tag string in `v<version>` form.
|
|
717
|
-
|
|
718
|
-
This function is retained for backward compatibility and now uses the
|
|
719
|
-
selected package-manager backend.
|
|
720
|
-
|
|
721
|
-
Returns:
|
|
722
|
-
The release tag string prefixed with `v`.
|
|
723
|
-
"""
|
|
724
|
-
return get_release_tag_from_project_version()
|
common_python_tasks/tasks.py
CHANGED
|
@@ -459,7 +459,6 @@ def build_image(
|
|
|
459
459
|
resolve_extension_content,
|
|
460
460
|
)
|
|
461
461
|
from .project import (
|
|
462
|
-
get_package_manager,
|
|
463
462
|
has_debug_dependency_group,
|
|
464
463
|
resolve_container_entrypoint_command,
|
|
465
464
|
)
|
|
@@ -599,7 +598,6 @@ def build_image(
|
|
|
599
598
|
dockerfile_text = render_template_text(
|
|
600
599
|
load_data_file("Dockerfile.j2")[1],
|
|
601
600
|
{
|
|
602
|
-
"PACKAGE_MANAGER": get_package_manager(),
|
|
603
601
|
"EXTENSION_CONTENT": combined_content,
|
|
604
602
|
"CONTAINER_DEPS_IMAGE": deps_image_tag,
|
|
605
603
|
"CONTAINER_DEPS_MOVE_SCRIPT": container_deps_move_script,
|
|
@@ -898,15 +896,13 @@ def publish_package(
|
|
|
898
896
|
repository: Optional configured repository name to publish to.
|
|
899
897
|
repository_url: Optional repository upload URL to publish to.
|
|
900
898
|
"""
|
|
901
|
-
from .project import
|
|
899
|
+
from .project import get_publish_command
|
|
902
900
|
from .utils import run_command
|
|
903
901
|
|
|
904
902
|
if build_first:
|
|
905
903
|
build_package(clean_dist=True)
|
|
906
904
|
run_command(
|
|
907
|
-
|
|
908
|
-
repository=repository, repository_url=repository_url
|
|
909
|
-
)
|
|
905
|
+
get_publish_command(repository=repository, repository_url=repository_url)
|
|
910
906
|
)
|
|
911
907
|
|
|
912
908
|
|
|
@@ -1010,7 +1006,6 @@ def _get_changed_dependency_names() -> list[str]:
|
|
|
1010
1006
|
"--unified=3",
|
|
1011
1007
|
"--",
|
|
1012
1008
|
"pyproject.toml",
|
|
1013
|
-
"poetry.lock",
|
|
1014
1009
|
"uv.lock",
|
|
1015
1010
|
],
|
|
1016
1011
|
capture_output=True,
|
|
@@ -1028,7 +1023,6 @@ def _get_dependency_update_changed_files() -> list[Path]:
|
|
|
1028
1023
|
"--porcelain",
|
|
1029
1024
|
"--",
|
|
1030
1025
|
"pyproject.toml",
|
|
1031
|
-
"poetry.lock",
|
|
1032
1026
|
"uv.lock",
|
|
1033
1027
|
],
|
|
1034
1028
|
capture_output=True,
|
|
@@ -1065,7 +1059,7 @@ def update_dependencies(
|
|
|
1065
1059
|
draft: bool = False,
|
|
1066
1060
|
run_tests: bool = False,
|
|
1067
1061
|
) -> None:
|
|
1068
|
-
"""Update project dependencies with
|
|
1062
|
+
"""Update project dependencies with uv.
|
|
1069
1063
|
|
|
1070
1064
|
Args:
|
|
1071
1065
|
*dependencies: Optional dependency names to update. When omitted, update
|
|
@@ -1080,14 +1074,14 @@ def update_dependencies(
|
|
|
1080
1074
|
"""
|
|
1081
1075
|
from .git import get_current_branch, get_default_branch
|
|
1082
1076
|
from .github import create_pull_request
|
|
1083
|
-
from .project import
|
|
1077
|
+
from .project import get_dependency_update_command
|
|
1084
1078
|
from .utils import run_command
|
|
1085
1079
|
|
|
1086
1080
|
should_create_branch = branch or branch_name is not None or pr
|
|
1087
1081
|
if should_create_branch or commit:
|
|
1088
1082
|
_ensure_clean_dependency_update_worktree()
|
|
1089
1083
|
|
|
1090
|
-
run_command(
|
|
1084
|
+
run_command(get_dependency_update_command(dependencies))
|
|
1091
1085
|
if not _get_dependency_update_changed_files():
|
|
1092
1086
|
LOGGER.info(
|
|
1093
1087
|
"Dependency update completed with no lockfile or pyproject changes."
|
|
@@ -1117,7 +1111,6 @@ def update_dependencies(
|
|
|
1117
1111
|
path
|
|
1118
1112
|
for path in (
|
|
1119
1113
|
Path("pyproject.toml"),
|
|
1120
|
-
Path("poetry.lock"),
|
|
1121
1114
|
Path("uv.lock"),
|
|
1122
1115
|
)
|
|
1123
1116
|
if path.exists()
|
|
@@ -1148,10 +1141,10 @@ def build_package(wheel_only: bool = False, clean_dist: bool = False) -> None:
|
|
|
1148
1141
|
wheel_only: Whether to build only the wheel artifact.
|
|
1149
1142
|
clean_dist: Whether to remove existing distribution artifacts first.
|
|
1150
1143
|
"""
|
|
1151
|
-
from .project import
|
|
1144
|
+
from .project import get_build_command
|
|
1152
1145
|
from .utils import run_command
|
|
1153
1146
|
|
|
1154
|
-
command =
|
|
1147
|
+
command = get_build_command(wheel_only=wheel_only)
|
|
1155
1148
|
if clean_dist:
|
|
1156
1149
|
dist_path = Path("dist")
|
|
1157
1150
|
if dist_path.exists() and any(dist_path.iterdir()):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: common-python-tasks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Opinionated Poe the Poet tasks for Python package development.
|
|
5
5
|
Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
|
|
6
6
|
Project-URL: Source, http://github.com/ci-sourcerer/common-python-tasks.git
|
|
@@ -43,9 +43,7 @@ It supplies sensible defaults for formatting, linting, testing, packaging, relea
|
|
|
43
43
|
Add `common-python-tasks` as a development dependency from your project root.
|
|
44
44
|
|
|
45
45
|
```shell
|
|
46
|
-
uv add --dev common-python-tasks==0.
|
|
47
|
-
# or, for Poetry projects
|
|
48
|
-
poetry add --group dev common-python-tasks==0.8.0
|
|
46
|
+
uv add --dev common-python-tasks==0.9.0
|
|
49
47
|
```
|
|
50
48
|
|
|
51
49
|
Configure Poe the Poet to expose the default `common` task set.
|
|
@@ -70,7 +68,7 @@ The helper script performs the same development-dependency installation and Poe
|
|
|
70
68
|
```shell
|
|
71
69
|
curl --fail --silent --show-error --location \
|
|
72
70
|
--output /tmp/add_common_python_tasks.py \
|
|
73
|
-
https://raw.githubusercontent.com/ci-sourcerer/common-python-tasks/v0.
|
|
71
|
+
https://raw.githubusercontent.com/ci-sourcerer/common-python-tasks/v0.9.0/scripts/add_common_python_tasks.py
|
|
74
72
|
```
|
|
75
73
|
|
|
76
74
|
Review the downloaded script before executing it.
|
|
@@ -82,10 +80,10 @@ less /tmp/add_common_python_tasks.py
|
|
|
82
80
|
Run the reviewed script with the same pinned package version.
|
|
83
81
|
|
|
84
82
|
```shell
|
|
85
|
-
COMMON_PYTHON_TASKS_VERSION=0.
|
|
83
|
+
COMMON_PYTHON_TASKS_VERSION=0.9.0 python3 /tmp/add_common_python_tasks.py
|
|
86
84
|
```
|
|
87
85
|
|
|
88
|
-
To install another release, replace both occurrences of `0.
|
|
86
|
+
To install another release, replace both occurrences of `0.9.0` with that release's version.
|
|
89
87
|
|
|
90
88
|
## Available tasks
|
|
91
89
|
|
|
@@ -108,7 +106,7 @@ The generated tables below list public tasks only. Tags identify which tasks are
|
|
|
108
106
|
| --- | --- | --- |
|
|
109
107
|
| `publish-package` | Publish the package to the PyPI server. | common, packaging |
|
|
110
108
|
| `publish-github-release` | Publish or update a GitHub Release for the current repository. | common, packaging, release |
|
|
111
|
-
| `update-dependencies` | Update project dependencies with
|
|
109
|
+
| `update-dependencies` | Update project dependencies with uv. | common, packaging |
|
|
112
110
|
| `build-package` | Build the package (wheel and sdist). | build, common, packaging |
|
|
113
111
|
| `bump-version` | Bump the project version. | common, packaging |
|
|
114
112
|
| `changelog` | Print the changelog for the current version based on git history and git-cliff. | common, packaging, release |
|
|
@@ -143,7 +141,7 @@ The generated tables below list public tasks only. Tags identify which tasks are
|
|
|
143
141
|
|
|
144
142
|
Every project needs a `pyproject.toml` file at its root and Poe the Poet available in its development environment.
|
|
145
143
|
|
|
146
|
-
Tasks that install, build, publish, or update dependencies
|
|
144
|
+
Tasks that install, build, publish, or update dependencies require uv. Package and release tasks need a resolvable project version from `project.version` or Git tags. Dynamic versioning is supported but is not a baseline requirement.
|
|
147
145
|
|
|
148
146
|
### Task selection
|
|
149
147
|
|
|
@@ -163,18 +161,27 @@ include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'
|
|
|
163
161
|
|
|
164
162
|
The `containers` tag also includes container-based development-stack tasks. Use the tags in the task tables to tailor a smaller task set.
|
|
165
163
|
|
|
166
|
-
### Package
|
|
164
|
+
### Package management
|
|
167
165
|
|
|
168
|
-
Packaging-related tasks, including `build-package`, `publish-package`, release version resolution, and container build metadata,
|
|
166
|
+
Packaging-related tasks, including `build-package`, `publish-package`, dependency updates, release version resolution, and container build metadata, use uv exclusively. The `uv` executable must be available on `PATH`.
|
|
169
167
|
|
|
170
|
-
|
|
168
|
+
### Publish target selection
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
2. `COMMON_PYTHON_TASKS_BUILD_TOOL`
|
|
174
|
-
3. `PACKAGE_MANAGER`
|
|
175
|
-
4. Auto-discovery (`auto`) based on installed tools and project metadata
|
|
170
|
+
The `publish-package` task resolves publish targets with this precedence.
|
|
176
171
|
|
|
177
|
-
|
|
172
|
+
1. Explicit task arguments (`repository` or `repository_url`)
|
|
173
|
+
2. Environment-variable fallback
|
|
174
|
+
3. uv project configuration fallback from `[[tool.uv.index]]`
|
|
175
|
+
4. The `uv publish` default
|
|
176
|
+
|
|
177
|
+
uv prefers named publish indexes.
|
|
178
|
+
|
|
179
|
+
- `COMMON_PYTHON_TASKS_PUBLISH_REPOSITORY`
|
|
180
|
+
- `UV_PUBLISH_INDEX`
|
|
181
|
+
- `COMMON_PYTHON_TASKS_PUBLISH_URL`
|
|
182
|
+
- `UV_PUBLISH_URL`
|
|
183
|
+
|
|
184
|
+
When using uv project configuration fallback, `[[tool.uv.index]]` must include both `name` and `publish-url` for publish selection. If multiple publishable indexes exist, set exactly one `default = true` or pass an explicit repository.
|
|
178
185
|
|
|
179
186
|
### Configuration precedence
|
|
180
187
|
|
|
@@ -196,7 +203,7 @@ After installing the package, a minimal project configuration looks like this.
|
|
|
196
203
|
```toml
|
|
197
204
|
[project]
|
|
198
205
|
name = "simple-cli-tool"
|
|
199
|
-
version = "0.
|
|
206
|
+
version = "0.9.0"
|
|
200
207
|
|
|
201
208
|
[tool.poe]
|
|
202
209
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -232,9 +239,10 @@ addopts = "-ra"
|
|
|
232
239
|
#### Project and package settings
|
|
233
240
|
|
|
234
241
|
- `PACKAGE_NAME`: Overrides the package name inferred from `pyproject.toml`
|
|
235
|
-
- `
|
|
236
|
-
- `
|
|
237
|
-
- `
|
|
242
|
+
- `COMMON_PYTHON_TASKS_PUBLISH_REPOSITORY`: Preferred publish repository/index name for `publish-package`
|
|
243
|
+
- `COMMON_PYTHON_TASKS_PUBLISH_URL`: Preferred uv publish upload URL for `publish-package` when no repository/index is selected
|
|
244
|
+
- `UV_PUBLISH_INDEX`: uv publish-index fallback for `publish-package`
|
|
245
|
+
- `UV_PUBLISH_URL`: uv publish upload-url fallback for `publish-package`
|
|
238
246
|
|
|
239
247
|
#### Container image settings
|
|
240
248
|
|
|
@@ -386,7 +394,7 @@ GITHUB_RELEASE_ASSETS="dist/*.whl:dist/*.tar.gz" poe publish-github-release
|
|
|
386
394
|
|
|
387
395
|
### Container build fails with "unable to find package"
|
|
388
396
|
|
|
389
|
-
Check that `pyproject.toml` has a correct package name and the package-discovery settings required by its build backend. For
|
|
397
|
+
Check that `pyproject.toml` has a correct package name and the package-discovery settings required by its build backend. For a `src` layout built with Hatch, configure `[tool.hatch.build.targets.wheel] packages = ["src/your_package"]`.
|
|
390
398
|
|
|
391
399
|
### Stack fails to start or services cannot connect
|
|
392
400
|
|
|
@@ -411,9 +419,9 @@ Ensure the project-root `.env` file is writable and inspect its permissions with
|
|
|
411
419
|
|
|
412
420
|
See the [standard Dockerfile template](src/common_python_tasks/data/generic/Dockerfile.j2) for the implementation.
|
|
413
421
|
|
|
414
|
-
- Multi-stage build: The build stage installs
|
|
415
|
-
- Cache mounts: Pip and
|
|
416
|
-
- Explicit build metadata: `PYTHON_VERSION`, `
|
|
422
|
+
- Multi-stage build: The build stage installs uv and builds a wheel. The runtime stage installs only the wheel to keep the final image slim and reproducible.
|
|
423
|
+
- Cache mounts: Pip and uv cache mounts speed up iterative builds without bloating the final image.
|
|
424
|
+
- Explicit build metadata: `PYTHON_VERSION`, `UV_VERSION`, `PACKAGE_VERSION`, `PACKAGE_NAME`, `AUTHORS`, and `GIT_COMMIT` make image metadata predictable and auditable.
|
|
417
425
|
- Project-level build settings: `CONTAINER_APT_PACKAGES`, `CONTAINER_CUSTOM_ENTRYPOINT`, and `CONTAINER_DEPS_IMAGE` configure generated image behavior without being encoded as generic Docker arguments.
|
|
418
426
|
- Optional debug stage: The image exports and installs the `debug` dependency group only when present and does not include it in the default final image.
|
|
419
427
|
- Stable package path: Symlinks give entrypoints and consumers consistent `/pkg` and `/_$PACKAGE_NAME` paths regardless of wheel layout.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
common_python_tasks/__init__.py,sha256=HIumMQ0gSD2sVp6efGAILcQJVIUGiasNEij2Nrt3H7k,847
|
|
2
|
+
common_python_tasks/__main__.py,sha256=GpgSd_toMbEaWA_cZvgsZE7jo4XpzuI-awBVm2uhVyw,3512
|
|
3
|
+
common_python_tasks/docker.py,sha256=Xw3VWXkSImccHHgk2krzA1XYCjojvwLsYpXY5wKoYco,24187
|
|
4
|
+
common_python_tasks/docker_compose.py,sha256=OblQjlVa_8HoWNYqu3599gJc34eAYiSgJhrbWuWBJlc,21193
|
|
5
|
+
common_python_tasks/env.py,sha256=BQeRrFBeXMFUK_4Z7cUKprJdXBZn688EHfWrhbm0WD4,16819
|
|
6
|
+
common_python_tasks/git.py,sha256=XdDkrw26B4oJZjNoKMWURFlGwKK3_uBiujXkF6rmVM0,11509
|
|
7
|
+
common_python_tasks/github.py,sha256=_LVanl-z4PsdLM94L8y7tkX-AZO2n3Qy39FRY7reDhA,21589
|
|
8
|
+
common_python_tasks/project.py,sha256=Kz2oZjHPqwRKzIkV4KuOmBzB2wUapaVrUZznQL4FN9c,17128
|
|
9
|
+
common_python_tasks/tasks.py,sha256=HIKDaSC8xR4xi35cGxDm_CmAqjjulh0VQtoiIwUNJ3U,58840
|
|
10
|
+
common_python_tasks/utils.py,sha256=cmplbay7ElSD41qqQrTT5112CtuUkW-AN8o3iTLulBM,20653
|
|
11
|
+
common_python_tasks/data/dockerfile_extensions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
common_python_tasks/data/fastapi/alembic.ini.j2,sha256=dOiAmZngzX6eyVXeM6BhzgQmes2zwUEdKwOgm8mxjMs,985
|
|
13
|
+
common_python_tasks/data/fastapi/compose-base.yml.j2,sha256=vDLPAdRJ66lsWZdsKbYqEvY_liXaHZ0ly4ljzqmJsyY,910
|
|
14
|
+
common_python_tasks/data/fastapi/compose-db-debug.yml.j2,sha256=iREdSy4F8tJ5m9rlB6CROBkj9OtDFVuGphqIuDvHhww,1193
|
|
15
|
+
common_python_tasks/data/fastapi/compose-db.yml.j2,sha256=16i2rrsYPIJIQn4gpopr5-8KBirMS7OwpXHPaAC03k8,1777
|
|
16
|
+
common_python_tasks/data/fastapi/compose-debug.yml.j2,sha256=sIH9IFvmRAszN6wSejpGVQygXh1g2lVmlzOgPhTx5CY,661
|
|
17
|
+
common_python_tasks/data/generic/.coveragerc,sha256=8zXwDq18wIKQV4NtW2NJYpo3nxCO4LsAChyluYTUoJc,85
|
|
18
|
+
common_python_tasks/data/generic/.dockerignore,sha256=NFTre70gSZ6yi3qg5jxyvcnj3G7FazXLgi--6fOJfQk,65
|
|
19
|
+
common_python_tasks/data/generic/Dockerfile.deps.j2,sha256=rCGsBwLHQIRsGFzDbiRi_nWc-SZ3MAFZ_BeZJFV28Ck,432
|
|
20
|
+
common_python_tasks/data/generic/Dockerfile.j2,sha256=l5PRPWg3R4mkLMb_WEQ_f8pLLSYLH-Y2fWIgy5wSCvQ,5620
|
|
21
|
+
common_python_tasks/data/generic/cliff.toml,sha256=po1MnEGEEgrpyBz3XLWr0h7emXb6ASX63lrU2r6o240,77
|
|
22
|
+
common_python_tasks/data/generic/pytest.ini,sha256=RHak5QrLzKHvUBjFmaaiOYFrd_8YBRQubXEOT0NJfco,134
|
|
23
|
+
common_python_tasks/data/generic/ruff.toml,sha256=njvd-5inSGmgY865SWCT4SR9IrKlzS7JQFZmAG2UT_E,53
|
|
24
|
+
common_python_tasks-0.9.0.dist-info/METADATA,sha256=XpzIcJPOkek42mCbOnlPJb1Iw5IqLQAsYaQk2LaHe40,21049
|
|
25
|
+
common_python_tasks-0.9.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
26
|
+
common_python_tasks-0.9.0.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
|
|
27
|
+
common_python_tasks-0.9.0.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
common_python_tasks/__init__.py,sha256=HIumMQ0gSD2sVp6efGAILcQJVIUGiasNEij2Nrt3H7k,847
|
|
2
|
-
common_python_tasks/__main__.py,sha256=GpgSd_toMbEaWA_cZvgsZE7jo4XpzuI-awBVm2uhVyw,3512
|
|
3
|
-
common_python_tasks/docker.py,sha256=T1uGH5FqjypkZ3qml83TNW46ax4gJLgZHB-L5P6-4Us,25101
|
|
4
|
-
common_python_tasks/docker_compose.py,sha256=i0ebx615-rASwESVd75uwhveiPp40nLub8gPUfSCdrs,21391
|
|
5
|
-
common_python_tasks/env.py,sha256=BQeRrFBeXMFUK_4Z7cUKprJdXBZn688EHfWrhbm0WD4,16819
|
|
6
|
-
common_python_tasks/git.py,sha256=XdDkrw26B4oJZjNoKMWURFlGwKK3_uBiujXkF6rmVM0,11509
|
|
7
|
-
common_python_tasks/github.py,sha256=khaGDvhilxCklMoWGoraofeKRngrW0pCD6bfO7zj7O0,20892
|
|
8
|
-
common_python_tasks/project.py,sha256=8fHa5nudToSUP8gC1XUaDUKN3x34GVmYUzkXtHTq5Y8,22482
|
|
9
|
-
common_python_tasks/tasks.py,sha256=_jIzHJy45g949RON1sNbyqG8lG-o6Tx-aKLnRHOFxEk,59132
|
|
10
|
-
common_python_tasks/utils.py,sha256=cmplbay7ElSD41qqQrTT5112CtuUkW-AN8o3iTLulBM,20653
|
|
11
|
-
common_python_tasks/data/dockerfile_extensions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
common_python_tasks/data/fastapi/alembic.ini.j2,sha256=dOiAmZngzX6eyVXeM6BhzgQmes2zwUEdKwOgm8mxjMs,985
|
|
13
|
-
common_python_tasks/data/fastapi/compose-base.yml.j2,sha256=JtXilJ0RfdW5PXSF2xDFRxE9-gUcanJBgHWvKdGJWEE,974
|
|
14
|
-
common_python_tasks/data/fastapi/compose-db-debug.yml.j2,sha256=iREdSy4F8tJ5m9rlB6CROBkj9OtDFVuGphqIuDvHhww,1193
|
|
15
|
-
common_python_tasks/data/fastapi/compose-db.yml.j2,sha256=FEWgBlebzkgdaaf98xAzDLqk_VGgsYNRF82onqRUhJw,1841
|
|
16
|
-
common_python_tasks/data/fastapi/compose-debug.yml.j2,sha256=6397JFObue-YrweFDPyFg4anwe7H6XaMElRqyBee2Q0,665
|
|
17
|
-
common_python_tasks/data/generic/.coveragerc,sha256=8zXwDq18wIKQV4NtW2NJYpo3nxCO4LsAChyluYTUoJc,85
|
|
18
|
-
common_python_tasks/data/generic/.dockerignore,sha256=K1hgsu8R2Myq6vPIgL86Cvw9aKxly9HfVbcTr3IdKyY,69
|
|
19
|
-
common_python_tasks/data/generic/Dockerfile.deps.j2,sha256=rCGsBwLHQIRsGFzDbiRi_nWc-SZ3MAFZ_BeZJFV28Ck,432
|
|
20
|
-
common_python_tasks/data/generic/Dockerfile.j2,sha256=Zj48rM2XFvlXxY7KcLx1mLRZX_4pza7MbuEN97jlFHQ,7313
|
|
21
|
-
common_python_tasks/data/generic/cliff.toml,sha256=po1MnEGEEgrpyBz3XLWr0h7emXb6ASX63lrU2r6o240,77
|
|
22
|
-
common_python_tasks/data/generic/pytest.ini,sha256=RHak5QrLzKHvUBjFmaaiOYFrd_8YBRQubXEOT0NJfco,134
|
|
23
|
-
common_python_tasks/data/generic/ruff.toml,sha256=m7oPo_t8_D10dXPd5504xay6BEkekauK-oCV4XPJW7g,64
|
|
24
|
-
common_python_tasks-0.8.0.dist-info/METADATA,sha256=XOxRbLqZEDhoIljJdI83Rv00tVDNOznrV5WXN-XUk1g,20670
|
|
25
|
-
common_python_tasks-0.8.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
26
|
-
common_python_tasks-0.8.0.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
|
|
27
|
-
common_python_tasks-0.8.0.dist-info/RECORD,,
|
|
File without changes
|
{common_python_tasks-0.8.0.dist-info → common_python_tasks-0.9.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|