isa-model 0.4.0__py3-none-any.whl → 0.4.4__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 +35 -80
- 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/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/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/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/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/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.4.dist-info}/METADATA +71 -24
- isa_model-0.4.4.dist-info/RECORD +180 -0
- isa_model/core/security/secrets.py +0 -358
- isa_model/core/storage/hf_storage.py +0 -419
- 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.4.dist-info}/WHEEL +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/top_level.txt +0 -0
@@ -8,13 +8,31 @@ Provides a singleton Supabase client instance that:
|
|
8
8
|
"""
|
9
9
|
|
10
10
|
import logging
|
11
|
+
import os
|
11
12
|
from typing import Optional
|
13
|
+
from urllib.parse import urlparse
|
12
14
|
from supabase import create_client, Client
|
15
|
+
from supabase.lib.client_options import ClientOptions
|
16
|
+
from dotenv import load_dotenv
|
17
|
+
from functools import wraps
|
18
|
+
import httpx
|
13
19
|
|
14
20
|
from ..config.config_manager import ConfigManager
|
15
21
|
|
16
22
|
logger = logging.getLogger(__name__)
|
17
23
|
|
24
|
+
def require_client(default_return=None):
|
25
|
+
"""Decorator to check if client is available before executing method"""
|
26
|
+
def decorator(func):
|
27
|
+
@wraps(func)
|
28
|
+
def wrapper(self, *args, **kwargs):
|
29
|
+
if self._client is None:
|
30
|
+
logger.error(f"Error in {func.__name__}: Supabase client not available")
|
31
|
+
return default_return
|
32
|
+
return func(self, *args, **kwargs)
|
33
|
+
return wrapper
|
34
|
+
return decorator
|
35
|
+
|
18
36
|
class SupabaseClient:
|
19
37
|
"""Singleton Supabase client with environment-aware configuration"""
|
20
38
|
|
@@ -32,53 +50,144 @@ class SupabaseClient:
|
|
32
50
|
self._initialize_client()
|
33
51
|
SupabaseClient._initialized = True
|
34
52
|
|
53
|
+
def _configure_proxy_bypass(self):
|
54
|
+
"""Configure proxy bypass for Supabase connections"""
|
55
|
+
# Check if URL is localhost or Docker internal
|
56
|
+
if self.url:
|
57
|
+
parsed_url = urlparse(self.url)
|
58
|
+
hostname = parsed_url.hostname or ''
|
59
|
+
|
60
|
+
# Check if it's a local connection
|
61
|
+
is_local = hostname in ['localhost', '127.0.0.1', '0.0.0.0', 'host.docker.internal']
|
62
|
+
is_docker = hostname.startswith('172.') or hostname.startswith('192.168.')
|
63
|
+
|
64
|
+
if is_local or is_docker:
|
65
|
+
logger.info(f"Detected local Supabase at {hostname}, configuring proxy bypass...")
|
66
|
+
|
67
|
+
# Get current proxy settings
|
68
|
+
http_proxy = os.environ.get('HTTP_PROXY') or os.environ.get('http_proxy')
|
69
|
+
https_proxy = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy')
|
70
|
+
|
71
|
+
if http_proxy or https_proxy:
|
72
|
+
# Temporarily disable proxy for this connection
|
73
|
+
no_proxy = os.environ.get('NO_PROXY', '') or os.environ.get('no_proxy', '')
|
74
|
+
|
75
|
+
# Add local addresses to no_proxy
|
76
|
+
local_addresses = [
|
77
|
+
'localhost', '127.0.0.1', '0.0.0.0',
|
78
|
+
'host.docker.internal', '*.local',
|
79
|
+
'172.*', '192.168.*', '10.*' # Common Docker/local network ranges
|
80
|
+
]
|
81
|
+
|
82
|
+
no_proxy_list = no_proxy.split(',') if no_proxy else []
|
83
|
+
for addr in local_addresses:
|
84
|
+
if addr not in no_proxy_list:
|
85
|
+
no_proxy_list.append(addr)
|
86
|
+
|
87
|
+
# Also add the specific hostname if it's not already there
|
88
|
+
if hostname and hostname not in no_proxy_list:
|
89
|
+
no_proxy_list.append(hostname)
|
90
|
+
|
91
|
+
# Update environment variables
|
92
|
+
updated_no_proxy = ','.join(no_proxy_list)
|
93
|
+
os.environ['NO_PROXY'] = updated_no_proxy
|
94
|
+
os.environ['no_proxy'] = updated_no_proxy
|
95
|
+
|
96
|
+
logger.info(f"Updated NO_PROXY to: {updated_no_proxy}")
|
97
|
+
|
98
|
+
# For httpx-based connections, we might need to unset proxy for local connections
|
99
|
+
if is_local or is_docker:
|
100
|
+
# Store original values to restore later if needed
|
101
|
+
self._original_http_proxy = os.environ.pop('HTTP_PROXY', None)
|
102
|
+
self._original_https_proxy = os.environ.pop('HTTPS_PROXY', None)
|
103
|
+
os.environ.pop('http_proxy', None)
|
104
|
+
os.environ.pop('https_proxy', None)
|
105
|
+
logger.info("Temporarily disabled HTTP/HTTPS proxy for local Supabase connection")
|
106
|
+
|
35
107
|
def _initialize_client(self):
|
36
|
-
"""Initialize the Supabase client with
|
108
|
+
"""Initialize the Supabase client with flexible environment handling"""
|
37
109
|
try:
|
38
|
-
#
|
39
|
-
|
40
|
-
|
110
|
+
# Load environment variables with fallback strategy
|
111
|
+
load_dotenv()
|
112
|
+
|
113
|
+
# Determine environment and load appropriate .env file
|
114
|
+
env = os.getenv("ENVIRONMENT", "development")
|
115
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
116
|
+
project_root = os.path.abspath(os.path.join(current_dir, '../../..'))
|
41
117
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
118
|
+
# Load environment-specific .env file
|
119
|
+
if env == "development":
|
120
|
+
env_file = os.path.join(project_root, "deployment/dev/.env")
|
121
|
+
elif env == "staging":
|
122
|
+
env_file = os.path.join(project_root, "deployment/staging/env/.env.staging")
|
123
|
+
elif env == "production":
|
124
|
+
env_file = os.path.join(project_root, "deployment/production/env/.env.production")
|
125
|
+
else:
|
126
|
+
env_file = os.path.join(project_root, f"deployment/{env}/.env.{env}")
|
127
|
+
|
128
|
+
if os.path.exists(env_file):
|
129
|
+
load_dotenv(env_file)
|
130
|
+
logger.debug(f"Loaded environment from {env_file}")
|
131
|
+
|
132
|
+
# Try multiple environment variable names for flexibility
|
133
|
+
self.url = (
|
134
|
+
os.getenv('SUPABASE_CLOUD_URL') or
|
135
|
+
os.getenv('NEXT_PUBLIC_SUPABASE_URL') or
|
136
|
+
os.getenv('SUPABASE_URL') or
|
137
|
+
os.getenv('SUPABASE_LOCAL_URL')
|
138
|
+
)
|
139
|
+
self.key = (
|
140
|
+
os.getenv('SUPABASE_CLOUD_SERVICE_ROLE_KEY') or
|
141
|
+
os.getenv('SUPABASE_SERVICE_ROLE_KEY') or
|
142
|
+
os.getenv('SUPABASE_LOCAL_SERVICE_ROLE_KEY') or
|
143
|
+
os.getenv('SUPABASE_ANON_KEY') or
|
144
|
+
os.getenv('SUPABASE_LOCAL_ANON_KEY')
|
145
|
+
)
|
146
|
+
|
147
|
+
# Get schema from environment variable (with dev fallback)
|
148
|
+
# Force dev schema for local development
|
149
|
+
self.schema = os.getenv('DB_SCHEMA', 'dev')
|
150
|
+
self.environment = env
|
47
151
|
|
48
152
|
if not self.url or not self.key:
|
49
|
-
|
153
|
+
logger.warning("Missing Supabase credentials. Database operations will not be available.")
|
154
|
+
logger.warning(f"URL found: {bool(self.url)}, Key found: {bool(self.key)}")
|
155
|
+
self._client = None
|
156
|
+
return
|
50
157
|
|
51
158
|
# Create the client
|
52
159
|
self._client = create_client(self.url, self.key)
|
53
160
|
|
54
161
|
logger.info(f"Supabase client initialized for {self.environment} environment (schema: {self.schema})")
|
55
162
|
|
163
|
+
# Skip connection test to avoid unnecessary database queries and error logs
|
164
|
+
# Database operations will fail gracefully when needed
|
165
|
+
logger.debug("Supabase client initialized (connection test skipped)")
|
166
|
+
|
56
167
|
except Exception as e:
|
57
168
|
logger.error(f"Failed to initialize Supabase client: {e}")
|
58
|
-
raise
|
169
|
+
# Don't raise - allow graceful degradation
|
170
|
+
self._client = None
|
59
171
|
|
60
|
-
def get_client(self) -> Client:
|
172
|
+
def get_client(self) -> Optional[Client]:
|
61
173
|
"""Get the Supabase client instance"""
|
62
174
|
if not self._client:
|
63
|
-
|
175
|
+
logger.warning("Supabase client not available - database operations will be skipped")
|
176
|
+
return None
|
64
177
|
return self._client
|
65
178
|
|
179
|
+
@require_client(default_return=None)
|
66
180
|
def table(self, table_name: str):
|
67
181
|
"""Get a table with the correct schema"""
|
68
|
-
if not self._client:
|
69
|
-
raise RuntimeError("Supabase client not initialized")
|
70
|
-
|
71
182
|
# Use the configured schema for the environment
|
72
183
|
if self.schema and self.schema != "public":
|
73
184
|
return self._client.schema(self.schema).table(table_name)
|
74
185
|
else:
|
75
186
|
return self._client.table(table_name)
|
76
187
|
|
188
|
+
@require_client(default_return=None)
|
77
189
|
def rpc(self, function_name: str, params: Optional[dict] = None):
|
78
190
|
"""Call an RPC function with the correct schema"""
|
79
|
-
if not self._client:
|
80
|
-
raise RuntimeError("Supabase client not initialized")
|
81
|
-
|
82
191
|
# RPC functions typically use the public schema
|
83
192
|
# But we can extend this if needed for schema-specific functions
|
84
193
|
return self._client.rpc(function_name, params)
|
@@ -101,6 +210,32 @@ class SupabaseClient:
|
|
101
210
|
except Exception as e:
|
102
211
|
logger.warning(f"Database connection test failed: {e}")
|
103
212
|
return False
|
213
|
+
|
214
|
+
async def execute_sql(self, sql: str) -> dict:
|
215
|
+
"""Execute raw SQL command"""
|
216
|
+
try:
|
217
|
+
result = self._client.rpc('execute_sql', {'sql': sql})
|
218
|
+
return {"success": True, "data": result.data}
|
219
|
+
except Exception as e:
|
220
|
+
logger.error(f"SQL execution failed: {e}")
|
221
|
+
return {"success": False, "error": str(e)}
|
222
|
+
|
223
|
+
async def execute_query(self, query: str, params: tuple = None) -> dict:
|
224
|
+
"""Execute SQL query with parameters"""
|
225
|
+
try:
|
226
|
+
# For now, use rpc to execute queries
|
227
|
+
# In production, you might want to use prepared statements
|
228
|
+
if params:
|
229
|
+
# Simple parameter substitution (not recommended for production)
|
230
|
+
formatted_query = query % params
|
231
|
+
else:
|
232
|
+
formatted_query = query
|
233
|
+
|
234
|
+
result = self._client.rpc('execute_query', {'query': formatted_query})
|
235
|
+
return {"success": True, "data": result.data}
|
236
|
+
except Exception as e:
|
237
|
+
logger.error(f"Query execution failed: {e}")
|
238
|
+
return {"success": False, "error": str(e)}
|
104
239
|
|
105
240
|
# Global singleton instance
|
106
241
|
_supabase_client = None
|
@@ -0,0 +1,316 @@
|
|
1
|
+
"""
|
2
|
+
Dependency management and checking utilities for ISA Model.
|
3
|
+
|
4
|
+
This module provides utilities for checking optional dependencies
|
5
|
+
and providing clear error messages when dependencies are missing.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import importlib.util
|
9
|
+
import logging
|
10
|
+
from typing import Dict, List, Optional, Tuple
|
11
|
+
from functools import wraps
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class DependencyChecker:
|
17
|
+
"""Utility class for checking and managing optional dependencies."""
|
18
|
+
|
19
|
+
# Cache for dependency availability
|
20
|
+
_cache: Dict[str, bool] = {}
|
21
|
+
|
22
|
+
# Dependency groups with their packages
|
23
|
+
DEPENDENCY_GROUPS = {
|
24
|
+
# LLM Services
|
25
|
+
"openai": ["openai"],
|
26
|
+
"cerebras": ["cerebras.cloud.sdk"],
|
27
|
+
"local_llm": ["torch", "transformers", "accelerate"],
|
28
|
+
"vllm": ["vllm"],
|
29
|
+
|
30
|
+
# Vision Services
|
31
|
+
"vision_torch": ["torch", "torchvision", "PIL"],
|
32
|
+
"vision_tf": ["tensorflow", "keras"],
|
33
|
+
"vision_transformers": ["transformers", "PIL"],
|
34
|
+
|
35
|
+
# Audio Services
|
36
|
+
"audio": ["librosa", "soundfile", "numba"],
|
37
|
+
"openai_audio": ["openai"],
|
38
|
+
|
39
|
+
# Image Generation
|
40
|
+
"replicate": ["replicate"],
|
41
|
+
"image_gen": ["PIL", "requests"],
|
42
|
+
|
43
|
+
# Training
|
44
|
+
"training_torch": ["torch", "datasets", "peft", "trl"],
|
45
|
+
"training_tf": ["tensorflow", "keras"],
|
46
|
+
|
47
|
+
# Deployment
|
48
|
+
"modal": ["modal"],
|
49
|
+
"docker": ["docker"],
|
50
|
+
"kubernetes": ["kubernetes"],
|
51
|
+
|
52
|
+
# Storage
|
53
|
+
"s3": ["boto3"],
|
54
|
+
"gcs": ["google.cloud.storage"],
|
55
|
+
"minio": ["minio"],
|
56
|
+
|
57
|
+
# Monitoring
|
58
|
+
"mlflow": ["mlflow"],
|
59
|
+
"wandb": ["wandb"],
|
60
|
+
"influxdb": ["influxdb_client"],
|
61
|
+
"loki": ["python_logging_loki"],
|
62
|
+
}
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def check_dependency(cls, package: str) -> bool:
|
66
|
+
"""
|
67
|
+
Check if a single package is available.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
package: Package name to check (e.g., 'torch', 'openai')
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
True if package is available, False otherwise
|
74
|
+
"""
|
75
|
+
if package in cls._cache:
|
76
|
+
return cls._cache[package]
|
77
|
+
|
78
|
+
spec = importlib.util.find_spec(package.split('.')[0])
|
79
|
+
available = spec is not None
|
80
|
+
cls._cache[package] = available
|
81
|
+
|
82
|
+
if not available:
|
83
|
+
logger.debug(f"Package '{package}' is not available")
|
84
|
+
|
85
|
+
return available
|
86
|
+
|
87
|
+
@classmethod
|
88
|
+
def check_dependencies(cls, packages: List[str]) -> Tuple[bool, List[str]]:
|
89
|
+
"""
|
90
|
+
Check if multiple packages are available.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
packages: List of package names to check
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
Tuple of (all_available, missing_packages)
|
97
|
+
"""
|
98
|
+
missing = []
|
99
|
+
for package in packages:
|
100
|
+
if not cls.check_dependency(package):
|
101
|
+
missing.append(package)
|
102
|
+
|
103
|
+
return len(missing) == 0, missing
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def check_group(cls, group: str) -> Tuple[bool, List[str]]:
|
107
|
+
"""
|
108
|
+
Check if all packages in a dependency group are available.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
group: Name of the dependency group
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Tuple of (all_available, missing_packages)
|
115
|
+
"""
|
116
|
+
if group not in cls.DEPENDENCY_GROUPS:
|
117
|
+
raise ValueError(f"Unknown dependency group: {group}")
|
118
|
+
|
119
|
+
packages = cls.DEPENDENCY_GROUPS[group]
|
120
|
+
return cls.check_dependencies(packages)
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def get_install_command(cls, group: Optional[str] = None, packages: Optional[List[str]] = None) -> str:
|
124
|
+
"""
|
125
|
+
Get the pip install command for missing dependencies.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
group: Dependency group name
|
129
|
+
packages: List of package names
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
Pip install command string
|
133
|
+
"""
|
134
|
+
if group:
|
135
|
+
# Map groups to pyproject.toml extras
|
136
|
+
extras_map = {
|
137
|
+
"openai": "cloud",
|
138
|
+
"cerebras": "cloud",
|
139
|
+
"local_llm": "local",
|
140
|
+
"vision_torch": "vision",
|
141
|
+
"vision_tf": "vision",
|
142
|
+
"audio": "audio",
|
143
|
+
"training_torch": "training",
|
144
|
+
"modal": "cloud",
|
145
|
+
"mlflow": "monitoring",
|
146
|
+
}
|
147
|
+
|
148
|
+
if group in extras_map:
|
149
|
+
return f"pip install 'isa-model[{extras_map[group]}]'"
|
150
|
+
|
151
|
+
if packages:
|
152
|
+
return f"pip install {' '.join(packages)}"
|
153
|
+
|
154
|
+
return "pip install isa-model[all]"
|
155
|
+
|
156
|
+
@classmethod
|
157
|
+
def require_dependencies(cls, packages: List[str] = None, group: str = None,
|
158
|
+
message: str = None):
|
159
|
+
"""
|
160
|
+
Decorator to check dependencies before running a function.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
packages: List of required packages
|
164
|
+
group: Dependency group name
|
165
|
+
message: Custom error message
|
166
|
+
"""
|
167
|
+
def decorator(func):
|
168
|
+
@wraps(func)
|
169
|
+
def wrapper(*args, **kwargs):
|
170
|
+
if group:
|
171
|
+
available, missing = cls.check_group(group)
|
172
|
+
elif packages:
|
173
|
+
available, missing = cls.check_dependencies(packages)
|
174
|
+
else:
|
175
|
+
raise ValueError("Either packages or group must be specified")
|
176
|
+
|
177
|
+
if not available:
|
178
|
+
error_msg = message or f"Missing required dependencies: {', '.join(missing)}"
|
179
|
+
install_cmd = cls.get_install_command(group=group, packages=missing)
|
180
|
+
raise ImportError(f"{error_msg}\nInstall with: {install_cmd}")
|
181
|
+
|
182
|
+
return func(*args, **kwargs)
|
183
|
+
return wrapper
|
184
|
+
return decorator
|
185
|
+
|
186
|
+
|
187
|
+
# Convenience functions for checking common dependencies
|
188
|
+
def is_torch_available() -> bool:
|
189
|
+
"""Check if PyTorch is available."""
|
190
|
+
return DependencyChecker.check_dependency("torch")
|
191
|
+
|
192
|
+
|
193
|
+
def is_tensorflow_available() -> bool:
|
194
|
+
"""Check if TensorFlow is available."""
|
195
|
+
return DependencyChecker.check_dependency("tensorflow")
|
196
|
+
|
197
|
+
|
198
|
+
def is_transformers_available() -> bool:
|
199
|
+
"""Check if Transformers is available."""
|
200
|
+
return DependencyChecker.check_dependency("transformers")
|
201
|
+
|
202
|
+
|
203
|
+
def is_openai_available() -> bool:
|
204
|
+
"""Check if OpenAI SDK is available."""
|
205
|
+
return DependencyChecker.check_dependency("openai")
|
206
|
+
|
207
|
+
|
208
|
+
def is_replicate_available() -> bool:
|
209
|
+
"""Check if Replicate SDK is available."""
|
210
|
+
return DependencyChecker.check_dependency("replicate")
|
211
|
+
|
212
|
+
|
213
|
+
def is_modal_available() -> bool:
|
214
|
+
"""Check if Modal SDK is available."""
|
215
|
+
return DependencyChecker.check_dependency("modal")
|
216
|
+
|
217
|
+
|
218
|
+
def is_cerebras_available() -> bool:
|
219
|
+
"""Check if Cerebras SDK is available."""
|
220
|
+
return DependencyChecker.check_dependency("cerebras")
|
221
|
+
|
222
|
+
|
223
|
+
# Conditional imports with proper error handling
|
224
|
+
def import_torch():
|
225
|
+
"""Import PyTorch with proper error handling."""
|
226
|
+
if not is_torch_available():
|
227
|
+
raise ImportError(
|
228
|
+
"PyTorch is not installed. "
|
229
|
+
"Install with: pip install 'isa-model[local]' or pip install torch"
|
230
|
+
)
|
231
|
+
import torch
|
232
|
+
return torch
|
233
|
+
|
234
|
+
|
235
|
+
def import_transformers():
|
236
|
+
"""Import Transformers with proper error handling."""
|
237
|
+
if not is_transformers_available():
|
238
|
+
raise ImportError(
|
239
|
+
"Transformers is not installed. "
|
240
|
+
"Install with: pip install 'isa-model[local]' or pip install transformers"
|
241
|
+
)
|
242
|
+
import transformers
|
243
|
+
return transformers
|
244
|
+
|
245
|
+
|
246
|
+
def import_openai():
|
247
|
+
"""Import OpenAI with proper error handling."""
|
248
|
+
if not is_openai_available():
|
249
|
+
raise ImportError(
|
250
|
+
"OpenAI SDK is not installed. "
|
251
|
+
"Install with: pip install 'isa-model[cloud]' or pip install openai"
|
252
|
+
)
|
253
|
+
import openai
|
254
|
+
return openai
|
255
|
+
|
256
|
+
|
257
|
+
def import_replicate():
|
258
|
+
"""Import Replicate with proper error handling."""
|
259
|
+
if not is_replicate_available():
|
260
|
+
raise ImportError(
|
261
|
+
"Replicate SDK is not installed. "
|
262
|
+
"Install with: pip install 'isa-model[cloud]' or pip install replicate"
|
263
|
+
)
|
264
|
+
import replicate
|
265
|
+
return replicate
|
266
|
+
|
267
|
+
|
268
|
+
def import_cerebras():
|
269
|
+
"""Import Cerebras with proper error handling."""
|
270
|
+
if not is_cerebras_available():
|
271
|
+
raise ImportError(
|
272
|
+
"Cerebras SDK is not installed. "
|
273
|
+
"Install with: pip install cerebras-cloud-sdk"
|
274
|
+
)
|
275
|
+
from cerebras.cloud.sdk import Cerebras
|
276
|
+
return Cerebras
|
277
|
+
|
278
|
+
|
279
|
+
# Lazy loading utilities
|
280
|
+
class LazyImport:
|
281
|
+
"""Lazy import wrapper for optional dependencies."""
|
282
|
+
|
283
|
+
def __init__(self, module_name: str, package_name: str = None,
|
284
|
+
install_hint: str = None):
|
285
|
+
"""
|
286
|
+
Initialize lazy import wrapper.
|
287
|
+
|
288
|
+
Args:
|
289
|
+
module_name: Full module name to import
|
290
|
+
package_name: Package name to check (defaults to module_name)
|
291
|
+
install_hint: Custom installation hint
|
292
|
+
"""
|
293
|
+
self.module_name = module_name
|
294
|
+
self.package_name = package_name or module_name.split('.')[0]
|
295
|
+
self.install_hint = install_hint
|
296
|
+
self._module = None
|
297
|
+
|
298
|
+
def __getattr__(self, name):
|
299
|
+
"""Lazy load the module when accessed."""
|
300
|
+
if self._module is None:
|
301
|
+
if not DependencyChecker.check_dependency(self.package_name):
|
302
|
+
hint = self.install_hint or f"pip install {self.package_name}"
|
303
|
+
raise ImportError(
|
304
|
+
f"{self.module_name} is not installed. Install with: {hint}"
|
305
|
+
)
|
306
|
+
|
307
|
+
import importlib
|
308
|
+
self._module = importlib.import_module(self.module_name)
|
309
|
+
|
310
|
+
return getattr(self._module, name)
|
311
|
+
|
312
|
+
def __call__(self, *args, **kwargs):
|
313
|
+
"""Support direct calling for classes."""
|
314
|
+
if self._module is None:
|
315
|
+
self.__getattr__('__call__') # Trigger lazy loading
|
316
|
+
return self._module(*args, **kwargs)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"""
|
2
|
+
Service Discovery Module for ISA Model Core
|
3
|
+
|
4
|
+
Provides service discovery capabilities using Consul for dynamic service resolution.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .consul_discovery import (
|
8
|
+
ConsulServiceDiscovery,
|
9
|
+
get_consul_discovery,
|
10
|
+
discover_service,
|
11
|
+
resolve_url
|
12
|
+
)
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"ConsulServiceDiscovery",
|
16
|
+
"get_consul_discovery",
|
17
|
+
"discover_service",
|
18
|
+
"resolve_url"
|
19
|
+
]
|