Skip to content

← Everyday Standard Library Tools step 53 of 55

Medium Primitives

Load a Server Config and Report the First Bad Setting

Load a TOML server config into a frozen dataclass. If a setting is wrong, report the first bad one with its name and the reason. Then the person who edits the file knows what to fix.

solve('[server]\nhost = "localhost"\nport = 8080\n')
# {"ok": True, "host": "localhost", "port": 8080, "workers": 4, "debug": False}
solve('[server]\nhost = "h"\nport = 70000\n')
# {"ok": False, "field": "server.port", "reason": "out of range"}

What to write

The starter already has ConfigError(field, reason), which keeps both strings as attributes, and this dataclass:

@dataclass(frozen=True, slots=True)
class ServerConfig:
    host: str
    port: int
    workers: int = 4
    debug: bool = False

Fix load_config(stream: BinaryIO) -> ServerConfig:

  • Read the stream with tomllib.load.
  • Check the TOML syntax first, then the server table, then host, port, workers and debug, in that order. Raise a ConfigError for the first problem you find, using the table below.
  • For each setting, check that it is there and has the right type before you check its range.
  • Use workers = 4 and debug = False only when the key is missing. A value that is present must pass its check.
  • Keep host exactly as written, even if it is empty or only spaces. This exercise checks its type only.
  • Ignore other keys and other tables. Accept server written as an inline table too, such as server = { host = "h", port = 80 }.
  • Check the types at runtime before you build ServerConfig. The dataclass does not check them, and neither does typing.cast.
Problem field reason
TOMLDecodeError while parsing "<document>" "malformed TOML"
server is missing or is not a table "server" "missing table"
host is missing "server.host" "missing"
host is not a string "server.host" "expected string"
port is missing "server.port" "missing"
port is not an integer, or is a boolean "server.port" "expected integer"
port is outside 1 to 65535 "server.port" "out of range"
workers is present but not an integer, or is a boolean "server.workers" "expected integer"
workers is present but below 1 "server.workers" "out of range"
debug is present but not a boolean "server.debug" "expected boolean"

Turn only TOMLDecodeError into the "<document>" error. Let any other error, such as a stream that fails to read, propagate.

Then fix solve(document). It already encodes the text as UTF-8, wraps it in io.BytesIO and calls load_config. Catch ConfigError and return {"ok": False, "field": exc.field, "reason": exc.reason}. On success, keep the report the starter returns, with the keys ok, host, port, workers and debug.

Edge cases

  • port = true is an "expected integer" error, because a boolean is not a port number.
  • workers = 0 is an "out of range" error, not a reason to use the default.
  • The test documents use TOML 1.0 syntax.