proxilion 0.0.1__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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Priority queue implementation for request scheduling.
|
|
3
|
+
|
|
4
|
+
Provides a thread-safe priority queue with aging mechanism
|
|
5
|
+
to prevent starvation of low-priority requests.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import heapq
|
|
11
|
+
import logging
|
|
12
|
+
import threading
|
|
13
|
+
import uuid
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from enum import IntEnum
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PriorityLevel(IntEnum):
|
|
24
|
+
"""
|
|
25
|
+
Request priority levels.
|
|
26
|
+
|
|
27
|
+
Lower values = higher priority.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
CRITICAL: Highest priority, processed immediately.
|
|
31
|
+
HIGH: Important requests, processed before normal.
|
|
32
|
+
NORMAL: Default priority level.
|
|
33
|
+
LOW: Background tasks, can be delayed.
|
|
34
|
+
BACKGROUND: Lowest priority, processed when idle.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
CRITICAL = 0
|
|
38
|
+
HIGH = 1
|
|
39
|
+
NORMAL = 2
|
|
40
|
+
LOW = 3
|
|
41
|
+
BACKGROUND = 4
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(order=True)
|
|
45
|
+
class QueuedRequest:
|
|
46
|
+
"""
|
|
47
|
+
A request queued for processing.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
id: Unique identifier for the request.
|
|
51
|
+
priority: Priority level for ordering.
|
|
52
|
+
created_at: When the request was created.
|
|
53
|
+
timeout: Maximum time to wait in queue (seconds).
|
|
54
|
+
payload: Request data to process.
|
|
55
|
+
callback: Optional callback when processed.
|
|
56
|
+
metadata: Additional metadata about the request.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> request = QueuedRequest(
|
|
60
|
+
... id="req-123",
|
|
61
|
+
... priority=PriorityLevel.HIGH,
|
|
62
|
+
... payload={"tool": "search", "args": {"query": "test"}},
|
|
63
|
+
... timeout=30.0,
|
|
64
|
+
... )
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Sort key: (priority, created_at) - lower priority value and earlier time wins
|
|
68
|
+
sort_key: tuple[int, float] = field(init=False, repr=False, compare=True)
|
|
69
|
+
|
|
70
|
+
id: str = field(default_factory=lambda: str(uuid.uuid4()), compare=False)
|
|
71
|
+
priority: PriorityLevel = field(default=PriorityLevel.NORMAL, compare=False)
|
|
72
|
+
created_at: datetime = field(
|
|
73
|
+
default_factory=lambda: datetime.now(timezone.utc), compare=False
|
|
74
|
+
)
|
|
75
|
+
timeout: float | None = field(default=None, compare=False)
|
|
76
|
+
payload: Any = field(default=None, compare=False)
|
|
77
|
+
callback: Callable[[Any], None] | None = field(default=None, compare=False)
|
|
78
|
+
metadata: dict[str, Any] = field(default_factory=dict, compare=False)
|
|
79
|
+
|
|
80
|
+
# Track original priority for aging
|
|
81
|
+
_original_priority: PriorityLevel = field(init=False, repr=False, compare=False)
|
|
82
|
+
|
|
83
|
+
def __post_init__(self) -> None:
|
|
84
|
+
"""Initialize sort key and original priority."""
|
|
85
|
+
self._original_priority = self.priority
|
|
86
|
+
self._update_sort_key()
|
|
87
|
+
|
|
88
|
+
def _update_sort_key(self) -> None:
|
|
89
|
+
"""Update sort key based on current priority and creation time."""
|
|
90
|
+
self.sort_key = (self.priority.value, self.created_at.timestamp())
|
|
91
|
+
|
|
92
|
+
def boost_priority(self, levels: int = 1) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Boost the priority of this request.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
levels: Number of priority levels to boost.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if priority was boosted, False if already at highest.
|
|
101
|
+
"""
|
|
102
|
+
new_value = max(0, self.priority.value - levels)
|
|
103
|
+
if new_value < self.priority.value:
|
|
104
|
+
self.priority = PriorityLevel(new_value)
|
|
105
|
+
self._update_sort_key()
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def is_expired(self) -> bool:
|
|
110
|
+
"""Check if the request has exceeded its timeout."""
|
|
111
|
+
if self.timeout is None:
|
|
112
|
+
return False
|
|
113
|
+
age = (datetime.now(timezone.utc) - self.created_at).total_seconds()
|
|
114
|
+
return age > self.timeout
|
|
115
|
+
|
|
116
|
+
def age_seconds(self) -> float:
|
|
117
|
+
"""Get the age of this request in seconds."""
|
|
118
|
+
return (datetime.now(timezone.utc) - self.created_at).total_seconds()
|
|
119
|
+
|
|
120
|
+
def to_dict(self) -> dict[str, Any]:
|
|
121
|
+
"""Convert to dictionary representation."""
|
|
122
|
+
return {
|
|
123
|
+
"id": self.id,
|
|
124
|
+
"priority": self.priority.name,
|
|
125
|
+
"original_priority": self._original_priority.name,
|
|
126
|
+
"created_at": self.created_at.isoformat(),
|
|
127
|
+
"timeout": self.timeout,
|
|
128
|
+
"age_seconds": self.age_seconds(),
|
|
129
|
+
"is_expired": self.is_expired(),
|
|
130
|
+
"metadata": self.metadata,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class PriorityQueue:
|
|
135
|
+
"""
|
|
136
|
+
Thread-safe priority queue with aging mechanism.
|
|
137
|
+
|
|
138
|
+
Implements a priority queue that prevents starvation of
|
|
139
|
+
low-priority requests by periodically boosting their priority.
|
|
140
|
+
|
|
141
|
+
Attributes:
|
|
142
|
+
max_size: Maximum number of requests in queue.
|
|
143
|
+
aging_interval: Seconds before priority boost.
|
|
144
|
+
aging_boost: Priority levels to boost on aging.
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
>>> queue = PriorityQueue(max_size=100, aging_interval=60.0)
|
|
148
|
+
>>>
|
|
149
|
+
>>> # Enqueue requests
|
|
150
|
+
>>> queue.enqueue(QueuedRequest(priority=PriorityLevel.LOW, payload="task1"))
|
|
151
|
+
>>> queue.enqueue(QueuedRequest(priority=PriorityLevel.HIGH, payload="task2"))
|
|
152
|
+
>>>
|
|
153
|
+
>>> # High priority dequeued first
|
|
154
|
+
>>> req = queue.dequeue()
|
|
155
|
+
>>> assert req.payload == "task2"
|
|
156
|
+
>>>
|
|
157
|
+
>>> # Check queue stats
|
|
158
|
+
>>> print(queue.size_by_priority())
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
max_size: int = 1000,
|
|
164
|
+
aging_interval: float = 60.0,
|
|
165
|
+
aging_boost: int = 1,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Initialize the priority queue.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
max_size: Maximum number of requests in queue.
|
|
172
|
+
aging_interval: Seconds before priority boost.
|
|
173
|
+
aging_boost: Priority levels to boost on aging.
|
|
174
|
+
"""
|
|
175
|
+
self.max_size = max_size
|
|
176
|
+
self.aging_interval = aging_interval
|
|
177
|
+
self.aging_boost = aging_boost
|
|
178
|
+
|
|
179
|
+
self._heap: list[QueuedRequest] = []
|
|
180
|
+
self._lock = threading.RLock()
|
|
181
|
+
self._not_empty = threading.Condition(self._lock)
|
|
182
|
+
self._not_full = threading.Condition(self._lock)
|
|
183
|
+
|
|
184
|
+
# Track requests by ID for fast lookup
|
|
185
|
+
self._request_map: dict[str, QueuedRequest] = {}
|
|
186
|
+
|
|
187
|
+
# Statistics
|
|
188
|
+
self._total_enqueued = 0
|
|
189
|
+
self._total_dequeued = 0
|
|
190
|
+
self._total_expired = 0
|
|
191
|
+
self._total_aged = 0
|
|
192
|
+
|
|
193
|
+
def enqueue(
|
|
194
|
+
self,
|
|
195
|
+
request: QueuedRequest,
|
|
196
|
+
block: bool = True,
|
|
197
|
+
timeout: float | None = None,
|
|
198
|
+
) -> bool:
|
|
199
|
+
"""
|
|
200
|
+
Add a request to the queue.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
request: The request to enqueue.
|
|
204
|
+
block: Whether to block if queue is full.
|
|
205
|
+
timeout: Maximum time to wait (seconds).
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
True if enqueued, False if queue is full and not blocking.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If request with same ID already exists.
|
|
212
|
+
"""
|
|
213
|
+
with self._not_full:
|
|
214
|
+
if request.id in self._request_map:
|
|
215
|
+
raise ValueError(f"Request with ID {request.id} already in queue")
|
|
216
|
+
|
|
217
|
+
while len(self._heap) >= self.max_size:
|
|
218
|
+
if not block:
|
|
219
|
+
return False
|
|
220
|
+
if not self._not_full.wait(timeout):
|
|
221
|
+
return False # Timeout
|
|
222
|
+
|
|
223
|
+
heapq.heappush(self._heap, request)
|
|
224
|
+
self._request_map[request.id] = request
|
|
225
|
+
self._total_enqueued += 1
|
|
226
|
+
|
|
227
|
+
self._not_empty.notify()
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
def dequeue(
|
|
231
|
+
self,
|
|
232
|
+
block: bool = True,
|
|
233
|
+
timeout: float | None = None,
|
|
234
|
+
) -> QueuedRequest | None:
|
|
235
|
+
"""
|
|
236
|
+
Remove and return the highest priority request.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
block: Whether to block if queue is empty.
|
|
240
|
+
timeout: Maximum time to wait (seconds).
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The highest priority request, or None if empty/timeout.
|
|
244
|
+
"""
|
|
245
|
+
with self._not_empty:
|
|
246
|
+
while not self._heap:
|
|
247
|
+
if not block:
|
|
248
|
+
return None
|
|
249
|
+
if not self._not_empty.wait(timeout):
|
|
250
|
+
return None # Timeout
|
|
251
|
+
|
|
252
|
+
# Age requests before dequeuing
|
|
253
|
+
self._age_requests()
|
|
254
|
+
|
|
255
|
+
# Remove expired requests
|
|
256
|
+
self._remove_expired()
|
|
257
|
+
|
|
258
|
+
if not self._heap:
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
# Re-heapify after aging may have changed priorities
|
|
262
|
+
heapq.heapify(self._heap)
|
|
263
|
+
|
|
264
|
+
request = heapq.heappop(self._heap)
|
|
265
|
+
self._request_map.pop(request.id, None)
|
|
266
|
+
self._total_dequeued += 1
|
|
267
|
+
|
|
268
|
+
self._not_full.notify()
|
|
269
|
+
return request
|
|
270
|
+
|
|
271
|
+
def peek(self) -> QueuedRequest | None:
|
|
272
|
+
"""
|
|
273
|
+
View the highest priority request without removing it.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
The highest priority request, or None if empty.
|
|
277
|
+
"""
|
|
278
|
+
with self._lock:
|
|
279
|
+
if not self._heap:
|
|
280
|
+
return None
|
|
281
|
+
return self._heap[0]
|
|
282
|
+
|
|
283
|
+
def size(self) -> int:
|
|
284
|
+
"""Get the current queue size."""
|
|
285
|
+
with self._lock:
|
|
286
|
+
return len(self._heap)
|
|
287
|
+
|
|
288
|
+
def is_empty(self) -> bool:
|
|
289
|
+
"""Check if the queue is empty."""
|
|
290
|
+
return self.size() == 0
|
|
291
|
+
|
|
292
|
+
def is_full(self) -> bool:
|
|
293
|
+
"""Check if the queue is full."""
|
|
294
|
+
return self.size() >= self.max_size
|
|
295
|
+
|
|
296
|
+
def size_by_priority(self) -> dict[PriorityLevel, int]:
|
|
297
|
+
"""
|
|
298
|
+
Get queue size breakdown by priority level.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Dictionary mapping priority levels to counts.
|
|
302
|
+
"""
|
|
303
|
+
with self._lock:
|
|
304
|
+
counts: dict[PriorityLevel, int] = dict.fromkeys(PriorityLevel, 0)
|
|
305
|
+
for request in self._heap:
|
|
306
|
+
counts[request.priority] += 1
|
|
307
|
+
return counts
|
|
308
|
+
|
|
309
|
+
def get_request(self, request_id: str) -> QueuedRequest | None:
|
|
310
|
+
"""
|
|
311
|
+
Get a request by ID without removing it.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
request_id: The request ID to find.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
The request if found, None otherwise.
|
|
318
|
+
"""
|
|
319
|
+
with self._lock:
|
|
320
|
+
return self._request_map.get(request_id)
|
|
321
|
+
|
|
322
|
+
def remove(self, request_id: str) -> bool:
|
|
323
|
+
"""
|
|
324
|
+
Remove a specific request from the queue.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
request_id: The request ID to remove.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
True if removed, False if not found.
|
|
331
|
+
"""
|
|
332
|
+
with self._lock:
|
|
333
|
+
if request_id not in self._request_map:
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
request = self._request_map.pop(request_id)
|
|
337
|
+
self._heap.remove(request)
|
|
338
|
+
heapq.heapify(self._heap)
|
|
339
|
+
|
|
340
|
+
self._not_full.notify()
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
def clear(self) -> int:
|
|
344
|
+
"""
|
|
345
|
+
Clear all requests from the queue.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
Number of requests cleared.
|
|
349
|
+
"""
|
|
350
|
+
with self._lock:
|
|
351
|
+
count = len(self._heap)
|
|
352
|
+
self._heap.clear()
|
|
353
|
+
self._request_map.clear()
|
|
354
|
+
self._not_full.notify_all()
|
|
355
|
+
return count
|
|
356
|
+
|
|
357
|
+
def _age_requests(self) -> None:
|
|
358
|
+
"""Boost priority of old requests to prevent starvation."""
|
|
359
|
+
now = datetime.now(timezone.utc)
|
|
360
|
+
aged_count = 0
|
|
361
|
+
|
|
362
|
+
for request in self._heap:
|
|
363
|
+
age = (now - request.created_at).total_seconds()
|
|
364
|
+
# Calculate how many aging intervals have passed
|
|
365
|
+
intervals_passed = int(age / self.aging_interval)
|
|
366
|
+
# Calculate target priority based on intervals
|
|
367
|
+
aging_amount = intervals_passed * self.aging_boost
|
|
368
|
+
target_value = max(0, request._original_priority.value - aging_amount)
|
|
369
|
+
|
|
370
|
+
if target_value < request.priority.value:
|
|
371
|
+
request.priority = PriorityLevel(target_value)
|
|
372
|
+
request._update_sort_key()
|
|
373
|
+
aged_count += 1
|
|
374
|
+
|
|
375
|
+
if aged_count > 0:
|
|
376
|
+
self._total_aged += aged_count
|
|
377
|
+
logger.debug(f"Aged {aged_count} requests in queue")
|
|
378
|
+
|
|
379
|
+
def _remove_expired(self) -> None:
|
|
380
|
+
"""Remove expired requests from the queue."""
|
|
381
|
+
expired = [r for r in self._heap if r.is_expired()]
|
|
382
|
+
for request in expired:
|
|
383
|
+
self._heap.remove(request)
|
|
384
|
+
self._request_map.pop(request.id, None)
|
|
385
|
+
self._total_expired += 1
|
|
386
|
+
logger.debug(f"Removed expired request: {request.id}")
|
|
387
|
+
|
|
388
|
+
if expired:
|
|
389
|
+
heapq.heapify(self._heap)
|
|
390
|
+
|
|
391
|
+
def get_stats(self) -> dict[str, Any]:
|
|
392
|
+
"""
|
|
393
|
+
Get queue statistics.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Dictionary with queue statistics.
|
|
397
|
+
"""
|
|
398
|
+
with self._lock:
|
|
399
|
+
return {
|
|
400
|
+
"current_size": len(self._heap),
|
|
401
|
+
"max_size": self.max_size,
|
|
402
|
+
"total_enqueued": self._total_enqueued,
|
|
403
|
+
"total_dequeued": self._total_dequeued,
|
|
404
|
+
"total_expired": self._total_expired,
|
|
405
|
+
"total_aged": self._total_aged,
|
|
406
|
+
"size_by_priority": {
|
|
407
|
+
level.name: count
|
|
408
|
+
for level, count in self.size_by_priority().items()
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
def __len__(self) -> int:
|
|
413
|
+
"""Get queue size."""
|
|
414
|
+
return self.size()
|
|
415
|
+
|
|
416
|
+
def __contains__(self, request_id: str) -> bool:
|
|
417
|
+
"""Check if a request ID is in the queue."""
|
|
418
|
+
with self._lock:
|
|
419
|
+
return request_id in self._request_map
|