MogensR commited on
Commit
2a02320
·
1 Parent(s): 4d1eba1

Create config/settings.py

Browse files
Files changed (1) hide show
  1. config/settings.py +364 -0
config/settings.py ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main settings module for BackgroundFX Pro.
3
+
4
+ Automatically loads the appropriate configuration based on environment.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Optional, Dict, Any
10
+ from dataclasses import dataclass, field
11
+ import logging
12
+
13
+ from .base import BaseConfig, config_manager, ConfigurationError
14
+ from .validators import validate_production_config
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class Settings(BaseConfig):
21
+ """
22
+ Main application settings with all configuration options.
23
+ """
24
+
25
+ # API Configuration
26
+ api_version: str = "v1"
27
+ api_prefix: str = "/api"
28
+ api_title: str = "BackgroundFX Pro API"
29
+ api_description: str = "AI-powered background removal and replacement"
30
+ api_docs_enabled: bool = True
31
+
32
+ # Database Configuration
33
+ database_url: str = field(
34
+ default_factory=lambda: os.getenv(
35
+ "DATABASE_URL",
36
+ "postgresql://user:password@localhost/backgroundfx"
37
+ )
38
+ )
39
+ database_pool_size: int = 20
40
+ database_max_overflow: int = 40
41
+ database_echo: bool = False
42
+
43
+ # MongoDB Configuration
44
+ mongodb_url: str = field(
45
+ default_factory=lambda: os.getenv(
46
+ "MONGODB_URL",
47
+ "mongodb://localhost:27017/backgroundfx"
48
+ )
49
+ )
50
+ mongodb_database: str = "backgroundfx"
51
+
52
+ # Redis Configuration
53
+ redis_url: str = field(
54
+ default_factory=lambda: os.getenv(
55
+ "REDIS_URL",
56
+ "redis://localhost:6379/0"
57
+ )
58
+ )
59
+ redis_max_connections: int = 50
60
+ redis_decode_responses: bool = True
61
+
62
+ # Storage Configuration
63
+ storage_backend: str = "s3" # Options: s3, local, gcs, azure
64
+ storage_bucket: str = "backgroundfx-uploads"
65
+ storage_region: str = "us-east-1"
66
+ storage_access_key: str = field(default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID", ""))
67
+ storage_secret_key: str = field(default_factory=lambda: os.getenv("AWS_SECRET_ACCESS_KEY", ""))
68
+ storage_endpoint_url: Optional[str] = None # For S3-compatible services
69
+ storage_use_ssl: bool = True
70
+ local_storage_path: Path = field(default_factory=lambda: Path("storage"))
71
+
72
+ # Processing Configuration
73
+ max_image_size: int = 50 * 1024 * 1024 # 50MB
74
+ max_video_size: int = 500 * 1024 * 1024 # 500MB
75
+ max_batch_size: int = 100
76
+ processing_timeout: int = 300 # seconds
77
+ enable_gpu: bool = True
78
+ gpu_memory_fraction: float = 0.8
79
+
80
+ # Model Configuration
81
+ models_dir: Path = field(default_factory=lambda: Path("models"))
82
+ model_cache_dir: Path = field(default_factory=lambda: Path("/tmp/model_cache"))
83
+ default_model: str = "rembg"
84
+ model_download_timeout: int = 600 # seconds
85
+ model_configs: Dict[str, Dict[str, Any]] = field(default_factory=lambda: {
86
+ "rembg": {
87
+ "path": "models/rembg",
88
+ "version": "1.0.0",
89
+ "gpu_enabled": True,
90
+ "batch_size": 4
91
+ },
92
+ "u2net": {
93
+ "path": "models/u2net",
94
+ "version": "1.0.0",
95
+ "gpu_enabled": True,
96
+ "batch_size": 2
97
+ },
98
+ "sam2": {
99
+ "path": "models/sam2",
100
+ "version": "2.0.0",
101
+ "gpu_enabled": True,
102
+ "batch_size": 1
103
+ }
104
+ })
105
+
106
+ # Queue Configuration
107
+ celery_broker_url: str = field(
108
+ default_factory=lambda: os.getenv(
109
+ "CELERY_BROKER_URL",
110
+ "redis://localhost:6379/1"
111
+ )
112
+ )
113
+ celery_result_backend: str = field(
114
+ default_factory=lambda: os.getenv(
115
+ "CELERY_RESULT_BACKEND",
116
+ "redis://localhost:6379/2"
117
+ )
118
+ )
119
+ celery_task_time_limit: int = 600 # seconds
120
+ celery_task_soft_time_limit: int = 540 # seconds
121
+ celery_worker_concurrency: int = 4
122
+ celery_worker_prefetch_multiplier: int = 1
123
+
124
+ # Authentication Configuration
125
+ jwt_secret_key: str = field(
126
+ default_factory=lambda: os.getenv(
127
+ "JWT_SECRET_KEY",
128
+ "change-me-in-production"
129
+ )
130
+ )
131
+ jwt_algorithm: str = "HS256"
132
+ jwt_expiration_delta: int = 3600 # seconds
133
+ jwt_refresh_expiration_delta: int = 86400 * 7 # 7 days
134
+
135
+ # OAuth Configuration
136
+ oauth_google_client_id: str = field(default_factory=lambda: os.getenv("GOOGLE_CLIENT_ID", ""))
137
+ oauth_google_client_secret: str = field(default_factory=lambda: os.getenv("GOOGLE_CLIENT_SECRET", ""))
138
+ oauth_github_client_id: str = field(default_factory=lambda: os.getenv("GITHUB_CLIENT_ID", ""))
139
+ oauth_github_client_secret: str = field(default_factory=lambda: os.getenv("GITHUB_CLIENT_SECRET", ""))
140
+
141
+ # Email Configuration
142
+ smtp_host: str = field(default_factory=lambda: os.getenv("SMTP_HOST", "smtp.gmail.com"))
143
+ smtp_port: int = 587
144
+ smtp_user: str = field(default_factory=lambda: os.getenv("SMTP_USER", ""))
145
+ smtp_password: str = field(default_factory=lambda: os.getenv("SMTP_PASSWORD", ""))
146
+ smtp_use_tls: bool = True
147
+ email_from: str = "noreply@backgroundfx.pro"
148
+ email_from_name: str = "BackgroundFX Pro"
149
+
150
+ # Monitoring Configuration
151
+ sentry_dsn: str = field(default_factory=lambda: os.getenv("SENTRY_DSN", ""))
152
+ sentry_environment: str = field(default_factory=lambda: os.getenv("ENVIRONMENT", "development"))
153
+ sentry_traces_sample_rate: float = 0.1
154
+
155
+ prometheus_enabled: bool = True
156
+ prometheus_port: int = 9090
157
+
158
+ # Webhook Configuration
159
+ webhook_timeout: int = 30 # seconds
160
+ webhook_max_retries: int = 3
161
+ webhook_retry_delay: int = 5 # seconds
162
+
163
+ # Cache Configuration
164
+ cache_ttl: int = 3600 # seconds
165
+ cache_max_entries: int = 10000
166
+ cache_eviction_policy: str = "lru"
167
+
168
+ # CDN Configuration
169
+ cdn_enabled: bool = True
170
+ cdn_base_url: str = "https://cdn.backgroundfx.pro"
171
+ cdn_cache_control: str = "public, max-age=31536000"
172
+
173
+ # Payment Configuration
174
+ stripe_secret_key: str = field(default_factory=lambda: os.getenv("STRIPE_SECRET_KEY", ""))
175
+ stripe_publishable_key: str = field(default_factory=lambda: os.getenv("STRIPE_PUBLISHABLE_KEY", ""))
176
+ stripe_webhook_secret: str = field(default_factory=lambda: os.getenv("STRIPE_WEBHOOK_SECRET", ""))
177
+
178
+ # Plan Limits
179
+ plan_limits: Dict[str, Dict[str, Any]] = field(default_factory=lambda: {
180
+ "free": {
181
+ "images_per_month": 100,
182
+ "videos_per_month": 5,
183
+ "max_image_size": 10 * 1024 * 1024, # 10MB
184
+ "max_video_size": 50 * 1024 * 1024, # 50MB
185
+ "api_calls_per_hour": 100,
186
+ "storage_gb": 1,
187
+ "quality": "medium"
188
+ },
189
+ "pro": {
190
+ "images_per_month": 5000,
191
+ "videos_per_month": 100,
192
+ "max_image_size": 50 * 1024 * 1024, # 50MB
193
+ "max_video_size": 200 * 1024 * 1024, # 200MB
194
+ "api_calls_per_hour": 1000,
195
+ "storage_gb": 50,
196
+ "quality": "high"
197
+ },
198
+ "enterprise": {
199
+ "images_per_month": -1, # Unlimited
200
+ "videos_per_month": -1, # Unlimited
201
+ "max_image_size": 100 * 1024 * 1024, # 100MB
202
+ "max_video_size": 500 * 1024 * 1024, # 500MB
203
+ "api_calls_per_hour": -1, # Unlimited
204
+ "storage_gb": 500,
205
+ "quality": "ultra"
206
+ }
207
+ })
208
+
209
+ def __post_init__(self):
210
+ """Initialize and validate settings."""
211
+ super().__post_init__()
212
+
213
+ # Add production validator for production environment
214
+ if self.environment == "production":
215
+ self.add_validator(validate_production_config)
216
+
217
+ # Setup model directories
218
+ self.models_dir.mkdir(parents=True, exist_ok=True)
219
+ self.model_cache_dir.mkdir(parents=True, exist_ok=True)
220
+
221
+ # Setup local storage if using local backend
222
+ if self.storage_backend == "local":
223
+ self.local_storage_path.mkdir(parents=True, exist_ok=True)
224
+
225
+ def get_database_config(self) -> Dict[str, Any]:
226
+ """Get database configuration as dictionary."""
227
+ return {
228
+ "url": self.database_url,
229
+ "pool_size": self.database_pool_size,
230
+ "max_overflow": self.database_max_overflow,
231
+ "echo": self.database_echo
232
+ }
233
+
234
+ def get_redis_config(self) -> Dict[str, Any]:
235
+ """Get Redis configuration as dictionary."""
236
+ return {
237
+ "url": self.redis_url,
238
+ "max_connections": self.redis_max_connections,
239
+ "decode_responses": self.redis_decode_responses
240
+ }
241
+
242
+ def get_storage_config(self) -> Dict[str, Any]:
243
+ """Get storage configuration as dictionary."""
244
+ config = {
245
+ "backend": self.storage_backend,
246
+ "bucket": self.storage_bucket,
247
+ "region": self.storage_region,
248
+ "use_ssl": self.storage_use_ssl
249
+ }
250
+
251
+ if self.storage_backend == "s3":
252
+ config.update({
253
+ "access_key": self.storage_access_key,
254
+ "secret_key": self.storage_secret_key,
255
+ "endpoint_url": self.storage_endpoint_url
256
+ })
257
+ elif self.storage_backend == "local":
258
+ config["path"] = str(self.local_storage_path)
259
+
260
+ return config
261
+
262
+ def get_model_config(self, model_name: str) -> Dict[str, Any]:
263
+ """Get configuration for specific model."""
264
+ if model_name not in self.model_configs:
265
+ raise ConfigurationError(f"Model configuration not found: {model_name}")
266
+ return self.model_configs[model_name]
267
+
268
+ def get_plan_limits(self, plan: str) -> Dict[str, Any]:
269
+ """Get limits for specific plan."""
270
+ if plan not in self.plan_limits:
271
+ raise ConfigurationError(f"Plan not found: {plan}")
272
+ return self.plan_limits[plan]
273
+
274
+ def is_feature_enabled(self, feature: str) -> bool:
275
+ """Check if a feature is enabled."""
276
+ feature_map = {
277
+ "video": self.enable_video_processing,
278
+ "batch": self.enable_batch_processing,
279
+ "ai_backgrounds": self.enable_ai_backgrounds,
280
+ "webhooks": self.enable_webhooks,
281
+ "gpu": self.enable_gpu
282
+ }
283
+ return feature_map.get(feature, False)
284
+
285
+
286
+ def load_settings(environment: Optional[str] = None) -> Settings:
287
+ """
288
+ Load settings for specified environment.
289
+
290
+ Args:
291
+ environment: Environment name (development, staging, production, testing)
292
+ If not specified, uses ENVIRONMENT env var or defaults to development
293
+
294
+ Returns:
295
+ Settings instance configured for the environment
296
+ """
297
+ environment = environment or os.getenv("ENVIRONMENT", "development")
298
+
299
+ # Import environment-specific configuration
300
+ try:
301
+ if environment == "development":
302
+ from .environments.development import DevelopmentSettings
303
+ settings_class = DevelopmentSettings
304
+ elif environment == "staging":
305
+ from .environments.staging import StagingSettings
306
+ settings_class = StagingSettings
307
+ elif environment == "production":
308
+ from .environments.production import ProductionSettings
309
+ settings_class = ProductionSettings
310
+ elif environment == "testing":
311
+ from .environments.testing import TestingSettings
312
+ settings_class = TestingSettings
313
+ else:
314
+ logger.warning(f"Unknown environment: {environment}, using base settings")
315
+ settings_class = Settings
316
+ except ImportError:
317
+ logger.warning(f"Environment module not found: {environment}, using base settings")
318
+ settings_class = Settings
319
+
320
+ # Create settings instance
321
+ settings = settings_class()
322
+
323
+ # Load from environment variables
324
+ from .base import EnvironmentSource
325
+ settings.add_source(EnvironmentSource())
326
+
327
+ # Load from .env file if exists
328
+ env_file = Path(f".env.{environment}")
329
+ if env_file.exists():
330
+ from .base import EnvFileSource
331
+ settings.add_source(EnvFileSource(str(env_file)))
332
+ elif Path(".env").exists():
333
+ from .base import EnvFileSource
334
+ settings.add_source(EnvFileSource(".env"))
335
+
336
+ # Load from config file if exists
337
+ config_file = Path(f"config/{environment}.yaml")
338
+ if config_file.exists():
339
+ from .base import YAMLFileSource
340
+ settings.add_source(YAMLFileSource(str(config_file)))
341
+
342
+ # Load all sources
343
+ settings.load_from_sources()
344
+
345
+ # Validate
346
+ settings.validate()
347
+
348
+ # Register with manager
349
+ config_manager.register(environment, settings)
350
+ config_manager.set_active(environment)
351
+
352
+ logger.info(f"Loaded settings for environment: {environment}")
353
+
354
+ return settings
355
+
356
+
357
+ # Auto-load settings on import
358
+ settings = load_settings()
359
+
360
+ # Export commonly used settings
361
+ DEBUG = settings.debug
362
+ SECRET_KEY = settings.secret_key
363
+ DATABASE_URL = settings.database_url
364
+ REDIS_URL = settings.redis_url