summaryrefslogtreecommitdiffhomepage
path: root/libs/dynaconf/contrib/flask_dynaconf.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/dynaconf/contrib/flask_dynaconf.py')
-rw-r--r--libs/dynaconf/contrib/flask_dynaconf.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/libs/dynaconf/contrib/flask_dynaconf.py b/libs/dynaconf/contrib/flask_dynaconf.py
new file mode 100644
index 000000000..a305194a2
--- /dev/null
+++ b/libs/dynaconf/contrib/flask_dynaconf.py
@@ -0,0 +1,230 @@
+from __future__ import annotations
+
+import warnings
+from collections import ChainMap
+from contextlib import suppress
+
+try:
+ from flask.config import Config
+
+ flask_installed = True
+except ImportError: # pragma: no cover
+ flask_installed = False
+ Config = object
+
+
+import dynaconf
+import pkg_resources
+
+
+class FlaskDynaconf:
+ """The arguments are.
+ app = The created app
+ dynaconf_args = Extra args to be passed to Dynaconf (validator for example)
+
+ All other values are stored as config vars specially::
+
+ ENVVAR_PREFIX_FOR_DYNACONF = env prefix for your envvars to be loaded
+ example:
+ if you set to `MYSITE` then
+ export MYSITE_SQL_PORT='@int 5445'
+
+ with that exported to env you access using:
+ app.config.SQL_PORT
+ app.config.get('SQL_PORT')
+ app.config.get('sql_port')
+ # get is case insensitive
+ app.config['SQL_PORT']
+
+ Dynaconf uses `@int, @bool, @float, @json` to cast
+ env vars
+
+ SETTINGS_FILE_FOR_DYNACONF = The name of the module or file to use as
+ default to load settings. If nothing is
+ passed it will be `settings.*` or value
+ found in `ENVVAR_FOR_DYNACONF`
+ Dynaconf supports
+ .py, .yml, .toml, ini, json
+
+ ATTENTION: Take a look at `settings.yml` and `.secrets.yml` to know the
+ required settings format.
+
+ Settings load order in Dynaconf:
+
+ - Load all defaults and Flask defaults
+ - Load all passed variables when applying FlaskDynaconf
+ - Update with data in settings files
+ - Update with data in environment vars `ENVVAR_FOR_DYNACONF_`
+
+
+ TOML files are very useful to have `envd` settings, lets say,
+ `production` and `development`.
+
+ You can also achieve the same using multiple `.py` files naming as
+ `settings.py`, `production_settings.py` and `development_settings.py`
+ (see examples/validator)
+
+ Example::
+
+ app = Flask(__name__)
+ FlaskDynaconf(
+ app,
+ ENV='MYSITE',
+ SETTINGS_FILE='settings.yml',
+ EXTRA_VALUE='You can add additional config vars here'
+ )
+
+ Take a look at examples/flask in Dynaconf repository
+
+ """
+
+ def __init__(
+ self,
+ app=None,
+ instance_relative_config=False,
+ dynaconf_instance=None,
+ extensions_list=False,
+ **kwargs,
+ ):
+ """kwargs holds initial dynaconf configuration"""
+ if not flask_installed: # pragma: no cover
+ raise RuntimeError(
+ "To use this extension Flask must be installed "
+ "install it with: pip install flask"
+ )
+ self.kwargs = {k.upper(): v for k, v in kwargs.items()}
+ kwargs.setdefault("ENVVAR_PREFIX", "FLASK")
+ env_prefix = f"{kwargs['ENVVAR_PREFIX']}_ENV" # FLASK_ENV
+ kwargs.setdefault("ENV_SWITCHER", env_prefix)
+ kwargs.setdefault("ENVIRONMENTS", True)
+ kwargs.setdefault("load_dotenv", True)
+ kwargs.setdefault(
+ "default_settings_paths", dynaconf.DEFAULT_SETTINGS_FILES
+ )
+
+ self.dynaconf_instance = dynaconf_instance
+ self.instance_relative_config = instance_relative_config
+ self.extensions_list = extensions_list
+ if app:
+ self.init_app(app, **kwargs)
+
+ def init_app(self, app, **kwargs):
+ """kwargs holds initial dynaconf configuration"""
+ self.kwargs.update(kwargs)
+ self.settings = self.dynaconf_instance or dynaconf.LazySettings(
+ **self.kwargs
+ )
+ dynaconf.settings = self.settings # rebind customized settings
+ app.config = self.make_config(app)
+ app.dynaconf = self.settings
+
+ if self.extensions_list:
+ if not isinstance(self.extensions_list, str):
+ self.extensions_list = "EXTENSIONS"
+ app.config.load_extensions(self.extensions_list)
+
+ def make_config(self, app):
+ root_path = app.root_path
+ if self.instance_relative_config: # pragma: no cover
+ root_path = app.instance_path
+ if self.dynaconf_instance:
+ self.settings.update(self.kwargs)
+ return DynaconfConfig(
+ root_path=root_path,
+ defaults=app.config,
+ _settings=self.settings,
+ _app=app,
+ )
+
+
+class DynaconfConfig(Config):
+ """
+ Replacement for flask.config_class that responds as a Dynaconf instance.
+ """
+
+ def __init__(self, _settings, _app, *args, **kwargs):
+ """perform the initial load"""
+ super().__init__(*args, **kwargs)
+
+ # Bring Dynaconf instance value to Flask Config
+ Config.update(self, _settings.store)
+
+ self._settings = _settings
+ self._app = _app
+
+ def __contains__(self, item):
+ return hasattr(self, item)
+
+ def __getitem__(self, key):
+ try:
+ return self._settings[key]
+ except KeyError:
+ return Config.__getitem__(self, key)
+
+ def __setitem__(self, key, value):
+ """
+ Allows app.config['key'] = 'foo'
+ """
+ return self._settings.__setitem__(key, value)
+
+ def _chain_map(self):
+ return ChainMap(self._settings, dict(dict.items(self)))
+
+ def keys(self):
+ return self._chain_map().keys()
+
+ def values(self):
+ return self._chain_map().values()
+
+ def items(self):
+ return self._chain_map().items()
+
+ def setdefault(self, key, value=None):
+ return self._chain_map().setdefault(key, value)
+
+ def __iter__(self):
+ return self._chain_map().__iter__()
+
+ def __getattr__(self, name):
+ """
+ First try to get value from dynaconf then from Flask Config
+ """
+ with suppress(AttributeError):
+ return getattr(self._settings, name)
+
+ with suppress(KeyError):
+ return self[name]
+
+ raise AttributeError(
+ f"'{self.__class__.__name__}' object has no attribute '{name}'"
+ )
+
+ def __call__(self, name, *args, **kwargs):
+ return self.get(name, *args, **kwargs)
+
+ def get(self, key, default=None):
+ """Gets config from dynaconf variables
+ if variables does not exists in dynaconf try getting from
+ `app.config` to support runtime settings."""
+ return self._settings.get(key, Config.get(self, key, default))
+
+ def load_extensions(self, key="EXTENSIONS", app=None):
+ """Loads flask extensions dynamically."""
+ app = app or self._app
+ extensions = app.config.get(key)
+ if not extensions:
+ warnings.warn(
+ f"Settings is missing {key} to load Flask Extensions",
+ RuntimeWarning,
+ )
+ return
+
+ for object_reference in app.config[key]:
+ # add a placeholder `name` to create a valid entry point
+ entry_point_spec = f"__name = {object_reference}"
+ # parse the entry point specification
+ entry_point = pkg_resources.EntryPoint.parse(entry_point_spec)
+ # dynamically resolve the entry point
+ initializer = entry_point.resolve()
+ # Invoke extension initializer
+ initializer(app)