isa-model 0.4.0__py3-none-any.whl → 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isa_model/client.py +466 -43
- isa_model/core/cache/redis_cache.py +12 -3
- isa_model/core/config/config_manager.py +230 -3
- isa_model/core/config.py +90 -0
- isa_model/core/database/direct_db_client.py +114 -0
- isa_model/core/database/migration_manager.py +563 -0
- isa_model/core/database/migrations.py +21 -1
- isa_model/core/database/supabase_client.py +154 -19
- isa_model/core/dependencies.py +316 -0
- isa_model/core/discovery/__init__.py +19 -0
- isa_model/core/discovery/consul_discovery.py +190 -0
- isa_model/core/logging/__init__.py +54 -0
- isa_model/core/logging/influx_logger.py +523 -0
- isa_model/core/logging/loki_logger.py +160 -0
- isa_model/core/models/__init__.py +27 -18
- isa_model/core/models/config_models.py +625 -0
- isa_model/core/models/deployment_billing_tracker.py +430 -0
- isa_model/core/models/model_manager.py +40 -17
- isa_model/core/models/model_metadata.py +690 -0
- isa_model/core/models/model_repo.py +174 -18
- isa_model/core/models/system_models.py +857 -0
- isa_model/core/repositories/__init__.py +9 -0
- isa_model/core/repositories/config_repository.py +912 -0
- isa_model/core/services/intelligent_model_selector.py +399 -21
- isa_model/core/storage/hf_storage.py +1 -1
- isa_model/core/types.py +1 -0
- isa_model/deployment/__init__.py +5 -48
- isa_model/deployment/core/__init__.py +2 -31
- isa_model/deployment/core/deployment_manager.py +1278 -370
- isa_model/deployment/local/__init__.py +31 -0
- isa_model/deployment/local/config.py +248 -0
- isa_model/deployment/local/gpu_gateway.py +607 -0
- isa_model/deployment/local/health_checker.py +428 -0
- isa_model/deployment/local/provider.py +586 -0
- isa_model/deployment/local/tensorrt_service.py +621 -0
- isa_model/deployment/local/transformers_service.py +644 -0
- isa_model/deployment/local/vllm_service.py +527 -0
- isa_model/deployment/modal/__init__.py +8 -0
- isa_model/deployment/modal/config.py +136 -0
- isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
- isa_model/deployment/modal/services/__init__.py +3 -0
- isa_model/deployment/modal/services/audio/__init__.py +1 -0
- isa_model/deployment/modal/services/embedding/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
- isa_model/deployment/modal/services/video/__init__.py +1 -0
- isa_model/deployment/modal/services/vision/__init__.py +1 -0
- isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
- isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
- isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/storage/__init__.py +5 -0
- isa_model/deployment/storage/deployment_repository.py +824 -0
- isa_model/deployment/triton/__init__.py +10 -0
- isa_model/deployment/triton/config.py +196 -0
- isa_model/deployment/triton/configs/__init__.py +1 -0
- isa_model/deployment/triton/provider.py +512 -0
- isa_model/deployment/triton/scripts/__init__.py +1 -0
- isa_model/deployment/triton/templates/__init__.py +1 -0
- isa_model/inference/__init__.py +47 -1
- isa_model/inference/ai_factory.py +137 -10
- isa_model/inference/legacy_services/__init__.py +21 -0
- isa_model/inference/legacy_services/model_evaluation.py +637 -0
- isa_model/inference/legacy_services/model_service.py +573 -0
- isa_model/inference/legacy_services/model_serving.py +717 -0
- isa_model/inference/legacy_services/model_training.py +561 -0
- isa_model/inference/models/__init__.py +21 -0
- isa_model/inference/models/inference_config.py +551 -0
- isa_model/inference/models/inference_record.py +675 -0
- isa_model/inference/models/performance_models.py +714 -0
- isa_model/inference/repositories/__init__.py +9 -0
- isa_model/inference/repositories/inference_repository.py +828 -0
- isa_model/inference/services/audio/base_stt_service.py +184 -11
- isa_model/inference/services/audio/openai_stt_service.py +22 -6
- isa_model/inference/services/custom_model_manager.py +277 -0
- isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
- isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
- isa_model/inference/services/llm/__init__.py +10 -2
- isa_model/inference/services/llm/base_llm_service.py +335 -24
- isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
- isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
- isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
- isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
- isa_model/inference/services/llm/local_llm_service.py +747 -0
- isa_model/inference/services/llm/ollama_llm_service.py +9 -2
- isa_model/inference/services/llm/openai_llm_service.py +33 -16
- isa_model/inference/services/llm/yyds_llm_service.py +8 -2
- isa_model/inference/services/vision/__init__.py +22 -1
- isa_model/inference/services/vision/blip_vision_service.py +359 -0
- isa_model/inference/services/vision/helpers/image_utils.py +8 -5
- isa_model/inference/services/vision/isa_vision_service.py +65 -4
- isa_model/inference/services/vision/openai_vision_service.py +19 -10
- isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
- isa_model/serving/api/cache_manager.py +245 -0
- isa_model/serving/api/dependencies/__init__.py +1 -0
- isa_model/serving/api/dependencies/auth.py +194 -0
- isa_model/serving/api/dependencies/database.py +139 -0
- isa_model/serving/api/error_handlers.py +284 -0
- isa_model/serving/api/fastapi_server.py +172 -22
- isa_model/serving/api/middleware/auth.py +8 -2
- isa_model/serving/api/middleware/security.py +23 -33
- isa_model/serving/api/middleware/tenant_context.py +414 -0
- isa_model/serving/api/routes/analytics.py +4 -1
- isa_model/serving/api/routes/config.py +645 -0
- isa_model/serving/api/routes/deployment_billing.py +315 -0
- isa_model/serving/api/routes/deployments.py +138 -2
- isa_model/serving/api/routes/gpu_gateway.py +440 -0
- isa_model/serving/api/routes/health.py +32 -12
- isa_model/serving/api/routes/inference_monitoring.py +486 -0
- isa_model/serving/api/routes/local_deployments.py +448 -0
- isa_model/serving/api/routes/tenants.py +575 -0
- isa_model/serving/api/routes/unified.py +680 -18
- isa_model/serving/api/routes/webhooks.py +479 -0
- isa_model/serving/api/startup.py +68 -54
- isa_model/utils/gpu_utils.py +311 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/METADATA +66 -24
- isa_model-0.4.3.dist-info/RECORD +193 -0
- isa_model/core/storage/minio_storage.py +0 -0
- isa_model/deployment/cloud/__init__.py +0 -9
- isa_model/deployment/cloud/modal/__init__.py +0 -10
- isa_model/deployment/core/deployment_config.py +0 -356
- isa_model/deployment/core/isa_deployment_service.py +0 -401
- isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
- isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
- isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
- isa_model/deployment/runtime/deployed_service.py +0 -338
- isa_model/deployment/services/__init__.py +0 -9
- isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
- isa_model/deployment/services/model_service.py +0 -332
- isa_model/deployment/services/service_monitor.py +0 -356
- isa_model/deployment/services/service_registry.py +0 -527
- isa_model/eval/__init__.py +0 -92
- isa_model/eval/benchmarks/__init__.py +0 -27
- isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
- isa_model/eval/benchmarks.py +0 -701
- isa_model/eval/config/__init__.py +0 -10
- isa_model/eval/config/evaluation_config.py +0 -108
- isa_model/eval/evaluators/__init__.py +0 -24
- isa_model/eval/evaluators/audio_evaluator.py +0 -727
- isa_model/eval/evaluators/base_evaluator.py +0 -503
- isa_model/eval/evaluators/embedding_evaluator.py +0 -742
- isa_model/eval/evaluators/llm_evaluator.py +0 -472
- isa_model/eval/evaluators/vision_evaluator.py +0 -564
- isa_model/eval/example_evaluation.py +0 -395
- isa_model/eval/factory.py +0 -798
- isa_model/eval/infrastructure/__init__.py +0 -24
- isa_model/eval/infrastructure/experiment_tracker.py +0 -466
- isa_model/eval/isa_benchmarks.py +0 -700
- isa_model/eval/isa_integration.py +0 -582
- isa_model/eval/metrics.py +0 -951
- isa_model/eval/tests/unit/test_basic.py +0 -396
- isa_model/serving/api/routes/evaluations.py +0 -579
- isa_model/training/__init__.py +0 -168
- isa_model/training/annotation/annotation_schema.py +0 -47
- isa_model/training/annotation/processors/annotation_processor.py +0 -126
- isa_model/training/annotation/storage/dataset_manager.py +0 -131
- isa_model/training/annotation/storage/dataset_schema.py +0 -44
- isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
- isa_model/training/annotation/tests/test_minio copy.py +0 -113
- isa_model/training/annotation/tests/test_minio_upload.py +0 -43
- isa_model/training/annotation/views/annotation_controller.py +0 -158
- isa_model/training/cloud/__init__.py +0 -22
- isa_model/training/cloud/job_orchestrator.py +0 -402
- isa_model/training/cloud/runpod_trainer.py +0 -454
- isa_model/training/cloud/storage_manager.py +0 -482
- isa_model/training/core/__init__.py +0 -26
- isa_model/training/core/config.py +0 -181
- isa_model/training/core/dataset.py +0 -222
- isa_model/training/core/trainer.py +0 -720
- isa_model/training/core/utils.py +0 -213
- isa_model/training/examples/intelligent_training_example.py +0 -281
- isa_model/training/factory.py +0 -424
- isa_model/training/intelligent/__init__.py +0 -25
- isa_model/training/intelligent/decision_engine.py +0 -643
- isa_model/training/intelligent/intelligent_factory.py +0 -888
- isa_model/training/intelligent/knowledge_base.py +0 -751
- isa_model/training/intelligent/resource_optimizer.py +0 -839
- isa_model/training/intelligent/task_classifier.py +0 -576
- isa_model/training/storage/__init__.py +0 -24
- isa_model/training/storage/core_integration.py +0 -439
- isa_model/training/storage/training_repository.py +0 -552
- isa_model/training/storage/training_storage.py +0 -628
- isa_model-0.4.0.dist-info/RECORD +0 -182
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
- /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -15,34 +15,29 @@ import logging
|
|
15
15
|
import os
|
16
16
|
from typing import Dict, Any, Optional
|
17
17
|
|
18
|
-
from .routes import health, unified, deployments, logs, analytics, settings, evaluations
|
18
|
+
from .routes import health, unified, deployments, logs, analytics, settings, inference_monitoring, webhooks, tenants # config, training, annotation, and evaluations temporarily disabled
|
19
19
|
from .middleware.request_logger import RequestLoggerMiddleware
|
20
20
|
from .middleware.security import setup_security_middleware, check_redis_health
|
21
|
+
from .middleware.tenant_context import TenantContextMiddleware
|
21
22
|
from .startup import run_startup_initialization
|
23
|
+
from ...core.logging import api_logger, setup_logger
|
22
24
|
|
23
|
-
logger = logging
|
25
|
+
logger = api_logger # Use Loki-configured logger instead of standard logging
|
24
26
|
|
25
27
|
def configure_logging():
|
26
|
-
"""Configure logging based on environment variables
|
28
|
+
"""Configure logging based on environment variables
|
29
|
+
|
30
|
+
Note: Loki integration is handled automatically by isa_model.core.logging.setup_logger
|
31
|
+
This function only sets log levels for existing loggers.
|
32
|
+
"""
|
27
33
|
log_level = os.getenv('LOG_LEVEL', 'INFO').upper()
|
28
34
|
verbose_logging = os.getenv('VERBOSE_LOGGING', 'false').lower() == 'true'
|
29
|
-
|
35
|
+
|
30
36
|
# Set log level
|
31
37
|
level = getattr(logging, log_level, logging.INFO)
|
32
|
-
|
33
|
-
#
|
34
|
-
|
35
|
-
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s'
|
36
|
-
else:
|
37
|
-
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
38
|
-
|
39
|
-
# Configure root logger
|
40
|
-
logging.basicConfig(
|
41
|
-
level=level,
|
42
|
-
format=log_format,
|
43
|
-
datefmt='%Y-%m-%d %H:%M:%S',
|
44
|
-
force=True # Override existing configuration
|
45
|
-
)
|
38
|
+
|
39
|
+
# Note: Don't call logging.basicConfig() here as it conflicts with Loki handlers
|
40
|
+
# The Loki logger (api_logger) is already configured with proper handlers
|
46
41
|
|
47
42
|
# Set uvicorn logger level to match
|
48
43
|
uvicorn_logger = logging.getLogger("uvicorn")
|
@@ -52,6 +47,69 @@ def configure_logging():
|
|
52
47
|
app_logger = logging.getLogger("isa_model")
|
53
48
|
app_logger.setLevel(level)
|
54
49
|
|
50
|
+
# Suppress verbose third-party library logs
|
51
|
+
# HTTP libraries - only show WARNING and above
|
52
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
53
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
54
|
+
logging.getLogger("httpcore.http11").setLevel(logging.WARNING)
|
55
|
+
logging.getLogger("httpcore.connection").setLevel(logging.WARNING)
|
56
|
+
|
57
|
+
# Database and ORM libraries
|
58
|
+
logging.getLogger("supabase").setLevel(logging.WARNING)
|
59
|
+
logging.getLogger("postgrest").setLevel(logging.WARNING)
|
60
|
+
|
61
|
+
# AI/ML libraries
|
62
|
+
logging.getLogger("openai").setLevel(logging.WARNING)
|
63
|
+
logging.getLogger("anthropic").setLevel(logging.WARNING)
|
64
|
+
logging.getLogger("google").setLevel(logging.WARNING)
|
65
|
+
logging.getLogger("google.cloud").setLevel(logging.WARNING)
|
66
|
+
logging.getLogger("google.generativeai").setLevel(logging.WARNING)
|
67
|
+
|
68
|
+
# Other verbose libraries
|
69
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
70
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
71
|
+
logging.getLogger("aiohttp").setLevel(logging.WARNING)
|
72
|
+
|
73
|
+
# Reduce startup debug logs
|
74
|
+
if not verbose_logging:
|
75
|
+
# Reduce startup initialization debug logs
|
76
|
+
startup_logger = logging.getLogger("isa_model.serving.api.startup")
|
77
|
+
startup_logger.setLevel(logging.WARNING)
|
78
|
+
|
79
|
+
# Reduce model registry debug logs
|
80
|
+
model_logger = logging.getLogger("isa_model.core.models.model_repo")
|
81
|
+
model_logger.setLevel(logging.WARNING)
|
82
|
+
|
83
|
+
# Reduce intelligent selector debug logs
|
84
|
+
selector_logger = logging.getLogger("isa_model.core.services.intelligent_model_selector")
|
85
|
+
selector_logger.setLevel(logging.WARNING)
|
86
|
+
|
87
|
+
# Training module removed - logger configuration no longer needed
|
88
|
+
|
89
|
+
# Reduce knowledge base logs
|
90
|
+
kb_logger = logging.getLogger("isa_model.core.knowledge_base")
|
91
|
+
kb_logger.setLevel(logging.WARNING)
|
92
|
+
|
93
|
+
# Reduce database migration logs
|
94
|
+
migration_logger = logging.getLogger("isa_model.core.database.migrations")
|
95
|
+
migration_logger.setLevel(logging.WARNING)
|
96
|
+
|
97
|
+
# Reduce AI factory logs
|
98
|
+
ai_factory_logger = logging.getLogger("isa_model.inference.ai_factory")
|
99
|
+
ai_factory_logger.setLevel(logging.WARNING)
|
100
|
+
|
101
|
+
# Reduce embedding service logs
|
102
|
+
embed_logger = logging.getLogger("isa_model.inference.services.embedding")
|
103
|
+
embed_logger.setLevel(logging.WARNING)
|
104
|
+
|
105
|
+
# Reduce config manager logs
|
106
|
+
config_logger = logging.getLogger("isa_model.core.config")
|
107
|
+
config_logger.setLevel(logging.WARNING)
|
108
|
+
|
109
|
+
# Reduce core integration logs
|
110
|
+
core_logger = logging.getLogger("isa_model.core")
|
111
|
+
core_logger.setLevel(logging.WARNING)
|
112
|
+
|
55
113
|
logger.info(f"Logging configured - Level: {log_level}, Verbose: {verbose_logging}")
|
56
114
|
|
57
115
|
def create_app(config: Dict[str, Any] = None) -> FastAPI:
|
@@ -79,6 +137,9 @@ def create_app(config: Dict[str, Any] = None) -> FastAPI:
|
|
79
137
|
# This includes CORS, rate limiting, security headers, request validation
|
80
138
|
setup_security_middleware(app)
|
81
139
|
|
140
|
+
# Add tenant context middleware (before request logger)
|
141
|
+
app.add_middleware(TenantContextMiddleware)
|
142
|
+
|
82
143
|
# Add custom middleware
|
83
144
|
app.add_middleware(RequestLoggerMiddleware)
|
84
145
|
|
@@ -112,8 +173,26 @@ def create_app(config: Dict[str, Any] = None) -> FastAPI:
|
|
112
173
|
# SETTINGS API - Configuration and API key management
|
113
174
|
app.include_router(settings.router, prefix="/api/v1/settings", tags=["settings"])
|
114
175
|
|
115
|
-
# EVALUATIONS API -
|
116
|
-
app.include_router(evaluations.router, prefix="/api/v1/evaluations", tags=["evaluations"])
|
176
|
+
# EVALUATIONS API - Temporarily disabled for staging optimization
|
177
|
+
# app.include_router(evaluations.router, prefix="/api/v1/evaluations", tags=["evaluations"])
|
178
|
+
|
179
|
+
# INFERENCE MONITORING API - InfluxDB-based inference monitoring and analytics
|
180
|
+
app.include_router(inference_monitoring.router, prefix="/api/v1/monitoring", tags=["monitoring"])
|
181
|
+
|
182
|
+
# TRAINING API - Disabled for staging optimization
|
183
|
+
# app.include_router(training.router, prefix="/api/v1/training", tags=["training"])
|
184
|
+
|
185
|
+
# WEBHOOKS API - Webhook management and notifications
|
186
|
+
app.include_router(webhooks.router, prefix="/api/v1/webhooks", tags=["webhooks"])
|
187
|
+
|
188
|
+
# TENANTS API - Multi-tenancy and organization management
|
189
|
+
app.include_router(tenants.router, prefix="/api/v1/tenants", tags=["tenants"])
|
190
|
+
|
191
|
+
# ANNOTATION API - Temporarily disabled for staging optimization
|
192
|
+
# app.include_router(annotation.router, prefix="/api/v1/annotations", tags=["annotations"])
|
193
|
+
|
194
|
+
# CONFIG API - Configuration management
|
195
|
+
# app.include_router(config.router, prefix="/api/v1/config", tags=["config"]) # Temporarily disabled
|
117
196
|
|
118
197
|
# Mount static files
|
119
198
|
static_path = os.path.join(os.path.dirname(__file__), "../static")
|
@@ -149,7 +228,45 @@ def create_app(config: Dict[str, Any] = None) -> FastAPI:
|
|
149
228
|
logger.info("✅ Application startup completed successfully")
|
150
229
|
except Exception as e:
|
151
230
|
logger.error(f"❌ Application startup failed: {e}")
|
152
|
-
|
231
|
+
logger.error("⚠️ Server will continue but may have reduced functionality")
|
232
|
+
# Store startup failure state for health checks
|
233
|
+
app.state.startup_failed = True
|
234
|
+
app.state.startup_error = str(e)
|
235
|
+
# Continue running to allow debugging and partial functionality
|
236
|
+
|
237
|
+
# Add shutdown event handler
|
238
|
+
@app.on_event("shutdown")
|
239
|
+
async def shutdown_event():
|
240
|
+
logger.info("🧹 Starting application shutdown cleanup...")
|
241
|
+
try:
|
242
|
+
# Close database connections
|
243
|
+
try:
|
244
|
+
from .dependencies.database import close_database_pool
|
245
|
+
await close_database_pool()
|
246
|
+
logger.info("✅ Database connections closed")
|
247
|
+
except Exception as e:
|
248
|
+
logger.error(f"❌ Error closing database connections: {e}")
|
249
|
+
|
250
|
+
# Clean up AI factory and services
|
251
|
+
try:
|
252
|
+
from ...inference.ai_factory import AIFactory
|
253
|
+
factory = AIFactory.get_instance()
|
254
|
+
await factory.cleanup()
|
255
|
+
logger.info("✅ AI Factory cleaned up")
|
256
|
+
except Exception as e:
|
257
|
+
logger.error(f"❌ Error cleaning up AI Factory: {e}")
|
258
|
+
|
259
|
+
# Clean up startup initializer resources
|
260
|
+
try:
|
261
|
+
from .startup import startup_initializer
|
262
|
+
await startup_initializer.cleanup()
|
263
|
+
logger.info("✅ Startup resources cleaned up")
|
264
|
+
except Exception as e:
|
265
|
+
logger.error(f"❌ Error cleaning up startup resources: {e}")
|
266
|
+
|
267
|
+
logger.info("✅ Application shutdown completed successfully")
|
268
|
+
except Exception as e:
|
269
|
+
logger.error(f"❌ Error during application shutdown: {e}")
|
153
270
|
|
154
271
|
return app
|
155
272
|
|
@@ -158,4 +275,37 @@ app = create_app()
|
|
158
275
|
|
159
276
|
if __name__ == "__main__":
|
160
277
|
import uvicorn
|
161
|
-
|
278
|
+
import os
|
279
|
+
import signal
|
280
|
+
|
281
|
+
port = int(os.getenv("PORT", 8082))
|
282
|
+
|
283
|
+
# Configure uvicorn for graceful shutdown
|
284
|
+
config = uvicorn.Config(
|
285
|
+
app,
|
286
|
+
host="0.0.0.0",
|
287
|
+
port=port,
|
288
|
+
log_level=os.getenv("LOG_LEVEL", "info").lower(),
|
289
|
+
access_log=True,
|
290
|
+
loop="asyncio",
|
291
|
+
# Graceful shutdown configuration
|
292
|
+
timeout_keep_alive=30, # Keep connections alive for 30 seconds
|
293
|
+
timeout_graceful_shutdown=30, # 30 second graceful shutdown timeout
|
294
|
+
)
|
295
|
+
|
296
|
+
server = uvicorn.Server(config)
|
297
|
+
|
298
|
+
# Setup signal handlers for graceful shutdown
|
299
|
+
def signal_handler(signum, frame):
|
300
|
+
logger.info(f"Received signal {signum}, initiating graceful shutdown...")
|
301
|
+
server.should_exit = True
|
302
|
+
|
303
|
+
signal.signal(signal.SIGINT, signal_handler)
|
304
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
305
|
+
|
306
|
+
try:
|
307
|
+
server.run()
|
308
|
+
except KeyboardInterrupt:
|
309
|
+
logger.info("Keyboard interrupt received, shutting down...")
|
310
|
+
finally:
|
311
|
+
logger.info("Server shutdown complete")
|
@@ -35,10 +35,16 @@ class APIKeyManager:
|
|
35
35
|
# Load API keys first to check if auth should be enabled
|
36
36
|
self.load_api_keys()
|
37
37
|
|
38
|
-
# Determine auth state:
|
38
|
+
# Determine auth state: check explicit setting first, then auto-detect from keys
|
39
39
|
explicit_auth = AUTH_ENABLED
|
40
40
|
has_keys = len(self.api_keys) > 0
|
41
|
-
|
41
|
+
|
42
|
+
# If explicitly disabled (REQUIRE_API_KEYS=false), respect that setting
|
43
|
+
if os.getenv("REQUIRE_API_KEYS", "").lower() == "false":
|
44
|
+
self.auth_enabled = False
|
45
|
+
else:
|
46
|
+
# Otherwise, enable if explicitly set OR if API keys exist
|
47
|
+
self.auth_enabled = explicit_auth or has_keys
|
42
48
|
|
43
49
|
if self.auth_enabled:
|
44
50
|
logger.info(f"API Key authentication is ENABLED ({'explicit' if explicit_auth else 'auto-detected from keys'})")
|
@@ -13,7 +13,6 @@ import time
|
|
13
13
|
import logging
|
14
14
|
import os
|
15
15
|
import redis
|
16
|
-
import structlog
|
17
16
|
from typing import Dict, Any, Optional, Callable
|
18
17
|
from fastapi import FastAPI, Request, Response, HTTPException, status
|
19
18
|
from fastapi.middleware.cors import CORSMiddleware
|
@@ -25,12 +24,16 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
|
25
24
|
from starlette.responses import JSONResponse
|
26
25
|
import html
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
from ....core.config.config_manager import ConfigManager
|
28
|
+
|
29
|
+
# Configure logging
|
30
|
+
logger = logging.getLogger(__name__)
|
30
31
|
|
31
32
|
# Configuration from environment variables
|
33
|
+
config_manager = ConfigManager()
|
32
34
|
MAX_REQUEST_SIZE = int(os.getenv("MAX_REQUEST_SIZE_MB", "50")) * 1024 * 1024 # 50MB default
|
33
|
-
|
35
|
+
# Use Consul discovery for Redis URL with fallback
|
36
|
+
REDIS_URL = os.getenv("REDIS_URL", config_manager.get_redis_url())
|
34
37
|
ENABLE_RATE_LIMITING = os.getenv("ENABLE_RATE_LIMITING", "true").lower() == "true"
|
35
38
|
RATE_LIMIT_PER_MINUTE = os.getenv("RATE_LIMIT_PER_MINUTE", "100")
|
36
39
|
RATE_LIMIT_PER_HOUR = os.getenv("RATE_LIMIT_PER_HOUR", "1000")
|
@@ -41,7 +44,7 @@ SECURITY_HEADERS = {
|
|
41
44
|
"X-Frame-Options": "DENY",
|
42
45
|
"X-XSS-Protection": "1; mode=block",
|
43
46
|
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
|
44
|
-
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self'",
|
47
|
+
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://fastapi.tiangolo.com data:; connect-src 'self'",
|
45
48
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
46
49
|
"Permissions-Policy": "geolocation=(), microphone=(), camera=()"
|
47
50
|
}
|
@@ -100,7 +103,7 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
100
103
|
return response
|
101
104
|
|
102
105
|
except Exception as e:
|
103
|
-
logger.error("Error in security headers middleware
|
106
|
+
logger.error(f"Error in security headers middleware: {e}")
|
104
107
|
return JSONResponse(
|
105
108
|
status_code=500,
|
106
109
|
content={"error": "Internal server error"},
|
@@ -119,10 +122,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
119
122
|
content_length = request.headers.get("content-length")
|
120
123
|
if content_length and int(content_length) > MAX_REQUEST_SIZE:
|
121
124
|
logger.warning(
|
122
|
-
"Request too large"
|
123
|
-
|
124
|
-
max_size=MAX_REQUEST_SIZE,
|
125
|
-
client_ip=get_remote_address_with_proxy(request)
|
125
|
+
f"Request too large: {content_length} bytes > {MAX_REQUEST_SIZE} bytes "
|
126
|
+
f"from client {get_remote_address_with_proxy(request)}"
|
126
127
|
)
|
127
128
|
raise HTTPException(
|
128
129
|
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
@@ -134,19 +135,15 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
134
135
|
sanitized_query = html.escape(request.url.query)
|
135
136
|
if sanitized_query != request.url.query:
|
136
137
|
logger.warning(
|
137
|
-
"Potentially malicious query parameters detected"
|
138
|
-
|
139
|
-
sanitized=sanitized_query,
|
140
|
-
client_ip=get_remote_address_with_proxy(request)
|
138
|
+
f"Potentially malicious query parameters detected from client {get_remote_address_with_proxy(request)}: "
|
139
|
+
f"'{request.url.query}' -> '{sanitized_query}'"
|
141
140
|
)
|
142
141
|
|
143
142
|
# Log request details for monitoring
|
144
143
|
logger.info(
|
145
|
-
"Request received"
|
146
|
-
|
147
|
-
|
148
|
-
client_ip=get_remote_address_with_proxy(request),
|
149
|
-
user_agent=request.headers.get("user-agent", "unknown")
|
144
|
+
f"Request received: {request.method} {request.url.path} "
|
145
|
+
f"from {get_remote_address_with_proxy(request)} "
|
146
|
+
f"(UA: {request.headers.get('user-agent', 'unknown')})"
|
150
147
|
)
|
151
148
|
|
152
149
|
response = await call_next(request)
|
@@ -154,12 +151,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
154
151
|
# Log response details
|
155
152
|
process_time = time.time() - request.state.start_time
|
156
153
|
logger.info(
|
157
|
-
"Request completed"
|
158
|
-
|
159
|
-
path=request.url.path,
|
160
|
-
status_code=response.status_code,
|
161
|
-
process_time=process_time,
|
162
|
-
client_ip=get_remote_address_with_proxy(request)
|
154
|
+
f"Request completed: {request.method} {request.url.path} -> {response.status_code} "
|
155
|
+
f"in {process_time:.3f}s from {get_remote_address_with_proxy(request)}"
|
163
156
|
)
|
164
157
|
|
165
158
|
return response
|
@@ -168,11 +161,8 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
168
161
|
raise
|
169
162
|
except Exception as e:
|
170
163
|
logger.error(
|
171
|
-
"Error in request validation middleware"
|
172
|
-
|
173
|
-
path=request.url.path,
|
174
|
-
method=request.method,
|
175
|
-
client_ip=get_remote_address_with_proxy(request)
|
164
|
+
f"Error in request validation middleware: {e} "
|
165
|
+
f"({request.method} {request.url.path} from {get_remote_address_with_proxy(request)})"
|
176
166
|
)
|
177
167
|
raise HTTPException(
|
178
168
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
@@ -186,13 +176,13 @@ def setup_security_middleware(app: FastAPI):
|
|
186
176
|
if ENABLE_RATE_LIMITING:
|
187
177
|
app.state.limiter = limiter
|
188
178
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
189
|
-
logger.info("Rate limiting enabled
|
179
|
+
logger.info(f"Rate limiting enabled (Redis backend: {redis_client is not None})")
|
190
180
|
|
191
181
|
# Trusted hosts (production should specify allowed hosts)
|
192
182
|
allowed_hosts = os.getenv("ALLOWED_HOSTS", "*").split(",")
|
193
183
|
if allowed_hosts != ["*"]:
|
194
184
|
app.add_middleware(TrustedHostMiddleware, allowed_hosts=allowed_hosts)
|
195
|
-
logger.info("Trusted hosts middleware enabled
|
185
|
+
logger.info(f"Trusted hosts middleware enabled: {allowed_hosts}")
|
196
186
|
|
197
187
|
# CORS configuration
|
198
188
|
cors_origins = os.getenv("CORS_ORIGINS", "*").split(",")
|
@@ -204,7 +194,7 @@ def setup_security_middleware(app: FastAPI):
|
|
204
194
|
allow_headers=["*"],
|
205
195
|
expose_headers=["X-Process-Time"]
|
206
196
|
)
|
207
|
-
logger.info("CORS middleware enabled
|
197
|
+
logger.info(f"CORS middleware enabled for origins: {cors_origins}")
|
208
198
|
|
209
199
|
# Custom security middleware
|
210
200
|
app.add_middleware(SecurityHeadersMiddleware)
|