Package: dryenv
dryenvSimple configuration with environment variables and pydantic, without repeating yourself!pip install dryenvBasic usageBased on pydantic.Configuring DryEnvAdditional featuresUsage with Django and PyCharmBasic usageFor example, instead of writing:# settings.pyimportosDATABASE_HOST=os.getenv("DATABASE_HOST","localhost")DATABASE_USERNAME=os.getenv("DATABASE_USERNAME","admin")DATABASE_PASSWORD=os.getenv("DATABASE_PASSWORD","secretpassword")DATABASE_TIMEOUT=int(os.getenv("DATABASE_TIMEOUT",10))DATABASE_PERSIST_CONNECTION=os.getenv("DATABASE_PERSIST_CONNECTION","true").lower()=="true"# database.pyimportsettingsconnection=connect(host=settings.DATABASE_HOST,username=settings.DATABASE_USERNAME,password=settings.DATABASE_PASSWORD,timeout=settings.DATABASE_TIMEOUT,persist_connection=settings.DATABASE_PERSIST_CONNECTION,)write:# settings.pyfromdryenvimportDryEnvclassDATABASE(DryEnv):HOST="localhost"USERNAME="admin"PASSWORD="secretpassword"TIMEOUT=10PERSIST_CONNECTION=True# database.pyfromsettingsimportDATABASEconnection=connect(host=DATABASE.HOST,username=DATABASE.USERNAME,password=DATABASE.PASSWORD,timeout=DATABASE.TIMEOUT,persist_connection=DATABASE.PERSIST_CONNECTION,)or even:# settings.pyfromdryenvimportDryEnvclassDATABASE(DryEnv):# Looking up environment variables is case-insensitivehost="localhost"username="admin"password="secretpassword"timeout=10persist_connection=True# database.pyfromsettingsimportDATABASEconnection=connect(**DATABASE.dict())Based on pydantic.DryEnvis a thin wrapper aroundpydantic.BaseSettings, which does most of the heavy lifting.DryEnvmakes things a little neater and more convenient by automatically:Settingenv_prefixbased on the class name, unless the class name isRoot(case insensitive) in which case the prefix is empty.Instantiating the class to trigger the environment lookups.For example, this:fromdryenvimportDryEnvclassDATABASE(DryEnv):HOST="localhost"USERNAME="admin"PASSWORD="secretpassword"TIMEOUT=10PERSIST_CONNECTION=Trueis roughly equivalent to:frompydanticimportBaseSettingsclassDATABASE(BaseSettings):classConfig:env_prefix="DATABASE_"HOST="localhost"USERNAME="admin"PASSWORD="secretpassword"TIMEOUT=10PERSIST_CONNECTION=TrueDATABASE=DATABASE()Here are the most important points about what pydantic provides:You can omit the default value and just declare a variable with a type annotation, e.g.HOST: str. This makes the setting required.Variables will be parsed based on their type, which is determined by the annotation or the default value.For most simple field types (such as int, float, str, etc.), the environment variable value is parsed the same way it would be if passed directly to the initialiser (as a string). Booleans are parsed more intelligently,see here. Complex types like list, set, dict, and sub-models are populated from the environment by treating the environment variable's value as a JSON-encoded string.For more informationread the pydantic documentation.This package could quite easily be part of pydantic itself. If you'd like that,vote on the issue here.Configuring DryEnvYou can override the automaticenv_prefixsetting by either:Naming your classRoot(case insensitive) in which case the prefix is empty, orSettingenv_prefixas normal under theConfigclass.You can turn off the automatic instantiation by settingauto_init = Falsein theConfig.You can instantiateDryEnvyourself with your own constructor arguments by simply calling it as if it were the class. You can also access the class itself as normal withtype().Additional featuresThe instance methodDryEnv.prefixed_dict()is similar to pydantic'sdict(), but theenv_prefixis included in the keys, so they match the original environment variable names.For example:classDATABASE(DryEnv):HOST="localhost"USERNAME="admin"assertDATABASE.dict()=={"HOST":"localhost","USERNAME":"admin"}assertDATABASE.prefixed_dict()=={"DATABASE_HOST":"localhost","DATABASE_USERNAME":"admin"}The functionpopulate_globals()will search for instances ofDryEnvin the global variables in the calling context and then update the global variables with theprefixed_dict()of thoseDryEnvisntances. For example, if you calledpopulate_globals()after the example above,DATABASE_HOSTandDATABASE_USERNAMEwould become global variables. This is useful in e.g. Django where settings need to be declared at the global level. You can pass your own dict for the function to use instead of the current global variables.Usage with Django and PyCharmIf you use PyCharm with the Django integration, it's able to intelligently inspect and navigate to values indjango.conf.settings...most of the time. For some reason a class declared insettings.pydoesn't work, so you can't navigate to the definition of aDryEnvor autocomplete its values. To work around this, I suggest you:Declare appropriate settings in a different file e.g.simple_settings.py.Import values from there in your apps instead ofdjango.conf.settingsso that PyCharm understands them.In yoursettings.py, writefrom simple_settings import *and callpopulate_globals()in one of the settings files. This will allow Django and libraries to find settings likeDEBUGandSECRET_KEYat the global level while letting you define them withdryenvand then forgetting about them.Alternatively, you can add the lineDATABASE = DATABASEorDATABASE = DATABASE()and then PyCharm will recognise this as a normal variable instead of a class.