rxon 1.0.0__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.
- rxon/__init__.py +202 -0
- rxon/blob.py +92 -0
- rxon/constants.py +136 -0
- rxon/exceptions.py +74 -0
- rxon/models.py +388 -0
- rxon/py.typed +0 -0
- rxon/schema.py +568 -0
- rxon/security.py +242 -0
- rxon/testing.py +95 -0
- rxon/transports/__init__.py +4 -0
- rxon/transports/base.py +99 -0
- rxon/transports/factory.py +31 -0
- rxon/transports/http.py +322 -0
- rxon/transports/http_server.py +283 -0
- rxon/utils.py +103 -0
- rxon/validators.py +33 -0
- rxon-1.0.0.dist-info/METADATA +153 -0
- rxon-1.0.0.dist-info/RECORD +21 -0
- rxon-1.0.0.dist-info/WHEEL +5 -0
- rxon-1.0.0.dist-info/licenses/LICENSE +348 -0
- rxon-1.0.0.dist-info/top_level.txt +1 -0
rxon/__init__.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Dmitrii Gagarin aka madgagarin
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
|
|
6
|
+
from rxon.blob import RXON_BLOB_SCHEME, calculate_config_hash, parse_uri
|
|
7
|
+
from rxon.constants import (
|
|
8
|
+
AUTH_HEADER_CLIENT,
|
|
9
|
+
AUTH_HEADER_WORKER,
|
|
10
|
+
COMMAND_CANCEL_TASK,
|
|
11
|
+
ENDPOINT_TASK_NEXT,
|
|
12
|
+
ENDPOINT_TASK_RESULT,
|
|
13
|
+
ENDPOINT_WORKER_HEARTBEAT,
|
|
14
|
+
ENDPOINT_WORKER_REGISTER,
|
|
15
|
+
ERROR_CODE_CONTRACT_VIOLATION,
|
|
16
|
+
ERROR_CODE_DEPENDENCY,
|
|
17
|
+
ERROR_CODE_DEPENDENCY_MISSING,
|
|
18
|
+
ERROR_CODE_INTEGRITY_MISMATCH,
|
|
19
|
+
ERROR_CODE_INTERNAL,
|
|
20
|
+
ERROR_CODE_INVALID_INPUT,
|
|
21
|
+
ERROR_CODE_LIMIT_EXCEEDED,
|
|
22
|
+
ERROR_CODE_PERMANENT,
|
|
23
|
+
ERROR_CODE_RESOURCE_EXHAUSTED,
|
|
24
|
+
ERROR_CODE_SECURITY,
|
|
25
|
+
ERROR_CODE_TIMEOUT,
|
|
26
|
+
ERROR_CODE_TRANSIENT,
|
|
27
|
+
EVENT_TYPE_PROGRESS,
|
|
28
|
+
HB_RESP_CANCEL_TASKS,
|
|
29
|
+
HB_RESP_REQUIRE_FULL_SYNC,
|
|
30
|
+
JOB_STATUS_CANCELLED,
|
|
31
|
+
JOB_STATUS_ERROR,
|
|
32
|
+
JOB_STATUS_FAILED,
|
|
33
|
+
JOB_STATUS_FINISHED,
|
|
34
|
+
JOB_STATUS_PENDING,
|
|
35
|
+
JOB_STATUS_QUARANTINED,
|
|
36
|
+
JOB_STATUS_RUNNING,
|
|
37
|
+
JOB_STATUS_WAITING_FOR_HUMAN,
|
|
38
|
+
JOB_STATUS_WAITING_FOR_PARALLEL,
|
|
39
|
+
JOB_STATUS_WAITING_FOR_WORKER,
|
|
40
|
+
PROTOCOL_VERSION,
|
|
41
|
+
STS_TOKEN_ENDPOINT,
|
|
42
|
+
TASK_STATUS_CANCELLED,
|
|
43
|
+
TASK_STATUS_FAILURE,
|
|
44
|
+
TASK_STATUS_SUCCESS,
|
|
45
|
+
WORKER_API_PREFIX,
|
|
46
|
+
WS_ENDPOINT,
|
|
47
|
+
)
|
|
48
|
+
from rxon.exceptions import (
|
|
49
|
+
IntegrityError,
|
|
50
|
+
ParamValidationError,
|
|
51
|
+
RxonAuthError,
|
|
52
|
+
RxonError,
|
|
53
|
+
RxonNetworkError,
|
|
54
|
+
RxonProtocolError,
|
|
55
|
+
S3ConfigMismatchError,
|
|
56
|
+
)
|
|
57
|
+
from rxon.models import (
|
|
58
|
+
DeviceUsage,
|
|
59
|
+
ExecutionReceipt,
|
|
60
|
+
FileMetadata,
|
|
61
|
+
HardwareDevice,
|
|
62
|
+
Heartbeat,
|
|
63
|
+
InstalledArtifact,
|
|
64
|
+
Resources,
|
|
65
|
+
ResourcesUsage,
|
|
66
|
+
SecurityContext,
|
|
67
|
+
SkillInfo,
|
|
68
|
+
TaskError,
|
|
69
|
+
TaskPayload,
|
|
70
|
+
TaskResult,
|
|
71
|
+
TokenResponse,
|
|
72
|
+
WorkerCapabilities,
|
|
73
|
+
WorkerCommand,
|
|
74
|
+
WorkerEventPayload,
|
|
75
|
+
WorkerRegistration,
|
|
76
|
+
)
|
|
77
|
+
from rxon.schema import (
|
|
78
|
+
Header,
|
|
79
|
+
extract_input_schema_from_func,
|
|
80
|
+
extract_json_schema,
|
|
81
|
+
extract_output_schema_from_func,
|
|
82
|
+
extract_schema_from_func,
|
|
83
|
+
extract_skill_contract,
|
|
84
|
+
get_compiled_validator,
|
|
85
|
+
validate_data,
|
|
86
|
+
)
|
|
87
|
+
from rxon.security import (
|
|
88
|
+
create_client_ssl_context,
|
|
89
|
+
create_server_ssl_context,
|
|
90
|
+
extract_cert_identity,
|
|
91
|
+
sign_bubbling_chain,
|
|
92
|
+
sign_bubbling_chain_hmac,
|
|
93
|
+
sign_payload_ed25519,
|
|
94
|
+
verify_bubbling_chain_hmac,
|
|
95
|
+
verify_bubbling_chain_signature,
|
|
96
|
+
verify_signature_ed25519,
|
|
97
|
+
)
|
|
98
|
+
from rxon.transports.base import Listener, Transport
|
|
99
|
+
from rxon.transports.factory import create_transport
|
|
100
|
+
from rxon.transports.http import HttpTransport
|
|
101
|
+
from rxon.transports.http_server import HttpListener
|
|
102
|
+
from rxon.utils import from_dict, loads, to_dict
|
|
103
|
+
from rxon.validators import is_valid_identifier, validate_identifier
|
|
104
|
+
|
|
105
|
+
__version__ = PROTOCOL_VERSION
|
|
106
|
+
|
|
107
|
+
__all__ = [
|
|
108
|
+
"RXON_BLOB_SCHEME",
|
|
109
|
+
"calculate_config_hash",
|
|
110
|
+
"parse_uri",
|
|
111
|
+
"AUTH_HEADER_CLIENT",
|
|
112
|
+
"AUTH_HEADER_WORKER",
|
|
113
|
+
"COMMAND_CANCEL_TASK",
|
|
114
|
+
"ENDPOINT_TASK_NEXT",
|
|
115
|
+
"ENDPOINT_TASK_RESULT",
|
|
116
|
+
"ENDPOINT_WORKER_HEARTBEAT",
|
|
117
|
+
"ENDPOINT_WORKER_REGISTER",
|
|
118
|
+
"ERROR_CODE_CONTRACT_VIOLATION",
|
|
119
|
+
"ERROR_CODE_DEPENDENCY",
|
|
120
|
+
"ERROR_CODE_DEPENDENCY_MISSING",
|
|
121
|
+
"ERROR_CODE_INTEGRITY_MISMATCH",
|
|
122
|
+
"ERROR_CODE_INTERNAL",
|
|
123
|
+
"ERROR_CODE_INVALID_INPUT",
|
|
124
|
+
"ERROR_CODE_LIMIT_EXCEEDED",
|
|
125
|
+
"ERROR_CODE_PERMANENT",
|
|
126
|
+
"ERROR_CODE_RESOURCE_EXHAUSTED",
|
|
127
|
+
"ERROR_CODE_SECURITY",
|
|
128
|
+
"ERROR_CODE_TIMEOUT",
|
|
129
|
+
"ERROR_CODE_TRANSIENT",
|
|
130
|
+
"HB_RESP_REQUIRE_FULL_SYNC",
|
|
131
|
+
"HB_RESP_CANCEL_TASKS",
|
|
132
|
+
"JOB_STATUS_CANCELLED",
|
|
133
|
+
"JOB_STATUS_ERROR",
|
|
134
|
+
"JOB_STATUS_FAILED",
|
|
135
|
+
"JOB_STATUS_FINISHED",
|
|
136
|
+
"JOB_STATUS_PENDING",
|
|
137
|
+
"JOB_STATUS_QUARANTINED",
|
|
138
|
+
"JOB_STATUS_RUNNING",
|
|
139
|
+
"JOB_STATUS_WAITING_FOR_HUMAN",
|
|
140
|
+
"JOB_STATUS_WAITING_FOR_PARALLEL",
|
|
141
|
+
"JOB_STATUS_WAITING_FOR_WORKER",
|
|
142
|
+
"EVENT_TYPE_PROGRESS",
|
|
143
|
+
"PROTOCOL_VERSION",
|
|
144
|
+
"STS_TOKEN_ENDPOINT",
|
|
145
|
+
"TASK_STATUS_CANCELLED",
|
|
146
|
+
"TASK_STATUS_FAILURE",
|
|
147
|
+
"TASK_STATUS_SUCCESS",
|
|
148
|
+
"WORKER_API_PREFIX",
|
|
149
|
+
"WS_ENDPOINT",
|
|
150
|
+
"RxonError",
|
|
151
|
+
"RxonNetworkError",
|
|
152
|
+
"RxonAuthError",
|
|
153
|
+
"RxonProtocolError",
|
|
154
|
+
"IntegrityError",
|
|
155
|
+
"ParamValidationError",
|
|
156
|
+
"S3ConfigMismatchError",
|
|
157
|
+
"DeviceUsage",
|
|
158
|
+
"ExecutionReceipt",
|
|
159
|
+
"FileMetadata",
|
|
160
|
+
"HardwareDevice",
|
|
161
|
+
"Heartbeat",
|
|
162
|
+
"InstalledArtifact",
|
|
163
|
+
"Resources",
|
|
164
|
+
"ResourcesUsage",
|
|
165
|
+
"SecurityContext",
|
|
166
|
+
"SkillInfo",
|
|
167
|
+
"TaskError",
|
|
168
|
+
"TaskPayload",
|
|
169
|
+
"TaskResult",
|
|
170
|
+
"TokenResponse",
|
|
171
|
+
"WorkerCapabilities",
|
|
172
|
+
"WorkerCommand",
|
|
173
|
+
"WorkerEventPayload",
|
|
174
|
+
"WorkerRegistration",
|
|
175
|
+
"create_client_ssl_context",
|
|
176
|
+
"create_server_ssl_context",
|
|
177
|
+
"extract_cert_identity",
|
|
178
|
+
"sign_payload_ed25519",
|
|
179
|
+
"verify_signature_ed25519",
|
|
180
|
+
"sign_bubbling_chain",
|
|
181
|
+
"verify_bubbling_chain_signature",
|
|
182
|
+
"sign_bubbling_chain_hmac",
|
|
183
|
+
"verify_bubbling_chain_hmac",
|
|
184
|
+
"Listener",
|
|
185
|
+
"Transport",
|
|
186
|
+
"create_transport",
|
|
187
|
+
"HttpTransport",
|
|
188
|
+
"HttpListener",
|
|
189
|
+
"from_dict",
|
|
190
|
+
"to_dict",
|
|
191
|
+
"loads",
|
|
192
|
+
"is_valid_identifier",
|
|
193
|
+
"validate_identifier",
|
|
194
|
+
"extract_input_schema_from_func",
|
|
195
|
+
"extract_output_schema_from_func",
|
|
196
|
+
"extract_schema_from_func",
|
|
197
|
+
"extract_skill_contract",
|
|
198
|
+
"extract_json_schema",
|
|
199
|
+
"get_compiled_validator",
|
|
200
|
+
"validate_data",
|
|
201
|
+
"Header",
|
|
202
|
+
]
|
rxon/blob.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Dmitrii Gagarin aka madgagarin
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from hashlib import sha256
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from rxon.models import FileMetadata
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"RXON_BLOB_SCHEME",
|
|
15
|
+
"calculate_config_hash",
|
|
16
|
+
"parse_uri",
|
|
17
|
+
"BlobProvider",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
RXON_BLOB_SCHEME = "s3"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BlobProvider(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Abstract interface for Blob Storage providers (S3, GCS, Local).
|
|
26
|
+
RXON uses URIs like s3://bucket/key to reference heavy data.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
async def upload(self, local_path: str, uri: str) -> str | FileMetadata:
|
|
31
|
+
"""Uploads a local file to the storage and returns etag or FileMetadata."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
async def download(self, uri: str, local_path: str) -> bool:
|
|
36
|
+
"""Downloads a file from storage to local path."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def get_metadata(self, uri: str) -> dict[str, Any] | None:
|
|
41
|
+
"""Returns metadata (size, etag, content-type) for a URI."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def delete(self, uri: str) -> bool:
|
|
46
|
+
"""Deletes a single object from storage."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def delete_dir(self, uri: str) -> bool:
|
|
51
|
+
"""Deletes all objects under a specific URI prefix."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def calculate_config_hash(endpoint: str | None, access_key: str | None, bucket: str | None) -> str | None:
|
|
56
|
+
"""
|
|
57
|
+
Calculates a consistent hash of the Blob/S3 configuration.
|
|
58
|
+
Used to ensure Workers and Orchestrators are talking to the same storage.
|
|
59
|
+
Uses '|' as separator.
|
|
60
|
+
"""
|
|
61
|
+
if not endpoint or not access_key or not bucket:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
config_str = f"{endpoint}|{access_key}|{bucket}"
|
|
65
|
+
return sha256(config_str.encode()).hexdigest()[:16]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_uri(uri: str, default_bucket: str | None = None, prefix: str = "") -> tuple[str, str, bool]:
|
|
69
|
+
"""
|
|
70
|
+
Parses a Blob/S3 URI or relative path into (bucket, key, is_directory).
|
|
71
|
+
Protocol: s3://bucket/key
|
|
72
|
+
|
|
73
|
+
:param uri: Full URI (s3://bucket/key) or relative path (key)
|
|
74
|
+
:param default_bucket: Bucket to use if URI is relative
|
|
75
|
+
:param prefix: Optional prefix to prepend to relative paths
|
|
76
|
+
:return: (bucket, key, is_directory)
|
|
77
|
+
:raises ValueError: If URI format is invalid or bucket is missing
|
|
78
|
+
"""
|
|
79
|
+
is_dir = uri.endswith("/")
|
|
80
|
+
|
|
81
|
+
if uri.startswith(f"{RXON_BLOB_SCHEME}://"):
|
|
82
|
+
parsed = urlparse(uri)
|
|
83
|
+
bucket = parsed.netloc
|
|
84
|
+
key = parsed.path.lstrip("/")
|
|
85
|
+
return bucket, key, is_dir
|
|
86
|
+
else:
|
|
87
|
+
if not default_bucket:
|
|
88
|
+
raise ValueError(f"Cannot parse relative path '{uri}' without a default bucket.")
|
|
89
|
+
|
|
90
|
+
clean_path = uri.lstrip("/")
|
|
91
|
+
key = f"{prefix}{clean_path}" if prefix else clean_path
|
|
92
|
+
return default_bucket, key, is_dir
|
rxon/constants.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Dmitrii Gagarin aka madgagarin
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Centralized constants for the RXON protocol.
|
|
8
|
+
Shared between Orchestrator and Worker SDK.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AUTH_HEADER_CLIENT",
|
|
13
|
+
"AUTH_HEADER_WORKER",
|
|
14
|
+
"ERROR_CODE_TRANSIENT",
|
|
15
|
+
"ERROR_CODE_PERMANENT",
|
|
16
|
+
"ERROR_CODE_INVALID_INPUT",
|
|
17
|
+
"ERROR_CODE_INTEGRITY_MISMATCH",
|
|
18
|
+
"ERROR_CODE_TIMEOUT",
|
|
19
|
+
"ERROR_CODE_LATE_RESULT",
|
|
20
|
+
"ERROR_CODE_RESOURCE_EXHAUSTED",
|
|
21
|
+
"ERROR_CODE_LIMIT_EXCEEDED",
|
|
22
|
+
"ERROR_CODE_DEPENDENCY_MISSING",
|
|
23
|
+
"ERROR_CODE_CONTRACT_VIOLATION",
|
|
24
|
+
"ERROR_CODE_DEPENDENCY",
|
|
25
|
+
"ERROR_CODE_SECURITY",
|
|
26
|
+
"ERROR_CODE_INTERNAL",
|
|
27
|
+
"TASK_STATUS_SUCCESS",
|
|
28
|
+
"TASK_STATUS_FAILURE",
|
|
29
|
+
"TASK_STATUS_CANCELLED",
|
|
30
|
+
"COMMAND_CANCEL_TASK",
|
|
31
|
+
"EVENT_TYPE_PROGRESS",
|
|
32
|
+
"STS_TOKEN_ENDPOINT",
|
|
33
|
+
"WS_ENDPOINT",
|
|
34
|
+
"ENDPOINT_WORKER_REGISTER",
|
|
35
|
+
"ENDPOINT_TASK_NEXT",
|
|
36
|
+
"ENDPOINT_TASK_RESULT",
|
|
37
|
+
"ENDPOINT_WORKER_HEARTBEAT",
|
|
38
|
+
"ENDPOINT_WORKER_EVENTS",
|
|
39
|
+
"JOB_STATUS_PENDING",
|
|
40
|
+
"JOB_STATUS_WAITING_FOR_WORKER",
|
|
41
|
+
"JOB_STATUS_RUNNING",
|
|
42
|
+
"JOB_STATUS_FINISHED",
|
|
43
|
+
"JOB_STATUS_FAILED",
|
|
44
|
+
"JOB_STATUS_ERROR",
|
|
45
|
+
"JOB_STATUS_QUARANTINED",
|
|
46
|
+
"JOB_STATUS_CANCELLED",
|
|
47
|
+
"JOB_STATUS_WAITING_FOR_HUMAN",
|
|
48
|
+
"JOB_STATUS_WAITING_FOR_PARALLEL",
|
|
49
|
+
"WORKER_API_PREFIX",
|
|
50
|
+
"PROTOCOL_VERSION",
|
|
51
|
+
"PROTOCOL_VERSION_HEADER",
|
|
52
|
+
"IGNORED_REASON_LATE",
|
|
53
|
+
"IGNORED_REASON_CANCELLED",
|
|
54
|
+
"IGNORED_REASON_NOT_FOUND",
|
|
55
|
+
"IGNORED_REASON_MISMATCH",
|
|
56
|
+
"IGNORED_REASON_STALE",
|
|
57
|
+
"HB_RESP_REQUIRE_FULL_SYNC",
|
|
58
|
+
"HB_RESP_CANCEL_TASKS",
|
|
59
|
+
"AUTH_HEADER_WORKER_ID",
|
|
60
|
+
"STS_REFRESH_ENDPOINT",
|
|
61
|
+
"TERMINAL_STATES",
|
|
62
|
+
"WORKER_STATUS_ACTIVE",
|
|
63
|
+
"WORKER_STATUS_DRAINING",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
PROTOCOL_VERSION = "1.0.0"
|
|
67
|
+
PROTOCOL_VERSION_HEADER = "X-RXON-Version"
|
|
68
|
+
|
|
69
|
+
AUTH_HEADER_CLIENT = "X-Client-Token"
|
|
70
|
+
AUTH_HEADER_WORKER = "X-Worker-Token"
|
|
71
|
+
AUTH_HEADER_WORKER_ID = "X-Worker-ID"
|
|
72
|
+
|
|
73
|
+
ERROR_CODE_TRANSIENT = "TRANSIENT_ERROR"
|
|
74
|
+
ERROR_CODE_PERMANENT = "PERMANENT_ERROR"
|
|
75
|
+
ERROR_CODE_INVALID_INPUT = "INVALID_INPUT_ERROR"
|
|
76
|
+
ERROR_CODE_INTEGRITY_MISMATCH = "INTEGRITY_MISMATCH_ERROR"
|
|
77
|
+
ERROR_CODE_TIMEOUT = "TIMEOUT_ERROR"
|
|
78
|
+
ERROR_CODE_LATE_RESULT = "LATE_RESULT"
|
|
79
|
+
ERROR_CODE_RESOURCE_EXHAUSTED = "RESOURCE_EXHAUSTED_ERROR"
|
|
80
|
+
ERROR_CODE_LIMIT_EXCEEDED = "LIMIT_EXCEEDED_ERROR"
|
|
81
|
+
ERROR_CODE_CONTRACT_VIOLATION = "CONTRACT_VIOLATION_ERROR"
|
|
82
|
+
ERROR_CODE_DEPENDENCY_MISSING = "DEPENDENCY_MISSING_ERROR"
|
|
83
|
+
|
|
84
|
+
IGNORED_REASON_LATE = "deadline_exceeded"
|
|
85
|
+
IGNORED_REASON_CANCELLED = "job_cancelled"
|
|
86
|
+
IGNORED_REASON_NOT_FOUND = "job_not_found"
|
|
87
|
+
IGNORED_REASON_MISMATCH = "worker_mismatch"
|
|
88
|
+
IGNORED_REASON_STALE = "stale_task"
|
|
89
|
+
|
|
90
|
+
HB_RESP_REQUIRE_FULL_SYNC = "require_full_sync"
|
|
91
|
+
HB_RESP_CANCEL_TASKS = "cancel_task_ids"
|
|
92
|
+
|
|
93
|
+
ERROR_CODE_DEPENDENCY = "DEPENDENCY_ERROR"
|
|
94
|
+
ERROR_CODE_SECURITY = "SECURITY_ERROR"
|
|
95
|
+
ERROR_CODE_INTERNAL = "INTERNAL_ERROR"
|
|
96
|
+
|
|
97
|
+
TASK_STATUS_SUCCESS = "success"
|
|
98
|
+
TASK_STATUS_FAILURE = "failure"
|
|
99
|
+
TASK_STATUS_CANCELLED = "cancelled"
|
|
100
|
+
|
|
101
|
+
COMMAND_CANCEL_TASK = "cancel_task"
|
|
102
|
+
EVENT_TYPE_PROGRESS = "progress"
|
|
103
|
+
|
|
104
|
+
WORKER_API_PREFIX = "/_worker"
|
|
105
|
+
|
|
106
|
+
STS_TOKEN_ENDPOINT = f"{WORKER_API_PREFIX}/auth/token"
|
|
107
|
+
STS_REFRESH_ENDPOINT = f"{WORKER_API_PREFIX}/auth/token/refresh"
|
|
108
|
+
WS_ENDPOINT = f"{WORKER_API_PREFIX}/ws"
|
|
109
|
+
|
|
110
|
+
ENDPOINT_WORKER_REGISTER = f"{WORKER_API_PREFIX}/workers/register"
|
|
111
|
+
ENDPOINT_TASK_NEXT = f"{WORKER_API_PREFIX}/workers/{{worker_id}}/tasks/next"
|
|
112
|
+
ENDPOINT_TASK_RESULT = f"{WORKER_API_PREFIX}/tasks/result"
|
|
113
|
+
ENDPOINT_WORKER_HEARTBEAT = f"{WORKER_API_PREFIX}/workers/{{worker_id}}"
|
|
114
|
+
ENDPOINT_WORKER_EVENTS = f"{WORKER_API_PREFIX}/events"
|
|
115
|
+
|
|
116
|
+
JOB_STATUS_PENDING = "pending"
|
|
117
|
+
JOB_STATUS_WAITING_FOR_WORKER = "waiting_for_worker"
|
|
118
|
+
JOB_STATUS_RUNNING = "running"
|
|
119
|
+
JOB_STATUS_FINISHED = "finished"
|
|
120
|
+
JOB_STATUS_FAILED = "failed"
|
|
121
|
+
JOB_STATUS_ERROR = "error"
|
|
122
|
+
JOB_STATUS_QUARANTINED = "quarantined"
|
|
123
|
+
JOB_STATUS_CANCELLED = "cancelled"
|
|
124
|
+
JOB_STATUS_WAITING_FOR_HUMAN = "waiting_for_human"
|
|
125
|
+
JOB_STATUS_WAITING_FOR_PARALLEL = "waiting_for_parallel_tasks"
|
|
126
|
+
|
|
127
|
+
TERMINAL_STATES = {
|
|
128
|
+
JOB_STATUS_FINISHED,
|
|
129
|
+
JOB_STATUS_FAILED,
|
|
130
|
+
JOB_STATUS_ERROR,
|
|
131
|
+
JOB_STATUS_QUARANTINED,
|
|
132
|
+
JOB_STATUS_CANCELLED,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
WORKER_STATUS_ACTIVE = "active"
|
|
136
|
+
WORKER_STATUS_DRAINING = "draining"
|
rxon/exceptions.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright (c) 2025-2026 Dmitrii Gagarin aka madgagarin
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rxon.constants import ERROR_CODE_LIMIT_EXCEEDED
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"RxonError",
|
|
12
|
+
"RxonNetworkError",
|
|
13
|
+
"RxonAuthError",
|
|
14
|
+
"RxonProtocolError",
|
|
15
|
+
"RxonRateLimitError",
|
|
16
|
+
"S3ConfigMismatchError",
|
|
17
|
+
"IntegrityError",
|
|
18
|
+
"ParamValidationError",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RxonError(Exception):
|
|
23
|
+
"""Base exception for all RXON library errors."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, details: dict[str, Any] | None = None):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.message = message
|
|
28
|
+
self.details = details or {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RxonNetworkError(RxonError):
|
|
32
|
+
"""Raised when a network operation fails (connection refused, timeout, etc.)."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RxonAuthError(RxonError):
|
|
38
|
+
"""Raised when authentication fails (401/403) and cannot be recovered."""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RxonProtocolError(RxonError):
|
|
44
|
+
"""Raised when the server response violates the protocol (unexpected status, bad JSON)."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RxonRateLimitError(RxonProtocolError):
|
|
50
|
+
"""Raised when the orchestrator rate limits the worker (HTTP 429)."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, message: str, details: dict[str, Any] | None = None):
|
|
53
|
+
details = details or {}
|
|
54
|
+
if "code" not in details:
|
|
55
|
+
details["code"] = ERROR_CODE_LIMIT_EXCEEDED
|
|
56
|
+
super().__init__(message, details)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class S3ConfigMismatchError(RxonProtocolError):
|
|
60
|
+
"""Raised when Worker and Orchestrator S3 configurations do not match."""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class IntegrityError(RxonProtocolError):
|
|
66
|
+
"""Raised when file integrity check (size/hash) fails."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ParamValidationError(RxonProtocolError):
|
|
72
|
+
"""Raised when task parameters fail validation."""
|
|
73
|
+
|
|
74
|
+
pass
|