1- import collections
21import datetime
32import os
43import warnings
5- from pathlib import Path
4+ from typing import Any , Callable , Mapping , NamedTuple , Optional , Union
65
76from .ciphers import DecryptError
87from .exceptions import ConfigError , ConfigWarning
98from .sources import BaseSource , EnvDir , EnvFile , HostEnv , Source
9+ from .types import StrPath
1010
1111BOOLEAN_STRINGS = {
1212 "true" : True ,
1717 "0" : False ,
1818}
1919
20- ConfigValue = collections .namedtuple (
21- "ConfigValue" , ["raw" , "value" , "source" , "default" , "sensitive" , "ttl" ]
22- )
20+
21+ class ConfigValue (NamedTuple ):
22+ raw : Any
23+ value : Any
24+ source : Optional [BaseSource ]
25+ default : Any
26+ sensitive : bool
27+ ttl : Optional [int ]
28+
29+
30+ SourceTypes = Union [BaseSource , StrPath , Mapping [str , Any ]]
31+ CastCallable = Callable [..., Any ]
2332
2433
2534class undefined :
@@ -28,32 +37,35 @@ def __bool__(self):
2837
2938
3039class Config :
31- def __init__ (self , * sources , ** kwargs ):
40+ _sources : list [BaseSource ]
41+ _defined : dict [str , ConfigValue ]
42+
43+ def __init__ (self , * sources : SourceTypes , ** kwargs : Any ):
3244 self ._debug = False
3345 self ._previous_debug = False
3446 self .setup (* sources , ** kwargs )
3547
3648 def __enter__ (self ):
3749 return self
3850
39- def __exit__ (self , * exc_details ):
51+ def __exit__ (self , * exc_details : Any ):
4052 self ._debug = self ._previous_debug
4153
42- def setup (self , * sources , ** kwargs ):
54+ def setup (self , * sources : SourceTypes , ** kwargs : Any ):
4355 self ._debug = kwargs .pop ("debug" , self ._debug )
4456 self ._previous_debug = self ._debug
4557 self .reset ()
4658 for source in sources :
4759 if isinstance (source , BaseSource ):
4860 self .source (source )
49- elif isinstance (source , (str , Path )):
61+ elif isinstance (source , (str , os . PathLike )):
5062 if not os .path .exists (source ):
5163 raise ConfigError (f"File or directory not found: `{ source } `" )
5264 if os .path .isdir (source ):
5365 self .dir (source , ** kwargs )
5466 else :
5567 self .file (source , ** kwargs )
56- elif hasattr (source , "__getitem__" ):
68+ elif isinstance (source , Mapping ): # type: ignore
5769 self .env (source , ** kwargs )
5870 else :
5971 raise ConfigError (f"Unknown configuration source: { source } " )
@@ -66,31 +78,31 @@ def reset(self):
6678 self ._defined = {}
6779 return self
6880
69- def debug (self , value = True ):
81+ def debug (self , value : bool = True ):
7082 self ._previous_debug = self ._debug
7183 self ._debug = value
7284 return self
7385
74- def source (self , source ):
86+ def source (self , source : BaseSource ):
7587 """
7688 Adds a configuration source to the list of checked sources.
7789 """
7890 self ._sources .append (source )
7991 return self
8092
81- def file (self , path , ** kwargs ):
93+ def file (self , path : StrPath , ** kwargs : Any ):
8294 """
8395 Adds an `EnvFile` source to the list of checked sources.
8496 """
8597 return self .source (EnvFile (path , ** kwargs ))
8698
87- def dir (self , path , ** kwargs ):
99+ def dir (self , path : StrPath , ** kwargs : Any ):
88100 """
89101 Adds an `EnvDir` source to the list of checked sources.
90102 """
91103 return self .source (EnvDir (path , ** kwargs ))
92104
93- def env (self , environ = None , ** kwargs ):
105+ def env (self , environ : Optional [ Mapping [ str , Any ]] = None , ** kwargs : Any ):
94106 """
95107 Adds either a `HostEnv` source, or a generic `Source` to the list of checked
96108 sources, based on whether `environ` is set.
@@ -105,16 +117,23 @@ def defined(self):
105117 """
106118 return {k : v .value for k , v in self ._defined .items ()}
107119
108- def __call__ (self , key , default = undefined , cast = None , sensitive = False , ttl = None ):
109- sources_checked = []
120+ def __call__ (
121+ self ,
122+ key : str ,
123+ default : Any = undefined ,
124+ cast : Optional [CastCallable ] = None ,
125+ sensitive : bool = False ,
126+ ttl : Optional [Union [int , datetime .timedelta ]] = None ,
127+ ) -> Any :
128+ sources_checked : list [str ] = []
110129 key = str (key )
130+ if isinstance (ttl , datetime .timedelta ):
131+ ttl = int (ttl .total_seconds ())
111132 for source in self ._sources :
112133 sources_checked .append (str (source ))
113134 try :
114135 raw = source [key ]
115136 if sensitive :
116- if isinstance (ttl , datetime .timedelta ):
117- ttl = int (ttl .total_seconds ())
118137 raw = source .decrypt (raw , ttl = ttl )
119138 value = self ._perform_cast (raw , cast , key = key )
120139 self ._defined [key ] = ConfigValue (
@@ -160,7 +179,7 @@ def __call__(self, key, default=undefined, cast=None, sensitive=False, ttl=None)
160179 raise KeyError (f"`{ key } ` not found in any of: { checked } " )
161180 return default
162181
163- def _perform_cast (self , value , cast , key = "" ):
182+ def _perform_cast (self , value : Any , cast : Optional [ CastCallable ] , key : str = "" ):
164183 if cast is None or value is None :
165184 return value
166185 elif cast is bool and isinstance (value , str ):
0 commit comments