continuum-task-server-sdk 1.1.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.
@@ -0,0 +1,94 @@
1
+ """Exception types raised by the Continuum SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class ContinuumError(Exception):
7
+ """Base exception for all Continuum SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ *,
13
+ status_code: int | None = None,
14
+ body: str | None = None,
15
+ ) -> None:
16
+ super().__init__(message)
17
+ self.status_code = status_code
18
+ self.body = body
19
+
20
+ def __str__(self) -> str:
21
+ base = super().__str__()
22
+ if self.status_code is not None:
23
+ return f"[{self.status_code}] {base}"
24
+ return base
25
+
26
+
27
+ class BadRequestError(ContinuumError):
28
+ """HTTP / command 400."""
29
+
30
+
31
+ class UnauthorizedError(ContinuumError):
32
+ """HTTP 401 - check API key type (Organization vs Worker)."""
33
+
34
+
35
+ class ForbiddenError(ContinuumError):
36
+ """HTTP 403."""
37
+
38
+
39
+ class NotFoundError(ContinuumError):
40
+ """HTTP 404."""
41
+
42
+
43
+ class ConflictError(ContinuumError):
44
+ """HTTP 409."""
45
+
46
+
47
+ class RateLimitError(ContinuumError):
48
+ """HTTP / command 429."""
49
+
50
+
51
+ class ServerError(ContinuumError):
52
+ """HTTP 5xx."""
53
+
54
+
55
+ class TransportError(ContinuumError):
56
+ """WebSocket / STOMP connection failed or dropped."""
57
+
58
+
59
+ class ProtocolError(ContinuumError):
60
+ """Malformed STOMP frame or unexpected protocol response."""
61
+
62
+
63
+ class CommandTimeoutError(ContinuumError):
64
+ """Timed out waiting for a correlated command reply."""
65
+
66
+
67
+ class AmbiguousCommandError(TransportError):
68
+ """A non-idempotent command was sent but its reply was lost.
69
+
70
+ The server may or may not have applied the mutation (claim / enqueue).
71
+ Do not retry with the same intent without checking queue state.
72
+ """
73
+
74
+
75
+ _STATUS_TO_EXC: dict[int, type[ContinuumError]] = {
76
+ 400: BadRequestError,
77
+ 401: UnauthorizedError,
78
+ 403: ForbiddenError,
79
+ 404: NotFoundError,
80
+ 409: ConflictError,
81
+ 429: RateLimitError,
82
+ }
83
+
84
+
85
+ def error_for_status(status_code: int, body: str) -> ContinuumError:
86
+ """Build the appropriate exception for a non-2xx HTTP or command status."""
87
+ exc_cls = _STATUS_TO_EXC.get(status_code)
88
+ if exc_cls is None:
89
+ if 500 <= status_code < 600:
90
+ exc_cls = ServerError
91
+ else:
92
+ exc_cls = ContinuumError
93
+ message = body if body else f"Request failed with status {status_code}"
94
+ return exc_cls(message, status_code=status_code, body=body)
@@ -0,0 +1,197 @@
1
+ """Pydantic models mirroring the Continuum Task Server JSON shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime
7
+ from enum import Enum
8
+ from typing import Any
9
+ from uuid import UUID
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class TaskStatus(str, Enum):
15
+ """Status of a queue item in the task processing lifecycle."""
16
+
17
+ OPEN = "OPEN"
18
+ CLAIMED = "CLAIMED"
19
+ STARTED = "STARTED"
20
+ TIMEDOUT = "TIMEDOUT"
21
+ CANCELLED = "CANCELLED"
22
+ KILLED = "KILLED"
23
+ ENDED = "ENDED"
24
+
25
+
26
+ class _Base(BaseModel):
27
+ model_config = ConfigDict(populate_by_name=True, extra="ignore")
28
+
29
+
30
+ class TaskType(_Base):
31
+ id: UUID | None = None
32
+ name: str | None = None
33
+ description: str | None = None
34
+ active_flag: bool = Field(default=False, alias="activeFlag")
35
+ organization_id: UUID | None = Field(default=None, alias="organizationId")
36
+ is_global: bool = Field(default=False, alias="isGlobal")
37
+ max_duration_sec: int | None = Field(default=None, alias="maxDurationSec")
38
+ heartbeat_timeout_sec: int | None = Field(default=None, alias="heartbeatTimeoutSec")
39
+
40
+
41
+ class TaskItem(_Base):
42
+ id: UUID | None = None
43
+ name: str | None = None
44
+ organization_id: UUID | None = Field(default=None, alias="organizationId")
45
+ is_global: bool = Field(default=False, alias="isGlobal")
46
+ active_flag: bool = Field(default=False, alias="activeFlag")
47
+ published_version: int | None = Field(default=None, alias="publishedVersion")
48
+ task_type_id: UUID | None = Field(default=None, alias="taskTypeId")
49
+
50
+
51
+ class TaskItemVersion(_Base):
52
+ id: UUID | None = None
53
+ task_item_id: UUID | None = Field(default=None, alias="taskItemId")
54
+ version_number: int = Field(default=0, alias="versionNumber")
55
+ content_url: str | None = Field(default=None, alias="contentUrl")
56
+ main_path: str | None = Field(default=None, alias="mainPath")
57
+ item_definition: str | None = Field(default=None, alias="itemDefinition")
58
+ active_flag: bool = Field(default=False, alias="activeFlag")
59
+ created_by: UUID | None = Field(default=None, alias="createdBy")
60
+ created_dtm: datetime | None = Field(default=None, alias="createdDtm")
61
+
62
+ @property
63
+ def item_definition_json(self) -> Any:
64
+ if not self.item_definition:
65
+ return None
66
+ try:
67
+ return json.loads(self.item_definition)
68
+ except (ValueError, TypeError):
69
+ return self.item_definition
70
+
71
+
72
+ class QueueItem(_Base):
73
+ id: UUID
74
+ depth: int = 0
75
+ task_type_id: UUID | None = Field(default=None, alias="taskTypeId")
76
+ task_item_version_id: UUID | None = Field(default=None, alias="taskItemVersionId")
77
+ priority: int = 0
78
+ task_status: TaskStatus | None = Field(default=None, alias="taskStatus")
79
+ organization_id: UUID | None = Field(default=None, alias="organizationId")
80
+ created_dtm: datetime | None = Field(default=None, alias="createdDtm")
81
+ created_by: UUID | None = Field(default=None, alias="createdBy")
82
+ input_data: str | None = Field(default=None, alias="inputData")
83
+ output_data: str | None = Field(default=None, alias="outputData")
84
+
85
+ @property
86
+ def input_data_json(self) -> Any:
87
+ """Parse `input_data` as JSON. Returns None if input_data is None/empty,
88
+ or the raw string if it's not valid JSON."""
89
+ if not self.input_data:
90
+ return None
91
+ try:
92
+ return json.loads(self.input_data)
93
+ except (ValueError, TypeError):
94
+ return self.input_data
95
+
96
+ @property
97
+ def output_data_json(self) -> Any:
98
+ if not self.output_data:
99
+ return None
100
+ try:
101
+ return json.loads(self.output_data)
102
+ except (ValueError, TypeError):
103
+ return self.output_data
104
+
105
+
106
+ class Content(_Base):
107
+ """Raw content returned from a content-store or queue-item content endpoint."""
108
+
109
+ data: bytes
110
+ mime_type: str = "application/octet-stream"
111
+
112
+ def as_text(self, encoding: str = "utf-8") -> str:
113
+ return self.data.decode(encoding)
114
+
115
+
116
+ class TransportMode(str, Enum):
117
+ """How ``TaskServer`` obtains work."""
118
+
119
+ HTTP = "http"
120
+ WEBSOCKET = "websocket"
121
+
122
+
123
+ class WaitMode(str, Enum):
124
+ """Durable completion-wait mode."""
125
+
126
+ EACH = "EACH"
127
+ ALL = "ALL"
128
+
129
+
130
+ class QueueEventType(str, Enum):
131
+ """Wire ``eventType`` values on ``/user/queue/events``."""
132
+
133
+ WORK_AVAILABLE = "work.available"
134
+ COMPLETED = "queue.completed"
135
+
136
+
137
+ class WaitTargetStatus(_Base):
138
+ """Per-item outcome on an ALL-mode completion summary."""
139
+
140
+ queue_item_id: UUID = Field(alias="queueItemId")
141
+ status: TaskStatus | str | None = None
142
+
143
+
144
+ class QueueEvent(_Base):
145
+ """Compact ``work.available`` hint or durable ``queue.completed`` delivery."""
146
+
147
+ event_id: UUID = Field(alias="eventId")
148
+ event_version: int = Field(default=1, alias="eventVersion")
149
+ event_type: QueueEventType = Field(alias="eventType")
150
+ occurred_at: datetime | None = Field(default=None, alias="occurredAt")
151
+ queue_item_id: UUID | None = Field(default=None, alias="queueItemId")
152
+ organization_id: UUID | None = Field(default=None, alias="organizationId")
153
+ task_type_id: UUID | None = Field(default=None, alias="taskTypeId")
154
+ task_name: str | None = Field(default=None, alias="taskName")
155
+ status: TaskStatus | None = None
156
+ subscription_id: UUID | None = Field(default=None, alias="subscriptionId")
157
+ correlation: str | None = None
158
+ wait_mode: WaitMode | None = Field(default=None, alias="waitMode")
159
+ targets: list[WaitTargetStatus] | None = None
160
+
161
+
162
+ class WorkSubscriptionResult(_Base):
163
+ """Result of ``subscribeWork`` / ``unsubscribeWork``."""
164
+
165
+ task_name: str | None = Field(default=None, alias="taskName")
166
+ subscribed: bool | None = None
167
+
168
+
169
+ class WaitSubscriptionResult(_Base):
170
+ """Result of ``subscribeWait`` / bind-only replay."""
171
+
172
+ subscription_id: UUID | None = Field(default=None, alias="subscriptionId")
173
+ subscriber_id: str | None = Field(default=None, alias="subscriberId")
174
+ wait_mode: WaitMode | None = Field(default=None, alias="waitMode")
175
+ pending_count: int = Field(default=0, alias="pendingCount")
176
+ closed: bool = False
177
+ subscribed: bool | None = None
178
+
179
+
180
+ class EventAckResult(_Base):
181
+ """Result of ``ackEvent``."""
182
+
183
+ subscription_id: UUID | None = Field(default=None, alias="subscriptionId")
184
+ closed: bool = False
185
+ acked: bool = True
186
+
187
+
188
+ class EnqueueAndSubscribeWaitResult(_Base):
189
+ """Combined result of ``enqueueAndSubscribeWait``."""
190
+
191
+ item: QueueItem
192
+ subscription_id: UUID | None = Field(default=None, alias="subscriptionId")
193
+ subscriber_id: str | None = Field(default=None, alias="subscriberId")
194
+ wait_mode: WaitMode | None = Field(default=None, alias="waitMode")
195
+ pending_count: int = Field(default=0, alias="pendingCount")
196
+ closed: bool = False
197
+ subscribed: bool | None = None