unrealon 1.1.5__py3-none-any.whl → 2.0.4__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.
- {unrealon-1.1.5.dist-info/licenses → unrealon-2.0.4.dist-info}/LICENSE +1 -1
- unrealon-2.0.4.dist-info/METADATA +491 -0
- unrealon-2.0.4.dist-info/RECORD +129 -0
- {unrealon-1.1.5.dist-info → unrealon-2.0.4.dist-info}/WHEEL +2 -1
- unrealon-2.0.4.dist-info/entry_points.txt +3 -0
- unrealon-2.0.4.dist-info/top_level.txt +3 -0
- unrealon_browser/__init__.py +5 -2
- unrealon_browser/cli/browser_cli.py +18 -9
- unrealon_browser/cli/interactive_mode.py +18 -7
- unrealon_browser/core/browser_manager.py +76 -13
- unrealon_browser/dto/__init__.py +21 -0
- unrealon_browser/dto/bot_detection.py +175 -0
- unrealon_browser/dto/models/config.py +14 -1
- unrealon_browser/managers/__init__.py +4 -1
- unrealon_browser/managers/logger_bridge.py +3 -6
- unrealon_browser/managers/page_wait_manager.py +198 -0
- unrealon_browser/stealth/__init__.py +27 -0
- unrealon_browser/stealth/bypass_techniques.pyc +0 -0
- unrealon_browser/stealth/manager.pyc +0 -0
- unrealon_browser/stealth/nodriver_stealth.pyc +0 -0
- unrealon_browser/stealth/playwright_stealth.pyc +0 -0
- unrealon_browser/stealth/scanner_tester.pyc +0 -0
- unrealon_browser/stealth/undetected_chrome.pyc +0 -0
- unrealon_core/__init__.py +160 -0
- unrealon_core/config/__init__.py +16 -0
- unrealon_core/config/environment.py +98 -0
- unrealon_core/config/urls.py +93 -0
- unrealon_core/enums/__init__.py +24 -0
- unrealon_core/enums/status.py +216 -0
- unrealon_core/enums/types.py +240 -0
- unrealon_core/error_handling/__init__.py +45 -0
- unrealon_core/error_handling/circuit_breaker.py +292 -0
- unrealon_core/error_handling/error_context.py +324 -0
- unrealon_core/error_handling/recovery.py +371 -0
- unrealon_core/error_handling/retry.py +268 -0
- unrealon_core/exceptions/__init__.py +46 -0
- unrealon_core/exceptions/base.py +292 -0
- unrealon_core/exceptions/communication.py +22 -0
- unrealon_core/exceptions/driver.py +11 -0
- unrealon_core/exceptions/proxy.py +11 -0
- unrealon_core/exceptions/task.py +12 -0
- unrealon_core/exceptions/validation.py +17 -0
- unrealon_core/models/__init__.py +98 -0
- unrealon_core/models/arq_context.py +252 -0
- unrealon_core/models/arq_responses.py +125 -0
- unrealon_core/models/base.py +291 -0
- unrealon_core/models/bridge_stats.py +58 -0
- unrealon_core/models/communication.py +39 -0
- unrealon_core/models/config.py +47 -0
- unrealon_core/models/connection_stats.py +47 -0
- unrealon_core/models/driver.py +30 -0
- unrealon_core/models/driver_details.py +98 -0
- unrealon_core/models/logging.py +28 -0
- unrealon_core/models/task.py +21 -0
- unrealon_core/models/typed_responses.py +210 -0
- unrealon_core/models/websocket/__init__.py +91 -0
- unrealon_core/models/websocket/base.py +49 -0
- unrealon_core/models/websocket/config.py +200 -0
- unrealon_core/models/websocket/driver.py +215 -0
- unrealon_core/models/websocket/errors.py +138 -0
- unrealon_core/models/websocket/heartbeat.py +100 -0
- unrealon_core/models/websocket/logging.py +261 -0
- unrealon_core/models/websocket/proxy.py +496 -0
- unrealon_core/models/websocket/tasks.py +275 -0
- unrealon_core/models/websocket/utils.py +153 -0
- unrealon_core/models/websocket_session.py +144 -0
- unrealon_core/monitoring/__init__.py +43 -0
- unrealon_core/monitoring/alerts.py +398 -0
- unrealon_core/monitoring/dashboard.py +307 -0
- unrealon_core/monitoring/health_check.py +354 -0
- unrealon_core/monitoring/metrics.py +352 -0
- unrealon_core/utils/__init__.py +11 -0
- unrealon_core/utils/time.py +61 -0
- unrealon_core/version.py +219 -0
- unrealon_driver/__init__.py +88 -50
- unrealon_driver/core_module/__init__.py +34 -0
- unrealon_driver/core_module/base.py +184 -0
- unrealon_driver/core_module/config.py +30 -0
- unrealon_driver/core_module/event_manager.py +127 -0
- unrealon_driver/core_module/protocols.py +98 -0
- unrealon_driver/core_module/registry.py +146 -0
- unrealon_driver/decorators/__init__.py +15 -0
- unrealon_driver/decorators/retry.py +117 -0
- unrealon_driver/decorators/schedule.py +137 -0
- unrealon_driver/decorators/task.py +61 -0
- unrealon_driver/decorators/timing.py +132 -0
- unrealon_driver/driver/__init__.py +20 -0
- unrealon_driver/driver/communication/__init__.py +10 -0
- unrealon_driver/driver/communication/session.py +203 -0
- unrealon_driver/driver/communication/websocket_client.py +197 -0
- unrealon_driver/driver/core/__init__.py +10 -0
- unrealon_driver/driver/core/config.py +85 -0
- unrealon_driver/driver/core/driver.py +221 -0
- unrealon_driver/driver/factory/__init__.py +9 -0
- unrealon_driver/driver/factory/manager_factory.py +130 -0
- unrealon_driver/driver/lifecycle/__init__.py +11 -0
- unrealon_driver/driver/lifecycle/daemon.py +76 -0
- unrealon_driver/driver/lifecycle/initialization.py +97 -0
- unrealon_driver/driver/lifecycle/shutdown.py +48 -0
- unrealon_driver/driver/monitoring/__init__.py +9 -0
- unrealon_driver/driver/monitoring/health.py +63 -0
- unrealon_driver/driver/utilities/__init__.py +10 -0
- unrealon_driver/driver/utilities/logging.py +51 -0
- unrealon_driver/driver/utilities/serialization.py +61 -0
- unrealon_driver/managers/__init__.py +32 -0
- unrealon_driver/managers/base.py +174 -0
- unrealon_driver/managers/browser.py +98 -0
- unrealon_driver/managers/cache.py +116 -0
- unrealon_driver/managers/http.py +107 -0
- unrealon_driver/managers/logger.py +286 -0
- unrealon_driver/managers/proxy.py +99 -0
- unrealon_driver/managers/registry.py +87 -0
- unrealon_driver/managers/threading.py +54 -0
- unrealon_driver/managers/update.py +107 -0
- unrealon_driver/utils/__init__.py +9 -0
- unrealon_driver/utils/time.py +10 -0
- unrealon/__init__.py +0 -40
- unrealon-1.1.5.dist-info/METADATA +0 -621
- unrealon-1.1.5.dist-info/RECORD +0 -54
- unrealon-1.1.5.dist-info/entry_points.txt +0 -9
- unrealon_browser/managers/stealth.py +0 -388
- unrealon_driver/exceptions.py +0 -33
- unrealon_driver/html_analyzer/__init__.py +0 -32
- unrealon_driver/html_analyzer/cleaner.py +0 -657
- unrealon_driver/html_analyzer/config.py +0 -64
- unrealon_driver/html_analyzer/manager.py +0 -247
- unrealon_driver/html_analyzer/models.py +0 -115
- unrealon_driver/html_analyzer/websocket_analyzer.py +0 -157
- unrealon_driver/models/__init__.py +0 -31
- unrealon_driver/models/websocket.py +0 -98
- unrealon_driver/parser/__init__.py +0 -36
- unrealon_driver/parser/cli_manager.py +0 -142
- unrealon_driver/parser/daemon_manager.py +0 -403
- unrealon_driver/parser/managers/__init__.py +0 -25
- unrealon_driver/parser/managers/config.py +0 -293
- unrealon_driver/parser/managers/error.py +0 -412
- unrealon_driver/parser/managers/result.py +0 -321
- unrealon_driver/parser/parser_manager.py +0 -458
- unrealon_driver/smart_logging/__init__.py +0 -24
- unrealon_driver/smart_logging/models.py +0 -44
- unrealon_driver/smart_logging/smart_logger.py +0 -406
- unrealon_driver/smart_logging/unified_logger.py +0 -525
- unrealon_driver/websocket/__init__.py +0 -31
- unrealon_driver/websocket/client.py +0 -249
- unrealon_driver/websocket/config.py +0 -188
- unrealon_driver/websocket/manager.py +0 -90
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ARQ Context Models
|
|
3
|
+
|
|
4
|
+
Strictly typed models for ARQ worker context and job parameters.
|
|
5
|
+
Following critical requirements - no raw Dict[str, Any].
|
|
6
|
+
|
|
7
|
+
Phase 2: Core Systems - ARQ Context
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
13
|
+
|
|
14
|
+
from .base import UnrealOnBaseModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ARQWorkerStats(UnrealOnBaseModel):
|
|
18
|
+
"""ARQ worker statistics."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(
|
|
21
|
+
validate_assignment=True,
|
|
22
|
+
extra="forbid"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
processed_jobs: int = Field(default=0, description="Number of processed jobs")
|
|
26
|
+
failed_jobs: int = Field(default=0, description="Number of failed jobs")
|
|
27
|
+
start_time: datetime = Field(description="Worker start time")
|
|
28
|
+
websocket_messages_sent: int = Field(default=0, description="WebSocket messages sent")
|
|
29
|
+
websocket_messages_failed: int = Field(default=0, description="Failed WebSocket messages")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ARQWorkerContext(UnrealOnBaseModel):
|
|
33
|
+
"""ARQ worker context with strict typing."""
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(
|
|
36
|
+
validate_assignment=True,
|
|
37
|
+
extra="forbid"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
stats: ARQWorkerStats = Field(description="Worker statistics")
|
|
41
|
+
websocket_bridge: Optional[object] = Field(default=None, description="WebSocket bridge instance")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TaskAssignmentParams(UnrealOnBaseModel):
|
|
45
|
+
"""Parameters for task assignment job."""
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(
|
|
48
|
+
validate_assignment=True,
|
|
49
|
+
extra="forbid"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
task_type: str = Field(description="Type of task")
|
|
53
|
+
task_data: "TaskDataModel" = Field(description="Task data")
|
|
54
|
+
driver_id: Optional[str] = Field(default=None, description="Target driver ID")
|
|
55
|
+
priority: str = Field(default="normal", description="Task priority")
|
|
56
|
+
timeout: float = Field(default=300.0, description="Task timeout in seconds")
|
|
57
|
+
retry_count: int = Field(default=3, description="Number of retries")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TaskDataModel(UnrealOnBaseModel):
|
|
61
|
+
"""Strictly typed task data."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(
|
|
64
|
+
validate_assignment=True,
|
|
65
|
+
extra="forbid"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
task_id: str = Field(description="Unique task identifier")
|
|
69
|
+
url: str = Field(description="Target URL")
|
|
70
|
+
parser_type: Optional[str] = Field(default=None, description="Parser type")
|
|
71
|
+
options: Optional["TaskOptionsModel"] = Field(default=None, description="Task options")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TaskOptionsModel(UnrealOnBaseModel):
|
|
75
|
+
"""Task options model."""
|
|
76
|
+
|
|
77
|
+
model_config = ConfigDict(
|
|
78
|
+
validate_assignment=True,
|
|
79
|
+
extra="forbid"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
timeout_seconds: Optional[float] = Field(default=None, description="Request timeout")
|
|
83
|
+
user_agent: Optional[str] = Field(default=None, description="User agent string")
|
|
84
|
+
headers: Optional["HeadersModel"] = Field(default=None, description="HTTP headers")
|
|
85
|
+
proxy_enabled: bool = Field(default=True, description="Whether to use proxy")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class HeadersModel(UnrealOnBaseModel):
|
|
89
|
+
"""HTTP headers model."""
|
|
90
|
+
|
|
91
|
+
model_config = ConfigDict(
|
|
92
|
+
validate_assignment=True,
|
|
93
|
+
extra="forbid"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
accept: Optional[str] = Field(default=None, description="Accept header")
|
|
97
|
+
accept_language: Optional[str] = Field(default=None, description="Accept-Language header")
|
|
98
|
+
referer: Optional[str] = Field(default=None, description="Referer header")
|
|
99
|
+
authorization: Optional[str] = Field(default=None, description="Authorization header")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TaskResultParams(UnrealOnBaseModel):
|
|
103
|
+
"""Parameters for task result processing."""
|
|
104
|
+
|
|
105
|
+
model_config = ConfigDict(
|
|
106
|
+
validate_assignment=True,
|
|
107
|
+
extra="forbid"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
driver_id: str = Field(description="Driver ID")
|
|
111
|
+
connection_id: str = Field(description="WebSocket connection ID")
|
|
112
|
+
result_data: "TaskResultDataModel" = Field(description="Task result data")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TaskResultDataModel(UnrealOnBaseModel):
|
|
116
|
+
"""Task result data model."""
|
|
117
|
+
|
|
118
|
+
model_config = ConfigDict(
|
|
119
|
+
validate_assignment=True,
|
|
120
|
+
extra="forbid"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
task_id: str = Field(description="Task ID")
|
|
124
|
+
status: str = Field(description="Task status")
|
|
125
|
+
result_data: Optional["ParsedDataModel"] = Field(default=None, description="Parsed data")
|
|
126
|
+
error_message: Optional[str] = Field(default=None, description="Error message if failed")
|
|
127
|
+
execution_time_seconds: float = Field(description="Execution time")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ParsedDataModel(UnrealOnBaseModel):
|
|
131
|
+
"""Parsed data model."""
|
|
132
|
+
|
|
133
|
+
model_config = ConfigDict(
|
|
134
|
+
validate_assignment=True,
|
|
135
|
+
extra="forbid"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
title: Optional[str] = Field(default=None, description="Page title")
|
|
139
|
+
content: Optional[str] = Field(default=None, description="Page content")
|
|
140
|
+
links: Optional[list[str]] = Field(default=None, description="Extracted links")
|
|
141
|
+
images: Optional[list[str]] = Field(default=None, description="Extracted images")
|
|
142
|
+
metadata: Optional["MetadataModel"] = Field(default=None, description="Page metadata")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MetadataModel(UnrealOnBaseModel):
|
|
146
|
+
"""Page metadata model."""
|
|
147
|
+
|
|
148
|
+
model_config = ConfigDict(
|
|
149
|
+
validate_assignment=True,
|
|
150
|
+
extra="forbid"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
description: Optional[str] = Field(default=None, description="Page description")
|
|
154
|
+
keywords: Optional[str] = Field(default=None, description="Page keywords")
|
|
155
|
+
author: Optional[str] = Field(default=None, description="Page author")
|
|
156
|
+
language: Optional[str] = Field(default=None, description="Page language")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class LogEntryParams(UnrealOnBaseModel):
|
|
160
|
+
"""Parameters for log entry processing."""
|
|
161
|
+
|
|
162
|
+
model_config = ConfigDict(
|
|
163
|
+
validate_assignment=True,
|
|
164
|
+
extra="forbid"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
driver_id: str = Field(description="Driver ID")
|
|
168
|
+
connection_id: str = Field(description="WebSocket connection ID")
|
|
169
|
+
log_data: "LogDataModel" = Field(description="Log data")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class LogDataModel(UnrealOnBaseModel):
|
|
173
|
+
"""Log data model."""
|
|
174
|
+
|
|
175
|
+
model_config = ConfigDict(
|
|
176
|
+
validate_assignment=True,
|
|
177
|
+
extra="forbid"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
level: str = Field(description="Log level")
|
|
181
|
+
message: str = Field(description="Log message")
|
|
182
|
+
logger_name: str = Field(description="Logger name")
|
|
183
|
+
module: str = Field(description="Module name")
|
|
184
|
+
timestamp: datetime = Field(description="Log timestamp")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class DriverRegistrationParams(UnrealOnBaseModel):
|
|
188
|
+
"""Parameters for driver registration."""
|
|
189
|
+
|
|
190
|
+
model_config = ConfigDict(
|
|
191
|
+
validate_assignment=True,
|
|
192
|
+
extra="forbid"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
connection_id: str = Field(description="WebSocket connection ID")
|
|
196
|
+
driver_data: "DriverDataModel" = Field(description="Driver data")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class DriverDataModel(UnrealOnBaseModel):
|
|
200
|
+
"""Driver data model."""
|
|
201
|
+
|
|
202
|
+
model_config = ConfigDict(
|
|
203
|
+
validate_assignment=True,
|
|
204
|
+
extra="forbid"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
driver_id: str = Field(description="Driver ID")
|
|
208
|
+
driver_type: str = Field(description="Driver type")
|
|
209
|
+
version: str = Field(description="Driver version")
|
|
210
|
+
capabilities: list[str] = Field(description="Driver capabilities")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class HeartbeatParams(UnrealOnBaseModel):
|
|
214
|
+
"""Parameters for heartbeat processing."""
|
|
215
|
+
|
|
216
|
+
model_config = ConfigDict(
|
|
217
|
+
validate_assignment=True,
|
|
218
|
+
extra="forbid"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
driver_id: str = Field(description="Driver ID")
|
|
222
|
+
connection_id: str = Field(description="WebSocket connection ID")
|
|
223
|
+
heartbeat_data: "HeartbeatDataModel" = Field(description="Heartbeat data")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class HeartbeatDataModel(UnrealOnBaseModel):
|
|
227
|
+
"""Heartbeat data model."""
|
|
228
|
+
|
|
229
|
+
model_config = ConfigDict(
|
|
230
|
+
validate_assignment=True,
|
|
231
|
+
extra="forbid"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
driver_id: str = Field(description="Driver ID")
|
|
235
|
+
driver_status: str = Field(description="Driver status")
|
|
236
|
+
cpu_usage: float = Field(description="CPU usage percentage")
|
|
237
|
+
memory_usage: float = Field(description="Memory usage in MB")
|
|
238
|
+
active_tasks: int = Field(description="Number of active tasks")
|
|
239
|
+
completed_tasks: int = Field(description="Number of completed tasks")
|
|
240
|
+
failed_tasks: int = Field(description="Number of failed tasks")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Update forward references
|
|
244
|
+
TaskAssignmentParams.model_rebuild()
|
|
245
|
+
TaskDataModel.model_rebuild()
|
|
246
|
+
TaskOptionsModel.model_rebuild()
|
|
247
|
+
TaskResultParams.model_rebuild()
|
|
248
|
+
TaskResultDataModel.model_rebuild()
|
|
249
|
+
ParsedDataModel.model_rebuild()
|
|
250
|
+
LogEntryParams.model_rebuild()
|
|
251
|
+
DriverRegistrationParams.model_rebuild()
|
|
252
|
+
HeartbeatParams.model_rebuild()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ARQ Response Models
|
|
3
|
+
|
|
4
|
+
Strictly typed response models for ARQ job functions.
|
|
5
|
+
Following critical requirements - no raw Dict[str, Any].
|
|
6
|
+
|
|
7
|
+
Phase 2: Core Systems - ARQ Responses
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from pydantic import Field, ConfigDict
|
|
13
|
+
|
|
14
|
+
from .base import UnrealOnBaseModel
|
|
15
|
+
from ..utils.time import utc_now
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JobResponseBase(UnrealOnBaseModel):
|
|
19
|
+
"""Base response model for ARQ jobs."""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(
|
|
22
|
+
validate_assignment=True,
|
|
23
|
+
extra="forbid"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
success: bool = Field(description="Whether operation succeeded")
|
|
27
|
+
message: str = Field(description="Response message")
|
|
28
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Response timestamp")
|
|
29
|
+
error_code: Optional[str] = Field(default=None, description="Error code if failed")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TaskAssignmentResponse(JobResponseBase):
|
|
33
|
+
"""Response for task assignment job."""
|
|
34
|
+
|
|
35
|
+
task_id: str = Field(description="Assigned task ID")
|
|
36
|
+
driver_id: str = Field(description="Target driver ID")
|
|
37
|
+
task_type: str = Field(description="Task type")
|
|
38
|
+
priority: str = Field(description="Task priority")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TaskResultResponse(JobResponseBase):
|
|
42
|
+
"""Response for task result processing job."""
|
|
43
|
+
|
|
44
|
+
task_id: str = Field(description="Task ID")
|
|
45
|
+
driver_id: str = Field(description="Driver ID")
|
|
46
|
+
status: str = Field(description="Task status")
|
|
47
|
+
processing_time: Optional[float] = Field(default=None, description="Processing time in seconds")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class LogEntryResponse(JobResponseBase):
|
|
51
|
+
"""Response for log entry processing job."""
|
|
52
|
+
|
|
53
|
+
driver_id: str = Field(description="Driver ID")
|
|
54
|
+
level: str = Field(description="Log level")
|
|
55
|
+
entries_processed: int = Field(default=1, description="Number of log entries processed")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DriverRegistrationResponse(JobResponseBase):
|
|
59
|
+
"""Response for driver registration job."""
|
|
60
|
+
|
|
61
|
+
driver_id: str = Field(description="Driver ID")
|
|
62
|
+
driver_type: str = Field(description="Driver type")
|
|
63
|
+
capabilities: list[str] = Field(description="Driver capabilities")
|
|
64
|
+
connection_id: str = Field(description="WebSocket connection ID")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class HeartbeatResponse(JobResponseBase):
|
|
68
|
+
"""Response for heartbeat processing job."""
|
|
69
|
+
|
|
70
|
+
driver_id: str = Field(description="Driver ID")
|
|
71
|
+
status: str = Field(description="Driver status")
|
|
72
|
+
is_healthy: bool = Field(description="Whether driver is healthy")
|
|
73
|
+
success_rate: Optional[float] = Field(default=None, description="Driver success rate")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ConfigurationUpdateResponse(JobResponseBase):
|
|
77
|
+
"""Response for configuration update job."""
|
|
78
|
+
|
|
79
|
+
driver_id: str = Field(description="Driver ID")
|
|
80
|
+
configuration_applied: bool = Field(description="Whether configuration was applied")
|
|
81
|
+
restart_required: bool = Field(default=False, description="Whether restart is required")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class PingResponse(JobResponseBase):
|
|
85
|
+
"""Response for ping job."""
|
|
86
|
+
|
|
87
|
+
pong_timestamp: datetime = Field(description="Pong timestamp")
|
|
88
|
+
latency_ms: Optional[float] = Field(default=None, description="Latency in milliseconds")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DriverStatusResponse(JobResponseBase):
|
|
92
|
+
"""Response for driver status query."""
|
|
93
|
+
|
|
94
|
+
driver_id: str = Field(description="Driver ID")
|
|
95
|
+
status: str = Field(description="Driver status")
|
|
96
|
+
last_seen: Optional[datetime] = Field(default=None, description="Last seen timestamp")
|
|
97
|
+
active_tasks: int = Field(default=0, description="Number of active tasks")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DriverListResponse(JobResponseBase):
|
|
101
|
+
"""Response for driver list query."""
|
|
102
|
+
|
|
103
|
+
drivers: list["DriverInfo"] = Field(description="List of available drivers")
|
|
104
|
+
total_count: int = Field(description="Total number of drivers")
|
|
105
|
+
filter_applied: Optional[str] = Field(default=None, description="Filter that was applied")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class DriverInfo(UnrealOnBaseModel):
|
|
109
|
+
"""Driver information for list response."""
|
|
110
|
+
|
|
111
|
+
model_config = ConfigDict(
|
|
112
|
+
validate_assignment=True,
|
|
113
|
+
extra="forbid"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
driver_id: str = Field(description="Driver ID")
|
|
117
|
+
driver_type: str = Field(description="Driver type")
|
|
118
|
+
status: str = Field(description="Driver status")
|
|
119
|
+
capabilities: list[str] = Field(description="Driver capabilities")
|
|
120
|
+
last_seen: Optional[datetime] = Field(default=None, description="Last seen timestamp")
|
|
121
|
+
active_tasks: int = Field(default=0, description="Number of active tasks")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Update forward references
|
|
125
|
+
DriverListResponse.model_rebuild()
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Pydantic models for UnrealOn system.
|
|
3
|
+
|
|
4
|
+
These models provide common functionality and validation patterns
|
|
5
|
+
used throughout the system. Built with Pydantic v2 for maximum
|
|
6
|
+
performance and type safety.
|
|
7
|
+
|
|
8
|
+
Phase 1: Bedrock Foundation - These models are the foundation
|
|
9
|
+
for all other models in the system.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Optional, Dict, Any, Union
|
|
14
|
+
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
|
15
|
+
import uuid
|
|
16
|
+
|
|
17
|
+
from ..utils.time import utc_now
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UnrealOnBaseModel(BaseModel):
|
|
21
|
+
"""
|
|
22
|
+
Base model for all UnrealOn models.
|
|
23
|
+
|
|
24
|
+
Provides:
|
|
25
|
+
- Strict validation (no extra fields)
|
|
26
|
+
- JSON serialization/deserialization
|
|
27
|
+
- Type safety with Pydantic v2
|
|
28
|
+
- Common utility methods
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(
|
|
32
|
+
# Pydantic v2 configuration for maximum strictness
|
|
33
|
+
str_strip_whitespace=True, # Auto-strip whitespace
|
|
34
|
+
validate_assignment=True, # Validate on assignment
|
|
35
|
+
use_enum_values=True, # Use enum values in serialization
|
|
36
|
+
extra='forbid', # Strict - no extra fields allowed
|
|
37
|
+
frozen=False, # Allow mutation by default
|
|
38
|
+
arbitrary_types_allowed=True, # Allow custom types
|
|
39
|
+
validate_default=True, # Validate default values
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
43
|
+
"""Convert model to dictionary for JSON serialization."""
|
|
44
|
+
return self.model_dump(mode='json', exclude_none=True)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_dict(cls, data: Dict[str, Any]):
|
|
48
|
+
"""Create instance from dictionary."""
|
|
49
|
+
return cls.model_validate(data)
|
|
50
|
+
|
|
51
|
+
def to_json(self) -> str:
|
|
52
|
+
"""Convert model to JSON string."""
|
|
53
|
+
return self.model_dump_json(exclude_none=True)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_json(cls, json_str: str):
|
|
57
|
+
"""Create instance from JSON string."""
|
|
58
|
+
return cls.model_validate_json(json_str)
|
|
59
|
+
|
|
60
|
+
def update_from_dict(self, data: Dict[str, Any]) -> 'UnrealOnBaseModel':
|
|
61
|
+
"""Update model fields from dictionary."""
|
|
62
|
+
for key, value in data.items():
|
|
63
|
+
if hasattr(self, key):
|
|
64
|
+
setattr(self, key, value)
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def copy_with_updates(self, **updates) -> 'UnrealOnBaseModel':
|
|
68
|
+
"""Create a copy with updated fields."""
|
|
69
|
+
return self.model_copy(update=updates)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TimestampedModel(UnrealOnBaseModel):
|
|
73
|
+
"""
|
|
74
|
+
Base model with automatic timestamp fields.
|
|
75
|
+
|
|
76
|
+
Provides:
|
|
77
|
+
- created_at: Auto-set on creation
|
|
78
|
+
- updated_at: Manual update via touch()
|
|
79
|
+
- age_seconds: Calculated property
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
created_at: datetime = Field(
|
|
83
|
+
default_factory=utc_now,
|
|
84
|
+
description="Timestamp when the model was created"
|
|
85
|
+
)
|
|
86
|
+
updated_at: Optional[datetime] = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="Timestamp when the model was last updated"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def touch(self) -> 'TimestampedModel':
|
|
92
|
+
"""Update the updated_at timestamp."""
|
|
93
|
+
self.updated_at = utc_now()
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def age_seconds(self) -> float:
|
|
98
|
+
"""Get age of the model in seconds."""
|
|
99
|
+
return (utc_now() - self.created_at).total_seconds()
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def last_modified_seconds(self) -> float:
|
|
103
|
+
"""Get seconds since last modification."""
|
|
104
|
+
reference_time = self.updated_at or self.created_at
|
|
105
|
+
return (utc_now() - reference_time).total_seconds()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class IdentifiedModel(UnrealOnBaseModel):
|
|
109
|
+
"""
|
|
110
|
+
Base model with unique ID field.
|
|
111
|
+
|
|
112
|
+
Provides:
|
|
113
|
+
- id: UUID4 string by default
|
|
114
|
+
- String representation
|
|
115
|
+
- Equality comparison by ID
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
id: str = Field(
|
|
119
|
+
default_factory=lambda: str(uuid.uuid4()),
|
|
120
|
+
description="Unique identifier for the model",
|
|
121
|
+
min_length=1
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@field_validator('id')
|
|
125
|
+
@classmethod
|
|
126
|
+
def validate_id(cls, v: str) -> str:
|
|
127
|
+
"""Validate ID is not empty."""
|
|
128
|
+
if not v or not v.strip():
|
|
129
|
+
raise ValueError("ID cannot be empty")
|
|
130
|
+
return v.strip()
|
|
131
|
+
|
|
132
|
+
def __str__(self) -> str:
|
|
133
|
+
return f"{self.__class__.__name__}(id={self.id})"
|
|
134
|
+
|
|
135
|
+
def __repr__(self) -> str:
|
|
136
|
+
return f"{self.__class__.__name__}(id='{self.id}')"
|
|
137
|
+
|
|
138
|
+
def __eq__(self, other) -> bool:
|
|
139
|
+
"""Compare models by ID."""
|
|
140
|
+
if not isinstance(other, IdentifiedModel):
|
|
141
|
+
return False
|
|
142
|
+
return self.id == other.id
|
|
143
|
+
|
|
144
|
+
def __hash__(self) -> int:
|
|
145
|
+
"""Hash by ID for use in sets/dicts."""
|
|
146
|
+
return hash(self.id)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class StatusModel(UnrealOnBaseModel):
|
|
150
|
+
"""
|
|
151
|
+
Base model with status tracking.
|
|
152
|
+
|
|
153
|
+
Provides:
|
|
154
|
+
- status: Current status string
|
|
155
|
+
- status_message: Optional status description
|
|
156
|
+
- status_updated_at: Timestamp of last status change
|
|
157
|
+
- Status update methods
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
status: str = Field(
|
|
161
|
+
description="Current status of the model",
|
|
162
|
+
min_length=1
|
|
163
|
+
)
|
|
164
|
+
status_message: Optional[str] = Field(
|
|
165
|
+
default=None,
|
|
166
|
+
description="Optional message describing the status"
|
|
167
|
+
)
|
|
168
|
+
status_updated_at: datetime = Field(
|
|
169
|
+
default_factory=utc_now,
|
|
170
|
+
description="Timestamp when status was last updated"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@field_validator('status')
|
|
174
|
+
@classmethod
|
|
175
|
+
def validate_status(cls, v: str) -> str:
|
|
176
|
+
"""Validate status is not empty."""
|
|
177
|
+
if not v or not v.strip():
|
|
178
|
+
raise ValueError("Status cannot be empty")
|
|
179
|
+
return v.strip()
|
|
180
|
+
|
|
181
|
+
def update_status(self, status: str, message: Optional[str] = None) -> 'StatusModel':
|
|
182
|
+
"""Update status with timestamp and optional message."""
|
|
183
|
+
self.status = status
|
|
184
|
+
self.status_message = message
|
|
185
|
+
self.status_updated_at = utc_now()
|
|
186
|
+
return self
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def status_age_seconds(self) -> float:
|
|
190
|
+
"""Get age of current status in seconds."""
|
|
191
|
+
return (utc_now() - self.status_updated_at).total_seconds()
|
|
192
|
+
|
|
193
|
+
def is_status(self, *statuses: str) -> bool:
|
|
194
|
+
"""Check if current status matches any of the provided statuses."""
|
|
195
|
+
return self.status in statuses
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class MetadataModel(UnrealOnBaseModel):
|
|
199
|
+
"""
|
|
200
|
+
Base model with metadata field.
|
|
201
|
+
|
|
202
|
+
Provides:
|
|
203
|
+
- metadata: Dictionary for arbitrary key-value data
|
|
204
|
+
- Helper methods for metadata manipulation
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
metadata: Dict[str, Any] = Field(
|
|
208
|
+
default_factory=dict,
|
|
209
|
+
description="Arbitrary metadata key-value pairs"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def set_metadata(self, key: str, value: Any) -> 'MetadataModel':
|
|
213
|
+
"""Set metadata value."""
|
|
214
|
+
self.metadata[key] = value
|
|
215
|
+
return self
|
|
216
|
+
|
|
217
|
+
def get_metadata(self, key: str, default: Any = None) -> Any:
|
|
218
|
+
"""Get metadata value with optional default."""
|
|
219
|
+
return self.metadata.get(key, default)
|
|
220
|
+
|
|
221
|
+
def has_metadata(self, key: str) -> bool:
|
|
222
|
+
"""Check if metadata key exists."""
|
|
223
|
+
return key in self.metadata
|
|
224
|
+
|
|
225
|
+
def remove_metadata(self, key: str) -> 'MetadataModel':
|
|
226
|
+
"""Remove metadata key if it exists."""
|
|
227
|
+
self.metadata.pop(key, None)
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
def clear_metadata(self) -> 'MetadataModel':
|
|
231
|
+
"""Clear all metadata."""
|
|
232
|
+
self.metadata.clear()
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def update_metadata(self, **updates) -> 'MetadataModel':
|
|
236
|
+
"""Update metadata with multiple key-value pairs."""
|
|
237
|
+
self.metadata.update(updates)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Combined base models for common use cases
|
|
242
|
+
class FullBaseModel(IdentifiedModel, TimestampedModel, StatusModel, MetadataModel):
|
|
243
|
+
"""
|
|
244
|
+
Complete base model with ID, timestamps, status, and metadata.
|
|
245
|
+
|
|
246
|
+
Use this for complex entities that need full tracking:
|
|
247
|
+
- Drivers
|
|
248
|
+
- Tasks
|
|
249
|
+
- Complex configurations
|
|
250
|
+
"""
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SimpleBaseModel(IdentifiedModel, TimestampedModel):
|
|
255
|
+
"""
|
|
256
|
+
Simple base model with ID and timestamps only.
|
|
257
|
+
|
|
258
|
+
Use this for simple entities:
|
|
259
|
+
- Messages
|
|
260
|
+
- Simple configurations
|
|
261
|
+
- Temporary objects
|
|
262
|
+
"""
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ConfigBaseModel(UnrealOnBaseModel):
|
|
267
|
+
"""
|
|
268
|
+
Base model for configuration objects.
|
|
269
|
+
|
|
270
|
+
Provides validation for configuration models
|
|
271
|
+
without unnecessary fields like timestamps.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
def validate_config(self) -> bool:
|
|
275
|
+
"""Override in subclasses to add custom validation."""
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
def merge_with(self, other: 'ConfigBaseModel') -> 'ConfigBaseModel':
|
|
279
|
+
"""Merge this config with another, other takes precedence."""
|
|
280
|
+
if not isinstance(other, self.__class__):
|
|
281
|
+
raise ValueError(f"Cannot merge {self.__class__} with {other.__class__}")
|
|
282
|
+
|
|
283
|
+
# Get all fields from both models
|
|
284
|
+
self_dict = self.model_dump()
|
|
285
|
+
other_dict = other.model_dump()
|
|
286
|
+
|
|
287
|
+
# Merge dictionaries (other takes precedence)
|
|
288
|
+
merged = {**self_dict, **other_dict}
|
|
289
|
+
|
|
290
|
+
# Create new instance
|
|
291
|
+
return self.__class__.model_validate(merged)
|