A Python KeyError when reading YAML config means the parsed dict does not have the key you expected. The 4 most common 2026 causes: case mismatch (YAML is case-sensitive), nested keys not where you think, missing environment overrides, and schema drift between dev and production config files.

Minimal reproducer
# config.yaml
database:
host: localhost
port: 5432
# code.py
import yaml
with open('config.yaml') as f:
cfg = yaml.safe_load(f)
user = cfg['database']['user'] # KeyError: 'user'
Fix 1: Print the parsed dict during development
import pprint
pprint.pprint(cfg) # see exactly what got parsed
# common surprise: leading whitespace, BOM, or wrong indent
# YAML is whitespace-sensitive, 2 spaces vs 4 spaces matters
Fix 2: Safe access with chained .get()
host = cfg.get('database', {}).get('host', 'localhost')
port = cfg.get('database', {}).get('port', 5432)
Fix 3: Define a schema with Pydantic
from pydantic import BaseModel
class DatabaseConfig(BaseModel):
host: str = 'localhost'
port: int = 5432
user: str = 'root'
password: str = ''
class AppConfig(BaseModel):
database: DatabaseConfig = DatabaseConfig()
cfg = AppConfig(**yaml.safe_load(open('config.yaml')))
# Now cfg.database.user works, with defaults if missing
Fix 4: Use environment variables as override
import os
# Merge YAML defaults with environment overrides
db_user = os.environ.get('DB_USER') or cfg.get('database', {}).get('user', 'root')
db_pass = os.environ.get('DB_PASSWORD') or cfg.get('database', {}).get('password', '')
Secrets in YAML are a bad practice. Read them from environment or a secrets manager (AWS Secrets Manager, GCP Secret Manager) and merge after.
YAML gotchas to know
- Case sensitive:
Hostandhostare different keys. - Booleans:
yes,no,on,off,true,falseare all booleans, not strings. Quote them if you mean strings. - Leading zeros:
port: 022is octal (=18), not 22. Always quote port numbers in YAML 1.1. - Multiline strings: use
|for literal newlines or>for folded. - Anchors and aliases:
&nameand*namelet you reuse blocks. PyYAML supports them.
Frequently Asked Questions
Should I use yaml.load or yaml.safe_load?
Always yaml.safe_load. The plain yaml.load() can execute arbitrary Python code from the YAML file (remote code execution vulnerability). PyYAML emits a warning on yaml.load() since v5.1 to discourage this.
What is the difference between PyYAML and ruamel.yaml?
PyYAML is older, faster, well-known. ruamel.yaml preserves comments and original formatting on round-trip (good for tools that read AND write the same config). For read-only loads, PyYAML is fine.
How do I check if a YAML file has all required keys before using it?
Use Pydantic BaseModel (best, validates types too) or schema libraries like jsonschema, cerberus. For simple cases, list required keys and check: missing = [k for k in REQUIRED if k not in cfg]; raise ValueError if missing.
Can I have environment variable substitution in YAML?
Not natively in PyYAML. Use jinja2 to pre-process: rendered = Template(open(‘config.yaml’).read()).render(env=os.environ); cfg = yaml.safe_load(rendered). Or use a config library that supports it (Dynaconf, OmegaConf, Hydra).
Why does PyYAML convert my version number 1.0 into a float?
YAML implicitly types unquoted values. Quote the version: version: “1.0” forces it to a string. Same for port numbers, IP addresses, dates, all are best quoted in config files.
