1+ """
2+ This is the configuration tooling for scikit-build-core. This is build around
3+ the :class:`Source` Protocol. Sources are created with some input (like a toml
4+ file for the :class:`TOMLSource`). Sources also usually have some prefix (like
5+ ``tool.scikit-build``) as well. The :class:`SourceChain` holds a collection of
6+ Sources, and is the primary way to use them.
7+
8+ An end user interacts with :class:`SourceChain` via ``.convert_target``, which
9+ takes a Dataclass class and returns an instance with fields populated.
10+
11+ Example of usage::
12+
13+ sources = SourceChain(TOMLSource("tool", "mypackage", settings=pyproject_dict), ...)
14+ settings = sources.convert_target(SomeSettingsModel)
15+
16+ unrecognized_options = list(source.unrecognized_options(SomeSettingsModel)
17+
18+
19+ Naming conventions:
20+
21+ - ``model`` is the complete Dataclass.
22+ - ``target`` is the type to convert a single item to.
23+ - ``settings`` is the input data source (unless it already has a name, like
24+ ``env``).
25+ - ``options`` are the names of the items in the ``model``, formatted in the
26+ style of the current Source.
27+ - ``fields`` are the tuple of strings describing a nested field in the
28+ ``model``.
29+ """
30+
31+
132from __future__ import annotations
233
334import dataclasses
435import os
36+ import typing
537from collections .abc import Generator , Iterator , Mapping , Sequence
638from typing import Any , TypeVar , Union
739
@@ -17,16 +49,16 @@ def __dir__() -> list[str]:
1749 return __all__
1850
1951
20- def _dig_strict (dict_ : Mapping [str , Any ], * names : str ) -> Any :
52+ def _dig_strict (__dict : Mapping [str , Any ], * names : str ) -> Any :
2153 for name in names :
22- dict_ = dict_ [name ]
23- return dict_
54+ __dict = __dict [name ]
55+ return __dict
2456
2557
26- def _dig_not_strict (dict_ : Mapping [str , Any ], * names : str ) -> Any :
58+ def _dig_not_strict (__dict : Mapping [str , Any ], * names : str ) -> Any :
2759 for name in names :
28- dict_ = dict_ .get (name , {})
29- return dict_
60+ __dict = __dict .get (name , {})
61+ return __dict
3062
3163
3264def _dig_fields (__opt : Any , * names : str ) -> Any :
@@ -53,7 +85,8 @@ def __args__(self) -> list[Any]:
5385
5486def _process_union (target : type [Any ]) -> Any :
5587 """
56- Selects the non-None item in an Optional or Optional-like Union. Passes through non-Unions.
88+ Selects the non-None item in an Optional or Optional-like Union. Passes
89+ through non-Unions.
5790 """
5891
5992 if (
@@ -77,8 +110,8 @@ def _process_union(target: type[Any]) -> Any:
77110
78111def _get_target_raw_type (target : type [Any ]) -> type [Any ]:
79112 """
80- Takes a type like Optional[str] and returns str,
81- or Optional[Dict[str, int]] and returns dict.
113+ Takes a type like `` Optional[str]`` and returns str,
114+ or `` Optional[Dict[str, int]]`` and returns dict.
82115 """
83116
84117 target = _process_union (target )
@@ -88,31 +121,31 @@ def _get_target_raw_type(target: type[Any]) -> type[Any]:
88121 return target
89122
90123
91- def _get_inner_type (target : type [Any ]) -> type [Any ]:
124+ def _get_inner_type (__target : type [Any ]) -> type [Any ]:
92125 """
93- Takes a types like List[str] and returns str,
94- or Dict[str, int] and returns int.
126+ Takes a types like `` List[str]`` and returns str,
127+ or `` Dict[str, int]`` and returns int.
95128 """
96129
97- raw_target = _get_target_raw_type (target )
98- target = _process_union (target )
130+ raw_target = _get_target_raw_type (__target )
131+ target = _process_union (__target )
99132 if raw_target == list :
100- assert isinstance (target , TypeLike )
133+ assert isinstance (__target , TypeLike )
101134 return target .__args__ [0 ]
102135 if raw_target == dict :
103- assert isinstance (target , TypeLike )
136+ assert isinstance (__target , TypeLike )
104137 return target .__args__ [1 ]
105138 msg = f"Expected a list or dict, got { target !r} "
106139 raise AssertionError (msg )
107140
108141
109- def _nested_dataclass_to_names (target : type [Any ], * inner : str ) -> Iterator [list [str ]]:
142+ def _nested_dataclass_to_names (__target : type [Any ], * inner : str ) -> Iterator [list [str ]]:
110143 """
111- Yields each entry, like ("a", "b", "c") for a.b.c
144+ Yields each entry, like `` ("a", "b", "c")`` for `` a.b.c``.
112145 """
113146
114- if dataclasses .is_dataclass (target ):
115- for field in dataclasses .fields (target ):
147+ if dataclasses .is_dataclass (__target ):
148+ for field in dataclasses .fields (__target ):
116149 yield from _nested_dataclass_to_names (field .type , * inner , field .name )
117150 else :
118151 yield list (inner )
@@ -121,23 +154,40 @@ def _nested_dataclass_to_names(target: type[Any], *inner: str) -> Iterator[list[
121154class Source (Protocol ):
122155 def has_item (self , * fields : str , is_dict : bool ) -> bool :
123156 """
124- Check if the source contains a chain of fields. For example, fields =
125- [Field(name="a"), Field(name="b")] will check if the source contains the
126- key "a.b".
157+ Check if the source contains a chain of fields. For example, `` fields =
158+ [Field(name="a"), Field(name="b")]`` will check if the source contains the
159+ key "a.b". ``is_dict`` should be set if it can be nested.
127160 """
128161 ...
129162
130163 def get_item (self , * fields : str , is_dict : bool ) -> Any :
164+ """
165+ Select an item from a chain of fields. Raises KeyError if
166+ the there is no item. ``is_dict`` should be set if it can be nested.
167+ """
131168 ...
132169
133170 @classmethod
134171 def convert (cls , item : Any , target : type [Any ]) -> object :
172+ """
173+ Convert an ``item`` from the base representation of the source's source
174+ into a ``target`` type. Raises TypeError if the conversion fails.
175+ """
135176 ...
136177
137178 def unrecognized_options (self , options : object ) -> Generator [str , None , None ]:
179+ """
180+ Given a model, produce an iterator of all unrecognized option names.
181+ Empty iterator if this can't be computed for the source (like for
182+ environment variables).
183+ """
138184 ...
139185
140186 def all_option_names (self , target : type [Any ]) -> Iterator [str ]:
187+ """
188+ Given a model, produce a list of all possible names (used for producing
189+ suggestions).
190+ """
141191 ...
142192
143193
@@ -185,7 +235,7 @@ def convert(cls, item: str, target: type[Any]) -> object:
185235 if callable (raw_target ):
186236 return raw_target (item )
187237 msg = f"Can't convert target { target } "
188- raise AssertionError (msg )
238+ raise TypeError (msg )
189239
190240 def unrecognized_options (
191241 self , options : object # noqa: ARG002
@@ -294,7 +344,7 @@ def convert(
294344 if callable (raw_target ):
295345 return raw_target (item )
296346 msg = f"Can't convert target { target } "
297- raise AssertionError (msg )
347+ raise TypeError (msg )
298348
299349 def unrecognized_options (self , options : object ) -> Generator [str , None , None ]:
300350 if not self .verify :
@@ -363,7 +413,7 @@ def convert(cls, item: Any, target: type[Any]) -> object:
363413 if callable (raw_target ):
364414 return raw_target (item )
365415 msg = f"Can't convert target { target } "
366- raise AssertionError (msg )
416+ raise TypeError (msg )
367417
368418 def unrecognized_options (self , options : object ) -> Generator [str , None , None ]:
369419 yield from _unrecognized_dict (self .settings , options , self .prefixes )
@@ -397,11 +447,6 @@ def get_item(self, *fields: str, is_dict: bool) -> Any:
397447 msg = f"{ fields !r} not found in any source"
398448 raise KeyError (msg )
399449
400- @classmethod
401- def convert (cls , item : Any , target : type [T ]) -> T : # noqa: ARG003
402- msg = "SourceChain cannot convert items, use the result from has_item"
403- raise NotImplementedError (msg )
404-
405450 def convert_target (self , target : type [T ], * prefixes : str ) -> T :
406451 """
407452 Given a dataclass type, create an object of that dataclass filled
@@ -467,6 +512,8 @@ def unrecognized_options(self, options: object) -> Generator[str, None, None]:
467512 for source in self .sources :
468513 yield from source .unrecognized_options (options )
469514
470- def all_option_names (self , target : type [Any ]) -> Iterator [str ]:
471- for source in self .sources :
472- yield from source .all_option_names (target )
515+
516+ if typing .TYPE_CHECKING :
517+ _ : Source = typing .cast (EnvSource , None )
518+ _ = typing .cast (ConfSource , None )
519+ _ = typing .cast (TOMLSource , None )
0 commit comments