python-semantic-release 10.5.3__py3-none-any.whl → 10.6.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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-semantic-release
3
- Version: 10.5.3
3
+ Version: 10.6.0
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>, codejedi365 <codejedi365@gmail.com>
6
6
  License: MIT
7
- Project-URL: changelog, https://github.com/python-semantic-release/python-semantic-release/blob/master/CHANGELOG.md
7
+ Project-URL: changelog, https://python-semantic-release.readthedocs.io/en/stable/misc/psr_changelog.html
8
8
  Project-URL: documentation, https://python-semantic-release.readthedocs.io
9
9
  Project-URL: homepage, https://python-semantic-release.readthedocs.io
10
10
  Project-URL: issues, https://github.com/python-semantic-release/python-semantic-release/issues
@@ -1,4 +1,4 @@
1
- python_semantic_release-10.5.3.dist-info/licenses/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
1
+ python_semantic_release-10.6.0.dist-info/licenses/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
2
2
  semantic_release/__init__.py,sha256=tRJWhrn_dUt0QycXD2DoJSfEP5uwmxngH7jvbG2i-hA,1317
3
3
  semantic_release/__main__.py,sha256=pksxr6g1vkKq98Q1lShsxG8tk55IMiSMHzAHKyFU5x0,1704
4
4
  semantic_release/const.py,sha256=wInJR7vcOgT1ysm5VuJQ6lD_ZGYnCwRVKz7Uz3htQc4,861
@@ -6,7 +6,7 @@ semantic_release/enums.py,sha256=vrEw1UNRcNrFjPqOFnuUzfeoqKj0ChixVVlyk5fqbng,174
6
6
  semantic_release/errors.py,sha256=FyocaqHbRhux-iNmCf9nI7awyUaGKjG9_5C_QDvhEas,3399
7
7
  semantic_release/gitproject.py,sha256=18jsD8YFsaKKqt_l5a0ccygIE_sOqvqf9FoEkJjyCkk,18584
8
8
  semantic_release/globals.py,sha256=IBhBbhZr2jx8dmpySnnu9m9jOGYu9Yu-vqHvAGQxgnw,464
9
- semantic_release/helpers.py,sha256=2uQXOiuiemiviVD52SH2FF6cn14p_gqvREeHvP7dexw,10012
9
+ semantic_release/helpers.py,sha256=3munzjFYx_wxi97tRjCoP7EhcMtbgnO7G0ENlShCm8w,10334
10
10
  semantic_release/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
12
12
  semantic_release/changelog/context.py,sha256=234LLcB_uuGaQ5N_zTbcQYF27ZwYIBmicIWkhFs7-aQ,5993
@@ -15,21 +15,21 @@ semantic_release/changelog/template.py,sha256=eqOjtVfBbksgTK4CAZOajfkaAcKueJ-FhF
15
15
  semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  semantic_release/cli/changelog_writer.py,sha256=2jP5b0LK61Y2tb22GQSFFmwHwXd2WjbopgsMAmsQsfY,9284
17
17
  semantic_release/cli/cli_context.py,sha256=Nop71LdVCJOeSUHgTXunMyK3xAu_QKQC2cRp1QBVkX0,4134
18
- semantic_release/cli/config.py,sha256=zNXlJHZBnFk2GEemgCg2Yw42EWihtFMot1twI7bEN-g,33668
18
+ semantic_release/cli/config.py,sha256=jfG_N4rUo9pMpoC1eHNku_fK47eQXPrzRENu2g8CwWg,34297
19
19
  semantic_release/cli/const.py,sha256=h7XE2D0D__TAZSrUUtVszwvzpkHTMOiQCf97XQNbEvA,163
20
20
  semantic_release/cli/github_actions_output.py,sha256=yC0nsMvEFGACjDwB8DdmGKwNGI8aIIhDxRHrmcS7tzA,5410
21
21
  semantic_release/cli/masking_filter.py,sha256=7t7XFL7Iy4QTvaYevZ-jnTkJBz15GoBw1vjW3hihe38,3159
22
- semantic_release/cli/util.py,sha256=4rf4xDHb7l-XpdcFNtslFz1xHslnBu6cTgWHXVnQXQs,3746
22
+ semantic_release/cli/util.py,sha256=6TAbHgDZU6T-rm5yzg2TdGP_-ueHQhfbAkghP1q6we0,3750
23
23
  semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  semantic_release/cli/commands/changelog.py,sha256=wJfd4VVfrGnu2jnIpG25cdVcbXIX-ElKl3b2jdtPPa0,5391
25
- semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
25
+ semantic_release/cli/commands/generate_config.py,sha256=sKTO2V_XGRFc6M5KaTEPS4UOmqDuQOyTm64IYLr1VBc,2452
26
26
  semantic_release/cli/commands/main.py,sha256=u1zhkkvKCZ2TtUqjzvdFTe5UZsvfws_pjqqo6CY0bBo,4351
27
- semantic_release/cli/commands/publish.py,sha256=CE_LJTxFnc337MfpsfdJopi7QCwwE13GqGNQ-dNgWis,2871
27
+ semantic_release/cli/commands/publish.py,sha256=q9sNYCqlIjbT8RjJvHPVYzDdAjtnXe4_IdT0_h4S9MA,3047
28
28
  semantic_release/cli/commands/version.py,sha256=8pn21AskKRMg1WJJXNNTZzlS1bNZefHKOPDRe-3NF5M,29502
29
29
  semantic_release/commit_parser/__init__.py,sha256=uCC2YI-EDPUAa6TwWYJA8TS7baWE2bXrLEGcrhJKofI,1323
30
30
  semantic_release/commit_parser/_base.py,sha256=DLsHnbXG-39JkUbcnsBCSV2GmV35w1rasyoMhK8G0UE,3058
31
31
  semantic_release/commit_parser/angular.py,sha256=MY_fo9F4EZ-ac8wYzBR0uD94O5Li2D-8zEMR01wss4c,18534
32
- semantic_release/commit_parser/emoji.py,sha256=0VecUMMcj43UaKDZ2GaFFUG26zOJAvXsEobTHaXNkk0,17749
32
+ semantic_release/commit_parser/emoji.py,sha256=IMRq0sUat-cU7aNIg38M0kAmZ376p7zuRy3XSOgsrOM,17826
33
33
  semantic_release/commit_parser/scipy.py,sha256=gA9TfmrxGVvtv6kki6N_9abJx-_WlpBsSX2TBh0YgPw,19463
34
34
  semantic_release/commit_parser/tag.py,sha256=bVO2XghM0G_eW2rG9Xc2q5TPsjtxr-xcHK5RpE1u_HM,3537
35
35
  semantic_release/commit_parser/token.py,sha256=1_q8mJ4SRu7kNfa-Nxr8fEyuvCfjPgiPEitqSP1KR5g,7904
@@ -62,23 +62,24 @@ semantic_release/hvcs/__init__.py,sha256=JwoaLOF-12L-OBo_9-tOXXhdiHKeVungA9865to
62
62
  semantic_release/hvcs/_base.py,sha256=gUCAbSHidhB6ou1oOP--wqEaVc4X2NPLjZQ5P_g3Bwc,2600
63
63
  semantic_release/hvcs/bitbucket.py,sha256=dbWvqf79ACN42srhfVRtJ8m5WNI-_d7NE1cxZPwtCLs,10148
64
64
  semantic_release/hvcs/gitea.py,sha256=PWK3URTuiWvx0SwSlSIGCbtVmD7XjaYo051fiTUYsJY,13463
65
- semantic_release/hvcs/github.py,sha256=guXx7Zc2o3z2eOs8KzYt3BifO5cNWuti1ZmZdL7bN3Q,20695
65
+ semantic_release/hvcs/github.py,sha256=taaoLs1khLlpJp3IjyRiNBk_c_bQim8goYSistnL_Xk,21157
66
66
  semantic_release/hvcs/gitlab.py,sha256=uUnTKq9Kpqc83ppXA3NXfVYnz4O1ej2noz68jCYzg68,10606
67
67
  semantic_release/hvcs/remote_hvcs_base.py,sha256=QtkjdMy9l-c7UOtyPz25cqVOkCk4IU-EOw6A8lP8l-U,6327
68
68
  semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARlg56hgU,663
69
69
  semantic_release/hvcs/util.py,sha256=PUNV4yUlpzDtNCFmh2joaPdU4JyfUBnVp0zaQsT9EDQ,2871
70
70
  semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
71
71
  semantic_release/version/algorithm.py,sha256=IxgYNF78W7qdMzdV4WsoljwfJgB-sn2XdfkevD0aZlo,16254
72
- semantic_release/version/declaration.py,sha256=eot_lUyFaEhzK4bPncfv9tahf51LdxZP6EaS54h3aAs,3635
72
+ semantic_release/version/declaration.py,sha256=qWHnDm8xakX_dXcPDhKk_3J5Zuu7HUYKmbqm5Iv68uQ,3743
73
73
  semantic_release/version/translator.py,sha256=iIfu3WreB9qqPHgqJLILbBluVQQNcpP0DEsnn_WzAaM,3689
74
74
  semantic_release/version/version.py,sha256=Y3Qqqv7CypirikT7jNqqFMNAvR2UjV-VQx-c2G0mZc0,14519
75
75
  semantic_release/version/declarations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  semantic_release/version/declarations/enum.py,sha256=3n5Py9DoFkmItIdsmtQrJgmAhepTv_1EnogzSdXu1Wg,244
77
- semantic_release/version/declarations/i_version_replacer.py,sha256=oP6BxJuxwI44roI6448tomShv1sMoy9ry8TlhhIQtfc,2416
77
+ semantic_release/version/declarations/file.py,sha256=m-MohBTN69T3unoPcSBgwA76ftwPr2WXfGgVjSvf1vY,4683
78
+ semantic_release/version/declarations/i_version_replacer.py,sha256=YktOY_7cfguZHEyV5f_R-ldMznj140mcS0BXdoa9zf8,2584
78
79
  semantic_release/version/declarations/pattern.py,sha256=sKk0uQpJjWVZc8RJUjxQoEPUvFLxXNGGBow5h1IqCTM,8378
79
80
  semantic_release/version/declarations/toml.py,sha256=2K4DtX5Qq1iHT8cG8mISPTMmp50w6Av0KmLAKZPYqq8,4931
80
- python_semantic_release-10.5.3.dist-info/METADATA,sha256=XFvZTTkpyX9iO4fzsBv6XBqN-_qxjLf3eC3Rr9ZFbGQ,4064
81
- python_semantic_release-10.5.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
- python_semantic_release-10.5.3.dist-info/entry_points.txt,sha256=kzkCyDJsMOwgpFwEWKE9wxN1tXaUP6g6GIO4xtc0QuE,162
83
- python_semantic_release-10.5.3.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
84
- python_semantic_release-10.5.3.dist-info/RECORD,,
81
+ python_semantic_release-10.6.0.dist-info/METADATA,sha256=loZ9-00JeyoQIE0DUn9O3JlLMhbpDVX8AHW6ULb_G6c,4053
82
+ python_semantic_release-10.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
83
+ python_semantic_release-10.6.0.dist-info/entry_points.txt,sha256=kzkCyDJsMOwgpFwEWKE9wxN1tXaUP6g6GIO4xtc0QuE,162
84
+ python_semantic_release-10.6.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
85
+ python_semantic_release-10.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import sys
5
+ from typing import Literal
4
6
 
5
7
  import click
6
8
  import tomlkit
@@ -31,7 +33,9 @@ from semantic_release.cli.config import RawConfig
31
33
  "'semantic_release'"
32
34
  ),
33
35
  )
34
- def generate_config(fmt: str = "toml", is_pyproject_toml: bool = False) -> None:
36
+ def generate_config(
37
+ fmt: Literal["toml", "json"], is_pyproject_toml: bool = False
38
+ ) -> None:
35
39
  """
36
40
  Generate default configuration for semantic-release, to help you get started
37
41
  quickly. You can inspect the defaults, write to a file and then edit according to
@@ -42,14 +46,29 @@ def generate_config(fmt: str = "toml", is_pyproject_toml: bool = False) -> None:
42
46
  """
43
47
  # due to possible IntEnum values (which are not supported by tomlkit.dumps, see sdispater/tomlkit#237),
44
48
  # we must ensure the transformation of the model to a dict uses json serializable values
45
- config = RawConfig().model_dump(mode="json", exclude_none=True)
49
+ config_dct = {
50
+ "semantic_release": RawConfig().model_dump(mode="json", exclude_none=True)
51
+ }
46
52
 
47
- config_dct = {"semantic_release": config}
48
- if is_pyproject_toml and fmt == "toml":
49
- config_dct = {"tool": config_dct}
53
+ if is_pyproject_toml:
54
+ output = tomlkit.dumps({"tool": config_dct})
50
55
 
51
- if fmt == "toml":
52
- click.echo(tomlkit.dumps(config_dct))
56
+ elif fmt == "toml":
57
+ output = tomlkit.dumps(config_dct)
53
58
 
54
59
  elif fmt == "json":
55
- click.echo(json.dumps(config_dct, indent=4))
60
+ output = json.dumps(config_dct, indent=4)
61
+
62
+ else:
63
+ raise ValueError(f"Unsupported format: {fmt}")
64
+
65
+ # Write output directly to stdout buffer as UTF-8 bytes
66
+ # This ensures consistent UTF-8 output on all platforms, especially Windows where
67
+ # shell redirection (>, >>) defaults to the system encoding (e.g., UTF-16LE or cp1252)
68
+ # By writing to sys.stdout.buffer, we bypass the encoding layer and guarantee UTF-8.
69
+ try:
70
+ sys.stdout.buffer.write(f"{output.strip()}\n".encode("utf-8")) # noqa: UP012; allow explicit encoding declaration
71
+ sys.stdout.buffer.flush()
72
+ except (AttributeError, TypeError):
73
+ # Fallback for environments without buffer (shouldn't happen in standard Python)
74
+ click.echo(output)
@@ -6,6 +6,7 @@ import click
6
6
  from git import Repo
7
7
 
8
8
  from semantic_release.cli.util import noop_report
9
+ from semantic_release.errors import AssetUploadError
9
10
  from semantic_release.globals import logger
10
11
  from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
11
12
  from semantic_release.version.algorithm import tags_and_versions
@@ -90,9 +91,13 @@ def publish(cli_ctx: CliContextObj, tag: str) -> None:
90
91
  )
91
92
  return
92
93
 
93
- publish_distributions(
94
- tag=tag,
95
- hvcs_client=hvcs_client,
96
- dist_glob_patterns=dist_glob_patterns,
97
- noop=runtime.global_cli_options.noop,
98
- )
94
+ try:
95
+ publish_distributions(
96
+ tag=tag,
97
+ hvcs_client=hvcs_client,
98
+ dist_glob_patterns=dist_glob_patterns,
99
+ noop=runtime.global_cli_options.noop,
100
+ )
101
+ except AssetUploadError as err:
102
+ click.echo(err, err=True)
103
+ ctx.exit(1)
@@ -57,6 +57,7 @@ from semantic_release.errors import (
57
57
  )
58
58
  from semantic_release.globals import logger
59
59
  from semantic_release.helpers import dynamic_import
60
+ from semantic_release.version.declarations.file import FileVersionDeclaration
60
61
  from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
61
62
  from semantic_release.version.declarations.pattern import PatternVersionDeclaration
62
63
  from semantic_release.version.declarations.toml import TomlVersionDeclaration
@@ -757,12 +758,22 @@ class RuntimeContext:
757
758
  ) from err
758
759
 
759
760
  try:
760
- version_declarations.extend(
761
- PatternVersionDeclaration.from_string_definition(
762
- definition, raw.tag_format
761
+ for definition in iter(raw.version_variables or ()):
762
+ # Check if this is a file replacement definition (pattern is "*")
763
+ parts = definition.split(":", maxsplit=2)
764
+ if len(parts) >= 2 and parts[1] == "*":
765
+ # Use FileVersionDeclaration for entire file replacement
766
+ version_declarations.append(
767
+ FileVersionDeclaration.from_string_definition(definition)
768
+ )
769
+ continue
770
+
771
+ # Use PatternVersionDeclaration for pattern-based replacement
772
+ version_declarations.append(
773
+ PatternVersionDeclaration.from_string_definition(
774
+ definition, raw.tag_format
775
+ )
763
776
  )
764
- for definition in iter(raw.version_variables or ())
765
- )
766
777
  except ValueError as err:
767
778
  raise InvalidConfiguration(
768
779
  str.join(
@@ -75,7 +75,7 @@ def load_raw_config_file(config_file: Path | str) -> dict[Any, Any]:
75
75
  while trying to read the specified configuration file
76
76
  """
77
77
  logger.info("Loading configuration from %s", config_file)
78
- raw_text = (Path() / config_file).resolve().read_text(encoding="utf-8")
78
+ raw_text = (Path() / config_file).resolve().read_text(encoding="utf-8-sig")
79
79
  try:
80
80
  logger.debug("Trying to parse configuration %s in TOML format", config_file)
81
81
  return parse_toml(raw_text)
@@ -65,7 +65,12 @@ class EmojiParserOptions(ParserOptions):
65
65
  )
66
66
  """Commit-type prefixes that should result in a patch release bump."""
67
67
 
68
- other_allowed_tags: Tuple[str, ...] = (":memo:", ":checkmark:")
68
+ other_allowed_tags: Tuple[str, ...] = (
69
+ ":checkmark:",
70
+ ":construction_worker:",
71
+ ":memo:",
72
+ ":recycle:",
73
+ )
69
74
  """Commit-type prefixes that are allowed but do not result in a version bump."""
70
75
 
71
76
  allowed_tags: Tuple[str, ...] = (
@@ -9,7 +9,7 @@ from functools import lru_cache, reduce, wraps
9
9
  from pathlib import Path, PurePosixPath
10
10
  from re import IGNORECASE, compile as regexp
11
11
  from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Sequence, TypeVar
12
- from urllib.parse import urlsplit
12
+ from urllib.parse import urlsplit, urlunsplit
13
13
 
14
14
  from semantic_release.globals import logger
15
15
 
@@ -215,6 +215,16 @@ class ParsedGitUrl(NamedTuple):
215
215
  repo_name: str
216
216
 
217
217
 
218
+ def _hide_credentials_in_url(url: str) -> str:
219
+ url_parts = urlsplit(url)
220
+
221
+ if not url_parts.scheme or "@" not in url_parts.netloc:
222
+ return url
223
+
224
+ _, _, host = url_parts.netloc.rpartition("@")
225
+ return urlunsplit(url_parts._replace(netloc=f"<credentials>@{host}"))
226
+
227
+
218
228
  @lru_cache(maxsize=512)
219
229
  def parse_git_url(url: str) -> ParsedGitUrl:
220
230
  """
@@ -242,7 +252,7 @@ def parse_git_url(url: str) -> ParsedGitUrl:
242
252
 
243
253
  Raises ValueError if the url can't be parsed.
244
254
  """
245
- logger.debug("Parsing git url %r", url)
255
+ logger.debug("Parsing git url %r", _hide_credentials_in_url(url))
246
256
 
247
257
  # Normalizers are a list of tuples of (pattern, replacement)
248
258
  normalizers = [
@@ -463,14 +463,25 @@ class Github(RemoteHvcsBase):
463
463
 
464
464
  # Upload assets
465
465
  n_succeeded = 0
466
+ errors = []
466
467
  for file_path in (
467
468
  f for f in glob.glob(dist_glob, recursive=True) if os.path.isfile(f)
468
469
  ):
469
470
  try:
470
471
  self.upload_release_asset(release_id, file_path)
471
472
  n_succeeded += 1
472
- except HTTPError: # noqa: PERF203
473
+ except HTTPError as err: # noqa: PERF203
473
474
  logger.exception("error uploading asset %s", file_path)
475
+ status_code = (
476
+ err.response.status_code if err.response is not None else "unknown"
477
+ )
478
+ error_msg = f"Failed to upload asset '{file_path}' to release"
479
+ if status_code != "unknown":
480
+ error_msg += f" (HTTP {status_code})"
481
+ errors.append(error_msg)
482
+
483
+ if errors:
484
+ raise AssetUploadError("\n".join(errors))
474
485
 
475
486
  return n_succeeded
476
487
 
@@ -9,6 +9,7 @@ from deprecated.sphinx import deprecated
9
9
 
10
10
  from semantic_release.globals import logger
11
11
  from semantic_release.version.declarations.enum import VersionStampType
12
+ from semantic_release.version.declarations.file import FileVersionDeclaration
12
13
  from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
13
14
  from semantic_release.version.declarations.pattern import PatternVersionDeclaration
14
15
  from semantic_release.version.declarations.toml import TomlVersionDeclaration
@@ -19,11 +20,12 @@ if TYPE_CHECKING: # pragma: no cover
19
20
 
20
21
  # Globals
21
22
  __all__ = [
23
+ "FileVersionDeclaration",
22
24
  "IVersionReplacer",
23
- "VersionStampType",
24
25
  "PatternVersionDeclaration",
25
26
  "TomlVersionDeclaration",
26
27
  "VersionDeclarationABC",
28
+ "VersionStampType",
27
29
  ]
28
30
 
29
31
 
@@ -0,0 +1,145 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from deprecated.sphinx import deprecated
7
+
8
+ from semantic_release.globals import logger
9
+ from semantic_release.version.declarations.enum import VersionStampType
10
+ from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
11
+
12
+ if TYPE_CHECKING: # pragma: no cover
13
+ from semantic_release.version.version import Version
14
+
15
+
16
+ class FileVersionDeclaration(IVersionReplacer):
17
+ """
18
+ IVersionReplacer implementation that replaces the entire file content
19
+ with the version string.
20
+
21
+ This is useful for files that contain only a version number, such as
22
+ VERSION files or similar single-line version storage files.
23
+ """
24
+
25
+ def __init__(self, path: Path | str, stamp_format: VersionStampType) -> None:
26
+ self._content: str | None = None
27
+ self._path = Path(path).resolve()
28
+ self._stamp_format = stamp_format
29
+
30
+ @property
31
+ def content(self) -> str:
32
+ """A cached property that stores the content of the configured source file."""
33
+ if self._content is None:
34
+ logger.debug("No content stored, reading from source file %s", self._path)
35
+
36
+ if not self._path.exists():
37
+ logger.debug(
38
+ f"path {self._path!r} does not exist, assuming empty content"
39
+ )
40
+ self._content = ""
41
+ else:
42
+ self._content = self._path.read_text()
43
+
44
+ return self._content
45
+
46
+ @content.deleter
47
+ def content(self) -> None:
48
+ self._content = None
49
+
50
+ @deprecated(
51
+ version="10.6.0",
52
+ reason="Function is unused and will be removed in a future release",
53
+ )
54
+ def parse(self) -> set[Version]:
55
+ raise NotImplementedError # pragma: no cover
56
+
57
+ def replace(self, new_version: Version) -> str:
58
+ """
59
+ Replace the file content with the new version string.
60
+
61
+ :param new_version: The new version number as a `Version` instance
62
+ :return: The new content (just the version string)
63
+ """
64
+ new_content = (
65
+ new_version.as_tag()
66
+ if self._stamp_format == VersionStampType.TAG_FORMAT
67
+ else str(new_version)
68
+ )
69
+
70
+ logger.debug(
71
+ "Replacing entire file content: path=%r old_content=%r new_content=%r",
72
+ self._path,
73
+ self.content.strip(),
74
+ new_content,
75
+ )
76
+
77
+ return new_content
78
+
79
+ def update_file_w_version(
80
+ self, new_version: Version, noop: bool = False
81
+ ) -> Path | None:
82
+ if noop:
83
+ if not self._path.exists():
84
+ logger.warning(
85
+ f"FILE NOT FOUND: file '{self._path}' does not exist but it will be created"
86
+ )
87
+
88
+ return self._path
89
+
90
+ new_content = self.replace(new_version)
91
+ if new_content == self.content.strip():
92
+ return None
93
+
94
+ self._path.write_text(f"{new_content}\n")
95
+ del self.content
96
+
97
+ return self._path
98
+
99
+ @classmethod
100
+ def from_string_definition(cls, replacement_def: str) -> FileVersionDeclaration:
101
+ """
102
+ Create an instance of self from a string representing one item
103
+ of the "version_variables" list in the configuration.
104
+
105
+ This method expects a definition in the format:
106
+ "file:*:format_type"
107
+
108
+ where:
109
+ - file is the path to the file
110
+ - * is the literal asterisk character indicating file replacement
111
+ - format_type is either "nf" (number format) or "tf" (tag format)
112
+ """
113
+ parts = replacement_def.split(":", maxsplit=2)
114
+
115
+ if len(parts) <= 1:
116
+ raise ValueError(
117
+ f"Invalid replacement definition {replacement_def!r}, missing ':'"
118
+ )
119
+
120
+ if len(parts) == 2:
121
+ # apply default version_type of "number_format" (ie. "1.2.3")
122
+ parts = [*parts, VersionStampType.NUMBER_FORMAT.value]
123
+
124
+ path, pattern, version_type = parts
125
+
126
+ # Validate that the pattern is exactly "*"
127
+ if pattern != "*":
128
+ raise ValueError(
129
+ f"Invalid pattern {pattern!r} for FileVersionDeclaration, expected '*'"
130
+ )
131
+
132
+ try:
133
+ stamp_type = VersionStampType(version_type)
134
+ except ValueError as err:
135
+ raise ValueError(
136
+ str.join(
137
+ " ",
138
+ [
139
+ "Invalid stamp type, must be one of:",
140
+ str.join(", ", [e.value for e in VersionStampType]),
141
+ ],
142
+ )
143
+ ) from err
144
+
145
+ return cls(path, stamp_type)
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from abc import ABCMeta, abstractmethod
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from deprecated.sphinx import deprecated
7
+
6
8
  if TYPE_CHECKING: # pragma: no cover
7
9
  from pathlib import Path
8
10
 
@@ -32,6 +34,10 @@ class IVersionReplacer(metaclass=ABCMeta):
32
34
  )
33
35
  )
34
36
 
37
+ @deprecated(
38
+ version="9.20.0",
39
+ reason="Function is unused and will be removed in a future release",
40
+ )
35
41
  @abstractmethod
36
42
  def parse(self) -> set[Version]:
37
43
  """