Configuration Files¶
apywire supports loading specs from JSON, TOML, and INI configuration files. The apywire.formats module provides utilities for parsing and serializing specs in these formats.
Why Use Configuration Files?¶
- Separation of Concerns: Keep configuration separate from code
- Environment-Specific: Different configs for dev, staging, production
- Version Control: Track configuration changes
- Non-Developers: Allow non-programmers to modify configuration
- CLI Workflow: Generate specs with CLI, edit, then compile
Quick Start with CLI¶
The fastest way to create a configuration file is using the CLI:
# Generate a spec from class introspection
python -m apywire generate --format toml "datetime.datetime now" > config.toml
# Edit config.toml to customize values, then compile
python -m apywire compile --format toml config.toml > wiring.py
See Command Line Interface for full CLI documentation.
Supported Formats¶
JSON¶
JSON provides direct mapping with no conversion needed.
Example: config.json
{
"database_url": "postgresql://localhost/mydb",
"pool_size": 20,
"myapp.Database db": {
"url": "{database_url}",
"pool_size": "{pool_size}"
}
}
Loading:
from apywire import Wiring
from apywire.formats import json_to_spec
with open("config.json") as f:
spec = json_to_spec(f.read())
wired = Wiring(spec)
db = wired.db()
Benefits:
- ✅ Built into Python standard library
- ✅ Universal format
- ✅ No conversion needed
- ❌ No comments
TOML¶
TOML is Python-friendly with top-level keys as constants and tables as wiring entries.
Example: config.toml
# Constants as top-level keys
database_url = "postgresql://localhost/mydb"
pool_size = 20
# Wiring entries as tables (quote names with spaces)
["myapp.Database db"]
url = "{database_url}"
pool_size = "{pool_size}"
Quoted Section Names
TOML section names with spaces must be quoted: ["module.Class name"]
Loading:
from apywire import Wiring
from apywire.formats import toml_to_spec
with open("config.toml") as f:
spec = toml_to_spec(f.read())
wired = Wiring(spec)
Benefits:
- ✅ Built into Python 3.11+ (
tomllib) - ✅ Supports comments
- ✅ Type-safe (integers, booleans, etc.)
- ✅ Clean syntax for nested structures
INI¶
INI uses a [constants] section for constants (required by the format).
Example: config.ini
[constants]
database_url = postgresql://localhost/mydb
pool_size = 20
[myapp.Database db]
url = {database_url}
pool_size = {pool_size}
Loading:
from apywire import Wiring
from apywire.formats import ini_to_spec
with open("config.ini") as f:
spec = ini_to_spec(f.read())
wired = Wiring(spec)
Benefits:
- ✅ Built into Python standard library
- ✅ Simple syntax
- ✅ Supports comments
- ❌ All values are strings (automatic type conversion provided)
YAML¶
YAML is not directly supported by the formats module, but you can use PyYAML:
import yaml
from apywire import Wiring
with open("config.yaml") as f:
spec = yaml.safe_load(f)
wired = Wiring(spec)
Format Conversion¶
The formats module provides functions to convert between formats:
from apywire.formats import (
json_to_spec, spec_to_json,
toml_to_spec, spec_to_toml,
ini_to_spec, spec_to_ini,
)
# Load from one format
with open("config.json") as f:
spec = json_to_spec(f.read())
# Save to another format
toml_output = spec_to_toml(spec)
with open("config.toml", "w") as f:
f.write(toml_output)
Placeholder Expansion¶
Constants can reference other constants and wired objects using {name} syntax.
Constant → Constant¶
When a constant references only other constants, it's expanded immediately:
Constant → Wired Object¶
When a constant references a wired object, it becomes a lazy accessor:
["datetime.datetime server_start"]
year = 2025
month = 1
day = 1
# This is auto-promoted to an accessor
status = "Server started at {server_start}"
Environment-Based Configuration¶
Load different configs based on environment:
import os
from apywire import Wiring
from apywire.formats import toml_to_spec
env = os.getenv("APP_ENV", "dev")
config_file = f"config.{env}.toml"
with open(config_file) as f:
spec = toml_to_spec(f.read())
wired = Wiring(spec, thread_safe=(env == "production"))
Environment Variables¶
You can substitute environment variables before parsing:
import os
import re
from apywire import Wiring
from apywire.formats import toml_to_spec
def substitute_env(content: str) -> str:
"""Replace ${VAR} with environment variable values."""
return re.sub(
r'\$\{([^}]+)\}',
lambda m: os.getenv(m.group(1), ''),
content
)
with open("config.toml") as f:
content = substitute_env(f.read())
spec = toml_to_spec(content)
wired = Wiring(spec)
Complete Example¶
Project Structure¶
myapp/
├── config/
│ ├── dev.toml
│ ├── production.toml
│ └── config.example.toml
├── app/
│ ├── __init__.py
│ ├── config.py
│ └── main.py
└── wiring.py # Generated via CLI
config/dev.toml¶
debug = true
database_url = "postgresql://localhost/myapp_dev"
pool_size = 5
["myapp.Database db"]
url = "{database_url}"
pool_size = "{pool_size}"
["myapp.Cache cache"]
backend = "memory"
app/config.py¶
import os
from pathlib import Path
from apywire import Wiring
from apywire.formats import toml_to_spec
def load_wiring() -> Wiring:
env = os.getenv("APP_ENV", "dev")
config_dir = Path(__file__).parent.parent / "config"
config_file = config_dir / f"{env}.toml"
with open(config_file) as f:
spec = toml_to_spec(f.read())
return Wiring(spec, thread_safe=(env == "production"))
app/main.py¶
from app.config import load_wiring
def main():
wired = load_wiring()
db = wired.db()
cache = wired.cache()
# Use services...
if __name__ == "__main__":
main()
Best Practices¶
- Keep Secrets Out: Use environment variables for passwords and API keys
- Separate Configs: Use different files for dev/staging/production
- Document Your Config: Include a
config.example.tomlin version control - Validate Early: Check required keys before creating Wiring instance
Next Steps¶
- Command Line Interface - Generate and compile specs
- Basic Usage - Learn the fundamentals
- Compilation - Understanding compiled output