common-python-tasks 0.4.1__tar.gz → 0.5.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.
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/PKG-INFO +20 -18
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/README.md +19 -17
- common_python_tasks-0.5.0/scripts/add_common_python_tasks.py +143 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/scripts/release_script.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/env.py +25 -12
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/github.py +37 -9
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/project.py +62 -14
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/tasks.py +32 -5
- common_python_tasks-0.4.1/scripts/add-common-python-tasks.sh +0 -35
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/.gitignore +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/.poetry/plugins/poetry_dynamic_versioning-1.10.0.dist-info/licenses/LICENSE +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/.poetry/plugins/poetry_plugin_export-1.10.0.dist-info/licenses/LICENSE +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/LICENSE +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/pyproject.toml +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/__init__.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/__main__.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/dockerfile_extensions/.gitkeep +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/fastapi/alembic.ini.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/fastapi/compose-base.yml.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/fastapi/compose-db-debug.yml.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/fastapi/compose-db.yml.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/fastapi/compose-debug.yml.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/.coveragerc +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/.dockerignore +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/.flake8 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/.isort.cfg +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/Dockerfile.deps.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/Dockerfile.j2 +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/cliff.toml +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/pytest.ini +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/docker.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/docker_compose.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/git.py +0 -0
- {common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: common-python-tasks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -46,14 +46,16 @@ Instead of writing your own tasks for formatting, linting, testing, packaging, a
|
|
|
46
46
|
You can add `common-python-tasks` to a new project by using the handy automated installation script.
|
|
47
47
|
|
|
48
48
|
```shell
|
|
49
|
-
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/
|
|
49
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add_common_python_tasks.py | jq -r '.content' | base64 -d | python3
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
|
|
53
53
|
|
|
54
54
|
```shell
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add_common_python_tasks.py \
|
|
56
|
+
| jq -r '.content' \
|
|
57
|
+
| base64 -d \
|
|
58
|
+
| COMMON_PYTHON_TASKS_VERSION=0.5.0 python3
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
This will complete the following steps.
|
|
@@ -73,7 +75,7 @@ There's no real reason to run the automated script; I just like automating every
|
|
|
73
75
|
```toml
|
|
74
76
|
[project]
|
|
75
77
|
dependencies = [
|
|
76
|
-
"common-python-tasks==0.
|
|
78
|
+
"common-python-tasks==0.5.0" # Always pin to a specific version
|
|
77
79
|
]
|
|
78
80
|
|
|
79
81
|
[tool.poe]
|
|
@@ -220,7 +222,7 @@ Supported values are `poetry`, `uv`, and `auto`.
|
|
|
220
222
|
|
|
221
223
|
Tasks that need configuration files (`pytest`, `coverage`, `flake8`, `isort`) follow this order of precedence.
|
|
222
224
|
|
|
223
|
-
1. **`pyproject.toml` sections**: `[tool.pytest]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
225
|
+
1. **`pyproject.toml` sections**: `[tool.pytest.ini_options]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
224
226
|
2. **Environment variables**: Override config paths; see [Environment Variables](#environment-variables)
|
|
225
227
|
3. **Local config files**: `pytest.ini`, `.coveragerc`, `.flake8`, `.isort.cfg` in the project root
|
|
226
228
|
4. **Bundled defaults**: Sensible defaults included with this package, found in the [`src/common_python_tasks/data`](src/common_python_tasks/data) directory
|
|
@@ -248,19 +250,19 @@ The following environment variables configure package and container behavior.
|
|
|
248
250
|
- `PACKAGE_MANAGER`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
|
|
249
251
|
- `CONTAINER_REGISTRY_USERNAME`: Specifies the container registry username for image tagging; the default is the current local user
|
|
250
252
|
- `CONTAINER_REGISTRY_URL`: Specifies the registry URL (default: `docker.io/{username}`)
|
|
251
|
-
- `CONTAINER_BUILD_ARGS`: Provides additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format
|
|
252
|
-
- `CONTAINER_EXTENSION_FILES`: Specifies colon delimited local extension Dockerfile paths to include in the rendered build
|
|
253
|
-
- `CONTAINER_EXTENSIONS`: Specifies colon delimited extension bundle names or parameterized bundle values to include in the rendered build
|
|
254
|
-
- `CONTAINER_ENV`: Provides colon delimited `KEY=VALUE` declarations to inject into the builder stage of the rendered Dockerfile
|
|
253
|
+
- `CONTAINER_BUILD_ARGS`: Provides additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format. Escape literal colons in values as `\:` or quote the complete value
|
|
254
|
+
- `CONTAINER_EXTENSION_FILES`: Specifies colon delimited local extension Dockerfile paths to include in the rendered build; escape literal colons as `\:` or quote the complete path
|
|
255
|
+
- `CONTAINER_EXTENSIONS`: Specifies colon delimited extension bundle names or parameterized bundle values to include in the rendered build; escape literal colons as `\:` or quote the complete value
|
|
256
|
+
- `CONTAINER_ENV`: Provides colon delimited `KEY=VALUE` declarations to inject into the builder stage of the rendered Dockerfile. Escape literal colons in values as `\:` or quote the complete value
|
|
255
257
|
- `.containerenv`: Can also supply the same declarations from a file in the project root. It is loaded first, then `container_envfile`, then `CONTAINER_ENV`, and finally `container_env`.
|
|
256
258
|
- `CONTAINER_PRUNE_KEEP`: Controls image pruning after builds (`-1` keeps all, `0` keeps the latest only, `N` keeps the latest plus `N` previous)
|
|
257
|
-
- `CUSTOM_ENTRYPOINT`: Specifies a custom entrypoint script name for containers
|
|
259
|
+
- `CUSTOM_ENTRYPOINT`: Specifies a custom entrypoint script name for containers. The value must match a key in `[project].scripts`; unknown values fail the build
|
|
258
260
|
- `CONTAINER_DEPS_CONTENT`: Supplies inline Dockerfile instructions for a dependency image that installs artifacts into `/tmp/deps`
|
|
259
|
-
- `CONTAINER_DEPS_FILE`: Points to one or more explicit Dockerfiles to build the dependency image. It may be a colon delimited list of file paths and is used only when `CONTAINER_DEPS_CONTENT` is unset
|
|
261
|
+
- `CONTAINER_DEPS_FILE`: Points to one or more explicit Dockerfiles to build the dependency image. It may be a colon delimited list of file paths, with literal colons escaped as `\:`, and is used only when `CONTAINER_DEPS_CONTENT` is unset
|
|
260
262
|
- `CONTAINER_DEPS_MAPPINGS`: Maps copied dependency names from `/tmp/deps` into destination paths as space separated `name:/target/path` entries. This is used only when `CONTAINER_DEPS_MOVE_SCRIPT` or `CONTAINER_DEPS_MOVE_SCRIPT_PATH` is not set
|
|
261
263
|
- `CONTAINER_DEPS_MOVE_SCRIPT`: Supplies a raw executable script to run after `/tmp/deps` is copied into the image. The script is written to `/tmp/container-deps-move-script` and executed with its own shebang
|
|
262
264
|
- `CONTAINER_DEPS_MOVE_SCRIPT_PATH`: Supplies a host path to a script file to run after `/tmp/deps` is copied into the image. This path takes precedence over `CONTAINER_DEPS_MOVE_SCRIPT` when both are set
|
|
263
|
-
- `GITHUB_RELEASE_ASSETS`: Colon separated list of file paths or glob patterns to attach to the GitHub Release (default: `dist/*`)
|
|
265
|
+
- `GITHUB_RELEASE_ASSETS`: Colon separated list of file paths or glob patterns to attach to the GitHub Release (default: `dist/*`); escape literal colons as `\:` or quote the complete path
|
|
264
266
|
- `SKIP_GITHUB_RELEASE`: Truthy value to skip GitHub Release publication
|
|
265
267
|
- `GITHUB_TOKEN` or `GH_TOKEN`: GitHub authentication token used to publish releases and upload assets
|
|
266
268
|
- `GITHUB_REPOSITORY`: Optional override for the repository slug used when publishing a GitHub Release
|
|
@@ -307,8 +309,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
|
|
|
307
309
|
```toml
|
|
308
310
|
[project]
|
|
309
311
|
name = "simple-cli-tool"
|
|
310
|
-
version = "0.
|
|
311
|
-
dependencies = ["common-python-tasks==0.
|
|
312
|
+
version = "0.5.0"
|
|
313
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
312
314
|
|
|
313
315
|
[tool.poe]
|
|
314
316
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -320,7 +322,7 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
|
|
|
320
322
|
|
|
321
323
|
```toml
|
|
322
324
|
[project]
|
|
323
|
-
dependencies = ["common-python-tasks==0.
|
|
325
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
324
326
|
|
|
325
327
|
[tool.poe]
|
|
326
328
|
include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
|
|
@@ -336,7 +338,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
|
|
|
336
338
|
|
|
337
339
|
```toml
|
|
338
340
|
[project]
|
|
339
|
-
dependencies = ["common-python-tasks==0.
|
|
341
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
340
342
|
|
|
341
343
|
[tool.poe]
|
|
342
344
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -424,7 +426,7 @@ The standard Python Dockerfile incorporates several intentional design choices.
|
|
|
424
426
|
- 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
|
|
425
427
|
- 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
|
|
426
428
|
- 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
|
|
427
|
-
- Safe entrypoint selection: The default entrypoint resolves the console script matching the package name
|
|
429
|
+
- Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python` when no matching script exists. `CUSTOM_ENTRYPOINT` allows an override at build time, and the override is validated strictly against `[project].scripts`
|
|
428
430
|
- Minimal final image: Uses the slim Python base by default, cleans wheel artifacts and caches, and sets `runtime` as the explicit final target so the debug stage remains optional
|
|
429
431
|
|
|
430
432
|
## Notes
|
|
@@ -11,14 +11,16 @@ Instead of writing your own tasks for formatting, linting, testing, packaging, a
|
|
|
11
11
|
You can add `common-python-tasks` to a new project by using the handy automated installation script.
|
|
12
12
|
|
|
13
13
|
```shell
|
|
14
|
-
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/
|
|
14
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add_common_python_tasks.py | jq -r '.content' | base64 -d | python3
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
|
|
18
18
|
|
|
19
19
|
```shell
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add_common_python_tasks.py \
|
|
21
|
+
| jq -r '.content' \
|
|
22
|
+
| base64 -d \
|
|
23
|
+
| COMMON_PYTHON_TASKS_VERSION=0.5.0 python3
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
This will complete the following steps.
|
|
@@ -38,7 +40,7 @@ There's no real reason to run the automated script; I just like automating every
|
|
|
38
40
|
```toml
|
|
39
41
|
[project]
|
|
40
42
|
dependencies = [
|
|
41
|
-
"common-python-tasks==0.
|
|
43
|
+
"common-python-tasks==0.5.0" # Always pin to a specific version
|
|
42
44
|
]
|
|
43
45
|
|
|
44
46
|
[tool.poe]
|
|
@@ -185,7 +187,7 @@ Supported values are `poetry`, `uv`, and `auto`.
|
|
|
185
187
|
|
|
186
188
|
Tasks that need configuration files (`pytest`, `coverage`, `flake8`, `isort`) follow this order of precedence.
|
|
187
189
|
|
|
188
|
-
1. **`pyproject.toml` sections**: `[tool.pytest]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
190
|
+
1. **`pyproject.toml` sections**: `[tool.pytest.ini_options]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
189
191
|
2. **Environment variables**: Override config paths; see [Environment Variables](#environment-variables)
|
|
190
192
|
3. **Local config files**: `pytest.ini`, `.coveragerc`, `.flake8`, `.isort.cfg` in the project root
|
|
191
193
|
4. **Bundled defaults**: Sensible defaults included with this package, found in the [`src/common_python_tasks/data`](src/common_python_tasks/data) directory
|
|
@@ -213,19 +215,19 @@ The following environment variables configure package and container behavior.
|
|
|
213
215
|
- `PACKAGE_MANAGER`: Alias for `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`
|
|
214
216
|
- `CONTAINER_REGISTRY_USERNAME`: Specifies the container registry username for image tagging; the default is the current local user
|
|
215
217
|
- `CONTAINER_REGISTRY_URL`: Specifies the registry URL (default: `docker.io/{username}`)
|
|
216
|
-
- `CONTAINER_BUILD_ARGS`: Provides additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format
|
|
217
|
-
- `CONTAINER_EXTENSION_FILES`: Specifies colon delimited local extension Dockerfile paths to include in the rendered build
|
|
218
|
-
- `CONTAINER_EXTENSIONS`: Specifies colon delimited extension bundle names or parameterized bundle values to include in the rendered build
|
|
219
|
-
- `CONTAINER_ENV`: Provides colon delimited `KEY=VALUE` declarations to inject into the builder stage of the rendered Dockerfile
|
|
218
|
+
- `CONTAINER_BUILD_ARGS`: Provides additional Docker build arguments in `KEY=VALUE:OTHER=VALUE` format. Escape literal colons in values as `\:` or quote the complete value
|
|
219
|
+
- `CONTAINER_EXTENSION_FILES`: Specifies colon delimited local extension Dockerfile paths to include in the rendered build; escape literal colons as `\:` or quote the complete path
|
|
220
|
+
- `CONTAINER_EXTENSIONS`: Specifies colon delimited extension bundle names or parameterized bundle values to include in the rendered build; escape literal colons as `\:` or quote the complete value
|
|
221
|
+
- `CONTAINER_ENV`: Provides colon delimited `KEY=VALUE` declarations to inject into the builder stage of the rendered Dockerfile. Escape literal colons in values as `\:` or quote the complete value
|
|
220
222
|
- `.containerenv`: Can also supply the same declarations from a file in the project root. It is loaded first, then `container_envfile`, then `CONTAINER_ENV`, and finally `container_env`.
|
|
221
223
|
- `CONTAINER_PRUNE_KEEP`: Controls image pruning after builds (`-1` keeps all, `0` keeps the latest only, `N` keeps the latest plus `N` previous)
|
|
222
|
-
- `CUSTOM_ENTRYPOINT`: Specifies a custom entrypoint script name for containers
|
|
224
|
+
- `CUSTOM_ENTRYPOINT`: Specifies a custom entrypoint script name for containers. The value must match a key in `[project].scripts`; unknown values fail the build
|
|
223
225
|
- `CONTAINER_DEPS_CONTENT`: Supplies inline Dockerfile instructions for a dependency image that installs artifacts into `/tmp/deps`
|
|
224
|
-
- `CONTAINER_DEPS_FILE`: Points to one or more explicit Dockerfiles to build the dependency image. It may be a colon delimited list of file paths and is used only when `CONTAINER_DEPS_CONTENT` is unset
|
|
226
|
+
- `CONTAINER_DEPS_FILE`: Points to one or more explicit Dockerfiles to build the dependency image. It may be a colon delimited list of file paths, with literal colons escaped as `\:`, and is used only when `CONTAINER_DEPS_CONTENT` is unset
|
|
225
227
|
- `CONTAINER_DEPS_MAPPINGS`: Maps copied dependency names from `/tmp/deps` into destination paths as space separated `name:/target/path` entries. This is used only when `CONTAINER_DEPS_MOVE_SCRIPT` or `CONTAINER_DEPS_MOVE_SCRIPT_PATH` is not set
|
|
226
228
|
- `CONTAINER_DEPS_MOVE_SCRIPT`: Supplies a raw executable script to run after `/tmp/deps` is copied into the image. The script is written to `/tmp/container-deps-move-script` and executed with its own shebang
|
|
227
229
|
- `CONTAINER_DEPS_MOVE_SCRIPT_PATH`: Supplies a host path to a script file to run after `/tmp/deps` is copied into the image. This path takes precedence over `CONTAINER_DEPS_MOVE_SCRIPT` when both are set
|
|
228
|
-
- `GITHUB_RELEASE_ASSETS`: Colon separated list of file paths or glob patterns to attach to the GitHub Release (default: `dist/*`)
|
|
230
|
+
- `GITHUB_RELEASE_ASSETS`: Colon separated list of file paths or glob patterns to attach to the GitHub Release (default: `dist/*`); escape literal colons as `\:` or quote the complete path
|
|
229
231
|
- `SKIP_GITHUB_RELEASE`: Truthy value to skip GitHub Release publication
|
|
230
232
|
- `GITHUB_TOKEN` or `GH_TOKEN`: GitHub authentication token used to publish releases and upload assets
|
|
231
233
|
- `GITHUB_REPOSITORY`: Optional override for the repository slug used when publishing a GitHub Release
|
|
@@ -272,8 +274,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
|
|
|
272
274
|
```toml
|
|
273
275
|
[project]
|
|
274
276
|
name = "simple-cli-tool"
|
|
275
|
-
version = "0.
|
|
276
|
-
dependencies = ["common-python-tasks==0.
|
|
277
|
+
version = "0.5.0"
|
|
278
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
277
279
|
|
|
278
280
|
[tool.poe]
|
|
279
281
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -285,7 +287,7 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
|
|
|
285
287
|
|
|
286
288
|
```toml
|
|
287
289
|
[project]
|
|
288
|
-
dependencies = ["common-python-tasks==0.
|
|
290
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
289
291
|
|
|
290
292
|
[tool.poe]
|
|
291
293
|
include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
|
|
@@ -301,7 +303,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
|
|
|
301
303
|
|
|
302
304
|
```toml
|
|
303
305
|
[project]
|
|
304
|
-
dependencies = ["common-python-tasks==0.
|
|
306
|
+
dependencies = ["common-python-tasks==0.5.0"]
|
|
305
307
|
|
|
306
308
|
[tool.poe]
|
|
307
309
|
include_script = "common_python_tasks:tasks()"
|
|
@@ -389,7 +391,7 @@ The standard Python Dockerfile incorporates several intentional design choices.
|
|
|
389
391
|
- 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
|
|
390
392
|
- 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
|
|
391
393
|
- 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
|
|
392
|
-
- Safe entrypoint selection: The default entrypoint resolves the console script matching the package name
|
|
394
|
+
- Safe entrypoint selection: The default entrypoint resolves the console script matching the package name and falls back to `python` when no matching script exists. `CUSTOM_ENTRYPOINT` allows an override at build time, and the override is validated strictly against `[project].scripts`
|
|
393
395
|
- Minimal final image: Uses the slim Python base by default, cleans wheel artifacts and caches, and sets `runtime` as the explicit final target so the debug stage remains optional
|
|
394
396
|
|
|
395
397
|
## Notes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tomllib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
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
|
+
PYPROJECT_PATH = Path("pyproject.toml")
|
|
19
|
+
TOOL_POE_PATTERN = re.compile(r"^\s*\[tool\.poe\]\s*(?:#.*)?$")
|
|
20
|
+
TABLE_PATTERN = re.compile(r"^\s*\[")
|
|
21
|
+
INCLUDE_SCRIPT_PATTERN = re.compile(r"^\s*include_script\s*=")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _require_command(command: str) -> None:
|
|
25
|
+
if shutil.which(command) is None:
|
|
26
|
+
sys.exit(f"Required command not found: {command}")
|
|
27
|
+
|
|
28
|
+
|
|
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
|
+
def _get_package_requirement() -> str:
|
|
59
|
+
if os.getenv("COMMON_PYTHON_TASKS_VERSION", "").strip():
|
|
60
|
+
return f"{PACKAGE_NAME}=={os.environ['COMMON_PYTHON_TASKS_VERSION'].strip()}"
|
|
61
|
+
return PACKAGE_NAME
|
|
62
|
+
|
|
63
|
+
|
|
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)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_include_script() -> str:
|
|
73
|
+
if os.getenv("TAGS_TO_INCLUDE", "").split():
|
|
74
|
+
return f"common_python_tasks:tasks(include_tags={os.environ['TAGS_TO_INCLUDE'].split()!r})"
|
|
75
|
+
return "common_python_tasks:tasks()"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _find_tool_poe_section(lines: list[str]) -> tuple[int, int] | None:
|
|
79
|
+
for start_index, line in enumerate(lines):
|
|
80
|
+
if not TOOL_POE_PATTERN.match(line):
|
|
81
|
+
continue
|
|
82
|
+
return start_index, next(
|
|
83
|
+
(
|
|
84
|
+
index
|
|
85
|
+
for index in range(start_index + 1, len(lines))
|
|
86
|
+
if TABLE_PATTERN.match(lines[index])
|
|
87
|
+
),
|
|
88
|
+
len(lines),
|
|
89
|
+
)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _set_include_script(pyproject_text: str, include_script: str) -> str:
|
|
94
|
+
tomllib.loads(pyproject_text)
|
|
95
|
+
lines = pyproject_text.splitlines(keepends=True)
|
|
96
|
+
assignment = f"include_script = {json.dumps(include_script)}\n"
|
|
97
|
+
section = _find_tool_poe_section(lines)
|
|
98
|
+
if section is None:
|
|
99
|
+
return f"{pyproject_text.rstrip()}\n\n[tool.poe]\n{assignment}"
|
|
100
|
+
|
|
101
|
+
section_start, section_end = section
|
|
102
|
+
for index in range(section_start + 1, section_end):
|
|
103
|
+
if INCLUDE_SCRIPT_PATTERN.match(lines[index]):
|
|
104
|
+
lines[index] = assignment
|
|
105
|
+
return "".join(lines)
|
|
106
|
+
|
|
107
|
+
lines.insert(section_start + 1, assignment)
|
|
108
|
+
return "".join(lines)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _configure_poe() -> None:
|
|
112
|
+
PYPROJECT_PATH.write_text(
|
|
113
|
+
_set_include_script(
|
|
114
|
+
PYPROJECT_PATH.read_text(encoding="utf-8"),
|
|
115
|
+
_get_include_script(),
|
|
116
|
+
),
|
|
117
|
+
encoding="utf-8",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _print_available_tasks() -> None:
|
|
122
|
+
result = subprocess.run(
|
|
123
|
+
["poe", "--help", "--ansi"],
|
|
124
|
+
check=True,
|
|
125
|
+
capture_output=True,
|
|
126
|
+
shell=False,
|
|
127
|
+
text=True,
|
|
128
|
+
)
|
|
129
|
+
_, marker, configured_tasks = result.stdout.partition("Configured tasks:")
|
|
130
|
+
print(configured_tasks.strip() if marker else result.stdout.strip())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def main() -> None:
|
|
134
|
+
"""Install common tasks and configure Poe in the current project."""
|
|
135
|
+
_install_package(_select_package_manager())
|
|
136
|
+
_configure_poe()
|
|
137
|
+
print("\n\033[1;32mCommon Python tasks added to project.\033[0m")
|
|
138
|
+
print("\n\033[1mAvailable tasks:\033[0m")
|
|
139
|
+
_print_available_tasks()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|
|
File without changes
|
|
@@ -186,11 +186,12 @@ def split_delimited_values(
|
|
|
186
186
|
separators: str = ":",
|
|
187
187
|
allow_whitespace: bool = False,
|
|
188
188
|
) -> list[str]:
|
|
189
|
-
"""Split a delimited string while preserving quoted
|
|
189
|
+
"""Split a delimited string while preserving quoted or escaped separators.
|
|
190
190
|
|
|
191
191
|
Args:
|
|
192
192
|
value: The string to split.
|
|
193
|
-
separators: Explicit separator characters to use.
|
|
193
|
+
separators: Explicit separator characters to use. Prefix a separator with
|
|
194
|
+
a backslash to include it literally in a token.
|
|
194
195
|
allow_whitespace: Treat whitespace as additional separators.
|
|
195
196
|
|
|
196
197
|
Returns:
|
|
@@ -207,7 +208,7 @@ def split_delimited_values(
|
|
|
207
208
|
|
|
208
209
|
|
|
209
210
|
def split_colon_delimited_values(value: str) -> list[str]:
|
|
210
|
-
"""Split
|
|
211
|
+
"""Split colon-delimited values while preserving quoted or escaped colons."""
|
|
211
212
|
return split_delimited_values(value, separators=":", allow_whitespace=True)
|
|
212
213
|
|
|
213
214
|
|
|
@@ -221,7 +222,7 @@ def parse_container_build_args(
|
|
|
221
222
|
env_build_args: str | None,
|
|
222
223
|
) -> dict[str, str] | None:
|
|
223
224
|
"""Parse container build args from CLI or environment input."""
|
|
224
|
-
if cli_build_args
|
|
225
|
+
if cli_build_args and any(token.strip() for token in cli_build_args):
|
|
225
226
|
build_arg_tokens = [
|
|
226
227
|
token.strip() for token in cli_build_args if token and token.strip()
|
|
227
228
|
]
|
|
@@ -233,10 +234,15 @@ def parse_container_build_args(
|
|
|
233
234
|
parsed_build_args: dict[str, str] = {}
|
|
234
235
|
for token in build_arg_tokens:
|
|
235
236
|
if "=" not in token:
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
utils.fatal(
|
|
238
|
+
f"Invalid build-arg token {token!r}; expected KEY=VALUE. "
|
|
239
|
+
"Escape literal colons in values as \\: or quote the value."
|
|
240
|
+
)
|
|
238
241
|
key, value = token.split("=", 1)
|
|
239
|
-
|
|
242
|
+
key = key.strip()
|
|
243
|
+
if not key:
|
|
244
|
+
utils.fatal(f"Invalid build-arg token {token!r}; the key must not be empty")
|
|
245
|
+
parsed_build_args[key] = value.strip()
|
|
240
246
|
|
|
241
247
|
return parsed_build_args or None
|
|
242
248
|
|
|
@@ -254,13 +260,20 @@ def parse_container_env_tokens(value: str | list[str] | None) -> list[str]:
|
|
|
254
260
|
return []
|
|
255
261
|
|
|
256
262
|
if isinstance(value, list):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if not value.strip():
|
|
263
|
+
tokens = [token.strip() for token in value if token and token.strip()]
|
|
264
|
+
elif not value.strip():
|
|
260
265
|
return []
|
|
266
|
+
else:
|
|
267
|
+
tokens = split_colon_delimited_values(value)
|
|
261
268
|
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
for token in tokens:
|
|
270
|
+
if "=" not in token or not token.split("=", 1)[0].strip():
|
|
271
|
+
utils.fatal(
|
|
272
|
+
f"Invalid container env token {token!r}; expected KEY=VALUE. "
|
|
273
|
+
"Escape literal colons in values as \\: or quote the value."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return tokens
|
|
264
277
|
|
|
265
278
|
|
|
266
279
|
def _parse_container_envfile_paths(
|
|
@@ -3,6 +3,8 @@ import logging
|
|
|
3
3
|
import mimetypes
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from functools import lru_cache
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any, Literal
|
|
8
10
|
|
|
@@ -39,7 +41,7 @@ def should_publish_github_release() -> bool:
|
|
|
39
41
|
"""Return whether GitHub Releases publication is enabled.
|
|
40
42
|
|
|
41
43
|
Returns:
|
|
42
|
-
True if GitHub Release publishing is enabled, False if it is skipped.
|
|
44
|
+
`True` if GitHub Release publishing is enabled, `False` if it is skipped.
|
|
43
45
|
"""
|
|
44
46
|
return not env_truthy("SKIP_GITHUB_RELEASE")
|
|
45
47
|
|
|
@@ -51,13 +53,25 @@ def get_github_repository() -> str | None:
|
|
|
51
53
|
The GitHub repository slug in the form `owner/repo`, or `None` if it
|
|
52
54
|
cannot be determined.
|
|
53
55
|
"""
|
|
54
|
-
|
|
56
|
+
return _get_github_repository(
|
|
57
|
+
os.getenv("GITHUB_REPOSITORY", "").strip(),
|
|
58
|
+
Path.cwd(),
|
|
59
|
+
utils.run_command,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@lru_cache(maxsize=32)
|
|
64
|
+
def _get_github_repository(
|
|
65
|
+
repository: str,
|
|
66
|
+
_working_directory: Path,
|
|
67
|
+
run_command: Callable[..., Any],
|
|
68
|
+
) -> str | None:
|
|
55
69
|
if repository:
|
|
56
70
|
if re.fullmatch(r"[^/]+/[^/]+", repository):
|
|
57
71
|
return repository
|
|
58
72
|
LOGGER.warning("Ignoring invalid GITHUB_REPOSITORY value: %s", repository)
|
|
59
73
|
|
|
60
|
-
result =
|
|
74
|
+
result = run_command(
|
|
61
75
|
["git", "remote", "get-url", "origin"],
|
|
62
76
|
capture_output=True,
|
|
63
77
|
acceptable_returncodes={0, 128},
|
|
@@ -94,8 +108,13 @@ def get_github_token() -> str | None:
|
|
|
94
108
|
if token:
|
|
95
109
|
return token
|
|
96
110
|
|
|
111
|
+
return _get_github_cli_token(utils.run_command)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@lru_cache(maxsize=8)
|
|
115
|
+
def _get_github_cli_token(run_command: Callable[..., Any]) -> str | None:
|
|
97
116
|
try:
|
|
98
|
-
result =
|
|
117
|
+
result = run_command(
|
|
99
118
|
["gh", "auth", "token"],
|
|
100
119
|
capture_output=True,
|
|
101
120
|
acceptable_returncodes={0, 1},
|
|
@@ -132,13 +151,18 @@ def get_github_api_base_url() -> str:
|
|
|
132
151
|
The base URL for GitHub API requests, defaulting to GitHub.com API or
|
|
133
152
|
GitHub Enterprise API if configured.
|
|
134
153
|
"""
|
|
135
|
-
|
|
154
|
+
return _get_github_api_base_url(
|
|
155
|
+
os.getenv("GITHUB_API_URL", "").strip(),
|
|
156
|
+
os.getenv("GITHUB_SERVER_URL", "https://github.com").strip(),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@lru_cache(maxsize=32)
|
|
161
|
+
def _get_github_api_base_url(api_url: str, server_url: str) -> str:
|
|
136
162
|
if api_url:
|
|
137
163
|
return api_url.rstrip("/")
|
|
138
164
|
|
|
139
|
-
server_url = (
|
|
140
|
-
os.getenv("GITHUB_SERVER_URL", "https://github.com").strip().rstrip("/")
|
|
141
|
-
)
|
|
165
|
+
server_url = server_url.rstrip("/")
|
|
142
166
|
if server_url.endswith("github.com"):
|
|
143
167
|
return "https://api.github.com"
|
|
144
168
|
return f"{server_url}/api/v3"
|
|
@@ -156,7 +180,11 @@ def get_github_upload_api_base_url(api_base_url: str | None = None) -> str:
|
|
|
156
180
|
Returns:
|
|
157
181
|
The upload base URL for GitHub API requests.
|
|
158
182
|
"""
|
|
159
|
-
|
|
183
|
+
return _get_github_upload_api_base_url(api_base_url or get_github_api_base_url())
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@lru_cache(maxsize=32)
|
|
187
|
+
def _get_github_upload_api_base_url(api_base: str) -> str:
|
|
160
188
|
if api_base == "https://api.github.com":
|
|
161
189
|
return "https://uploads.github.com"
|
|
162
190
|
return api_base
|
|
@@ -18,6 +18,11 @@ from . import utils
|
|
|
18
18
|
LOGGER = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
PACKAGE_MANAGER_ENV_VAR = "COMMON_PYTHON_TASKS_PACKAGE_MANAGER"
|
|
21
|
+
PACKAGE_MANAGER_ENV_VARS = (
|
|
22
|
+
PACKAGE_MANAGER_ENV_VAR,
|
|
23
|
+
"COMMON_PYTHON_TASKS_BUILD_TOOL",
|
|
24
|
+
"PACKAGE_MANAGER",
|
|
25
|
+
)
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
class PackageManager(StrEnum):
|
|
@@ -45,9 +50,10 @@ def _parse_tool_version_output(output: str, tool_name: str) -> str:
|
|
|
45
50
|
|
|
46
51
|
|
|
47
52
|
def _find_package_manager_override() -> tuple[str, str] | None:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
for env_var in PACKAGE_MANAGER_ENV_VARS:
|
|
54
|
+
value = os.getenv(env_var)
|
|
55
|
+
if value is not None and value.strip():
|
|
56
|
+
return env_var, value.strip().lower()
|
|
51
57
|
return None
|
|
52
58
|
|
|
53
59
|
|
|
@@ -105,7 +111,8 @@ def get_package_manager() -> PackageManager:
|
|
|
105
111
|
"""Resolve the package manager backend used for build and publish commands.
|
|
106
112
|
|
|
107
113
|
Resolution order:
|
|
108
|
-
1. Environment
|
|
114
|
+
1. Environment overrides via `COMMON_PYTHON_TASKS_PACKAGE_MANAGER`,
|
|
115
|
+
`COMMON_PYTHON_TASKS_BUILD_TOOL`, or `PACKAGE_MANAGER`, in that order.
|
|
109
116
|
2. Auto-discovery from installed executables and project metadata.
|
|
110
117
|
|
|
111
118
|
Supported values are `poetry`, `uv`, and `auto`.
|
|
@@ -374,7 +381,13 @@ def read_pyproject_toml() -> dict[str, Any]:
|
|
|
374
381
|
return tomllib.loads(Path("pyproject.toml").read_text())
|
|
375
382
|
|
|
376
383
|
|
|
377
|
-
|
|
384
|
+
BLACK_TARGET_PYTHON_MINORS = range(7, 17)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _specifier_supports_python_minor(specifier_set: SpecifierSet, minor: int) -> bool:
|
|
388
|
+
return any(
|
|
389
|
+
specifier_set.contains(Version(f"3.{minor}.{patch}")) for patch in range(1000)
|
|
390
|
+
)
|
|
378
391
|
|
|
379
392
|
|
|
380
393
|
def get_black_target_version() -> str | None:
|
|
@@ -394,9 +407,9 @@ def get_black_target_version() -> str | None:
|
|
|
394
407
|
LOGGER.warning("Unable to parse requires-python specifier: %s", requires_python)
|
|
395
408
|
return None
|
|
396
409
|
|
|
397
|
-
for
|
|
398
|
-
if specifier_set
|
|
399
|
-
return f"
|
|
410
|
+
for minor in BLACK_TARGET_PYTHON_MINORS:
|
|
411
|
+
if _specifier_supports_python_minor(specifier_set, minor):
|
|
412
|
+
return f"py3{minor:02d}"
|
|
400
413
|
return None
|
|
401
414
|
|
|
402
415
|
|
|
@@ -413,22 +426,57 @@ def has_debug_dependency_group() -> bool:
|
|
|
413
426
|
return bool(debug_group)
|
|
414
427
|
|
|
415
428
|
|
|
416
|
-
def
|
|
429
|
+
def _get_project_scripts() -> dict[str, str]:
|
|
430
|
+
scripts = read_pyproject_toml().get("project", {}).get("scripts")
|
|
431
|
+
if not isinstance(scripts, dict):
|
|
432
|
+
return {}
|
|
433
|
+
|
|
434
|
+
normalized_scripts = {
|
|
435
|
+
str(script_name).strip(): str(script_target)
|
|
436
|
+
for script_name, script_target in scripts.items()
|
|
437
|
+
if str(script_name).strip()
|
|
438
|
+
}
|
|
439
|
+
return normalized_scripts
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _get_available_container_entrypoint_scripts() -> tuple[str, ...]:
|
|
443
|
+
return tuple(sorted(_get_project_scripts().keys()))
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def resolve_container_entrypoint_command(entrypoint_script: str | None = None) -> str:
|
|
417
447
|
"""Resolve the command used in the generated container entrypoint script.
|
|
418
448
|
|
|
419
449
|
Args:
|
|
420
|
-
|
|
450
|
+
entrypoint_script: Optional explicit entrypoint script override.
|
|
421
451
|
|
|
422
452
|
Returns:
|
|
423
453
|
A command name to place in `/pkg/entrypoint.sh`.
|
|
424
454
|
"""
|
|
425
455
|
|
|
426
|
-
|
|
427
|
-
|
|
456
|
+
available_scripts = _get_available_container_entrypoint_scripts()
|
|
457
|
+
scripts_set = set(available_scripts)
|
|
458
|
+
|
|
459
|
+
if entrypoint_script is not None and entrypoint_script.strip():
|
|
460
|
+
selected_entrypoint = entrypoint_script.strip()
|
|
461
|
+
if selected_entrypoint in scripts_set:
|
|
462
|
+
return selected_entrypoint
|
|
463
|
+
|
|
464
|
+
if available_scripts:
|
|
465
|
+
utils.fatal(
|
|
466
|
+
"Invalid CUSTOM_ENTRYPOINT value "
|
|
467
|
+
f"{selected_entrypoint!r}. "
|
|
468
|
+
"It must match a key in [project].scripts. "
|
|
469
|
+
f"Available scripts: {', '.join(available_scripts)}"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
utils.fatal(
|
|
473
|
+
"Invalid CUSTOM_ENTRYPOINT value "
|
|
474
|
+
f"{selected_entrypoint!r}. "
|
|
475
|
+
"No [project].scripts entries were found in pyproject.toml."
|
|
476
|
+
)
|
|
428
477
|
|
|
429
478
|
script_name = utils.get_package_name(use_underscores=True)
|
|
430
|
-
|
|
431
|
-
if isinstance(scripts, dict) and script_name in scripts:
|
|
479
|
+
if script_name in scripts_set:
|
|
432
480
|
return script_name
|
|
433
481
|
|
|
434
482
|
return "python"
|
|
@@ -582,7 +582,7 @@ def build_image(
|
|
|
582
582
|
|
|
583
583
|
apt_packages = (parsed_build_args or {}).get("APT_PACKAGES", "").strip()
|
|
584
584
|
entrypoint_command = resolve_container_entrypoint_command(
|
|
585
|
-
(parsed_build_args or {}).get("CUSTOM_ENTRYPOINT")
|
|
585
|
+
entrypoint_script=(parsed_build_args or {}).get("CUSTOM_ENTRYPOINT")
|
|
586
586
|
)
|
|
587
587
|
container_env_vars = load_container_env_tokens(
|
|
588
588
|
container_env,
|
|
@@ -884,7 +884,7 @@ def run_container(
|
|
|
884
884
|
f"No local images found for {package_name}. Build the image first or specify a tag."
|
|
885
885
|
)
|
|
886
886
|
|
|
887
|
-
LOGGER.
|
|
887
|
+
LOGGER.debug("Running container %s", selected_image)
|
|
888
888
|
run_args = ["docker", "run", "--rm", "-i", "-t"]
|
|
889
889
|
if privileged:
|
|
890
890
|
run_args.append("--privileged")
|
|
@@ -1088,6 +1088,26 @@ def changelog() -> None:
|
|
|
1088
1088
|
print(_changelog())
|
|
1089
1089
|
|
|
1090
1090
|
|
|
1091
|
+
def _build_and_publish_release_package(release_tag: str) -> None:
|
|
1092
|
+
from .utils import run_command
|
|
1093
|
+
|
|
1094
|
+
package_published = False
|
|
1095
|
+
try:
|
|
1096
|
+
build_package(clean_dist=True)
|
|
1097
|
+
publish_package(build_first=False)
|
|
1098
|
+
package_published = True
|
|
1099
|
+
finally:
|
|
1100
|
+
if not package_published:
|
|
1101
|
+
LOGGER.warning(
|
|
1102
|
+
"Package build or publication failed; deleting local release tag %s",
|
|
1103
|
+
release_tag,
|
|
1104
|
+
)
|
|
1105
|
+
run_command(
|
|
1106
|
+
["git", "tag", "--delete", release_tag],
|
|
1107
|
+
acceptable_returncodes={0, 1},
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
|
|
1091
1111
|
def _run_release_flow(
|
|
1092
1112
|
component: str = "auto",
|
|
1093
1113
|
stage: str | None = None,
|
|
@@ -1167,8 +1187,7 @@ def _run_release_flow(
|
|
|
1167
1187
|
run_command(["sh", "-lc", post_script], env=hook_env, dry_run=False)
|
|
1168
1188
|
return
|
|
1169
1189
|
|
|
1170
|
-
|
|
1171
|
-
publish_package(build_first=False)
|
|
1190
|
+
_build_and_publish_release_package(hook_env["RELEASE_TAG"])
|
|
1172
1191
|
|
|
1173
1192
|
if include_containers:
|
|
1174
1193
|
build_image(
|
|
@@ -1410,6 +1429,7 @@ def fastapi_stack_up(
|
|
|
1410
1429
|
from .env import (
|
|
1411
1430
|
get_cache_id_suffix,
|
|
1412
1431
|
load_container_env_tokens,
|
|
1432
|
+
parse_container_build_args,
|
|
1413
1433
|
)
|
|
1414
1434
|
from .project import (
|
|
1415
1435
|
get_package_manager,
|
|
@@ -1429,6 +1449,13 @@ def fastapi_stack_up(
|
|
|
1429
1449
|
)
|
|
1430
1450
|
|
|
1431
1451
|
container_env_vars = load_container_env_tokens(container_env, container_envfile)
|
|
1452
|
+
parsed_build_args = parse_container_build_args(
|
|
1453
|
+
build_args,
|
|
1454
|
+
os.getenv("CONTAINER_BUILD_ARGS"),
|
|
1455
|
+
)
|
|
1456
|
+
entrypoint_command = resolve_container_entrypoint_command(
|
|
1457
|
+
entrypoint_script=(parsed_build_args or {}).get("CUSTOM_ENTRYPOINT")
|
|
1458
|
+
)
|
|
1432
1459
|
|
|
1433
1460
|
cache_id_suffix = get_cache_id_suffix(no_cache)
|
|
1434
1461
|
|
|
@@ -1439,7 +1466,7 @@ def fastapi_stack_up(
|
|
|
1439
1466
|
"EXTENSION_CONTENT": "",
|
|
1440
1467
|
"HAS_DEBUG_DEPS": has_debug_deps,
|
|
1441
1468
|
"APT_PACKAGES": "",
|
|
1442
|
-
"ENTRYPOINT_COMMAND":
|
|
1469
|
+
"ENTRYPOINT_COMMAND": entrypoint_command,
|
|
1443
1470
|
"CONTAINER_ENV_VARS": container_env_vars,
|
|
1444
1471
|
"CACHE_ID_SUFFIX": cache_id_suffix,
|
|
1445
1472
|
},
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
|
|
3
|
-
# This script adds the common-python-tasks package to the current project
|
|
4
|
-
# and updates the pyproject.toml file to include the requested tasks.
|
|
5
|
-
|
|
6
|
-
set -e
|
|
7
|
-
|
|
8
|
-
pkg="common-python-tasks"
|
|
9
|
-
if [ -n "$COMMON_PYTHON_TASKS_VERSION" ]; then
|
|
10
|
-
ver="$COMMON_PYTHON_TASKS_VERSION"
|
|
11
|
-
else
|
|
12
|
-
searchOutput=$(poetry search "$pkg")
|
|
13
|
-
if [ "$searchOutput" = "No matching packages were found." ]; then
|
|
14
|
-
printf 'Package %s not found\n' "$pkg" >&2
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
ver=$(printf "%s" "$searchOutput" | awk -v p="$pkg" '$1==p{print $2}' | tail -n1)
|
|
18
|
-
fi
|
|
19
|
-
if [ -n "$ver" ]; then
|
|
20
|
-
poetry add --group dev "$pkg==$ver" || exit 1
|
|
21
|
-
else
|
|
22
|
-
printf 'Error parsing version for %s\n' "$pkg" >&2
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
script=$([ -n "$TAGS_TO_INCLUDE" ] && python -c "import sys; tags = sys.argv[1].split(); print('common_python_tasks:tasks(include_tags='+repr(tags)+')')" "$TAGS_TO_INCLUDE" || echo "common_python_tasks:tasks()")
|
|
27
|
-
cat >>pyproject.toml <<EOF
|
|
28
|
-
|
|
29
|
-
[tool.poe]
|
|
30
|
-
include_script = "$script"
|
|
31
|
-
|
|
32
|
-
EOF
|
|
33
|
-
|
|
34
|
-
printf "\n\033[1;32mCommon Python tasks added to project.\033[0m\n\n\033[1mAvailable tasks:\033[0m\n"
|
|
35
|
-
poe --help --ansi | awk '/Configured tasks:/ {flag=1; next} flag'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/data/generic/.flake8
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{common_python_tasks-0.4.1 → common_python_tasks-0.5.0}/src/common_python_tasks/docker_compose.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|