common-python-tasks 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- common_python_tasks/data/fastapi/alembic.ini.j2 +7 -8
- common_python_tasks/data/generic/ruff.toml +6 -0
- common_python_tasks/docker.py +2 -2
- common_python_tasks/docker_compose.py +2 -2
- common_python_tasks/git.py +1 -1
- common_python_tasks/github.py +49 -3
- common_python_tasks/project.py +64 -16
- common_python_tasks/tasks.py +339 -212
- common_python_tasks/utils.py +31 -20
- common_python_tasks-0.7.0.dist-info/METADATA +406 -0
- {common_python_tasks-0.5.1.dist-info → common_python_tasks-0.7.0.dist-info}/RECORD +13 -14
- {common_python_tasks-0.5.1.dist-info → common_python_tasks-0.7.0.dist-info}/WHEEL +1 -1
- common_python_tasks/data/generic/.flake8 +0 -9
- common_python_tasks/data/generic/.isort.cfg +0 -4
- common_python_tasks-0.5.1.dist-info/METADATA +0 -438
- {common_python_tasks-0.5.1.dist-info → common_python_tasks-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,16 +5,15 @@ prepend_sys_path = .
|
|
|
5
5
|
output_encoding = utf-8
|
|
6
6
|
|
|
7
7
|
[post_write_hooks]
|
|
8
|
-
hooks =
|
|
8
|
+
hooks = ruff_check,ruff_format
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
ruff_check.type = exec
|
|
11
|
+
ruff_check.executable = ruff
|
|
12
|
+
ruff_check.options = check --fix-only --select F401,I REVISION_SCRIPT_FILENAME
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
isort.type = console_scripts
|
|
17
|
-
isort.entrypoint = isort
|
|
14
|
+
ruff_format.type = exec
|
|
15
|
+
ruff_format.executable = ruff
|
|
16
|
+
ruff_format.options = format REVISION_SCRIPT_FILENAME
|
|
18
17
|
|
|
19
18
|
# Logging configuration
|
|
20
19
|
[loggers]
|
common_python_tasks/docker.py
CHANGED
|
@@ -190,7 +190,7 @@ def prune_images_keep(
|
|
|
190
190
|
if line not in lines
|
|
191
191
|
]
|
|
192
192
|
|
|
193
|
-
tags_in_order
|
|
193
|
+
tags_in_order = []
|
|
194
194
|
seen_tags: set[str] = set()
|
|
195
195
|
for entry in lines:
|
|
196
196
|
try:
|
|
@@ -509,7 +509,7 @@ def build_image(
|
|
|
509
509
|
for t in tags_to_use
|
|
510
510
|
]
|
|
511
511
|
|
|
512
|
-
all_tags
|
|
512
|
+
all_tags = []
|
|
513
513
|
for resolved_tag in [*short_tags, *full_tags]:
|
|
514
514
|
if resolved_tag not in all_tags:
|
|
515
515
|
all_tags.append(resolved_tag)
|
|
@@ -414,8 +414,8 @@ def load_compose_files(
|
|
|
414
414
|
)
|
|
415
415
|
]
|
|
416
416
|
|
|
417
|
-
temp_config_files
|
|
418
|
-
addon_template_vars
|
|
417
|
+
temp_config_files = []
|
|
418
|
+
addon_template_vars = {}
|
|
419
419
|
|
|
420
420
|
if "db" in compose_addons and compose_type == "fastapi":
|
|
421
421
|
alembic_path, alembic_cleanup = ensure_alembic_config(compose_type)
|
common_python_tasks/git.py
CHANGED
|
@@ -185,7 +185,7 @@ def infer_bump_component_from_git_cliff() -> ReleaseComponent:
|
|
|
185
185
|
try:
|
|
186
186
|
current = Version.parse(current_version)
|
|
187
187
|
except Exception:
|
|
188
|
-
utils.fatal("Unable to parse current project version:
|
|
188
|
+
utils.fatal(f"Unable to parse current project version: {current_version!r}")
|
|
189
189
|
|
|
190
190
|
def _parse_semver(version_value: str | Any) -> tuple[int, int, int]:
|
|
191
191
|
if not isinstance(version_value, str):
|
common_python_tasks/github.py
CHANGED
|
@@ -344,6 +344,52 @@ def github_api_request(
|
|
|
344
344
|
return GitHubClient().api_request(method, path, payload=payload)
|
|
345
345
|
|
|
346
346
|
|
|
347
|
+
def create_pull_request(
|
|
348
|
+
title: str,
|
|
349
|
+
head: str,
|
|
350
|
+
base: str,
|
|
351
|
+
body: str | None = None,
|
|
352
|
+
draft: bool = False,
|
|
353
|
+
) -> dict[str, Any] | None:
|
|
354
|
+
"""Create a GitHub pull request for the current repository.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
title: Pull request title.
|
|
358
|
+
head: Source branch name.
|
|
359
|
+
base: Base branch name.
|
|
360
|
+
body: Optional pull request body.
|
|
361
|
+
draft: Whether to create the pull request as a draft.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The GitHub API response for the created pull request, or `None` when
|
|
365
|
+
repository information is unavailable.
|
|
366
|
+
"""
|
|
367
|
+
repository = get_github_repository()
|
|
368
|
+
token = get_github_token()
|
|
369
|
+
|
|
370
|
+
if repository is None:
|
|
371
|
+
LOGGER.debug(
|
|
372
|
+
"Skipping pull request creation because this is not a GitHub repository"
|
|
373
|
+
)
|
|
374
|
+
return None
|
|
375
|
+
if token is None:
|
|
376
|
+
utils.fatal(
|
|
377
|
+
"GITHUB_TOKEN or GH_TOKEN environment variable must be set to open a GitHub pull request"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return GitHubClient(repository=repository, token=token).api_request(
|
|
381
|
+
"POST",
|
|
382
|
+
"/pulls",
|
|
383
|
+
payload={
|
|
384
|
+
"title": title,
|
|
385
|
+
"head": head,
|
|
386
|
+
"base": base,
|
|
387
|
+
"body": body or "",
|
|
388
|
+
"draft": draft,
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
347
393
|
def publish_github_release(
|
|
348
394
|
tag_name: str,
|
|
349
395
|
release_name: str | None = None,
|
|
@@ -388,7 +434,7 @@ def publish_github_release(
|
|
|
388
434
|
release_name = release_name or tag_name
|
|
389
435
|
release_body = body if body is not None else _changelog()
|
|
390
436
|
if release_body is not None and _is_unreleased_placeholder(release_body):
|
|
391
|
-
LOGGER.
|
|
437
|
+
LOGGER.debug(
|
|
392
438
|
"git-cliff returned an unreleased placeholder; using latest tagged release notes instead"
|
|
393
439
|
)
|
|
394
440
|
release_body = _latest_tagged_changelog()
|
|
@@ -497,7 +543,7 @@ def get_github_release_asset_paths(
|
|
|
497
543
|
else:
|
|
498
544
|
source_items = ["dist/*"]
|
|
499
545
|
|
|
500
|
-
asset_paths
|
|
546
|
+
asset_paths = []
|
|
501
547
|
for part in source_items:
|
|
502
548
|
if any(char in part for char in "*?[]"):
|
|
503
549
|
asset_paths.extend(
|
|
@@ -589,7 +635,7 @@ def upload_github_release_assets(
|
|
|
589
635
|
Returns:
|
|
590
636
|
A list of decoded JSON responses for the uploaded assets.
|
|
591
637
|
"""
|
|
592
|
-
uploaded_assets
|
|
638
|
+
uploaded_assets = []
|
|
593
639
|
for asset_path in asset_paths:
|
|
594
640
|
LOGGER.info("Uploading GitHub Release asset: %s", asset_path)
|
|
595
641
|
uploaded = upload_github_release_asset(release_id, asset_path)
|
common_python_tasks/project.py
CHANGED
|
@@ -8,9 +8,9 @@ from enum import StrEnum, auto
|
|
|
8
8
|
from functools import lru_cache
|
|
9
9
|
from importlib import metadata
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any, Sequence
|
|
12
12
|
|
|
13
|
-
from packaging.specifiers import SpecifierSet
|
|
13
|
+
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
14
14
|
from packaging.version import Version
|
|
15
15
|
|
|
16
16
|
from . import utils
|
|
@@ -123,7 +123,7 @@ def get_package_manager() -> PackageManager:
|
|
|
123
123
|
override = _find_package_manager_override()
|
|
124
124
|
if override is None:
|
|
125
125
|
package_manager, reason = _resolve_package_manager_auto()
|
|
126
|
-
LOGGER.
|
|
126
|
+
LOGGER.debug(
|
|
127
127
|
"Using package manager backend: %s (%s)",
|
|
128
128
|
package_manager,
|
|
129
129
|
reason,
|
|
@@ -133,12 +133,12 @@ def get_package_manager() -> PackageManager:
|
|
|
133
133
|
env_var, raw_value = override
|
|
134
134
|
if raw_value not in PackageManager:
|
|
135
135
|
utils.fatal(
|
|
136
|
-
f"Invalid {env_var} value {raw_value!r}.
|
|
136
|
+
f"Invalid {env_var} value {raw_value!r}. Use one of: poetry, uv, auto."
|
|
137
137
|
)
|
|
138
138
|
|
|
139
139
|
if raw_value == PackageManager.AUTO:
|
|
140
140
|
package_manager, reason = _resolve_package_manager_auto()
|
|
141
|
-
LOGGER.
|
|
141
|
+
LOGGER.debug(
|
|
142
142
|
"Using package manager backend: %s (%s via %s=%s)",
|
|
143
143
|
package_manager,
|
|
144
144
|
reason,
|
|
@@ -149,7 +149,7 @@ def get_package_manager() -> PackageManager:
|
|
|
149
149
|
|
|
150
150
|
package_manager = PackageManager(raw_value)
|
|
151
151
|
_ensure_package_manager_available(package_manager)
|
|
152
|
-
LOGGER.
|
|
152
|
+
LOGGER.debug(
|
|
153
153
|
"Using package manager backend: %s (from %s)",
|
|
154
154
|
package_manager,
|
|
155
155
|
env_var,
|
|
@@ -179,14 +179,62 @@ def get_package_manager_build_command(wheel_only: bool = False) -> list[str]:
|
|
|
179
179
|
return command
|
|
180
180
|
|
|
181
181
|
|
|
182
|
-
def
|
|
182
|
+
def get_package_manager_update_command(
|
|
183
|
+
dependencies: Sequence[str] | None = None,
|
|
184
|
+
) -> list[str]:
|
|
185
|
+
"""Return the dependency update command for the selected package manager.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
dependencies: Optional dependency names to update. When omitted, all
|
|
189
|
+
dependencies are updated.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Command tokens for the dependency update command.
|
|
193
|
+
"""
|
|
194
|
+
if dependencies is None:
|
|
195
|
+
dependencies = []
|
|
196
|
+
|
|
197
|
+
package_manager = PackageManager(get_package_manager())
|
|
198
|
+
if package_manager == PackageManager.UV:
|
|
199
|
+
command = ["uv", "sync", "--upgrade"]
|
|
200
|
+
for dependency in dependencies:
|
|
201
|
+
command.extend(["--upgrade-package", dependency])
|
|
202
|
+
return command
|
|
203
|
+
|
|
204
|
+
return ["poetry", "update", *dependencies]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_package_manager_publish_command(
|
|
208
|
+
repository: str | None = None,
|
|
209
|
+
repository_url: str | None = None,
|
|
210
|
+
) -> list[str]:
|
|
183
211
|
"""Return the publish command for the selected package manager.
|
|
184
212
|
|
|
213
|
+
Args:
|
|
214
|
+
repository: Optional configured repository name to publish to.
|
|
215
|
+
repository_url: Optional repository upload URL to publish to.
|
|
216
|
+
|
|
185
217
|
Returns:
|
|
186
218
|
Command tokens for the package publish command.
|
|
187
219
|
"""
|
|
220
|
+
if repository is not None and repository_url is not None:
|
|
221
|
+
utils.fatal("Specify either `repository` or `repository_url`, not both.")
|
|
222
|
+
|
|
188
223
|
package_manager = PackageManager(get_package_manager())
|
|
189
|
-
|
|
224
|
+
command = [package_manager.value, "publish"]
|
|
225
|
+
if repository_url is not None:
|
|
226
|
+
if package_manager == PackageManager.POETRY:
|
|
227
|
+
utils.fatal(
|
|
228
|
+
"Poetry publish only supports configured repository names. "
|
|
229
|
+
"Pass `repository` instead of `repository_url`."
|
|
230
|
+
)
|
|
231
|
+
command += ["--publish-url", repository_url]
|
|
232
|
+
elif repository is not None:
|
|
233
|
+
if package_manager == PackageManager.UV:
|
|
234
|
+
command += ["--index", repository]
|
|
235
|
+
else:
|
|
236
|
+
command += ["-r", repository]
|
|
237
|
+
return command
|
|
190
238
|
|
|
191
239
|
|
|
192
240
|
@lru_cache
|
|
@@ -381,7 +429,7 @@ def read_pyproject_toml() -> dict[str, Any]:
|
|
|
381
429
|
return tomllib.loads(Path("pyproject.toml").read_text())
|
|
382
430
|
|
|
383
431
|
|
|
384
|
-
|
|
432
|
+
RUFF_TARGET_PYTHON_MINORS = range(7, 16)
|
|
385
433
|
|
|
386
434
|
|
|
387
435
|
def _specifier_supports_python_minor(specifier_set: SpecifierSet, minor: int) -> bool:
|
|
@@ -390,11 +438,11 @@ def _specifier_supports_python_minor(specifier_set: SpecifierSet, minor: int) ->
|
|
|
390
438
|
)
|
|
391
439
|
|
|
392
440
|
|
|
393
|
-
def
|
|
394
|
-
"""Return the
|
|
441
|
+
def get_ruff_target_version() -> str | None:
|
|
442
|
+
"""Return the Ruff target-version token based on `project.requires-python`.
|
|
395
443
|
|
|
396
444
|
Returns:
|
|
397
|
-
The lowest supported Python version in
|
|
445
|
+
The lowest supported Python version in Ruff format, such as
|
|
398
446
|
`py311`, or `None` when the project metadata is missing or invalid.
|
|
399
447
|
"""
|
|
400
448
|
requires_python = read_pyproject_toml().get("project", {}).get("requires-python")
|
|
@@ -403,13 +451,13 @@ def get_black_target_version() -> str | None:
|
|
|
403
451
|
|
|
404
452
|
try:
|
|
405
453
|
specifier_set = SpecifierSet(requires_python)
|
|
406
|
-
except
|
|
454
|
+
except InvalidSpecifier:
|
|
407
455
|
LOGGER.warning("Unable to parse requires-python specifier: %s", requires_python)
|
|
408
456
|
return None
|
|
409
457
|
|
|
410
|
-
for minor in
|
|
458
|
+
for minor in RUFF_TARGET_PYTHON_MINORS:
|
|
411
459
|
if _specifier_supports_python_minor(specifier_set, minor):
|
|
412
|
-
return f"py3{minor
|
|
460
|
+
return f"py3{minor}"
|
|
413
461
|
return None
|
|
414
462
|
|
|
415
463
|
|
|
@@ -592,7 +640,7 @@ def get_project_version_from_poetry() -> str:
|
|
|
592
640
|
parts = poetry_version_output.rsplit(" ", 1)
|
|
593
641
|
if len(parts) != 2 or not parts[1].strip():
|
|
594
642
|
utils.fatal(
|
|
595
|
-
"Unexpected poetry version output format:
|
|
643
|
+
f"Unexpected poetry version output format: {poetry_version_output!r}"
|
|
596
644
|
)
|
|
597
645
|
return parts[1].strip()
|
|
598
646
|
|