mcli-framework 7.0.0__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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Authentication API routes"""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
7
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
8
|
+
from sqlalchemy.orm import Session
|
|
9
|
+
|
|
10
|
+
from mcli.ml.auth import (
|
|
11
|
+
AuthManager,
|
|
12
|
+
UserCreate,
|
|
13
|
+
UserLogin,
|
|
14
|
+
TokenResponse,
|
|
15
|
+
UserResponse,
|
|
16
|
+
PasswordChange,
|
|
17
|
+
PasswordReset,
|
|
18
|
+
get_current_user,
|
|
19
|
+
get_current_active_user,
|
|
20
|
+
check_rate_limit,
|
|
21
|
+
)
|
|
22
|
+
from mcli.ml.database.session import get_db
|
|
23
|
+
from mcli.ml.database.models import User
|
|
24
|
+
|
|
25
|
+
router = APIRouter()
|
|
26
|
+
auth_manager = AuthManager()
|
|
27
|
+
security = HTTPBearer()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@router.post("/register", response_model=UserResponse)
|
|
31
|
+
async def register(
|
|
32
|
+
user_data: UserCreate,
|
|
33
|
+
db: Session = Depends(get_db),
|
|
34
|
+
_: bool = Depends(check_rate_limit)
|
|
35
|
+
):
|
|
36
|
+
"""Register a new user"""
|
|
37
|
+
try:
|
|
38
|
+
user = await auth_manager.register_user(user_data, db)
|
|
39
|
+
return UserResponse.from_orm(user)
|
|
40
|
+
except HTTPException:
|
|
41
|
+
raise
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise HTTPException(
|
|
44
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
45
|
+
detail=str(e)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post("/login", response_model=TokenResponse)
|
|
50
|
+
async def login(
|
|
51
|
+
login_data: UserLogin,
|
|
52
|
+
db: Session = Depends(get_db),
|
|
53
|
+
_: bool = Depends(check_rate_limit)
|
|
54
|
+
):
|
|
55
|
+
"""Login and receive access token"""
|
|
56
|
+
return await auth_manager.login(login_data, db)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@router.post("/refresh", response_model=TokenResponse)
|
|
60
|
+
async def refresh_token(
|
|
61
|
+
refresh_token: str,
|
|
62
|
+
db: Session = Depends(get_db)
|
|
63
|
+
):
|
|
64
|
+
"""Refresh access token using refresh token"""
|
|
65
|
+
return await auth_manager.refresh_access_token(refresh_token, db)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.post("/logout")
|
|
69
|
+
async def logout(
|
|
70
|
+
current_user: User = Depends(get_current_active_user)
|
|
71
|
+
):
|
|
72
|
+
"""Logout current user"""
|
|
73
|
+
# In a real implementation, you might want to blacklist the token
|
|
74
|
+
return {"message": "Successfully logged out"}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.get("/me", response_model=UserResponse)
|
|
78
|
+
async def get_current_user_info(
|
|
79
|
+
current_user: User = Depends(get_current_active_user)
|
|
80
|
+
):
|
|
81
|
+
"""Get current user information"""
|
|
82
|
+
return UserResponse.from_orm(current_user)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@router.put("/me", response_model=UserResponse)
|
|
86
|
+
async def update_current_user(
|
|
87
|
+
updates: dict,
|
|
88
|
+
current_user: User = Depends(get_current_active_user),
|
|
89
|
+
db: Session = Depends(get_db)
|
|
90
|
+
):
|
|
91
|
+
"""Update current user information"""
|
|
92
|
+
# Update allowed fields
|
|
93
|
+
allowed_fields = ["first_name", "last_name", "email"]
|
|
94
|
+
for field in allowed_fields:
|
|
95
|
+
if field in updates:
|
|
96
|
+
setattr(current_user, field, updates[field])
|
|
97
|
+
|
|
98
|
+
db.commit()
|
|
99
|
+
db.refresh(current_user)
|
|
100
|
+
return UserResponse.from_orm(current_user)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@router.post("/change-password")
|
|
104
|
+
async def change_password(
|
|
105
|
+
password_data: PasswordChange,
|
|
106
|
+
current_user: User = Depends(get_current_active_user),
|
|
107
|
+
db: Session = Depends(get_db)
|
|
108
|
+
):
|
|
109
|
+
"""Change current user's password"""
|
|
110
|
+
# Verify current password
|
|
111
|
+
if not auth_manager.verify_password(
|
|
112
|
+
password_data.current_password,
|
|
113
|
+
current_user.password_hash
|
|
114
|
+
):
|
|
115
|
+
raise HTTPException(
|
|
116
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
117
|
+
detail="Current password is incorrect"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Update password
|
|
121
|
+
current_user.password_hash = auth_manager.hash_password(password_data.new_password)
|
|
122
|
+
db.commit()
|
|
123
|
+
|
|
124
|
+
return {"message": "Password changed successfully"}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@router.post("/reset-password")
|
|
128
|
+
async def reset_password_request(
|
|
129
|
+
reset_data: PasswordReset,
|
|
130
|
+
db: Session = Depends(get_db),
|
|
131
|
+
_: bool = Depends(check_rate_limit)
|
|
132
|
+
):
|
|
133
|
+
"""Request password reset"""
|
|
134
|
+
# Find user by email
|
|
135
|
+
user = db.query(User).filter(User.email == reset_data.email).first()
|
|
136
|
+
|
|
137
|
+
if not user:
|
|
138
|
+
# Don't reveal if email exists
|
|
139
|
+
return {"message": "If the email exists, a reset link has been sent"}
|
|
140
|
+
|
|
141
|
+
# In a real implementation, send email with reset token
|
|
142
|
+
# For now, just return success
|
|
143
|
+
return {"message": "If the email exists, a reset link has been sent"}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@router.post("/verify-email/{token}")
|
|
147
|
+
async def verify_email(
|
|
148
|
+
token: str,
|
|
149
|
+
db: Session = Depends(get_db)
|
|
150
|
+
):
|
|
151
|
+
"""Verify email address"""
|
|
152
|
+
# In a real implementation, verify the token and update user
|
|
153
|
+
return {"message": "Email verified successfully"}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@router.get("/sessions")
|
|
157
|
+
async def get_user_sessions(
|
|
158
|
+
current_user: User = Depends(get_current_active_user),
|
|
159
|
+
db: Session = Depends(get_db)
|
|
160
|
+
):
|
|
161
|
+
"""Get all active sessions for current user"""
|
|
162
|
+
# In a real implementation, return active sessions from database
|
|
163
|
+
return {
|
|
164
|
+
"sessions": [
|
|
165
|
+
{
|
|
166
|
+
"id": "session-1",
|
|
167
|
+
"ip_address": "127.0.0.1",
|
|
168
|
+
"user_agent": "Mozilla/5.0",
|
|
169
|
+
"created_at": datetime.utcnow().isoformat(),
|
|
170
|
+
"last_active": datetime.utcnow().isoformat()
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@router.delete("/sessions/{session_id}")
|
|
177
|
+
async def revoke_session(
|
|
178
|
+
session_id: str,
|
|
179
|
+
current_user: User = Depends(get_current_active_user),
|
|
180
|
+
db: Session = Depends(get_db)
|
|
181
|
+
):
|
|
182
|
+
"""Revoke a specific session"""
|
|
183
|
+
# In a real implementation, revoke the session
|
|
184
|
+
return {"message": f"Session {session_id} revoked successfully"}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@router.post("/api-key")
|
|
188
|
+
async def create_api_key(
|
|
189
|
+
name: str,
|
|
190
|
+
expires_in_days: Optional[int] = None,
|
|
191
|
+
current_user: User = Depends(get_current_active_user),
|
|
192
|
+
db: Session = Depends(get_db)
|
|
193
|
+
):
|
|
194
|
+
"""Create a new API key"""
|
|
195
|
+
import secrets
|
|
196
|
+
from datetime import timedelta
|
|
197
|
+
|
|
198
|
+
api_key = secrets.token_urlsafe(32)
|
|
199
|
+
|
|
200
|
+
# Store API key in database (hashed)
|
|
201
|
+
# In real implementation, store this properly
|
|
202
|
+
current_user.api_key = api_key
|
|
203
|
+
current_user.api_key_created_at = datetime.utcnow()
|
|
204
|
+
|
|
205
|
+
db.commit()
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
"api_key": api_key,
|
|
209
|
+
"name": name,
|
|
210
|
+
"created_at": datetime.utcnow().isoformat(),
|
|
211
|
+
"expires_at": (
|
|
212
|
+
datetime.utcnow() + timedelta(days=expires_in_days)
|
|
213
|
+
).isoformat() if expires_in_days else None
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@router.get("/api-keys")
|
|
218
|
+
async def list_api_keys(
|
|
219
|
+
current_user: User = Depends(get_current_active_user)
|
|
220
|
+
):
|
|
221
|
+
"""List all API keys for current user"""
|
|
222
|
+
# In a real implementation, return API keys from database
|
|
223
|
+
return {
|
|
224
|
+
"api_keys": [
|
|
225
|
+
{
|
|
226
|
+
"id": "key-1",
|
|
227
|
+
"name": "Production API",
|
|
228
|
+
"created_at": datetime.utcnow().isoformat(),
|
|
229
|
+
"last_used": datetime.utcnow().isoformat(),
|
|
230
|
+
"expires_at": None
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@router.delete("/api-keys/{key_id}")
|
|
237
|
+
async def revoke_api_key(
|
|
238
|
+
key_id: str,
|
|
239
|
+
current_user: User = Depends(get_current_active_user),
|
|
240
|
+
db: Session = Depends(get_db)
|
|
241
|
+
):
|
|
242
|
+
"""Revoke an API key"""
|
|
243
|
+
# In a real implementation, revoke the API key
|
|
244
|
+
return {"message": f"API key {key_id} revoked successfully"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Backtesting API routes"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from mcli.ml.auth import get_current_active_user
|
|
5
|
+
from mcli.ml.database.models import User
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
@router.post("/run")
|
|
10
|
+
async def run_backtest(current_user: User = Depends(get_current_active_user)):
|
|
11
|
+
"""Run backtest"""
|
|
12
|
+
return {"status": "started"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Data API routes"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from mcli.ml.auth import get_current_active_user
|
|
5
|
+
from mcli.ml.database.models import User
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
@router.get("/stocks/{ticker}")
|
|
10
|
+
async def get_stock_data(ticker: str, current_user: User = Depends(get_current_active_user)):
|
|
11
|
+
"""Get stock data"""
|
|
12
|
+
return {"ticker": ticker, "data": {}}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Model management API routes"""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Query
|
|
8
|
+
from sqlalchemy.orm import Session
|
|
9
|
+
from sqlalchemy import select
|
|
10
|
+
|
|
11
|
+
from mcli.ml.auth import get_current_active_user, require_role, Permission
|
|
12
|
+
from mcli.ml.database.session import get_db, get_async_db
|
|
13
|
+
from mcli.ml.database.models import User, Model, ModelStatus, UserRole
|
|
14
|
+
from mcli.ml.api.schemas import ModelCreate, ModelResponse, ModelUpdate, ModelMetrics
|
|
15
|
+
from mcli.ml.cache import cached
|
|
16
|
+
from mcli.ml.tasks import train_model_task
|
|
17
|
+
|
|
18
|
+
router = APIRouter()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@router.get("/", response_model=List[ModelResponse])
|
|
22
|
+
@cached(expire=300)
|
|
23
|
+
async def list_models(
|
|
24
|
+
skip: int = Query(0, ge=0),
|
|
25
|
+
limit: int = Query(100, le=1000),
|
|
26
|
+
status: Optional[ModelStatus] = None,
|
|
27
|
+
current_user: User = Depends(get_current_active_user),
|
|
28
|
+
db: Session = Depends(get_db)
|
|
29
|
+
):
|
|
30
|
+
"""List all available models"""
|
|
31
|
+
query = db.query(Model)
|
|
32
|
+
|
|
33
|
+
if status:
|
|
34
|
+
query = query.filter(Model.status == status)
|
|
35
|
+
|
|
36
|
+
models = query.offset(skip).limit(limit).all()
|
|
37
|
+
return [ModelResponse.from_orm(m) for m in models]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get("/{model_id}", response_model=ModelResponse)
|
|
41
|
+
@cached(expire=60)
|
|
42
|
+
async def get_model(
|
|
43
|
+
model_id: UUID,
|
|
44
|
+
current_user: User = Depends(get_current_active_user),
|
|
45
|
+
db: Session = Depends(get_db)
|
|
46
|
+
):
|
|
47
|
+
"""Get specific model details"""
|
|
48
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
49
|
+
|
|
50
|
+
if not model:
|
|
51
|
+
raise HTTPException(
|
|
52
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
53
|
+
detail="Model not found"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return ModelResponse.from_orm(model)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@router.post("/", response_model=ModelResponse)
|
|
60
|
+
async def create_model(
|
|
61
|
+
model_data: ModelCreate,
|
|
62
|
+
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
63
|
+
db: Session = Depends(get_db)
|
|
64
|
+
):
|
|
65
|
+
"""Create a new model"""
|
|
66
|
+
model = Model(
|
|
67
|
+
**model_data.dict(),
|
|
68
|
+
created_by=current_user.username,
|
|
69
|
+
status=ModelStatus.TRAINING
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
db.add(model)
|
|
73
|
+
db.commit()
|
|
74
|
+
db.refresh(model)
|
|
75
|
+
|
|
76
|
+
# Trigger training task
|
|
77
|
+
train_model_task.delay(str(model.id))
|
|
78
|
+
|
|
79
|
+
return ModelResponse.from_orm(model)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@router.put("/{model_id}", response_model=ModelResponse)
|
|
83
|
+
async def update_model(
|
|
84
|
+
model_id: UUID,
|
|
85
|
+
updates: ModelUpdate,
|
|
86
|
+
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
87
|
+
db: Session = Depends(get_db)
|
|
88
|
+
):
|
|
89
|
+
"""Update model metadata"""
|
|
90
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
91
|
+
|
|
92
|
+
if not model:
|
|
93
|
+
raise HTTPException(
|
|
94
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
95
|
+
detail="Model not found"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for field, value in updates.dict(exclude_unset=True).items():
|
|
99
|
+
setattr(model, field, value)
|
|
100
|
+
|
|
101
|
+
model.updated_at = datetime.utcnow()
|
|
102
|
+
db.commit()
|
|
103
|
+
db.refresh(model)
|
|
104
|
+
|
|
105
|
+
return ModelResponse.from_orm(model)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.post("/{model_id}/deploy")
|
|
109
|
+
async def deploy_model(
|
|
110
|
+
model_id: UUID,
|
|
111
|
+
endpoint: Optional[str] = None,
|
|
112
|
+
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
113
|
+
db: Session = Depends(get_db)
|
|
114
|
+
):
|
|
115
|
+
"""Deploy model to production"""
|
|
116
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
117
|
+
|
|
118
|
+
if not model:
|
|
119
|
+
raise HTTPException(
|
|
120
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
121
|
+
detail="Model not found"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if model.status != ModelStatus.TRAINED:
|
|
125
|
+
raise HTTPException(
|
|
126
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
127
|
+
detail="Model must be trained before deployment"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Deploy model (in real implementation, this would deploy to serving infrastructure)
|
|
131
|
+
model.status = ModelStatus.DEPLOYED
|
|
132
|
+
model.deployed_at = datetime.utcnow()
|
|
133
|
+
model.deployment_endpoint = endpoint or f"https://api.mcli-ml.com/models/{model_id}/predict"
|
|
134
|
+
|
|
135
|
+
db.commit()
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"message": "Model deployed successfully",
|
|
139
|
+
"endpoint": model.deployment_endpoint
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@router.post("/{model_id}/archive")
|
|
144
|
+
async def archive_model(
|
|
145
|
+
model_id: UUID,
|
|
146
|
+
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
147
|
+
db: Session = Depends(get_db)
|
|
148
|
+
):
|
|
149
|
+
"""Archive a model"""
|
|
150
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
151
|
+
|
|
152
|
+
if not model:
|
|
153
|
+
raise HTTPException(
|
|
154
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
155
|
+
detail="Model not found"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
model.status = ModelStatus.ARCHIVED
|
|
159
|
+
db.commit()
|
|
160
|
+
|
|
161
|
+
return {"message": "Model archived successfully"}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@router.get("/{model_id}/metrics", response_model=ModelMetrics)
|
|
165
|
+
@cached(expire=60)
|
|
166
|
+
async def get_model_metrics(
|
|
167
|
+
model_id: UUID,
|
|
168
|
+
current_user: User = Depends(get_current_active_user),
|
|
169
|
+
db: Session = Depends(get_db)
|
|
170
|
+
):
|
|
171
|
+
"""Get model performance metrics"""
|
|
172
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
173
|
+
|
|
174
|
+
if not model:
|
|
175
|
+
raise HTTPException(
|
|
176
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
177
|
+
detail="Model not found"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return ModelMetrics(
|
|
181
|
+
model_id=model.id,
|
|
182
|
+
train_accuracy=model.train_accuracy,
|
|
183
|
+
val_accuracy=model.val_accuracy,
|
|
184
|
+
test_accuracy=model.test_accuracy,
|
|
185
|
+
train_loss=model.train_loss,
|
|
186
|
+
val_loss=model.val_loss,
|
|
187
|
+
test_loss=model.test_loss,
|
|
188
|
+
additional_metrics=model.metrics or {}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@router.post("/{model_id}/retrain")
|
|
193
|
+
async def retrain_model(
|
|
194
|
+
model_id: UUID,
|
|
195
|
+
hyperparameters: Optional[dict] = None,
|
|
196
|
+
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
197
|
+
db: Session = Depends(get_db)
|
|
198
|
+
):
|
|
199
|
+
"""Retrain an existing model"""
|
|
200
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
201
|
+
|
|
202
|
+
if not model:
|
|
203
|
+
raise HTTPException(
|
|
204
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
205
|
+
detail="Model not found"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Update hyperparameters if provided
|
|
209
|
+
if hyperparameters:
|
|
210
|
+
model.hyperparameters = hyperparameters
|
|
211
|
+
|
|
212
|
+
model.status = ModelStatus.TRAINING
|
|
213
|
+
db.commit()
|
|
214
|
+
|
|
215
|
+
# Trigger retraining task
|
|
216
|
+
train_model_task.delay(str(model.id), retrain=True)
|
|
217
|
+
|
|
218
|
+
return {"message": "Model retraining started", "model_id": str(model.id)}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@router.post("/{model_id}/upload")
|
|
222
|
+
async def upload_model_artifact(
|
|
223
|
+
model_id: UUID,
|
|
224
|
+
file: UploadFile = File(...),
|
|
225
|
+
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
226
|
+
db: Session = Depends(get_db)
|
|
227
|
+
):
|
|
228
|
+
"""Upload model artifact file"""
|
|
229
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
230
|
+
|
|
231
|
+
if not model:
|
|
232
|
+
raise HTTPException(
|
|
233
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
234
|
+
detail="Model not found"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Save file (in real implementation, save to S3 or similar)
|
|
238
|
+
file_path = f"/models/{model_id}/{file.filename}"
|
|
239
|
+
|
|
240
|
+
# Update model path
|
|
241
|
+
model.model_path = file_path
|
|
242
|
+
db.commit()
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"message": "Model artifact uploaded successfully",
|
|
246
|
+
"path": file_path
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@router.delete("/{model_id}")
|
|
251
|
+
async def delete_model(
|
|
252
|
+
model_id: UUID,
|
|
253
|
+
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
254
|
+
db: Session = Depends(get_db)
|
|
255
|
+
):
|
|
256
|
+
"""Delete a model"""
|
|
257
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
258
|
+
|
|
259
|
+
if not model:
|
|
260
|
+
raise HTTPException(
|
|
261
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
262
|
+
detail="Model not found"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if model.status == ModelStatus.DEPLOYED:
|
|
266
|
+
raise HTTPException(
|
|
267
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
268
|
+
detail="Cannot delete deployed model. Archive it first."
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
db.delete(model)
|
|
272
|
+
db.commit()
|
|
273
|
+
|
|
274
|
+
return {"message": "Model deleted successfully"}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@router.get("/{model_id}/download")
|
|
278
|
+
async def download_model(
|
|
279
|
+
model_id: UUID,
|
|
280
|
+
current_user: User = Depends(get_current_active_user),
|
|
281
|
+
db: Session = Depends(get_db)
|
|
282
|
+
):
|
|
283
|
+
"""Download model artifact"""
|
|
284
|
+
model = db.query(Model).filter(Model.id == model_id).first()
|
|
285
|
+
|
|
286
|
+
if not model:
|
|
287
|
+
raise HTTPException(
|
|
288
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
289
|
+
detail="Model not found"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if not model.model_path:
|
|
293
|
+
raise HTTPException(
|
|
294
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
295
|
+
detail="Model artifact not available"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# In real implementation, return file from storage
|
|
299
|
+
return {
|
|
300
|
+
"download_url": f"https://storage.mcli-ml.com{model.model_path}",
|
|
301
|
+
"expires_in": 3600
|
|
302
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Monitoring API routes"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from mcli.ml.auth import get_current_active_user
|
|
5
|
+
from mcli.ml.database.models import User
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
@router.get("/drift")
|
|
10
|
+
async def get_drift_status(current_user: User = Depends(get_current_active_user)):
|
|
11
|
+
"""Get drift monitoring status"""
|
|
12
|
+
return {"drift_detected": False}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Portfolio management API routes"""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
from mcli.ml.auth import get_current_active_user
|
|
5
|
+
from mcli.ml.database.models import User
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
@router.get("/")
|
|
10
|
+
async def list_portfolios(current_user: User = Depends(get_current_active_user)):
|
|
11
|
+
"""List user portfolios"""
|
|
12
|
+
return {"portfolios": []}
|