11import datetime
22import os
33import warnings
4- from typing import Any , Callable , Mapping , NamedTuple , Optional , Union
4+ from collections .abc import Callable , Mapping
5+ from typing import Any , NamedTuple , TypeVar , overload
56
67from .ciphers import DecryptError
78from .exceptions import ConfigError , ConfigWarning
1011
1112BOOLEAN_STRINGS = {
1213 "true" : True ,
14+ "t" : True ,
1315 "yes" : True ,
16+ "y" : True ,
1417 "1" : True ,
1518 "false" : False ,
19+ "f" : False ,
1620 "no" : False ,
21+ "n" : False ,
1722 "0" : False ,
1823}
1924
2025
26+ SourceTypes = BaseSource | StrPath | Mapping [str , Any ]
27+
28+ T = TypeVar ("T" )
29+
30+
2131class ConfigValue (NamedTuple ):
2232 raw : Any
2333 value : Any
24- source : Optional [ BaseSource ]
34+ source : BaseSource | None
2535 default : Any
2636 sensitive : bool
27- ttl : Optional [int ]
28-
29-
30- SourceTypes = Union [BaseSource , StrPath , Mapping [str , Any ]]
31- CastCallable = Callable [..., Any ]
37+ ttl : int | None
3238
3339
34- class undefined :
40+ class Undefined :
3541 def __bool__ (self ):
3642 return False
3743
3844
45+ undefined = Undefined ()
46+
47+
3948class Config :
4049 _sources : list [BaseSource ]
4150 _defined : dict [str , ConfigValue ]
@@ -102,7 +111,7 @@ def dir(self, path: StrPath, **kwargs: Any):
102111 """
103112 return self .source (EnvDir (path , ** kwargs ))
104113
105- def env (self , environ : Optional [ Mapping [str , Any ]] = None , ** kwargs : Any ):
114+ def env (self , environ : Mapping [str , Any ] | None = None , ** kwargs : Any ):
106115 """
107116 Adds either a `HostEnv` source, or a generic `Source` to the list of checked
108117 sources, based on whether `environ` is set.
@@ -117,13 +126,38 @@ def defined(self):
117126 """
118127 return {k : v .value for k , v in self ._defined .items ()}
119128
129+ # When default=None, the returned value may be None (any cast of None is None).
130+ @overload
131+ def __call__ (
132+ self ,
133+ key : str ,
134+ default : None ,
135+ * ,
136+ cast : Callable [..., T ] = str ,
137+ sensitive : bool = ...,
138+ ttl : int | datetime .timedelta | None = ...,
139+ ) -> T | None : ...
140+
141+ # Otherwise, no matter what the default, the returned value is of type T.
142+ @overload
143+ def __call__ (
144+ self ,
145+ key : str ,
146+ default : Any = undefined ,
147+ * ,
148+ cast : Callable [..., T ] = str ,
149+ sensitive : bool = ...,
150+ ttl : int | datetime .timedelta | None = ...,
151+ ) -> T : ...
152+
120153 def __call__ (
121154 self ,
122155 key : str ,
123156 default : Any = undefined ,
124- cast : Optional [CastCallable ] = None ,
157+ * ,
158+ cast : Callable = str ,
125159 sensitive : bool = False ,
126- ttl : Optional [ Union [ int , datetime .timedelta ]] = None ,
160+ ttl : int | datetime .timedelta | None = None ,
127161 ) -> Any :
128162 sources_checked : list [str ] = []
129163 key = str (key )
@@ -175,16 +209,38 @@ def __call__(
175209 ConfigWarning ,
176210 stacklevel = 2 ,
177211 )
178- else :
179- raise KeyError (f"`{ key } ` not found in any of: { checked } " )
180- return default
212+ return default
213+ raise KeyError (f"`{ key } ` not found in any of: { checked } " )
181214
182- def _perform_cast (self , value : Any , cast : Optional [CastCallable ], key : str = "" ):
183- if cast is None or value is None :
215+ # None always casts to None.
216+ @overload
217+ def _perform_cast (
218+ self ,
219+ value : None ,
220+ cast : Callable [..., T ],
221+ key : str = "" ,
222+ ) -> None : ...
223+
224+ # Otherwise, this always returns the cast type.
225+ @overload
226+ def _perform_cast (
227+ self ,
228+ value : Any ,
229+ cast : Callable [..., T ],
230+ key : str = "" ,
231+ ) -> T : ...
232+
233+ def _perform_cast (
234+ self ,
235+ value : Any ,
236+ cast : Callable ,
237+ key : str = "" ,
238+ ) -> Any :
239+ if value is None :
184240 return value
185241 elif cast is bool and isinstance (value , str ):
186242 try :
187- return BOOLEAN_STRINGS [value .lower ()]
243+ return cast ( BOOLEAN_STRINGS [value .lower ()])
188244 except KeyError :
189245 raise ValueError (f"Invalid boolean for `{ key } `: `{ value } `" )
190246 try :
0 commit comments