A Config[A] says which keys to read and how to decode them. Configs are effects that fail with
ConfigError, so they compose with everything else.
import pyfect as fx
from pyfect import config, config_provider
@config.gen
def server():
host = yield from config.string("HOST").with_default("localhost")
port = yield from config.port("PORT")
debug = yield from config.boolean("DEBUG").with_default(False)
return (host, port, debug)
# Configs read from CurrentConfigProvider: environment variables unless a layer
# installs another source (a .env file, a directory, nested dicts).
env = config_provider.layer(config_provider.from_env(env={"PORT": "8080", "DEBUG": "true"}))
# server is a Config[tuple[str, int, bool]] and an Effect failing with ConfigError.
# ConfigError is still in the type, so run_sync (which takes only effects that
# cannot fail) would be a type error: run_sync_exit returns the Exit instead.
assert fx.run_sync_exit(server.pipe(fx.provide(env))) == fx.Success(("localhost", 8080, True))
# Invalid data fails with a readable message, not a default.
bad = config_provider.layer(config_provider.from_env(env={"PORT": "99999"}))
exit = fx.run_sync_exit(server.pipe(fx.provide(bad)))
assert isinstance(exit, fx.Failure)
assert "Expected a value between 1 and 65535" in str(exit.cause.squash())Missing data is absent: with_default and config.option recover it. Data that is present
but invalid fails, so a typo in PORT is an error, not a silent default.
Sources and secrets#
A ConfigProvider is where values come from: from_env (the default), from_dot_env,
from_dir, from_unknown (nested dicts) or your own lookup. Install one with
config_provider.layer. nested reads a config under a prefix, and config.redacted keeps a
secret out of logs and error messages.
import pyfect as fx
from pyfect import config, config_provider, redacted
provider = config_provider.from_unknown({"db": {"url": "postgres://db", "password": "hunter2"}})
@config.gen
def database():
url = yield from config.string("url")
password = yield from config.redacted("password") # a Redacted[str]
return url, password
# or_die: a config error at startup is fatal (a defect, out of the error type).
url, password = fx.run_sync(
database.nested("db").pipe(fx.provide(config_provider.layer(provider))).or_die()
)
assert url == "postgres://db"
assert str(password) == "<redacted>" # never printed by str, repr or loggers
assert redacted.value(password) == "hunter2"Constructors: string, integer, number, boolean, port, url, duration, literal,
log_level, byte_size, date_time, redacted, list_of, dict_of; combine with
config.all, all_dict or @config.gen.
Reference: pyfect.config,
pyfect.config_provider,
pyfect.redacted.
On This Page
Sources and secrets