Skip to content

Commit 5b2eaaa

Browse files
committed
feat: auto needs
Signed-off-by: Henry Schreiner <[email protected]>
1 parent d746f2b commit 5b2eaaa

File tree

2 files changed

+80
-40
lines changed

2 files changed

+80
-40
lines changed

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ classifiers = [
3232
dynamic = ["version"]
3333
dependencies = [
3434
"typing_extensions >=4.6; python_version<'3.11'",
35-
"graphlib_backport >=1; python_version<'3.9'",
3635
]
3736

3837
[project.urls]
@@ -101,7 +100,7 @@ disallow_untyped_defs = true
101100
disallow_incomplete_defs = true
102101

103102
[[tool.mypy.overrides]]
104-
module = ["setuptools_scm", "graphlib"]
103+
module = ["setuptools_scm"]
105104
ignore_missing_imports = true
106105

107106

src/dynamic_metadata/loader.py

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
from __future__ import annotations
22

3+
import dataclasses
34
import importlib
45
import sys
5-
from collections.abc import Generator, Iterable, Mapping
6+
from collections.abc import Iterator, Mapping
67
from pathlib import Path
7-
from typing import Any, Protocol, Union, runtime_checkable
8-
9-
from graphlib import TopologicalSorter
8+
from typing import TYPE_CHECKING, Any, Protocol, Union, runtime_checkable
109

1110
from .info import ALL_FIELDS
1211

13-
__all__ = ["load_dynamic_metadata", "load_provider"]
12+
if TYPE_CHECKING:
13+
from collections.abc import Generator, Iterable
14+
15+
StrMapping = Mapping[str, Any]
16+
else:
17+
StrMapping = Mapping
18+
19+
20+
__all__ = ["load_dynamic_metadata", "load_provider", "process_dynamic_metadata"]
1421

1522

1623
def __dir__() -> list[str]:
@@ -38,20 +45,10 @@ def dynamic_wheel(
3845
) -> bool: ...
3946

4047

41-
@runtime_checkable
42-
class DynamicMetadataNeeds(DynamicMetadataProtocol, Protocol):
43-
def dynamic_metadata_needs(
44-
self,
45-
field: str,
46-
settings: Mapping[str, object] | None = None,
47-
) -> list[str]: ...
48-
49-
5048
DMProtocols = Union[
5149
DynamicMetadataProtocol,
5250
DynamicMetadataRequirementsProtocol,
5351
DynamicMetadataWheelProtocol,
54-
DynamicMetadataNeeds,
5552
]
5653

5754

@@ -73,11 +70,9 @@ def load_provider(
7370
sys.path.pop(0)
7471

7572

76-
def _load_dynamic_metadata(
73+
def load_dynamic_metadata(
7774
metadata: Mapping[str, Mapping[str, str]],
78-
) -> Generator[
79-
tuple[str, DMProtocols | None, dict[str, str], frozenset[str]], None, None
80-
]:
75+
) -> Generator[tuple[str, DMProtocols | None, dict[str, str]], None, None]:
8176
for field, orig_config in metadata.items():
8277
if "provider" in orig_config:
8378
if field not in ALL_FIELDS:
@@ -87,27 +82,73 @@ def _load_dynamic_metadata(
8782
provider = config.pop("provider")
8883
provider_path = config.pop("provider-path", None)
8984
loaded_provider = load_provider(provider, provider_path)
90-
needs = frozenset(
91-
loaded_provider.dynamic_metadata_needs(field, config)
92-
if isinstance(loaded_provider, DynamicMetadataNeeds)
93-
else []
94-
)
95-
if needs > ALL_FIELDS:
96-
msg = f"Invalid dyanmic_metada_needs: {needs - ALL_FIELDS}"
97-
raise KeyError(msg)
98-
yield field, loaded_provider, config, needs
85+
yield field, loaded_provider, config
9986
else:
100-
yield field, None, dict(orig_config), frozenset()
87+
yield field, None, dict(orig_config)
10188

10289

103-
def load_dynamic_metadata(
104-
metadata: Mapping[str, Mapping[str, str]],
105-
) -> list[tuple[str, DMProtocols | None, dict[str, str]]]:
106-
initial = {f: (p, c, n) for (f, p, c, n) in _load_dynamic_metadata(metadata)}
90+
@dataclasses.dataclass
91+
class DynamicPyProject(StrMapping):
92+
settings: dict[str, dict[str, Any]]
93+
project: dict[str, Any]
94+
providers: dict[str, DMProtocols]
10795

108-
dynamic_fields = initial.keys()
109-
sorter = TopologicalSorter(
110-
{f: n & dynamic_fields for f, (_, _, n) in initial.items()}
96+
def __getitem__(self, key: str) -> Any:
97+
# Try to get the settings from either the static file or dynamic metadata provider
98+
if key in self.project:
99+
return self.project[key]
100+
101+
# Check if we are in a loop, i.e. something else is already requesting
102+
# this key while trying to get another key
103+
if key not in self.providers:
104+
dep_type = "missing" if key in self.settings else "circular"
105+
msg = f"Encountered a {dep_type} dependency at {key}"
106+
raise ValueError(msg)
107+
108+
provider = self.providers.pop(key)
109+
self.project[key] = provider.dynamic_metadata(
110+
key, self.settings[key], self.project
111+
)
112+
self.project["dynamic"].remove(key)
113+
114+
return self.project[key]
115+
116+
def __iter__(self) -> Iterator[str]:
117+
# 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
122+
123+
def __len__(self) -> int:
124+
return len(self.project) + len(self.providers)
125+
126+
def __contains__(self, key: object) -> bool:
127+
return key in self.project or key in self.providers
128+
129+
130+
def process_dynamic_metadata(
131+
project: Mapping[str, Any],
132+
metadata: Mapping[str, Mapping[str, str]],
133+
) -> dict[str, Any]:
134+
"""Process dynamic metadata.
135+
136+
This function loads the dynamic metadata providers and calls them to
137+
generate the dynamic metadata. It takes the original project table and
138+
returns a new project table. Empty providers are not supported; you
139+
need to implement this yourself for now if you support that.
140+
"""
141+
142+
initial = {f: (p, s) for (f, p, s) in load_dynamic_metadata(metadata)}
143+
for f, (p, _) in initial.items():
144+
if p is None:
145+
msg = f"{f} does not have a provider"
146+
raise KeyError(msg)
147+
148+
settings = DynamicPyProject(
149+
settings={f: s for f, (p, s) in initial.items() if p is not None},
150+
project=dict(project),
151+
providers={k: p for k, (p, _) in initial.items() if p is not None},
111152
)
112-
order = sorter.static_order()
113-
return [(f, *initial[f][:2]) for f in order]
153+
154+
return dict(settings)

0 commit comments

Comments
 (0)