simplified environment variable parsing
environs is a Python library for parsing environment variables. It allows you to store configuration separate from your code, as per The Twelve-Factor App methodology.
.env files
.env files without mutating os.environpip install environs
With some environment variables set...
export GITHUB_USER=sloria
export MAX_CONNECTIONS=100
export SHIP_DATE='1984-06-25'
export TTL=42
export ENABLE_LOGIN=true
export GITHUB_REPOS=webargs,konch,ped
export GITHUB_REPO_PRIORITY="webargs=2,konch=3"
export LOCATIONS="x:234 y:123"
export COORDINATES=23.3,50.0
export LOG_LEVEL=DEBUG
Parse them with environs...
…
The following are all type-casting methods of Env:
env.strenv.boolenv.intenv.floatenv.decimalenv.list (accepts optional subcast and delimiter keyword arguments)env.dict (accepts optional subcast_keys, subcast_values, delimiter, and key_value_delimiter keyword arguments)env.jsonenv.datetimeenv.dateenv.timeenv.timedelta (assumes value is an integer in seconds, or an ordered duration string like 7h7s or 7w 7d 7h 7m 7s 7ms 7us)env.urlurllib.parse.ParseResult and therefore expects a ParseResult for its default.from urllib.parse import urlparse
from environs import env
MY_API_URL = env.url(
"MY_API_URL",
default=urlparse("http://api.example.com"),
)
If you want the return value to be a string, use env.str with validate.URL instead.
from environs import env, validate
MY_API_URL = env.str(
"MY_API_URL",
default="http://api.example.com",
validate=validate.URL(),
)
env.uuidenv.log_levelenv.path (casts to a pathlib.Path)env.enum (casts to any given enum type specified in enum keyword argument)by_value=True to parse and validate by the Enum's values..env files# .env
DEBUG=true
PORT=4567
Call Env.read_env before parsing variables.
from environs import env
env.read_env()
env.bool("DEBUG") # => True
env.int("PORT") # => 4567
[!NOTE]
read_envloads values into theEnvinstance without modifyingos.environ.
By default, Env.read_env will look for a .env file in current
directory and (if no .env exists in the CWD) recurse
upwards until a .env file is found.
You can also read a specific file:
from environs import env
with open(".env.test", "w") as fobj:
fobj.write("A=foo\n")
fobj.write("B=123\n")
env.read_env(".env.test", recurse=False)
assert env("A") == "foo"
assert env.int("B") == 123
Pass prefix to the constructor if all your environment variables have the same prefix.
from environs import Env
# export MYAPP_HOST=lolcathost
# export MYAPP_PORT=3000
env = Env(prefix="MYAPP_")
host = env("HOST", "localhost") # => 'lolcathost'
port = env.int("PORT", 5000) # => 3000
Alternatively, you can use the prefixed context manager.
from environs import env
# export MYAPP_HOST=lolcathost
# export MYAPP_PORT=3000
with env.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=10101
with env.prefixed("MYAPP_"):
with env.prefixed("DB_"):
db_host = env("HOST", "lolcathost")
db_port = env.int("PORT", 10101)
# export CONNECTION_URL=https://${USER:-sloria}:${PASSWORD}@${HOST:-localhost}/
# export PASSWORD=secret
# export YEAR=${CURRENT_YEAR:-2020}
from environs import Env
env = Env(expand_vars=True)
connection_url = env("CONNECTION_URL") # =>'https://sloria:secret@localhost'
year = env.int("YEAR") # =>2020
…
environs.validate is equivalent to marshmallow.validate, so you can use any of the validators provided by that module.
By 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, pass eager=False to Env.
Call env.seal() after all variables have been parsed.
…
env.seal() validates all parsed variables and prevents further parsing (calling a parser method will raise an error).
env.url() supports non-standard URL schemes via the schemes argument.
from urllib.parse import urlparse
REDIS_URL = env.url(
"REDIS_URL", urlparse("redis://redis:6379"), schemes=["redis"], require_tld=False
)
# 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}
Some values should not be stored in the environment. For this use case a commonly
used technique is to store the value (e.g., a password) in a file and set the path
to that file in an environment variable. Use FileAwareEnv in place of Env to automatically check for environment variables with the _FILE suffix. If the
file is found, its contents will be read and returned.
from environs import FileAwareEnv
# printf 'my secret password' >/run/secrets/password
# export PASSWORD_FILE=/run/secrets/password
env = FileAwareEnv()
password = env.str("PASSWORD") # => 'my secret password'
It's also possible to set a different suffix for the variable names:
from environs import FileAwareEnv
# printf 'my secret password' >/run/secrets/password
# export PASSWORD_SECRET=/run/secrets/password
env = FileAwareEnv(file_suffix="_SECRET")
password = env.str("PASSWORD") # => 'my secret password'
To strip leading and trailing whitespaces from the value:
from environs import FileAwareEnv
# printf ' value with leading and trailing whitespaces \n' >/run/secrets/password
# export PASSWORD_FILE=/run/secrets/password
env: FileAwareEnv = FileAwareEnv(strip_whitespace=True)
password: str = env.str("PASSWORD") # => `value with leading and trailing whitespaces`
# export DOMAIN='http://myapp.com'
# export COLOR=invalid
from furl import furl
# Register a new parser method for paths
@env.parser_for("furl")
def furl_parser(value):
return furl(value)
domain = env.furl("DOMAIN") # => furl('https://myapp.com')
# Custom parsers can take extra keyword arguments
@env.parser_for("choice")
def choice_parser(value, choices):
if value not in choices:
raise environs.EnvError("Invalid!")
return value
color = env.choice("COLOR", choices=["black"]) # => raises EnvError
# myapp/settings.py
from environs import Env
env = Env()
env.read_env()
# Override in .env for local development
DEBUG = env.bool("FLASK_DEBUG", default=False)
# SECRET_KEY is required
SECRET_KEY = env.str("SECRET_KEY")
Load the configuration after you initialize your app.
# myapp/app.py
from flask import Flask
app = Flask(__name__)
app.config.from_object("myapp.settings")
For local development, use a .env file to override the default
configuration.
# .env
DEBUG=true
SECRET_KEY="not so secret"
Note: Because environs depends on python-dotenv,
the flask CLI will automatically read .env and .flaskenv files.
environs includes a number of helpers for parsing connection URLs. To install environs with django support:
pip install environs[django]
Use env.dj_db_url, env.dj_cache_url and env.dj_email_url to parse the DATABASE_URL, CACHE_URL
and EMAIL_URL environment variables, respectively.
For more details on URL patterns, see the following projects that environs is using for converting URLs.
Basic example:
…
For local development, use a .env file to override the default
configuration.
# .env
DEBUG=true
SECRET_KEY="not so secret"
For a more complete example, see
django_example.py
in the examples/ directory.
See The 12-factor App section on configuration.
os.environ?While os.environ is 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 you
There are many great Python libraries for parsing environment variables. In fact, most of the credit for environs' public API goes to the authors of envparse and django-environ.
environs aims to meet three additional goals:
See this GitHub issue which details specific differences with envparse.
MIT licensed. See the LICENSE file for more details.
No open issues yet, or sync has not completed.