aiptx 2.0.7__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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Task Scheduler - Priority-based task scheduling
|
|
3
|
+
|
|
4
|
+
Provides task scheduling with:
|
|
5
|
+
- Priority queues
|
|
6
|
+
- Rate limiting
|
|
7
|
+
- Concurrent execution limits
|
|
8
|
+
- Task dependencies
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import heapq
|
|
14
|
+
from typing import Optional, List, Dict, Any, Callable, Awaitable
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from enum import IntEnum
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TaskPriority(IntEnum):
|
|
24
|
+
"""Task priority levels (lower value = higher priority)"""
|
|
25
|
+
CRITICAL = 0
|
|
26
|
+
HIGH = 1
|
|
27
|
+
NORMAL = 2
|
|
28
|
+
LOW = 3
|
|
29
|
+
BACKGROUND = 4
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TaskStatus(str):
|
|
33
|
+
PENDING = "pending"
|
|
34
|
+
QUEUED = "queued"
|
|
35
|
+
RUNNING = "running"
|
|
36
|
+
COMPLETED = "completed"
|
|
37
|
+
FAILED = "failed"
|
|
38
|
+
CANCELLED = "cancelled"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(order=True)
|
|
42
|
+
class ScheduledTask:
|
|
43
|
+
"""A task in the scheduler queue"""
|
|
44
|
+
priority: TaskPriority
|
|
45
|
+
task_id: str = field(compare=False)
|
|
46
|
+
name: str = field(compare=False)
|
|
47
|
+
handler: Callable[..., Awaitable[Any]] = field(compare=False)
|
|
48
|
+
args: tuple = field(default_factory=tuple, compare=False)
|
|
49
|
+
kwargs: Dict[str, Any] = field(default_factory=dict, compare=False)
|
|
50
|
+
timeout: int = field(default=300, compare=False)
|
|
51
|
+
retries: int = field(default=0, compare=False)
|
|
52
|
+
depends_on: List[str] = field(default_factory=list, compare=False)
|
|
53
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat(), compare=False)
|
|
54
|
+
status: str = field(default=TaskStatus.PENDING, compare=False)
|
|
55
|
+
result: Any = field(default=None, compare=False)
|
|
56
|
+
error: Optional[str] = field(default=None, compare=False)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TaskScheduler:
|
|
60
|
+
"""
|
|
61
|
+
Priority-based task scheduler with concurrency control.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
scheduler = TaskScheduler(max_concurrent=5)
|
|
65
|
+
|
|
66
|
+
# Add tasks
|
|
67
|
+
scheduler.add_task(ScheduledTask(
|
|
68
|
+
priority=TaskPriority.HIGH,
|
|
69
|
+
task_id="scan_1",
|
|
70
|
+
name="Port Scan",
|
|
71
|
+
handler=port_scan,
|
|
72
|
+
args=("192.168.1.1",),
|
|
73
|
+
))
|
|
74
|
+
|
|
75
|
+
# Start scheduler
|
|
76
|
+
await scheduler.run()
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
max_concurrent: int = 5,
|
|
82
|
+
rate_limit: Optional[float] = None, # Tasks per second
|
|
83
|
+
on_task_start: Optional[Callable[[ScheduledTask], None]] = None,
|
|
84
|
+
on_task_complete: Optional[Callable[[ScheduledTask], None]] = None,
|
|
85
|
+
):
|
|
86
|
+
self.max_concurrent = max_concurrent
|
|
87
|
+
self.rate_limit = rate_limit
|
|
88
|
+
self.on_task_start = on_task_start
|
|
89
|
+
self.on_task_complete = on_task_complete
|
|
90
|
+
|
|
91
|
+
self._queue: List[ScheduledTask] = []
|
|
92
|
+
self._tasks: Dict[str, ScheduledTask] = {}
|
|
93
|
+
self._running: Dict[str, asyncio.Task] = {}
|
|
94
|
+
self._completed: Dict[str, ScheduledTask] = {}
|
|
95
|
+
self._last_task_time: float = 0
|
|
96
|
+
self._shutdown = False
|
|
97
|
+
self._task_id_counter = 0
|
|
98
|
+
|
|
99
|
+
def add_task(self, task: ScheduledTask) -> str:
|
|
100
|
+
"""Add task to the scheduler"""
|
|
101
|
+
if not task.task_id:
|
|
102
|
+
self._task_id_counter += 1
|
|
103
|
+
task.task_id = f"task_{self._task_id_counter}"
|
|
104
|
+
|
|
105
|
+
self._tasks[task.task_id] = task
|
|
106
|
+
task.status = TaskStatus.QUEUED
|
|
107
|
+
heapq.heappush(self._queue, task)
|
|
108
|
+
|
|
109
|
+
logger.debug(f"Task {task.task_id} added to queue with priority {task.priority.name}")
|
|
110
|
+
return task.task_id
|
|
111
|
+
|
|
112
|
+
def create_task(
|
|
113
|
+
self,
|
|
114
|
+
name: str,
|
|
115
|
+
handler: Callable[..., Awaitable[Any]],
|
|
116
|
+
priority: TaskPriority = TaskPriority.NORMAL,
|
|
117
|
+
**kwargs
|
|
118
|
+
) -> str:
|
|
119
|
+
"""Create and add a task"""
|
|
120
|
+
task = ScheduledTask(
|
|
121
|
+
priority=priority,
|
|
122
|
+
task_id="",
|
|
123
|
+
name=name,
|
|
124
|
+
handler=handler,
|
|
125
|
+
**kwargs
|
|
126
|
+
)
|
|
127
|
+
return self.add_task(task)
|
|
128
|
+
|
|
129
|
+
def cancel_task(self, task_id: str) -> bool:
|
|
130
|
+
"""Cancel a pending or running task"""
|
|
131
|
+
if task_id in self._running:
|
|
132
|
+
self._running[task_id].cancel()
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
if task_id in self._tasks:
|
|
136
|
+
task = self._tasks[task_id]
|
|
137
|
+
task.status = TaskStatus.CANCELLED
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def get_task(self, task_id: str) -> Optional[ScheduledTask]:
|
|
143
|
+
"""Get task by ID"""
|
|
144
|
+
return self._tasks.get(task_id) or self._completed.get(task_id)
|
|
145
|
+
|
|
146
|
+
def get_status(self) -> Dict[str, Any]:
|
|
147
|
+
"""Get scheduler status"""
|
|
148
|
+
return {
|
|
149
|
+
"queued": len(self._queue),
|
|
150
|
+
"running": len(self._running),
|
|
151
|
+
"completed": len(self._completed),
|
|
152
|
+
"total": len(self._tasks),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async def run(self) -> Dict[str, ScheduledTask]:
|
|
156
|
+
"""
|
|
157
|
+
Run the scheduler until all tasks complete.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Dict mapping task_id to completed task
|
|
161
|
+
"""
|
|
162
|
+
self._shutdown = False
|
|
163
|
+
|
|
164
|
+
while not self._shutdown:
|
|
165
|
+
# Check if we're done
|
|
166
|
+
if not self._queue and not self._running:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
# Start new tasks if we have capacity
|
|
170
|
+
while (
|
|
171
|
+
self._queue
|
|
172
|
+
and len(self._running) < self.max_concurrent
|
|
173
|
+
and self._can_run_next()
|
|
174
|
+
):
|
|
175
|
+
task = self._get_next_runnable_task()
|
|
176
|
+
if task:
|
|
177
|
+
await self._start_task(task)
|
|
178
|
+
else:
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
# Wait for any task to complete
|
|
182
|
+
if self._running:
|
|
183
|
+
done, _ = await asyncio.wait(
|
|
184
|
+
self._running.values(),
|
|
185
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
for future in done:
|
|
189
|
+
# Find the task_id for this future
|
|
190
|
+
for task_id, task_future in list(self._running.items()):
|
|
191
|
+
if task_future == future:
|
|
192
|
+
await self._handle_task_complete(task_id, future)
|
|
193
|
+
break
|
|
194
|
+
else:
|
|
195
|
+
await asyncio.sleep(0.1)
|
|
196
|
+
|
|
197
|
+
return self._completed
|
|
198
|
+
|
|
199
|
+
async def run_all(self, tasks: List[ScheduledTask]) -> Dict[str, ScheduledTask]:
|
|
200
|
+
"""Add all tasks and run until completion"""
|
|
201
|
+
for task in tasks:
|
|
202
|
+
self.add_task(task)
|
|
203
|
+
return await self.run()
|
|
204
|
+
|
|
205
|
+
def shutdown(self) -> None:
|
|
206
|
+
"""Signal scheduler to stop after current tasks"""
|
|
207
|
+
self._shutdown = True
|
|
208
|
+
|
|
209
|
+
async def _start_task(self, task: ScheduledTask) -> None:
|
|
210
|
+
"""Start executing a task"""
|
|
211
|
+
task.status = TaskStatus.RUNNING
|
|
212
|
+
|
|
213
|
+
if self.on_task_start:
|
|
214
|
+
self.on_task_start(task)
|
|
215
|
+
|
|
216
|
+
# Respect rate limit
|
|
217
|
+
if self.rate_limit:
|
|
218
|
+
import time
|
|
219
|
+
now = time.time()
|
|
220
|
+
min_interval = 1.0 / self.rate_limit
|
|
221
|
+
if now - self._last_task_time < min_interval:
|
|
222
|
+
await asyncio.sleep(min_interval - (now - self._last_task_time))
|
|
223
|
+
self._last_task_time = time.time()
|
|
224
|
+
|
|
225
|
+
# Create async task
|
|
226
|
+
async_task = asyncio.create_task(
|
|
227
|
+
self._execute_task(task)
|
|
228
|
+
)
|
|
229
|
+
self._running[task.task_id] = async_task
|
|
230
|
+
|
|
231
|
+
async def _execute_task(self, task: ScheduledTask) -> Any:
|
|
232
|
+
"""Execute task with timeout and retries"""
|
|
233
|
+
retries = 0
|
|
234
|
+
last_error = None
|
|
235
|
+
|
|
236
|
+
while retries <= task.retries:
|
|
237
|
+
try:
|
|
238
|
+
result = await asyncio.wait_for(
|
|
239
|
+
task.handler(*task.args, **task.kwargs),
|
|
240
|
+
timeout=task.timeout,
|
|
241
|
+
)
|
|
242
|
+
task.result = result
|
|
243
|
+
task.status = TaskStatus.COMPLETED
|
|
244
|
+
return result
|
|
245
|
+
|
|
246
|
+
except asyncio.TimeoutError:
|
|
247
|
+
last_error = f"Task timed out after {task.timeout}s"
|
|
248
|
+
except asyncio.CancelledError:
|
|
249
|
+
task.status = TaskStatus.CANCELLED
|
|
250
|
+
raise
|
|
251
|
+
except Exception as e:
|
|
252
|
+
last_error = str(e)
|
|
253
|
+
logger.error(f"Task {task.task_id} failed: {last_error}")
|
|
254
|
+
|
|
255
|
+
retries += 1
|
|
256
|
+
|
|
257
|
+
task.status = TaskStatus.FAILED
|
|
258
|
+
task.error = last_error
|
|
259
|
+
raise Exception(last_error)
|
|
260
|
+
|
|
261
|
+
async def _handle_task_complete(self, task_id: str, future: asyncio.Task) -> None:
|
|
262
|
+
"""Handle task completion"""
|
|
263
|
+
del self._running[task_id]
|
|
264
|
+
task = self._tasks.pop(task_id, None)
|
|
265
|
+
|
|
266
|
+
if task:
|
|
267
|
+
try:
|
|
268
|
+
future.result()
|
|
269
|
+
except Exception:
|
|
270
|
+
pass # Error already recorded in task
|
|
271
|
+
|
|
272
|
+
self._completed[task_id] = task
|
|
273
|
+
|
|
274
|
+
if self.on_task_complete:
|
|
275
|
+
self.on_task_complete(task)
|
|
276
|
+
|
|
277
|
+
def _get_next_runnable_task(self) -> Optional[ScheduledTask]:
|
|
278
|
+
"""Get next task that has all dependencies met"""
|
|
279
|
+
for i, task in enumerate(self._queue):
|
|
280
|
+
if task.status == TaskStatus.CANCELLED:
|
|
281
|
+
self._queue.pop(i)
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
deps_met = all(
|
|
285
|
+
dep_id in self._completed and self._completed[dep_id].status == TaskStatus.COMPLETED
|
|
286
|
+
for dep_id in task.depends_on
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if deps_met:
|
|
290
|
+
return heapq.heappop(self._queue)
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def _can_run_next(self) -> bool:
|
|
295
|
+
"""Check if we can run the next task"""
|
|
296
|
+
return len(self._running) < self.max_concurrent
|