common-python-tasks 0.4.0__py3-none-any.whl → 0.4.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.
@@ -9,7 +9,8 @@ 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
- POETRY_VERSION: ${POETRY_VERSION}
12
+ PACKAGE_MANAGER: ${PACKAGE_MANAGER}
13
+ PACKAGE_MANAGER_VERSION: ${PACKAGE_MANAGER_VERSION}
13
14
 
14
15
  services:
15
16
  api:
@@ -8,7 +8,8 @@ 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
- POETRY_VERSION: ${POETRY_VERSION}
11
+ PACKAGE_MANAGER: ${PACKAGE_MANAGER}
12
+ PACKAGE_MANAGER_VERSION: ${PACKAGE_MANAGER_VERSION}
12
13
  WORKDIR_PATH: ${WORKDIR_PATH}
13
14
 
14
15
  services:
@@ -7,24 +7,48 @@ 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') %}
10
11
 
11
12
  FROM python:${PYTHON_VERSION} AS builder
12
13
 
14
+ ARG PACKAGE_VERSION
15
+
16
+ {% if package_manager == 'poetry' %}
13
17
  ARG POETRY_VERSION
14
18
  ARG POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION
15
19
  ARG POETRY_PLUGIN_EXPORT_VERSION
16
- ARG PACKAGE_VERSION
17
20
 
18
21
  # Bypass VCS-based version detection: use the host-computed version directly.
19
22
  # This avoids requiring a .git directory in the build context and prevents
20
23
  # false dirty-version detection from a partial working tree.
21
24
  ENV POETRY_DYNAMIC_VERSIONING_BYPASS=${PACKAGE_VERSION}
22
25
 
23
- # Install Poetry and required Poetry plugins for build
24
- RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} pip install --root-user-action=ignore \
25
- "poetry==$POETRY_VERSION" \
26
- "poetry-dynamic-versioning[plugin]==$POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION" \
27
- "poetry-plugin-export==$POETRY_PLUGIN_EXPORT_VERSION"
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
+ ARG UV_VERSION
38
+
39
+ # Bypass VCS-based version detection: use the host-computed version directly.
40
+ # This avoids requiring a .git directory in the build context and prevents
41
+ # false dirty-version detection from a partial working tree.
42
+ ENV UV_DYNAMIC_VERSIONING_BYPASS=${PACKAGE_VERSION}
43
+
44
+ # Install uv for package build and dependency export steps
45
+ RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} \
46
+ sh -c 'if [ -n "${UV_VERSION:-}" ]; then \
47
+ pip install --root-user-action=ignore "uv==$UV_VERSION"; \
48
+ else \
49
+ pip install --root-user-action=ignore uv; \
50
+ fi'
51
+ {% endif %}
28
52
 
29
53
  # Build package
30
54
  WORKDIR /tmp/build
@@ -32,15 +56,24 @@ COPY . /tmp/build/
32
56
 
33
57
  {% if HAS_DEBUG_DEPS %}
34
58
  # Export debug requirements when the project defines a debug dependency group
59
+ {% if package_manager == 'poetry' %}
35
60
  RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} \
36
61
  poetry export --only debug --without-hashes -f requirements.txt --output requirements-debug.txt
62
+ {% else %}
63
+ RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache{{ CACHE_ID_SUFFIX }} \
64
+ uv export --group debug --no-hashes --format requirements-txt --output-file requirements-debug.txt
65
+ {% endif %}
37
66
  {% endif %}
38
67
 
39
- # Build the wheel, caching Poetry's cache directory to speed up subsequent builds.
68
+ # Build the wheel, caching the package-manager cache directory to speed up subsequent builds.
40
69
  # If running this in a CI system, you must be using a persistent builder to take
41
70
  # advantage of the cache mount, and you should configure your CI to persist the
42
71
  # cache directory between builds
72
+ {% if package_manager == 'poetry' %}
43
73
  RUN --mount=type=cache,target=/root/.cache/pypoetry,id=pypoetry-cache{{ CACHE_ID_SUFFIX }} poetry build --format=wheel
74
+ {% else %}
75
+ RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache{{ CACHE_ID_SUFFIX }} uv build --wheel
76
+ {% endif %}
44
77
 
45
78
  FROM python:${PYTHON_VERSION}-${PYTHON_VARIANT} AS runtime
46
79
  ARG WORKDIR_PATH=/workspace
@@ -93,7 +93,6 @@ def _safe_unlink(path: Path | str | None) -> None:
93
93
 
94
94
 
95
95
  def _build_docker_build_command(
96
- *,
97
96
  context_path: Path,
98
97
  dockerfile_path: Path,
99
98
  build_args: dict[str, str],
@@ -331,12 +330,12 @@ def build_deps_image(
331
330
 
332
331
  full_tag = f"{deps_image_name}:{deps_tag}"
333
332
  build_cmd = _build_docker_build_command(
334
- context_path=context_path,
335
- dockerfile_path=dockerfile_path,
336
- build_args=build_args,
337
- archs=archs,
338
- no_cache=no_cache,
339
- plain=plain,
333
+ context_path,
334
+ dockerfile_path,
335
+ build_args,
336
+ archs,
337
+ no_cache,
338
+ plain,
340
339
  tags=[full_tag],
341
340
  )
342
341
 
@@ -451,24 +450,37 @@ def build_image(
451
450
  commit_tag = version_tag
452
451
 
453
452
  python_version = platform.python_version()
454
- poetry_version = project.get_poetry_version()
453
+ package_manager = project.get_package_manager()
454
+ package_manager_version = project.get_package_manager_version(package_manager)
455
455
 
456
- build_args = _merge_build_args(
457
- {
458
- "PYTHON_VERSION": python_version,
459
- "POETRY_VERSION": poetry_version,
456
+ manager_build_args: dict[str, str | None]
457
+ if package_manager == project.PackageManager.POETRY:
458
+ manager_build_args = {
459
+ "POETRY_VERSION": package_manager_version,
460
460
  "POETRY_DYNAMIC_VERSIONING_PLUGIN_VERSION": project.get_installed_requirement_version(
461
461
  "poetry-dynamic-versioning[plugin]"
462
462
  ),
463
463
  "POETRY_PLUGIN_EXPORT_VERSION": project.get_installed_requirement_version(
464
464
  "poetry-plugin-export"
465
465
  ),
466
+ }
467
+ else:
468
+ manager_build_args = {
469
+ "UV_VERSION": package_manager_version,
470
+ }
471
+
472
+ build_args = _merge_build_args(
473
+ {
474
+ "PYTHON_VERSION": python_version,
475
+ "PACKAGE_MANAGER": package_manager,
476
+ "PACKAGE_MANAGER_VERSION": package_manager_version,
466
477
  "PACKAGE_VERSION": package_version,
467
478
  "PACKAGE_NAME": utils.get_package_name(use_underscores=True),
468
479
  "AUTHORS": ",".join(
469
480
  [f"{name} <{email}>" for name, email in project.get_authors()]
470
481
  ),
471
482
  "GIT_COMMIT": commit_tag,
483
+ **manager_build_args,
472
484
  },
473
485
  extra_build_args,
474
486
  )
@@ -503,12 +515,12 @@ def build_image(
503
515
  all_tags.append(resolved_tag)
504
516
 
505
517
  build_cmd = _build_docker_build_command(
506
- context_path=context_path,
507
- dockerfile_path=dockerfile_path,
508
- build_args=build_args,
509
- archs=archs,
510
- no_cache=no_cache,
511
- plain=plain,
518
+ context_path,
519
+ dockerfile_path,
520
+ build_args,
521
+ archs,
522
+ no_cache,
523
+ plain,
512
524
  target=None if omit_target else target,
513
525
  tags=all_tags,
514
526
  )
@@ -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 get_poetry_version
17
+ from .project import get_package_manager, get_package_manager_version
18
18
 
19
19
  LOGGER = logging.getLogger(__name__)
20
20
 
@@ -232,7 +232,8 @@ _COMPOSE_VAR_REQUIREMENTS: dict[str, dict[str, set[str]]] = {
232
232
  "ENVIRONMENT",
233
233
  "IMAGE_TAG",
234
234
  "PYTHON_VERSION",
235
- "POETRY_VERSION",
235
+ "PACKAGE_MANAGER",
236
+ "PACKAGE_MANAGER_VERSION",
236
237
  },
237
238
  "compose-db": {
238
239
  "PACKAGE_NAME",
@@ -242,7 +243,8 @@ _COMPOSE_VAR_REQUIREMENTS: dict[str, dict[str, set[str]]] = {
242
243
  "DB_PORT",
243
244
  "IMAGE_TAG",
244
245
  "PYTHON_VERSION",
245
- "POETRY_VERSION",
246
+ "PACKAGE_MANAGER",
247
+ "PACKAGE_MANAGER_VERSION",
246
248
  "POSTGRES_VERSION",
247
249
  "WORKDIR_PATH",
248
250
  },
@@ -328,9 +330,10 @@ def get_compose_env(
328
330
  "ENVIRONMENT": os.getenv("ENVIRONMENT", "production"),
329
331
  "IMAGE_TAG": image_tag or "latest",
330
332
  "PACKAGE_NAME": package_name,
333
+ "PACKAGE_MANAGER": get_package_manager(),
334
+ "PACKAGE_MANAGER_VERSION": get_package_manager_version(),
331
335
  "PACKAGE_UNDERSCORE_NAME": utils.package_name_to_underscore(package_name),
332
336
  "WORKDIR_PATH": get_workdir_path(),
333
- "POETRY_VERSION": get_poetry_version(),
334
337
  "POSTGRES_VERSION": os.getenv("POSTGRES_VERSION", "17"),
335
338
  "PYTHON_VERSION": platform.python_version(),
336
339
  "SECRET_KEY": os.getenv("SECRET_KEY", ""),
@@ -96,7 +96,7 @@ def compute_next_release_version(
96
96
  Returns:
97
97
  A `NextReleaseVersion` with the serialized new version and normalized component.
98
98
  """
99
- current_version_text = project.get_project_version_from_poetry()
99
+ current_version_text = project.get_project_version()
100
100
  release_component = (
101
101
  infer_bump_component_from_git_cliff() if component is None else component
102
102
  )
@@ -181,14 +181,11 @@ def infer_bump_component_from_git_cliff() -> ReleaseComponent:
181
181
  except Exception:
182
182
  utils.fatal(f"Unable to parse bumped version from git-cliff: {version_text!r}")
183
183
 
184
- current_version = project.get_project_version_from_poetry()
184
+ current_version = project.get_project_version()
185
185
  try:
186
186
  current = Version.parse(current_version)
187
187
  except Exception:
188
- utils.fatal(
189
- "Unable to parse current version from Poetry output: "
190
- f"{current_version!r}"
191
- )
188
+ utils.fatal("Unable to parse current project version: " f"{current_version!r}")
192
189
 
193
190
  def _parse_semver(version_value: str | Any) -> tuple[int, int, int]:
194
191
  if not isinstance(version_value, str):
@@ -1,7 +1,10 @@
1
1
  import ast
2
2
  import logging
3
+ import os
3
4
  import re
5
+ import shutil
4
6
  import tomllib
7
+ from enum import StrEnum, auto
5
8
  from functools import lru_cache
6
9
  from importlib import metadata
7
10
  from pathlib import Path
@@ -14,6 +17,170 @@ from . import utils
14
17
 
15
18
  LOGGER = logging.getLogger(__name__)
16
19
 
20
+ PACKAGE_MANAGER_ENV_VAR = "COMMON_PYTHON_TASKS_PACKAGE_MANAGER"
21
+
22
+
23
+ class PackageManager(StrEnum):
24
+ """Supported package-manager backends for build and publish workflows."""
25
+
26
+ POETRY = auto()
27
+ UV = auto()
28
+ AUTO = auto()
29
+
30
+
31
+ def _parse_tool_version_output(output: str, tool_name: str) -> str:
32
+ cleaned_output = output.strip()
33
+ if not cleaned_output:
34
+ utils.fatal(f"Unable to determine {tool_name} version from empty output")
35
+
36
+ match = re.search(r"(\d+\.\d+\.\d+(?:[-+._a-zA-Z0-9]*)?)", cleaned_output)
37
+ if match is not None:
38
+ return match.group(1)
39
+
40
+ tokens = cleaned_output.replace("(", " ").replace(")", " ").split()
41
+ if tokens:
42
+ return tokens[-1]
43
+
44
+ utils.fatal(f"Unable to parse {tool_name} version output: {output!r}")
45
+
46
+
47
+ def _find_package_manager_override() -> tuple[str, str] | None:
48
+ value = os.getenv(PACKAGE_MANAGER_ENV_VAR)
49
+ if value is not None and value.strip():
50
+ return PACKAGE_MANAGER_ENV_VAR, value.strip().lower()
51
+ return None
52
+
53
+
54
+ def _ensure_package_manager_available(package_manager: PackageManager) -> None:
55
+ if shutil.which(package_manager) is None:
56
+ utils.fatal(
57
+ f"Requested package manager '{package_manager}' is not installed or not in PATH"
58
+ )
59
+
60
+
61
+ def _detect_declared_package_manager(
62
+ pyproject_data: dict[str, Any],
63
+ ) -> PackageManager | None:
64
+ build_backend = (
65
+ pyproject_data.get("build-system", {}).get("build-backend", "") or ""
66
+ )
67
+ if isinstance(build_backend, str) and "poetry" in build_backend.lower():
68
+ return PackageManager.POETRY
69
+
70
+ if pyproject_data.get("tool", {}).get("poetry") is not None:
71
+ return PackageManager.POETRY
72
+
73
+ if pyproject_data.get("tool", {}).get("uv") is not None:
74
+ return PackageManager.UV
75
+
76
+ return None
77
+
78
+
79
+ def _resolve_package_manager_auto() -> tuple[PackageManager, str]:
80
+ has_poetry = shutil.which("poetry") is not None
81
+ has_uv = shutil.which("uv") is not None
82
+
83
+ if has_uv and not has_poetry:
84
+ return PackageManager.UV, "uv found and poetry missing"
85
+
86
+ if has_poetry and not has_uv:
87
+ return PackageManager.POETRY, "poetry found and uv missing"
88
+
89
+ if not has_poetry and not has_uv:
90
+ utils.fatal(
91
+ "No supported package manager found in PATH. Install poetry or uv, "
92
+ "or set one via COMMON_PYTHON_TASKS_PACKAGE_MANAGER."
93
+ )
94
+
95
+ pyproject_data = read_pyproject_toml()
96
+ declared_manager = _detect_declared_package_manager(pyproject_data)
97
+ if declared_manager is not None:
98
+ return declared_manager, "project metadata"
99
+
100
+ return PackageManager.POETRY, "default fallback"
101
+
102
+
103
+ @lru_cache
104
+ def get_package_manager() -> PackageManager:
105
+ """Resolve the package manager backend used for build and publish commands.
106
+
107
+ Resolution order:
108
+ 1. Environment override via `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`.
109
+ 2. Auto-discovery from installed executables and project metadata.
110
+
111
+ Supported values are `poetry`, `uv`, and `auto`.
112
+
113
+ Returns:
114
+ The selected package manager backend.
115
+ """
116
+ override = _find_package_manager_override()
117
+ if override is None:
118
+ package_manager, reason = _resolve_package_manager_auto()
119
+ LOGGER.info(
120
+ "Using package manager backend: %s (%s)",
121
+ package_manager,
122
+ reason,
123
+ )
124
+ return package_manager
125
+
126
+ env_var, raw_value = override
127
+ if raw_value not in PackageManager:
128
+ utils.fatal(
129
+ f"Invalid {env_var} value {raw_value!r}. " "Use one of: poetry, uv, auto."
130
+ )
131
+
132
+ if raw_value == PackageManager.AUTO:
133
+ package_manager, reason = _resolve_package_manager_auto()
134
+ LOGGER.info(
135
+ "Using package manager backend: %s (%s via %s=%s)",
136
+ package_manager,
137
+ reason,
138
+ env_var,
139
+ raw_value,
140
+ )
141
+ return package_manager
142
+
143
+ package_manager = PackageManager(raw_value)
144
+ _ensure_package_manager_available(package_manager)
145
+ LOGGER.info(
146
+ "Using package manager backend: %s (from %s)",
147
+ package_manager,
148
+ env_var,
149
+ )
150
+ return package_manager
151
+
152
+
153
+ def get_package_manager_build_command(wheel_only: bool = False) -> list[str]:
154
+ """Return the build command for the selected package manager.
155
+
156
+ Args:
157
+ wheel_only: Whether to build only a wheel artifact.
158
+
159
+ Returns:
160
+ Command tokens for the package build command.
161
+ """
162
+ package_manager = PackageManager(get_package_manager())
163
+ if package_manager == PackageManager.UV:
164
+ command = ["uv", "build"]
165
+ if wheel_only:
166
+ command.append("--wheel")
167
+ return command
168
+
169
+ command = ["poetry", "build"]
170
+ if wheel_only:
171
+ command += ["--format", "wheel"]
172
+ return command
173
+
174
+
175
+ def get_package_manager_publish_command() -> list[str]:
176
+ """Return the publish command for the selected package manager.
177
+
178
+ Returns:
179
+ Command tokens for the package publish command.
180
+ """
181
+ package_manager = PackageManager(get_package_manager())
182
+ return [package_manager.value, "publish"]
183
+
17
184
 
18
185
  @lru_cache
19
186
  def get_authors() -> list[tuple[str, str]]:
@@ -77,11 +244,31 @@ def get_poetry_version() -> str:
77
244
  Returns:
78
245
  The Poetry version parsed from the output of `poetry --version`.
79
246
  """
80
- return (
81
- utils.run_command(["poetry", "--version"], capture_output=True)
82
- .stdout.strip()
83
- .split()[-1]
84
- )[0:-1]
247
+ return _parse_tool_version_output(
248
+ utils.run_command(["poetry", "--version"], capture_output=True).stdout,
249
+ tool_name="poetry",
250
+ )
251
+
252
+
253
+ def get_package_manager_version(package_manager: PackageManager | None = None) -> str:
254
+ """Return the version string for the selected package manager.
255
+
256
+ Args:
257
+ package_manager: Optional explicit package manager backend.
258
+
259
+ Returns:
260
+ Parsed package-manager version.
261
+ """
262
+ selected_manager = PackageManager(package_manager or get_package_manager())
263
+ if selected_manager == PackageManager.POETRY:
264
+ return get_poetry_version()
265
+
266
+ return _parse_tool_version_output(
267
+ utils.run_command(
268
+ [selected_manager.value, "--version"], capture_output=True
269
+ ).stdout,
270
+ tool_name=selected_manager.value,
271
+ )
85
272
 
86
273
 
87
274
  @lru_cache
@@ -94,8 +281,8 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
94
281
 
95
282
  It normalizes extras like `package[extra]` and tries common name variants
96
283
  with hyphens and underscores. If the package is not available to
97
- importlib.metadata, it falls back to `poetry show` and then
98
- `poetry self show`.
284
+ importlib.metadata, it falls back to package-manager runtime and package
285
+ listing commands.
99
286
 
100
287
  Args:
101
288
  requirement_name: The requirement name to lookup.
@@ -103,8 +290,6 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
103
290
  Returns:
104
291
  The installed version string, or `None` if the package cannot be resolved.
105
292
  """
106
- package_name = requirement_name.split("[", 1)[0]
107
-
108
293
  package_name = requirement_name.split("[", 1)[0]
109
294
  candidates = {
110
295
  package_name,
@@ -118,10 +303,12 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
118
303
  except metadata.PackageNotFoundError:
119
304
  pass
120
305
 
121
- def _query_poetry_runtime_version(candidate: str) -> str | None:
306
+ package_manager = PackageManager(get_package_manager())
307
+
308
+ def _query_runtime_version(candidate: str) -> str | None:
122
309
  result = utils.run_command(
123
310
  [
124
- "poetry",
311
+ package_manager,
125
312
  "run",
126
313
  "python",
127
314
  "-c",
@@ -139,35 +326,39 @@ def get_installed_requirement_version(requirement_name: str) -> str | None:
139
326
  return version
140
327
  return None
141
328
 
142
- version = get_local_poetry_plugin_version(package_name)
143
- if version:
144
- return version
329
+ if package_manager == PackageManager.POETRY:
330
+ version = get_local_poetry_plugin_version(package_name)
331
+ if version:
332
+ return version
145
333
 
146
334
  for candidate in candidates:
147
- version = _query_poetry_runtime_version(candidate)
335
+ version = _query_runtime_version(candidate)
148
336
  if version:
149
337
  return version
150
338
 
151
- def _parse_poetry_show_version(output: str) -> str | None:
339
+ def _parse_package_show_version(output: str) -> str | None:
152
340
  for line in output.splitlines():
153
341
  if ":" not in line:
154
342
  continue
155
343
  key, value = line.split(":", 1)
156
- if key.strip() == "version":
344
+ if key.strip().lower() == "version":
157
345
  return value.strip()
158
346
  return None
159
347
 
160
- for cmd in (
161
- ["poetry", "show", package_name],
162
- ["poetry", "self", "show", package_name],
163
- ):
348
+ show_commands = (
349
+ (["poetry", "show", package_name], ["poetry", "self", "show", package_name])
350
+ if package_manager == PackageManager.POETRY
351
+ else (["uv", "pip", "show", package_name],)
352
+ )
353
+
354
+ for cmd in show_commands:
164
355
  result = utils.run_command(
165
- cmd,
356
+ list(cmd),
166
357
  capture_output=True,
167
358
  acceptable_returncodes={0, 1},
168
359
  )
169
360
  if result.returncode == 0:
170
- version = _parse_poetry_show_version(result.stdout)
361
+ version = _parse_package_show_version(result.stdout)
171
362
  if version:
172
363
  return version
173
364
  return None
@@ -244,7 +435,7 @@ def resolve_container_entrypoint_command(custom_entrypoint: str | None = None) -
244
435
 
245
436
 
246
437
  def _extract_tags_from_script_argument(
247
- script_value: str, *, script_key: str, argument_key: str
438
+ script_value: str, script_key: str, argument_key: str
248
439
  ) -> set[str] | None:
249
440
  pattern = re.compile(rf"{re.escape(argument_key)}\s*=\s*(\[[^\]]*\])")
250
441
  match = pattern.search(script_value)
@@ -358,10 +549,80 @@ def get_project_version_from_poetry() -> str:
358
549
  return parts[1].strip()
359
550
 
360
551
 
552
+ def _get_project_version_from_pyproject() -> str | None:
553
+ """Return `project.version` from pyproject metadata when present.
554
+
555
+ Returns:
556
+ The configured `project.version`, or `None` if missing.
557
+ """
558
+ version = read_pyproject_toml().get("project", {}).get("version")
559
+ if isinstance(version, str) and version.strip():
560
+ return version.strip()
561
+ return None
562
+
563
+
564
+ def get_project_version_from_vcs() -> str | None:
565
+ """Return a PEP 440 version derived from VCS tags.
566
+
567
+ Returns:
568
+ A version string when git metadata is available; otherwise `None`.
569
+ """
570
+ try:
571
+ from dunamai import Style, Version
572
+
573
+ return Version.from_git().serialize(style=Style.Pep440, dirty=False)
574
+ except Exception:
575
+ return None
576
+
577
+
578
+ def get_project_version() -> str:
579
+ """Return the current project version for release and tagging workflows.
580
+
581
+ For Poetry projects, this uses `poetry version` to preserve
582
+ poetry-dynamic-versioning behavior. For uv workflows, it prefers
583
+ `project.version` and falls back to VCS-derived versioning.
584
+
585
+ Returns:
586
+ The resolved current project version.
587
+ """
588
+ package_manager = PackageManager(get_package_manager())
589
+ if package_manager == PackageManager.POETRY:
590
+ return get_project_version_from_poetry()
591
+
592
+ pyproject_version = _get_project_version_from_pyproject()
593
+ if pyproject_version and pyproject_version not in {"0.0.0", "0.0.0.dev0"}:
594
+ return pyproject_version
595
+
596
+ vcs_version = get_project_version_from_vcs()
597
+ if vcs_version is not None:
598
+ return vcs_version
599
+
600
+ if pyproject_version:
601
+ LOGGER.warning(
602
+ "Using placeholder project.version=%s because VCS version could not be resolved",
603
+ pyproject_version,
604
+ )
605
+ return pyproject_version
606
+
607
+ utils.fatal("Unable to determine project version from pyproject metadata or VCS")
608
+
609
+
610
+ def get_release_tag_from_project_version() -> str:
611
+ """Return release tag string in `v<version>` form.
612
+
613
+ Returns:
614
+ Release tag prefixed with `v`.
615
+ """
616
+ return f"v{get_project_version()}"
617
+
618
+
361
619
  def get_release_tag_from_poetry_version() -> str:
362
- """Return release tag string in `v<version>` form from Poetry metadata.
620
+ """Return release tag string in `v<version>` form.
621
+
622
+ This function is retained for backward compatibility and now uses the
623
+ selected package-manager backend.
363
624
 
364
625
  Returns:
365
626
  The release tag string prefixed with `v`.
366
627
  """
367
- return f"v{get_project_version_from_poetry()}"
628
+ return get_release_tag_from_project_version()
@@ -5,6 +5,7 @@ import os
5
5
  from functools import partial, wraps
6
6
  from pathlib import Path
7
7
 
8
+ from common_python_tasks.utils import confirm
8
9
  from poethepoet_tasks import TaskCollection
9
10
 
10
11
 
@@ -116,7 +117,6 @@ def _wrap_task_function(func: callable, task_name: str | None) -> callable:
116
117
 
117
118
  def _script(
118
119
  func=None,
119
- *,
120
120
  task_name: str | None = None,
121
121
  help: str | None = None,
122
122
  task_args: bool = True,
@@ -158,28 +158,13 @@ def _should_update_release_changelog() -> bool:
158
158
  return env_truthy("RELEASE_UPDATE_CHANGELOG")
159
159
 
160
160
 
161
- def _confirm(message: str) -> bool:
162
- yes_values = {"y", "yes"}
163
- no_values = {"n", "no"}
164
- while True:
165
- try:
166
- response = input(f"{message} [y/N]: ").strip().lower()
167
- except (EOFError, OSError):
168
- return False
169
-
170
- if response in yes_values:
171
- return True
172
- if response in no_values:
173
- return False
174
-
175
-
176
161
  def _confirm_release_with_uncommitted_changes(dirty_files: list[Path]) -> bool:
177
162
  preview = ", ".join(str(path) for path in dirty_files[:5])
178
163
  if len(dirty_files) > 5:
179
164
  preview = f"{preview}, ... and {len(dirty_files) - 5} more"
180
165
 
181
166
  LOGGER.warning("Repository has uncommitted changes: %s", preview)
182
- return _confirm("Proceed with release anyway?")
167
+ return confirm("Proceed with release anyway?")
183
168
 
184
169
 
185
170
  def _resolve_allow_dirty_release(dirty_files: list[Path]) -> bool:
@@ -560,6 +545,7 @@ def build_image(
560
545
  resolve_extension_content,
561
546
  )
562
547
  from .project import (
548
+ get_package_manager,
563
549
  has_debug_dependency_group,
564
550
  resolve_container_entrypoint_command,
565
551
  )
@@ -695,6 +681,7 @@ def build_image(
695
681
  dockerfile_text = render_template_text(
696
682
  load_data_file("Dockerfile.j2")[1],
697
683
  {
684
+ "PACKAGE_MANAGER": get_package_manager(),
698
685
  "EXTENSION_CONTENT": combined_content,
699
686
  "DEPS_IMAGE": deps_image_tag,
700
687
  "CONTAINER_DEPS_MOVE_SCRIPT": container_deps_move_script,
@@ -783,7 +770,6 @@ def build_deps_image_task(
783
770
  @tasks.script(tags=["containers"])
784
771
  def run_container(
785
772
  tag: str | None = None,
786
- *,
787
773
  entrypoint: str | None = None,
788
774
  command: str | None = None,
789
775
  root: bool = False,
@@ -976,11 +962,12 @@ def publish_package(build_first: bool = True) -> None:
976
962
  Args:
977
963
  build_first: If `True`, build the package before publishing.
978
964
  """
965
+ from .project import get_package_manager_publish_command
979
966
  from .utils import run_command
980
967
 
981
968
  if build_first:
982
969
  build_package(clean_dist=True)
983
- run_command(["poetry", "publish"])
970
+ run_command(get_package_manager_publish_command())
984
971
 
985
972
 
986
973
  @tasks.script(
@@ -988,7 +975,6 @@ def publish_package(build_first: bool = True) -> None:
988
975
  )
989
976
  def publish_github_release(
990
977
  tag_name: str | None = None,
991
- *,
992
978
  release_name: str | None = None,
993
979
  body: str | None = None,
994
980
  prerelease: bool = False,
@@ -1001,12 +987,12 @@ def publish_github_release(
1001
987
  get_github_release_asset_paths,
1002
988
  )
1003
989
  from .github import publish_github_release as publish_github_release_helper
1004
- from .project import get_release_tag_from_poetry_version
990
+ from .project import get_release_tag_from_project_version
1005
991
  from .utils import fatal
1006
992
 
1007
993
  resolved_tag_name = tag_name or os.getenv("GITHUB_RELEASE_TAG")
1008
994
  if resolved_tag_name is None:
1009
- resolved_tag_name = get_release_tag_from_poetry_version()
995
+ resolved_tag_name = get_release_tag_from_project_version()
1010
996
  if not resolved_tag_name:
1011
997
  fatal("Unable to determine the release tag for GitHub Release publication")
1012
998
 
@@ -1029,11 +1015,10 @@ def publish_github_release(
1029
1015
  @tasks.script(task_name="build-package", tags=["packaging", "build", "common"])
1030
1016
  def build_package(wheel_only: bool = False, clean_dist: bool = False) -> None:
1031
1017
  """Build the package (wheel and sdist)."""
1018
+ from .project import get_package_manager_build_command
1032
1019
  from .utils import run_command
1033
1020
 
1034
- command = ["poetry", "build"]
1035
- if wheel_only:
1036
- command += ["--format", "wheel"]
1021
+ command = get_package_manager_build_command(wheel_only=wheel_only)
1037
1022
  if clean_dist:
1038
1023
  dist_path = Path("dist")
1039
1024
  if dist_path.exists() and any(dist_path.iterdir()):
@@ -1044,7 +1029,6 @@ def build_package(wheel_only: bool = False, clean_dist: bool = False) -> None:
1044
1029
  @tasks.script(tags=["packaging", "common"])
1045
1030
  def bump_version(
1046
1031
  component: str = "auto",
1047
- *,
1048
1032
  stage: str | None = None,
1049
1033
  dry_run: bool = False,
1050
1034
  allow_dirty: bool = False,
@@ -1106,10 +1090,9 @@ def changelog() -> None:
1106
1090
 
1107
1091
  def _run_release_flow(
1108
1092
  component: str = "auto",
1109
- *,
1110
1093
  stage: str | None = None,
1111
1094
  dry_run: bool = False,
1112
- include_containers: bool,
1095
+ include_containers: bool = False,
1113
1096
  debug: bool = False,
1114
1097
  no_cache: bool = False,
1115
1098
  plain: bool = False,
@@ -1224,7 +1207,6 @@ def _run_release_flow(
1224
1207
  @tasks.script(task_name="release", tags=["packaging", "release", "containers"])
1225
1208
  def release(
1226
1209
  component: str = "auto",
1227
- *,
1228
1210
  stage: str | None = None,
1229
1211
  dry_run: bool = False,
1230
1212
  debug: bool = False,
@@ -1278,7 +1260,6 @@ def release(
1278
1260
  @tasks.script(task_name="release", tags=["packaging", "release", "common"])
1279
1261
  def release_without_containers(
1280
1262
  component: str = "auto",
1281
- *,
1282
1263
  stage: str | None = None,
1283
1264
  dry_run: bool = False,
1284
1265
  assets: list[str] | None = None,
@@ -1431,6 +1412,7 @@ def fastapi_stack_up(
1431
1412
  load_container_env_tokens,
1432
1413
  )
1433
1414
  from .project import (
1415
+ get_package_manager,
1434
1416
  has_debug_dependency_group,
1435
1417
  resolve_container_entrypoint_command,
1436
1418
  )
@@ -1453,6 +1435,7 @@ def fastapi_stack_up(
1453
1435
  dockerfile_text = render_template_text(
1454
1436
  load_data_file("Dockerfile.j2")[1],
1455
1437
  {
1438
+ "PACKAGE_MANAGER": get_package_manager(),
1456
1439
  "EXTENSION_CONTENT": "",
1457
1440
  "HAS_DEBUG_DEPS": has_debug_deps,
1458
1441
  "APT_PACKAGES": "",
@@ -585,3 +585,27 @@ def changelog() -> str | None:
585
585
  result,
586
586
  count=1,
587
587
  ).strip()
588
+
589
+
590
+ def confirm(message: str) -> bool:
591
+ """Prompt the user for a yes/no confirmation.
592
+
593
+ Args:
594
+ message: The confirmation message to display.
595
+
596
+ Returns:
597
+ `True` if the user confirms with 'y' or 'yes', `False` for 'n', 'no', or empty
598
+ input. The prompt will repeat until a valid response is received.
599
+ """
600
+ yes_values = {"y", "yes"}
601
+ no_values = {"n", "no"}
602
+ while True:
603
+ try:
604
+ response = input(f"{message} [y/N]: ").strip().lower()
605
+ except (EOFError, OSError):
606
+ return False
607
+
608
+ if response in yes_values:
609
+ return True
610
+ if response in no_values:
611
+ return False
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: common-python-tasks
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Opinionated Poe the Poet tasks for Python package development.
5
+ Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
6
+ Project-URL: Source, http://github.com/ci-sourcerer/common-python-tasks.git
7
+ Project-URL: Issues, http://github.com/ci-sourcerer/common-python-tasks/issues
8
+ Author-email: Joseph Asbury <ci_sourcerer@yahoo.com>
5
9
  License-Expression: MIT
6
10
  License-File: LICENSE
7
- Author: Joseph Asbury
8
- Author-email: ci_sourcerer@yahoo.com
9
- Requires-Python: >=3.11,<4.0
10
11
  Classifier: Development Status :: 3 - Alpha
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: Programming Language :: Python :: 3
@@ -15,23 +16,21 @@ Classifier: Programming Language :: Python :: 3.12
15
16
  Classifier: Programming Language :: Python :: 3.13
16
17
  Classifier: Programming Language :: Python :: 3.14
17
18
  Classifier: Topic :: Software Development :: Build Tools
18
- Requires-Dist: autoflake (>=2.3.1,<3.0.0)
19
- Requires-Dist: black (>=26.3.1,<27.0.0)
20
- Requires-Dist: docker (>=7.1.0,<8.0.0)
21
- Requires-Dist: dunamai (>=1.25.0,<2.0.0)
22
- Requires-Dist: flake8 (>=7.3.0,<8.0.0)
23
- Requires-Dist: git-cliff (>=2.12.0,<3.0.0)
24
- Requires-Dist: isort (>=8.0.1,<9.0.0)
25
- Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
26
- Requires-Dist: packaging (>=26.2,<27.0)
27
- Requires-Dist: poethepoet-tasks (>=0.3.0,<0.4.0)
28
- Requires-Dist: pytest (>=9.0.3,<10.0.0)
29
- Requires-Dist: pytest-cov (>=7.0.0,<8.0.0)
30
- Requires-Dist: requests (>=2.34.2,<3.0.0)
31
- Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
32
- Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
33
- Project-URL: Issues, http://github.com/ci-sourcerer/common-python-tasks/issues
34
- Project-URL: Source, http://github.com/ci-sourcerer/common-python-tasks.git
19
+ Requires-Python: <4.0,>=3.11
20
+ Requires-Dist: autoflake<3.0.0,>=2.3.1
21
+ Requires-Dist: black<27.0.0,>=26.3.1
22
+ Requires-Dist: docker<8.0.0,>=7.1.0
23
+ Requires-Dist: dunamai<2.0.0,>=1.25.0
24
+ Requires-Dist: flake8<8.0.0,>=7.3.0
25
+ Requires-Dist: git-cliff<3.0.0,>=2.12.0
26
+ Requires-Dist: isort<9.0.0,>=8.0.1
27
+ Requires-Dist: jinja2<4.0.0,>=3.1.6
28
+ Requires-Dist: packaging<27.0,>=26.2
29
+ Requires-Dist: poethepoet-tasks<0.4.0,>=0.3.0
30
+ Requires-Dist: pytest-cov<8.0.0,>=7.0.0
31
+ Requires-Dist: pytest<10.0.0,>=9.0.3
32
+ Requires-Dist: requests<3.0.0,>=2.34.2
33
+ Requires-Dist: tomlkit<0.14.0,>=0.13.3
35
34
  Description-Content-Type: text/markdown
36
35
 
37
36
  # Common Python Tasks
@@ -53,7 +52,7 @@ curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents
53
52
  To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
54
53
 
55
54
  ```shell
56
- COMMON_PYTHON_TASKS_VERSION=0.4.0 \
55
+ COMMON_PYTHON_TASKS_VERSION=0.4.1 \
57
56
  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)"
58
57
  ```
59
58
 
@@ -61,7 +60,7 @@ This will complete the following steps.
61
60
 
62
61
  1. Add the latest version of `common-python-tasks` to your `pyproject.toml` dependencies
63
62
  2. Configure Poe the Poet to expose the default common task set
64
- 3. Install the package using Poetry
63
+ 3. Install dependencies for your project
65
64
 
66
65
  **Always review scripts before running them!** Even though I believe I write good software, it's best practice to verify any script you download from the Internet.
67
66
 
@@ -74,7 +73,7 @@ There's no real reason to run the automated script; I just like automating every
74
73
  ```toml
75
74
  [project]
76
75
  dependencies = [
77
- "common-python-tasks==0.4.0" # Always pin to a specific version
76
+ "common-python-tasks==0.4.1" # Always pin to a specific version
78
77
  ]
79
78
 
80
79
  [tool.poe]
@@ -84,6 +83,8 @@ There's no real reason to run the automated script; I just like automating every
84
83
  2. Install the package
85
84
 
86
85
  ```shell
86
+ uv sync
87
+ # or, for Poetry workflows
87
88
  poetry install
88
89
  ```
89
90
 
@@ -196,9 +197,24 @@ The `fastapi` stack includes a service for your FastAPI application. It uses the
196
197
 
197
198
  Your project must meet the following requirements.
198
199
 
199
- - Use Poetry for dependency management
200
- - Have a `pyproject.toml` file at the root
201
- - Have a package name (automatically inferred from `project.name` in `pyproject.toml` or set via `PACKAGE_NAME` environment variable)
200
+ - Uses Poetry or uv for dependency management
201
+ - Uses Poe the Poet for task management
202
+ - Has `poetry-dynamic-versioning` or `uv-dynamic-versioning` as a dependency
203
+ - Has a `pyproject.toml` file at the root
204
+ - Has a package name (automatically inferred from `project.name` in `pyproject.toml` or set via `PACKAGE_NAME` environment variable)
205
+
206
+ ### Package manager backend selection
207
+
208
+ Packaging-related tasks (`build-package`, `publish-package`, release tagging/version resolution, and related container build metadata) support both Poetry and uv.
209
+
210
+ Backend selection uses this precedence.
211
+
212
+ 1. `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
213
+ 2. `COMMON_PYTHON_TASKS_BUILD_TOOL`
214
+ 3. `PACKAGE_MANAGER`
215
+ 4. Auto-discovery (`auto`) based on installed tools and project metadata
216
+
217
+ Supported values are `poetry`, `uv`, and `auto`.
202
218
 
203
219
  ### Configuration precedence
204
220
 
@@ -227,7 +243,9 @@ The following environment variables configure the paths to configuration files.
227
243
  The following environment variables configure package and container behavior.
228
244
 
229
245
  - `PACKAGE_NAME`: Overrides the package name; the default comes from `pyproject.toml`
230
- - `POETRY_VERSION`: Overrides the Poetry version for container builds
246
+ - `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`: Selects package manager backend (`poetry`, `uv`, or `auto`)
247
+ - `COMMON_PYTHON_TASKS_BUILD_TOOL`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
248
+ - `PACKAGE_MANAGER`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
231
249
  - `CONTAINER_REGISTRY_USERNAME`: Specifies the container registry username for image tagging; the default is the current local user
232
250
  - `CONTAINER_REGISTRY_URL`: Specifies the registry URL (default: `docker.io/{username}`)
233
251
  - `CONTAINER_BUILD_ARGS`: Provides additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format
@@ -289,8 +307,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
289
307
  ```toml
290
308
  [project]
291
309
  name = "simple-cli-tool"
292
- version = "0.4.0"
293
- dependencies = ["common-python-tasks==0.4.0"]
310
+ version = "0.4.1"
311
+ dependencies = ["common-python-tasks==0.4.1"]
294
312
 
295
313
  [tool.poe]
296
314
  include_script = "common_python_tasks:tasks()"
@@ -302,7 +320,7 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
302
320
 
303
321
  ```toml
304
322
  [project]
305
- dependencies = ["common-python-tasks==0.4.0"]
323
+ dependencies = ["common-python-tasks==0.4.1"]
306
324
 
307
325
  [tool.poe]
308
326
  include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
@@ -318,7 +336,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
318
336
 
319
337
  ```toml
320
338
  [project]
321
- dependencies = ["common-python-tasks==0.4.0"]
339
+ dependencies = ["common-python-tasks==0.4.1"]
322
340
 
323
341
  [tool.poe]
324
342
  include_script = "common_python_tasks:tasks()"
@@ -367,7 +385,7 @@ COMMON_PYTHON_TASKS_LOG_LEVEL=DEBUG poe test
367
385
  Make sure your `pyproject.toml` contains the following.
368
386
 
369
387
  - A correct package name in `[project]`
370
- - A package location defined with this configuration: `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`
388
+ - Package discovery settings required by your chosen build backend (for Poetry projects, this is commonly `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`)
371
389
 
372
390
  ### Stack fails to start or services won't connect
373
391
 
@@ -401,9 +419,9 @@ If `SECRET_KEY` or `DB_PASS` aren't auto-generated:
401
419
 
402
420
  The standard Python Dockerfile incorporates several intentional design choices.
403
421
 
404
- - Multi-stage build: The build stage installs Poetry and builds a wheel while the runtime stage installs only the wheel to keep the final image slim and reproducible
405
- - Pip and Poetry cache mounts: Speed up iterative builds without bloating the final image
406
- - Explicit inputs through build args: `PYTHON_VERSION`, `POETRY_VERSION`, `PACKAGE_NAME`, `AUTHORS`, `GIT_COMMIT`, and `CUSTOM_ENTRYPOINT` make image metadata and behavior predictable and auditable
422
+ - Multi-stage build: The build stage installs the selected package manager backend (Poetry or uv) and builds a wheel while the runtime stage installs only the wheel to keep the final image slim and reproducible
423
+ - Pip and package-manager cache mounts: Speed up iterative builds without bloating the final image
424
+ - Explicit inputs through build args: `PYTHON_VERSION`, `PACKAGE_MANAGER`, `PACKAGE_MANAGER_VERSION`, `PACKAGE_NAME`, `AUTHORS`, `GIT_COMMIT`, and `CUSTOM_ENTRYPOINT` make image metadata and behavior predictable and auditable
407
425
  - Optional debug stage: Exports and installs the `debug` dependency group only when present, without failing otherwise, and is not part of the default final image
408
426
  - Stable package path: Creates symlinks to the installed package so entrypoints and consumers have a consistent `/pkg` and `/_$PACKAGE_NAME` path regardless of wheel layout. This ensures that the package can be imported and executed from a known location and supports the less common case of reading files directly from the package path
409
427
  - Safe entrypoint selection: The default entrypoint resolves the console script matching the package name, while `CUSTOM_ENTRYPOINT` allows an override at build time and keeps runtime behavior predictable
@@ -413,7 +431,6 @@ The standard Python Dockerfile incorporates several intentional design choices.
413
431
 
414
432
  - This project dogfoods itself; it uses `common-python-tasks` for its own development. **That said, you must set the environment variable `PYTHONPATH=src` when running tasks locally to ensure you are using the local version of the package instead of the installed version**
415
433
  - `RELEASE_UPDATE_CHANGELOG` is enabled by default for releases and prepends a tagged section into `CHANGELOG.md` before the release tag is created. Set it to a falsy value if you prefer to manage changelog commits yourself.
416
- - `RELEASE_PRE_SCRIPT` and `RELEASE_POST_SCRIPT` hooks may not be necessary for most users, as this package promotes the use of `poetry-dynamic-versioning` and `git-cliff` to automate versioning and changelog generation based on git history. One advanced use case for the `RELEASE_PRE_SCRIPT` hook is to edit another file before release, such as a README that references the current stable version number. This allows you to keep the README up to date with the latest version without hardcoding it.
434
+ - `RELEASE_PRE_SCRIPT` and `RELEASE_POST_SCRIPT` hooks may not be necessary for most users, as this package promotes dynamic versioning and `git-cliff` to automate versioning and changelog generation based on git history. One advanced use case for the `RELEASE_PRE_SCRIPT` hook is to edit another file before release, such as a README that references the current stable version number. This allows you to keep the README up to date with the latest version without hardcoding it.
417
435
  - Contributions welcome! Open an issue/discussion to discuss changes before submitting a PR. I do not claim to have all the answers, and you can help determine the future of low-code solutions for Python. I am very interested in your feedback as I don't want to work in a vacuum
418
436
  - Alpha status: Expect breaking changes between minor versions until 1.0.0
419
-
@@ -1,28 +1,28 @@
1
1
  common_python_tasks/__init__.py,sha256=A-qMF818PGbDxM_DH9l1HwQMV-tXn8CZZiVQy-F_sLc,550
2
2
  common_python_tasks/__main__.py,sha256=AZiLLusMDs0rZIIBuCDbt6jABlQD-kEwIQIep7GxgEc,3019
3
+ common_python_tasks/docker.py,sha256=KQ_Ym6tungnYqkf1EyXgdLpMjLNX879SWXKM9tIrfEg,19694
4
+ common_python_tasks/docker_compose.py,sha256=UnTP5-Kw31ZzkMUeuml1GxHkHmNsX76-9uezq0qPVBQ,21057
5
+ common_python_tasks/env.py,sha256=DNz-qpFnJ-41pHBb7MIJuemgLqn_rI3GUEZTfC2I4bQ,15064
6
+ common_python_tasks/git.py,sha256=wJ3gePpWE4fhNJYZCEWTDFrhHqaOVkaQveHeiGIbCHo,11188
7
+ common_python_tasks/github.py,sha256=sABDB7BvrdWkpbh4u0-8OeAiu-jWUYfjre_nKOw15bI,18393
8
+ common_python_tasks/project.py,sha256=zyhs1oqptxRALPCzJtNOsQJBeNEbWzKIWBG0BUoiXLg,19271
9
+ common_python_tasks/tasks.py,sha256=P-T6UMhOyhOfOTG2M9EGiK4YfTm9l5mGplF6SnLt1vo,53926
10
+ common_python_tasks/utils.py,sha256=ej4nKFPMFhu1wSgkL9x50DML_sPoPmR6aGcIMyAmfN8,19586
3
11
  common_python_tasks/data/dockerfile_extensions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
12
  common_python_tasks/data/fastapi/alembic.ini.j2,sha256=IVXmnF1pJvW3o8dXBLIVhmng7DyXu_sWXx_W3VlAcGE,910
5
- common_python_tasks/data/fastapi/compose-base.yml.j2,sha256=FVTvaTPMF80cwTCXZQ0VUiu2DmLnvQP7cOdRfZPx4aI,918
13
+ common_python_tasks/data/fastapi/compose-base.yml.j2,sha256=JtXilJ0RfdW5PXSF2xDFRxE9-gUcanJBgHWvKdGJWEE,974
6
14
  common_python_tasks/data/fastapi/compose-db-debug.yml.j2,sha256=iREdSy4F8tJ5m9rlB6CROBkj9OtDFVuGphqIuDvHhww,1193
7
- common_python_tasks/data/fastapi/compose-db.yml.j2,sha256=Ui79TMUt2AEC6czm1FMCCt9oU29JsibOnfRXFhzCYcw,1785
15
+ common_python_tasks/data/fastapi/compose-db.yml.j2,sha256=FEWgBlebzkgdaaf98xAzDLqk_VGgsYNRF82onqRUhJw,1841
8
16
  common_python_tasks/data/fastapi/compose-debug.yml.j2,sha256=6397JFObue-YrweFDPyFg4anwe7H6XaMElRqyBee2Q0,665
9
17
  common_python_tasks/data/generic/.coveragerc,sha256=8zXwDq18wIKQV4NtW2NJYpo3nxCO4LsAChyluYTUoJc,85
10
18
  common_python_tasks/data/generic/.dockerignore,sha256=K1hgsu8R2Myq6vPIgL86Cvw9aKxly9HfVbcTr3IdKyY,69
11
19
  common_python_tasks/data/generic/.flake8,sha256=PzH98ak-AKcENaBCzOhI2eHvLxrneklm7KaIWY9g4so,138
12
20
  common_python_tasks/data/generic/.isort.cfg,sha256=P_hdco_n95VeXJjlUcXkChqNdct0SNDuk_n9ZaHzXPE,71
13
21
  common_python_tasks/data/generic/Dockerfile.deps.j2,sha256=rCGsBwLHQIRsGFzDbiRi_nWc-SZ3MAFZ_BeZJFV28Ck,432
14
- common_python_tasks/data/generic/Dockerfile.j2,sha256=HKbq-A5M4U8nYHFoRJaNy8WuTsBt0xVA-UUHQe-kvnI,5704
22
+ common_python_tasks/data/generic/Dockerfile.j2,sha256=UG-QxIuFT27j925LsghPl5Wqcw5wNsPPuqB6rcRngz0,7263
15
23
  common_python_tasks/data/generic/cliff.toml,sha256=po1MnEGEEgrpyBz3XLWr0h7emXb6ASX63lrU2r6o240,77
16
24
  common_python_tasks/data/generic/pytest.ini,sha256=RHak5QrLzKHvUBjFmaaiOYFrd_8YBRQubXEOT0NJfco,134
17
- common_python_tasks/docker.py,sha256=1brnM00owbh23jc8bTEHVdDrPtqvp2sojxJ_omtQBqM,19288
18
- common_python_tasks/docker_compose.py,sha256=lZ4bkD6RLSstTKsH2S8_P1b2Axhot31vfNAl28nfaTY,20879
19
- common_python_tasks/env.py,sha256=DNz-qpFnJ-41pHBb7MIJuemgLqn_rI3GUEZTfC2I4bQ,15064
20
- common_python_tasks/git.py,sha256=H_qF6rJlo9L3LBp56fW-qi0-6HrOXwdc9YJy7XgKG7E,11257
21
- common_python_tasks/github.py,sha256=sABDB7BvrdWkpbh4u0-8OeAiu-jWUYfjre_nKOw15bI,18393
22
- common_python_tasks/project.py,sha256=D0aGytyJy8UPoVXLnOvQB9I3Ty1fhbN_YcVSsvduU64,11053
23
- common_python_tasks/tasks.py,sha256=HQaEII_lZ4BSnyBgYNRclCphWISh5pO4YlSH-wBAbp8,54013
24
- common_python_tasks/utils.py,sha256=gnucqE-fbvXqeUryclWaVWH9VTP9cHvmxiXArhrn05I,18917
25
- common_python_tasks-0.4.0.dist-info/METADATA,sha256=Sy5uHeRjDHx8WI4UbRp8R68WLaAMscCRc5giYRpSmB8,21168
26
- common_python_tasks-0.4.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
27
- common_python_tasks-0.4.0.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
28
- common_python_tasks-0.4.0.dist-info/RECORD,,
25
+ common_python_tasks-0.4.1.dist-info/METADATA,sha256=p2tiuAPYuNHa8wx_m9NcgkEbfwQYO9lRY-ox71rOM8g,22054
26
+ common_python_tasks-0.4.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
27
+ common_python_tasks-0.4.1.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
28
+ common_python_tasks-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.4.1
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any