44import sys
55from collections .abc import Generator , Iterable , Mapping
66from pathlib import Path
7- from typing import Any , Protocol , Union
7+ from typing import Any , Protocol , Union , runtime_checkable
8+
9+ from graphlib import TopologicalSorter
10+
11+ from .info import ALL_FIELDS
812
913__all__ = ["load_dynamic_metadata" , "load_provider" ]
1014
@@ -13,34 +17,41 @@ def __dir__() -> list[str]:
1317 return __all__
1418
1519
20+ @runtime_checkable
1621class DynamicMetadataProtocol (Protocol ):
1722 def dynamic_metadata (
18- self , fields : Iterable [str ], settings : dict [str , Any ]
23+ self , fields : Iterable [str ], settings : dict [str , Any ], metadata : dict [ str , Any ]
1924 ) -> dict [str , Any ]: ...
2025
2126
27+ @runtime_checkable
2228class DynamicMetadataRequirementsProtocol (DynamicMetadataProtocol , Protocol ):
2329 def get_requires_for_dynamic_metadata (
2430 self , settings : dict [str , Any ]
2531 ) -> list [str ]: ...
2632
2733
34+ @runtime_checkable
2835class DynamicMetadataWheelProtocol (DynamicMetadataProtocol , Protocol ):
2936 def dynamic_wheel (
3037 self , field : str , settings : Mapping [str , Any ] | None = None
3138 ) -> bool : ...
3239
3340
34- class DynamicMetadataRequirementsWheelProtocol (
35- DynamicMetadataRequirementsProtocol , DynamicMetadataWheelProtocol , Protocol
36- ): ...
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 ]: ...
3748
3849
3950DMProtocols = Union [
4051 DynamicMetadataProtocol ,
4152 DynamicMetadataRequirementsProtocol ,
4253 DynamicMetadataWheelProtocol ,
43- DynamicMetadataRequirementsWheelProtocol ,
54+ DynamicMetadataNeeds ,
4455]
4556
4657
@@ -62,14 +73,41 @@ def load_provider(
6273 sys .path .pop (0 )
6374
6475
65- def load_dynamic_metadata (
76+ def _load_dynamic_metadata (
6677 metadata : Mapping [str , Mapping [str , str ]],
67- ) -> 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+ ]:
6881 for field , orig_config in metadata .items ():
6982 if "provider" in orig_config :
83+ if field not in ALL_FIELDS :
84+ msg = f"{ field } is not a valid field"
85+ raise KeyError (msg )
7086 config = dict (orig_config )
7187 provider = config .pop ("provider" )
7288 provider_path = config .pop ("provider-path" , None )
73- yield field , load_provider (provider , provider_path ), config
89+ 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
7499 else :
75- yield field , None , dict (orig_config )
100+ yield field , None , dict (orig_config ), frozenset ()
101+
102+
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 )}
107+
108+ dynamic_fields = initial .keys ()
109+ sorter = TopologicalSorter (
110+ {f : n & dynamic_fields for f , (_ , _ , n ) in initial .items ()}
111+ )
112+ order = sorter .static_order ()
113+ return [(f , * initial [f ][:2 ]) for f in order ]
0 commit comments