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
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
1from __future__ import annotations
3import errno
4import json
5import os
6import types
7import typing as t
9from werkzeug.utils import import_string
11if t.TYPE_CHECKING:
12 import typing_extensions as te
14 from .sansio.app import App
17T = t.TypeVar("T")
20class ConfigAttribute(t.Generic[T]):
21 """Makes an attribute forward to the config"""
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
29 @t.overload
30 def __get__(self, obj: None, owner: None) -> te.Self:
31 ...
33 @t.overload
34 def __get__(self, obj: App, owner: type[App]) -> T:
35 ...
37 def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self:
38 if obj is None:
39 return self
41 rv = obj.config[self.__name__]
43 if self.get_converter is not None:
44 rv = self.get_converter(rv)
46 return rv # type: ignore[no-any-return]
48 def __set__(self, obj: App, value: t.Any) -> None:
49 obj.config[self.__name__] = value
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.
57 Either you can fill the config from a config file::
59 app.config.from_pyfile('yourconfig.cfg')
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::
67 DEBUG = True
68 SECRET_KEY = 'development key'
69 app.config.from_object(__name__)
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.
77 Probably the most interesting way to load configurations is from an
78 environment variable pointing to a file::
80 app.config.from_envvar('YOURAPPLICATION_SETTINGS')
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::
86 export YOURAPPLICATION_SETTINGS='/path/to/config/file'
88 On windows use `set` instead.
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 """
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
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::
109 app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
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)
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.
136 Keys are loaded in :func:`sorted` order.
138 The default loading function attempts to parse values as any
139 valid JSON type, including dicts and lists.
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.
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`.
152 .. versionadded:: 2.1
153 """
154 prefix = f"{prefix}_"
155 len_prefix = len(prefix)
157 for key in sorted(os.environ):
158 if not key.startswith(prefix):
159 continue
161 value = os.environ[key]
163 try:
164 value = loads(value)
165 except Exception:
166 # Keep the value as a string if loading failed.
167 pass
169 # Change to key.removeprefix(prefix) on Python >= 3.9.
170 key = key[len_prefix:]
172 if "__" not in key:
173 # A non-nested key, set directly.
174 self[key] = value
175 continue
177 # Traverse nested dictionaries with keys separated by "__".
178 current = self
179 *parts, tail = key.split("__")
181 for part in parts:
182 # If an intermediate dict does not exist, create it.
183 if part not in current:
184 current[part] = {}
186 current = current[part]
188 current[tail] = value
190 return True
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.
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.
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
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:
227 - a string: in this case the object with that name will be imported
228 - an actual object reference: that object is used directly
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.
235 Example of module-based configuration::
237 app.config.from_object('yourapplication.default_config')
238 from yourapplication import default_config
239 app.config.from_object(default_config)
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.
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.
250 See :ref:`config-dev-prod` for an example of class-based configuration
251 using :meth:`from_object`.
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)
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.
272 .. code-block:: python
274 import json
275 app.config.from_file("config.json", load=json.load)
277 import tomllib
278 app.config.from_file("config.toml", load=tomllib.load, text=False)
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.
290 .. versionchanged:: 2.3
291 The ``text`` parameter was added.
293 .. versionadded:: 2.0
294 """
295 filename = os.path.join(self.root_path, filename)
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
304 e.strerror = f"Unable to load configuration file ({e.strerror})"
305 raise
307 return self.from_mapping(obj)
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.
315 :return: Always returns ``True``.
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
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::
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_')
339 The resulting dictionary `image_store_config` would look like::
341 {
342 'type': 'fs',
343 'path': '/var/app/images',
344 'base_url': 'http://img.website.com'
345 }
347 This is often useful when configuration options map directly to
348 keyword arguments in functions or class constructors.
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
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
371 def __repr__(self) -> str:
372 return f"<{type(self).__name__} {dict.__repr__(self)}>"