We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Config, Logs and Untrusted Input step 5 of 16
Merge Settings from a File, the Environment and the Command Line
Your command-line tool takes its settings from three places: a config file, environment variables and command-line options. The command line beats the environment, which beats the file, which beats the defaults. Write a resolver that merges them and records where each final value came from, so that "why is the port 9200?" has an answer.
solve({"port": 9000}, {"APP_PORT": "9100", "PATH": "/bin"}, ["--port=9200", "--verbose"])
# {"ok": True, "host": "localhost", "port": 9200, "workers": 4, "verbose": True,
# "sources": {"host": "default", "port": "cli", "workers": "default", "verbose": "cli"}}
What to write
Fill in resolve_config(file_table, env, argv) and return a Resolved. Keep
the supplied constants, dataclasses, ConfigError and solve as they are.
Read only the three inputs, not a file, os.environ or sys.argv, and do not
change them.
| Field | Type | Default |
|---|---|---|
host |
str |
"localhost" |
port |
int, not bool |
8080 |
workers |
int, not bool |
4 |
verbose |
bool |
False |
Apply the sources in order
defaults < file < environment < command line
Start from the defaults. Apply the file entries in the order they appear, then
the environment entries in order, then the command-line tokens in order. Each
value replaces the field's current value. Set sources[field] to
"default", "file", "env" or "cli" for the last source that set the
field, even when it set the same value as before.
Check each value as soon as you reach it, and stop at the first error. A bad value in the file is an error even when the command line would replace it.
Check file values, convert text values
The file arrives already parsed, as from tomllib.load, so its values have
types. Its keys must match the field names exactly. Check that each value has
its field's type, without converting it. A file port of "9000" is a type
error. Reject True and False for an integer field, because bool is a
subclass of int.
Environment and command-line values are always text. Convert them by field type:
- For a
strfield, keep the text exactly, including spaces and the empty string. - For an
intfield, useint(raw). It accepts a sign, spaces around the number and underscores between digits, as in"1_000". It rejects"3.0"and"0x10". Turn itsValueErrorinto aConfigErrorwith kind"type". - For a
boolfield, useraw.strip().lower(). Accept1,true,yesandonasTrue, and0,false,noandoffasFalse. Any other text is a"type"error.
Check types only. An empty host and a negative port are both allowed.
Environment variables
Only variables whose names start with exactly APP_ belong to this tool.
Ignore the rest, such as PATH. Lowercase the part after the prefix to get
the field name, so APP_PORT and APP_port both set port. If both appear,
the later one wins. A prefixed name that matches no field, such as
APP_TIEMOUT, is an "unknown" error.
Command-line options
--key=valuesets a field. Split at the first=, so--host=a=bsetshostto"a=b". In the key, change dashes to underscores, but keep its case.--verbosesetsverbosetoTrue, and--no-verbosesets it toFalse. These are the only options without an=.- Options apply in order, so a repeated option takes its last value.
- There is no
--port 9000form with a separate value, no positional argument, no--helpand no--end marker.
Errors
Raise ConfigError(layer, key, kind). layer names the source of the bad
value: "file", "env" or "cli". key is the original file key or
variable name, or the whole command-line token. Check in this order, and use
the first kind that applies:
| Check | kind |
|---|---|
The token does not start with -- |
"malformed" |
The field name is not in FIELDS |
"unknown" |
An option for a non-boolean field has no = |
"malformed" |
| The file value has the wrong type, or the text does not convert | "type" |
So --port is malformed, --missing is unknown, and --port=x is a type
error. --no-verbose=false contains =, so its key is no_verbose, which is
unknown. A bare -- has an empty key, which is unknown too.
What the driver reports
Return Resolved(config=Config(...), sources=...), with a new sources
dictionary on every call. solve turns a success into the report in the
first example, and a ConfigError into a report like this one:
solve({"port": "9000"}, {}, ["--port=9200"])
# {"ok": False, "layer": "file", "key": "port", "kind": "type"}
Your code must also pass mypy --strict. The values you collect have the
type object. Check each one with isinstance before you pass it to
Config, so mypy knows its type.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.