common-python-tasks 0.0.1__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.0.1/LICENSE +21 -0
- common_python_tasks-0.0.1/PKG-INFO +295 -0
- common_python_tasks-0.0.1/README.md +262 -0
- common_python_tasks-0.0.1/pyproject.toml +61 -0
- common_python_tasks-0.0.1/src/common_python_tasks/__init__.py +16 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/.coveragerc +8 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/.dockerignore +10 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/.flake8 +9 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/.isort.cfg +4 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/Containerfile +76 -0
- common_python_tasks-0.0.1/src/common_python_tasks/data/pytest.ini +4 -0
- common_python_tasks-0.0.1/src/common_python_tasks/tasks.py +870 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joseph Asbury
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: common-python-tasks
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Opinionated Poe the Poet tasks for Python package development.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Joseph Asbury
|
|
8
|
+
Author-email: ci_sourcerer@yahoo.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
19
|
+
Requires-Dist: autoflake (>=2.3.1,<3.0.0)
|
|
20
|
+
Requires-Dist: black (>=25.11.0,<26.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: isort (>=7.0.0,<8.0.0)
|
|
24
|
+
Requires-Dist: poethepoet-tasks (>=0.3.0,<0.4.0)
|
|
25
|
+
Requires-Dist: pytest (>=9.0.1,<10.0.0)
|
|
26
|
+
Requires-Dist: pytest-cov (>=7.0.0,<8.0.0)
|
|
27
|
+
Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
|
|
28
|
+
Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
|
|
29
|
+
Project-URL: issues, http://github.com/ci-sourcerer/common-python-tasks/issues
|
|
30
|
+
Project-URL: source, http://github.com/ci-sourcerer/common-python-tasks
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Common Python tasks
|
|
34
|
+
|
|
35
|
+
This package is a collection of (very) opinionated [Poe the Poet Python tasks](https://poethepoet.natn.io/guides/packaged_tasks.html) for common Python development workflows.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
### Automated setup
|
|
40
|
+
|
|
41
|
+
You can add `common-python-tasks` to a new project by using the handy automated installation script.
|
|
42
|
+
|
|
43
|
+
```shell
|
|
44
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add-common-python-tasks.sh | TAGS_TO_INCLUDE="format lint test" sh
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This will complete the following steps.
|
|
48
|
+
|
|
49
|
+
1. Add the latest version of `common-python-tasks` to your `pyproject.toml` dependencies
|
|
50
|
+
2. Configure Poe the Poet to include only the tasks with the specified tags
|
|
51
|
+
3. Install the package using Poetry
|
|
52
|
+
|
|
53
|
+
**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.
|
|
54
|
+
|
|
55
|
+
### Manual setup
|
|
56
|
+
|
|
57
|
+
1. Add `common-python-tasks` to your `pyproject.toml` and configure Poe the Poet to include the desired tasks
|
|
58
|
+
|
|
59
|
+
```toml
|
|
60
|
+
[project]
|
|
61
|
+
name = "my-awesome-project"
|
|
62
|
+
version = "0.0.1"
|
|
63
|
+
dependencies = [
|
|
64
|
+
"common-python-tasks==0.0.1", # Always pin to a specific version
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.poe]
|
|
68
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint', 'test'])" # Include or exclude tasks by tags
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. Install the package
|
|
72
|
+
|
|
73
|
+
```shell
|
|
74
|
+
poetry install
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
3. Run tasks
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
poe format # Format your code
|
|
81
|
+
poe lint # Check code quality
|
|
82
|
+
poe test # Run tests with coverage
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Available tasks
|
|
86
|
+
|
|
87
|
+
Internal tasks are used by other tasks and are not meant to be run directly.
|
|
88
|
+
|
|
89
|
+
| Task | Description | Tags |
|
|
90
|
+
| - | - | - |
|
|
91
|
+
| `build` | Build the project; also builds container images when the `containers` tag is included | packaging, containers |
|
|
92
|
+
| `build-image` | Build a container image using the bundled Containerfile template | containers, build |
|
|
93
|
+
| `build-package` | Build the package (wheel and sdist) | packaging, build |
|
|
94
|
+
| `bump-version` | Bump project version and create a git tag | packaging |
|
|
95
|
+
| `clean` | Remove build, cache, and coverage artifacts | clean |
|
|
96
|
+
| `format` | Format code with autoflake, black, and isort | format |
|
|
97
|
+
| `lint` | Run autoflake, black, isort checks, and flake8 linting | lint |
|
|
98
|
+
| `publish-package` | Publish the package to PyPI via Poetry | packaging |
|
|
99
|
+
| `push-image` | Push container images to the configured registry | containers, packaging, release |
|
|
100
|
+
| `run-container` | Run the built container image with the selected tag | containers |
|
|
101
|
+
| `test` | Run tests with pytest and generate coverage reports | test |
|
|
102
|
+
|
|
103
|
+
## How it works
|
|
104
|
+
|
|
105
|
+
### Prerequisites
|
|
106
|
+
|
|
107
|
+
Your project must meet the following requirements.
|
|
108
|
+
|
|
109
|
+
- Use Poetry for dependency management
|
|
110
|
+
- Have a `pyproject.toml` file at the root
|
|
111
|
+
- Have a package name (automatically inferred from `project.name` in `pyproject.toml`, or set via `PACKAGE_NAME` environment variable)
|
|
112
|
+
|
|
113
|
+
### Configuration precedence
|
|
114
|
+
|
|
115
|
+
Tasks that need configuration files (`pytest`, `coverage`, `flake8`, `isort`) follow this order of precedence.
|
|
116
|
+
|
|
117
|
+
1. **`pyproject.toml` sections** - `[tool.pytest]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
118
|
+
2. **Environment variables** - Override config paths (see [Environment Variables](#environment-variables))
|
|
119
|
+
3. **Local config files** - `pytest.ini`, `.coveragerc`, `.flake8`, `.isort.cfg` in project root
|
|
120
|
+
4. **Bundled defaults** - Sensible defaults included with this package, found in the [`src/common_python_tasks/data`](src/common_python_tasks/data) directory
|
|
121
|
+
|
|
122
|
+
You can start with zero configuration and customize as needed.
|
|
123
|
+
|
|
124
|
+
### Environment variables
|
|
125
|
+
|
|
126
|
+
#### Configuration files
|
|
127
|
+
|
|
128
|
+
The following environment variables configure the paths to configuration files.
|
|
129
|
+
|
|
130
|
+
- `PYTEST_CONFIG` specifies the path to the pytest configuration file
|
|
131
|
+
- `COVERAGE_RCFILE` specifies the path to the coverage configuration file
|
|
132
|
+
- `FLAKE8_CONFIG` specifies the path to the flake8 configuration file
|
|
133
|
+
- `ISORT_CONFIG` specifies the path to the isort configuration file
|
|
134
|
+
|
|
135
|
+
#### Package/Container settings
|
|
136
|
+
|
|
137
|
+
The following environment variables configure package and container behavior.
|
|
138
|
+
|
|
139
|
+
- `PACKAGE_NAME` overrides the package name (default is from `pyproject.toml`)
|
|
140
|
+
- `POETRY_VERSION` overrides the Poetry version for container builds
|
|
141
|
+
- `DOCKERHUB_USERNAME` specifies the Docker Hub username for image tagging (default is current local user)
|
|
142
|
+
- `CONTAINER_REGISTRY_URL` specifies the registry URL (default is `docker.io/{username}`)
|
|
143
|
+
- `CUSTOM_IMAGE_ENTRYPOINT` specifies a custom entrypoint script name for containers
|
|
144
|
+
|
|
145
|
+
#### Debugging
|
|
146
|
+
|
|
147
|
+
The following environment variable enables debugging output.
|
|
148
|
+
|
|
149
|
+
- `COMMON_PYTHON_TASKS_LOG_LEVEL` should be set to `DEBUG` to see detailed configuration resolution
|
|
150
|
+
|
|
151
|
+
### Usage examples
|
|
152
|
+
|
|
153
|
+
You can include or exclude tasks by tags in your `pyproject.toml`
|
|
154
|
+
|
|
155
|
+
#### Minimal setup
|
|
156
|
+
|
|
157
|
+
```toml
|
|
158
|
+
[project]
|
|
159
|
+
name = "simple-cli-tool"
|
|
160
|
+
version = "0.0.1"
|
|
161
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
162
|
+
|
|
163
|
+
[tool.poe]
|
|
164
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint'])"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Available tasks: `format`, `lint`.
|
|
168
|
+
|
|
169
|
+
#### Container-based project
|
|
170
|
+
|
|
171
|
+
```toml
|
|
172
|
+
[project]
|
|
173
|
+
name = "containerized-app"
|
|
174
|
+
version = "0.0.1"
|
|
175
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
176
|
+
|
|
177
|
+
[tool.poe]
|
|
178
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint', 'test', 'containers'])"
|
|
179
|
+
|
|
180
|
+
[tool.poe.env]
|
|
181
|
+
DOCKERHUB_USERNAME = "myusername"
|
|
182
|
+
PACKAGE_NAME = "containerized-app"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Available tasks: All tasks including `build-image` and `push-image`.
|
|
186
|
+
|
|
187
|
+
#### Custom pytest configuration
|
|
188
|
+
|
|
189
|
+
```toml
|
|
190
|
+
[project]
|
|
191
|
+
name = "custom-test-setup"
|
|
192
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
193
|
+
dynamic = ["version"]
|
|
194
|
+
|
|
195
|
+
[tool.poe]
|
|
196
|
+
include_script = "common_python_tasks:tasks(include_tags=['test'])"
|
|
197
|
+
|
|
198
|
+
[tool.pytest.ini_options]
|
|
199
|
+
testpaths = ["tests", "integration"]
|
|
200
|
+
addopts = "-ra"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The `test` task will automatically use your `[tool.pytest.ini_options]` configuration.
|
|
204
|
+
|
|
205
|
+
## Release workflow
|
|
206
|
+
|
|
207
|
+
The `release` tag is used to identify tasks that are part of the release process. To perform a complete release, follow these steps.
|
|
208
|
+
|
|
209
|
+
```shell
|
|
210
|
+
# 1. Ensure all changes are committed
|
|
211
|
+
git add .
|
|
212
|
+
git commit -m "Prepare for release" # You probably want a better commit message than this
|
|
213
|
+
|
|
214
|
+
# 2. Bump the version (creates a git tag)
|
|
215
|
+
poe bump-version patch # or 'minor', 'major'; for pre-releases: poe bump-version patch --stage alpha
|
|
216
|
+
|
|
217
|
+
# 3. Build the package
|
|
218
|
+
poetry build
|
|
219
|
+
|
|
220
|
+
# 4. Publish to PyPI
|
|
221
|
+
poe publish-package
|
|
222
|
+
|
|
223
|
+
# 5. (Optional) If using containers
|
|
224
|
+
poe build-image
|
|
225
|
+
poe push-image
|
|
226
|
+
|
|
227
|
+
# 6. Push tags to remote
|
|
228
|
+
git push --tags
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Troubleshooting
|
|
232
|
+
|
|
233
|
+
### "No tests were collected"
|
|
234
|
+
|
|
235
|
+
The `test` task exits with code 5 if no tests are found. You can address this in one of the following ways.
|
|
236
|
+
|
|
237
|
+
- Add tests to your `tests/` directory
|
|
238
|
+
- Exclude the `test` tag and simply do not run `poe test` with this configuration `include_script = "common_python_tasks:tasks(exclude_tags=['test', 'internal'])"`
|
|
239
|
+
|
|
240
|
+
### Tasks not showing up with `poe --help`
|
|
241
|
+
|
|
242
|
+
Check your `[tool.poe]` configuration in `pyproject.toml`. Make sure you're using `include_script`, not `includes`.
|
|
243
|
+
|
|
244
|
+
```toml
|
|
245
|
+
# Correct
|
|
246
|
+
[tool.poe]
|
|
247
|
+
include_script = "common_python_tasks:tasks(exclude_tags=['internal'])"
|
|
248
|
+
|
|
249
|
+
# Incorrect
|
|
250
|
+
[tool.poe]
|
|
251
|
+
includes = "common_python_tasks:tasks"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Version bump fails with "no changes since last tag"
|
|
255
|
+
|
|
256
|
+
This is expected behavior. The `bump-version` task requires commits between the last tag and HEAD. You can resolve this in one of the following ways.
|
|
257
|
+
|
|
258
|
+
- Make changes and commit them first
|
|
259
|
+
- If you need to re-tag the same commit, delete the old tag (for example, `git tag -d v0.0.1`). This is not recommended. Versions should be immutable, and if you need to fix something, you should create a new patch version instead
|
|
260
|
+
|
|
261
|
+
### Config files not being used
|
|
262
|
+
|
|
263
|
+
Check the configuration precedence (see [How it works](#how-it-works)). Use debug logging to see which config is selected.
|
|
264
|
+
|
|
265
|
+
```shell
|
|
266
|
+
COMMON_PYTHON_TASKS_LOG_LEVEL=DEBUG poe test
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Container build fails with "unable to find package"
|
|
270
|
+
|
|
271
|
+
Make sure your `pyproject.toml` contains the following.
|
|
272
|
+
|
|
273
|
+
- A correct package name in `[project]`
|
|
274
|
+
- A package location defined with this configuration `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`
|
|
275
|
+
|
|
276
|
+
## Design choices
|
|
277
|
+
|
|
278
|
+
### Containerfile (see [src/common_python_tasks/data/Containerfile](src/common_python_tasks/data/Containerfile))
|
|
279
|
+
|
|
280
|
+
The standard Python Containerfile incorporates several intentional design choices.
|
|
281
|
+
|
|
282
|
+
- 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
|
|
283
|
+
- Cache-aware installs mean pip and Poetry cache mounts speed up iterative builds without bloating the final image
|
|
284
|
+
- Explicit inputs through build args (`PYTHON_VERSION`, `POETRY_VERSION`, `PACKAGE_NAME`, `AUTHORS`, `GIT_COMMIT`, `CUSTOM_ENTRYPOINT`) make image metadata and behavior predictable and auditable
|
|
285
|
+
- 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
|
|
286
|
+
- 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, which ensures that the package can be reliably imported and executed from a known location, and allows for the less common use case of reading files directly from the package path
|
|
287
|
+
- Safe entrypoint selection means the default entrypoint resolves the console script matching the package name while `CUSTOM_ENTRYPOINT` allows overriding at build time while keeping runtime behavior predictable
|
|
288
|
+
- Minimal final image uses the slim Python base, cleans wheel artifacts and caches, and sets `runtime` as the explicit final target so the debug stage is opt-in
|
|
289
|
+
|
|
290
|
+
## Notes
|
|
291
|
+
|
|
292
|
+
- This project dogfoods itself - it uses `common-python-tasks` for its own development
|
|
293
|
+
- 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
|
|
294
|
+
- Alpha status: expect breaking changes between minor versions until 1.0.0
|
|
295
|
+
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Common Python tasks
|
|
2
|
+
|
|
3
|
+
This package is a collection of (very) opinionated [Poe the Poet Python tasks](https://poethepoet.natn.io/guides/packaged_tasks.html) for common Python development workflows.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
### Automated setup
|
|
8
|
+
|
|
9
|
+
You can add `common-python-tasks` to a new project by using the handy automated installation script.
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add-common-python-tasks.sh | TAGS_TO_INCLUDE="format lint test" sh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This will complete the following steps.
|
|
16
|
+
|
|
17
|
+
1. Add the latest version of `common-python-tasks` to your `pyproject.toml` dependencies
|
|
18
|
+
2. Configure Poe the Poet to include only the tasks with the specified tags
|
|
19
|
+
3. Install the package using Poetry
|
|
20
|
+
|
|
21
|
+
**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.
|
|
22
|
+
|
|
23
|
+
### Manual setup
|
|
24
|
+
|
|
25
|
+
1. Add `common-python-tasks` to your `pyproject.toml` and configure Poe the Poet to include the desired tasks
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
[project]
|
|
29
|
+
name = "my-awesome-project"
|
|
30
|
+
version = "0.0.1"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"common-python-tasks==0.0.1", # Always pin to a specific version
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.poe]
|
|
36
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint', 'test'])" # Include or exclude tasks by tags
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Install the package
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
poetry install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. Run tasks
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
poe format # Format your code
|
|
49
|
+
poe lint # Check code quality
|
|
50
|
+
poe test # Run tests with coverage
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Available tasks
|
|
54
|
+
|
|
55
|
+
Internal tasks are used by other tasks and are not meant to be run directly.
|
|
56
|
+
|
|
57
|
+
| Task | Description | Tags |
|
|
58
|
+
| - | - | - |
|
|
59
|
+
| `build` | Build the project; also builds container images when the `containers` tag is included | packaging, containers |
|
|
60
|
+
| `build-image` | Build a container image using the bundled Containerfile template | containers, build |
|
|
61
|
+
| `build-package` | Build the package (wheel and sdist) | packaging, build |
|
|
62
|
+
| `bump-version` | Bump project version and create a git tag | packaging |
|
|
63
|
+
| `clean` | Remove build, cache, and coverage artifacts | clean |
|
|
64
|
+
| `format` | Format code with autoflake, black, and isort | format |
|
|
65
|
+
| `lint` | Run autoflake, black, isort checks, and flake8 linting | lint |
|
|
66
|
+
| `publish-package` | Publish the package to PyPI via Poetry | packaging |
|
|
67
|
+
| `push-image` | Push container images to the configured registry | containers, packaging, release |
|
|
68
|
+
| `run-container` | Run the built container image with the selected tag | containers |
|
|
69
|
+
| `test` | Run tests with pytest and generate coverage reports | test |
|
|
70
|
+
|
|
71
|
+
## How it works
|
|
72
|
+
|
|
73
|
+
### Prerequisites
|
|
74
|
+
|
|
75
|
+
Your project must meet the following requirements.
|
|
76
|
+
|
|
77
|
+
- Use Poetry for dependency management
|
|
78
|
+
- Have a `pyproject.toml` file at the root
|
|
79
|
+
- Have a package name (automatically inferred from `project.name` in `pyproject.toml`, or set via `PACKAGE_NAME` environment variable)
|
|
80
|
+
|
|
81
|
+
### Configuration precedence
|
|
82
|
+
|
|
83
|
+
Tasks that need configuration files (`pytest`, `coverage`, `flake8`, `isort`) follow this order of precedence.
|
|
84
|
+
|
|
85
|
+
1. **`pyproject.toml` sections** - `[tool.pytest]`, `[tool.coverage]`, `[tool.isort]` take priority
|
|
86
|
+
2. **Environment variables** - Override config paths (see [Environment Variables](#environment-variables))
|
|
87
|
+
3. **Local config files** - `pytest.ini`, `.coveragerc`, `.flake8`, `.isort.cfg` in project root
|
|
88
|
+
4. **Bundled defaults** - Sensible defaults included with this package, found in the [`src/common_python_tasks/data`](src/common_python_tasks/data) directory
|
|
89
|
+
|
|
90
|
+
You can start with zero configuration and customize as needed.
|
|
91
|
+
|
|
92
|
+
### Environment variables
|
|
93
|
+
|
|
94
|
+
#### Configuration files
|
|
95
|
+
|
|
96
|
+
The following environment variables configure the paths to configuration files.
|
|
97
|
+
|
|
98
|
+
- `PYTEST_CONFIG` specifies the path to the pytest configuration file
|
|
99
|
+
- `COVERAGE_RCFILE` specifies the path to the coverage configuration file
|
|
100
|
+
- `FLAKE8_CONFIG` specifies the path to the flake8 configuration file
|
|
101
|
+
- `ISORT_CONFIG` specifies the path to the isort configuration file
|
|
102
|
+
|
|
103
|
+
#### Package/Container settings
|
|
104
|
+
|
|
105
|
+
The following environment variables configure package and container behavior.
|
|
106
|
+
|
|
107
|
+
- `PACKAGE_NAME` overrides the package name (default is from `pyproject.toml`)
|
|
108
|
+
- `POETRY_VERSION` overrides the Poetry version for container builds
|
|
109
|
+
- `DOCKERHUB_USERNAME` specifies the Docker Hub username for image tagging (default is current local user)
|
|
110
|
+
- `CONTAINER_REGISTRY_URL` specifies the registry URL (default is `docker.io/{username}`)
|
|
111
|
+
- `CUSTOM_IMAGE_ENTRYPOINT` specifies a custom entrypoint script name for containers
|
|
112
|
+
|
|
113
|
+
#### Debugging
|
|
114
|
+
|
|
115
|
+
The following environment variable enables debugging output.
|
|
116
|
+
|
|
117
|
+
- `COMMON_PYTHON_TASKS_LOG_LEVEL` should be set to `DEBUG` to see detailed configuration resolution
|
|
118
|
+
|
|
119
|
+
### Usage examples
|
|
120
|
+
|
|
121
|
+
You can include or exclude tasks by tags in your `pyproject.toml`
|
|
122
|
+
|
|
123
|
+
#### Minimal setup
|
|
124
|
+
|
|
125
|
+
```toml
|
|
126
|
+
[project]
|
|
127
|
+
name = "simple-cli-tool"
|
|
128
|
+
version = "0.0.1"
|
|
129
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
130
|
+
|
|
131
|
+
[tool.poe]
|
|
132
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint'])"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Available tasks: `format`, `lint`.
|
|
136
|
+
|
|
137
|
+
#### Container-based project
|
|
138
|
+
|
|
139
|
+
```toml
|
|
140
|
+
[project]
|
|
141
|
+
name = "containerized-app"
|
|
142
|
+
version = "0.0.1"
|
|
143
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
144
|
+
|
|
145
|
+
[tool.poe]
|
|
146
|
+
include_script = "common_python_tasks:tasks(include_tags=['format', 'lint', 'test', 'containers'])"
|
|
147
|
+
|
|
148
|
+
[tool.poe.env]
|
|
149
|
+
DOCKERHUB_USERNAME = "myusername"
|
|
150
|
+
PACKAGE_NAME = "containerized-app"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Available tasks: All tasks including `build-image` and `push-image`.
|
|
154
|
+
|
|
155
|
+
#### Custom pytest configuration
|
|
156
|
+
|
|
157
|
+
```toml
|
|
158
|
+
[project]
|
|
159
|
+
name = "custom-test-setup"
|
|
160
|
+
dependencies = ["common-python-tasks==0.0.1"]
|
|
161
|
+
dynamic = ["version"]
|
|
162
|
+
|
|
163
|
+
[tool.poe]
|
|
164
|
+
include_script = "common_python_tasks:tasks(include_tags=['test'])"
|
|
165
|
+
|
|
166
|
+
[tool.pytest.ini_options]
|
|
167
|
+
testpaths = ["tests", "integration"]
|
|
168
|
+
addopts = "-ra"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The `test` task will automatically use your `[tool.pytest.ini_options]` configuration.
|
|
172
|
+
|
|
173
|
+
## Release workflow
|
|
174
|
+
|
|
175
|
+
The `release` tag is used to identify tasks that are part of the release process. To perform a complete release, follow these steps.
|
|
176
|
+
|
|
177
|
+
```shell
|
|
178
|
+
# 1. Ensure all changes are committed
|
|
179
|
+
git add .
|
|
180
|
+
git commit -m "Prepare for release" # You probably want a better commit message than this
|
|
181
|
+
|
|
182
|
+
# 2. Bump the version (creates a git tag)
|
|
183
|
+
poe bump-version patch # or 'minor', 'major'; for pre-releases: poe bump-version patch --stage alpha
|
|
184
|
+
|
|
185
|
+
# 3. Build the package
|
|
186
|
+
poetry build
|
|
187
|
+
|
|
188
|
+
# 4. Publish to PyPI
|
|
189
|
+
poe publish-package
|
|
190
|
+
|
|
191
|
+
# 5. (Optional) If using containers
|
|
192
|
+
poe build-image
|
|
193
|
+
poe push-image
|
|
194
|
+
|
|
195
|
+
# 6. Push tags to remote
|
|
196
|
+
git push --tags
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Troubleshooting
|
|
200
|
+
|
|
201
|
+
### "No tests were collected"
|
|
202
|
+
|
|
203
|
+
The `test` task exits with code 5 if no tests are found. You can address this in one of the following ways.
|
|
204
|
+
|
|
205
|
+
- Add tests to your `tests/` directory
|
|
206
|
+
- Exclude the `test` tag and simply do not run `poe test` with this configuration `include_script = "common_python_tasks:tasks(exclude_tags=['test', 'internal'])"`
|
|
207
|
+
|
|
208
|
+
### Tasks not showing up with `poe --help`
|
|
209
|
+
|
|
210
|
+
Check your `[tool.poe]` configuration in `pyproject.toml`. Make sure you're using `include_script`, not `includes`.
|
|
211
|
+
|
|
212
|
+
```toml
|
|
213
|
+
# Correct
|
|
214
|
+
[tool.poe]
|
|
215
|
+
include_script = "common_python_tasks:tasks(exclude_tags=['internal'])"
|
|
216
|
+
|
|
217
|
+
# Incorrect
|
|
218
|
+
[tool.poe]
|
|
219
|
+
includes = "common_python_tasks:tasks"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Version bump fails with "no changes since last tag"
|
|
223
|
+
|
|
224
|
+
This is expected behavior. The `bump-version` task requires commits between the last tag and HEAD. You can resolve this in one of the following ways.
|
|
225
|
+
|
|
226
|
+
- Make changes and commit them first
|
|
227
|
+
- If you need to re-tag the same commit, delete the old tag (for example, `git tag -d v0.0.1`). This is not recommended. Versions should be immutable, and if you need to fix something, you should create a new patch version instead
|
|
228
|
+
|
|
229
|
+
### Config files not being used
|
|
230
|
+
|
|
231
|
+
Check the configuration precedence (see [How it works](#how-it-works)). Use debug logging to see which config is selected.
|
|
232
|
+
|
|
233
|
+
```shell
|
|
234
|
+
COMMON_PYTHON_TASKS_LOG_LEVEL=DEBUG poe test
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Container build fails with "unable to find package"
|
|
238
|
+
|
|
239
|
+
Make sure your `pyproject.toml` contains the following.
|
|
240
|
+
|
|
241
|
+
- A correct package name in `[project]`
|
|
242
|
+
- A package location defined with this configuration `[tool.poetry] packages = [{ include = "your_package", from = "src" }]`
|
|
243
|
+
|
|
244
|
+
## Design choices
|
|
245
|
+
|
|
246
|
+
### Containerfile (see [src/common_python_tasks/data/Containerfile](src/common_python_tasks/data/Containerfile))
|
|
247
|
+
|
|
248
|
+
The standard Python Containerfile incorporates several intentional design choices.
|
|
249
|
+
|
|
250
|
+
- 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
|
|
251
|
+
- Cache-aware installs mean pip and Poetry cache mounts speed up iterative builds without bloating the final image
|
|
252
|
+
- Explicit inputs through build args (`PYTHON_VERSION`, `POETRY_VERSION`, `PACKAGE_NAME`, `AUTHORS`, `GIT_COMMIT`, `CUSTOM_ENTRYPOINT`) make image metadata and behavior predictable and auditable
|
|
253
|
+
- 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
|
|
254
|
+
- 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, which ensures that the package can be reliably imported and executed from a known location, and allows for the less common use case of reading files directly from the package path
|
|
255
|
+
- Safe entrypoint selection means the default entrypoint resolves the console script matching the package name while `CUSTOM_ENTRYPOINT` allows overriding at build time while keeping runtime behavior predictable
|
|
256
|
+
- Minimal final image uses the slim Python base, cleans wheel artifacts and caches, and sets `runtime` as the explicit final target so the debug stage is opt-in
|
|
257
|
+
|
|
258
|
+
## Notes
|
|
259
|
+
|
|
260
|
+
- This project dogfoods itself - it uses `common-python-tasks` for its own development
|
|
261
|
+
- 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
|
|
262
|
+
- Alpha status: expect breaking changes between minor versions until 1.0.0
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "common-python-tasks"
|
|
3
|
+
description = "Opinionated Poe the Poet tasks for Python package development."
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.10,<4.0"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Joseph Asbury", email = "ci_sourcerer@yahoo.com" }]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.10",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Topic :: Software Development :: Build Tools",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"autoflake (>=2.3.1,<3.0.0)",
|
|
22
|
+
"black (>=25.11.0,<26.0.0)",
|
|
23
|
+
"dunamai (>=1.25.0,<2.0.0)",
|
|
24
|
+
"flake8 (>=7.3.0,<8.0.0)",
|
|
25
|
+
"isort (>=7.0.0,<8.0.0)",
|
|
26
|
+
"poethepoet-tasks (>=0.3.0,<0.4.0)",
|
|
27
|
+
"pytest-cov (>=7.0.0,<8.0.0)",
|
|
28
|
+
"pytest (>=9.0.1,<10.0.0)",
|
|
29
|
+
"tomlkit (>=0.13.3,<0.14.0)",
|
|
30
|
+
]
|
|
31
|
+
dynamic = []
|
|
32
|
+
version = "0.0.1"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
homepage = "http://github.com/ci-sourcerer/common-python-tasks"
|
|
36
|
+
issues = "http://github.com/ci-sourcerer/common-python-tasks/issues"
|
|
37
|
+
source = "http://github.com/ci-sourcerer/common-python-tasks"
|
|
38
|
+
|
|
39
|
+
[tool.poe]
|
|
40
|
+
include_script = "common_python_tasks:tasks(exclude_tags=['containers'])"
|
|
41
|
+
|
|
42
|
+
[tool.poetry.requires-plugins]
|
|
43
|
+
poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
|
|
44
|
+
poetry-plugin-export = { version = ">=1.9.0,<2.0.0" }
|
|
45
|
+
|
|
46
|
+
[tool.poetry]
|
|
47
|
+
packages = [{ include = "common_python_tasks", from = "src" }]
|
|
48
|
+
include = ["src/common_python_tasks/data/*"]
|
|
49
|
+
# This is set by poetry-dynamic-versioning
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
debug = ["debugpy (>=1.8.16,<2.0.0)"]
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["poetry-core>=2.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
|
|
56
|
+
build-backend = "poetry.core.masonry.api"
|
|
57
|
+
|
|
58
|
+
[tool.poetry-dynamic-versioning]
|
|
59
|
+
enable = false
|
|
60
|
+
style = "pep440"
|
|
61
|
+
dirty = true
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
|
|
6
|
+
from poethepoet_tasks import TaskCollection
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
|
|
10
|
+
__all__ = ["TaskCollection"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def tasks(include_tags: Sequence[str] = tuple(), exclude_tags: Sequence[str] = tuple()):
|
|
14
|
+
from .tasks import tasks
|
|
15
|
+
|
|
16
|
+
return tasks(include_tags=include_tags, exclude_tags=exclude_tags)
|