feat: implemented declarative schema-based settings system
This commit is contained in:
261
CONTRIBUTE.md
Normal file
261
CONTRIBUTE.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# Contributing to Alfred
|
||||
|
||||
## Settings Management System
|
||||
|
||||
Alfred uses a **declarative, schema-based configuration system** that ensures type safety, validation, and maintainability.
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
settings.toml # Schema definitions (single source of truth)
|
||||
↓
|
||||
settings_schema.py # Parser & validation
|
||||
↓
|
||||
settings_bootstrap.py # Generation & resolution
|
||||
↓
|
||||
.env # Runtime configuration
|
||||
.env.make # Build variables for Makefile
|
||||
↓
|
||||
settings.py # Pydantic Settings (runtime validation)
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
- **`settings.toml`** — Declarative schema for all settings
|
||||
- **`alfred/settings_schema.py`** — Schema parser and validation logic
|
||||
- **`alfred/settings_bootstrap.py`** — Bootstrap logic (generates `.env` and `.env.make`)
|
||||
- **`alfred/settings.py`** — Pydantic Settings class (runtime)
|
||||
- **`.env`** — Generated configuration file (gitignored)
|
||||
- **`.env.make`** — Build variables for Makefile (gitignored)
|
||||
|
||||
### Setting Sources
|
||||
|
||||
Settings can come from different sources:
|
||||
|
||||
| Source | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `toml` | From `pyproject.toml` | Version numbers, build config |
|
||||
| `env` | From `.env` file | User-provided values, API keys |
|
||||
| `generated` | Auto-generated secrets | JWT secrets, passwords |
|
||||
| `computed` | Calculated from other settings | Database URIs |
|
||||
|
||||
### How to Add a New Setting
|
||||
|
||||
#### 1. Define in `settings.toml`
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.MY_NEW_SETTING]
|
||||
type = "string" # string, integer, float, boolean, secret, computed
|
||||
source = "env" # env, toml, generated, computed
|
||||
default = "default_value" # Optional: default value
|
||||
description = "Description here" # Required: clear description
|
||||
category = "app" # app, api, database, security, build
|
||||
required = true # Optional: default is true
|
||||
validator = "range:1:100" # Optional: validation rule
|
||||
export_to_env_make = false # Optional: export to .env.make for Makefile
|
||||
```
|
||||
|
||||
#### 2. Regenerate Configuration
|
||||
|
||||
```bash
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
This will:
|
||||
- Read the schema from `settings.toml`
|
||||
- Generate/update `.env` with the new setting
|
||||
- Generate/update `.env.make` if `export_to_env_make = true`
|
||||
- Preserve existing secrets
|
||||
|
||||
#### 3. Validate
|
||||
|
||||
```bash
|
||||
make validate
|
||||
```
|
||||
|
||||
This ensures all settings are valid according to the schema.
|
||||
|
||||
#### 4. Use in Code
|
||||
|
||||
The setting is automatically available in `settings.py`:
|
||||
|
||||
```python
|
||||
from alfred.settings import settings
|
||||
|
||||
print(settings.my_new_setting)
|
||||
```
|
||||
|
||||
### Setting Types
|
||||
|
||||
#### String Setting
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.API_URL]
|
||||
type = "string"
|
||||
source = "env"
|
||||
default = "https://api.example.com"
|
||||
description = "API base URL"
|
||||
category = "api"
|
||||
```
|
||||
|
||||
#### Integer Setting with Validation
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.MAX_RETRIES]
|
||||
type = "integer"
|
||||
source = "env"
|
||||
default = 3
|
||||
description = "Maximum retry attempts"
|
||||
category = "app"
|
||||
validator = "range:1:10"
|
||||
```
|
||||
|
||||
#### Secret (Auto-generated)
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.API_SECRET]
|
||||
type = "secret"
|
||||
source = "generated"
|
||||
secret_rule = "32:b64" # 32 bytes, base64 encoded
|
||||
description = "API secret key"
|
||||
category = "security"
|
||||
```
|
||||
|
||||
Secret rules:
|
||||
- `"32:b64"` — 32 bytes, URL-safe base64
|
||||
- `"16:hex"` — 16 bytes, hexadecimal
|
||||
|
||||
#### Computed Setting
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.DATABASE_URL]
|
||||
type = "computed"
|
||||
source = "computed"
|
||||
compute_from = ["DB_HOST", "DB_PORT", "DB_NAME"]
|
||||
compute_template = "postgresql://{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
description = "Database connection URL"
|
||||
category = "database"
|
||||
```
|
||||
|
||||
#### From TOML (Build Variables)
|
||||
|
||||
```toml
|
||||
[tool.alfred.settings_schema.APP_VERSION]
|
||||
type = "string"
|
||||
source = "toml"
|
||||
toml_path = "tool.poetry.version"
|
||||
description = "Application version"
|
||||
category = "build"
|
||||
export_to_env_make = true # Available in Makefile
|
||||
```
|
||||
|
||||
### Validators
|
||||
|
||||
Available validators:
|
||||
|
||||
- **`range:min:max`** — Numeric range validation
|
||||
```toml
|
||||
validator = "range:0.0:2.0" # For floats
|
||||
validator = "range:1:100" # For integers
|
||||
```
|
||||
|
||||
### Categories
|
||||
|
||||
Organize settings by category:
|
||||
|
||||
- **`app`** — Application settings
|
||||
- **`api`** — API keys and external services
|
||||
- **`database`** — Database configuration
|
||||
- **`security`** — Secrets and security keys
|
||||
- **`build`** — Build-time configuration
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always add a description** — Make it clear what the setting does
|
||||
2. **Use appropriate types** — Don't use strings for numbers
|
||||
3. **Add validation** — Use validators for numeric ranges
|
||||
4. **Categorize properly** — Helps with organization
|
||||
5. **Use computed settings** — For values derived from others (e.g., URIs)
|
||||
6. **Mark secrets as generated** — Let the system handle secret generation
|
||||
7. **Export build vars** — Set `export_to_env_make = true` for Makefile variables
|
||||
|
||||
### Workflow Example
|
||||
|
||||
```bash
|
||||
# 1. Edit settings.toml
|
||||
vim settings.toml
|
||||
|
||||
# 2. Regenerate configuration
|
||||
make bootstrap
|
||||
|
||||
# 3. Validate
|
||||
make validate
|
||||
|
||||
# 4. Test
|
||||
python -c "from alfred.settings import settings; print(settings.my_new_setting)"
|
||||
|
||||
# 5. Commit (settings.toml only, not .env)
|
||||
git add settings.toml
|
||||
git commit -m "Add MY_NEW_SETTING"
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
make bootstrap # Generate .env and .env.make from schema
|
||||
make validate # Validate all settings against schema
|
||||
make help # Show all available commands
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Setting not found in schema:**
|
||||
```
|
||||
KeyError: Missing [tool.alfred.settings_schema] section
|
||||
```
|
||||
→ Check that `settings.toml` exists and has the correct structure
|
||||
|
||||
**Validation error:**
|
||||
```
|
||||
ValueError: MY_SETTING must be between 1 and 100, got 150
|
||||
```
|
||||
→ Check the validator in `settings.toml` and adjust the value in `.env`
|
||||
|
||||
**Secret not preserved:**
|
||||
→ Secrets are automatically preserved during `make bootstrap`. If lost, they were never in `.env` (check `.env` exists before running bootstrap)
|
||||
|
||||
### Testing
|
||||
|
||||
When adding a new setting, consider adding tests:
|
||||
|
||||
```python
|
||||
# tests/test_settings_schema.py
|
||||
def test_my_new_setting(self, create_schema_file):
|
||||
"""Test MY_NEW_SETTING definition."""
|
||||
schema_toml = """
|
||||
[tool.alfred.settings_schema.MY_NEW_SETTING]
|
||||
type = "string"
|
||||
source = "env"
|
||||
default = "test"
|
||||
"""
|
||||
base_dir = create_schema_file(schema_toml)
|
||||
schema = load_schema(base_dir)
|
||||
|
||||
definition = schema.get("MY_NEW_SETTING")
|
||||
assert definition.default == "test"
|
||||
```
|
||||
|
||||
### Migration from Old System
|
||||
|
||||
If you're migrating from the old system:
|
||||
|
||||
1. Settings are now in `settings.toml` instead of scattered across files
|
||||
2. No more `.env.example` — schema is the source of truth
|
||||
3. Secrets are auto-generated and preserved
|
||||
4. Validation happens at bootstrap time, not just runtime
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
Open an issue or check the existing settings in `settings.toml` for examples.
|
||||
Reference in New Issue
Block a user