Skip to content

Commit 0e73054

Browse files
Merge pull request #1004 from thejcannon/jcannon/1003
Be nicer about possible misconfigurations
2 parents 18aa7f0 + d49399b commit 0e73054

File tree

5 files changed

+104
-36
lines changed

5 files changed

+104
-36
lines changed

src/setuptools_scm/_config.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ def _check_tag_regex(value: str | Pattern[str] | None) -> Pattern[str]:
4444

4545
group_names = regex.groupindex.keys()
4646
if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names):
47-
warnings.warn(
48-
"Expected tag_regex to contain a single match group or a group named"
49-
" 'version' to identify the version part of any tag."
47+
raise ValueError(
48+
f"Expected tag_regex '{regex.pattern}' to contain a single match group or"
49+
" a group named 'version' to identify the version part of any tag."
5050
)
5151

5252
return regex
@@ -107,6 +107,9 @@ class Configuration:
107107

108108
parent: _t.PathT | None = None
109109

110+
def __post_init__(self) -> None:
111+
self.tag_regex = _check_tag_regex(self.tag_regex)
112+
110113
@property
111114
def absolute_root(self) -> str:
112115
return _check_absolute_root(self.root, self.relative_to)
@@ -155,13 +158,11 @@ def from_data(
155158
given configuration data
156159
create a config instance after validating tag regex/version class
157160
"""
158-
tag_regex = _check_tag_regex(data.pop("tag_regex", None))
159161
version_cls = _validate_version_cls(
160162
data.pop("version_cls", None), data.pop("normalize", True)
161163
)
162164
return cls(
163165
relative_to=relative_to,
164166
version_cls=version_cls,
165-
tag_regex=tag_regex,
166167
**data,
167168
)

src/setuptools_scm/_entrypoints.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import Callable
88
from typing import Iterator
99
from typing import cast
10-
from typing import overload
1110

1211
from . import _log
1312
from . import version
@@ -102,32 +101,26 @@ def _iter_version_schemes(
102101
yield scheme_value
103102

104103

105-
@overload
106104
def _call_version_scheme(
107105
version: version.ScmVersion,
108106
entrypoint: str,
109107
given_value: _t.VERSION_SCHEMES,
110-
default: str,
111-
) -> str: ...
112-
113-
114-
@overload
115-
def _call_version_scheme(
116-
version: version.ScmVersion,
117-
entrypoint: str,
118-
given_value: _t.VERSION_SCHEMES,
119-
default: None,
120-
) -> str | None: ...
121-
122-
123-
def _call_version_scheme(
124-
version: version.ScmVersion,
125-
entrypoint: str,
126-
given_value: _t.VERSION_SCHEMES,
127-
default: str | None,
128-
) -> str | None:
108+
default: str | None = None,
109+
) -> str:
110+
found_any_implementation = False
129111
for scheme in _iter_version_schemes(entrypoint, given_value):
112+
found_any_implementation = True
130113
result = scheme(version)
131114
if result is not None:
132115
return result
133-
return default
116+
if not found_any_implementation:
117+
raise ValueError(
118+
f'Couldn\'t find any implementations for entrypoint "{entrypoint}"'
119+
f' with value "{given_value}".'
120+
)
121+
if default is not None:
122+
return default
123+
raise ValueError(
124+
f'None of the "{entrypoint}" entrypoints matching "{given_value}"'
125+
" returned a value."
126+
)

src/setuptools_scm/version.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,21 @@ def _parse_version_tag(
6262
log.debug(
6363
"key %s data %s, %s, %r", key, match.groupdict(), match.groups(), full
6464
)
65-
result = _TagDict(
66-
version=match.group(key),
67-
prefix=full[: match.start(key)],
68-
suffix=full[match.end(key) :],
69-
)
7065

71-
log.debug("tag %r parsed to %r", tag, result)
72-
assert result["version"]
73-
return result
66+
if version := match.group(key):
67+
result = _TagDict(
68+
version=version,
69+
prefix=full[: match.start(key)],
70+
suffix=full[match.end(key) :],
71+
)
72+
73+
log.debug("tag %r parsed to %r", tag, result)
74+
return result
75+
76+
raise ValueError(
77+
f'The tag_regex "{config.tag_regex.pattern}" matched tag "{tag}", '
78+
"however the matched group has no value."
79+
)
7480
else:
7581
log.debug("tag %r did not parse", tag)
7682

@@ -449,8 +455,9 @@ def format_version(version: ScmVersion) -> str:
449455
if version.preformatted:
450456
assert isinstance(version.tag, str)
451457
return version.tag
458+
452459
main_version = _entrypoints._call_version_scheme(
453-
version, "setuptools_scm.version_scheme", version.config.version_scheme, None
460+
version, "setuptools_scm.version_scheme", version.config.version_scheme
454461
)
455462
log.debug("version %s", main_version)
456463
assert main_version is not None

testing/test_config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,23 @@ def test_config_overrides(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> No
9898

9999
assert pristine.root != overridden.root
100100
assert pristine.fallback_root != overridden.fallback_root
101+
102+
103+
@pytest.mark.parametrize(
104+
"tag_regex",
105+
[
106+
r".*",
107+
r"(.+)(.+)",
108+
r"((.*))",
109+
],
110+
)
111+
def test_config_bad_regex(tag_regex: str) -> None:
112+
with pytest.raises(
113+
ValueError,
114+
match=(
115+
f"Expected tag_regex '{re.escape(tag_regex)}' to contain a single match"
116+
" group or a group named 'version' to identify the version part of any"
117+
" tag."
118+
),
119+
):
120+
Configuration(tag_regex=re.compile(tag_regex))

testing/test_version.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import re
4+
35
from dataclasses import replace
46
from datetime import date
57
from datetime import datetime
@@ -223,6 +225,17 @@ def test_tag_regex1(tag: str, expected: str) -> None:
223225
assert result.tag.public == expected
224226

225227

228+
def test_regex_match_but_no_version() -> None:
229+
with pytest.raises(
230+
ValueError,
231+
match=(
232+
r'The tag_regex "\(\?P<version>\)\.\*" matched tag "v1",'
233+
" however the matched group has no value"
234+
),
235+
):
236+
meta("v1", config=replace(c, tag_regex=re.compile("(?P<version>).*")))
237+
238+
226239
@pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/471")
227240
def test_version_bump_bad() -> None:
228241
class YikesVersion:
@@ -463,3 +476,37 @@ def __repr__(self) -> str:
463476

464477
assert isinstance(scm_version.tag, MyVersion)
465478
assert str(scm_version.tag) == "Custom 1.0.0-foo"
479+
480+
481+
@pytest.mark.parametrize("config_key", ["version_scheme", "local_scheme"])
482+
def test_no_matching_entrypoints(config_key: str) -> None:
483+
version = meta(
484+
"1.0",
485+
config=replace(c, **{config_key: "nonexistant"}), # type: ignore
486+
)
487+
with pytest.raises(
488+
ValueError,
489+
match=(
490+
r'Couldn\'t find any implementations for entrypoint "setuptools_scm\..*?"'
491+
' with value "nonexistant"'
492+
),
493+
):
494+
format_version(version)
495+
496+
497+
def test_all_entrypoints_return_none() -> None:
498+
version = meta(
499+
"1.0",
500+
config=replace(
501+
c,
502+
version_scheme=lambda v: None, # type: ignore
503+
),
504+
)
505+
with pytest.raises(
506+
ValueError,
507+
match=(
508+
'None of the "setuptools_scm.version_scheme" entrypoints matching'
509+
r" .*? returned a value."
510+
),
511+
):
512+
format_version(version)

0 commit comments

Comments
 (0)