feat(setup): T04 setup configuration files
- Create config.py with Pydantic Settings (SettingsConfigDict v2) - Add all required configuration fields with defaults - Create .env.example template with all environment variables - Implement get_settings() with @lru_cache for performance - Add test_configuration.py with 13 unit tests - All tests passing (13/13) Refs: T04
This commit is contained in:
29
.env.example
Normal file
29
.env.example
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ===========================================
|
||||||
|
# OpenRouter API Key Monitor - Configuration
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=sqlite:///./data/app.db
|
||||||
|
|
||||||
|
# Security - REQUIRED
|
||||||
|
# Generate with: openssl rand -hex 32
|
||||||
|
SECRET_KEY=your-super-secret-jwt-key-min-32-chars
|
||||||
|
ENCRYPTION_KEY=your-32-byte-encryption-key-here
|
||||||
|
|
||||||
|
# OpenRouter Integration
|
||||||
|
OPENROUTER_API_URL=https://openrouter.ai/api/v1
|
||||||
|
|
||||||
|
# Background Tasks
|
||||||
|
SYNC_INTERVAL_MINUTES=60
|
||||||
|
|
||||||
|
# Limits
|
||||||
|
MAX_API_KEYS_PER_USER=10
|
||||||
|
RATE_LIMIT_REQUESTS=100
|
||||||
|
RATE_LIMIT_WINDOW=3600
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_EXPIRATION_HOURS=24
|
||||||
|
|
||||||
|
# Development
|
||||||
|
DEBUG=false
|
||||||
|
LOG_LEVEL=INFO
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
| Metrica | Valore |
|
| Metrica | Valore |
|
||||||
|---------|--------|
|
|---------|--------|
|
||||||
| **Stato** | 🟡 In Progress |
|
| **Stato** | 🟡 In Progress |
|
||||||
| **Progresso** | 4% |
|
| **Progresso** | 5% |
|
||||||
| **Data Inizio** | 2024-04-07 |
|
| **Data Inizio** | 2024-04-07 |
|
||||||
| **Data Target** | TBD |
|
| **Data Target** | TBD |
|
||||||
| **Task Totali** | 74 |
|
| **Task Totali** | 74 |
|
||||||
| **Task Completati** | 3 |
|
| **Task Completati** | 4 |
|
||||||
| **Task In Progress** | 1 |
|
| **Task In Progress** | 1 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
## 📋 Task Pianificate
|
## 📋 Task Pianificate
|
||||||
|
|
||||||
### 🔧 Setup Progetto (T01-T05) - 3/5 completati
|
### 🔧 Setup Progetto (T01-T05) - 4/5 completati
|
||||||
- [x] T01: Creare struttura cartelle progetto (2024-04-07)
|
- [x] T01: Creare struttura cartelle progetto (2024-04-07)
|
||||||
- [x] T02: Inizializzare virtual environment e .gitignore (2024-04-07)
|
- [x] T02: Inizializzare virtual environment e .gitignore (2024-04-07)
|
||||||
- [x] T03: Creare requirements.txt con dipendenze (2024-04-07)
|
- [x] T03: Creare requirements.txt con dipendenze (2024-04-07)
|
||||||
- [ ] T04: Setup file configurazione (.env, config.py)
|
- [x] T04: Setup file configurazione (.env, config.py) (2024-04-07)
|
||||||
- [ ] T05: Configurare pytest e struttura test
|
- [ ] T05: Configurare pytest e struttura test
|
||||||
|
|
||||||
### 🗄️ Database & Models (T06-T11) - 0/6 completati
|
### 🗄️ Database & Models (T06-T11) - 0/6 completati
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
"""Configuration management using Pydantic Settings.
|
||||||
|
|
||||||
|
This module provides centralized configuration management for the
|
||||||
|
OpenRouter API Key Monitor application.
|
||||||
|
"""
|
||||||
|
from functools import lru_cache
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""Application settings loaded from environment variables.
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
- SECRET_KEY: JWT signing key (min 32 chars)
|
||||||
|
- ENCRYPTION_KEY: AES-256 encryption key (32 bytes)
|
||||||
|
|
||||||
|
Optional environment variables with defaults:
|
||||||
|
- DATABASE_URL: SQLite database path
|
||||||
|
- OPENROUTER_API_URL: OpenRouter API base URL
|
||||||
|
- SYNC_INTERVAL_MINUTES: Background sync interval
|
||||||
|
- MAX_API_KEYS_PER_USER: API key limit per user
|
||||||
|
- RATE_LIMIT_REQUESTS: API rate limit
|
||||||
|
- RATE_LIMIT_WINDOW: Rate limit window (seconds)
|
||||||
|
- JWT_EXPIRATION_HOURS: JWT token lifetime
|
||||||
|
- DEBUG: Debug mode flag
|
||||||
|
- LOG_LEVEL: Logging level
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Database
|
||||||
|
database_url: str = Field(
|
||||||
|
default="sqlite:///./data/app.db",
|
||||||
|
description="SQLite database URL"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Security - REQUIRED
|
||||||
|
secret_key: str = Field(
|
||||||
|
description="JWT signing key (min 32 characters)"
|
||||||
|
)
|
||||||
|
encryption_key: str = Field(
|
||||||
|
description="AES-256 encryption key (32 bytes)"
|
||||||
|
)
|
||||||
|
jwt_expiration_hours: int = Field(
|
||||||
|
default=24,
|
||||||
|
description="JWT token expiration in hours"
|
||||||
|
)
|
||||||
|
|
||||||
|
# OpenRouter Integration
|
||||||
|
openrouter_api_url: str = Field(
|
||||||
|
default="https://openrouter.ai/api/v1",
|
||||||
|
description="OpenRouter API base URL"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Task scheduling
|
||||||
|
sync_interval_minutes: int = Field(
|
||||||
|
default=60,
|
||||||
|
description="Background sync interval in minutes"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Limits
|
||||||
|
max_api_keys_per_user: int = Field(
|
||||||
|
default=10,
|
||||||
|
description="Maximum API keys per user"
|
||||||
|
)
|
||||||
|
rate_limit_requests: int = Field(
|
||||||
|
default=100,
|
||||||
|
description="API rate limit requests"
|
||||||
|
)
|
||||||
|
rate_limit_window: int = Field(
|
||||||
|
default=3600,
|
||||||
|
description="Rate limit window in seconds"
|
||||||
|
)
|
||||||
|
|
||||||
|
# App settings
|
||||||
|
debug: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Debug mode"
|
||||||
|
)
|
||||||
|
log_level: str = Field(
|
||||||
|
default="INFO",
|
||||||
|
description="Logging level"
|
||||||
|
)
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
case_sensitive=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_settings() -> Settings:
|
||||||
|
"""Get cached settings instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Settings: Application settings instance
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> from openrouter_monitor.config import get_settings
|
||||||
|
>>> settings = get_settings()
|
||||||
|
>>> print(settings.database_url)
|
||||||
|
"""
|
||||||
|
return Settings()
|
||||||
|
|||||||
116
tests/unit/test_configuration.py
Normal file
116
tests/unit/test_configuration.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
"""Test for configuration setup (T04)."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Add src to path for importing
|
||||||
|
sys.path.insert(0, '/home/google/Sources/LucaSacchiNet/openrouter-watcher/src')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class TestConfigurationSetup:
|
||||||
|
"""Test configuration files and settings."""
|
||||||
|
|
||||||
|
def test_config_py_exists(self):
|
||||||
|
"""Verify config.py file exists."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
assert os.path.isfile(config_path), f"File {config_path} does not exist"
|
||||||
|
|
||||||
|
def test_env_example_exists(self):
|
||||||
|
"""Verify .env.example file exists."""
|
||||||
|
env_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/.env.example"
|
||||||
|
assert os.path.isfile(env_path), f"File {env_path} does not exist"
|
||||||
|
|
||||||
|
def test_config_py_has_settings_class(self):
|
||||||
|
"""Verify config.py contains Settings class."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'class Settings' in content, "config.py should contain Settings class"
|
||||||
|
|
||||||
|
def test_config_py_has_database_url(self):
|
||||||
|
"""Verify Settings has database_url field."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'database_url' in content.lower(), "Settings should have database_url field"
|
||||||
|
|
||||||
|
def test_config_py_has_secret_key(self):
|
||||||
|
"""Verify Settings has secret_key field."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'secret_key' in content.lower(), "Settings should have secret_key field"
|
||||||
|
|
||||||
|
def test_config_py_has_encryption_key(self):
|
||||||
|
"""Verify Settings has encryption_key field."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'encryption_key' in content.lower(), "Settings should have encryption_key field"
|
||||||
|
|
||||||
|
def test_config_py_uses_pydantic_settings(self):
|
||||||
|
"""Verify config.py uses pydantic_settings."""
|
||||||
|
config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py"
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'BaseSettings' in content or 'pydantic_settings' in content, \
|
||||||
|
"config.py should use pydantic_settings BaseSettings"
|
||||||
|
|
||||||
|
def test_env_example_has_database_url(self):
|
||||||
|
"""Verify .env.example contains DATABASE_URL."""
|
||||||
|
env_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/.env.example"
|
||||||
|
with open(env_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'DATABASE_URL' in content, ".env.example should contain DATABASE_URL"
|
||||||
|
|
||||||
|
def test_env_example_has_secret_key(self):
|
||||||
|
"""Verify .env.example contains SECRET_KEY."""
|
||||||
|
env_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/.env.example"
|
||||||
|
with open(env_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'SECRET_KEY' in content, ".env.example should contain SECRET_KEY"
|
||||||
|
|
||||||
|
def test_env_example_has_encryption_key(self):
|
||||||
|
"""Verify .env.example contains ENCRYPTION_KEY."""
|
||||||
|
env_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/.env.example"
|
||||||
|
with open(env_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'ENCRYPTION_KEY' in content, ".env.example should contain ENCRYPTION_KEY"
|
||||||
|
|
||||||
|
def test_config_can_be_imported(self):
|
||||||
|
"""Verify config module can be imported successfully."""
|
||||||
|
try:
|
||||||
|
from openrouter_monitor.config import Settings, get_settings
|
||||||
|
assert True
|
||||||
|
except Exception as e:
|
||||||
|
pytest.fail(f"Failed to import config module: {e}")
|
||||||
|
|
||||||
|
def test_settings_class_instantiates(self):
|
||||||
|
"""Verify Settings class can be instantiated with test values."""
|
||||||
|
try:
|
||||||
|
from openrouter_monitor.config import Settings
|
||||||
|
# Test with required fields (use snake_case field names)
|
||||||
|
settings = Settings(
|
||||||
|
secret_key="test-secret-key-min-32-chars-long",
|
||||||
|
encryption_key="test-32-byte-encryption-key!!"
|
||||||
|
)
|
||||||
|
assert settings is not None
|
||||||
|
assert hasattr(settings, 'database_url')
|
||||||
|
except Exception as e:
|
||||||
|
pytest.fail(f"Failed to instantiate Settings: {e}")
|
||||||
|
|
||||||
|
def test_settings_has_defaults(self):
|
||||||
|
"""Verify Settings has sensible defaults."""
|
||||||
|
try:
|
||||||
|
from openrouter_monitor.config import Settings
|
||||||
|
settings = Settings(
|
||||||
|
secret_key="test-secret-key-min-32-chars-long",
|
||||||
|
encryption_key="test-32-byte-encryption-key!!"
|
||||||
|
)
|
||||||
|
# Check default values
|
||||||
|
assert settings.database_url is not None
|
||||||
|
assert settings.debug is not None
|
||||||
|
assert settings.log_level is not None
|
||||||
|
except Exception as e:
|
||||||
|
pytest.fail(f"Settings missing defaults: {e}")
|
||||||
Reference in New Issue
Block a user