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.
Files changed (165) hide show
  1. jettask/__init__.py +12 -3
  2. jettask/cli.py +314 -228
  3. jettask/config/__init__.py +9 -1
  4. jettask/config/config.py +245 -0
  5. jettask/config/env_loader.py +381 -0
  6. jettask/config/lua_scripts.py +158 -0
  7. jettask/config/nacos_config.py +132 -5
  8. jettask/core/__init__.py +1 -1
  9. jettask/core/app.py +1573 -666
  10. jettask/core/app_importer.py +33 -16
  11. jettask/core/container.py +532 -0
  12. jettask/core/task.py +1 -4
  13. jettask/core/unified_manager_base.py +2 -2
  14. jettask/executor/__init__.py +38 -0
  15. jettask/executor/core.py +625 -0
  16. jettask/executor/executor.py +338 -0
  17. jettask/executor/orchestrator.py +290 -0
  18. jettask/executor/process_entry.py +638 -0
  19. jettask/executor/task_executor.py +317 -0
  20. jettask/messaging/__init__.py +68 -0
  21. jettask/messaging/event_pool.py +2188 -0
  22. jettask/messaging/reader.py +519 -0
  23. jettask/messaging/registry.py +266 -0
  24. jettask/messaging/scanner.py +369 -0
  25. jettask/messaging/sender.py +312 -0
  26. jettask/persistence/__init__.py +118 -0
  27. jettask/persistence/backlog_monitor.py +567 -0
  28. jettask/{backend/data_access.py → persistence/base.py} +58 -57
  29. jettask/persistence/consumer.py +315 -0
  30. jettask/{core → persistence}/db_manager.py +23 -22
  31. jettask/persistence/maintenance.py +81 -0
  32. jettask/persistence/message_consumer.py +259 -0
  33. jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
  34. jettask/persistence/offline_recovery.py +196 -0
  35. jettask/persistence/queue_discovery.py +215 -0
  36. jettask/persistence/task_persistence.py +218 -0
  37. jettask/persistence/task_updater.py +583 -0
  38. jettask/scheduler/__init__.py +2 -2
  39. jettask/scheduler/loader.py +6 -5
  40. jettask/scheduler/run_scheduler.py +1 -1
  41. jettask/scheduler/scheduler.py +7 -7
  42. jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
  43. jettask/task/__init__.py +16 -0
  44. jettask/{router.py → task/router.py} +26 -8
  45. jettask/task/task_center/__init__.py +9 -0
  46. jettask/task/task_executor.py +318 -0
  47. jettask/task/task_registry.py +291 -0
  48. jettask/test_connection_monitor.py +73 -0
  49. jettask/utils/__init__.py +31 -1
  50. jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
  51. jettask/utils/db_connector.py +1629 -0
  52. jettask/{db_init.py → utils/db_init.py} +1 -1
  53. jettask/utils/rate_limit/__init__.py +30 -0
  54. jettask/utils/rate_limit/concurrency_limiter.py +665 -0
  55. jettask/utils/rate_limit/config.py +145 -0
  56. jettask/utils/rate_limit/limiter.py +41 -0
  57. jettask/utils/rate_limit/manager.py +269 -0
  58. jettask/utils/rate_limit/qps_limiter.py +154 -0
  59. jettask/utils/rate_limit/task_limiter.py +384 -0
  60. jettask/utils/serializer.py +3 -0
  61. jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
  62. jettask/utils/time_sync.py +173 -0
  63. jettask/webui/__init__.py +27 -0
  64. jettask/{api/v1 → webui/api}/alerts.py +1 -1
  65. jettask/{api/v1 → webui/api}/analytics.py +2 -2
  66. jettask/{api/v1 → webui/api}/namespaces.py +1 -1
  67. jettask/{api/v1 → webui/api}/overview.py +1 -1
  68. jettask/{api/v1 → webui/api}/queues.py +3 -3
  69. jettask/{api/v1 → webui/api}/scheduled.py +1 -1
  70. jettask/{api/v1 → webui/api}/settings.py +1 -1
  71. jettask/{api.py → webui/app.py} +253 -145
  72. jettask/webui/namespace_manager/__init__.py +10 -0
  73. jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
  74. jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
  75. jettask/{run.py → webui/run.py} +2 -2
  76. jettask/{services → webui/services}/__init__.py +1 -3
  77. jettask/{services → webui/services}/overview_service.py +34 -16
  78. jettask/{services → webui/services}/queue_service.py +1 -1
  79. jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
  80. jettask/{services → webui/services}/settings_service.py +1 -1
  81. jettask/worker/__init__.py +53 -0
  82. jettask/worker/lifecycle.py +1507 -0
  83. jettask/worker/manager.py +583 -0
  84. jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
  85. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/METADATA +2 -71
  86. jettask-0.2.23.dist-info/RECORD +145 -0
  87. jettask/__main__.py +0 -140
  88. jettask/api/__init__.py +0 -103
  89. jettask/backend/__init__.py +0 -1
  90. jettask/backend/api/__init__.py +0 -3
  91. jettask/backend/api/v1/__init__.py +0 -17
  92. jettask/backend/api/v1/monitoring.py +0 -431
  93. jettask/backend/api/v1/namespaces.py +0 -504
  94. jettask/backend/api/v1/queues.py +0 -342
  95. jettask/backend/api/v1/tasks.py +0 -367
  96. jettask/backend/core/__init__.py +0 -3
  97. jettask/backend/core/cache.py +0 -221
  98. jettask/backend/core/database.py +0 -200
  99. jettask/backend/core/exceptions.py +0 -102
  100. jettask/backend/dependencies.py +0 -261
  101. jettask/backend/init_meta_db.py +0 -158
  102. jettask/backend/main.py +0 -1426
  103. jettask/backend/main_unified.py +0 -78
  104. jettask/backend/main_v2.py +0 -394
  105. jettask/backend/models/__init__.py +0 -3
  106. jettask/backend/models/requests.py +0 -236
  107. jettask/backend/models/responses.py +0 -230
  108. jettask/backend/namespace_api_old.py +0 -267
  109. jettask/backend/services/__init__.py +0 -3
  110. jettask/backend/start.py +0 -42
  111. jettask/backend/unified_api_router.py +0 -1541
  112. jettask/cleanup_deprecated_tables.sql +0 -16
  113. jettask/core/consumer_manager.py +0 -1695
  114. jettask/core/delay_scanner.py +0 -256
  115. jettask/core/event_pool.py +0 -1700
  116. jettask/core/heartbeat_process.py +0 -222
  117. jettask/core/task_batch.py +0 -153
  118. jettask/core/worker_scanner.py +0 -271
  119. jettask/executors/__init__.py +0 -5
  120. jettask/executors/asyncio.py +0 -876
  121. jettask/executors/base.py +0 -30
  122. jettask/executors/common.py +0 -148
  123. jettask/executors/multi_asyncio.py +0 -309
  124. jettask/gradio_app.py +0 -570
  125. jettask/integrated_gradio_app.py +0 -1088
  126. jettask/main.py +0 -0
  127. jettask/monitoring/__init__.py +0 -3
  128. jettask/pg_consumer.py +0 -1896
  129. jettask/run_monitor.py +0 -22
  130. jettask/run_webui.py +0 -148
  131. jettask/scheduler/multi_namespace_scheduler.py +0 -294
  132. jettask/scheduler/unified_manager.py +0 -450
  133. jettask/task_center_client.py +0 -150
  134. jettask/utils/serializer_optimized.py +0 -33
  135. jettask/webui_exceptions.py +0 -67
  136. jettask-0.2.19.dist-info/RECORD +0 -150
  137. /jettask/{constants.py → config/constants.py} +0 -0
  138. /jettask/{backend/config.py → config/task_center.py} +0 -0
  139. /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
  140. /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
  141. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
  142. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
  143. /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
  144. /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
  145. /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
  146. /jettask/{models.py → persistence/models.py} +0 -0
  147. /jettask/scheduler/{manager.py → task_crud.py} +0 -0
  148. /jettask/{schema.sql → schemas/schema.sql} +0 -0
  149. /jettask/{task_center.py → task/task_center/client.py} +0 -0
  150. /jettask/{monitoring → utils}/file_watcher.py +0 -0
  151. /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
  152. /jettask/{api/v1 → webui/api}/__init__.py +0 -0
  153. /jettask/{webui_config.py → webui/config.py} +0 -0
  154. /jettask/{webui_models → webui/models}/__init__.py +0 -0
  155. /jettask/{webui_models → webui/models}/namespace.py +0 -0
  156. /jettask/{services → webui/services}/alert_service.py +0 -0
  157. /jettask/{services → webui/services}/analytics_service.py +0 -0
  158. /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
  159. /jettask/{services → webui/services}/task_service.py +0 -0
  160. /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
  161. /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
  162. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/WHEEL +0 -0
  163. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/entry_points.txt +0 -0
  164. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/licenses/LICENSE +0 -0
  165. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/top_level.txt +0 -0
@@ -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
- )
@@ -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()