Skip to content

Commit 6f2b676

Browse files
committed
fix: allow more fields in regex
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 2952e9f commit 6f2b676

File tree

7 files changed

+118
-18
lines changed

7 files changed

+118
-18
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
5+
if sys.version_info < (3, 9):
6+
from graphlib_backport import TopologicalSorter
7+
else:
8+
from graphlib import TopologicalSorter
9+
10+
__all__ = ["TopologicalSorter"]
11+
12+
13+
def __dir__() -> list[str]:
14+
return __all__

src/scikit_build_core/metadata/__init__.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from __future__ import annotations
22

3-
__all__: list[str] = ["_DICT_LIST_FIELDS", "_LIST_STR_FIELDS", "_STR_FIELDS"]
3+
import typing
4+
5+
if typing.TYPE_CHECKING:
6+
from collections.abc import Callable
7+
8+
9+
__all__: list[str] = ["_process_dynamic_metadata"]
410

511

612
# Name is not dynamically settable, so not in this list
@@ -23,9 +29,38 @@
2329
]
2430
)
2531

26-
_DICT_LIST_FIELDS = frozenset(
27-
[
28-
"urls",
29-
"optional-dependencies",
30-
]
31-
)
32+
T = typing.TypeVar("T", bound="str | list[str] | dict[str, str] | dict[str, list[str]]")
33+
34+
35+
def _process_dynamic_metadata(field: str, action: Callable[[str], str], result: T) -> T:
36+
"""
37+
Helper function for processing the an action on the various possible metadata fields.
38+
"""
39+
40+
if field in _STR_FIELDS:
41+
if not isinstance(result, str):
42+
msg = f"Field {field!r} must be a string"
43+
raise RuntimeError(msg)
44+
return action(result) # type: ignore[return-value]
45+
if field in _LIST_STR_FIELDS:
46+
if not (isinstance(result, list) and all(isinstance(r, str) for r in result)):
47+
msg = f"Field {field!r} must be a list of strings"
48+
raise RuntimeError(msg)
49+
return [action(r) for r in result] # type: ignore[return-value]
50+
if field == "urls":
51+
if not isinstance(result, dict) or not all(
52+
isinstance(v, str) for v in result.values()
53+
):
54+
msg = "Field 'urls' must be a dictionary of strings"
55+
raise RuntimeError(msg)
56+
return {k: action(v) for k, v in result.items()} # type: ignore[return-value]
57+
if field == "optional-dependencies":
58+
if not isinstance(result, dict) or not all(
59+
isinstance(v, list) for v in result.values()
60+
):
61+
msg = "Field 'optional-dependencies' must be a dictionary of lists"
62+
raise RuntimeError(msg)
63+
return {k: [action(r) for r in v] for k, v in result.items()} # type: ignore[return-value]
64+
65+
msg = f"Unsupported field {field!r} for action"
66+
raise RuntimeError(msg)

src/scikit_build_core/metadata/regex.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from __future__ import annotations
22

3+
import functools
34
import re
45
from pathlib import Path
56
from typing import TYPE_CHECKING, Any
67

7-
from . import _STR_FIELDS
8+
from . import _process_dynamic_metadata
89

910
if TYPE_CHECKING:
1011
from collections.abc import Mapping
@@ -19,14 +20,18 @@ def __dir__() -> list[str]:
1920
KEYS = {"input", "regex", "result", "remove"}
2021

2122

23+
def _process(match: re.Match[str], remove: str, result: str) -> str:
24+
retval = result.format(*match.groups(), **match.groupdict())
25+
if remove:
26+
retval = re.sub(remove, "", retval)
27+
return retval
28+
29+
2230
def dynamic_metadata(
2331
field: str,
2432
settings: Mapping[str, Any],
2533
) -> str:
2634
# Input validation
27-
if field not in _STR_FIELDS:
28-
msg = "Only string fields supported by this plugin"
29-
raise RuntimeError(msg)
3035
if settings.keys() > KEYS:
3136
msg = f"Only {KEYS} settings allowed by this plugin"
3237
raise RuntimeError(msg)
@@ -57,7 +62,6 @@ def dynamic_metadata(
5762
msg = f"Couldn't find {regex!r} in {input_filename}"
5863
raise RuntimeError(msg)
5964

60-
retval = result.format(*match.groups(), **match.groupdict())
61-
if remove:
62-
retval = re.sub(remove, "", retval)
63-
return retval
65+
return _process_dynamic_metadata(
66+
field, functools.partial(_process, match, remove), result
67+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any
4+
5+
if TYPE_CHECKING:
6+
from collections.abc import Mapping
7+
8+
from . import _process_dynamic_metadata
9+
10+
__all__ = ["dynamic_metadata", "dynamic_metadata_needs"]
11+
12+
13+
def __dir__() -> list[str]:
14+
return __all__
15+
16+
17+
KEYS = {"needs", "result"}
18+
19+
20+
def dynamic_metadata(
21+
field: str,
22+
settings: Mapping[str, str | list[str] | dict[str, str] | dict[str, list[str]]],
23+
metadata: Mapping[str, Any],
24+
) -> str | list[str] | dict[str, str] | dict[str, list[str]]:
25+
if settings.keys() > KEYS:
26+
msg = f"Only {KEYS} settings allowed by this plugin"
27+
raise RuntimeError(msg)
28+
29+
if "result" not in settings:
30+
msg = "Must contain the 'result' setting with a template substitution"
31+
raise RuntimeError(msg)
32+
33+
result = settings["result"]
34+
35+
return _process_dynamic_metadata(
36+
field,
37+
lambda r: r.format(**metadata),
38+
result,
39+
)
40+
41+
42+
def dynamic_metadata_needs(
43+
field: str, # noqa: ARG001
44+
settings: Mapping[str, Any],
45+
) -> list[str]:
46+
return settings.get("needs", [])

tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def pep518_wheelhouse(tmp_path_factory: pytest.TempPathFactory) -> Path:
6060
if importlib.util.find_spec("ninja") is not None:
6161
packages.append("ninja")
6262

63+
if sys.version_info < (3, 9):
64+
packages.append("graphlib_backport")
65+
6366
subprocess.run(
6467
[
6568
sys.executable,

tests/packages/dynamic_metadata/plugin_project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ readme.provider = "scikit_build_core.metadata.fancy_pypi_readme"
1212

1313
[tool.scikit-build.metadata.optional-dependencies]
1414
provider = "scikit_build_core.metadata.template"
15-
needs = ["version", "name"]
15+
needs = ["version"]
1616
result = {"dev" = ["{name}=={version}"]}
1717

1818
[tool.setuptools_scm]

tests/test_dynamic_metadata.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,6 @@ def test_regex(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
277277
def test_regex_errors() -> None:
278278
with pytest.raises(RuntimeError):
279279
regex.dynamic_metadata("version", {})
280-
with pytest.raises(RuntimeError, match="Only string fields supported"):
281-
regex.dynamic_metadata("author", {"input": "x", "regex": "x"})
282280

283281

284282
def test_multipart_regex(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:

0 commit comments

Comments
 (0)