Merge remote changes with local config improvements

- Merged remote .env.example updates
- Kept local graceful config error handling
- Kept local SMTP username/password variables
This commit is contained in:
Oli Passey
2025-06-30 21:36:00 +01:00
2 changed files with 140 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# Price Tracker Environment Variables
# Copy this file to .env and uncomment/modify the variables you want to override
# ================================
# Database Configuration
# ================================
# Path to the SQLite database file
DATABASE_PATH=/app/data/price_tracker.db
# ================================
# Scraping Configuration
# ================================
# Delay between requests in seconds (default: 2)
DELAY_BETWEEN_REQUESTS=2
# Maximum concurrent requests (default: 1)
MAX_CONCURRENT_REQUESTS=1
# Request timeout in seconds (default: 30)
REQUEST_TIMEOUT=30
# Number of retry attempts (default: 3)
RETRY_ATTEMPTS=3
# ================================
# Email Notifications
# ================================
# Enable/disable email notifications (true/false)
EMAIL_ENABLED=false
# SMTP server settings
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
# Email credentials (use app passwords for Gmail)
SENDER_EMAIL=your-email@gmail.com
SENDER_PASSWORD=your-app-password
# Where to send price alerts
RECIPIENT_EMAIL=alerts@yourdomain.com
# ================================
# Webhook Notifications
# ================================
# Enable/disable webhook notifications (true/false)
WEBHOOK_ENABLED=false
# Webhook URL for price alerts
WEBHOOK_URL=https://your-webhook-url.com/notify
# ================================
# Flask Application
# ================================
# Flask environment (development/production)
FLASK_ENV=production
# Python settings
PYTHONDONTWRITEBYTECODE=1
PYTHONUNBUFFERED=1

View File

@@ -273,6 +273,87 @@ class Config:
# Return as string
return value
def _apply_env_overrides(self):
"""Apply environment variable overrides to configuration."""
# Database path override
if os.getenv('DATABASE_PATH'):
if 'database' not in self._config:
self._config['database'] = {}
self._config['database']['path'] = os.getenv('DATABASE_PATH')
# Email notification overrides
email_env_vars = {
'SMTP_SERVER': ['notifications', 'email', 'smtp_server'],
'SMTP_PORT': ['notifications', 'email', 'smtp_port'],
'SENDER_EMAIL': ['notifications', 'email', 'sender_email'],
'SENDER_PASSWORD': ['notifications', 'email', 'sender_password'],
'RECIPIENT_EMAIL': ['notifications', 'email', 'recipient_email'],
'EMAIL_ENABLED': ['notifications', 'email', 'enabled']
}
for env_var, config_path in email_env_vars.items():
env_value = os.getenv(env_var)
if env_value is not None:
self._set_nested_config(config_path, env_value)
# Webhook notification overrides
webhook_env_vars = {
'WEBHOOK_URL': ['notifications', 'webhook', 'url'],
'WEBHOOK_ENABLED': ['notifications', 'webhook', 'enabled']
}
for env_var, config_path in webhook_env_vars.items():
env_value = os.getenv(env_var)
if env_value is not None:
self._set_nested_config(config_path, env_value)
# Scraping configuration overrides
scraping_env_vars = {
'DELAY_BETWEEN_REQUESTS': ['scraping', 'delay_between_requests'],
'MAX_CONCURRENT_REQUESTS': ['scraping', 'max_concurrent_requests'],
'REQUEST_TIMEOUT': ['scraping', 'timeout'],
'RETRY_ATTEMPTS': ['scraping', 'retry_attempts']
}
for env_var, config_path in scraping_env_vars.items():
env_value = os.getenv(env_var)
if env_value is not None:
self._set_nested_config(config_path, env_value)
def _set_nested_config(self, path: list, value: str):
"""Set a nested configuration value from environment variable."""
# Convert string values to appropriate types
converted_value = self._convert_env_value(value)
# Navigate to the correct nested location
current = self._config
for key in path[:-1]:
if key not in current:
current[key] = {}
current = current[key]
current[path[-1]] = converted_value
def _convert_env_value(self, value: str):
"""Convert environment variable string to appropriate type."""
# Handle boolean values
if value.lower() in ('true', 'false'):
return value.lower() == 'true'
# Handle integer values
if value.isdigit():
return int(value)
# Handle float values
try:
if '.' in value:
return float(value)
except ValueError:
pass
# Return as string
return value
@property
def database_path(self) -> str:
"""Get database file path."""