mcli-framework 7.12.2__py3-none-any.whl → 7.12.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/__init__.py +0 -2
- mcli/app/commands_cmd.py +30 -26
- mcli/app/completion_helpers.py +5 -5
- mcli/app/init_cmd.py +10 -10
- mcli/app/lock_cmd.py +29 -24
- mcli/app/main.py +2 -8
- mcli/app/model/model.py +5 -10
- mcli/app/store_cmd.py +8 -8
- mcli/app/video/__init__.py +0 -2
- mcli/app/video/video.py +1 -14
- mcli/chat/chat.py +90 -108
- mcli/chat/command_rag.py +0 -4
- mcli/chat/enhanced_chat.py +32 -41
- mcli/chat/system_controller.py +37 -37
- mcli/chat/system_integration.py +4 -5
- mcli/cli.py +2 -3
- mcli/lib/api/api.py +4 -9
- mcli/lib/api/daemon_client.py +19 -20
- mcli/lib/api/daemon_client_local.py +1 -3
- mcli/lib/api/daemon_decorator.py +6 -6
- mcli/lib/api/mcli_decorators.py +4 -8
- mcli/lib/auth/__init__.py +0 -1
- mcli/lib/auth/auth.py +4 -5
- mcli/lib/auth/mcli_manager.py +7 -12
- mcli/lib/auth/token_util.py +5 -5
- mcli/lib/config/__init__.py +29 -1
- mcli/lib/config/config.py +0 -1
- mcli/lib/custom_commands.py +1 -1
- mcli/lib/discovery/command_discovery.py +15 -15
- mcli/lib/erd/erd.py +7 -7
- mcli/lib/files/files.py +1 -1
- mcli/lib/fs/__init__.py +31 -1
- mcli/lib/fs/fs.py +12 -13
- mcli/lib/lib.py +0 -1
- mcli/lib/logger/logger.py +7 -10
- mcli/lib/performance/optimizer.py +25 -27
- mcli/lib/performance/rust_bridge.py +22 -27
- mcli/lib/performance/uvloop_config.py +0 -1
- mcli/lib/pickles/__init__.py +0 -1
- mcli/lib/pickles/pickles.py +0 -2
- mcli/lib/secrets/commands.py +0 -2
- mcli/lib/secrets/manager.py +0 -1
- mcli/lib/secrets/repl.py +2 -3
- mcli/lib/secrets/store.py +1 -2
- mcli/lib/services/data_pipeline.py +34 -34
- mcli/lib/services/lsh_client.py +38 -40
- mcli/lib/shell/shell.py +2 -2
- mcli/lib/toml/__init__.py +0 -1
- mcli/lib/ui/styling.py +0 -1
- mcli/lib/ui/visual_effects.py +33 -41
- mcli/lib/watcher/watcher.py +0 -1
- mcli/ml/__init__.py +1 -1
- mcli/ml/api/__init__.py +1 -1
- mcli/ml/api/app.py +8 -9
- mcli/ml/api/middleware.py +10 -10
- mcli/ml/api/routers/__init__.py +1 -1
- mcli/ml/api/routers/admin_router.py +3 -3
- mcli/ml/api/routers/auth_router.py +17 -18
- mcli/ml/api/routers/backtest_router.py +2 -2
- mcli/ml/api/routers/data_router.py +2 -2
- mcli/ml/api/routers/model_router.py +14 -15
- mcli/ml/api/routers/monitoring_router.py +2 -2
- mcli/ml/api/routers/portfolio_router.py +2 -2
- mcli/ml/api/routers/prediction_router.py +10 -9
- mcli/ml/api/routers/trade_router.py +2 -2
- mcli/ml/api/routers/websocket_router.py +6 -7
- mcli/ml/api/schemas.py +2 -2
- mcli/ml/auth/__init__.py +1 -1
- mcli/ml/auth/auth_manager.py +22 -23
- mcli/ml/auth/models.py +17 -17
- mcli/ml/auth/permissions.py +17 -17
- mcli/ml/backtesting/__init__.py +1 -1
- mcli/ml/backtesting/backtest_engine.py +31 -35
- mcli/ml/backtesting/performance_metrics.py +12 -14
- mcli/ml/backtesting/run.py +1 -2
- mcli/ml/cache.py +35 -36
- mcli/ml/cli/__init__.py +1 -1
- mcli/ml/cli/main.py +21 -24
- mcli/ml/config/__init__.py +1 -1
- mcli/ml/config/settings.py +28 -29
- mcli/ml/configs/__init__.py +1 -1
- mcli/ml/configs/dvc_config.py +14 -15
- mcli/ml/configs/mlflow_config.py +12 -13
- mcli/ml/configs/mlops_manager.py +19 -21
- mcli/ml/dashboard/__init__.py +4 -4
- mcli/ml/dashboard/app.py +20 -30
- mcli/ml/dashboard/app_supabase.py +16 -19
- mcli/ml/dashboard/app_training.py +11 -14
- mcli/ml/dashboard/cli.py +2 -2
- mcli/ml/dashboard/common.py +2 -3
- mcli/ml/dashboard/components/__init__.py +1 -1
- mcli/ml/dashboard/components/charts.py +13 -11
- mcli/ml/dashboard/components/metrics.py +7 -7
- mcli/ml/dashboard/components/tables.py +12 -9
- mcli/ml/dashboard/overview.py +2 -2
- mcli/ml/dashboard/pages/__init__.py +1 -1
- mcli/ml/dashboard/pages/cicd.py +15 -18
- mcli/ml/dashboard/pages/debug_dependencies.py +7 -7
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +11 -18
- mcli/ml/dashboard/pages/predictions_enhanced.py +24 -32
- mcli/ml/dashboard/pages/scrapers_and_logs.py +22 -24
- mcli/ml/dashboard/pages/test_portfolio.py +3 -6
- mcli/ml/dashboard/pages/trading.py +16 -18
- mcli/ml/dashboard/pages/workflows.py +20 -30
- mcli/ml/dashboard/utils.py +9 -9
- mcli/ml/dashboard/warning_suppression.py +3 -3
- mcli/ml/data_ingestion/__init__.py +1 -1
- mcli/ml/data_ingestion/api_connectors.py +41 -46
- mcli/ml/data_ingestion/data_pipeline.py +36 -46
- mcli/ml/data_ingestion/stream_processor.py +43 -46
- mcli/ml/database/__init__.py +1 -1
- mcli/ml/database/migrations/env.py +2 -2
- mcli/ml/database/models.py +22 -24
- mcli/ml/database/session.py +14 -14
- mcli/ml/experimentation/__init__.py +1 -1
- mcli/ml/experimentation/ab_testing.py +45 -46
- mcli/ml/features/__init__.py +1 -1
- mcli/ml/features/ensemble_features.py +22 -27
- mcli/ml/features/recommendation_engine.py +30 -30
- mcli/ml/features/stock_features.py +29 -32
- mcli/ml/features/test_feature_engineering.py +10 -11
- mcli/ml/logging.py +4 -4
- mcli/ml/mlops/__init__.py +1 -1
- mcli/ml/mlops/data_versioning.py +29 -30
- mcli/ml/mlops/experiment_tracker.py +24 -24
- mcli/ml/mlops/model_serving.py +31 -34
- mcli/ml/mlops/pipeline_orchestrator.py +27 -35
- mcli/ml/models/__init__.py +5 -6
- mcli/ml/models/base_models.py +23 -23
- mcli/ml/models/ensemble_models.py +31 -31
- mcli/ml/models/recommendation_models.py +18 -19
- mcli/ml/models/test_models.py +14 -16
- mcli/ml/monitoring/__init__.py +1 -1
- mcli/ml/monitoring/drift_detection.py +32 -36
- mcli/ml/monitoring/metrics.py +2 -2
- mcli/ml/optimization/__init__.py +1 -1
- mcli/ml/optimization/optimize.py +1 -2
- mcli/ml/optimization/portfolio_optimizer.py +30 -32
- mcli/ml/predictions/__init__.py +1 -1
- mcli/ml/preprocessing/__init__.py +1 -1
- mcli/ml/preprocessing/data_cleaners.py +22 -23
- mcli/ml/preprocessing/feature_extractors.py +23 -26
- mcli/ml/preprocessing/ml_pipeline.py +23 -23
- mcli/ml/preprocessing/test_preprocessing.py +7 -8
- mcli/ml/scripts/populate_sample_data.py +0 -4
- mcli/ml/serving/serve.py +1 -2
- mcli/ml/tasks.py +17 -17
- mcli/ml/tests/test_integration.py +29 -30
- mcli/ml/tests/test_training_dashboard.py +21 -21
- mcli/ml/trading/__init__.py +1 -1
- mcli/ml/trading/migrations.py +5 -5
- mcli/ml/trading/models.py +21 -23
- mcli/ml/trading/paper_trading.py +16 -13
- mcli/ml/trading/risk_management.py +17 -18
- mcli/ml/trading/trading_service.py +25 -28
- mcli/ml/training/__init__.py +1 -1
- mcli/ml/training/train.py +0 -1
- mcli/public/oi/oi.py +1 -2
- mcli/self/completion_cmd.py +6 -10
- mcli/self/logs_cmd.py +19 -24
- mcli/self/migrate_cmd.py +22 -20
- mcli/self/redis_cmd.py +10 -11
- mcli/self/self_cmd.py +10 -18
- mcli/self/store_cmd.py +10 -12
- mcli/self/visual_cmd.py +9 -14
- mcli/self/zsh_cmd.py +2 -4
- mcli/workflow/daemon/async_command_database.py +23 -24
- mcli/workflow/daemon/async_process_manager.py +27 -29
- mcli/workflow/daemon/client.py +27 -33
- mcli/workflow/daemon/daemon.py +32 -36
- mcli/workflow/daemon/enhanced_daemon.py +24 -33
- mcli/workflow/daemon/process_cli.py +11 -12
- mcli/workflow/daemon/process_manager.py +23 -26
- mcli/workflow/daemon/test_daemon.py +4 -5
- mcli/workflow/dashboard/dashboard_cmd.py +0 -1
- mcli/workflow/doc_convert.py +15 -17
- mcli/workflow/gcloud/__init__.py +0 -1
- mcli/workflow/gcloud/gcloud.py +11 -8
- mcli/workflow/git_commit/ai_service.py +14 -15
- mcli/workflow/lsh_integration.py +9 -11
- mcli/workflow/model_service/client.py +26 -31
- mcli/workflow/model_service/download_and_run_efficient_models.py +10 -14
- mcli/workflow/model_service/lightweight_embedder.py +25 -35
- mcli/workflow/model_service/lightweight_model_server.py +26 -32
- mcli/workflow/model_service/lightweight_test.py +7 -10
- mcli/workflow/model_service/model_service.py +80 -91
- mcli/workflow/model_service/ollama_efficient_runner.py +14 -18
- mcli/workflow/model_service/openai_adapter.py +23 -23
- mcli/workflow/model_service/pdf_processor.py +21 -26
- mcli/workflow/model_service/test_efficient_runner.py +12 -16
- mcli/workflow/model_service/test_example.py +11 -13
- mcli/workflow/model_service/test_integration.py +3 -5
- mcli/workflow/model_service/test_new_features.py +7 -8
- mcli/workflow/notebook/converter.py +1 -1
- mcli/workflow/notebook/notebook_cmd.py +5 -6
- mcli/workflow/notebook/schema.py +0 -1
- mcli/workflow/notebook/validator.py +7 -3
- mcli/workflow/openai/openai.py +1 -2
- mcli/workflow/registry/registry.py +4 -1
- mcli/workflow/repo/repo.py +6 -7
- mcli/workflow/scheduler/cron_parser.py +16 -19
- mcli/workflow/scheduler/job.py +10 -10
- mcli/workflow/scheduler/monitor.py +15 -15
- mcli/workflow/scheduler/persistence.py +17 -18
- mcli/workflow/scheduler/scheduler.py +37 -38
- mcli/workflow/secrets/__init__.py +1 -1
- mcli/workflow/sync/test_cmd.py +0 -1
- mcli/workflow/wakatime/__init__.py +5 -9
- mcli/workflow/wakatime/wakatime.py +1 -2
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/METADATA +1 -1
- mcli_framework-7.12.4.dist-info/RECORD +279 -0
- mcli_framework-7.12.2.dist-info/RECORD +0 -279
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
"""Model management API routes"""
|
|
1
|
+
"""Model management API routes."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
|
|
8
|
-
from sqlalchemy import select
|
|
9
8
|
from sqlalchemy.orm import Session
|
|
10
9
|
|
|
11
10
|
from mcli.ml.api.schemas import ModelCreate, ModelMetrics, ModelResponse, ModelUpdate
|
|
12
|
-
from mcli.ml.auth import
|
|
11
|
+
from mcli.ml.auth import get_current_active_user, require_role
|
|
13
12
|
from mcli.ml.cache import cached
|
|
14
13
|
from mcli.ml.database.models import Model, ModelStatus, User, UserRole
|
|
15
|
-
from mcli.ml.database.session import
|
|
14
|
+
from mcli.ml.database.session import get_db
|
|
16
15
|
from mcli.ml.tasks import train_model_task
|
|
17
16
|
|
|
18
17
|
router = APIRouter()
|
|
@@ -27,7 +26,7 @@ async def list_models(
|
|
|
27
26
|
current_user: User = Depends(get_current_active_user),
|
|
28
27
|
db: Session = Depends(get_db),
|
|
29
28
|
):
|
|
30
|
-
"""List all available models"""
|
|
29
|
+
"""List all available models."""
|
|
31
30
|
query = db.query(Model)
|
|
32
31
|
|
|
33
32
|
if status:
|
|
@@ -44,7 +43,7 @@ async def get_model(
|
|
|
44
43
|
current_user: User = Depends(get_current_active_user),
|
|
45
44
|
db: Session = Depends(get_db),
|
|
46
45
|
):
|
|
47
|
-
"""Get specific model details"""
|
|
46
|
+
"""Get specific model details."""
|
|
48
47
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
49
48
|
|
|
50
49
|
if not model:
|
|
@@ -59,7 +58,7 @@ async def create_model(
|
|
|
59
58
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
60
59
|
db: Session = Depends(get_db),
|
|
61
60
|
):
|
|
62
|
-
"""Create a new model"""
|
|
61
|
+
"""Create a new model."""
|
|
63
62
|
model = Model(
|
|
64
63
|
**model_data.dict(), created_by=current_user.username, status=ModelStatus.TRAINING
|
|
65
64
|
)
|
|
@@ -81,7 +80,7 @@ async def update_model(
|
|
|
81
80
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
82
81
|
db: Session = Depends(get_db),
|
|
83
82
|
):
|
|
84
|
-
"""Update model metadata"""
|
|
83
|
+
"""Update model metadata."""
|
|
85
84
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
86
85
|
|
|
87
86
|
if not model:
|
|
@@ -104,7 +103,7 @@ async def deploy_model(
|
|
|
104
103
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
105
104
|
db: Session = Depends(get_db),
|
|
106
105
|
):
|
|
107
|
-
"""Deploy model to production"""
|
|
106
|
+
"""Deploy model to production."""
|
|
108
107
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
109
108
|
|
|
110
109
|
if not model:
|
|
@@ -132,7 +131,7 @@ async def archive_model(
|
|
|
132
131
|
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
133
132
|
db: Session = Depends(get_db),
|
|
134
133
|
):
|
|
135
|
-
"""Archive a model"""
|
|
134
|
+
"""Archive a model."""
|
|
136
135
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
137
136
|
|
|
138
137
|
if not model:
|
|
@@ -151,7 +150,7 @@ async def get_model_metrics(
|
|
|
151
150
|
current_user: User = Depends(get_current_active_user),
|
|
152
151
|
db: Session = Depends(get_db),
|
|
153
152
|
):
|
|
154
|
-
"""Get model performance metrics"""
|
|
153
|
+
"""Get model performance metrics."""
|
|
155
154
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
156
155
|
|
|
157
156
|
if not model:
|
|
@@ -176,7 +175,7 @@ async def retrain_model(
|
|
|
176
175
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
177
176
|
db: Session = Depends(get_db),
|
|
178
177
|
):
|
|
179
|
-
"""Retrain an existing model"""
|
|
178
|
+
"""Retrain an existing model."""
|
|
180
179
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
181
180
|
|
|
182
181
|
if not model:
|
|
@@ -202,7 +201,7 @@ async def upload_model_artifact(
|
|
|
202
201
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
203
202
|
db: Session = Depends(get_db),
|
|
204
203
|
):
|
|
205
|
-
"""Upload model artifact file"""
|
|
204
|
+
"""Upload model artifact file."""
|
|
206
205
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
207
206
|
|
|
208
207
|
if not model:
|
|
@@ -224,7 +223,7 @@ async def delete_model(
|
|
|
224
223
|
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
225
224
|
db: Session = Depends(get_db),
|
|
226
225
|
):
|
|
227
|
-
"""Delete a model"""
|
|
226
|
+
"""Delete a model."""
|
|
228
227
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
229
228
|
|
|
230
229
|
if not model:
|
|
@@ -248,7 +247,7 @@ async def download_model(
|
|
|
248
247
|
current_user: User = Depends(get_current_active_user),
|
|
249
248
|
db: Session = Depends(get_db),
|
|
250
249
|
):
|
|
251
|
-
"""Download model artifact"""
|
|
250
|
+
"""Download model artifact."""
|
|
252
251
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
253
252
|
|
|
254
253
|
if not model:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Monitoring API routes"""
|
|
1
|
+
"""Monitoring API routes."""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
4
|
|
|
@@ -10,5 +10,5 @@ router = APIRouter()
|
|
|
10
10
|
|
|
11
11
|
@router.get("/drift")
|
|
12
12
|
async def get_drift_status(current_user: User = Depends(get_current_active_user)):
|
|
13
|
-
"""Get drift monitoring status"""
|
|
13
|
+
"""Get drift monitoring status."""
|
|
14
14
|
return {"drift_detected": False}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Portfolio management API routes"""
|
|
1
|
+
"""Portfolio management API routes."""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
4
|
|
|
@@ -10,5 +10,5 @@ router = APIRouter()
|
|
|
10
10
|
|
|
11
11
|
@router.get("/")
|
|
12
12
|
async def list_portfolios(current_user: User = Depends(get_current_active_user)):
|
|
13
|
-
"""List user portfolios"""
|
|
13
|
+
"""List user portfolios."""
|
|
14
14
|
return {"portfolios": []}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
"""Prediction API routes"""
|
|
1
|
+
"""Prediction API routes."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
4
|
from typing import List, Optional
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
+
import numpy as np
|
|
7
8
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status
|
|
8
9
|
from pydantic import BaseModel
|
|
9
10
|
from sqlalchemy.orm import Session
|
|
10
11
|
|
|
11
|
-
from mcli.ml.api.schemas import BatchPredictionRequest,
|
|
12
|
+
from mcli.ml.api.schemas import BatchPredictionRequest, PredictionResponse
|
|
12
13
|
from mcli.ml.auth import get_current_active_user
|
|
13
14
|
from mcli.ml.cache import cache_set, cached
|
|
14
15
|
from mcli.ml.database.models import Model, Prediction, StockData, User
|
|
@@ -32,7 +33,7 @@ async def create_prediction(
|
|
|
32
33
|
current_user: User = Depends(get_current_active_user),
|
|
33
34
|
db: Session = Depends(get_db),
|
|
34
35
|
):
|
|
35
|
-
"""Create a new prediction"""
|
|
36
|
+
"""Create a new prediction."""
|
|
36
37
|
|
|
37
38
|
# Get model (use default if not specified)
|
|
38
39
|
if request.model_id:
|
|
@@ -107,7 +108,7 @@ async def create_batch_predictions(
|
|
|
107
108
|
current_user: User = Depends(get_current_active_user),
|
|
108
109
|
db: Session = Depends(get_db),
|
|
109
110
|
):
|
|
110
|
-
"""Create predictions for multiple tickers"""
|
|
111
|
+
"""Create predictions for multiple tickers."""
|
|
111
112
|
predictions = []
|
|
112
113
|
|
|
113
114
|
for ticker_data in request.tickers:
|
|
@@ -135,7 +136,7 @@ async def list_predictions(
|
|
|
135
136
|
current_user: User = Depends(get_current_active_user),
|
|
136
137
|
db: Session = Depends(get_db),
|
|
137
138
|
):
|
|
138
|
-
"""List user's predictions"""
|
|
139
|
+
"""List user's predictions."""
|
|
139
140
|
query = db.query(Prediction).filter(Prediction.user_id == current_user.id)
|
|
140
141
|
|
|
141
142
|
if ticker:
|
|
@@ -159,7 +160,7 @@ async def get_prediction(
|
|
|
159
160
|
current_user: User = Depends(get_current_active_user),
|
|
160
161
|
db: Session = Depends(get_db),
|
|
161
162
|
):
|
|
162
|
-
"""Get specific prediction details"""
|
|
163
|
+
"""Get specific prediction details."""
|
|
163
164
|
prediction = (
|
|
164
165
|
db.query(Prediction)
|
|
165
166
|
.filter(Prediction.id == prediction_id, Prediction.user_id == current_user.id)
|
|
@@ -178,7 +179,7 @@ async def get_prediction_outcome(
|
|
|
178
179
|
current_user: User = Depends(get_current_active_user),
|
|
179
180
|
db: Session = Depends(get_db),
|
|
180
181
|
):
|
|
181
|
-
"""Get actual outcome of a prediction"""
|
|
182
|
+
"""Get actual outcome of a prediction."""
|
|
182
183
|
prediction = (
|
|
183
184
|
db.query(Prediction)
|
|
184
185
|
.filter(Prediction.id == prediction_id, Prediction.user_id == current_user.id)
|
|
@@ -221,7 +222,7 @@ async def get_latest_recommendations(
|
|
|
221
222
|
current_user: User = Depends(get_current_active_user),
|
|
222
223
|
db: Session = Depends(get_db),
|
|
223
224
|
):
|
|
224
|
-
"""Get latest stock recommendations"""
|
|
225
|
+
"""Get latest stock recommendations."""
|
|
225
226
|
# Get recent predictions with high confidence
|
|
226
227
|
predictions = (
|
|
227
228
|
db.query(Prediction)
|
|
@@ -254,7 +255,7 @@ async def get_latest_recommendations(
|
|
|
254
255
|
|
|
255
256
|
|
|
256
257
|
async def update_stock_data(ticker: str, db: Session):
|
|
257
|
-
"""Background task to update stock data"""
|
|
258
|
+
"""Background task to update stock data."""
|
|
258
259
|
# In real implementation, fetch latest stock data
|
|
259
260
|
stock = db.query(StockData).filter(StockData.ticker == ticker).first()
|
|
260
261
|
if stock:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Trading API routes"""
|
|
1
|
+
"""Trading API routes."""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
4
|
|
|
@@ -12,5 +12,5 @@ router = APIRouter()
|
|
|
12
12
|
async def get_politician_trades(
|
|
13
13
|
politician_id: str, current_user: User = Depends(get_current_active_user)
|
|
14
14
|
):
|
|
15
|
-
"""Get politician trades"""
|
|
15
|
+
"""Get politician trades."""
|
|
16
16
|
return {"politician_id": politician_id, "trades": []}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
"""WebSocket API routes for real-time updates"""
|
|
1
|
+
"""WebSocket API routes for real-time updates."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import json
|
|
5
4
|
from typing import Dict, Set
|
|
6
5
|
|
|
7
|
-
from fastapi import APIRouter,
|
|
6
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|
8
7
|
|
|
9
8
|
from mcli.ml.logging import get_logger
|
|
10
9
|
|
|
@@ -13,7 +12,7 @@ logger = get_logger(__name__)
|
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class ConnectionManager:
|
|
16
|
-
"""Manage WebSocket connections"""
|
|
15
|
+
"""Manage WebSocket connections."""
|
|
17
16
|
|
|
18
17
|
def __init__(self):
|
|
19
18
|
self.active_connections: Dict[str, Set[WebSocket]] = {}
|
|
@@ -36,7 +35,7 @@ class ConnectionManager:
|
|
|
36
35
|
for connection in self.active_connections[channel]:
|
|
37
36
|
try:
|
|
38
37
|
await connection.send_json(message)
|
|
39
|
-
except:
|
|
38
|
+
except Exception:
|
|
40
39
|
disconnected.add(connection)
|
|
41
40
|
|
|
42
41
|
# Remove disconnected clients
|
|
@@ -49,7 +48,7 @@ manager = ConnectionManager()
|
|
|
49
48
|
|
|
50
49
|
@router.websocket("/predictions")
|
|
51
50
|
async def websocket_predictions(websocket: WebSocket):
|
|
52
|
-
"""Real-time prediction updates"""
|
|
51
|
+
"""Real-time prediction updates."""
|
|
53
52
|
await manager.connect(websocket, "predictions")
|
|
54
53
|
try:
|
|
55
54
|
while True:
|
|
@@ -62,7 +61,7 @@ async def websocket_predictions(websocket: WebSocket):
|
|
|
62
61
|
|
|
63
62
|
@router.websocket("/prices")
|
|
64
63
|
async def websocket_prices(websocket: WebSocket):
|
|
65
|
-
"""Real-time price updates"""
|
|
64
|
+
"""Real-time price updates."""
|
|
66
65
|
await manager.connect(websocket, "prices")
|
|
67
66
|
try:
|
|
68
67
|
while True:
|
mcli/ml/api/schemas.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
"""API request/response schemas"""
|
|
1
|
+
"""API request/response schemas."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel
|
|
7
|
+
from pydantic import BaseModel
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# Model schemas
|
mcli/ml/auth/__init__.py
CHANGED
mcli/ml/auth/auth_manager.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
"""Authentication manager with JWT support"""
|
|
1
|
+
"""Authentication manager with JWT support."""
|
|
2
2
|
|
|
3
3
|
import secrets
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
|
-
from typing import
|
|
6
|
-
from uuid import UUID
|
|
5
|
+
from typing import Optional
|
|
7
6
|
|
|
8
7
|
import bcrypt
|
|
9
8
|
import jwt
|
|
10
9
|
from fastapi import Depends, HTTPException, Request, status
|
|
11
10
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
12
|
-
from sqlalchemy import select
|
|
13
11
|
from sqlalchemy.orm import Session
|
|
14
12
|
|
|
15
13
|
from mcli.ml.config import settings
|
|
@@ -23,7 +21,7 @@ security = HTTPBearer()
|
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
class AuthManager:
|
|
26
|
-
"""Authentication and authorization manager"""
|
|
24
|
+
"""Authentication and authorization manager."""
|
|
27
25
|
|
|
28
26
|
def __init__(self):
|
|
29
27
|
self.secret_key = settings.api.secret_key
|
|
@@ -32,19 +30,19 @@ class AuthManager:
|
|
|
32
30
|
self.refresh_token_expire_days = 7
|
|
33
31
|
|
|
34
32
|
def hash_password(self, password: str) -> str:
|
|
35
|
-
"""Hash a password using bcrypt"""
|
|
33
|
+
"""Hash a password using bcrypt."""
|
|
36
34
|
salt = bcrypt.gensalt()
|
|
37
35
|
hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
|
|
38
36
|
return hashed.decode("utf-8")
|
|
39
37
|
|
|
40
38
|
def verify_password(self, plain_password: str, hashed_password: str) -> bool:
|
|
41
|
-
"""Verify a password against a hash"""
|
|
39
|
+
"""Verify a password against a hash."""
|
|
42
40
|
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
|
|
43
41
|
|
|
44
42
|
def create_access_token(
|
|
45
43
|
self, user_id: str, username: str, role: str, expires_delta: Optional[timedelta] = None
|
|
46
44
|
) -> str:
|
|
47
|
-
"""Create a JWT access token"""
|
|
45
|
+
"""Create a JWT access token."""
|
|
48
46
|
if expires_delta:
|
|
49
47
|
expire = datetime.utcnow() + expires_delta
|
|
50
48
|
else:
|
|
@@ -63,7 +61,7 @@ class AuthManager:
|
|
|
63
61
|
return encoded_jwt
|
|
64
62
|
|
|
65
63
|
def create_refresh_token(self, user_id: str, expires_delta: Optional[timedelta] = None) -> str:
|
|
66
|
-
"""Create a refresh token"""
|
|
64
|
+
"""Create a refresh token."""
|
|
67
65
|
if expires_delta:
|
|
68
66
|
expire = datetime.utcnow() + expires_delta
|
|
69
67
|
else:
|
|
@@ -81,7 +79,7 @@ class AuthManager:
|
|
|
81
79
|
return encoded_jwt
|
|
82
80
|
|
|
83
81
|
def verify_token(self, token: str) -> Optional[TokenData]:
|
|
84
|
-
"""Verify and decode a JWT token"""
|
|
82
|
+
"""Verify and decode a JWT token."""
|
|
85
83
|
try:
|
|
86
84
|
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
|
87
85
|
|
|
@@ -110,7 +108,7 @@ class AuthManager:
|
|
|
110
108
|
)
|
|
111
109
|
|
|
112
110
|
async def register_user(self, user_data: UserCreate, db: Session) -> User:
|
|
113
|
-
"""Register a new user"""
|
|
111
|
+
"""Register a new user."""
|
|
114
112
|
# Check if user already exists
|
|
115
113
|
existing_user = (
|
|
116
114
|
db.query(User)
|
|
@@ -149,7 +147,7 @@ class AuthManager:
|
|
|
149
147
|
return new_user
|
|
150
148
|
|
|
151
149
|
async def authenticate_user(self, login_data: UserLogin, db: Session) -> Optional[User]:
|
|
152
|
-
"""Authenticate a user"""
|
|
150
|
+
"""Authenticate a user."""
|
|
153
151
|
user = db.query(User).filter(User.username == login_data.username).first()
|
|
154
152
|
|
|
155
153
|
if not user:
|
|
@@ -165,7 +163,7 @@ class AuthManager:
|
|
|
165
163
|
return user
|
|
166
164
|
|
|
167
165
|
async def login(self, login_data: UserLogin, db: Session) -> TokenResponse:
|
|
168
|
-
"""Login user and return tokens"""
|
|
166
|
+
"""Login user and return tokens."""
|
|
169
167
|
user = await self.authenticate_user(login_data, db)
|
|
170
168
|
|
|
171
169
|
if not user:
|
|
@@ -195,7 +193,7 @@ class AuthManager:
|
|
|
195
193
|
)
|
|
196
194
|
|
|
197
195
|
async def refresh_access_token(self, refresh_token: str, db: Session) -> TokenResponse:
|
|
198
|
-
"""Refresh access token using refresh token"""
|
|
196
|
+
"""Refresh access token using refresh token."""
|
|
199
197
|
try:
|
|
200
198
|
payload = jwt.decode(refresh_token, self.secret_key, algorithms=[self.algorithm])
|
|
201
199
|
|
|
@@ -238,7 +236,7 @@ class AuthManager:
|
|
|
238
236
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
239
237
|
db: Session = Depends(get_db),
|
|
240
238
|
) -> User:
|
|
241
|
-
"""Get current authenticated user from JWT token"""
|
|
239
|
+
"""Get current authenticated user from JWT token."""
|
|
242
240
|
token = credentials.credentials
|
|
243
241
|
|
|
244
242
|
token_data = self.verify_token(token)
|
|
@@ -256,7 +254,7 @@ class AuthManager:
|
|
|
256
254
|
return user
|
|
257
255
|
|
|
258
256
|
def require_role(self, *allowed_roles: UserRole):
|
|
259
|
-
"""Decorator/dependency to require specific roles"""
|
|
257
|
+
"""Decorator/dependency to require specific roles."""
|
|
260
258
|
|
|
261
259
|
async def role_checker(current_user: User = Depends(self.get_current_user)) -> User:
|
|
262
260
|
if current_user.role not in allowed_roles:
|
|
@@ -281,14 +279,14 @@ require_role = auth_manager.require_role
|
|
|
281
279
|
|
|
282
280
|
|
|
283
281
|
async def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
|
|
284
|
-
"""Get current active user"""
|
|
282
|
+
"""Get current active user."""
|
|
285
283
|
if not current_user.is_active:
|
|
286
284
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user")
|
|
287
285
|
return current_user
|
|
288
286
|
|
|
289
287
|
|
|
290
288
|
async def get_admin_user(current_user: User = Depends(get_current_user)) -> User:
|
|
291
|
-
"""Get current admin user"""
|
|
289
|
+
"""Get current admin user."""
|
|
292
290
|
if current_user.role != UserRole.ADMIN:
|
|
293
291
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
|
|
294
292
|
return current_user
|
|
@@ -298,11 +296,12 @@ import asyncio
|
|
|
298
296
|
|
|
299
297
|
# Rate limiting
|
|
300
298
|
from collections import defaultdict
|
|
301
|
-
|
|
299
|
+
|
|
300
|
+
# datetime and timedelta already imported at top
|
|
302
301
|
|
|
303
302
|
|
|
304
303
|
class RateLimiter:
|
|
305
|
-
"""Simple rate limiter"""
|
|
304
|
+
"""Simple rate limiter."""
|
|
306
305
|
|
|
307
306
|
def __init__(self, requests: int = 100, window: int = 60):
|
|
308
307
|
self.requests = requests
|
|
@@ -311,7 +310,7 @@ class RateLimiter:
|
|
|
311
310
|
self._cleanup_task = None
|
|
312
311
|
|
|
313
312
|
async def check_rate_limit(self, client_id: str) -> bool:
|
|
314
|
-
"""Check if client has exceeded rate limit"""
|
|
313
|
+
"""Check if client has exceeded rate limit."""
|
|
315
314
|
now = datetime.utcnow()
|
|
316
315
|
minute_ago = now - timedelta(seconds=self.window)
|
|
317
316
|
|
|
@@ -329,7 +328,7 @@ class RateLimiter:
|
|
|
329
328
|
return True
|
|
330
329
|
|
|
331
330
|
async def cleanup(self):
|
|
332
|
-
"""Periodic cleanup of old entries"""
|
|
331
|
+
"""Periodic cleanup of old entries."""
|
|
333
332
|
while True:
|
|
334
333
|
await asyncio.sleep(300) # Clean every 5 minutes
|
|
335
334
|
now = datetime.utcnow()
|
|
@@ -349,7 +348,7 @@ rate_limiter = RateLimiter(requests=settings.api.rate_limit, window=60)
|
|
|
349
348
|
|
|
350
349
|
|
|
351
350
|
async def check_rate_limit(request: Request):
|
|
352
|
-
"""FastAPI dependency to check rate limit"""
|
|
351
|
+
"""FastAPI dependency to check rate limit."""
|
|
353
352
|
client_ip = request.client.host
|
|
354
353
|
|
|
355
354
|
if not await rate_limiter.check_rate_limit(client_ip):
|
mcli/ml/auth/models.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Authentication data models"""
|
|
1
|
+
"""Authentication data models."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import List, Optional
|
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, EmailStr, Field, validator
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class UserCreate(BaseModel):
|
|
11
|
-
"""User registration model"""
|
|
11
|
+
"""User registration model."""
|
|
12
12
|
|
|
13
13
|
username: str = Field(..., min_length=3, max_length=50, pattern="^[a-zA-Z0-9_-]+$")
|
|
14
14
|
email: EmailStr
|
|
@@ -18,7 +18,7 @@ class UserCreate(BaseModel):
|
|
|
18
18
|
|
|
19
19
|
@validator("password")
|
|
20
20
|
def validate_password(cls, v):
|
|
21
|
-
"""Ensure password meets security requirements"""
|
|
21
|
+
"""Ensure password meets security requirements."""
|
|
22
22
|
if len(v) < 8:
|
|
23
23
|
raise ValueError("Password must be at least 8 characters long")
|
|
24
24
|
if not any(char.isdigit() for char in v):
|
|
@@ -31,14 +31,14 @@ class UserCreate(BaseModel):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class UserLogin(BaseModel):
|
|
34
|
-
"""User login model"""
|
|
34
|
+
"""User login model."""
|
|
35
35
|
|
|
36
36
|
username: str
|
|
37
37
|
password: str
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class UserResponse(BaseModel):
|
|
41
|
-
"""User response model"""
|
|
41
|
+
"""User response model."""
|
|
42
42
|
|
|
43
43
|
id: UUID
|
|
44
44
|
username: str
|
|
@@ -56,7 +56,7 @@ class UserResponse(BaseModel):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class TokenResponse(BaseModel):
|
|
59
|
-
"""JWT token response"""
|
|
59
|
+
"""JWT token response."""
|
|
60
60
|
|
|
61
61
|
access_token: str
|
|
62
62
|
token_type: str = "Bearer"
|
|
@@ -66,7 +66,7 @@ class TokenResponse(BaseModel):
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
class TokenData(BaseModel):
|
|
69
|
-
"""JWT token payload"""
|
|
69
|
+
"""JWT token payload."""
|
|
70
70
|
|
|
71
71
|
sub: str # User ID
|
|
72
72
|
username: str
|
|
@@ -77,27 +77,27 @@ class TokenData(BaseModel):
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
class PasswordReset(BaseModel):
|
|
80
|
-
"""Password reset request"""
|
|
80
|
+
"""Password reset request."""
|
|
81
81
|
|
|
82
82
|
email: EmailStr
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class PasswordResetConfirm(BaseModel):
|
|
86
|
-
"""Password reset confirmation"""
|
|
86
|
+
"""Password reset confirmation."""
|
|
87
87
|
|
|
88
88
|
token: str
|
|
89
89
|
new_password: str = Field(..., min_length=8, max_length=100)
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
class PasswordChange(BaseModel):
|
|
93
|
-
"""Password change request"""
|
|
93
|
+
"""Password change request."""
|
|
94
94
|
|
|
95
95
|
current_password: str
|
|
96
96
|
new_password: str = Field(..., min_length=8, max_length=100)
|
|
97
97
|
|
|
98
98
|
@validator("new_password")
|
|
99
99
|
def validate_password(cls, v, values):
|
|
100
|
-
"""Ensure new password is different and meets requirements"""
|
|
100
|
+
"""Ensure new password is different and meets requirements."""
|
|
101
101
|
if "current_password" in values and v == values["current_password"]:
|
|
102
102
|
raise ValueError("New password must be different from current password")
|
|
103
103
|
|
|
@@ -113,14 +113,14 @@ class PasswordChange(BaseModel):
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class APIKeyCreate(BaseModel):
|
|
116
|
-
"""API key creation model"""
|
|
116
|
+
"""API key creation model."""
|
|
117
117
|
|
|
118
118
|
name: str = Field(..., min_length=1, max_length=100)
|
|
119
119
|
expires_at: Optional[datetime] = None
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
class APIKeyResponse(BaseModel):
|
|
123
|
-
"""API key response"""
|
|
123
|
+
"""API key response."""
|
|
124
124
|
|
|
125
125
|
key: str
|
|
126
126
|
name: str
|
|
@@ -130,7 +130,7 @@ class APIKeyResponse(BaseModel):
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
class UserUpdate(BaseModel):
|
|
133
|
-
"""User update model"""
|
|
133
|
+
"""User update model."""
|
|
134
134
|
|
|
135
135
|
email: Optional[EmailStr] = None
|
|
136
136
|
first_name: Optional[str] = Field(None, max_length=50)
|
|
@@ -140,7 +140,7 @@ class UserUpdate(BaseModel):
|
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
class UserPermissions(BaseModel):
|
|
143
|
-
"""User permissions model"""
|
|
143
|
+
"""User permissions model."""
|
|
144
144
|
|
|
145
145
|
user_id: UUID
|
|
146
146
|
permissions: List[str]
|
|
@@ -148,7 +148,7 @@ class UserPermissions(BaseModel):
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
class SessionInfo(BaseModel):
|
|
151
|
-
"""Session information"""
|
|
151
|
+
"""Session information."""
|
|
152
152
|
|
|
153
153
|
session_id: str
|
|
154
154
|
user_id: UUID
|
|
@@ -160,7 +160,7 @@ class SessionInfo(BaseModel):
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
class LoginAttempt(BaseModel):
|
|
163
|
-
"""Login attempt tracking"""
|
|
163
|
+
"""Login attempt tracking."""
|
|
164
164
|
|
|
165
165
|
username: str
|
|
166
166
|
ip_address: str
|