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.
@@ -5,16 +5,15 @@ prepend_sys_path = .
5
5
  output_encoding = utf-8
6
6
 
7
7
  [post_write_hooks]
8
- hooks = black,isort
8
+ hooks = ruff_check,ruff_format
9
9
 
10
- black.type = console_scripts
11
- black.entrypoint = black
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
- autoflake.type = console_scripts
14
- autoflake.entrypoint = autoflake
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]
@@ -0,0 +1,6 @@
1
+ line-length = 88
2
+ extend-exclude = [".dist", ".poetry", "build"]
3
+
4
+ [lint]
5
+ select = ["E4", "E7", "E9", "F", "I", "W"]
6
+ ignore = ["W191"]
@@ -190,7 +190,7 @@ def prune_images_keep(
190
190
  if line not in lines
191
191
  ]
192
192
 
193
- tags_in_order: list[str] = []
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: list[str] = []
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: list[Path] = []
418
- addon_template_vars: dict[str, dict[str, str]] = {}
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)
@@ -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: " f"{current_version!r}")
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):
@@ -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.info(
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: list[Path] = []
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: list[dict[str, Any]] = []
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)
@@ -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.info(
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}. " "Use one of: poetry, uv, auto."
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.info(
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.info(
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 get_package_manager_publish_command() -> list[str]:
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
- return [package_manager.value, "publish"]
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
- BLACK_TARGET_PYTHON_MINORS = range(7, 17)
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 get_black_target_version() -> str | None:
394
- """Return the Black target-version token based on `project.requires-python`.
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 Black format, such as
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 Exception:
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 BLACK_TARGET_PYTHON_MINORS:
458
+ for minor in RUFF_TARGET_PYTHON_MINORS:
411
459
  if _specifier_supports_python_minor(specifier_set, minor):
412
- return f"py3{minor:02d}"
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: " f"{poetry_version_output!r}"
643
+ f"Unexpected poetry version output format: {poetry_version_output!r}"
596
644
  )
597
645
  return parts[1].strip()
598
646