Repository: callowayproject/bump-my-version (python)

Issue:
Single integer version "current_version" incorrectly autocast from str to int on dump/load, causing subsequent bumps to fail with pydantic_core._pydantic_core.ValidationError (Input should be a valid string)
* bump-my-version version: 0.12.0
* Python version: 3.9.18
* Operating System: Linux (Ubuntu 22.04)

### Description

When using a versioning scheme with only a single integer part (e.g. version 1, version 2, etc...) rather than a semantic version (e.g. version 1.2.3), something during a roundtrip of the config file causes subsequent bumps to fail. I'm not sure whether it's a problem with dumping the updated config after a bump, with loading the updated config on the next bump, or a combination of the two.

With this minimal example:

```
[bumpversion]
current_version = "2"
commit = False
tag = False
parse = (?P<major>\d+)
serialize = 
	{major}
...
```

After running a command like:

```
$ bump-my-version bump --allow-dirty --config-file .bumpversion.cfg major
```

The updated config file automatically becomes:

```
[bumpversion]
current_version = 3
commit = False
tag = False
parse = (?P<major>\d+)
serialize = 
	{major}
...
```

Instead of maintaining the string quotes, it has converted the value for the new `current_version` to an integer instead of a string.

Then on a future bump, you get the following error from Pydantic validation:

```
Traceback (most recent call last):
  File "/home/foo/virtualenv/bar/bin/bump-my-version", line 8, in <module>
    sys.exit(cli())
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/rich_click/rich_command.py", line 126, in main
    rv = self.invoke(ctx)
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/bumpversion/cli.py", line 284, in bump
    config = get_configuration(found_config_file, **overrides)
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/bumpversion/config.py", line 200, in get_configuration
    config = Config(**config_dict)  # type: ignore[arg-type]
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/pydantic_settings/main.py", line 71, in __init__
    super().__init__(
  File "/home/foo/virtualenv/bar/lib/python3.9/site-packages/pydantic/main.py", line 164, in __init__
    __pydantic_self__.__pydantic_validator__.validate_python(data, self_instance=__pydantic_self__)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Config
current_version
  Input should be a valid stsring [type=string_type, input_value=3, input_type=int]
    For further information visit https://errors.pydantic.dev/2.5/v/string_type
```

Apologies I haven't had time to try a TOML config or CLI only, but would assume they're subject to the same sort of validation on load.

### Expected behaviour

Running the bump command shouldn't require any manual edits inbetween runs.

Ideally the config would be written back in the exact same format with string quotes in the output if they were present in the input.

Alternatively, updating the autocast behaviour on load to handle ints as strings which are required by the Pydantic schema.

I'm not sure if it's viable to allow integer types for the `current_version` in the Pydantic schema as well as strings, or if it's just simpler to use strings consistently.

Let me know if I can provide any further information.

Cheers,
Kyle



Pull request:
Fix miscast of current_version
- When using the legacy configuration format, a single-digit version is parsed as an int

Fixes #99

Patch:
diff --git a/bumpversion/config/files_legacy.py b/bumpversion/config/files_legacy.py
index 7dcaba91..d930427a 100644
--- a/bumpversion/config/files_legacy.py
+++ b/bumpversion/config/files_legacy.py
@@ -43,6 +43,8 @@ def read_ini_file(file_path: Path) -> Dict[str, Any]:  # noqa: C901
         section_parts = section_name.split(":")
         num_parts = len(section_parts)
         options = {key: autocast.autocast_value(val) for key, val in config_parser.items(section_name)}
+        if "current_version" in options:
+            options["current_version"] = str(options["current_version"])
 
         if num_parts == 1:  # bumpversion section
             bumpversion_options.update(options)


Test patch:
diff --git a/tests/test_config/test_files_legacy.py b/tests/test_config/test_files_legacy.py
index 33c2a4fd..4acb7f4a 100644
--- a/tests/test_config/test_files_legacy.py
+++ b/tests/test_config/test_files_legacy.py
@@ -148,3 +148,22 @@ def test_utf8_message_from_config_file(tmp_path: Path, cfg_file):
         cfg = config.get_configuration(cfg_path)
 
     assert cfg.message == "Nová verze: {current_version} ☃, {new_version} ☀"
+
+
+def test_current_version_is_always_a_string(tmp_path: Path):
+    cfg_path = tmp_path / ".bumpversion.cfg"
+    initial_config = (
+        "[bumpversion]\n"
+        "current_version = 2\n"
+        "commit = False\n"
+        "tag = False\n"
+        "parse = (?P<major>\d+)\n"
+        "serialize = \n"
+        "	{major}\n"
+    )
+    cfg_path.write_bytes(initial_config.encode("utf-8"))
+
+    with inside_dir(tmp_path):
+        cfg = config.get_configuration(cfg_path)
+
+    assert cfg.current_version == "2"