Source code for descarteslabs.config

# Copyright 2018-2024 Descartes Labs.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
from threading import Lock

import dynaconf

from descarteslabs.exceptions import ConfigError

GCP_ENVIRONMENT = "gcp-production"  #: Standard GCP environment
AWS_ENVIRONMENT = "aws-production"  #: Standard AWS environment


[docs]class Settings(dynaconf.Dynaconf): """ Configuration settings for the Descartes Labs client. Based on the ``Dynaconf`` package. This settings class supports configuration from named "environments" in a ``settings.toml`` file as well as environment variables with names that are prefixed with ``DESCARTESLABS_`` (or the prefix specified in the ``envvar_prefix``). For the full capabilities of ``Dynaconf`` please consult https://www.dynaconf.com/. Note that normally ``Settings`` functions entirely automatically within the client. However, it is possible to perform custom initialization programmatically. In order to do this, the beginning of the client program must execute code like this: .. code-block:: from descarteslabs.config import Settings Settings.select_env(...) Before importing or otherwise accessing anything else within the :py:mod:`descarteslabs` package. """ class _EnvDescriptor: # Retrieve the correct env string for `peek_settings()` def __get__(self, obj, objtype=None): if obj is None: if objtype._settings is None: return None else: return objtype._settings.env_for_dynaconf else: return obj.env_for_dynaconf env = _EnvDescriptor() """str : The current client configuration name or `None` of no environment was selected.""" # The global settings instance, can only be set once via select_env or get_settings _settings = None _lock = Lock()
[docs] @classmethod def select_env(cls, env=None, settings_file=None, envvar_prefix="DESCARTESLABS"): """ Configure the Descartes Labs client. Parameters ---------- env : str, optional Name of the environment to configure. Must appear in ``descarteslabs/config/settings.toml`` If not supplied will be determined from the `DESCARTESLABS_ENV` environment variable (or use the prefix specified in the `envvar_prefix`_ENV), if set. Otherwise defaults to `aws-production`. settings_file : str, optional If supplied, will be consulted for additional configuration overrides. These are applied over those in the ``descarteslabs/config/settings.toml`` file, but are themselves overwritten by any environment variable settings matching the `envvar_prefix`. envvar_prefix : str, optional Prefix for environment variable names to consult for configuration overrides. Environment variables with a leading prefix of ``"<envvar_prefix>_"`` will override the settings in the resulting configuration after the settings file(s) have been consulted. Returns ------- Settings Returns a ``Settings`` instance, a dict-like object containing the configured settings for the client. Raises ------ ConfigError If no client configuration could be established, or if an invalid configuration name was specified, or if you try to change the client configuration after the client is already configured. """ # Once the settings has been lazy evaluated, we cannot change it. # The reviled double-check pattern. Actually ok with CPython and the GIL, # but not necessarily any other Python implementation. settings = cls._settings if settings is None: with cls._lock: settings = cls._settings if settings is None: settings = cls._select_env( env=env, settings_file=settings_file, envvar_prefix=envvar_prefix, ) if settings is not None and env is not None and env != settings.ENV: raise ConfigError( f"Client configuration '{settings.ENV}' has already been selected" ) return settings
[docs] @classmethod def get_settings(cls): """ Configure and retrieve the current or default settings for the client. Returns ------- Settings Returns a ``Settings`` instance, a dict-like object containing the configured settings for the client. Raises ------ ConfigError If no client configuration could be established, or if an invalid configuration name was specified, or if you try to change the client configuration after the client is already configured. """ # The reviled double-check pattern. Actually ok with CPython and the GIL, # but not necessarily any other Python implementation. settings = cls._settings if settings is None: with cls._lock: settings = cls._settings if settings is None: settings = cls._select_env() return settings
[docs] @classmethod def peek_settings(cls, env=None, settings_file=None, envvar_prefix="DESCARTESLABS"): """Retrieve the settings without configuring the client. Unlike :py:meth:`~Settings.get_settings` and :py:meth:`~Settings.select_env` which both will configure the client, the :py:meth:`~Settings.peek_settings` will not configure the client and :py:attr:`Settings.env` will not be set. See :py:meth:`select_env` for an explanation of the parameters, return value, and exceptions that can be raised. """ selector = f"{envvar_prefix}_ENV" original_selector_value = os.environ.get(selector) settings = cls._get_settings( env=env, settings_file=settings_file, envvar_prefix=envvar_prefix, ) # Return the environ back to its original state if original_selector_value is None: os.environ.pop(selector) else: os.environ[selector] = original_selector_value return settings
@classmethod def _select_env(cls, env=None, settings_file=None, envvar_prefix="DESCARTESLABS"): # Assign to the global instance. cls._settings = cls._get_settings( env=env, settings_file=settings_file, envvar_prefix=envvar_prefix ) # And return the settings object. return cls._settings @classmethod def _get_settings(cls, env=None, settings_file=None, envvar_prefix="DESCARTESLABS"): # Get the settings. If the settings are retrieved successfully, the os.environ # will contain the selector for the given settings. selector = f"{envvar_prefix}_ENV" original_selector_value = os.environ.get(selector) def restore_env(): if original_selector_value is None: os.environ.pop(selector) else: os.environ[selector] = original_selector_value if env: os.environ[selector] = env elif not os.environ.get(selector): # Default it. os.environ[selector] = "aws-production" builtin_settings_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "settings.toml" ) try: # By default this will load from one or more settings files and # then from the environment. settings = cls( # First load our client settings from TOML file. settings_file=[builtin_settings_file], # Then load the given settings from TOML file, if any. includes=[] if not settings_file else [settings_file], # Only allow TOML format. core_loaders=["TOML"], # Allow multiple environments ([default] is always used). environments=True, # Name of environment variable that selects the environment. env_switcher=selector, # Prefix to overwrite loaded settings, e.g,. {envvar_prefix}_MY_SETTING. envvar_prefix=envvar_prefix, ) except Exception as e: restore_env() raise ConfigError(str(e)) from e try: # Make sure we selected an environment! assert settings.env_for_dynaconf # default_domain must have been set assert settings.default_domain except (AttributeError, KeyError, AssertionError): message = f"Client configuration '{os.environ[selector]}' doesn't exist!" restore_env() if not env: message += " Check your DESCARTESLABS_ENV environment variable." raise ConfigError(message) from None return settings
get_settings = Settings.get_settings """An alias for :py:meth:`Settings.get_settings`""" peek_settings = Settings.peek_settings """An alias for :py:meth:`Settings.peek_settings`""" select_env = Settings.select_env """An alias for :py:meth:`Settings.select_env`""" __all__ = [ "AWS_ENVIRONMENT", "GCP_ENVIRONMENT", "Settings", "get_settings", "peek_settings", "select_env", ]