Package: environs
environs: simplified environment variable parsingenvironsis a Python library for parsing environment variables.
It allows you to store configuration separate from your code, as perThe Twelve-Factor Appmethodology.ContentsFeaturesInstallBasic usageSupported typesReading .env filesReading a specific fileHandling prefixesVariable expansionValidationDeferred validationSerializationDefining custom parser behaviorUsage with FlaskUsage with DjangoWhy...?Why envvars?Why not os.environ?Why another library?LicenseFeaturesType-castingRead.envfiles intoos.environ(useful for local development)ValidationDefine custom parser behaviorFramework-agnostic, but integrates well withFlaskandDjangoInstallpip install environsBasic usageWith some environment variables set...exportGITHUB_USER=sloriaexportMAX_CONNECTIONS=100exportSHIP_DATE='1984-06-25'exportTTL=42exportENABLE_LOGIN=trueexportGITHUB_REPOS=webargs,konch,pedexportGITHUB_REPO_PRIORITY="webargs=2,konch=3"exportCOORDINATES=23.3,50.0exportLOG_LEVEL=DEBUGParse them with environs...fromenvironsimportEnvenv=Env()env.read_env()# read .env file, if it exists# required variablesgh_user=env("GITHUB_USER")# => 'sloria'secret=env("SECRET")# => raises error if not set# castingmax_connections=env.int("MAX_CONNECTIONS")# => 100ship_date=env.date("SHIP_DATE")# => datetime.date(1984, 6, 25)ttl=env.timedelta("TTL")# => datetime.timedelta(0, 42)log_level=env.log_level("LOG_LEVEL")# => logging.DEBUG# providing a default valueenable_login=env.bool("ENABLE_LOGIN",False)# => Trueenable_feature_x=env.bool("ENABLE_FEATURE_X",False)# => False# parsing listsgh_repos=env.list("GITHUB_REPOS")# => ['webargs', 'konch', 'ped']coords=env.list("COORDINATES",subcast=float)# => [23.3, 50.0]# parsing dictsgh_repos_priorities=env.dict("GITHUB_REPO_PRIORITY",subcast_values=int)# => {'webargs': 2, 'konch': 3}Supported typesThe following are all type-casting methods ofEnv:env.strenv.boolenv.intenv.floatenv.decimalenv.list(accepts optionalsubcastanddelimiterkeyword arguments)env.dict(accepts optionalsubcast_keys,subcast_valuesanddelimiterkeyword arguments)env.jsonenv.datetimeenv.dateenv.timeenv.timedelta(assumes value is an integer in seconds)env.urlenv.uuidenv.log_levelenv.path(casts to apathlib.Path)env.enum(casts to any given enum type specified intypekeyword argument, accepts optionalignore_casekeyword argument)Reading.envfiles# .envDEBUG=truePORT=4567CallEnv.read_envbefore parsing variables.fromenvironsimportEnvenv=Env()# Read .env into os.environenv.read_env()env.bool("DEBUG")# => Trueenv.int("PORT")# => 4567Reading a specific fileBy default,Env.read_envwill look for a.envfile in current
directory and (if no .env exists in the CWD) recurse
upwards until a.envfile is found.You can also read a specific file:fromenvironsimportEnvwithopen(".env.test","w")asfobj:fobj.write("A=foo\n")fobj.write("B=123\n")env=Env()env.read_env(".env.test",recurse=False)assertenv("A")=="foo"assertenv.int("B")==123Handling prefixes# export MYAPP_HOST=lolcathost# export MYAPP_PORT=3000withenv.prefixed("MYAPP_"):host=env("HOST","localhost")# => 'lolcathost'port=env.int("PORT",5000)# => 3000# nested prefixes are also supported:# export MYAPP_DB_HOST=lolcathost# export MYAPP_DB_PORT=10101withenv.prefixed("MYAPP_"):withenv.prefixed("DB_"):db_host=env("HOST","lolcathost")db_port=env.int("PORT",10101)Variable expansion# export CONNECTION_URL=https://${USER:-sloria}:${PASSWORD}@${HOST:-localhost}/# export PASSWORD=secret# export YEAR=${CURRENT_YEAR:-2020}fromenvironsimportEnvenv=Env(expand_vars=True)connection_url=env("CONNECTION_URL")# =>'https://sloria:secret@localhost'year=env.int("YEAR")# =>2020Validation# export TTL=-2# export NODE_ENV='invalid'# export EMAIL='^_^'fromenvironsimportEnvfrommarshmallow.validateimportOneOf,Length,Emailenv=Env()# simple validatorenv.int("TTL",validate=lambdan:n>0)# => Environment variable "TTL" invalid: ['Invalid value.']# using marshmallow validatorsenv.str("NODE_ENV",validate=OneOf(["production","development"],error="NODE_ENV must be one of:{choices}"),)# => Environment variable "NODE_ENV" invalid: ['NODE_ENV must be one of: production, development']# multiple validatorsenv.str("EMAIL",validate=[Length(min=4),Email()])# => Environment variable "EMAIL" invalid: ['Shorter than minimum length 4.', 'Not a valid email address.']Deferred validationBy default, a validation error is raised immediately upon calling a parser method for an invalid environment variable.
To defer validation and raise an exception with the combined error messages for all invalid variables, passeager=FalsetoEnv.
Callenv.seal()after all variables have been parsed.# export TTL=-2# export NODE_ENV='invalid'# export EMAIL='^_^'fromenvironsimportEnvfrommarshmallow.validateimportOneOf,Email,Length,Rangeenv=Env(eager=False)TTL=env.int("TTL",validate=Range(min=0,max=100))NODE_ENV=env.str("NODE_ENV",validate=OneOf(["production","development"],error="NODE_ENV must be one of:{choices}"),)EMAIL=env.str("EMAIL",validate=[Length(min=4),Email()])env.seal()# environs.EnvValidationError: Environment variables invalid: {'TTL': ['Must be greater than or equal to 0 and less than or equal to 100.'], 'NODE_ENV': ['NODE_ENV must be one of: production, development'], 'EMAIL': ['Shorter than minimum length 4.', 'Not a valid email address.']}env.seal()validates all parsed variables and prevents further parsing (calling a parser method will raise an error).Serialization# serialize to a dictionary of simple types (numbers and strings)env.dump()# {'COORDINATES': [23.3, 50.0],# 'ENABLE_FEATURE_X': False,# 'ENABLE_LOGIN': True,# 'GITHUB_REPOS': ['webargs', 'konch', 'ped'],# 'GITHUB_USER': 'sloria',# 'MAX_CONNECTIONS': 100,# 'MYAPP_HOST': 'lolcathost',# 'MYAPP_PORT': 3000,# 'SHIP_DATE': '1984-06-25',# 'TTL': 42}Defining custom parser behavior# export DOMAIN='http://myapp.com'# export COLOR=invalidfromfurlimportfurl# Register a new parser method for paths@env.parser_for("furl")deffurl_parser(value):returnfurl(value)domain=env.furl("DOMAIN")# => furl('https://myapp.com')# Custom parsers can take extra keyword arguments@env.parser_for("choice")defchoice_parser(value,choices):ifvaluenotinchoices:raiseenvirons.EnvError("Invalid!")returnvaluecolor=env.choice("COLOR",choices=["black"])# => raises EnvErrorUsage with Flask# myapp/settings.pyfromenvironsimportEnvenv=Env()env.read_env()# Override in .env for local developmentDEBUG=env.bool("FLASK_DEBUG",default=False)# SECRET_KEY is requiredSECRET_KEY=env.str("SECRET_KEY")Load the configuration after you initialize your app.# myapp/app.pyfromflaskimportFlaskapp=Flask(__name__)app.config.from_object("myapp.settings")For local development, use a.envfile to override the default
configuration.# .envDEBUG=trueSECRET_KEY="not so secret"Note: Because environs depends onpython-dotenv,
theflaskCLI will automatically read .env and .flaskenv files.Usage with Djangoenvirons includes a number of helpers for parsing connection URLs. To
install environs with django support:pip install environs[django]Useenv.dj_db_url,env.dj_cache_urlandenv.dj_email_urlto parse theDATABASE_URL,CACHE_URLandEMAIL_URLenvironment variables, respectively.For more details on URL patterns, see the following projects that environs is using for converting URLs.dj-database-urldjango-cache-urldj-email-urlBasic example:# myproject/settings.pyfromenvironsimportEnvenv=Env()env.read_env()# Override in .env for local developmentDEBUG=env.bool("DEBUG",default=False)# SECRET_KEY is requiredSECRET_KEY=env.str("SECRET_KEY")# Parse database URLs, e.g.  "postgres://localhost:5432/mydb"DATABASES={"default":env.dj_db_url("DATABASE_URL")}# Parse email URLs, e.g. "smtp://"email=env.dj_email_url("EMAIL_URL",default="smtp://")EMAIL_HOST=email["EMAIL_HOST"]EMAIL_PORT=email["EMAIL_PORT"]EMAIL_HOST_PASSWORD=email["EMAIL_HOST_PASSWORD"]EMAIL_HOST_USER=email["EMAIL_HOST_USER"]EMAIL_USE_TLS=email["EMAIL_USE_TLS"]# Parse cache URLS, e.g "redis://localhost:6379/0"CACHES={"default":env.dj_cache_url("CACHE_URL")}For local development, use a.envfile to override the default
configuration.# .envDEBUG=trueSECRET_KEY="not so secret"For a more complete example, seedjango_example.pyin theexamples/directory.Why...?Why envvars?SeeThe 12-factor Appsection onconfiguration.Why notos.environ?Whileos.environis enough for simple use cases, a typical application
will need a way to manipulate and validate raw environment variables.
environs abstracts common tasks for handling environment variables.environs will help youcast envvars to the correct typespecify required envvarsdefine default valuesvalidate envvarsparse list and dict valuesparse dates, datetimes, and timedeltasparse expanded variablesserialize your configuration to JSON, YAML, etc.Why another library?There are many great Python libraries for parsing environment variables.
In fact, most of the credit for environs' public API goes to the
authors ofenvparseanddjango-environ.environs aims to meet three additional goals:Make it easy to extend parsing behavior and develop plugins.Leverage the deserialization and validation functionality provided
by a separate library (marshmallow).Clean up redundant API.Seethis GitHub
issuewhich details specific differences with envparse.LicenseMIT licensed. See theLICENSEfile
for more details.