4949
5050import dataclasses
5151import os
52- import sys
5352import typing
54- from typing import Any , Literal , Protocol , TypeVar , Union
53+ from typing import Any , Literal , Protocol , TypeVar
5554
5655from .._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
5965if 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
7469T = 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-
175105def _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 ):
0 commit comments