33import importlib
44import sys
55from pathlib import Path
6- from typing import TYPE_CHECKING , Any , Protocol , Union
6+ from typing import TYPE_CHECKING , Any , Protocol , Union , runtime_checkable
7+
8+ from .._compat .graphlib import TopologicalSorter
79
810if TYPE_CHECKING :
911 from collections .abc import Generator , Iterable , Mapping
@@ -15,34 +17,41 @@ def __dir__() -> list[str]:
1517 return __all__
1618
1719
20+ @runtime_checkable
1821class DynamicMetadataProtocol (Protocol ):
1922 def dynamic_metadata (
20- self , fields : Iterable [str ], settings : dict [str , Any ]
23+ self , fields : Iterable [str ], settings : dict [str , Any ], metadata : dict [ str , Any ]
2124 ) -> dict [str , Any ]: ...
2225
2326
27+ @runtime_checkable
2428class DynamicMetadataRequirementsProtocol (DynamicMetadataProtocol , Protocol ):
2529 def get_requires_for_dynamic_metadata (
2630 self , settings : dict [str , Any ]
2731 ) -> list [str ]: ...
2832
2933
34+ @runtime_checkable
3035class DynamicMetadataWheelProtocol (DynamicMetadataProtocol , Protocol ):
3136 def dynamic_wheel (
3237 self , field : str , settings : Mapping [str , Any ] | None = None
3338 ) -> bool : ...
3439
3540
36- class DynamicMetadataRequirementsWheelProtocol (
37- DynamicMetadataRequirementsProtocol , DynamicMetadataWheelProtocol , Protocol
38- ): ...
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 ]: ...
3948
4049
4150DMProtocols = Union [
4251 DynamicMetadataProtocol ,
4352 DynamicMetadataRequirementsProtocol ,
4453 DynamicMetadataWheelProtocol ,
45- DynamicMetadataRequirementsWheelProtocol ,
54+ DynamicMetadataNeeds ,
4655]
4756
4857
@@ -64,14 +73,31 @@ def load_provider(
6473 sys .path .pop (0 )
6574
6675
67- def load_dynamic_metadata (
76+ def _load_dynamic_metadata (
6877 metadata : Mapping [str , Mapping [str , str ]],
69- ) -> Generator [tuple [str , DMProtocols | None , dict [str , str ]], None , None ]:
78+ ) -> Generator [
79+ tuple [str , DMProtocols | None , dict [str , str ], frozenset [str ]], None , None
80+ ]:
7081 for field , orig_config in metadata .items ():
7182 if "provider" in orig_config :
7283 config = dict (orig_config )
7384 provider = config .pop ("provider" )
7485 provider_path = config .pop ("provider-path" , None )
75- yield field , load_provider (provider , provider_path ), config
86+ loaded_provider = load_provider (provider , provider_path )
87+ needs = frozenset (
88+ loaded_provider .dynamic_metadata_needs (field , config )
89+ if isinstance (loaded_provider , DynamicMetadataNeeds )
90+ else []
91+ )
92+ yield field , loaded_provider , config , needs
7693 else :
77- yield field , None , dict (orig_config )
94+ yield field , None , dict (orig_config ), frozenset ()
95+
96+
97+ def load_dynamic_metadata (
98+ metadata : Mapping [str , Mapping [str , str ]],
99+ ) -> list [tuple [str , DMProtocols | None , dict [str , str ]]]:
100+ initial = {f : (p , c , n ) for (f , p , c , n ) in _load_dynamic_metadata (metadata )}
101+ sorter = TopologicalSorter ({f : n for f , (_ , _ , n ) in initial .items ()})
102+ order = sorter .static_order ()
103+ return [(f , * initial [f ][:2 ]) for f in order ]
0 commit comments