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 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