common-python-tasks 0.1.0__tar.gz → 0.2.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 (33) hide show
  1. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/PKG-INFO +10 -10
  2. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/README.md +8 -8
  3. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/pyproject.toml +2 -2
  4. common_python_tasks-0.2.0/src/common_python_tasks/data/generic/.pytest_cache/v/cache/lastfailed +8 -0
  5. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.pytest_cache/v/cache/nodeids +11 -0
  6. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/github.py +33 -1
  7. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/tasks.py +43 -1
  8. common_python_tasks-0.1.0/src/common_python_tasks/data/generic/.pytest_cache/v/cache/lastfailed +0 -4
  9. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/LICENSE +0 -0
  10. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/__init__.py +0 -0
  11. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/__main__.py +0 -0
  12. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/compose.py +0 -0
  13. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/dockerfile_extensions/.gitkeep +0 -0
  14. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/fastapi/alembic.ini.j2 +0 -0
  15. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/fastapi/compose-base.yml.j2 +0 -0
  16. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/fastapi/compose-db-debug.yml.j2 +0 -0
  17. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/fastapi/compose-db.yml.j2 +0 -0
  18. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/fastapi/compose-debug.yml.j2 +0 -0
  19. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.coveragerc +0 -0
  20. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.dockerignore +0 -0
  21. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.flake8 +0 -0
  22. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.isort.cfg +0 -0
  23. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.pytest_cache/.gitignore +0 -0
  24. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.pytest_cache/CACHEDIR.TAG +0 -0
  25. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/.pytest_cache/README.md +0 -0
  26. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/Dockerfile.deps.j2 +0 -0
  27. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/Dockerfile.j2 +0 -0
  28. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/data/generic/pytest.ini +0 -0
  29. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/docker.py +0 -0
  30. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/env.py +0 -0
  31. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/git.py +0 -0
  32. {common_python_tasks-0.1.0 → common_python_tasks-0.2.0}/src/common_python_tasks/project.py +0 -0
  33. {common_python_tasks-0.1.0 → common_python_tasks-0.2.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.1.0
3
+ Version: 0.2.0
4
4
  Summary: Opinionated Poe the Poet tasks for Python package development.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -23,7 +23,7 @@ Requires-Dist: git-cliff (>=2.12.0,<3.0.0)
23
23
  Requires-Dist: isort (>=8.0.1,<9.0.0)
24
24
  Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
25
25
  Requires-Dist: poethepoet-tasks (>=0.3.0,<0.4.0)
26
- Requires-Dist: pytest (>=9.0.1,<10.0.0)
26
+ Requires-Dist: pytest (>=9.0.3,<10.0.0)
27
27
  Requires-Dist: pytest-cov (>=7.0.0,<8.0.0)
28
28
  Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
29
29
  Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
@@ -50,7 +50,7 @@ curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents
50
50
  To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
51
51
 
52
52
  ```shell
53
- COMMON_PYTHON_TASKS_VERSION=0.1.0 \
53
+ COMMON_PYTHON_TASKS_VERSION=0.2.0 \
54
54
  sh -c "$(curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add-common-python-tasks.sh | jq -r '.content' | base64 -d)"
55
55
  ```
56
56
 
@@ -71,9 +71,9 @@ There's no real reason to run the automated script; I just like automating every
71
71
  ```toml
72
72
  [project]
73
73
  name = "my-awesome-project"
74
- version = "0.1.0"
74
+ version = "0.2.0"
75
75
  dependencies = [
76
- "common-python-tasks==0.1.0", # Always pin to a specific version
76
+ "common-python-tasks==0.2.0", # Always pin to a specific version
77
77
  ]
78
78
 
79
79
  [tool.poe]
@@ -287,8 +287,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
287
287
  ```toml
288
288
  [project]
289
289
  name = "simple-cli-tool"
290
- version = "0.1.0"
291
- dependencies = ["common-python-tasks==0.1.0"]
290
+ version = "0.2.0"
291
+ dependencies = ["common-python-tasks==0.2.0"]
292
292
 
293
293
  [tool.poe]
294
294
  include_script = "common_python_tasks:tasks()"
@@ -301,8 +301,8 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
301
301
  ```toml
302
302
  [project]
303
303
  name = "containerized-app"
304
- version = "0.1.0"
305
- dependencies = ["common-python-tasks==0.1.0"]
304
+ version = "0.2.0"
305
+ dependencies = ["common-python-tasks==0.2.0"]
306
306
 
307
307
  [tool.poe]
308
308
  include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
@@ -319,7 +319,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
319
319
  ```toml
320
320
  [project]
321
321
  name = "custom-test-setup"
322
- dependencies = ["common-python-tasks==0.1.0"]
322
+ dependencies = ["common-python-tasks==0.2.0"]
323
323
  dynamic = ["version"]
324
324
 
325
325
  [tool.poe]
@@ -17,7 +17,7 @@ curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents
17
17
  To install a specific release, set the environment variable `COMMON_PYTHON_TASKS_VERSION`.
18
18
 
19
19
  ```shell
20
- COMMON_PYTHON_TASKS_VERSION=0.1.0 \
20
+ COMMON_PYTHON_TASKS_VERSION=0.2.0 \
21
21
  sh -c "$(curl -sSL https://api.github.com/repos/ci-sourcerer/common-python-tasks/contents/scripts/add-common-python-tasks.sh | jq -r '.content' | base64 -d)"
22
22
  ```
23
23
 
@@ -38,9 +38,9 @@ There's no real reason to run the automated script; I just like automating every
38
38
  ```toml
39
39
  [project]
40
40
  name = "my-awesome-project"
41
- version = "0.1.0"
41
+ version = "0.2.0"
42
42
  dependencies = [
43
- "common-python-tasks==0.1.0", # Always pin to a specific version
43
+ "common-python-tasks==0.2.0", # Always pin to a specific version
44
44
  ]
45
45
 
46
46
  [tool.poe]
@@ -254,8 +254,8 @@ By default, `tasks()` exposes the common task set. You can still include or excl
254
254
  ```toml
255
255
  [project]
256
256
  name = "simple-cli-tool"
257
- version = "0.1.0"
258
- dependencies = ["common-python-tasks==0.1.0"]
257
+ version = "0.2.0"
258
+ dependencies = ["common-python-tasks==0.2.0"]
259
259
 
260
260
  [tool.poe]
261
261
  include_script = "common_python_tasks:tasks()"
@@ -268,8 +268,8 @@ Available tasks: common defaults such as `format`, `lint`, `test`, and `build`.
268
268
  ```toml
269
269
  [project]
270
270
  name = "containerized-app"
271
- version = "0.1.0"
272
- dependencies = ["common-python-tasks==0.1.0"]
271
+ version = "0.2.0"
272
+ dependencies = ["common-python-tasks==0.2.0"]
273
273
 
274
274
  [tool.poe]
275
275
  include_script = "common_python_tasks:tasks(include_tags=['common', 'containers'])"
@@ -286,7 +286,7 @@ Available tasks: All tasks including `build-image` and `push-image`.
286
286
  ```toml
287
287
  [project]
288
288
  name = "custom-test-setup"
289
- dependencies = ["common-python-tasks==0.1.0"]
289
+ dependencies = ["common-python-tasks==0.2.0"]
290
290
  dynamic = ["version"]
291
291
 
292
292
  [tool.poe]
@@ -24,13 +24,13 @@ dependencies = [
24
24
  "isort (>=8.0.1,<9.0.0)",
25
25
  "poethepoet-tasks (>=0.3.0,<0.4.0)",
26
26
  "pytest-cov (>=7.0.0,<8.0.0)",
27
- "pytest (>=9.0.1,<10.0.0)",
27
+ "pytest (>=9.0.3,<10.0.0)",
28
28
  "tomlkit (>=0.13.3,<0.14.0)",
29
29
  "jinja2 (>=3.1.6,<4.0.0)",
30
30
  "git-cliff (>=2.12.0,<3.0.0)",
31
31
  ]
32
32
  dynamic = []
33
- version = "0.1.0"
33
+ version = "0.2.0"
34
34
 
35
35
  [project.urls]
36
36
  Homepage = "http://github.com/ci-sourcerer/common-python-tasks"
@@ -0,0 +1,8 @@
1
+ {
2
+ "tests/test_exec_handoff.py::test_build_compose_exec_script_signal_traps": true,
3
+ "tests/test_exec_handoff.py::test_build_compose_exec_script_full_integration": true,
4
+ "tests/test_release_script.py::test_main_pre_phase_replaces_placeholder_and_rebuilds_table": true,
5
+ "tests/test_release_script.py::test_main_post_phase_restores_placeholder_without_rebuilding_table": true,
6
+ "tests/test_release_script.py::test_main_pre_phase_fails_when_placeholder_is_missing": true,
7
+ "tests/test_release_script.py::test_main_post_phase_fails_when_release_version_is_missing": true
8
+ }
@@ -134,6 +134,10 @@
134
134
  "tests/test_git.py::TestHasTagsLaterInHistory::test_no_tags_exist",
135
135
  "tests/test_git.py::TestHasTagsLaterInHistory::test_no_tags_later_in_history",
136
136
  "tests/test_git.py::test_infer_bump_component_from_git_cliff_returns_expected_bump",
137
+ "tests/test_github.py::test_get_github_token_falls_back_to_gh_cli",
138
+ "tests/test_github.py::test_get_github_token_uses_environment_variable",
139
+ "tests/test_github.py::test_get_github_token_warns_when_gh_cli_fails",
140
+ "tests/test_github.py::test_get_github_token_warns_when_gh_cli_is_missing",
137
141
  "tests/test_project.py::test_get_authors_reads_pyproject",
138
142
  "tests/test_project.py::test_get_installed_requirement_version_returns_none_when_not_installed",
139
143
  "tests/test_project.py::test_get_installed_requirement_version_strips_extras_and_normalizes_name",
@@ -154,8 +158,12 @@
154
158
  "tests/test_release_script.py::test_main_dry_run_is_controlled_by_release_script_dry_run_env_pre_phase",
155
159
  "tests/test_release_script.py::test_main_dry_run_prints_transformed_readme",
156
160
  "tests/test_release_script.py::test_main_post_phase_fails_when_release_version_is_missing",
161
+ "tests/test_release_script.py::test_main_post_phase_leaves_readme_unchanged_when_release_version_is_missing",
162
+ "tests/test_release_script.py::test_main_post_phase_leaves_readme_unchanged_without_rebuilding_table",
157
163
  "tests/test_release_script.py::test_main_post_phase_restores_placeholder_without_rebuilding_table",
164
+ "tests/test_release_script.py::test_main_pre_phase_fails_when_latest_tagged_version_is_missing",
158
165
  "tests/test_release_script.py::test_main_pre_phase_fails_when_placeholder_is_missing",
166
+ "tests/test_release_script.py::test_main_pre_phase_replaces_latest_tagged_version_and_rebuilds_table",
159
167
  "tests/test_release_script.py::test_main_pre_phase_replaces_placeholder_and_rebuilds_table",
160
168
  "tests/test_release_script.py::test_release_script_dry_run_env_post_phase_prints_summary_without_modifying_files",
161
169
  "tests/test_release_script.py::test_release_script_dry_run_env_pre_phase_prints_summary_without_modifying_files",
@@ -176,6 +184,7 @@
176
184
  "tests/test_tasks.py::TestBumpVersion::test_bump_with_stage_param",
177
185
  "tests/test_tasks.py::TestBumpVersion::test_case_insensitive_component",
178
186
  "tests/test_tasks.py::TestBumpVersion::test_case_insensitive_stage",
187
+ "tests/test_tasks.py::TestBumpVersion::test_dirty_repo_can_be_allowed",
179
188
  "tests/test_tasks.py::TestBumpVersion::test_dirty_repo_fails",
180
189
  "tests/test_tasks.py::TestBumpVersion::test_dry_run_no_tags",
181
190
  "tests/test_tasks.py::TestBumpVersion::test_dry_run_with_existing_tag",
@@ -185,8 +194,10 @@
185
194
  "tests/test_tasks.py::TestBumpVersion::test_release_dry_run_only_bumps_version",
186
195
  "tests/test_tasks.py::TestBumpVersion::test_release_runs_container_steps",
187
196
  "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_accepts_none_stage_string",
197
+ "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_allows_dirty_repo_without_prompt_env",
188
198
  "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_can_disable_changelog_updates",
189
199
  "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_fails_when_not_on_default_branch",
200
+ "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_prompts_once_for_dirty_repo",
190
201
  "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_runs_pre_and_post_scripts",
191
202
  "tests/test_tasks.py::TestBumpVersion::test_release_without_containers_uses_phase_configured_release_script_hooks",
192
203
  "tests/test_tasks.py::TestGitHubReleasePublishing::test_publish_github_release_creates_release_when_missing",
@@ -89,7 +89,39 @@ def get_github_token() -> str | None:
89
89
  Returns:
90
90
  The GitHub token from `GITHUB_TOKEN` or `GH_TOKEN`, or `None` if unset.
91
91
  """
92
- return (os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN") or "").strip() or None
92
+ token = (os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN") or "").strip()
93
+ if token:
94
+ return token
95
+
96
+ try:
97
+ result = utils.run_command(
98
+ ["gh", "auth", "token"],
99
+ capture_output=True,
100
+ acceptable_returncodes={0, 1},
101
+ )
102
+ except FileNotFoundError:
103
+ LOGGER.warning(
104
+ "GitHub CLI not installed; cannot retrieve token via `gh auth token`"
105
+ )
106
+ return None
107
+
108
+ if result is None:
109
+ return None
110
+
111
+ if result.returncode != 0:
112
+ stderr = (result.stderr or "").strip()
113
+ message = "Failed to retrieve GitHub token via `gh auth token`"
114
+ if stderr:
115
+ message += f": {stderr}"
116
+ LOGGER.warning(message)
117
+ return None
118
+
119
+ token = (result.stdout or "").strip()
120
+ if not token:
121
+ LOGGER.warning("`gh auth token` returned an empty token")
122
+ return None
123
+
124
+ return token
93
125
 
94
126
 
95
127
  def get_github_api_base_url() -> str:
@@ -112,6 +112,8 @@ def _script(
112
112
 
113
113
  tasks.script = _script
114
114
 
115
+ NO_PROMPT_ENV_VAR = "COMMON_PYTHON_TASKS_NO_PROMPT"
116
+
115
117
 
116
118
  def _should_update_release_changelog() -> bool:
117
119
  from common_python_tasks.env import env_truthy
@@ -119,6 +121,39 @@ def _should_update_release_changelog() -> bool:
119
121
  return env_truthy("RELEASE_UPDATE_CHANGELOG")
120
122
 
121
123
 
124
+ def _confirm_release_with_uncommitted_changes(dirty_files: list[Path]) -> bool:
125
+ preview = ", ".join(str(path) for path in dirty_files[:5])
126
+ if len(dirty_files) > 5:
127
+ preview = f"{preview}, ... and {len(dirty_files) - 5} more"
128
+
129
+ LOGGER.warning("Repository has uncommitted changes: %s", preview)
130
+ try:
131
+ response = input("Proceed with release anyway? [y/N]: ").strip().lower()
132
+ except (EOFError, OSError):
133
+ return False
134
+ return response in {"y", "yes"}
135
+
136
+
137
+ def _resolve_allow_dirty_release(dirty_files: list[Path]) -> bool:
138
+ from common_python_tasks.utils import fatal
139
+
140
+ if not dirty_files:
141
+ return False
142
+
143
+ if os.getenv(NO_PROMPT_ENV_VAR) == "1":
144
+ LOGGER.warning(
145
+ "Proceeding with release despite uncommitted changes because %s=1",
146
+ NO_PROMPT_ENV_VAR,
147
+ )
148
+ return True
149
+
150
+ if _confirm_release_with_uncommitted_changes(dirty_files):
151
+ return True
152
+
153
+ fatal("Aborted due to uncommitted changes.")
154
+ return False
155
+
156
+
122
157
  def _update_release_changelog(release_tag: str, dry_run: bool = False) -> None:
123
158
  from common_python_tasks.utils import prepend_changelog, run_command
124
159
 
@@ -884,6 +919,7 @@ def bump_version(
884
919
  *,
885
920
  stage: str | None = None,
886
921
  dry_run: bool = False,
922
+ allow_dirty: bool = False,
887
923
  ) -> str:
888
924
  """Bump the project version.
889
925
 
@@ -892,6 +928,7 @@ def bump_version(
892
928
  `auto` to infer the bump from git history using git-cliff.
893
929
  stage: Optional pre-release stage to apply: `alpha`, `beta`, or `rc`.
894
930
  dry_run: If `True`, print what would happen without making changes.
931
+ allow_dirty: If `True`, allow version bumping with uncommitted changes.
895
932
 
896
933
  Returns:
897
934
  The new version string that was/would be set.
@@ -909,7 +946,7 @@ def bump_version(
909
946
 
910
947
  ensure_on_default_branch()
911
948
 
912
- if get_dirty_files():
949
+ if get_dirty_files() and not allow_dirty:
913
950
  fatal(
914
951
  "Repository has uncommitted changes. "
915
952
  "Please commit or stash changes before bumping version."
@@ -960,6 +997,7 @@ def _run_release_flow(
960
997
  from common_python_tasks.git import (
961
998
  build_release_hook_environment,
962
999
  ensure_on_default_branch,
1000
+ get_dirty_files,
963
1001
  )
964
1002
  from common_python_tasks.utils import (
965
1003
  log_dry_run,
@@ -971,7 +1009,10 @@ def _run_release_flow(
971
1009
  post_script = post_script or os.getenv("RELEASE_POST_SCRIPT")
972
1010
 
973
1011
  ensure_on_default_branch()
1012
+ allow_dirty = _resolve_allow_dirty_release(get_dirty_files())
974
1013
  hook_env = build_release_hook_environment(component, normalized_stage, dry_run)
1014
+ if allow_dirty:
1015
+ hook_env[NO_PROMPT_ENV_VAR] = "1"
975
1016
  if pre_script:
976
1017
  run_command(["sh", "-lc", pre_script], env=hook_env, dry_run=False)
977
1018
 
@@ -987,6 +1028,7 @@ def _run_release_flow(
987
1028
  component=hook_env["RELEASE_COMPONENT"],
988
1029
  stage=normalized_stage,
989
1030
  dry_run=dry_run,
1031
+ allow_dirty=allow_dirty,
990
1032
  )
991
1033
  if dry_run:
992
1034
  log_dry_run("Would push tags to origin")
@@ -1,4 +0,0 @@
1
- {
2
- "tests/test_exec_handoff.py::test_build_compose_exec_script_signal_traps": true,
3
- "tests/test_exec_handoff.py::test_build_compose_exec_script_full_integration": true
4
- }