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

107 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1import errno 

2import json 

3import os 

4import types 

5import typing as t 

6 

7from werkzeug.utils import import_string 

8 

9 

10class ConfigAttribute: 

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

12 

13 def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None: 

14 self.__name__ = name 

15 self.get_converter = get_converter 

16 

17 def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any: 

18 if obj is None: 

19 return self 

20 rv = obj.config[self.__name__] 

21 if self.get_converter is not None: 

22 rv = self.get_converter(rv) 

23 return rv 

24 

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

26 obj.config[self.__name__] = value 

27 

28 

29class Config(dict): 

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

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

32 config. 

33 

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

35 

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

37 

38 Or alternatively you can define the configuration options in the 

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

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

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

42 just before the call:: 

43 

44 DEBUG = True 

45 SECRET_KEY = 'development key' 

46 app.config.from_object(__name__) 

47 

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

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

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

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

52 the application. 

53 

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

55 environment variable pointing to a file:: 

56 

57 app.config.from_envvar('YOURAPPLICATION_SETTINGS') 

58 

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

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

61 use the export statement:: 

62 

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

64 

65 On windows use `set` instead. 

66 

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

68 config object is created by the application, this is 

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

70 :param defaults: an optional dictionary of default values 

71 """ 

72 

73 def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None: 

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

75 self.root_path = root_path 

76 

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

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

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

80 error messages for this line of code:: 

81 

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

83 

84 :param variable_name: name of the environment variable 

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

86 files. 

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

88 """ 

89 rv = os.environ.get(variable_name) 

90 if not rv: 

91 if silent: 

92 return False 

93 raise RuntimeError( 

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

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

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

97 " file" 

98 ) 

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

100 

101 def from_prefixed_env( 

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

103 ) -> bool: 

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

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

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

107 to more specific types than strings. 

108 

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

110 

111 The default loading function attempts to parse values as any 

112 valid JSON type, including dicts and lists. 

113 

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

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

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

117 

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

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

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

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

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

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

124 

125 .. versionadded:: 2.1 

126 """ 

127 prefix = f"{prefix}_" 

128 len_prefix = len(prefix) 

129 

130 for key in sorted(os.environ): 

131 if not key.startswith(prefix): 

132 continue 

133 

134 value = os.environ[key] 

135 

136 try: 

137 value = loads(value) 

138 except Exception: 

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

140 pass 

141 

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

143 key = key[len_prefix:] 

144 

145 if "__" not in key: 

146 # A non-nested key, set directly. 

147 self[key] = value 

148 continue 

149 

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

151 current = self 

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

153 

154 for part in parts: 

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

156 if part not in current: 

157 current[part] = {} 

158 

159 current = current[part] 

160 

161 current[tail] = value 

162 

163 return True 

164 

165 def from_pyfile(self, filename: str, silent: bool = False) -> bool: 

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

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

168 :meth:`from_object` function. 

169 

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

171 absolute filename or a filename relative to the 

172 root path. 

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

174 files. 

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

176 

177 .. versionadded:: 0.7 

178 `silent` parameter. 

179 """ 

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

181 d = types.ModuleType("config") 

182 d.__file__ = filename 

183 try: 

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

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

186 except OSError as e: 

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

188 return False 

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

190 raise 

191 self.from_object(d) 

192 return True 

193 

194 def from_object(self, obj: t.Union[object, str]) -> None: 

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

196 of the following two types: 

197 

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

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

200 

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

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

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

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

205 

206 Example of module-based configuration:: 

207 

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

209 from yourapplication import default_config 

210 app.config.from_object(default_config) 

211 

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

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

214 instantiated before being passed to this method. 

215 

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

217 rather configuration defaults. The actual config should be loaded 

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

219 package because the package might be installed system wide. 

220 

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

222 using :meth:`from_object`. 

223 

224 :param obj: an import name or object 

225 """ 

226 if isinstance(obj, str): 

227 obj = import_string(obj) 

228 for key in dir(obj): 

229 if key.isupper(): 

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

231 

232 def from_file( 

233 self, 

234 filename: str, 

235 load: t.Callable[[t.IO[t.Any]], t.Mapping], 

236 silent: bool = False, 

237 ) -> bool: 

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

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

240 :meth:`from_mapping` method. 

241 

242 .. code-block:: python 

243 

244 import json 

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

246 

247 import toml 

248 app.config.from_file("config.toml", load=toml.load) 

249 

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

251 absolute path or relative to the config root path. 

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

253 mapping of loaded data from the file. 

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

255 implements a ``read`` method. 

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

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

258 

259 .. versionadded:: 2.0 

260 """ 

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

262 

263 try: 

264 with open(filename) as f: 

265 obj = load(f) 

266 except OSError as e: 

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

268 return False 

269 

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

271 raise 

272 

273 return self.from_mapping(obj) 

274 

275 def from_mapping( 

276 self, mapping: t.Optional[t.Mapping[str, t.Any]] = None, **kwargs: t.Any 

277 ) -> bool: 

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

279 non-upper keys. 

280 

281 :return: Always returns ``True``. 

282 

283 .. versionadded:: 0.11 

284 """ 

285 mappings: t.Dict[str, t.Any] = {} 

286 if mapping is not None: 

287 mappings.update(mapping) 

288 mappings.update(kwargs) 

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

290 if key.isupper(): 

291 self[key] = value 

292 return True 

293 

294 def get_namespace( 

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

296 ) -> t.Dict[str, t.Any]: 

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

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

299 

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

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

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

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

304 

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

306 

307 { 

308 'type': 'fs', 

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

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

311 } 

312 

313 This is often useful when configuration options map directly to 

314 keyword arguments in functions or class constructors. 

315 

316 :param namespace: a configuration namespace 

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

318 dictionary should be lowercase 

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

320 dictionary should not include the namespace 

321 

322 .. versionadded:: 0.11 

323 """ 

324 rv = {} 

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

326 if not k.startswith(namespace): 

327 continue 

328 if trim_namespace: 

329 key = k[len(namespace) :] 

330 else: 

331 key = k 

332 if lowercase: 

333 key = key.lower() 

334 rv[key] = v 

335 return rv 

336 

337 def __repr__(self) -> str: 

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