Skip to content

Commit af3fd4b

Browse files
authored
Merge pull request #897 from PolicyEngine/feat/config-loader
Create config loader, but don't use it yet
2 parents 2d0216c + bc5ef4d commit af3fd4b

8 files changed

Lines changed: 1399 additions & 46 deletions

File tree

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: minor
2+
changes:
3+
added:
4+
- Created config loader to load custom configuration files; this remains unused

config/README.md

Lines changed: 247 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,96 +2,297 @@
22

33
This directory contains configuration files for the PolicyEngine Household API.
44

5-
## Current State
5+
## Current Implementation Status
66

7-
The application currently uses environment variables for all configuration. These configuration files are placeholders that establish the structure for future migration to a file-based configuration system.
7+
The application now has a `ConfigLoader` class (`policyengine_household_api/utils/config_loader.py`) that supports hierarchical configuration loading. While the system is ready, the application code still uses environment variables directly. Configuration files in this directory establish the structure for gradual migration.
88

9-
## Files
9+
## Configuration Priority
1010

11-
- `default.yaml` - Default configuration for local development (currently empty placeholders)
12-
- `production.yaml.example` - Example production configuration (not used in deployment)
13-
- `development.yaml.example` - Example development configuration
11+
The `ConfigLoader` loads configuration from multiple sources in the following priority order (highest to lowest):
1412

15-
## Migration Plan
13+
1. **Environment Variables** - Override everything
14+
2. **Mounted Config File** - External configuration file (via `CONFIG_FILE` env var)
15+
3. **Default Config** - Baked into the Docker image at `/app/config/default.yaml`
1616

17-
The configuration system will be migrated gradually:
17+
Environment variables always win, allowing you to override specific settings without changing config files.
1818

19-
1. **Phase 1 (Current)**: All configuration via environment variables
20-
2. **Phase 2**: Configuration files with environment variable overrides
21-
3. **Phase 3**: Configuration files as primary source, env vars for secrets only
19+
## Files in this Directory
20+
21+
- `default.yaml` - Default configuration for local development (currently mostly empty/commented)
22+
- `custom.yaml.example` - Example template for mounting custom configuration
23+
- `production.yaml.example` - Example production configuration
24+
- `development.yaml.example` - Example development configuration
25+
- `local.yaml.example` - Example for fully local runs without external dependencies
26+
27+
## Configuration Methods
28+
29+
### Method 1: Environment Variables (Highest Priority)
30+
31+
Environment variables override all other configuration sources. They work in two ways:
32+
33+
#### Explicit Mapping
34+
Pre-defined environment variables that map to specific config paths:
35+
36+
```bash
37+
# Database configuration
38+
USER_ANALYTICS_DB_CONNECTION_NAME=my-connection
39+
USER_ANALYTICS_DB_USERNAME=myuser
40+
USER_ANALYTICS_DB_PASSWORD=secret
41+
42+
# Auth configuration
43+
AUTH0_ADDRESS_NO_DOMAIN=my-auth0-domain
44+
AUTH0_AUDIENCE_NO_DOMAIN=my-audience
45+
46+
# AI configuration
47+
ANTHROPIC_API_KEY=sk-ant-...
48+
49+
# Debug mode
50+
FLASK_DEBUG=1
51+
52+
# Server configuration
53+
PORT=8080
54+
```
55+
56+
#### Double Underscore Notation
57+
Any environment variable with double underscores (`__`) is automatically mapped to nested config:
58+
59+
```bash
60+
# DATABASE__HOST becomes database.host
61+
DATABASE__HOST=localhost
62+
DATABASE__PORT=3306
63+
64+
# AUTH__ENABLED becomes auth.enabled
65+
AUTH__ENABLED=true
66+
67+
# SERVER__WORKERS becomes server.workers
68+
SERVER__WORKERS=4
69+
```
70+
71+
Note: System environment variables starting with underscores are ignored.
72+
73+
### Method 2: Mounted Config File (Medium Priority)
74+
75+
You can mount an external configuration file to override the defaults:
76+
77+
#### Docker Run
78+
```bash
79+
# Mount a custom config file
80+
docker run -v /path/to/your/config.yaml:/custom/config.yaml \
81+
-e CONFIG_FILE=/custom/config.yaml \
82+
policyengine/household-api
83+
```
84+
85+
#### Docker Compose
86+
```yaml
87+
version: '3.8'
88+
services:
89+
household-api:
90+
image: policyengine/household-api
91+
volumes:
92+
- ./my-config.yaml:/app/config/custom.yaml
93+
environment:
94+
- CONFIG_FILE=/app/config/custom.yaml
95+
# Still provide secrets via env vars
96+
- DATABASE__PASSWORD=${DB_PASSWORD}
97+
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
98+
```
99+
100+
#### Kubernetes ConfigMap
101+
```yaml
102+
apiVersion: v1
103+
kind: ConfigMap
104+
metadata:
105+
name: household-api-config
106+
data:
107+
config.yaml: |
108+
database:
109+
provider: postgres
110+
host: postgres.default.svc.cluster.local
111+
auth:
112+
enabled: true
113+
---
114+
apiVersion: apps/v1
115+
kind: Deployment
116+
spec:
117+
template:
118+
spec:
119+
containers:
120+
- name: api
121+
image: policyengine/household-api
122+
env:
123+
- name: CONFIG_FILE
124+
value: /config/config.yaml
125+
- name: DATABASE__PASSWORD
126+
valueFrom:
127+
secretKeyRef:
128+
name: db-secret
129+
key: password
130+
volumeMounts:
131+
- name: config
132+
mountPath: /config
133+
volumes:
134+
- name: config
135+
configMap:
136+
name: household-api-config
137+
```
138+
139+
### Method 3: Default Config (Lowest Priority)
140+
141+
The default configuration is baked into the Docker image at `/app/config/default.yaml`. This provides sensible defaults for local development and serves as a fallback.
22142

23143
## Configuration Structure
24144

25145
```yaml
26146
app:
27-
name: Application name
147+
name: Application name (default: policyengine-household-api)
28148
environment: Environment (local/development/staging/production)
149+
debug: Debug mode (true/false)
29150
30151
database:
31152
provider: Database type (sqlite/mysql/postgres)
32-
# Connection details
153+
connection_name: Cloud SQL connection name (for GCP)
154+
username: Database username
155+
password: Database password
156+
host: Database host
157+
port: Database port
158+
path: SQLite file path (for sqlite provider)
159+
pool_size: Connection pool size
33160
34161
storage:
35162
provider: Storage backend (local/gcs/s3)
36-
# Provider-specific settings
163+
bucket: Storage bucket name (for cloud providers)
164+
path: Local storage path (for local provider)
37165
38166
auth:
39-
enabled: Whether authentication is required
167+
enabled: Whether authentication is required (true/false)
40168
provider: Auth provider (none/auth0/cognito)
41-
# Provider-specific settings
169+
auth0:
170+
address: Auth0 domain
171+
audience: Auth0 audience
42172
43173
ai:
44-
enabled: Whether AI features are enabled
174+
enabled: Whether AI features are enabled (true/false)
45175
provider: AI service provider (none/anthropic/openai)
46-
# Provider-specific settings
176+
anthropic:
177+
api_key: Anthropic API key
178+
model: Model name
179+
max_tokens: Maximum tokens
180+
temperature: Temperature setting
47181
48182
server:
49-
port: Server port
183+
port: Server port (default: 8080)
50184
workers: Number of worker processes
185+
threads: Number of threads per worker
51186
timeout: Request timeout in seconds
52187
53188
logging:
54189
level: Log level (DEBUG/INFO/WARNING/ERROR)
55190
format: Log format (json/text)
56191
```
57192

58-
## Environment Variable Mapping
193+
## Usage Examples
59194

60-
When migrated, environment variables will map to configuration as follows:
195+
### Production Deployment (Current)
61196

62-
- `FLASK_DEBUG` → `app.debug`
63-
- `USER_ANALYTICS_DB_CONNECTION_NAME` → `database.connection_name`
64-
- `USER_ANALYTICS_DB_USERNAME` → `database.username`
65-
- `USER_ANALYTICS_DB_PASSWORD` → `database.password`
66-
- `AUTH0_ADDRESS_NO_DOMAIN` → `auth.auth0.address`
67-
- `AUTH0_AUDIENCE_NO_DOMAIN` → `auth.auth0.audience`
68-
- `ANTHROPIC_API_KEY` → `ai.anthropic.api_key`
197+
Currently in production, all configuration comes from environment variables:
69198

70-
## Usage (Future)
71-
72-
Once migration is complete:
199+
```bash
200+
# Via GitHub Actions secrets
201+
AUTH0_ADDRESS_NO_DOMAIN=${{ secrets.AUTH0_ADDRESS_NO_DOMAIN }}
202+
AUTH0_AUDIENCE_NO_DOMAIN=${{ secrets.AUTH0_AUDIENCE_NO_DOMAIN }}
203+
USER_ANALYTICS_DB_USERNAME=${{ secrets.USER_ANALYTICS_DB_USERNAME }}
204+
USER_ANALYTICS_DB_PASSWORD=${{ secrets.USER_ANALYTICS_DB_PASSWORD }}
205+
USER_ANALYTICS_DB_CONNECTION_NAME=${{ secrets.USER_ANALYTICS_DB_CONNECTION_NAME }}
206+
ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}
207+
```
73208

74209
### Local Development
75-
```bash
76-
# Use default.yaml automatically
77-
python -m policyengine_household_api.api
78210

79-
# Or specify a config file
80-
CONFIG_FILE=config/development.yaml python -m policyengine_household_api.api
211+
Use environment variables to override specific settings:
212+
213+
```bash
214+
docker run -e FLASK_DEBUG=1 \
215+
-e AUTH__ENABLED=false \
216+
-e AI__ENABLED=false \
217+
-e DATABASE__PROVIDER=sqlite \
218+
policyengine/household-api
81219
```
82220

83-
### Docker
221+
### Custom Cloud Provider
222+
223+
Mount a complete custom configuration:
224+
84225
```bash
85-
# Mount custom config
86-
docker run -v $(pwd)/my-config.yaml:/app/config/active.yaml household-api
226+
# Create custom config
227+
cat > aws-config.yaml <<EOF
228+
database:
229+
provider: postgres
230+
host: my-rds-instance.amazonaws.com
231+
storage:
232+
provider: s3
233+
bucket: my-household-data
234+
auth:
235+
provider: cognito
236+
pool_id: us-east-1_xxxxx
237+
EOF
87238
88-
# Or use environment variable to specify config location
89-
docker run -e CONFIG_FILE=/app/config/production.yaml household-api
239+
# Run with custom config
240+
docker run -v $(pwd)/aws-config.yaml:/app/config/aws.yaml \
241+
-e CONFIG_FILE=/app/config/aws.yaml \
242+
-e DATABASE__PASSWORD="${RDS_PASSWORD}" \
243+
policyengine/household-api
90244
```
91245

92-
### Production
93-
Environment variables will override config file values for sensitive data:
246+
## Migration Status
247+
248+
### Phase 1 (Current)
249+
- ✅ `ConfigLoader` class implemented
250+
- ✅ Hierarchical configuration loading working
251+
- ✅ Environment variable mapping functional
252+
- ✅ Docker image includes default config
253+
- ⏳ Application code still uses `os.getenv()` directly
254+
255+
### Phase 2 (Next Steps)
256+
- Gradually update application code to use `get_config_value()` instead of `os.getenv()`
257+
- Move non-sensitive defaults to config files
258+
- Keep sensitive values in environment variables
259+
260+
### Phase 3 (Future)
261+
- Configuration files become primary source
262+
- Environment variables used only for secrets and overrides
263+
- Full configuration validation with Pydantic models
264+
265+
## Using ConfigLoader in Code
266+
267+
To use the configuration system in new code:
268+
269+
```python
270+
from policyengine_household_api.utils import get_config_value
271+
272+
# Get a configuration value with a default
273+
db_provider = get_config_value("database.provider", "sqlite")
274+
275+
# Get nested configuration
276+
auth_enabled = get_config_value("auth.enabled", False)
277+
```
278+
279+
## Best Practices
280+
281+
1. **Never put secrets in config files** - Use environment variables for sensitive data
282+
2. **Use mounted configs for structure** - Define your infrastructure layout in config files
283+
3. **Use env vars for secrets** - Override sensitive values at runtime
284+
4. **Version control your configs** - Keep config files in source control (without secrets)
285+
5. **Environment-specific configs** - Have separate config files for dev/staging/prod
286+
287+
## Debugging Configuration
288+
289+
To see what configuration is being loaded, set the log level to DEBUG:
290+
94291
```bash
95-
# Config file provides structure, env vars provide secrets
96-
DATABASE__PASSWORD=secret CONFIG_FILE=production.yaml python -m app
97-
```
292+
docker run -e LOGGING__LEVEL=DEBUG policyengine/household-api
293+
```
294+
295+
The application will log:
296+
- Which config files were loaded
297+
- Which environment variables were applied
298+
- Any errors in loading configuration files

0 commit comments

Comments
 (0)