pip install magic-settings
Using settings from yaml
file
pip install magic-settings[yaml]
from magic_settings import BaseSettings, Property
from functools import partial
class MySettings(BaseSettings):
VERSION = Property(types=str)
PROJECT_DIR = Property(types=str)
LOGGING_LEVEL = Property(default='INFO', choices=['NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
RETRIES_NUMBER = Property(types=int, converts=[int])
COEFFICIENT = Property(types=float, converts=[float])
DEBUG = Property(types=bool, converts=[int, bool], default=False)
DISTRIBUTED_SERVICE_HOST_NAMES = Property(types=list, converts=[partial(str.split, sep=',')])
Class Property
is a descriptor with following parameters:
- types - Type of
value
or a tuple of possibletypes
. It is aValueError
ifvalue
is not one of thetypes
. - validators - List of
callable
objects each of which is successively applied tovalue
. RaisesValueError
ifvalue
does not pass at least one of the validations (if any validation function returnsFalse
). - choices - List of any objects. If
value
is not inchoices
- raisesValueError
. When using this parameter, parameterstypes
andvalidators
are ignored. - default - Sets the default value of
Property
. - converts - List of
callable
objects. It is a chain of transformations that are successively applied to thevalue
and overwrite it each time. It applies tovalue
only ifvalue
is a string. RaisesValueError
ifvalue
at least one of the transformations failed to apply.
Besides Property
following classes may be used for standard types:
BoolProperty
: accepts boolean values, converts case-insensitivetrue
orfalse
to appropriate python boolean value. Also this property accepts numbers (0
is False,1
is True).FloatProperty
: accepts float number values.IntProperty
: accepts integer number values.StringProperty
: accepts string values.StringListProperty
: accepts list of strings. You can specify delimiter in constructor of this class (,
is default value).HostListProperty
: accepts list of hosts. Each host is a tuple containing astring
hostname and anint
port. Pairs should be divided by comma, hostname and port should be divided by colon. For example,192.168.20.1:80,www.yandex.ru:1234,localhost:8888
will be converted into[('192.168.20.1', 80), ('www.yandex.ru', 1234), ('localhost', 8888)]
.
Above example may be simplified using these properties:
from magic_settings import (BaseSettings, Property,
BoolProperty, FloatProperty, IntProperty, StringListProperty, StringProperty)
class MySettings(BaseSettings):
VERSION = StringProperty()
PROJECT_DIR = StringProperty()
LOGGING_LEVEL = Property(default='INFO', choices=['NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
RETRIES_NUMBER = IntProperty()
COEFFICIENT = FloatProperty()
DEBUG = BoolProperty(default=False)
DISTRIBUTED_SERVICE_HOST_NAMES = StringListProperty()
Settings configuration occurs at the stage of creating a Settings object.
from my_project import my_module, my_awesome_module
from my_config import MySettings
settings = MySettings(
modules=[my_module, my_awesome_module],
prefix='MY_PROJECT_ENV_SETTINGS',
dotenv_path='/path/to/my/env',
override_env=True,
yaml_settings_path='/path/to/my/yaml/settings.yaml',
use_env=True
)
-
modules: List of Python modules with variables to import. Default
None
. -
prefix: The prefix with which the environment variables are taken. Default -
None
.settings.py
class MySettings(BaseSettings): PSYDUCK = Property(types=str)
.env
MYPROJECT_PSYDUCK=Owowowow
some_other_place.py
settings = MySettings(prefix='MYPROJECT')
or
settings = MySettings(prefix='MYPROJECT_')
-
dotenv_path: Path to env-file. Default -
None
. Using for exporting variables from env-file to environment. Ifdotenv_path
isNone
- walking up the directory tree looking for the specified file - called.env
by default. -
override_env:
True
- override existing system environment variables with variables from.env
- file,False
- do not override. Default -False
. -
yaml_settings_path: Path to yaml config file. Default -
None
. -
use_env:
True
- use environment variables. Default -True
.
ValueError: If modules type is not list
or NoneType
and if type of element in modules is not ModuleType
.
Loading settings can be initiated anywhere in the project.
from where_your_settings import settings
settings.init()
If called again, it goes through the configuration files and update properties.
In case of intersection of settings the following priority will be applied: my_module -> my_awesome_module -> .env -> settings.yaml
class MySettings(BaseSettings):
PSYDUCK = Property(types=str)
my_module.py
PSYDUCK = 'one'
my_awesome_module.py
PSYDUCK = 'two'
.env
MYPROJECTPREFIX_PSYDUCK=env
setting.yaml
PSYDUCK: yaml
>>> settings = MySettings(modules=[my_module])
>>> settings.PSYDUCK
'one'
>>> settings = MySettings(modules=[my_module, my_awesome_module])
>>> settings.PSYDUCK
'two'
>>> settings = MySettings(modules=[my_awesome_module, my_module])
>>> settings.PSYDUCK
'one'
>>> settings = MySettings(modules=[my_module, my_awesome_module], dotenv_path='/path/to/dotenv')
>>> settings.PSYDUCK
'env'
>>> settings = MySettings(modules=[my_module, my_awesome_module], dotenv_path='/path/to/dotenv', use_env=False)
>>> settings.PSYDUCK
'two'
>>> settings = MySettings(modules=[my_module, my_awesome_module], dotenv_path='/path/to/dotenv', yaml_settings_path='/path/to/yaml/settings.yaml')
>>> settings.PSYDUCK
'yaml'
my_module.py
PIKACHU = 'Psyduck_is_not_fine'
PSYDUCK = 'Owowowow'
from my_project import my_module
from my_config import MySettings
class MySettings(BaseSettings):
PSYDUCK = Property(types=str)
PIKACHU = Property(types=str)
settings = MySettings(modules=[my_module])
settings.init()
with settings.temp_set_attributes(PSYDUCK='I_am_ok', PIKACHU='Psyduck_is_ok'):
print(settings.PSYDUCK) # 'I_am_ok'
print(settings.PIKACHU) # 'Psyduck_is_ok'
print(settings.PSYDUCK) # 'Owowowow'
print(settings.PIKACHU) # 'Psyduck_is_not_fine'
Method temp_set_attributes
is not thread-safe.
You can use methods to_dict()
, to_json()
to get current settings:
from magic_settings import BaseSettings, Property
class MySettings(BaseSettings):
PSYDUCK = Property(types=str)
PIKACHU = Property(types=str)
settings = MySettings(dotenv_path='12345.env')
settings.PIKACHU = '3'
settings.PSYDUCK = '12345'
pprint(settings.to_dict())
{
'properties': {
'PIKACHU': '3',
'PSYDUCK': '12345'
},
'sources': [{
'source_type': 'dotenv',
'address': {
'dotenv_path': '12345.env',
'override': False
}
}]
}
It is recommended to use following BaseSettings
class methods during redefinition update_settings_from_source
method:
pre_validate
- check that types are configured correctly; check that the values fromchoices
and the default pass the type check.post_validate
- check if eachProperty
is assigned a value.
Example with storing settings in dict source
:
from magic_settings import BaseDynamicSettings, Property
source = {
'JIGGLYPUFF': 'pink'
}
class BaseDynamicSettingsDict(BaseDynamicSettings):
async def update_settings_from_source(self):
super().update_config(**source)
async def update_config(self, **kwargs):
source.update(kwargs)
return super().update_config(**kwargs)
class MyDynamicSettings(BaseDynamicSettingsDict):
JIGGLYPUFF = Property(types=str)
loop = asyncio.get_event_loop()
dynamic_settings = MyDynamicSettings(loop=loop, update_period=5, task_retries_number=5)
- update_period: time between updating settings from source, in seconds.
- task_retries_number: the number of attempts to update the settings when an exception occurred before stopping the task.
await dynamic_settings.update_settings_from_source()
await dynamic_settings.start_update()
await dynamic_settings.stop_update()
await dynamic_settings.update_config(JIGGLYPUFF='magenta')
- magic_settings.DynamicSettingsSourceError - this exception should be selected if the settings source in the class inherited from
BaseDynamicSettings
is unavailable.