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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

115 statements  

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 @t.overload 

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

34 

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

36 if obj is None: 

37 return self 

38 

39 rv = obj.config[self.__name__] 

40 

41 if self.get_converter is not None: 

42 rv = self.get_converter(rv) 

43 

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

45 

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

47 obj.config[self.__name__] = value 

48 

49 

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

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

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

53 config. 

54 

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

56 

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

58 

59 Or alternatively you can define the configuration options in the 

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

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

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

63 just before the call:: 

64 

65 DEBUG = True 

66 SECRET_KEY = 'development key' 

67 app.config.from_object(__name__) 

68 

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

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

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

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

73 the application. 

74 

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

76 environment variable pointing to a file:: 

77 

78 app.config.from_envvar('YOURAPPLICATION_SETTINGS') 

79 

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

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

82 use the export statement:: 

83 

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

85 

86 On windows use `set` instead. 

87 

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

89 config object is created by the application, this is 

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

91 :param defaults: an optional dictionary of default values 

92 """ 

93 

94 def __init__( 

95 self, 

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

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

98 ) -> None: 

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

100 self.root_path = root_path 

101 

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

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

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

105 error messages for this line of code:: 

106 

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

108 

109 :param variable_name: name of the environment variable 

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

111 files. 

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

113 """ 

114 rv = os.environ.get(variable_name) 

115 if not rv: 

116 if silent: 

117 return False 

118 raise RuntimeError( 

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

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

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

122 " file" 

123 ) 

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

125 

126 def from_prefixed_env( 

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

128 ) -> bool: 

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

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

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

132 to more specific types than strings. 

133 

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

135 

136 The default loading function attempts to parse values as any 

137 valid JSON type, including dicts and lists. 

138 

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

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

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

142 

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

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

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

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

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

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

149 

150 .. versionadded:: 2.1 

151 """ 

152 prefix = f"{prefix}_" 

153 

154 for key in sorted(os.environ): 

155 if not key.startswith(prefix): 

156 continue 

157 

158 value = os.environ[key] 

159 key = key.removeprefix(prefix) 

160 

161 try: 

162 value = loads(value) 

163 except Exception: 

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

165 pass 

166 

167 if "__" not in key: 

168 # A non-nested key, set directly. 

169 self[key] = value 

170 continue 

171 

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

173 current = self 

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

175 

176 for part in parts: 

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

178 if part not in current: 

179 current[part] = {} 

180 

181 current = current[part] 

182 

183 current[tail] = value 

184 

185 return True 

186 

187 def from_pyfile( 

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

189 ) -> bool: 

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

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

192 :meth:`from_object` function. 

193 

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

195 absolute filename or a filename relative to the 

196 root path. 

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

198 files. 

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

200 

201 .. versionadded:: 0.7 

202 `silent` parameter. 

203 """ 

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

205 d = types.ModuleType("config") 

206 d.__file__ = filename 

207 try: 

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

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

210 except OSError as e: 

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

212 return False 

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

214 raise 

215 self.from_object(d) 

216 return True 

217 

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

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

220 of the following two types: 

221 

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

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

224 

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

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

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

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

229 

230 Example of module-based configuration:: 

231 

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

233 from yourapplication import default_config 

234 app.config.from_object(default_config) 

235 

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

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

238 instantiated before being passed to this method. 

239 

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

241 rather configuration defaults. The actual config should be loaded 

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

243 package because the package might be installed system wide. 

244 

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

246 using :meth:`from_object`. 

247 

248 :param obj: an import name or object 

249 """ 

250 if isinstance(obj, str): 

251 obj = import_string(obj) 

252 for key in dir(obj): 

253 if key.isupper(): 

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

255 

256 def from_file( 

257 self, 

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

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

260 silent: bool = False, 

261 text: bool = True, 

262 ) -> bool: 

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

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

265 :meth:`from_mapping` method. 

266 

267 .. code-block:: python 

268 

269 import json 

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

271 

272 import tomllib 

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

274 

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

276 absolute path or relative to the config root path. 

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

278 mapping of loaded data from the file. 

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

280 implements a ``read`` method. 

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

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

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

284 

285 .. versionchanged:: 2.3 

286 The ``text`` parameter was added. 

287 

288 .. versionadded:: 2.0 

289 """ 

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

291 

292 try: 

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

294 obj = load(f) 

295 except OSError as e: 

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

297 return False 

298 

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

300 raise 

301 

302 return self.from_mapping(obj) 

303 

304 def from_mapping( 

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

306 ) -> bool: 

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

308 non-upper keys. 

309 

310 :return: Always returns ``True``. 

311 

312 .. versionadded:: 0.11 

313 """ 

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

315 if mapping is not None: 

316 mappings.update(mapping) 

317 mappings.update(kwargs) 

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

319 if key.isupper(): 

320 self[key] = value 

321 return True 

322 

323 def get_namespace( 

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

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

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

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

328 

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

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

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

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

333 

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

335 

336 { 

337 'type': 'fs', 

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

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

340 } 

341 

342 This is often useful when configuration options map directly to 

343 keyword arguments in functions or class constructors. 

344 

345 :param namespace: a configuration namespace 

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

347 dictionary should be lowercase 

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

349 dictionary should not include the namespace 

350 

351 .. versionadded:: 0.11 

352 """ 

353 rv = {} 

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

355 if not k.startswith(namespace): 

356 continue 

357 if trim_namespace: 

358 key = k[len(namespace) :] 

359 else: 

360 key = k 

361 if lowercase: 

362 key = key.lower() 

363 rv[key] = v 

364 return rv 

365 

366 def __repr__(self) -> str: 

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