Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/flask/config.py: 25%

118 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-22 06:29 +0000

1from __future__ import annotations 

2 

3import errno 

4import json 

5import os 

6import types 

7import typing as t 

8 

9from werkzeug.utils import import_string 

10 

11if t.TYPE_CHECKING: 

12 import typing_extensions as te 

13 

14 from .sansio.app import App 

15 

16 

17T = t.TypeVar("T") 

18 

19 

20class ConfigAttribute(t.Generic[T]): 

21 """Makes an attribute forward to the config""" 

22 

23 def __init__( 

24 self, name: str, get_converter: t.Callable[[t.Any], T] | None = None 

25 ) -> None: 

26 self.__name__ = name 

27 self.get_converter = get_converter 

28 

29 @t.overload 

30 def __get__(self, obj: None, owner: None) -> te.Self: 

31 ... 

32 

33 @t.overload 

34 def __get__(self, obj: App, owner: type[App]) -> T: 

35 ... 

36 

37 def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self: 

38 if obj is None: 

39 return self 

40 

41 rv = obj.config[self.__name__] 

42 

43 if self.get_converter is not None: 

44 rv = self.get_converter(rv) 

45 

46 return rv # type: ignore[no-any-return] 

47 

48 def __set__(self, obj: App, value: t.Any) -> None: 

49 obj.config[self.__name__] = value 

50 

51 

52class Config(dict): # type: ignore[type-arg] 

53 """Works exactly like a dict but provides ways to fill it from files 

54 or special dictionaries. There are two common patterns to populate the 

55 config. 

56 

57 Either you can fill the config from a config file:: 

58 

59 app.config.from_pyfile('yourconfig.cfg') 

60 

61 Or alternatively you can define the configuration options in the 

62 module that calls :meth:`from_object` or provide an import path to 

63 a module that should be loaded. It is also possible to tell it to 

64 use the same module and with that provide the configuration values 

65 just before the call:: 

66 

67 DEBUG = True 

68 SECRET_KEY = 'development key' 

69 app.config.from_object(__name__) 

70 

71 In both cases (loading from any Python file or loading from modules), 

72 only uppercase keys are added to the config. This makes it possible to use 

73 lowercase values in the config file for temporary values that are not added 

74 to the config or to define the config keys in the same file that implements 

75 the application. 

76 

77 Probably the most interesting way to load configurations is from an 

78 environment variable pointing to a file:: 

79 

80 app.config.from_envvar('YOURAPPLICATION_SETTINGS') 

81 

82 In this case before launching the application you have to set this 

83 environment variable to the file you want to use. On Linux and OS X 

84 use the export statement:: 

85 

86 export YOURAPPLICATION_SETTINGS='/path/to/config/file' 

87 

88 On windows use `set` instead. 

89 

90 :param root_path: path to which files are read relative from. When the 

91 config object is created by the application, this is 

92 the application's :attr:`~flask.Flask.root_path`. 

93 :param defaults: an optional dictionary of default values 

94 """ 

95 

96 def __init__( 

97 self, 

98 root_path: str | os.PathLike[str], 

99 defaults: dict[str, t.Any] | None = None, 

100 ) -> None: 

101 super().__init__(defaults or {}) 

102 self.root_path = root_path 

103 

104 def from_envvar(self, variable_name: str, silent: bool = False) -> bool: 

105 """Loads a configuration from an environment variable pointing to 

106 a configuration file. This is basically just a shortcut with nicer 

107 error messages for this line of code:: 

108 

109 app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) 

110 

111 :param variable_name: name of the environment variable 

112 :param silent: set to ``True`` if you want silent failure for missing 

113 files. 

114 :return: ``True`` if the file was loaded successfully. 

115 """ 

116 rv = os.environ.get(variable_name) 

117 if not rv: 

118 if silent: 

119 return False 

120 raise RuntimeError( 

121 f"The environment variable {variable_name!r} is not set" 

122 " and as such configuration could not be loaded. Set" 

123 " this variable and make it point to a configuration" 

124 " file" 

125 ) 

126 return self.from_pyfile(rv, silent=silent) 

127 

128 def from_prefixed_env( 

129 self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads 

130 ) -> bool: 

131 """Load any environment variables that start with ``FLASK_``, 

132 dropping the prefix from the env key for the config key. Values 

133 are passed through a loading function to attempt to convert them 

134 to more specific types than strings. 

135 

136 Keys are loaded in :func:`sorted` order. 

137 

138 The default loading function attempts to parse values as any 

139 valid JSON type, including dicts and lists. 

140 

141 Specific items in nested dicts can be set by separating the 

142 keys with double underscores (``__``). If an intermediate key 

143 doesn't exist, it will be initialized to an empty dict. 

144 

145 :param prefix: Load env vars that start with this prefix, 

146 separated with an underscore (``_``). 

147 :param loads: Pass each string value to this function and use 

148 the returned value as the config value. If any error is 

149 raised it is ignored and the value remains a string. The 

150 default is :func:`json.loads`. 

151 

152 .. versionadded:: 2.1 

153 """ 

154 prefix = f"{prefix}_" 

155 len_prefix = len(prefix) 

156 

157 for key in sorted(os.environ): 

158 if not key.startswith(prefix): 

159 continue 

160 

161 value = os.environ[key] 

162 

163 try: 

164 value = loads(value) 

165 except Exception: 

166 # Keep the value as a string if loading failed. 

167 pass 

168 

169 # Change to key.removeprefix(prefix) on Python >= 3.9. 

170 key = key[len_prefix:] 

171 

172 if "__" not in key: 

173 # A non-nested key, set directly. 

174 self[key] = value 

175 continue 

176 

177 # Traverse nested dictionaries with keys separated by "__". 

178 current = self 

179 *parts, tail = key.split("__") 

180 

181 for part in parts: 

182 # If an intermediate dict does not exist, create it. 

183 if part not in current: 

184 current[part] = {} 

185 

186 current = current[part] 

187 

188 current[tail] = value 

189 

190 return True 

191 

192 def from_pyfile( 

193 self, filename: str | os.PathLike[str], silent: bool = False 

194 ) -> bool: 

195 """Updates the values in the config from a Python file. This function 

196 behaves as if the file was imported as module with the 

197 :meth:`from_object` function. 

198 

199 :param filename: the filename of the config. This can either be an 

200 absolute filename or a filename relative to the 

201 root path. 

202 :param silent: set to ``True`` if you want silent failure for missing 

203 files. 

204 :return: ``True`` if the file was loaded successfully. 

205 

206 .. versionadded:: 0.7 

207 `silent` parameter. 

208 """ 

209 filename = os.path.join(self.root_path, filename) 

210 d = types.ModuleType("config") 

211 d.__file__ = filename 

212 try: 

213 with open(filename, mode="rb") as config_file: 

214 exec(compile(config_file.read(), filename, "exec"), d.__dict__) 

215 except OSError as e: 

216 if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): 

217 return False 

218 e.strerror = f"Unable to load configuration file ({e.strerror})" 

219 raise 

220 self.from_object(d) 

221 return True 

222 

223 def from_object(self, obj: object | str) -> None: 

224 """Updates the values from the given object. An object can be of one 

225 of the following two types: 

226 

227 - a string: in this case the object with that name will be imported 

228 - an actual object reference: that object is used directly 

229 

230 Objects are usually either modules or classes. :meth:`from_object` 

231 loads only the uppercase attributes of the module/class. A ``dict`` 

232 object will not work with :meth:`from_object` because the keys of a 

233 ``dict`` are not attributes of the ``dict`` class. 

234 

235 Example of module-based configuration:: 

236 

237 app.config.from_object('yourapplication.default_config') 

238 from yourapplication import default_config 

239 app.config.from_object(default_config) 

240 

241 Nothing is done to the object before loading. If the object is a 

242 class and has ``@property`` attributes, it needs to be 

243 instantiated before being passed to this method. 

244 

245 You should not use this function to load the actual configuration but 

246 rather configuration defaults. The actual config should be loaded 

247 with :meth:`from_pyfile` and ideally from a location not within the 

248 package because the package might be installed system wide. 

249 

250 See :ref:`config-dev-prod` for an example of class-based configuration 

251 using :meth:`from_object`. 

252 

253 :param obj: an import name or object 

254 """ 

255 if isinstance(obj, str): 

256 obj = import_string(obj) 

257 for key in dir(obj): 

258 if key.isupper(): 

259 self[key] = getattr(obj, key) 

260 

261 def from_file( 

262 self, 

263 filename: str | os.PathLike[str], 

264 load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]], 

265 silent: bool = False, 

266 text: bool = True, 

267 ) -> bool: 

268 """Update the values in the config from a file that is loaded 

269 using the ``load`` parameter. The loaded data is passed to the 

270 :meth:`from_mapping` method. 

271 

272 .. code-block:: python 

273 

274 import json 

275 app.config.from_file("config.json", load=json.load) 

276 

277 import tomllib 

278 app.config.from_file("config.toml", load=tomllib.load, text=False) 

279 

280 :param filename: The path to the data file. This can be an 

281 absolute path or relative to the config root path. 

282 :param load: A callable that takes a file handle and returns a 

283 mapping of loaded data from the file. 

284 :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` 

285 implements a ``read`` method. 

286 :param silent: Ignore the file if it doesn't exist. 

287 :param text: Open the file in text or binary mode. 

288 :return: ``True`` if the file was loaded successfully. 

289 

290 .. versionchanged:: 2.3 

291 The ``text`` parameter was added. 

292 

293 .. versionadded:: 2.0 

294 """ 

295 filename = os.path.join(self.root_path, filename) 

296 

297 try: 

298 with open(filename, "r" if text else "rb") as f: 

299 obj = load(f) 

300 except OSError as e: 

301 if silent and e.errno in (errno.ENOENT, errno.EISDIR): 

302 return False 

303 

304 e.strerror = f"Unable to load configuration file ({e.strerror})" 

305 raise 

306 

307 return self.from_mapping(obj) 

308 

309 def from_mapping( 

310 self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any 

311 ) -> bool: 

312 """Updates the config like :meth:`update` ignoring items with 

313 non-upper keys. 

314 

315 :return: Always returns ``True``. 

316 

317 .. versionadded:: 0.11 

318 """ 

319 mappings: dict[str, t.Any] = {} 

320 if mapping is not None: 

321 mappings.update(mapping) 

322 mappings.update(kwargs) 

323 for key, value in mappings.items(): 

324 if key.isupper(): 

325 self[key] = value 

326 return True 

327 

328 def get_namespace( 

329 self, namespace: str, lowercase: bool = True, trim_namespace: bool = True 

330 ) -> dict[str, t.Any]: 

331 """Returns a dictionary containing a subset of configuration options 

332 that match the specified namespace/prefix. Example usage:: 

333 

334 app.config['IMAGE_STORE_TYPE'] = 'fs' 

335 app.config['IMAGE_STORE_PATH'] = '/var/app/images' 

336 app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' 

337 image_store_config = app.config.get_namespace('IMAGE_STORE_') 

338 

339 The resulting dictionary `image_store_config` would look like:: 

340 

341 { 

342 'type': 'fs', 

343 'path': '/var/app/images', 

344 'base_url': 'http://img.website.com' 

345 } 

346 

347 This is often useful when configuration options map directly to 

348 keyword arguments in functions or class constructors. 

349 

350 :param namespace: a configuration namespace 

351 :param lowercase: a flag indicating if the keys of the resulting 

352 dictionary should be lowercase 

353 :param trim_namespace: a flag indicating if the keys of the resulting 

354 dictionary should not include the namespace 

355 

356 .. versionadded:: 0.11 

357 """ 

358 rv = {} 

359 for k, v in self.items(): 

360 if not k.startswith(namespace): 

361 continue 

362 if trim_namespace: 

363 key = k[len(namespace) :] 

364 else: 

365 key = k 

366 if lowercase: 

367 key = key.lower() 

368 rv[key] = v 

369 return rv 

370 

371 def __repr__(self) -> str: 

372 return f"<{type(self).__name__} {dict.__repr__(self)}>"