diff --git a/backend/src/hatchling/version/core.py b/backend/src/hatchling/version/core.py index b56fd3733..9b68eabfc 100644 --- a/backend/src/hatchling/version/core.py +++ b/backend/src/hatchling/version/core.py @@ -18,8 +18,10 @@ def __init__(self, root: str, relative_path: str) -> None: self.__path = os.path.normpath(os.path.join(root, relative_path)) self.__cached_read_data: tuple | None = None - def read(self, *, pattern: str | bool) -> str: + def read(self, *, pattern: str | bool, default: str | None = None) -> str: if not os.path.isfile(self.__path): + if default is not None: + return default message = f'file does not exist: {self.__relative_path}' raise OSError(message) diff --git a/backend/src/hatchling/version/source/regex.py b/backend/src/hatchling/version/source/regex.py index 9bb878d09..e30a5f225 100644 --- a/backend/src/hatchling/version/source/regex.py +++ b/backend/src/hatchling/version/source/regex.py @@ -20,8 +20,13 @@ def get_version_data(self) -> dict: message = 'option `pattern` must be a string' raise TypeError(message) + default = self.config.get('default') + if default is not None and not isinstance(default, str): + message = 'option `default` must be a string' + raise TypeError(message) + version_file = VersionFile(self.root, relative_path) - version = version_file.read(pattern=pattern) + version = version_file.read(pattern=pattern, default=default) return {'version': version, 'version_file': version_file} diff --git a/docs/plugins/version-source/regex.md b/docs/plugins/version-source/regex.md index 84d9e62c6..9bd725cb5 100644 --- a/docs/plugins/version-source/regex.md +++ b/docs/plugins/version-source/regex.md @@ -23,3 +23,4 @@ source = "regex" | --- | --- | | `path` (required) | A relative path to a file containing the project's version | | `pattern` | A regular expression that has a named group called `version` that represents the version. The default pattern looks for a variable named `__version__` or `VERSION` that is set to a string containing the version, optionally prefixed with the lowercase letter `v`. | +| `default` | A value used as the version if the file at `path` does not exist. | diff --git a/tests/backend/version/source/test_regex.py b/tests/backend/version/source/test_regex.py index 9eecf3be2..b7b938c1c 100644 --- a/tests/backend/version/source/test_regex.py +++ b/tests/backend/version/source/test_regex.py @@ -117,3 +117,28 @@ def foo(): with temp_dir.as_cwd(): source.set_version('foo', source.get_version_data()) assert source.get_version_data()['version'] == 'foo' + + +@pytest.mark.parametrize( + ('file_contents', 'default', 'expected_version'), + [ + ('1.2.3', '0.0.0', '1.2.3'), + ('1.2.3', '0.0.1', '1.2.3'), + (None, '0.dev', '0.dev'), + (None, '9.9.9', '9.9.9'), + ], +) +def test_with_default_value(temp_dir, file_contents, default, expected_version): + source = RegexSource( + str(temp_dir), + {'path': 'x/y', 'pattern': '^(?P.+)$', 'default': default}, + ) + + file_path = temp_dir / 'x' / 'y' + file_path.ensure_parent_dir_exists() + + if file_contents is not None: + file_path.write_text(file_contents) + + with temp_dir.as_cwd(): + assert source.get_version_data()['version'] == expected_version