Skip to content

Commit 0c7a678

Browse files
committed
tests: add tests for regex and template
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 5b2eaaa commit 0c7a678

File tree

8 files changed

+94
-77
lines changed

8 files changed

+94
-77
lines changed

README.md

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ https://github.com/scikit-build/scikit-build-core/issues/230.
1515

1616
> [!WARNING]
1717
>
18-
> This plugin is still a WiP!
18+
> This is still a WiP! The design may still change.
1919
2020
## For users
2121

@@ -64,16 +64,16 @@ needs to have a `"value"` named group (`?P<value>`), which it will set.
6464

6565
**You do not need to depend on dynamic-metadata to write a plugin.** This
6666
library provides testing and static typing helpers that are not needed at
67-
runtime.
67+
runtime, along with a reference implementation that you can either use as
68+
an example, or use directly if you are fine to require the dependency.
6869

6970
Like PEP 517's hooks, `dynamic-metadata` defines a set of hooks that you can
7071
implement; one required hook and two optional hooks. The required hook is:
7172

7273
```python
7374
def dynamic_metadata(
74-
field: str,
75-
settings: dict[str, object] | None = None,
76-
) -> str | dict[str, str | None]: ... # return the value of the metadata
75+
field: str, settings: Mapping[str, Any], project: Mapping[str, Any]
76+
) -> str | dict[str, Any]: ... # return the value of the metadata
7777
```
7878

7979
The backend will call this hook in the same directory as PEP 517's hooks.
@@ -84,7 +84,8 @@ A plugin can return METADATA 2.2 dynamic status:
8484

8585
```python
8686
def dynamic_wheel(
87-
field: str, settings: Mapping[str, Any] | None = None
87+
field: str,
88+
settings: Mapping[str, Any],
8889
) -> (
8990
bool
9091
): ... # Return true if metadata can change from SDist to wheel (METADATA 2.2 feature)
@@ -98,7 +99,7 @@ A plugin can also decide at runtime if it needs extra dependencies:
9899

99100
```python
100101
def get_requires_for_dynamic_metadata(
101-
settings: Mapping[str, Any] | None = None,
102+
settings: Mapping[str, Any],
102103
) -> list[str]: ... # return list of packages to require
103104
```
104105

@@ -113,6 +114,7 @@ Here is the regex plugin example implementation:
113114
def dynamic_metadata(
114115
field: str,
115116
settings: Mapping[str, Any],
117+
_project: Mapping[str, Any],
116118
) -> str:
117119
# Input validation
118120
if field not in {"version", "description", "requires-python"}:
@@ -147,36 +149,9 @@ library provides some helper functions you can use if you want, but you can
147149
implement them yourself following the standard provided or vendor the helper
148150
file (which will be tested and supported).
149151

150-
You should collect the contents of `tool.dynamic-metadata` and load each,
151-
something like this:
152-
153-
```python
154-
def load_provider(
155-
provider: str,
156-
provider_path: str | None = None,
157-
) -> DynamicMetadataProtocol:
158-
if provider_path is None:
159-
return importlib.import_module(provider)
160-
161-
if not Path(provider_path).is_dir():
162-
msg = "provider-path must be an existing directory"
163-
raise AssertionError(msg)
164-
165-
try:
166-
sys.path.insert(0, provider_path)
167-
return importlib.import_module(provider)
168-
finally:
169-
sys.path.pop(0)
170-
171-
172-
for dynamic_metadata in settings.metadata.values():
173-
if "provider" in dynamic_metadata:
174-
config = dynamic_metadata.copy()
175-
provider = config.pop("provider")
176-
provider_path = config.pop("provider-path", None)
177-
module = load_provider(provider, provider_path)
178-
# Run hooks from module
179-
```
152+
You should collect the contents of `tool.dynamic-metadata` and load each one.
153+
You should respect requests for metadata from other plugins, as well; to see
154+
how to do that, refer to `src/dynamic-metadata/loader.py`.
180155

181156
<!-- prettier-ignore-start -->
182157
[actions-badge]: https://github.com/scikit-build/dynamic-metadata/workflows/CI/badge.svg

src/dynamic_metadata/loader.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ def __dir__() -> list[str]:
2727
@runtime_checkable
2828
class DynamicMetadataProtocol(Protocol):
2929
def dynamic_metadata(
30-
self, fields: Iterable[str], settings: dict[str, Any], metadata: dict[str, Any]
30+
self,
31+
fields: Iterable[str],
32+
settings: dict[str, Any],
33+
project: Mapping[str, Any],
3134
) -> dict[str, Any]: ...
3235

3336

@@ -40,9 +43,7 @@ def get_requires_for_dynamic_metadata(
4043

4144
@runtime_checkable
4245
class DynamicMetadataWheelProtocol(DynamicMetadataProtocol, Protocol):
43-
def dynamic_wheel(
44-
self, field: str, settings: Mapping[str, Any] | None = None
45-
) -> bool: ...
46+
def dynamic_wheel(self, field: str, settings: Mapping[str, Any]) -> bool: ...
4647

4748

4849
DMProtocols = Union[
@@ -71,7 +72,7 @@ def load_provider(
7172

7273

7374
def load_dynamic_metadata(
74-
metadata: Mapping[str, Mapping[str, str]],
75+
metadata: Mapping[str, Mapping[str, Any]],
7576
) -> Generator[tuple[str, DMProtocols | None, dict[str, str]], None, None]:
7677
for field, orig_config in metadata.items():
7778
if "provider" in orig_config:
@@ -106,19 +107,14 @@ def __getitem__(self, key: str) -> Any:
106107
raise ValueError(msg)
107108

108109
provider = self.providers.pop(key)
109-
self.project[key] = provider.dynamic_metadata(
110-
key, self.settings[key], self.project
111-
)
110+
self.project[key] = provider.dynamic_metadata(key, self.settings[key], self)
112111
self.project["dynamic"].remove(key)
113112

114113
return self.project[key]
115114

116115
def __iter__(self) -> Iterator[str]:
117116
# Iterate over the keys of the static settings
118-
yield from self.project
119-
120-
# Iterate over the keys of the dynamic metadata providers
121-
yield from self.providers
117+
yield from [*self.project.keys(), *self.providers.keys()]
122118

123119
def __len__(self) -> int:
124120
return len(self.project) + len(self.providers)
@@ -129,7 +125,7 @@ def __contains__(self, key: object) -> bool:
129125

130126
def process_dynamic_metadata(
131127
project: Mapping[str, Any],
132-
metadata: Mapping[str, Mapping[str, str]],
128+
metadata: Mapping[str, Mapping[str, Any]],
133129
) -> dict[str, Any]:
134130
"""Process dynamic metadata.
135131

src/dynamic_metadata/plugins/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def _process_dynamic_metadata(field: str, action: Callable[[str], str], result:
2323
msg = f"Field {field!r} must be a list of strings"
2424
raise RuntimeError(msg)
2525
return [action(r) for r in result] # type: ignore[return-value]
26-
if field in DICT_STR_FIELDS:
26+
if field in DICT_STR_FIELDS | {"readme"}:
2727
if not isinstance(result, dict) or not all(
2828
isinstance(v, str) for v in result.values()
2929
):

src/dynamic_metadata/plugins/fancy_pypi_readme.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
__all__ = [
99
"dynamic_metadata",
10-
"dynamic_requires_needs",
1110
"get_requires_for_dynamic_metadata",
1211
]
1312

@@ -19,7 +18,7 @@ def __dir__() -> list[str]:
1918
def dynamic_metadata(
2019
field: str,
2120
settings: dict[str, list[str] | str],
22-
metadata: Mapping[str, Any],
21+
project: Mapping[str, Any],
2322
) -> dict[str, str | None]:
2423
from hatch_fancy_pypi_readme._builder import build_text
2524
from hatch_fancy_pypi_readme._config import load_and_validate_config
@@ -42,7 +41,7 @@ def dynamic_metadata(
4241
if hasattr(config, "substitutions"):
4342
try:
4443
text = build_text(
45-
config.fragments, config.substitutions, metadata["version"]
44+
config.fragments, config.substitutions, project["version"]
4645
)
4746
except TypeError:
4847
# Version 23.2.0 and before don't have a version field
@@ -63,10 +62,3 @@ def get_requires_for_dynamic_metadata(
6362
_settings: dict[str, object] | None = None,
6463
) -> list[str]:
6564
return ["hatch-fancy-pypi-readme>=22.3"]
66-
67-
68-
def dynamic_requires_needs(
69-
_field: str,
70-
_settings: dict[str, object],
71-
) -> list[str]:
72-
return ["version"]

src/dynamic_metadata/plugins/regex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def _process(match: re.Match[str], remove: str, result: str) -> str:
3030
def dynamic_metadata(
3131
field: str,
3232
settings: Mapping[str, Any],
33-
_metadata: Mapping[str, Any],
33+
_project: Mapping[str, Any],
3434
) -> str:
3535
# Input validation
3636
if settings.keys() > KEYS:

src/dynamic_metadata/plugins/setuptools_scm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def __dir__() -> list[str]:
1010
def dynamic_metadata(
1111
field: str,
1212
settings: dict[str, object],
13-
_metadata: dict[str, object],
13+
_project: dict[str, object],
1414
) -> str:
1515
# this is a classic implementation, waiting for the release of
1616
# vcs-versioning and an improved public interface

src/dynamic_metadata/plugins/template.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77

88
from . import _process_dynamic_metadata
99

10-
__all__ = ["dynamic_metadata", "dynamic_metadata_needs"]
10+
__all__ = ["dynamic_metadata"]
1111

1212

1313
def __dir__() -> list[str]:
1414
return __all__
1515

1616

17-
KEYS = {"needs", "result"}
17+
KEYS = {"result"}
1818

1919

2020
def dynamic_metadata(
2121
field: str,
2222
settings: Mapping[str, str | list[str] | dict[str, str] | dict[str, list[str]]],
23-
metadata: Mapping[str, Any],
23+
project: Mapping[str, Any],
2424
) -> str | list[str] | dict[str, str] | dict[str, list[str]]:
2525
if settings.keys() > KEYS:
2626
msg = f"Only {KEYS} settings allowed by this plugin"
@@ -34,13 +34,6 @@ def dynamic_metadata(
3434

3535
return _process_dynamic_metadata(
3636
field,
37-
lambda r: r.format(**metadata),
37+
lambda r: r.format(project=project),
3838
result,
3939
)
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/test_package.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,68 @@
11
from __future__ import annotations
22

3-
import dynamic_metadata as m
3+
import dynamic_metadata.loader
44

55

6-
def test_version():
7-
assert m.__version__
6+
def test_template_basic() -> None:
7+
pyproject = dynamic_metadata.loader.process_dynamic_metadata(
8+
{
9+
"name": "test",
10+
"version": "0.1.0",
11+
"dynamic": ["requires-python"],
12+
},
13+
{
14+
"requires-python": {
15+
"provider": "dynamic_metadata.plugins.template",
16+
"result": ">={project[version]}",
17+
},
18+
},
19+
)
20+
21+
assert pyproject["requires-python"] == ">=0.1.0"
22+
23+
24+
def test_template_needs() -> None:
25+
# These are intentionally out of order to test the order of processing
26+
pyproject = dynamic_metadata.loader.process_dynamic_metadata(
27+
{
28+
"name": "test",
29+
"version": "0.1.0",
30+
"dynamic": ["requires-python", "license", "readme"],
31+
},
32+
{
33+
"license": {
34+
"provider": "dynamic_metadata.plugins.template",
35+
"result": "{project[requires-python]}",
36+
},
37+
"readme": {
38+
"provider": "dynamic_metadata.plugins.template",
39+
"result": {"file": "{project[license]}"},
40+
},
41+
"requires-python": {
42+
"provider": "dynamic_metadata.plugins.template",
43+
"result": ">={project[version]}",
44+
},
45+
},
46+
)
47+
48+
assert pyproject["requires-python"] == ">=0.1.0"
49+
50+
51+
def test_regex() -> None:
52+
pyproject = dynamic_metadata.loader.process_dynamic_metadata(
53+
{
54+
"name": "test",
55+
"version": "0.1.0",
56+
"dynamic": ["requires-python"],
57+
},
58+
{
59+
"requires-python": {
60+
"provider": "dynamic_metadata.plugins.regex",
61+
"input": "pyproject.toml",
62+
"regex": r"name = \"(?P<name>.+)\"",
63+
"result": ">={name}",
64+
},
65+
},
66+
)
67+
68+
assert pyproject["requires-python"] == ">=dynamic-metadata"

0 commit comments

Comments
 (0)