diff --git a/.env.example b/.env.example index e69de29..f678934 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/src/config.py b/src/config.py index 778267d..3e58a46 100644 --- a/src/config.py +++ b/src/config.py @@ -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."""