Skip to content

Commit db72d46

Browse files
LecrisUThenryiii
andauthored
chore: Add an utils package (#1268)
For now just moving the annotation handling to that package --------- Signed-off-by: Cristian Le <git@lecris.dev> Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
1 parent 3a75f15 commit db72d46

6 files changed

Lines changed: 176 additions & 131 deletions

File tree

docs/api/scikit_build_core.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Subpackages
2121
scikit_build_core.resources
2222
scikit_build_core.settings
2323
scikit_build_core.setuptools
24+
scikit_build_core.utils
2425

2526
Submodules
2627
----------
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
scikit\_build\_core.utils package
2+
=================================
3+
4+
.. automodule:: scikit_build_core.utils
5+
:members:
6+
:show-inheritance:
7+
:undoc-members:
8+
9+
Submodules
10+
----------
11+
12+
scikit\_build\_core.utils.typing module
13+
---------------------------------------
14+
15+
.. automodule:: scikit_build_core.utils.typing
16+
:members:
17+
:show-inheritance:
18+
:undoc-members:

src/scikit_build_core/settings/sources.py

Lines changed: 44 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,22 @@
4949

5050
import dataclasses
5151
import os
52-
import sys
5352
import typing
54-
from typing import Any, Literal, Protocol, TypeVar, Union
53+
from typing import Any, Literal, Protocol, TypeVar
5554

5655
from .._compat.builtins import ExceptionGroup
57-
from .._compat.typing import Annotated, get_args, get_origin
56+
from .._compat.typing import get_args
57+
from ..utils.typing import (
58+
get_inner_type,
59+
get_target_raw_type,
60+
is_union_type,
61+
process_annotated,
62+
process_union,
63+
)
5864

5965
if typing.TYPE_CHECKING:
6066
from collections.abc import Generator, Iterator, Mapping, Sequence
6167

62-
# Runtime check for PEP 604 union syntax (A | B) support
63-
# types.UnionType only exists in Python 3.10+
64-
if sys.version_info >= (3, 10):
65-
from types import NoneType
66-
from types import UnionType as _UnionType
67-
else:
68-
NoneType = type(None)
69-
70-
class _UnionType:
71-
pass
72-
7368

7469
T = TypeVar("T")
7570

@@ -107,71 +102,6 @@ def _dig_fields(opt: Any, /, *names: str) -> Any:
107102
return opt
108103

109104

110-
def _process_union(target: Any, /) -> Any:
111-
"""
112-
Filters None out of Unions. If a Union only has one item, return that item.
113-
"""
114-
115-
origin = get_origin(target)
116-
117-
if _is_union_type(origin):
118-
non_none_args = [a for a in get_args(target) if a is not NoneType]
119-
if len(non_none_args) == 1:
120-
return non_none_args[0]
121-
return Union[tuple(non_none_args)]
122-
123-
return target
124-
125-
126-
def _process_annotated(target: type[Any], /) -> tuple[Any, tuple[Any, ...]]:
127-
"""
128-
Splits annotated into raw type and annotations. If not annotated, the annotations will be empty.
129-
"""
130-
131-
origin = get_origin(target)
132-
if origin is Annotated:
133-
return get_args(target)[0], get_args(target)[1:]
134-
135-
return target, ()
136-
137-
138-
def _get_target_raw_type(target: type[Any] | Any, /) -> Any:
139-
"""
140-
Takes a type like ``Optional[str]`` and returns str, or ``Optional[Dict[str,
141-
int]]`` and returns dict. Returns Union for a Union with more than one
142-
non-none type. Literal is also a valid return. Works through Annotated.
143-
"""
144-
145-
target, _ = _process_annotated(target)
146-
target = _process_union(target)
147-
origin = get_origin(target)
148-
return origin or target
149-
150-
151-
def _is_union_type(raw_target: Any) -> bool:
152-
"""
153-
Check if raw_target is a Union type (either ``typing.Union`` or ``types.UnionType``).
154-
Handles both ``typing.Union[A, B]`` and PEP 604 syntax (``A | B``).
155-
"""
156-
return raw_target is Union or raw_target is _UnionType
157-
158-
159-
def _get_inner_type(_target: type[Any], /) -> type[Any]:
160-
"""
161-
Takes a types like ``List[str]`` and returns str,
162-
or ``Dict[str, int]`` and returns int.
163-
"""
164-
165-
raw_target = _get_target_raw_type(_target)
166-
target = _process_union(_target)
167-
if raw_target is list:
168-
return get_args(target)[0] # type: ignore[no-any-return]
169-
if raw_target is dict:
170-
return get_args(target)[1] # type: ignore[no-any-return]
171-
msg = f"Expected a list or dict, got {target!r}"
172-
raise AssertionError(msg)
173-
174-
175105
def _nested_dataclass_to_names(
176106
target: type[Any] | Any, /, *inner: str
177107
) -> Iterator[list[str]]:
@@ -273,45 +203,43 @@ def convert(cls, item: str, target: type[Any] | Any) -> object:
273203
"""
274204
Convert an item from the environment (always a string) into a target type.
275205
"""
276-
target, _ = _process_annotated(target)
277-
raw_target = _get_target_raw_type(target)
206+
target, _ = process_annotated(target)
207+
raw_target = get_target_raw_type(target)
278208
if dataclasses.is_dataclass(raw_target):
279209
msg = f"Array of dataclasses are not supported in configuration settings ({raw_target})"
280210
raise TypeError(msg)
281211
if raw_target is list:
282212
return [
283-
cls.convert(i.strip(), _get_inner_type(target)) for i in item.split(";")
213+
cls.convert(i.strip(), get_inner_type(target)) for i in item.split(";")
284214
]
285215
if raw_target is dict:
286216
items = (i.strip().split("=") for i in item.split(";"))
287-
return {k: cls.convert(v, _get_inner_type(target)) for k, v in items}
217+
return {k: cls.convert(v, get_inner_type(target)) for k, v in items}
288218

289219
if raw_target is bool:
290220
return _process_bool(item)
291221

292-
if _is_union_type(raw_target) and str in get_args(target):
222+
if is_union_type(raw_target) and str in get_args(target):
293223
return item
294224

295-
if _is_union_type(raw_target):
296-
args = {_get_target_raw_type(t): t for t in get_args(target)}
225+
if is_union_type(raw_target):
226+
args = {get_target_raw_type(t): t for t in get_args(target)}
297227
if str in args:
298228
return item
299229
if dict in args and "=" in item:
300230
items = (i.strip().split("=") for i in item.split(";"))
301-
return {
302-
k: cls.convert(v, _get_inner_type(args[dict])) for k, v in items
303-
}
231+
return {k: cls.convert(v, get_inner_type(args[dict])) for k, v in items}
304232
if list in args:
305233
return [
306-
cls.convert(i.strip(), _get_inner_type(args[list]))
234+
cls.convert(i.strip(), get_inner_type(args[list]))
307235
for i in item.split(";")
308236
]
309237
msg = f"Can't convert into {target}"
310238
raise TypeError(msg)
311239

312240
if raw_target is Literal:
313-
if item not in get_args(_process_union(target)):
314-
msg = f"{item!r} not in {get_args(_process_union(target))!r}"
241+
if item not in get_args(process_union(target)):
242+
msg = f"{item!r} not in {get_args(process_union(target))!r}"
315243
raise TypeError(msg)
316244
return item
317245

@@ -411,34 +339,34 @@ def get_item(
411339
def convert(
412340
cls, item: str | list[str] | dict[str, str] | bool, target: type[Any] | Any
413341
) -> object:
414-
target, _ = _process_annotated(target)
415-
raw_target = _get_target_raw_type(target)
342+
target, _ = process_annotated(target)
343+
raw_target = get_target_raw_type(target)
416344
if dataclasses.is_dataclass(raw_target):
417345
msg = f"Array of dataclasses are not supported in configuration settings ({raw_target})"
418346
raise TypeError(msg)
419347
if raw_target is list:
420348
if isinstance(item, list):
421-
return [cls.convert(i, _get_inner_type(target)) for i in item]
349+
return [cls.convert(i, get_inner_type(target)) for i in item]
422350
if isinstance(item, (dict, bool)):
423351
msg = f"Expected {target}, got {type(item)}"
424352
raise TypeError(msg)
425353
return [
426-
cls.convert(i.strip(), _get_inner_type(target)) for i in item.split(";")
354+
cls.convert(i.strip(), get_inner_type(target)) for i in item.split(";")
427355
]
428356
if raw_target is dict:
429357
assert not isinstance(item, (str, list, bool))
430-
return {k: cls.convert(v, _get_inner_type(target)) for k, v in item.items()}
431-
if _is_union_type(raw_target):
432-
args = {_get_target_raw_type(t): t for t in get_args(target)}
358+
return {k: cls.convert(v, get_inner_type(target)) for k, v in item.items()}
359+
if is_union_type(raw_target):
360+
args = {get_target_raw_type(t): t for t in get_args(target)}
433361
if str in args:
434362
return item
435363
if dict in args and isinstance(item, dict):
436364
return {
437-
k: cls.convert(v, _get_inner_type(args[dict]))
365+
k: cls.convert(v, get_inner_type(args[dict]))
438366
for k, v in item.items()
439367
}
440368
if list in args and isinstance(item, list):
441-
return [cls.convert(i, _get_inner_type(args[list])) for i in item]
369+
return [cls.convert(i, get_inner_type(args[list])) for i in item]
442370
msg = f"Can't convert into {target}"
443371
raise TypeError(msg)
444372
if isinstance(item, (list, dict)):
@@ -447,8 +375,8 @@ def convert(
447375
if raw_target is bool:
448376
return item if isinstance(item, bool) else _process_bool(item)
449377
if raw_target is Literal:
450-
if item not in get_args(_process_union(target)):
451-
msg = f"{item!r} not in {get_args(_process_union(target))!r}"
378+
if item not in get_args(process_union(target)):
379+
msg = f"{item!r} not in {get_args(process_union(target))!r}"
452380
raise TypeError(msg)
453381
return item
454382
if callable(raw_target):
@@ -472,7 +400,7 @@ def unrecognized_options(self, options: object) -> Generator[str, None, None]:
472400
except KeyError:
473401
yield keystr
474402
continue
475-
if _get_target_raw_type(outer_option) is dict:
403+
if get_target_raw_type(outer_option) is dict:
476404
continue
477405

478406
def all_option_names(self, target: type[Any]) -> Iterator[str]:
@@ -511,8 +439,8 @@ def convert(cls, item: Any, target: type[Any] | Any) -> object:
511439
"""
512440
Convert an ``item`` from TOML into a ``target`` type.
513441
"""
514-
target, annotations = _process_annotated(target)
515-
raw_target = _get_target_raw_type(target)
442+
target, annotations = process_annotated(target)
443+
raw_target = get_target_raw_type(target)
516444
if dataclasses.is_dataclass(raw_target) and isinstance(raw_target, type):
517445
fields = dataclasses.fields(raw_target)
518446
values = ((k.replace("-", "_"), v) for k, v in item.items())
@@ -526,34 +454,34 @@ def convert(cls, item: Any, target: type[Any] | Any) -> object:
526454
if not isinstance(item, list):
527455
msg = f"Expected {target}, got {type(item).__name__}"
528456
raise TypeError(msg)
529-
return [cls.convert(it, _get_inner_type(target)) for it in item]
457+
return [cls.convert(it, get_inner_type(target)) for it in item]
530458
if raw_target is dict:
531459
if not isinstance(item, dict):
532460
msg = f"Expected {target}, got {type(item).__name__}"
533461
raise TypeError(msg)
534462
if annotations == ("EnvVar",):
535463
return {
536-
k: cls.convert(_dict_with_envvar(v), _get_inner_type(target))
464+
k: cls.convert(_dict_with_envvar(v), get_inner_type(target))
537465
for k, v in item.items()
538466
if _dict_with_envvar(v) is not None
539467
}
540-
return {k: cls.convert(v, _get_inner_type(target)) for k, v in item.items()}
468+
return {k: cls.convert(v, get_inner_type(target)) for k, v in item.items()}
541469
if raw_target is Any:
542470
return item
543-
if _is_union_type(raw_target):
544-
args = {_get_target_raw_type(t): t for t in get_args(target)}
471+
if is_union_type(raw_target):
472+
args = {get_target_raw_type(t): t for t in get_args(target)}
545473
if type(item) in args:
546474
if isinstance(item, dict):
547475
return {
548-
k: cls.convert(v, _get_inner_type(args[dict]))
476+
k: cls.convert(v, get_inner_type(args[dict]))
549477
for k, v in item.items()
550478
}
551479
if isinstance(item, list):
552-
return [cls.convert(i, _get_inner_type(args[list])) for i in item]
480+
return [cls.convert(i, get_inner_type(args[list])) for i in item]
553481
return item
554482
if raw_target is Literal:
555-
if item not in get_args(_process_union(target)):
556-
msg = f"{item!r} not in {get_args(_process_union(target))!r}"
483+
if item not in get_args(process_union(target)):
484+
msg = f"{item!r} not in {get_args(process_union(target))!r}"
557485
raise TypeError(msg)
558486
return item
559487
if callable(raw_target):
@@ -613,7 +541,7 @@ def convert_target(self, target: type[T], *prefixes: str) -> T:
613541
errors.append(e)
614542
continue
615543

616-
is_dict = _get_target_raw_type(field.type) is dict
544+
is_dict = get_target_raw_type(field.type) is dict
617545

618546
for source in self.sources:
619547
if source.has_item(*prefixes, field.name, is_dict=is_dict):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from __future__ import annotations
2+
3+
__all__: list[str] = []

0 commit comments

Comments
 (0)