jettask 0.2.20__py3-none-any.whl → 0.2.24__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 (110) hide show
  1. jettask/__init__.py +4 -0
  2. jettask/cli.py +12 -8
  3. jettask/config/lua_scripts.py +37 -0
  4. jettask/config/nacos_config.py +1 -1
  5. jettask/core/app.py +313 -340
  6. jettask/core/container.py +4 -4
  7. jettask/{persistence → core}/namespace.py +93 -27
  8. jettask/core/task.py +16 -9
  9. jettask/core/unified_manager_base.py +136 -26
  10. jettask/db/__init__.py +67 -0
  11. jettask/db/base.py +137 -0
  12. jettask/{utils/db_connector.py → db/connector.py} +130 -26
  13. jettask/db/models/__init__.py +16 -0
  14. jettask/db/models/scheduled_task.py +196 -0
  15. jettask/db/models/task.py +77 -0
  16. jettask/db/models/task_run.py +85 -0
  17. jettask/executor/__init__.py +0 -15
  18. jettask/executor/core.py +76 -31
  19. jettask/executor/process_entry.py +29 -114
  20. jettask/executor/task_executor.py +4 -0
  21. jettask/messaging/event_pool.py +928 -685
  22. jettask/messaging/scanner.py +30 -0
  23. jettask/persistence/__init__.py +28 -103
  24. jettask/persistence/buffer.py +170 -0
  25. jettask/persistence/consumer.py +330 -249
  26. jettask/persistence/manager.py +304 -0
  27. jettask/persistence/persistence.py +391 -0
  28. jettask/scheduler/__init__.py +15 -3
  29. jettask/scheduler/{task_crud.py → database.py} +61 -57
  30. jettask/scheduler/loader.py +2 -2
  31. jettask/scheduler/{scheduler_coordinator.py → manager.py} +23 -6
  32. jettask/scheduler/models.py +14 -10
  33. jettask/scheduler/schedule.py +166 -0
  34. jettask/scheduler/scheduler.py +12 -11
  35. jettask/schemas/__init__.py +50 -1
  36. jettask/schemas/backlog.py +43 -6
  37. jettask/schemas/namespace.py +70 -19
  38. jettask/schemas/queue.py +19 -3
  39. jettask/schemas/responses.py +493 -0
  40. jettask/task/__init__.py +0 -2
  41. jettask/task/router.py +3 -0
  42. jettask/test_connection_monitor.py +1 -1
  43. jettask/utils/__init__.py +7 -5
  44. jettask/utils/db_init.py +8 -4
  45. jettask/utils/namespace_dep.py +167 -0
  46. jettask/utils/queue_matcher.py +186 -0
  47. jettask/utils/rate_limit/concurrency_limiter.py +7 -1
  48. jettask/utils/stream_backlog.py +1 -1
  49. jettask/webui/__init__.py +0 -1
  50. jettask/webui/api/__init__.py +4 -4
  51. jettask/webui/api/alerts.py +806 -71
  52. jettask/webui/api/example_refactored.py +400 -0
  53. jettask/webui/api/namespaces.py +390 -45
  54. jettask/webui/api/overview.py +300 -54
  55. jettask/webui/api/queues.py +971 -267
  56. jettask/webui/api/scheduled.py +1249 -56
  57. jettask/webui/api/settings.py +129 -7
  58. jettask/webui/api/workers.py +442 -0
  59. jettask/webui/app.py +46 -2329
  60. jettask/webui/middleware/__init__.py +6 -0
  61. jettask/webui/middleware/namespace_middleware.py +135 -0
  62. jettask/webui/services/__init__.py +146 -0
  63. jettask/webui/services/heartbeat_service.py +251 -0
  64. jettask/webui/services/overview_service.py +60 -51
  65. jettask/webui/services/queue_monitor_service.py +426 -0
  66. jettask/webui/services/redis_monitor_service.py +87 -0
  67. jettask/webui/services/settings_service.py +174 -111
  68. jettask/webui/services/task_monitor_service.py +222 -0
  69. jettask/webui/services/timeline_pg_service.py +452 -0
  70. jettask/webui/services/timeline_service.py +189 -0
  71. jettask/webui/services/worker_monitor_service.py +467 -0
  72. jettask/webui/utils/__init__.py +11 -0
  73. jettask/webui/utils/time_utils.py +122 -0
  74. jettask/worker/lifecycle.py +8 -2
  75. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/METADATA +1 -1
  76. jettask-0.2.24.dist-info/RECORD +142 -0
  77. jettask/executor/executor.py +0 -338
  78. jettask/persistence/backlog_monitor.py +0 -567
  79. jettask/persistence/base.py +0 -2334
  80. jettask/persistence/db_manager.py +0 -516
  81. jettask/persistence/maintenance.py +0 -81
  82. jettask/persistence/message_consumer.py +0 -259
  83. jettask/persistence/models.py +0 -49
  84. jettask/persistence/offline_recovery.py +0 -196
  85. jettask/persistence/queue_discovery.py +0 -215
  86. jettask/persistence/task_persistence.py +0 -218
  87. jettask/persistence/task_updater.py +0 -583
  88. jettask/scheduler/add_execution_count.sql +0 -11
  89. jettask/scheduler/add_priority_field.sql +0 -26
  90. jettask/scheduler/add_scheduler_id.sql +0 -25
  91. jettask/scheduler/add_scheduler_id_index.sql +0 -10
  92. jettask/scheduler/make_scheduler_id_required.sql +0 -28
  93. jettask/scheduler/migrate_interval_seconds.sql +0 -9
  94. jettask/scheduler/performance_optimization.sql +0 -45
  95. jettask/scheduler/run_scheduler.py +0 -186
  96. jettask/scheduler/schema.sql +0 -84
  97. jettask/task/task_executor.py +0 -318
  98. jettask/webui/api/analytics.py +0 -323
  99. jettask/webui/config.py +0 -90
  100. jettask/webui/models/__init__.py +0 -3
  101. jettask/webui/models/namespace.py +0 -63
  102. jettask/webui/namespace_manager/__init__.py +0 -10
  103. jettask/webui/namespace_manager/multi.py +0 -593
  104. jettask/webui/namespace_manager/unified.py +0 -193
  105. jettask/webui/run.py +0 -46
  106. jettask-0.2.20.dist-info/RECORD +0 -145
  107. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/WHEEL +0 -0
  108. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/entry_points.txt +0 -0
  109. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/licenses/LICENSE +0 -0
  110. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,167 @@
1
+ """
2
+ 命名空间依赖注入工具
3
+ 提供统一的命名空间参数处理和数据库连接管理
4
+ """
5
+ import logging
6
+ from typing import Optional
7
+ from fastapi import Depends, HTTPException, Path, Request
8
+ from jettask.core.namespace import NamespaceConnection, NamespaceDataAccessManager
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class NamespaceContext:
14
+ """
15
+ 命名空间上下文对象
16
+ 封装命名空间的所有信息和数据库连接
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ namespace_name: str,
22
+ connection: NamespaceConnection,
23
+ manager: NamespaceDataAccessManager
24
+ ):
25
+ self.namespace_name = namespace_name
26
+ self.connection = connection
27
+ self.manager = manager
28
+
29
+ async def get_redis_client(self, decode: bool = True):
30
+ """获取 Redis 客户端"""
31
+ return await self.connection.get_redis_client(decode=decode)
32
+
33
+ async def get_pg_session(self):
34
+ """获取 PostgreSQL 会话"""
35
+ return await self.connection.get_pg_session()
36
+
37
+ @property
38
+ def redis_prefix(self) -> str:
39
+ """获取 Redis 键前缀"""
40
+ return self.connection.redis_prefix
41
+
42
+ @property
43
+ def redis_config(self) -> dict:
44
+ """获取 Redis 配置"""
45
+ return self.connection.redis_config
46
+
47
+ @property
48
+ def pg_config(self) -> dict:
49
+ """获取 PostgreSQL 配置"""
50
+ return self.connection.pg_config
51
+
52
+
53
+ def get_namespace_manager(request: Request) -> NamespaceDataAccessManager:
54
+ """
55
+ 获取命名空间数据访问管理器
56
+ 从 app.state 中获取全局实例
57
+ """
58
+ if not hasattr(request.app.state, 'namespace_data_access'):
59
+ raise HTTPException(
60
+ status_code=500,
61
+ detail="Namespace data access not initialized"
62
+ )
63
+
64
+ # namespace_data_access 是 NamespaceJetTaskDataAccess 实例
65
+ # 它包含一个 manager 属性
66
+ return request.app.state.namespace_data_access.manager
67
+
68
+
69
+ async def get_namespace_context(
70
+ namespace: str = Path(..., description="命名空间名称", example="default"),
71
+ manager: NamespaceDataAccessManager = Depends(get_namespace_manager)
72
+ ) -> NamespaceContext:
73
+ """
74
+ 获取命名空间上下文(依赖注入函数)
75
+
76
+ 这个函数会:
77
+ 1. 从路径参数中提取 namespace 名称
78
+ 2. 从任务中心API或Nacos获取命名空间配置
79
+ 3. 建立数据库连接(Redis + PostgreSQL)
80
+ 4. 返回封装好的命名空间上下文对象
81
+
82
+ 使用示例:
83
+ ```python
84
+ @router.get("/{namespace}/queues")
85
+ async def get_queues(
86
+ ns: NamespaceContext = Depends(get_namespace_context)
87
+ ):
88
+ # 直接使用 ns 对象访问数据库
89
+ redis_client = await ns.get_redis_client()
90
+ # ... 执行操作
91
+ return {"namespace": ns.namespace_name}
92
+ ```
93
+
94
+ Args:
95
+ namespace: 从路径参数中提取的命名空间名称
96
+ manager: 命名空间数据访问管理器(自动注入)
97
+
98
+ Returns:
99
+ NamespaceContext: 命名空间上下文对象
100
+
101
+ Raises:
102
+ HTTPException: 当命名空间不存在或配置错误时
103
+ """
104
+ try:
105
+ # 获取命名空间的数据库连接
106
+ connection = await manager.get_connection(namespace)
107
+
108
+ # 创建并返回上下文对象
109
+ return NamespaceContext(
110
+ namespace_name=namespace,
111
+ connection=connection,
112
+ manager=manager
113
+ )
114
+
115
+ except ValueError as e:
116
+ logger.error(f"命名空间 '{namespace}' 配置错误: {e}")
117
+ raise HTTPException(
118
+ status_code=404,
119
+ detail=f"命名空间 '{namespace}' 不存在或配置错误"
120
+ )
121
+ except Exception as e:
122
+ logger.error(f"获取命名空间 '{namespace}' 上下文失败: {e}", exc_info=True)
123
+ raise HTTPException(
124
+ status_code=500,
125
+ detail=f"获取命名空间连接失败: {str(e)}"
126
+ )
127
+
128
+
129
+ # 为了向后兼容,提供一个简化的依赖函数
130
+ async def get_namespace_connection(
131
+ namespace: str = Path(..., description="命名空间名称", example="default"),
132
+ manager: NamespaceDataAccessManager = Depends(get_namespace_manager)
133
+ ) -> NamespaceConnection:
134
+ """
135
+ 获取命名空间数据库连接(简化版本)
136
+
137
+ 直接返回 NamespaceConnection 对象,适用于简单场景
138
+
139
+ Args:
140
+ namespace: 命名空间名称
141
+ manager: 命名空间数据访问管理器
142
+
143
+ Returns:
144
+ NamespaceConnection: 数据库连接对象
145
+ """
146
+ try:
147
+ return await manager.get_connection(namespace)
148
+ except ValueError as e:
149
+ logger.error(f"命名空间 '{namespace}' 配置错误: {e}")
150
+ raise HTTPException(
151
+ status_code=404,
152
+ detail=f"命名空间 '{namespace}' 不存在或配置错误"
153
+ )
154
+ except Exception as e:
155
+ logger.error(f"获取命名空间 '{namespace}' 连接失败: {e}", exc_info=True)
156
+ raise HTTPException(
157
+ status_code=500,
158
+ detail=f"获取命名空间连接失败: {str(e)}"
159
+ )
160
+
161
+
162
+ __all__ = [
163
+ 'NamespaceContext',
164
+ 'get_namespace_context',
165
+ 'get_namespace_connection',
166
+ 'get_namespace_manager'
167
+ ]
@@ -0,0 +1,186 @@
1
+ """
2
+ 队列通配符匹配工具
3
+
4
+ 提供队列名称的通配符匹配功能,支持动态队列发现场景。
5
+ """
6
+
7
+ import fnmatch
8
+ from typing import List, Dict, Set, Tuple
9
+
10
+
11
+ def is_wildcard_pattern(queue: str) -> bool:
12
+ """
13
+ 检查队列名是否包含通配符
14
+
15
+ Args:
16
+ queue: 队列名称
17
+
18
+ Returns:
19
+ bool: 如果包含 * 或 ? 通配符则返回 True
20
+
21
+ Examples:
22
+ >>> is_wildcard_pattern('robust_*')
23
+ True
24
+ >>> is_wildcard_pattern('test?')
25
+ True
26
+ >>> is_wildcard_pattern('normal_queue')
27
+ False
28
+ """
29
+ return '*' in queue or '?' in queue
30
+
31
+
32
+ def separate_wildcard_and_static_queues(queues: List[str]) -> Tuple[List[str], List[str]]:
33
+ """
34
+ 分离通配符队列和静态队列
35
+
36
+ Args:
37
+ queues: 队列列表
38
+
39
+ Returns:
40
+ tuple: (wildcard_patterns, static_queues)
41
+ - wildcard_patterns: 包含通配符的队列模式列表
42
+ - static_queues: 不包含通配符的静态队列列表
43
+
44
+ Examples:
45
+ >>> separate_wildcard_and_static_queues(['test*', 'robot', 'data?'])
46
+ (['test*', 'data?'], ['robot'])
47
+ """
48
+ wildcard_patterns = []
49
+ static_queues = []
50
+
51
+ for queue in queues:
52
+ if is_wildcard_pattern(queue):
53
+ wildcard_patterns.append(queue)
54
+ else:
55
+ static_queues.append(queue)
56
+
57
+ return wildcard_patterns, static_queues
58
+
59
+
60
+ def match_queue_to_pattern(queue: str, patterns: List[str]) -> str:
61
+ """
62
+ 将实际队列名匹配到通配符模式
63
+
64
+ Args:
65
+ queue: 实际队列名,如 'robust_bench2'
66
+ patterns: 通配符模式列表,如 ['robust_*', 'test*']
67
+
68
+ Returns:
69
+ str: 匹配到的模式,如果没有匹配则返回 None
70
+
71
+ Examples:
72
+ >>> match_queue_to_pattern('robust_bench2', ['robust_*', 'test*'])
73
+ 'robust_*'
74
+ >>> match_queue_to_pattern('robot', ['robust_*', 'test*'])
75
+ None
76
+ """
77
+ for pattern in patterns:
78
+ if fnmatch.fnmatch(queue, pattern):
79
+ return pattern
80
+ return None
81
+
82
+
83
+ def find_matching_tasks(
84
+ queue: str,
85
+ tasks_by_queue: Dict[str, List[str]],
86
+ wildcard_mode: bool = False
87
+ ) -> List[str]:
88
+ """
89
+ 为实际队列名查找对应的任务列表
90
+
91
+ 支持两种方式:
92
+ 1. 直接匹配:queue 在 tasks_by_queue 的键中
93
+ 2. 通配符匹配:使用通配符模式匹配 tasks_by_queue 的键
94
+
95
+ Args:
96
+ queue: 实际队列名,如 'robust_bench2'
97
+ tasks_by_queue: 任务映射字典,键可能是通配符,如 {'robust_*': ['task1']}
98
+ wildcard_mode: 是否启用通配符匹配模式
99
+
100
+ Returns:
101
+ List[str]: 匹配到的任务名称列表
102
+
103
+ Examples:
104
+ >>> tasks_by_queue = {'robust_*': ['benchmark_task'], 'robot': ['clean_task']}
105
+ >>> find_matching_tasks('robust_bench2', tasks_by_queue, wildcard_mode=True)
106
+ ['benchmark_task']
107
+ >>> find_matching_tasks('robot', tasks_by_queue, wildcard_mode=True)
108
+ ['clean_task']
109
+ """
110
+ # 先尝试直接匹配
111
+ task_names = tasks_by_queue.get(queue, [])
112
+
113
+ # 如果没有直接匹配且启用了通配符模式,尝试通配符匹配
114
+ if not task_names and wildcard_mode:
115
+ for pattern, pattern_tasks in tasks_by_queue.items():
116
+ # 检查实际队列名是否匹配 tasks_by_queue 中的通配符模式
117
+ if fnmatch.fnmatch(queue, pattern):
118
+ task_names.extend(pattern_tasks)
119
+
120
+ return task_names
121
+
122
+
123
+ def match_task_queue_to_patterns(
124
+ task_queue: str,
125
+ queue_patterns: List[str]
126
+ ) -> bool:
127
+ """
128
+ 检查任务的队列名是否匹配任何队列模式
129
+
130
+ Args:
131
+ task_queue: 任务的队列名(可能是通配符),如 'robust_*'
132
+ queue_patterns: 队列模式列表(可能包含通配符),如 ['robust_*', 'test']
133
+
134
+ Returns:
135
+ bool: 如果匹配则返回 True
136
+
137
+ Examples:
138
+ >>> match_task_queue_to_patterns('robust_*', ['robust_*'])
139
+ True
140
+ >>> match_task_queue_to_patterns('robust_*', ['test*'])
141
+ True # 'robust_*' 匹配 'test*' 的模式
142
+ >>> match_task_queue_to_patterns('robot', ['robust_*'])
143
+ False
144
+ """
145
+ for queue_pattern in queue_patterns:
146
+ # 如果队列模式包含通配符
147
+ if is_wildcard_pattern(queue_pattern):
148
+ # 检查 task_queue 是否匹配这个通配符模式
149
+ if fnmatch.fnmatch(task_queue, queue_pattern) or task_queue == queue_pattern:
150
+ return True
151
+ else:
152
+ # 精确匹配
153
+ if task_queue == queue_pattern:
154
+ return True
155
+
156
+ return False
157
+
158
+
159
+ def discover_matching_queues(
160
+ wildcard_patterns: List[str],
161
+ all_queues: Set[str]
162
+ ) -> Set[str]:
163
+ """
164
+ 从所有队列中发现匹配通配符模式的队列
165
+
166
+ Args:
167
+ wildcard_patterns: 通配符模式列表,如 ['test*', 'robust_*']
168
+ all_queues: 所有可用的队列集合
169
+
170
+ Returns:
171
+ Set[str]: 匹配到的队列集合
172
+
173
+ Examples:
174
+ >>> all_queues = {'test1', 'test2', 'robust_bench', 'robot'}
175
+ >>> discover_matching_queues(['test*'], all_queues)
176
+ {'test1', 'test2'}
177
+ """
178
+ matched_queues = set()
179
+
180
+ for pattern in wildcard_patterns:
181
+ # 使用fnmatch进行通配符匹配
182
+ for queue in all_queues:
183
+ if fnmatch.fnmatch(queue, pattern):
184
+ matched_queues.add(queue)
185
+
186
+ return matched_queues
@@ -17,7 +17,7 @@ from redis.asyncio import Redis
17
17
  from typing import Dict, Optional, Set, List, Tuple
18
18
  from collections import defaultdict
19
19
 
20
- from jettask.utils.db_connector import get_sync_redis_client
20
+ from jettask.db.connector import get_sync_redis_client
21
21
 
22
22
  logger = logging.getLogger('app')
23
23
 
@@ -269,6 +269,12 @@ class ConcurrencyRateLimiter:
269
269
  # 订阅 Pub/Sub 频道
270
270
  self._pubsub = self.redis.pubsub()
271
271
  await self._pubsub.subscribe(self.release_channel)
272
+
273
+ # 标记PubSub连接,防止被空闲连接清理
274
+ if hasattr(self._pubsub, 'connection') and self._pubsub.connection:
275
+ self._pubsub.connection._is_pubsub_connection = True
276
+ logger.info(f"[CONCURRENCY] Marked PubSub connection {id(self._pubsub.connection)} to prevent cleanup")
277
+
272
278
  logger.debug(f"[CONCURRENCY] Subscribed to channel {self.release_channel}")
273
279
 
274
280
  # 启动 Pub/Sub 监听协程
@@ -39,7 +39,7 @@ class StreamBacklogMonitor:
39
39
  async def initialize(self):
40
40
  """初始化连接(使用统一的连接池管理)"""
41
41
  # 初始化Redis连接
42
- from jettask.utils.db_connector import get_async_redis_client
42
+ from jettask.db.connector import get_async_redis_client
43
43
 
44
44
  self.redis_client = get_async_redis_client(
45
45
  redis_url=self.redis_url,
jettask/webui/__init__.py CHANGED
@@ -6,7 +6,6 @@ Web UI 模块
6
6
 
7
7
  # 不在 __init__ 中导入 app,避免循环导入
8
8
  # 使用时直接: from jettask.webui.app import app
9
- from .config import *
10
9
 
11
10
  # 异常类直接从主模块导入(webui/exceptions.py已废弃并删除)
12
11
  from jettask.exceptions import (
@@ -3,26 +3,26 @@ API v1 路由模块集合
3
3
  """
4
4
  from fastapi import APIRouter
5
5
 
6
- # 导入7个主要模块的路由
6
+ # 导入8个主要模块的路由
7
7
  from .overview import router as overview_router # 概览
8
8
  from .namespaces import router as namespaces_router # 命名空间
9
9
  from .queues import router as queues_router # 队列
10
10
  from .scheduled import router as scheduled_router # 定时任务
11
11
  from .alerts import router as alerts_router # 告警
12
- from .analytics import router as analytics_router # 分析
13
12
  from .settings import router as settings_router # 设置
13
+ from .workers import router as workers_router # Worker 监控
14
14
 
15
15
  # 创建 v1 总路由,添加统一的 /api/v1 前缀
16
16
  api_router = APIRouter(prefix="/api/v1")
17
17
 
18
- # 按功能模块组织路由(namespaces和settings现在是平级的)
18
+ # 按功能模块组织路由
19
19
  api_router.include_router(overview_router) # 系统概览、健康检查
20
20
  api_router.include_router(namespaces_router) # 命名空间管理(独立路由)
21
21
  api_router.include_router(queues_router) # 队列管理、任务处理
22
22
  api_router.include_router(scheduled_router) # 定时任务管理
23
23
  api_router.include_router(alerts_router) # 告警规则管理
24
- api_router.include_router(analytics_router) # 数据分析查询
25
24
  api_router.include_router(settings_router) # 系统配置
25
+ api_router.include_router(workers_router) # Worker 监控
26
26
 
27
27
  __all__ = ['api_router']
28
28