jettask 0.2.19__py3-none-any.whl → 0.2.23__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.
- jettask/__init__.py +12 -3
- jettask/cli.py +314 -228
- jettask/config/__init__.py +9 -1
- jettask/config/config.py +245 -0
- jettask/config/env_loader.py +381 -0
- jettask/config/lua_scripts.py +158 -0
- jettask/config/nacos_config.py +132 -5
- jettask/core/__init__.py +1 -1
- jettask/core/app.py +1573 -666
- jettask/core/app_importer.py +33 -16
- jettask/core/container.py +532 -0
- jettask/core/task.py +1 -4
- jettask/core/unified_manager_base.py +2 -2
- jettask/executor/__init__.py +38 -0
- jettask/executor/core.py +625 -0
- jettask/executor/executor.py +338 -0
- jettask/executor/orchestrator.py +290 -0
- jettask/executor/process_entry.py +638 -0
- jettask/executor/task_executor.py +317 -0
- jettask/messaging/__init__.py +68 -0
- jettask/messaging/event_pool.py +2188 -0
- jettask/messaging/reader.py +519 -0
- jettask/messaging/registry.py +266 -0
- jettask/messaging/scanner.py +369 -0
- jettask/messaging/sender.py +312 -0
- jettask/persistence/__init__.py +118 -0
- jettask/persistence/backlog_monitor.py +567 -0
- jettask/{backend/data_access.py → persistence/base.py} +58 -57
- jettask/persistence/consumer.py +315 -0
- jettask/{core → persistence}/db_manager.py +23 -22
- jettask/persistence/maintenance.py +81 -0
- jettask/persistence/message_consumer.py +259 -0
- jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
- jettask/persistence/offline_recovery.py +196 -0
- jettask/persistence/queue_discovery.py +215 -0
- jettask/persistence/task_persistence.py +218 -0
- jettask/persistence/task_updater.py +583 -0
- jettask/scheduler/__init__.py +2 -2
- jettask/scheduler/loader.py +6 -5
- jettask/scheduler/run_scheduler.py +1 -1
- jettask/scheduler/scheduler.py +7 -7
- jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
- jettask/task/__init__.py +16 -0
- jettask/{router.py → task/router.py} +26 -8
- jettask/task/task_center/__init__.py +9 -0
- jettask/task/task_executor.py +318 -0
- jettask/task/task_registry.py +291 -0
- jettask/test_connection_monitor.py +73 -0
- jettask/utils/__init__.py +31 -1
- jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
- jettask/utils/db_connector.py +1629 -0
- jettask/{db_init.py → utils/db_init.py} +1 -1
- jettask/utils/rate_limit/__init__.py +30 -0
- jettask/utils/rate_limit/concurrency_limiter.py +665 -0
- jettask/utils/rate_limit/config.py +145 -0
- jettask/utils/rate_limit/limiter.py +41 -0
- jettask/utils/rate_limit/manager.py +269 -0
- jettask/utils/rate_limit/qps_limiter.py +154 -0
- jettask/utils/rate_limit/task_limiter.py +384 -0
- jettask/utils/serializer.py +3 -0
- jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
- jettask/utils/time_sync.py +173 -0
- jettask/webui/__init__.py +27 -0
- jettask/{api/v1 → webui/api}/alerts.py +1 -1
- jettask/{api/v1 → webui/api}/analytics.py +2 -2
- jettask/{api/v1 → webui/api}/namespaces.py +1 -1
- jettask/{api/v1 → webui/api}/overview.py +1 -1
- jettask/{api/v1 → webui/api}/queues.py +3 -3
- jettask/{api/v1 → webui/api}/scheduled.py +1 -1
- jettask/{api/v1 → webui/api}/settings.py +1 -1
- jettask/{api.py → webui/app.py} +253 -145
- jettask/webui/namespace_manager/__init__.py +10 -0
- jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
- jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
- jettask/{run.py → webui/run.py} +2 -2
- jettask/{services → webui/services}/__init__.py +1 -3
- jettask/{services → webui/services}/overview_service.py +34 -16
- jettask/{services → webui/services}/queue_service.py +1 -1
- jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
- jettask/{services → webui/services}/settings_service.py +1 -1
- jettask/worker/__init__.py +53 -0
- jettask/worker/lifecycle.py +1507 -0
- jettask/worker/manager.py +583 -0
- jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/METADATA +2 -71
- jettask-0.2.23.dist-info/RECORD +145 -0
- jettask/__main__.py +0 -140
- jettask/api/__init__.py +0 -103
- jettask/backend/__init__.py +0 -1
- jettask/backend/api/__init__.py +0 -3
- jettask/backend/api/v1/__init__.py +0 -17
- jettask/backend/api/v1/monitoring.py +0 -431
- jettask/backend/api/v1/namespaces.py +0 -504
- jettask/backend/api/v1/queues.py +0 -342
- jettask/backend/api/v1/tasks.py +0 -367
- jettask/backend/core/__init__.py +0 -3
- jettask/backend/core/cache.py +0 -221
- jettask/backend/core/database.py +0 -200
- jettask/backend/core/exceptions.py +0 -102
- jettask/backend/dependencies.py +0 -261
- jettask/backend/init_meta_db.py +0 -158
- jettask/backend/main.py +0 -1426
- jettask/backend/main_unified.py +0 -78
- jettask/backend/main_v2.py +0 -394
- jettask/backend/models/__init__.py +0 -3
- jettask/backend/models/requests.py +0 -236
- jettask/backend/models/responses.py +0 -230
- jettask/backend/namespace_api_old.py +0 -267
- jettask/backend/services/__init__.py +0 -3
- jettask/backend/start.py +0 -42
- jettask/backend/unified_api_router.py +0 -1541
- jettask/cleanup_deprecated_tables.sql +0 -16
- jettask/core/consumer_manager.py +0 -1695
- jettask/core/delay_scanner.py +0 -256
- jettask/core/event_pool.py +0 -1700
- jettask/core/heartbeat_process.py +0 -222
- jettask/core/task_batch.py +0 -153
- jettask/core/worker_scanner.py +0 -271
- jettask/executors/__init__.py +0 -5
- jettask/executors/asyncio.py +0 -876
- jettask/executors/base.py +0 -30
- jettask/executors/common.py +0 -148
- jettask/executors/multi_asyncio.py +0 -309
- jettask/gradio_app.py +0 -570
- jettask/integrated_gradio_app.py +0 -1088
- jettask/main.py +0 -0
- jettask/monitoring/__init__.py +0 -3
- jettask/pg_consumer.py +0 -1896
- jettask/run_monitor.py +0 -22
- jettask/run_webui.py +0 -148
- jettask/scheduler/multi_namespace_scheduler.py +0 -294
- jettask/scheduler/unified_manager.py +0 -450
- jettask/task_center_client.py +0 -150
- jettask/utils/serializer_optimized.py +0 -33
- jettask/webui_exceptions.py +0 -67
- jettask-0.2.19.dist-info/RECORD +0 -150
- /jettask/{constants.py → config/constants.py} +0 -0
- /jettask/{backend/config.py → config/task_center.py} +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
- /jettask/{models.py → persistence/models.py} +0 -0
- /jettask/scheduler/{manager.py → task_crud.py} +0 -0
- /jettask/{schema.sql → schemas/schema.sql} +0 -0
- /jettask/{task_center.py → task/task_center/client.py} +0 -0
- /jettask/{monitoring → utils}/file_watcher.py +0 -0
- /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
- /jettask/{api/v1 → webui/api}/__init__.py +0 -0
- /jettask/{webui_config.py → webui/config.py} +0 -0
- /jettask/{webui_models → webui/models}/__init__.py +0 -0
- /jettask/{webui_models → webui/models}/namespace.py +0 -0
- /jettask/{services → webui/services}/alert_service.py +0 -0
- /jettask/{services → webui/services}/analytics_service.py +0 -0
- /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
- /jettask/{services → webui/services}/task_service.py +0 -0
- /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
- /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/WHEEL +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/top_level.txt +0 -0
jettask/backend/core/database.py
DELETED
@@ -1,200 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Database connection management and utilities
|
3
|
-
"""
|
4
|
-
import asyncio
|
5
|
-
from contextlib import asynccontextmanager
|
6
|
-
from typing import Optional, Dict, Any, AsyncGenerator
|
7
|
-
import asyncpg
|
8
|
-
import redis.asyncio as redis
|
9
|
-
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
10
|
-
from sqlalchemy.orm import DeclarativeBase
|
11
|
-
import logging
|
12
|
-
|
13
|
-
from .exceptions import DatabaseConnectionError
|
14
|
-
|
15
|
-
logger = logging.getLogger(__name__)
|
16
|
-
|
17
|
-
|
18
|
-
class DatabaseManager:
|
19
|
-
"""数据库连接管理器"""
|
20
|
-
|
21
|
-
def __init__(self):
|
22
|
-
self._pg_pools: Dict[str, asyncpg.Pool] = {}
|
23
|
-
self._redis_pools: Dict[str, redis.ConnectionPool] = {}
|
24
|
-
self._sqlalchemy_engines: Dict[str, Any] = {}
|
25
|
-
self._session_makers: Dict[str, async_sessionmaker] = {}
|
26
|
-
|
27
|
-
async def get_pg_pool(self, connection_string: str, pool_name: str = "default") -> asyncpg.Pool:
|
28
|
-
"""获取PostgreSQL连接池"""
|
29
|
-
if pool_name not in self._pg_pools:
|
30
|
-
try:
|
31
|
-
# 解析连接字符串
|
32
|
-
if connection_string.startswith('postgresql://'):
|
33
|
-
connection_string = connection_string.replace('postgresql://', '')
|
34
|
-
elif connection_string.startswith('postgresql+asyncpg://'):
|
35
|
-
connection_string = connection_string.replace('postgresql+asyncpg://', '')
|
36
|
-
|
37
|
-
# 分离认证和主机信息
|
38
|
-
auth, host_info = connection_string.split('@')
|
39
|
-
user, password = auth.split(':')
|
40
|
-
host_port, database = host_info.split('/')
|
41
|
-
host, port = host_port.split(':')
|
42
|
-
|
43
|
-
pool = await asyncpg.create_pool(
|
44
|
-
host=host,
|
45
|
-
port=int(port),
|
46
|
-
user=user,
|
47
|
-
password=password,
|
48
|
-
database=database,
|
49
|
-
min_size=2,
|
50
|
-
max_size=10,
|
51
|
-
command_timeout=30
|
52
|
-
)
|
53
|
-
|
54
|
-
self._pg_pools[pool_name] = pool
|
55
|
-
logger.info(f"PostgreSQL连接池已创建: {pool_name}")
|
56
|
-
|
57
|
-
except Exception as e:
|
58
|
-
logger.error(f"创建PostgreSQL连接池失败: {e}")
|
59
|
-
raise DatabaseConnectionError(f"PostgreSQL connection failed: {e}")
|
60
|
-
|
61
|
-
return self._pg_pools[pool_name]
|
62
|
-
|
63
|
-
async def get_redis_client(self, connection_string: str, pool_name: str = "default") -> redis.Redis:
|
64
|
-
"""获取Redis客户端"""
|
65
|
-
if pool_name not in self._redis_pools:
|
66
|
-
try:
|
67
|
-
pool = redis.ConnectionPool.from_url(
|
68
|
-
connection_string,
|
69
|
-
max_connections=20,
|
70
|
-
decode_responses=False # 保持原始字节格式
|
71
|
-
)
|
72
|
-
self._redis_pools[pool_name] = pool
|
73
|
-
logger.info(f"Redis连接池已创建: {pool_name}")
|
74
|
-
|
75
|
-
except Exception as e:
|
76
|
-
logger.error(f"创建Redis连接池失败: {e}")
|
77
|
-
raise DatabaseConnectionError(f"Redis connection failed: {e}")
|
78
|
-
|
79
|
-
return redis.Redis(connection_pool=self._redis_pools[pool_name])
|
80
|
-
|
81
|
-
def get_sqlalchemy_session_maker(
|
82
|
-
self,
|
83
|
-
connection_string: str,
|
84
|
-
pool_name: str = "default"
|
85
|
-
) -> async_sessionmaker[AsyncSession]:
|
86
|
-
"""获取SQLAlchemy会话工厂"""
|
87
|
-
if pool_name not in self._session_makers:
|
88
|
-
try:
|
89
|
-
# 确保使用asyncpg驱动
|
90
|
-
if connection_string.startswith('postgresql://'):
|
91
|
-
connection_string = connection_string.replace('postgresql://', 'postgresql+asyncpg://')
|
92
|
-
|
93
|
-
engine = create_async_engine(
|
94
|
-
connection_string,
|
95
|
-
pool_size=5,
|
96
|
-
max_overflow=10,
|
97
|
-
pool_timeout=30,
|
98
|
-
pool_recycle=3600,
|
99
|
-
echo=False # 生产环境关闭SQL日志
|
100
|
-
)
|
101
|
-
|
102
|
-
session_maker = async_sessionmaker(
|
103
|
-
engine,
|
104
|
-
class_=AsyncSession,
|
105
|
-
expire_on_commit=False
|
106
|
-
)
|
107
|
-
|
108
|
-
self._sqlalchemy_engines[pool_name] = engine
|
109
|
-
self._session_makers[pool_name] = session_maker
|
110
|
-
logger.info(f"SQLAlchemy会话工厂已创建: {pool_name}")
|
111
|
-
|
112
|
-
except Exception as e:
|
113
|
-
logger.error(f"创建SQLAlchemy会话工厂失败: {e}")
|
114
|
-
raise DatabaseConnectionError(f"SQLAlchemy connection failed: {e}")
|
115
|
-
|
116
|
-
return self._session_makers[pool_name]
|
117
|
-
|
118
|
-
@asynccontextmanager
|
119
|
-
async def get_pg_connection(self, pool_name: str = "default") -> AsyncGenerator[asyncpg.Connection, None]:
|
120
|
-
"""获取PostgreSQL连接上下文管理器"""
|
121
|
-
if pool_name not in self._pg_pools:
|
122
|
-
raise DatabaseConnectionError(f"PostgreSQL pool '{pool_name}' not found")
|
123
|
-
|
124
|
-
pool = self._pg_pools[pool_name]
|
125
|
-
async with pool.acquire() as conn:
|
126
|
-
yield conn
|
127
|
-
|
128
|
-
@asynccontextmanager
|
129
|
-
async def get_sqlalchemy_session(self, pool_name: str = "default") -> AsyncGenerator[AsyncSession, None]:
|
130
|
-
"""获取SQLAlchemy会话上下文管理器"""
|
131
|
-
if pool_name not in self._session_makers:
|
132
|
-
raise DatabaseConnectionError(f"SQLAlchemy session maker '{pool_name}' not found")
|
133
|
-
|
134
|
-
session_maker = self._session_makers[pool_name]
|
135
|
-
async with session_maker() as session:
|
136
|
-
try:
|
137
|
-
yield session
|
138
|
-
await session.commit()
|
139
|
-
except Exception:
|
140
|
-
await session.rollback()
|
141
|
-
raise
|
142
|
-
finally:
|
143
|
-
await session.close()
|
144
|
-
|
145
|
-
async def close_all(self):
|
146
|
-
"""关闭所有连接"""
|
147
|
-
# 关闭PostgreSQL连接池
|
148
|
-
for name, pool in self._pg_pools.items():
|
149
|
-
try:
|
150
|
-
await pool.close()
|
151
|
-
logger.info(f"PostgreSQL连接池已关闭: {name}")
|
152
|
-
except Exception as e:
|
153
|
-
logger.error(f"关闭PostgreSQL连接池失败 {name}: {e}")
|
154
|
-
|
155
|
-
# 关闭Redis连接池
|
156
|
-
for name, pool in self._redis_pools.items():
|
157
|
-
try:
|
158
|
-
await pool.disconnect()
|
159
|
-
logger.info(f"Redis连接池已关闭: {name}")
|
160
|
-
except Exception as e:
|
161
|
-
logger.error(f"关闭Redis连接池失败 {name}: {e}")
|
162
|
-
|
163
|
-
# 关闭SQLAlchemy引擎
|
164
|
-
for name, engine in self._sqlalchemy_engines.items():
|
165
|
-
try:
|
166
|
-
await engine.dispose()
|
167
|
-
logger.info(f"SQLAlchemy引擎已关闭: {name}")
|
168
|
-
except Exception as e:
|
169
|
-
logger.error(f"关闭SQLAlchemy引擎失败 {name}: {e}")
|
170
|
-
|
171
|
-
# 清空缓存
|
172
|
-
self._pg_pools.clear()
|
173
|
-
self._redis_pools.clear()
|
174
|
-
self._sqlalchemy_engines.clear()
|
175
|
-
self._session_makers.clear()
|
176
|
-
|
177
|
-
|
178
|
-
# 全局数据库管理器实例
|
179
|
-
db_manager = DatabaseManager()
|
180
|
-
|
181
|
-
|
182
|
-
# 数据库模型基类
|
183
|
-
class Base(DeclarativeBase):
|
184
|
-
pass
|
185
|
-
|
186
|
-
|
187
|
-
# 便捷函数
|
188
|
-
async def get_pg_pool(connection_string: str, pool_name: str = "default") -> asyncpg.Pool:
|
189
|
-
"""便捷函数:获取PostgreSQL连接池"""
|
190
|
-
return await db_manager.get_pg_pool(connection_string, pool_name)
|
191
|
-
|
192
|
-
|
193
|
-
async def get_redis_client(connection_string: str, pool_name: str = "default") -> redis.Redis:
|
194
|
-
"""便捷函数:获取Redis客户端"""
|
195
|
-
return await db_manager.get_redis_client(connection_string, pool_name)
|
196
|
-
|
197
|
-
|
198
|
-
def get_session_maker(connection_string: str, pool_name: str = "default") -> async_sessionmaker[AsyncSession]:
|
199
|
-
"""便捷函数:获取SQLAlchemy会话工厂"""
|
200
|
-
return db_manager.get_sqlalchemy_session_maker(connection_string, pool_name)
|
@@ -1,102 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Custom exceptions for JetTask WebUI Backend
|
3
|
-
"""
|
4
|
-
from typing import Optional, Dict, Any
|
5
|
-
from fastapi import HTTPException
|
6
|
-
|
7
|
-
|
8
|
-
class JetTaskAPIException(HTTPException):
|
9
|
-
"""Base exception for JetTask API"""
|
10
|
-
|
11
|
-
def __init__(
|
12
|
-
self,
|
13
|
-
status_code: int,
|
14
|
-
detail: str,
|
15
|
-
error_code: Optional[str] = None,
|
16
|
-
extra_data: Optional[Dict[str, Any]] = None
|
17
|
-
):
|
18
|
-
super().__init__(status_code=status_code, detail=detail)
|
19
|
-
self.error_code = error_code
|
20
|
-
self.extra_data = extra_data or {}
|
21
|
-
|
22
|
-
|
23
|
-
class NamespaceNotFoundError(JetTaskAPIException):
|
24
|
-
"""Namespace not found error"""
|
25
|
-
|
26
|
-
def __init__(self, namespace: str):
|
27
|
-
super().__init__(
|
28
|
-
status_code=404,
|
29
|
-
detail=f"Namespace '{namespace}' not found",
|
30
|
-
error_code="NAMESPACE_NOT_FOUND",
|
31
|
-
extra_data={"namespace": namespace}
|
32
|
-
)
|
33
|
-
|
34
|
-
|
35
|
-
class QueueNotFoundError(JetTaskAPIException):
|
36
|
-
"""Queue not found error"""
|
37
|
-
|
38
|
-
def __init__(self, queue_name: str, namespace: str = "default"):
|
39
|
-
super().__init__(
|
40
|
-
status_code=404,
|
41
|
-
detail=f"Queue '{queue_name}' not found in namespace '{namespace}'",
|
42
|
-
error_code="QUEUE_NOT_FOUND",
|
43
|
-
extra_data={"queue_name": queue_name, "namespace": namespace}
|
44
|
-
)
|
45
|
-
|
46
|
-
|
47
|
-
class TaskNotFoundError(JetTaskAPIException):
|
48
|
-
"""Task not found error"""
|
49
|
-
|
50
|
-
def __init__(self, task_id: str):
|
51
|
-
super().__init__(
|
52
|
-
status_code=404,
|
53
|
-
detail=f"Task '{task_id}' not found",
|
54
|
-
error_code="TASK_NOT_FOUND",
|
55
|
-
extra_data={"task_id": task_id}
|
56
|
-
)
|
57
|
-
|
58
|
-
|
59
|
-
class ValidationError(JetTaskAPIException):
|
60
|
-
"""Validation error"""
|
61
|
-
|
62
|
-
def __init__(self, message: str, field: Optional[str] = None):
|
63
|
-
super().__init__(
|
64
|
-
status_code=400,
|
65
|
-
detail=message,
|
66
|
-
error_code="VALIDATION_ERROR",
|
67
|
-
extra_data={"field": field} if field else {}
|
68
|
-
)
|
69
|
-
|
70
|
-
|
71
|
-
class DatabaseConnectionError(JetTaskAPIException):
|
72
|
-
"""Database connection error"""
|
73
|
-
|
74
|
-
def __init__(self, message: str = "Database connection failed"):
|
75
|
-
super().__init__(
|
76
|
-
status_code=503,
|
77
|
-
detail=message,
|
78
|
-
error_code="DATABASE_CONNECTION_ERROR"
|
79
|
-
)
|
80
|
-
|
81
|
-
|
82
|
-
class RateLimitError(JetTaskAPIException):
|
83
|
-
"""Rate limit exceeded error"""
|
84
|
-
|
85
|
-
def __init__(self, limit: int, window: int):
|
86
|
-
super().__init__(
|
87
|
-
status_code=429,
|
88
|
-
detail=f"Rate limit exceeded: {limit} requests per {window} seconds",
|
89
|
-
error_code="RATE_LIMIT_EXCEEDED",
|
90
|
-
extra_data={"limit": limit, "window": window}
|
91
|
-
)
|
92
|
-
|
93
|
-
|
94
|
-
class InternalServerError(JetTaskAPIException):
|
95
|
-
"""Internal server error"""
|
96
|
-
|
97
|
-
def __init__(self, message: str = "Internal server error"):
|
98
|
-
super().__init__(
|
99
|
-
status_code=500,
|
100
|
-
detail=message,
|
101
|
-
error_code="INTERNAL_SERVER_ERROR"
|
102
|
-
)
|
jettask/backend/dependencies.py
DELETED
@@ -1,261 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Dependency injection for JetTask WebUI Backend
|
3
|
-
"""
|
4
|
-
from typing import Optional, AsyncGenerator
|
5
|
-
from fastapi import Depends, HTTPException, Header
|
6
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
7
|
-
import redis.asyncio as redis
|
8
|
-
|
9
|
-
from core.database import db_manager, DatabaseManager
|
10
|
-
from core.cache import cache_manager, CacheManager
|
11
|
-
from core.exceptions import NamespaceNotFoundError, DatabaseConnectionError
|
12
|
-
from namespace_data_access import get_namespace_data_access
|
13
|
-
import logging
|
14
|
-
|
15
|
-
logger = logging.getLogger(__name__)
|
16
|
-
|
17
|
-
|
18
|
-
# 依赖注入函数
|
19
|
-
|
20
|
-
async def get_database_manager() -> DatabaseManager:
|
21
|
-
"""获取数据库管理器"""
|
22
|
-
return db_manager
|
23
|
-
|
24
|
-
|
25
|
-
async def get_cache_manager() -> CacheManager:
|
26
|
-
"""获取缓存管理器"""
|
27
|
-
return cache_manager
|
28
|
-
|
29
|
-
|
30
|
-
async def get_namespace_from_path(namespace: str = "default") -> str:
|
31
|
-
"""从路径参数获取命名空间"""
|
32
|
-
if not namespace:
|
33
|
-
raise HTTPException(status_code=400, detail="Namespace is required")
|
34
|
-
return namespace
|
35
|
-
|
36
|
-
|
37
|
-
async def get_namespace_from_header(
|
38
|
-
x_namespace: Optional[str] = Header(None, alias="X-Namespace")
|
39
|
-
) -> str:
|
40
|
-
"""从请求头获取命名空间"""
|
41
|
-
return x_namespace or "default"
|
42
|
-
|
43
|
-
|
44
|
-
async def get_current_namespace(
|
45
|
-
path_namespace: str = Depends(get_namespace_from_path),
|
46
|
-
header_namespace: str = Depends(get_namespace_from_header)
|
47
|
-
) -> str:
|
48
|
-
"""获取当前命名空间(优先使用路径参数)"""
|
49
|
-
return path_namespace if path_namespace != "default" else header_namespace
|
50
|
-
|
51
|
-
|
52
|
-
async def validate_namespace(namespace: str) -> str:
|
53
|
-
"""验证命名空间是否存在"""
|
54
|
-
try:
|
55
|
-
namespace_data_access = get_namespace_data_access()
|
56
|
-
# 尝试获取命名空间连接来验证其存在
|
57
|
-
conn = await namespace_data_access.manager.get_connection(namespace)
|
58
|
-
if not conn:
|
59
|
-
raise NamespaceNotFoundError(namespace)
|
60
|
-
return namespace
|
61
|
-
except Exception as e:
|
62
|
-
logger.error(f"命名空间验证失败 {namespace}: {e}")
|
63
|
-
raise NamespaceNotFoundError(namespace)
|
64
|
-
|
65
|
-
|
66
|
-
async def get_validated_namespace(
|
67
|
-
namespace: str = Depends(get_current_namespace)
|
68
|
-
) -> str:
|
69
|
-
"""获取并验证命名空间"""
|
70
|
-
return await validate_namespace(namespace)
|
71
|
-
|
72
|
-
|
73
|
-
async def get_pg_connection(
|
74
|
-
namespace: str = Depends(get_validated_namespace)
|
75
|
-
) -> AsyncGenerator[object, None]:
|
76
|
-
"""获取PostgreSQL连接"""
|
77
|
-
try:
|
78
|
-
namespace_data_access = get_namespace_data_access()
|
79
|
-
conn = await namespace_data_access.manager.get_connection(namespace)
|
80
|
-
|
81
|
-
if not conn.AsyncSessionLocal:
|
82
|
-
raise DatabaseConnectionError("PostgreSQL not configured for this namespace")
|
83
|
-
|
84
|
-
async with conn.AsyncSessionLocal() as session:
|
85
|
-
yield session
|
86
|
-
|
87
|
-
except DatabaseConnectionError:
|
88
|
-
raise
|
89
|
-
except Exception as e:
|
90
|
-
logger.error(f"获取PostgreSQL连接失败: {e}")
|
91
|
-
raise DatabaseConnectionError(f"PostgreSQL connection failed: {e}")
|
92
|
-
|
93
|
-
|
94
|
-
async def get_redis_client(
|
95
|
-
namespace: str = Depends(get_validated_namespace)
|
96
|
-
) -> AsyncGenerator[redis.Redis, None]:
|
97
|
-
"""获取Redis客户端"""
|
98
|
-
try:
|
99
|
-
namespace_data_access = get_namespace_data_access()
|
100
|
-
conn = await namespace_data_access.manager.get_connection(namespace)
|
101
|
-
|
102
|
-
redis_client = await conn.get_redis_client(decode=False)
|
103
|
-
|
104
|
-
try:
|
105
|
-
yield redis_client
|
106
|
-
finally:
|
107
|
-
await redis_client.aclose()
|
108
|
-
|
109
|
-
except Exception as e:
|
110
|
-
logger.error(f"获取Redis客户端失败: {e}")
|
111
|
-
raise DatabaseConnectionError(f"Redis connection failed: {e}")
|
112
|
-
|
113
|
-
|
114
|
-
async def get_namespace_connection(
|
115
|
-
namespace: str = Depends(get_validated_namespace)
|
116
|
-
):
|
117
|
-
"""获取命名空间连接对象"""
|
118
|
-
try:
|
119
|
-
namespace_data_access = get_namespace_data_access()
|
120
|
-
return await namespace_data_access.manager.get_connection(namespace)
|
121
|
-
except Exception as e:
|
122
|
-
logger.error(f"获取命名空间连接失败: {e}")
|
123
|
-
raise DatabaseConnectionError(f"Namespace connection failed: {e}")
|
124
|
-
|
125
|
-
|
126
|
-
# 权限和认证相关依赖(预留)
|
127
|
-
|
128
|
-
async def get_current_user(
|
129
|
-
authorization: Optional[str] = Header(None)
|
130
|
-
) -> Optional[dict]:
|
131
|
-
"""获取当前用户(预留接口)"""
|
132
|
-
# 这里可以实现JWT令牌解析、用户认证等逻辑
|
133
|
-
# 目前返回None表示匿名用户
|
134
|
-
return None
|
135
|
-
|
136
|
-
|
137
|
-
async def require_auth(
|
138
|
-
current_user: Optional[dict] = Depends(get_current_user)
|
139
|
-
) -> dict:
|
140
|
-
"""要求用户认证(预留接口)"""
|
141
|
-
# 这里可以实现认证检查逻辑
|
142
|
-
# 目前直接返回匿名用户
|
143
|
-
return current_user or {"id": "anonymous", "role": "user"}
|
144
|
-
|
145
|
-
|
146
|
-
async def require_admin(
|
147
|
-
current_user: dict = Depends(require_auth)
|
148
|
-
) -> dict:
|
149
|
-
"""要求管理员权限(预留接口)"""
|
150
|
-
# 这里可以实现权限检查逻辑
|
151
|
-
# 目前直接返回用户
|
152
|
-
return current_user
|
153
|
-
|
154
|
-
|
155
|
-
# 请求验证相关依赖
|
156
|
-
|
157
|
-
def validate_page_params(page: int = 1, page_size: int = 20) -> tuple[int, int]:
|
158
|
-
"""验证分页参数"""
|
159
|
-
if page < 1:
|
160
|
-
raise HTTPException(status_code=400, detail="Page must be >= 1")
|
161
|
-
if page_size < 1 or page_size > 100:
|
162
|
-
raise HTTPException(status_code=400, detail="Page size must be between 1 and 100")
|
163
|
-
return page, page_size
|
164
|
-
|
165
|
-
|
166
|
-
def validate_time_range(
|
167
|
-
start_time: Optional[str] = None,
|
168
|
-
end_time: Optional[str] = None,
|
169
|
-
time_range: Optional[str] = None
|
170
|
-
) -> dict:
|
171
|
-
"""验证时间范围参数"""
|
172
|
-
from datetime import datetime, timedelta
|
173
|
-
|
174
|
-
result = {}
|
175
|
-
|
176
|
-
if time_range:
|
177
|
-
# 预设时间范围
|
178
|
-
time_map = {
|
179
|
-
'15m': timedelta(minutes=15),
|
180
|
-
'30m': timedelta(minutes=30),
|
181
|
-
'1h': timedelta(hours=1),
|
182
|
-
'3h': timedelta(hours=3),
|
183
|
-
'6h': timedelta(hours=6),
|
184
|
-
'12h': timedelta(hours=12),
|
185
|
-
'24h': timedelta(hours=24),
|
186
|
-
'3d': timedelta(days=3),
|
187
|
-
'7d': timedelta(days=7),
|
188
|
-
'30d': timedelta(days=30)
|
189
|
-
}
|
190
|
-
|
191
|
-
if time_range not in time_map:
|
192
|
-
raise HTTPException(
|
193
|
-
status_code=400,
|
194
|
-
detail=f"Invalid time_range: {time_range}"
|
195
|
-
)
|
196
|
-
|
197
|
-
now = datetime.now()
|
198
|
-
result['end_time'] = now
|
199
|
-
result['start_time'] = now - time_map[time_range]
|
200
|
-
result['time_range'] = time_range
|
201
|
-
|
202
|
-
elif start_time or end_time:
|
203
|
-
# 自定义时间范围
|
204
|
-
try:
|
205
|
-
if start_time:
|
206
|
-
result['start_time'] = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
|
207
|
-
if end_time:
|
208
|
-
result['end_time'] = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
|
209
|
-
|
210
|
-
if result.get('start_time') and result.get('end_time'):
|
211
|
-
if result['start_time'] >= result['end_time']:
|
212
|
-
raise HTTPException(
|
213
|
-
status_code=400,
|
214
|
-
detail="start_time must be before end_time"
|
215
|
-
)
|
216
|
-
except ValueError as e:
|
217
|
-
raise HTTPException(
|
218
|
-
status_code=400,
|
219
|
-
detail=f"Invalid datetime format: {e}"
|
220
|
-
)
|
221
|
-
|
222
|
-
else:
|
223
|
-
# 默认最近1小时
|
224
|
-
now = datetime.now()
|
225
|
-
result['end_time'] = now
|
226
|
-
result['start_time'] = now - timedelta(hours=1)
|
227
|
-
result['time_range'] = '1h'
|
228
|
-
|
229
|
-
return result
|
230
|
-
|
231
|
-
|
232
|
-
# 性能监控相关依赖
|
233
|
-
|
234
|
-
class RequestMetrics:
|
235
|
-
"""请求指标收集器"""
|
236
|
-
|
237
|
-
def __init__(self):
|
238
|
-
self.start_time = None
|
239
|
-
self.namespace = None
|
240
|
-
self.endpoint = None
|
241
|
-
|
242
|
-
def start(self, namespace: str, endpoint: str):
|
243
|
-
import time
|
244
|
-
self.start_time = time.time()
|
245
|
-
self.namespace = namespace
|
246
|
-
self.endpoint = endpoint
|
247
|
-
|
248
|
-
def finish(self):
|
249
|
-
if self.start_time:
|
250
|
-
import time
|
251
|
-
duration = time.time() - self.start_time
|
252
|
-
logger.info(
|
253
|
-
f"API请求完成: {self.endpoint} "
|
254
|
-
f"namespace={self.namespace} "
|
255
|
-
f"duration={duration:.3f}s"
|
256
|
-
)
|
257
|
-
|
258
|
-
|
259
|
-
async def get_request_metrics() -> RequestMetrics:
|
260
|
-
"""获取请求指标收集器"""
|
261
|
-
return RequestMetrics()
|