Package: magic-settings
Magic-settingsInstallationpipinstallmagic-settingsUsing settings fromyamlfilepipinstallmagic-settings[yaml]InitializationProject settings class declarationfrommagic_settingsimportBaseSettings,PropertyfromfunctoolsimportpartialclassMySettings(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=',')])ClassPropertyis a descriptor with following parameters:types- Type ofvalueor a tuple of possibletypes. It is aValueErrorifvalueis not one of thetypes.validators- List ofcallableobjects each of which is successively applied tovalue.  RaisesValueErrorifvaluedoes not pass at least one of the validations (if any validation function returnsFalse).choices- List of any objects. Ifvalueis not inchoices- raisesValueError. When using this parameter, parameterstypesandvalidatorsare ignored.default- Sets the default value ofProperty.converts- List ofcallableobjects. It is a chain of transformations that are successively applied to thevalueand overwrite it each time. It applies tovalueonly ifvalueis a string. RaisesValueErrorifvalueat least one of the transformations failed to apply.Property classesBesidesPropertyfollowing classes may be used for standard types:BoolProperty: accepts boolean values, converts case-insensitivetrueorfalseto appropriate python boolean value. Also this property accepts numbers (0is False,1is 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 astringhostname and anintport. 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:8888will be converted into[('192.168.20.1', 80), ('www.yandex.ru', 1234), ('localhost', 8888)].Above example may be simplified using these properties:frommagic_settingsimport(BaseSettings,Property,BoolProperty,FloatProperty,IntProperty,StringListProperty,StringProperty)classMySettings(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 configurationSettings configuration occurs at the stage of creating a Settings object.frommy_projectimportmy_module,my_awesome_modulefrommy_configimportMySettingssettings=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)Parametersmodules: List of Python modules with variables to import. DefaultNone.prefix: The prefix with which the environment variables are taken. Default -None.settings.pyclassMySettings(BaseSettings):PSYDUCK=Property(types=str).envMYPROJECT_PSYDUCK=Owowowowsome_other_place.pysettings=MySettings(prefix='MYPROJECT')orsettings=MySettings(prefix='MYPROJECT_')dotenv_path: Path to env-file. Default -None. Using for exporting variables from env-file to environment. Ifdotenv_pathisNone-  walking up the directory tree looking for the specified file - called.envby 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.ExceptionsValueError: Ifmodulestype is notlistorNoneTypeand if type of element inmodulesis notModuleType.Settings loadingLoading settings can be initiated anywhere in the project.fromwhere_your_settingsimportsettingssettings.init()If called again, it goes through the configuration files and update properties.Settings priorityIn case of intersection of settings the following priority will be applied:my_module->my_awesome_module->.env->settings.yamlclassMySettings(BaseSettings):PSYDUCK=Property(types=str)my_module.pyPSYDUCK='one'my_awesome_module.pyPSYDUCK='two'.envMYPROJECTPREFIX_PSYDUCK=envsetting.yamlPSYDUCK:yamlExamples>>>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'Temporary Property overridemy_module.pyPIKACHU='Psyduck_is_not_fine'PSYDUCK='Owowowow'frommy_projectimportmy_modulefrommy_configimportMySettingsclassMySettings(BaseSettings):PSYDUCK=Property(types=str)PIKACHU=Property(types=str)settings=MySettings(modules=[my_module])settings.init()withsettings.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'Methodtemp_set_attributesis not thread-safe.Settings listYou can use methodsto_dict(),to_json()to get current settings:frommagic_settingsimportBaseSettings,PropertyclassMySettings(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}}]}ValidationIt is recommended to use followingBaseSettingsclass methods during redefinitionupdate_settings_from_sourcemethod:pre_validate- check that types are configured correctly; check that the values fromchoicesand the default pass the type check.post_validate- check if eachPropertyis assigned a value.Dynamic settingsImplementing a custom dynamic settings sourceExample with storing settings in dictsource:frommagic_settingsimportBaseDynamicSettings,Propertysource={'JIGGLYPUFF':'pink'}classBaseDynamicSettingsDict(BaseDynamicSettings):asyncdefupdate_settings_from_source(self):super().update_config(**source)asyncdefupdate_config(self,**kwargs):source.update(kwargs)returnsuper().update_config(**kwargs)Definition of project`s dynamic settings classclassMyDynamicSettings(BaseDynamicSettingsDict):JIGGLYPUFF=Property(types=str)Dynamic Settings Initializationloop=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.Dynamic settings updateUpdating settings only onceawaitdynamic_settings.update_settings_from_source()Starting the update loopawaitdynamic_settings.start_update()Stopping the update loopawaitdynamic_settings.stop_update()Writing settings into the sourceawaitdynamic_settings.update_config(JIGGLYPUFF='magenta')Exceptionsmagic_settings.DynamicSettingsSourceError- this exception should be selected if the settings source in the class inherited fromBaseDynamicSettingsis unavailable.