common-python-tasks 0.7.0__tar.gz → 0.9.0__tar.gz

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.
Files changed (38) hide show
  1. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/.gitignore +2 -0
  2. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/PKG-INFO +63 -29
  3. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/README.md +61 -27
  4. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/pyproject.toml +7 -3
  5. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/scripts/add_common_python_tasks.py +8 -41
  6. common_python_tasks-0.9.0/scripts/readme_tasks_table.py +78 -0
  7. common_python_tasks-0.9.0/scripts/release_script.py +102 -0
  8. common_python_tasks-0.9.0/scripts/update_readme_tasks_table.py +37 -0
  9. common_python_tasks-0.9.0/scripts/utils.py +76 -0
  10. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/__init__.py +9 -0
  11. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/__main__.py +19 -3
  12. common_python_tasks-0.9.0/src/common_python_tasks/data/dockerfile_extensions/.gitkeep +0 -0
  13. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/fastapi/compose-base.yml.j2 +1 -2
  14. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/fastapi/compose-db.yml.j2 +1 -2
  15. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/fastapi/compose-debug.yml.j2 +1 -1
  16. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/.dockerignore +1 -1
  17. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/Dockerfile.j2 +6 -39
  18. common_python_tasks-0.9.0/src/common_python_tasks/data/generic/ruff.toml +2 -0
  19. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/docker.py +274 -154
  20. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/docker_compose.py +16 -11
  21. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/env.py +82 -36
  22. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/git.py +30 -16
  23. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/github.py +37 -3
  24. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/project.py +126 -297
  25. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/tasks.py +145 -171
  26. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/utils.py +29 -4
  27. common_python_tasks-0.7.0/scripts/release_script.py +0 -214
  28. common_python_tasks-0.7.0/src/common_python_tasks/data/generic/ruff.toml +0 -6
  29. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/.poetry/plugins/poetry_dynamic_versioning-1.10.0.dist-info/licenses/LICENSE +0 -0
  30. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/.poetry/plugins/poetry_plugin_export-1.10.0.dist-info/licenses/LICENSE +0 -0
  31. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/LICENSE +0 -0
  32. /common_python_tasks-0.7.0/src/common_python_tasks/data/dockerfile_extensions/.gitkeep → /common_python_tasks-0.9.0/scripts/__init__.py +0 -0
  33. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/fastapi/alembic.ini.j2 +0 -0
  34. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/fastapi/compose-db-debug.yml.j2 +0 -0
  35. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/.coveragerc +0 -0
  36. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/Dockerfile.deps.j2 +0 -0
  37. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/cliff.toml +0 -0
  38. {common_python_tasks-0.7.0 → common_python_tasks-0.9.0}/src/common_python_tasks/data/generic/pytest.ini +0 -0
@@ -214,3 +214,5 @@ __marimo__/
214
214
 
215
215
  # Streamlit
216
216
  .streamlit/secrets.toml
217
+
218
+ .serena
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: common-python-tasks
3
- Version: 0.7.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.7.0
47
- # or, for Poetry projects
48
- poetry add --group dev common-python-tasks==0.7.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.7.0/scripts/add_common_python_tasks.py
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.7.0 python3 /tmp/add_common_python_tasks.py
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.7.0` with that release's version.
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 Poetry or uv. | common, packaging |
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 also require either Poetry or uv. Package and release tasks need a resolvable project version from `project.version`, Poetry's version resolution, or Git tags. Dynamic-versioning plugins are supported but are not a baseline requirement.
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 manager backend selection
164
+ ### Package management
167
165
 
168
- Packaging-related tasks, including `build-package`, `publish-package`, release version resolution, and container build metadata, support Poetry and uv.
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
- Backend selection uses this precedence.
168
+ ### Publish target selection
171
169
 
172
- 1. `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
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
- Supported values are `poetry`, `uv`, and `auto`.
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.7.0"
206
+ version = "0.9.0"
200
207
 
201
208
  [tool.poe]
202
209
  include_script = "common_python_tasks:tasks()"
@@ -232,21 +239,25 @@ addopts = "-ra"
232
239
  #### Project and package settings
233
240
 
234
241
  - `PACKAGE_NAME`: Overrides the package name inferred from `pyproject.toml`
235
- - `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`: Selects the package-manager backend as `poetry`, `uv`, or `auto`
236
- - `COMMON_PYTHON_TASKS_BUILD_TOOL`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
237
- - `PACKAGE_MANAGER`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
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
 
241
249
  - `CONTAINER_REGISTRY_USERNAME`: Container-registry username for image tagging; the default is the current local user
242
250
  - `CONTAINER_REGISTRY_URL`: Registry URL with a default of `docker.io/{username}`
243
- - `CONTAINER_BUILD_ARGS`: Additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format. Escape literal colons as `\:` or quote the complete value.
251
+ - `CONTAINER_DOCKER_BUILD_ARGS`: Additional arguments passed directly to `docker build`, parsed using shell quoting rules. Free arguments provided to the task after `--` take precedence.
252
+ - `CONTAINER_DOCKERFILE_HOOK_PATH`: Optional host path to an executable hook script that receives the generated Dockerfile path and can modify the file before `docker build` runs.
253
+ - `CONTAINER_APT_PACKAGES`: Space-delimited system packages installed in the generated image
254
+ - `CONTAINER_CUSTOM_ENTRYPOINT`: Custom container entrypoint script. The value must match a key in `[project].scripts`.
255
+ - `CONTAINER_DEPS_IMAGE`: Existing dependency image used when neither `CONTAINER_DEPS_CONTENT` nor `CONTAINER_DEPS_FILE` is configured
244
256
  - `CONTAINER_EXTENSION_FILES`: Colon-delimited local extension Dockerfile paths. Escape literal colons as `\:` or quote the complete path.
245
257
  - `CONTAINER_EXTENSIONS`: Colon-delimited extension-bundle names or parameterized values. Escape literal colons as `\:` or quote the complete value.
246
258
  - `CONTAINER_ENV`: Colon-delimited `KEY=VALUE` declarations injected into the rendered Dockerfile's builder stage. Escape literal colons as `\:` or quote the complete value.
247
259
  - `.containerenv`: A project-root file that can supply the same declarations. It is loaded before the `container_envfile` task argument, `CONTAINER_ENV`, and the `container_env` task argument.
248
260
  - `CONTAINER_PRUNE_KEEP`: Image-pruning policy after builds. `-1` keeps all images, `0` keeps only the latest, and `N` keeps the latest plus `N` prior images.
249
- - `CUSTOM_ENTRYPOINT`: Custom container entrypoint script. The value must match a key in `[project].scripts`.
250
261
  - `CONTAINER_DEPS_CONTENT`: Inline Dockerfile instructions for a dependency image that installs artifacts into `/tmp/deps`
251
262
  - `CONTAINER_DEPS_FILE`: One or more dependency-image Dockerfiles. It accepts colon-delimited paths with literal colons escaped as `\:` and is used only when `CONTAINER_DEPS_CONTENT` is unset.
252
263
  - `CONTAINER_DEPS_MAPPINGS`: Space-delimited `name:/target/path` entries for copying items from `/tmp/deps`. It is used only when no dependency move script is set.
@@ -293,6 +304,28 @@ addopts = "-ra"
293
304
 
294
305
  Docker Compose development-stack tasks are available when the `containers` tag is selected. The current stack supports FastAPI applications and an optional PostgreSQL database.
295
306
 
307
+ ### Native Docker build arguments
308
+
309
+ Arguments after the task's `--` separator are passed directly to `docker build`. Docker performs option validation, so any supported build option and its value can be used without a package-specific allowlist.
310
+
311
+ ```shell
312
+ poe build-image --single-arch -- \
313
+ --secret id=pip_conf,env=PIP_CONF \
314
+ --ssh default \
315
+ --add-host example:127.0.0.1
316
+ ```
317
+
318
+ Set `CONTAINER_DOCKER_BUILD_ARGS` to provide the same arguments through the environment. The value uses shell quoting rules, and task arguments provided after `--` take precedence.
319
+
320
+ Settings that affect generated Dockerfile content or dependency-image orchestration can be persisted directly in the project configuration.
321
+
322
+ ```toml
323
+ [tool.poe.env]
324
+ CONTAINER_APT_PACKAGES = "curl jq"
325
+ CONTAINER_CUSTOM_ENTRYPOINT = "serve"
326
+ CONTAINER_DEPS_IMAGE = "example/dependencies:latest"
327
+ ```
328
+
296
329
  ### Stack configuration
297
330
 
298
331
  Set `COMPOSE_TYPE` to select the application stack. `fastapi` is currently supported and includes optional database support and Alembic migrations.
@@ -361,7 +394,7 @@ GITHUB_RELEASE_ASSETS="dist/*.whl:dist/*.tar.gz" poe publish-github-release
361
394
 
362
395
  ### Container build fails with "unable to find package"
363
396
 
364
- Check that `pyproject.toml` has a correct package name and the package-discovery settings required by its build backend. For Poetry projects, this commonly includes `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`.
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"]`.
365
398
 
366
399
  ### Stack fails to start or services cannot connect
367
400
 
@@ -386,12 +419,13 @@ Ensure the project-root `.env` file is writable and inspect its permissions with
386
419
 
387
420
  See the [standard Dockerfile template](src/common_python_tasks/data/generic/Dockerfile.j2) for the implementation.
388
421
 
389
- - Multi-stage build: The build stage installs the selected package-manager backend and builds a wheel. The runtime stage installs only the wheel to keep the final image slim and reproducible.
390
- - Cache mounts: Pip and package-manager cache mounts speed up iterative builds without bloating the final image.
391
- - Explicit build arguments: `PYTHON_VERSION`, `PACKAGE_MANAGER`, `PACKAGE_MANAGER_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 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.
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.
392
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.
393
427
  - Stable package path: Symlinks give entrypoints and consumers consistent `/pkg` and `/_$PACKAGE_NAME` paths regardless of wheel layout.
394
- - Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python`. `CUSTOM_ENTRYPOINT` is validated against `[project].scripts`.
428
+ - Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python`. `CONTAINER_CUSTOM_ENTRYPOINT` is validated against `[project].scripts`.
395
429
  - Minimal final image: The standard slim Python base, cache cleanup, and explicit `runtime` final target keep the default image small.
396
430
 
397
431
  ## Project notes
@@ -11,9 +11,7 @@ It supplies sensible defaults for formatting, linting, testing, packaging, relea
11
11
  Add `common-python-tasks` as a development dependency from your project root.
12
12
 
13
13
  ```shell
14
- uv add --dev common-python-tasks==0.7.0
15
- # or, for Poetry projects
16
- poetry add --group dev common-python-tasks==0.7.0
14
+ uv add --dev common-python-tasks==0.9.0
17
15
  ```
18
16
 
19
17
  Configure Poe the Poet to expose the default `common` task set.
@@ -38,7 +36,7 @@ The helper script performs the same development-dependency installation and Poe
38
36
  ```shell
39
37
  curl --fail --silent --show-error --location \
40
38
  --output /tmp/add_common_python_tasks.py \
41
- https://raw.githubusercontent.com/ci-sourcerer/common-python-tasks/v0.7.0/scripts/add_common_python_tasks.py
39
+ https://raw.githubusercontent.com/ci-sourcerer/common-python-tasks/v0.9.0/scripts/add_common_python_tasks.py
42
40
  ```
43
41
 
44
42
  Review the downloaded script before executing it.
@@ -50,10 +48,10 @@ less /tmp/add_common_python_tasks.py
50
48
  Run the reviewed script with the same pinned package version.
51
49
 
52
50
  ```shell
53
- COMMON_PYTHON_TASKS_VERSION=0.7.0 python3 /tmp/add_common_python_tasks.py
51
+ COMMON_PYTHON_TASKS_VERSION=0.9.0 python3 /tmp/add_common_python_tasks.py
54
52
  ```
55
53
 
56
- To install another release, replace both occurrences of `0.7.0` with that release's version.
54
+ To install another release, replace both occurrences of `0.9.0` with that release's version.
57
55
 
58
56
  ## Available tasks
59
57
 
@@ -76,7 +74,7 @@ The generated tables below list public tasks only. Tags identify which tasks are
76
74
  | --- | --- | --- |
77
75
  | `publish-package` | Publish the package to the PyPI server. | common, packaging |
78
76
  | `publish-github-release` | Publish or update a GitHub Release for the current repository. | common, packaging, release |
79
- | `update-dependencies` | Update project dependencies with Poetry or uv. | common, packaging |
77
+ | `update-dependencies` | Update project dependencies with uv. | common, packaging |
80
78
  | `build-package` | Build the package (wheel and sdist). | build, common, packaging |
81
79
  | `bump-version` | Bump the project version. | common, packaging |
82
80
  | `changelog` | Print the changelog for the current version based on git history and git-cliff. | common, packaging, release |
@@ -111,7 +109,7 @@ The generated tables below list public tasks only. Tags identify which tasks are
111
109
 
112
110
  Every project needs a `pyproject.toml` file at its root and Poe the Poet available in its development environment.
113
111
 
114
- Tasks that install, build, publish, or update dependencies also require either Poetry or uv. Package and release tasks need a resolvable project version from `project.version`, Poetry's version resolution, or Git tags. Dynamic-versioning plugins are supported but are not a baseline requirement.
112
+ 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.
115
113
 
116
114
  ### Task selection
117
115
 
@@ -131,18 +129,27 @@ include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'
131
129
 
132
130
  The `containers` tag also includes container-based development-stack tasks. Use the tags in the task tables to tailor a smaller task set.
133
131
 
134
- ### Package manager backend selection
132
+ ### Package management
135
133
 
136
- Packaging-related tasks, including `build-package`, `publish-package`, release version resolution, and container build metadata, support Poetry and uv.
134
+ 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`.
137
135
 
138
- Backend selection uses this precedence.
136
+ ### Publish target selection
139
137
 
140
- 1. `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
141
- 2. `COMMON_PYTHON_TASKS_BUILD_TOOL`
142
- 3. `PACKAGE_MANAGER`
143
- 4. Auto-discovery (`auto`) based on installed tools and project metadata
138
+ The `publish-package` task resolves publish targets with this precedence.
144
139
 
145
- Supported values are `poetry`, `uv`, and `auto`.
140
+ 1. Explicit task arguments (`repository` or `repository_url`)
141
+ 2. Environment-variable fallback
142
+ 3. uv project configuration fallback from `[[tool.uv.index]]`
143
+ 4. The `uv publish` default
144
+
145
+ uv prefers named publish indexes.
146
+
147
+ - `COMMON_PYTHON_TASKS_PUBLISH_REPOSITORY`
148
+ - `UV_PUBLISH_INDEX`
149
+ - `COMMON_PYTHON_TASKS_PUBLISH_URL`
150
+ - `UV_PUBLISH_URL`
151
+
152
+ 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.
146
153
 
147
154
  ### Configuration precedence
148
155
 
@@ -164,7 +171,7 @@ After installing the package, a minimal project configuration looks like this.
164
171
  ```toml
165
172
  [project]
166
173
  name = "simple-cli-tool"
167
- version = "0.7.0"
174
+ version = "0.9.0"
168
175
 
169
176
  [tool.poe]
170
177
  include_script = "common_python_tasks:tasks()"
@@ -200,21 +207,25 @@ addopts = "-ra"
200
207
  #### Project and package settings
201
208
 
202
209
  - `PACKAGE_NAME`: Overrides the package name inferred from `pyproject.toml`
203
- - `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`: Selects the package-manager backend as `poetry`, `uv`, or `auto`
204
- - `COMMON_PYTHON_TASKS_BUILD_TOOL`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
205
- - `PACKAGE_MANAGER`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
210
+ - `COMMON_PYTHON_TASKS_PUBLISH_REPOSITORY`: Preferred publish repository/index name for `publish-package`
211
+ - `COMMON_PYTHON_TASKS_PUBLISH_URL`: Preferred uv publish upload URL for `publish-package` when no repository/index is selected
212
+ - `UV_PUBLISH_INDEX`: uv publish-index fallback for `publish-package`
213
+ - `UV_PUBLISH_URL`: uv publish upload-url fallback for `publish-package`
206
214
 
207
215
  #### Container image settings
208
216
 
209
217
  - `CONTAINER_REGISTRY_USERNAME`: Container-registry username for image tagging; the default is the current local user
210
218
  - `CONTAINER_REGISTRY_URL`: Registry URL with a default of `docker.io/{username}`
211
- - `CONTAINER_BUILD_ARGS`: Additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format. Escape literal colons as `\:` or quote the complete value.
219
+ - `CONTAINER_DOCKER_BUILD_ARGS`: Additional arguments passed directly to `docker build`, parsed using shell quoting rules. Free arguments provided to the task after `--` take precedence.
220
+ - `CONTAINER_DOCKERFILE_HOOK_PATH`: Optional host path to an executable hook script that receives the generated Dockerfile path and can modify the file before `docker build` runs.
221
+ - `CONTAINER_APT_PACKAGES`: Space-delimited system packages installed in the generated image
222
+ - `CONTAINER_CUSTOM_ENTRYPOINT`: Custom container entrypoint script. The value must match a key in `[project].scripts`.
223
+ - `CONTAINER_DEPS_IMAGE`: Existing dependency image used when neither `CONTAINER_DEPS_CONTENT` nor `CONTAINER_DEPS_FILE` is configured
212
224
  - `CONTAINER_EXTENSION_FILES`: Colon-delimited local extension Dockerfile paths. Escape literal colons as `\:` or quote the complete path.
213
225
  - `CONTAINER_EXTENSIONS`: Colon-delimited extension-bundle names or parameterized values. Escape literal colons as `\:` or quote the complete value.
214
226
  - `CONTAINER_ENV`: Colon-delimited `KEY=VALUE` declarations injected into the rendered Dockerfile's builder stage. Escape literal colons as `\:` or quote the complete value.
215
227
  - `.containerenv`: A project-root file that can supply the same declarations. It is loaded before the `container_envfile` task argument, `CONTAINER_ENV`, and the `container_env` task argument.
216
228
  - `CONTAINER_PRUNE_KEEP`: Image-pruning policy after builds. `-1` keeps all images, `0` keeps only the latest, and `N` keeps the latest plus `N` prior images.
217
- - `CUSTOM_ENTRYPOINT`: Custom container entrypoint script. The value must match a key in `[project].scripts`.
218
229
  - `CONTAINER_DEPS_CONTENT`: Inline Dockerfile instructions for a dependency image that installs artifacts into `/tmp/deps`
219
230
  - `CONTAINER_DEPS_FILE`: One or more dependency-image Dockerfiles. It accepts colon-delimited paths with literal colons escaped as `\:` and is used only when `CONTAINER_DEPS_CONTENT` is unset.
220
231
  - `CONTAINER_DEPS_MAPPINGS`: Space-delimited `name:/target/path` entries for copying items from `/tmp/deps`. It is used only when no dependency move script is set.
@@ -261,6 +272,28 @@ addopts = "-ra"
261
272
 
262
273
  Docker Compose development-stack tasks are available when the `containers` tag is selected. The current stack supports FastAPI applications and an optional PostgreSQL database.
263
274
 
275
+ ### Native Docker build arguments
276
+
277
+ Arguments after the task's `--` separator are passed directly to `docker build`. Docker performs option validation, so any supported build option and its value can be used without a package-specific allowlist.
278
+
279
+ ```shell
280
+ poe build-image --single-arch -- \
281
+ --secret id=pip_conf,env=PIP_CONF \
282
+ --ssh default \
283
+ --add-host example:127.0.0.1
284
+ ```
285
+
286
+ Set `CONTAINER_DOCKER_BUILD_ARGS` to provide the same arguments through the environment. The value uses shell quoting rules, and task arguments provided after `--` take precedence.
287
+
288
+ Settings that affect generated Dockerfile content or dependency-image orchestration can be persisted directly in the project configuration.
289
+
290
+ ```toml
291
+ [tool.poe.env]
292
+ CONTAINER_APT_PACKAGES = "curl jq"
293
+ CONTAINER_CUSTOM_ENTRYPOINT = "serve"
294
+ CONTAINER_DEPS_IMAGE = "example/dependencies:latest"
295
+ ```
296
+
264
297
  ### Stack configuration
265
298
 
266
299
  Set `COMPOSE_TYPE` to select the application stack. `fastapi` is currently supported and includes optional database support and Alembic migrations.
@@ -329,7 +362,7 @@ GITHUB_RELEASE_ASSETS="dist/*.whl:dist/*.tar.gz" poe publish-github-release
329
362
 
330
363
  ### Container build fails with "unable to find package"
331
364
 
332
- Check that `pyproject.toml` has a correct package name and the package-discovery settings required by its build backend. For Poetry projects, this commonly includes `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`.
365
+ 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"]`.
333
366
 
334
367
  ### Stack fails to start or services cannot connect
335
368
 
@@ -354,12 +387,13 @@ Ensure the project-root `.env` file is writable and inspect its permissions with
354
387
 
355
388
  See the [standard Dockerfile template](src/common_python_tasks/data/generic/Dockerfile.j2) for the implementation.
356
389
 
357
- - Multi-stage build: The build stage installs the selected package-manager backend and builds a wheel. The runtime stage installs only the wheel to keep the final image slim and reproducible.
358
- - Cache mounts: Pip and package-manager cache mounts speed up iterative builds without bloating the final image.
359
- - Explicit build arguments: `PYTHON_VERSION`, `PACKAGE_MANAGER`, `PACKAGE_MANAGER_VERSION`, `PACKAGE_NAME`, `AUTHORS`, `GIT_COMMIT`, and `CUSTOM_ENTRYPOINT` make image metadata and behavior predictable and auditable.
390
+ - 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.
391
+ - Cache mounts: Pip and uv cache mounts speed up iterative builds without bloating the final image.
392
+ - Explicit build metadata: `PYTHON_VERSION`, `UV_VERSION`, `PACKAGE_VERSION`, `PACKAGE_NAME`, `AUTHORS`, and `GIT_COMMIT` make image metadata predictable and auditable.
393
+ - 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.
360
394
  - 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.
361
395
  - Stable package path: Symlinks give entrypoints and consumers consistent `/pkg` and `/_$PACKAGE_NAME` paths regardless of wheel layout.
362
- - Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python`. `CUSTOM_ENTRYPOINT` is validated against `[project].scripts`.
396
+ - Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python`. `CONTAINER_CUSTOM_ENTRYPOINT` is validated against `[project].scripts`.
363
397
  - Minimal final image: The standard slim Python base, cache cleanup, and explicit `runtime` final target keep the default image small.
364
398
 
365
399
  ## Project notes
@@ -37,12 +37,16 @@ Source = "http://github.com/ci-sourcerer/common-python-tasks.git"
37
37
  Issues = "http://github.com/ci-sourcerer/common-python-tasks/issues"
38
38
 
39
39
  [tool.poe]
40
- include_script = "common_python_tasks:tasks(exclude_tags=['containers'])"
40
+ include_script = "common_python_tasks:tasks(exclude_tags=['containers', 'fastapi'])"
41
41
 
42
42
  [tool.poe.env]
43
43
  PYTHONPATH = "src"
44
44
  RELEASE_UPDATE_CHANGELOG = "1"
45
- RELEASE_PRE_SCRIPT = "RELEASE_SCRIPT_PHASE=pre python scripts/release_script.py"
45
+ RELEASE_PRE_SCRIPT = "RELEASE_SCRIPT_PHASE=pre python -m scripts.release_script"
46
+ CONTAINER_APT_PACKAGES = "jq"
47
+
48
+ [tool.poe.tasks]
49
+ update-readme-tasks-table = { cmd = "python -m scripts.update_readme_tasks_table" }
46
50
 
47
51
  [tool.hatch.version]
48
52
  source = "uv-dynamic-versioning"
@@ -69,7 +73,7 @@ package = true
69
73
 
70
74
  [tool.ruff]
71
75
  line-length = 88
72
- extend-exclude = [".dist", ".poetry", "build"]
76
+ extend-exclude = [".dist", "build"]
73
77
 
74
78
  [tool.ruff.lint]
75
79
  select = ["E4", "E7", "E9", "F", "I", "W"]
@@ -10,11 +10,6 @@ import tomllib
10
10
  from pathlib import Path
11
11
 
12
12
  PACKAGE_NAME = "common-python-tasks"
13
- PACKAGE_MANAGER_ENV_VARS = (
14
- "COMMON_PYTHON_TASKS_PACKAGE_MANAGER",
15
- "COMMON_PYTHON_TASKS_BUILD_TOOL",
16
- "PACKAGE_MANAGER",
17
- )
18
13
  PYPROJECT_PATH = Path("pyproject.toml")
19
14
  TOOL_POE_PATTERN = re.compile(r"^\s*\[tool\.poe\]\s*(?:#.*)?$")
20
15
  TABLE_PATTERN = re.compile(r"^\s*\[")
@@ -26,47 +21,18 @@ def _require_command(command: str) -> None:
26
21
  sys.exit(f"Required command not found: {command}")
27
22
 
28
23
 
29
- def _get_requested_package_manager() -> str:
30
- for env_var in PACKAGE_MANAGER_ENV_VARS:
31
- if os.getenv(env_var, "").strip():
32
- return os.environ[env_var].strip().lower()
33
- return "auto"
34
-
35
-
36
- def _select_package_manager() -> str:
37
- requested_manager = _get_requested_package_manager()
38
- if requested_manager in {"poetry", "uv"}:
39
- _require_command(requested_manager)
40
- return requested_manager
41
- if requested_manager != "auto":
42
- sys.exit(f"Unsupported package manager: {requested_manager}")
43
-
44
- for lock_file, package_manager in (
45
- (Path("uv.lock"), "uv"),
46
- (Path("poetry.lock"), "poetry"),
47
- ):
48
- if lock_file.is_file() and shutil.which(package_manager) is not None:
49
- return package_manager
50
-
51
- for package_manager in ("uv", "poetry"):
52
- if shutil.which(package_manager) is not None:
53
- return package_manager
54
-
55
- sys.exit("Neither uv nor Poetry is installed.")
56
-
57
-
58
24
  def _get_package_requirement() -> str:
59
25
  if os.getenv("COMMON_PYTHON_TASKS_VERSION", "").strip():
60
26
  return f"{PACKAGE_NAME}=={os.environ['COMMON_PYTHON_TASKS_VERSION'].strip()}"
61
27
  return PACKAGE_NAME
62
28
 
63
29
 
64
- def _install_package(package_manager: str) -> None:
65
- if package_manager == "uv":
66
- command = ["uv", "add", "--dev", _get_package_requirement()]
67
- else:
68
- command = ["poetry", "add", "--group", "dev", _get_package_requirement()]
69
- subprocess.run(command, check=True, shell=False)
30
+ def _install_package() -> None:
31
+ subprocess.run(
32
+ ["uv", "add", "--dev", _get_package_requirement()],
33
+ check=True,
34
+ shell=False,
35
+ )
70
36
 
71
37
 
72
38
  def _get_include_script() -> str:
@@ -132,7 +98,8 @@ def _print_available_tasks() -> None:
132
98
 
133
99
  def main() -> None:
134
100
  """Install common tasks and configure Poe in the current project."""
135
- _install_package(_select_package_manager())
101
+ _require_command("uv")
102
+ _install_package()
136
103
  _configure_poe()
137
104
  print("\n\033[1;32mCommon Python tasks added to project.\033[0m")
138
105
  print("\n\033[1mAvailable tasks:\033[0m")
@@ -0,0 +1,78 @@
1
+ import re
2
+
3
+ from common_python_tasks.__main__ import (
4
+ _get_task_docstring,
5
+ get_available_tasks,
6
+ get_task_tags,
7
+ )
8
+
9
+ TASKS_TABLE_PATTERN = r"(?ms)<!-- tasks-table -->.*?<!-- end-tasks-table -->"
10
+ TASK_CATEGORY_ORDER = (
11
+ "Daily development",
12
+ "Packaging and releases",
13
+ "Container images",
14
+ "Development stacks",
15
+ )
16
+
17
+
18
+ def _get_task_category(task_name: str) -> str:
19
+ tags = get_task_tags(task_name) or []
20
+ if "web" in tags or "database" in tags:
21
+ return "Development stacks"
22
+ if task_name == "release":
23
+ return "Packaging and releases"
24
+ if "containers" in tags:
25
+ return "Container images"
26
+ if "packaging" in tags or "release" in tags:
27
+ return "Packaging and releases"
28
+ return "Daily development"
29
+
30
+
31
+ def _get_task_description(task_name: str) -> str:
32
+ return " ".join(
33
+ line.strip()
34
+ for line in (_get_task_docstring(task_name) or "").splitlines()
35
+ if line.strip()
36
+ )
37
+
38
+
39
+ def build_tasks_table() -> str:
40
+ """Build the markdown task table for README insertion."""
41
+ tasks_by_category = {category: [] for category in TASK_CATEGORY_ORDER}
42
+ for task_name in get_available_tasks(internal=False):
43
+ tasks_by_category[_get_task_category(task_name)].append(task_name)
44
+
45
+ lines = ["<!-- tasks-table -->"]
46
+ for category in TASK_CATEGORY_ORDER:
47
+ if not tasks_by_category[category]:
48
+ continue
49
+
50
+ lines.extend(
51
+ [
52
+ "",
53
+ f"### {category}",
54
+ "",
55
+ "| Task | Description | Tags |",
56
+ "| --- | --- | --- |",
57
+ ]
58
+ )
59
+ lines.extend(
60
+ f"| `{task_name}` | {_get_task_description(task_name)} | "
61
+ f"{', '.join(get_task_tags(task_name) or [])} |"
62
+ for task_name in tasks_by_category[category]
63
+ )
64
+
65
+ lines.extend(["", "<!-- end-tasks-table -->"])
66
+ return "\n".join(lines)
67
+
68
+
69
+ def replace_tasks_table(readme_text: str) -> str:
70
+ """Replace the task table in the README text with the generated task table.
71
+
72
+ Args:
73
+ readme_text: The text of the README.md file to update.
74
+
75
+ Returns:
76
+ The updated README.md text with the task table replaced.
77
+ """
78
+ return re.sub(TASKS_TABLE_PATTERN, build_tasks_table(), readme_text)