flo-python 0.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.
flo/__init__.py ADDED
@@ -0,0 +1,214 @@
1
+ """Flo Python SDK
2
+
3
+ A Python client for the Flo distributed systems platform.
4
+
5
+ Example:
6
+ import asyncio
7
+ from flo import FloClient
8
+
9
+ async def main():
10
+ async with FloClient("localhost:9000") as client:
11
+ # KV operations
12
+ await client.kv.put("key", b"value")
13
+ value = await client.kv.get("key")
14
+ print(f"Got: {value}")
15
+
16
+ # Queue operations
17
+ seq = await client.queue.enqueue("tasks", b'{"task": "process"}')
18
+ result = await client.queue.dequeue("tasks", 10)
19
+ for msg in result.messages:
20
+ print(f"Message: {msg.payload}")
21
+ await client.queue.ack("tasks", [msg.seq])
22
+
23
+ asyncio.run(main())
24
+ """
25
+
26
+ from .client import FloClient
27
+ from .exceptions import (
28
+ BadRequestError,
29
+ ConflictError,
30
+ ConnectionFailedError,
31
+ FloError,
32
+ GenericServerError,
33
+ IncompleteResponseError,
34
+ InternalServerError,
35
+ InvalidChecksumError,
36
+ InvalidEndpointError,
37
+ InvalidMagicError,
38
+ KeyTooLargeError,
39
+ NamespaceTooLargeError,
40
+ NotConnectedError,
41
+ NotFoundError,
42
+ OverloadedError,
43
+ PayloadTooLargeError,
44
+ ProtocolError,
45
+ RateLimitedError,
46
+ ServerError,
47
+ UnauthorizedError,
48
+ UnexpectedEofError,
49
+ UnsupportedVersionError,
50
+ ValidationError,
51
+ ValueTooLargeError,
52
+ )
53
+ from .types import (
54
+ # KV types
55
+ AckOptions,
56
+ # Action types
57
+ ActionDeleteOptions,
58
+ ActionInfo,
59
+ ActionInvokeOptions,
60
+ ActionInvokeResult,
61
+ ActionListOptions,
62
+ ActionListResult,
63
+ ActionRegisterOptions,
64
+ ActionRunStatus,
65
+ ActionStatusOptions,
66
+ ActionType,
67
+ DeleteOptions,
68
+ DequeueOptions,
69
+ DequeueResult,
70
+ DlqListOptions,
71
+ DlqRequeueOptions,
72
+ EnqueueOptions,
73
+ GetOptions,
74
+ HistoryOptions,
75
+ KVEntry,
76
+ Message,
77
+ NackOptions,
78
+ OpCode,
79
+ OptionTag,
80
+ PeekOptions,
81
+ PutOptions,
82
+ ScanOptions,
83
+ ScanResult,
84
+ StatusCode,
85
+ # Stream types
86
+ StorageTier,
87
+ StreamAppendOptions,
88
+ StreamAppendResult,
89
+ StreamGroupAckOptions,
90
+ StreamGroupJoinOptions,
91
+ StreamGroupReadOptions,
92
+ StreamID,
93
+ StreamInfo,
94
+ StreamInfoOptions,
95
+ StreamReadOptions,
96
+ StreamReadResult,
97
+ StreamRecord,
98
+ StreamTrimOptions,
99
+ # Worker types
100
+ TaskAssignment,
101
+ TouchOptions,
102
+ VersionEntry,
103
+ WorkerAwaitOptions,
104
+ WorkerAwaitResult,
105
+ WorkerCompleteOptions,
106
+ WorkerFailOptions,
107
+ WorkerInfo,
108
+ WorkerListOptions,
109
+ WorkerListResult,
110
+ WorkerRegisterOptions,
111
+ WorkerTask,
112
+ WorkerTouchOptions,
113
+ )
114
+ from .worker import ActionContext, Worker, WorkerConfig
115
+
116
+ __version__ = "0.1.0"
117
+
118
+ __all__ = [
119
+ # Client
120
+ "FloClient",
121
+ # High-level Worker API
122
+ "Worker",
123
+ "WorkerConfig",
124
+ "ActionContext",
125
+ # Exceptions
126
+ "FloError",
127
+ "NotConnectedError",
128
+ "ConnectionFailedError",
129
+ "InvalidEndpointError",
130
+ "UnexpectedEofError",
131
+ "ProtocolError",
132
+ "InvalidMagicError",
133
+ "UnsupportedVersionError",
134
+ "InvalidChecksumError",
135
+ "PayloadTooLargeError",
136
+ "IncompleteResponseError",
137
+ "ValidationError",
138
+ "NamespaceTooLargeError",
139
+ "KeyTooLargeError",
140
+ "ValueTooLargeError",
141
+ "ServerError",
142
+ "NotFoundError",
143
+ "BadRequestError",
144
+ "ConflictError",
145
+ "UnauthorizedError",
146
+ "OverloadedError",
147
+ "RateLimitedError",
148
+ "InternalServerError",
149
+ "GenericServerError",
150
+ # Types
151
+ "OpCode",
152
+ "StatusCode",
153
+ "OptionTag",
154
+ # Result types
155
+ "KVEntry",
156
+ "ScanResult",
157
+ "VersionEntry",
158
+ "Message",
159
+ "DequeueResult",
160
+ # Options
161
+ "GetOptions",
162
+ "PutOptions",
163
+ "DeleteOptions",
164
+ "ScanOptions",
165
+ "HistoryOptions",
166
+ "EnqueueOptions",
167
+ "DequeueOptions",
168
+ "AckOptions",
169
+ "NackOptions",
170
+ "DlqListOptions",
171
+ "DlqRequeueOptions",
172
+ "PeekOptions",
173
+ "TouchOptions",
174
+ # Stream types
175
+ "StreamID",
176
+ "StorageTier",
177
+ "StreamRecord",
178
+ "StreamAppendResult",
179
+ "StreamReadResult",
180
+ "StreamInfo",
181
+ # Stream options
182
+ "StreamAppendOptions",
183
+ "StreamReadOptions",
184
+ "StreamTrimOptions",
185
+ "StreamInfoOptions",
186
+ "StreamGroupJoinOptions",
187
+ "StreamGroupReadOptions",
188
+ "StreamGroupAckOptions",
189
+ # Action types
190
+ "ActionType",
191
+ "ActionInfo",
192
+ "ActionRunStatus",
193
+ "ActionInvokeResult",
194
+ "ActionListResult",
195
+ # Action options
196
+ "ActionRegisterOptions",
197
+ "ActionInvokeOptions",
198
+ "ActionStatusOptions",
199
+ "ActionListOptions",
200
+ "ActionDeleteOptions",
201
+ # Worker types
202
+ "TaskAssignment",
203
+ "WorkerTask",
204
+ "WorkerAwaitResult",
205
+ "WorkerInfo",
206
+ "WorkerListResult",
207
+ # Worker options
208
+ "WorkerRegisterOptions",
209
+ "WorkerAwaitOptions",
210
+ "WorkerTouchOptions",
211
+ "WorkerCompleteOptions",
212
+ "WorkerFailOptions",
213
+ "WorkerListOptions",
214
+ ]
flo/actions.py ADDED
@@ -0,0 +1,397 @@
1
+ """Flo Actions - Low-Level Action/Worker Protocol
2
+
3
+ This module provides low-level operations for actions and workers:
4
+ - Action registration, invocation, and status checking
5
+ - Worker registration, task awaiting, completion, and failure reporting
6
+
7
+ For a higher-level API, see `Worker` in worker.py.
8
+ """
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from .types import (
13
+ ActionDeleteOptions,
14
+ ActionInvokeOptions,
15
+ ActionInvokeResult,
16
+ ActionListOptions,
17
+ ActionListResult,
18
+ ActionRegisterOptions,
19
+ ActionRunStatus,
20
+ ActionStatusOptions,
21
+ ActionType,
22
+ OpCode,
23
+ OptionTag,
24
+ WorkerAwaitOptions,
25
+ WorkerAwaitResult,
26
+ WorkerCompleteOptions,
27
+ WorkerFailOptions,
28
+ WorkerListOptions,
29
+ WorkerListResult,
30
+ WorkerRegisterOptions,
31
+ WorkerTouchOptions,
32
+ )
33
+ from .wire import (
34
+ OptionsBuilder,
35
+ parse_task_assignment,
36
+ serialize_action_invoke_value,
37
+ serialize_action_list_value,
38
+ serialize_action_register_value,
39
+ serialize_worker_await_value,
40
+ serialize_worker_complete_value,
41
+ serialize_worker_fail_value,
42
+ serialize_worker_list_value,
43
+ serialize_worker_register_value,
44
+ serialize_worker_touch_value,
45
+ )
46
+
47
+ if TYPE_CHECKING:
48
+ from .client import FloClient
49
+
50
+
51
+ class ActionOperations:
52
+ """Action operations for the Flo client.
53
+
54
+ Provides low-level operations for registering and invoking actions.
55
+ For a higher-level API, see `Worker` in worker.py.
56
+ """
57
+
58
+ def __init__(self, client: "FloClient") -> None:
59
+ self._client = client
60
+
61
+ async def register(
62
+ self,
63
+ name: str,
64
+ action_type: ActionType = ActionType.USER,
65
+ options: ActionRegisterOptions | None = None,
66
+ ) -> None:
67
+ """Register an action.
68
+
69
+ Args:
70
+ name: Action name.
71
+ action_type: Type of action (USER or WASM).
72
+ options: Optional registration options.
73
+ """
74
+ opts = options or ActionRegisterOptions()
75
+ namespace = self._client.get_namespace(opts.namespace)
76
+
77
+ value = serialize_action_register_value(
78
+ action_type=action_type,
79
+ timeout_ms=opts.timeout_ms,
80
+ max_retries=opts.max_retries,
81
+ description=opts.description,
82
+ )
83
+
84
+ await self._client._send_and_check(
85
+ OpCode.ACTION_REGISTER,
86
+ namespace,
87
+ name.encode("utf-8"),
88
+ value,
89
+ )
90
+
91
+ async def invoke(
92
+ self,
93
+ name: str,
94
+ input_data: bytes,
95
+ options: ActionInvokeOptions | None = None,
96
+ ) -> ActionInvokeResult:
97
+ """Invoke an action.
98
+
99
+ Args:
100
+ name: Action name to invoke.
101
+ input_data: Input data for the action.
102
+ options: Optional invoke options.
103
+
104
+ Returns:
105
+ ActionInvokeResult with run_id.
106
+ """
107
+ opts = options or ActionInvokeOptions()
108
+ namespace = self._client.get_namespace(opts.namespace)
109
+
110
+ value = serialize_action_invoke_value(
111
+ input_data=input_data,
112
+ priority=opts.priority,
113
+ idempotency_key=opts.idempotency_key,
114
+ )
115
+
116
+ response = await self._client._send_and_check(
117
+ OpCode.ACTION_INVOKE,
118
+ namespace,
119
+ name.encode("utf-8"),
120
+ value,
121
+ )
122
+
123
+ # Response contains run_id as string
124
+ run_id = response.data.decode("utf-8") if response.data else ""
125
+ return ActionInvokeResult(run_id=run_id)
126
+
127
+ async def status(
128
+ self,
129
+ run_id: str,
130
+ options: ActionStatusOptions | None = None,
131
+ ) -> ActionRunStatus:
132
+ """Get action run status.
133
+
134
+ Args:
135
+ run_id: The run ID to check.
136
+ options: Optional status options.
137
+
138
+ Returns:
139
+ ActionRunStatus with current status.
140
+ """
141
+ opts = options or ActionStatusOptions()
142
+ namespace = self._client.get_namespace(opts.namespace)
143
+
144
+ response = await self._client._send_and_check(
145
+ OpCode.ACTION_STATUS,
146
+ namespace,
147
+ run_id.encode("utf-8"),
148
+ b"",
149
+ )
150
+
151
+ # Parse status response - format TBD based on server implementation
152
+ # For now return basic status
153
+ return ActionRunStatus(
154
+ run_id=run_id,
155
+ status="unknown",
156
+ result=response.data if response.data else None,
157
+ )
158
+
159
+ async def list(
160
+ self,
161
+ options: ActionListOptions | None = None,
162
+ ) -> ActionListResult:
163
+ """List registered actions.
164
+
165
+ Args:
166
+ options: Optional list options.
167
+
168
+ Returns:
169
+ ActionListResult with list of actions.
170
+ """
171
+ opts = options or ActionListOptions()
172
+ namespace = self._client.get_namespace(opts.namespace)
173
+
174
+ value = serialize_action_list_value(limit=opts.limit)
175
+
176
+ await self._client._send_and_check(
177
+ OpCode.ACTION_LIST,
178
+ namespace,
179
+ (opts.prefix or "").encode("utf-8"),
180
+ value,
181
+ )
182
+
183
+ # Parse list response - format TBD based on server implementation
184
+ return ActionListResult(actions=[])
185
+
186
+ async def delete(
187
+ self,
188
+ name: str,
189
+ options: ActionDeleteOptions | None = None,
190
+ ) -> None:
191
+ """Delete an action.
192
+
193
+ Args:
194
+ name: Action name to delete.
195
+ options: Optional delete options.
196
+ """
197
+ opts = options or ActionDeleteOptions()
198
+ namespace = self._client.get_namespace(opts.namespace)
199
+
200
+ await self._client._send_and_check(
201
+ OpCode.ACTION_DELETE,
202
+ namespace,
203
+ name.encode("utf-8"),
204
+ b"",
205
+ )
206
+
207
+
208
+ class WorkerOperations:
209
+ """Worker operations for the Flo client.
210
+
211
+ Provides low-level operations for worker registration and task processing.
212
+ For a higher-level API, see `Worker` in worker.py.
213
+ """
214
+
215
+ def __init__(self, client: "FloClient") -> None:
216
+ self._client = client
217
+
218
+ async def register(
219
+ self,
220
+ worker_id: str,
221
+ task_types: list[str],
222
+ options: WorkerRegisterOptions | None = None,
223
+ ) -> None:
224
+ """Register a worker.
225
+
226
+ Args:
227
+ worker_id: Unique worker identifier.
228
+ task_types: List of task types this worker can handle.
229
+ options: Optional registration options.
230
+ """
231
+ opts = options or WorkerRegisterOptions()
232
+ namespace = self._client.get_namespace(opts.namespace)
233
+
234
+ value = serialize_worker_register_value(task_types)
235
+
236
+ await self._client._send_and_check(
237
+ OpCode.WORKER_REGISTER,
238
+ namespace,
239
+ worker_id.encode("utf-8"),
240
+ value,
241
+ )
242
+
243
+ async def await_task(
244
+ self,
245
+ worker_id: str,
246
+ task_types: list[str],
247
+ options: WorkerAwaitOptions | None = None,
248
+ ) -> WorkerAwaitResult:
249
+ """Wait for a task assignment.
250
+
251
+ Args:
252
+ worker_id: Worker identifier.
253
+ task_types: Task types to listen for.
254
+ options: Optional await options (block_ms, timeout_ms).
255
+
256
+ Returns:
257
+ WorkerAwaitResult with task if available.
258
+ """
259
+ opts = options or WorkerAwaitOptions()
260
+ namespace = self._client.get_namespace(opts.namespace)
261
+
262
+ value = serialize_worker_await_value(task_types)
263
+
264
+ # Build options for block_ms and timeout_ms
265
+ options_builder = OptionsBuilder()
266
+ if opts.block_ms is not None:
267
+ options_builder.add_u32(OptionTag.BLOCK_MS, opts.block_ms)
268
+ if opts.timeout_ms is not None:
269
+ options_builder.add_u32(OptionTag.TIMEOUT_MS, opts.timeout_ms)
270
+
271
+ response = await self._client._send_and_check(
272
+ OpCode.WORKER_AWAIT,
273
+ namespace,
274
+ worker_id.encode("utf-8"),
275
+ value,
276
+ options_builder.build(),
277
+ )
278
+
279
+ # Parse task assignment response - if empty, no task available
280
+ if not response.data:
281
+ return WorkerAwaitResult(task=None)
282
+
283
+ # Parse task assignment
284
+ task = parse_task_assignment(response.data)
285
+ return WorkerAwaitResult(task=task)
286
+
287
+ async def touch(
288
+ self,
289
+ worker_id: str,
290
+ task_id: str,
291
+ options: WorkerTouchOptions | None = None,
292
+ ) -> None:
293
+ """Extend task lease (heartbeat).
294
+
295
+ Args:
296
+ worker_id: Worker identifier.
297
+ task_id: Task identifier to extend.
298
+ options: Optional touch options (extend_ms).
299
+ """
300
+ opts = options or WorkerTouchOptions()
301
+ namespace = self._client.get_namespace(opts.namespace)
302
+
303
+ value = serialize_worker_touch_value(task_id, opts.extend_ms)
304
+
305
+ await self._client._send_and_check(
306
+ OpCode.WORKER_TOUCH,
307
+ namespace,
308
+ worker_id.encode("utf-8"),
309
+ value,
310
+ )
311
+
312
+ async def complete(
313
+ self,
314
+ worker_id: str,
315
+ task_id: str,
316
+ result: bytes,
317
+ options: WorkerCompleteOptions | None = None,
318
+ ) -> None:
319
+ """Complete a task successfully.
320
+
321
+ Args:
322
+ worker_id: Worker identifier.
323
+ task_id: Task identifier to complete.
324
+ result: Result data from the task.
325
+ options: Optional complete options.
326
+ """
327
+ opts = options or WorkerCompleteOptions()
328
+ namespace = self._client.get_namespace(opts.namespace)
329
+
330
+ value = serialize_worker_complete_value(task_id, result)
331
+
332
+ await self._client._send_and_check(
333
+ OpCode.WORKER_COMPLETE,
334
+ namespace,
335
+ worker_id.encode("utf-8"),
336
+ value,
337
+ )
338
+
339
+ async def fail(
340
+ self,
341
+ worker_id: str,
342
+ task_id: str,
343
+ error_message: str,
344
+ options: WorkerFailOptions | None = None,
345
+ ) -> None:
346
+ """Fail a task.
347
+
348
+ Args:
349
+ worker_id: Worker identifier.
350
+ task_id: Task identifier that failed.
351
+ error_message: Error message describing the failure.
352
+ options: Optional fail options (retry flag).
353
+ """
354
+ opts = options or WorkerFailOptions()
355
+ namespace = self._client.get_namespace(opts.namespace)
356
+
357
+ value = serialize_worker_fail_value(task_id, error_message)
358
+
359
+ # Retry flag goes in TLV options (matches Go SDK)
360
+ options_builder = OptionsBuilder()
361
+ if opts.retry:
362
+ options_builder.add_flag(OptionTag.RETRY)
363
+
364
+ await self._client._send_and_check(
365
+ OpCode.WORKER_FAIL,
366
+ namespace,
367
+ worker_id.encode("utf-8"),
368
+ value,
369
+ options_builder.build(),
370
+ )
371
+
372
+ async def list(
373
+ self,
374
+ options: WorkerListOptions | None = None,
375
+ ) -> WorkerListResult:
376
+ """List registered workers.
377
+
378
+ Args:
379
+ options: Optional list options.
380
+
381
+ Returns:
382
+ WorkerListResult with list of workers.
383
+ """
384
+ opts = options or WorkerListOptions()
385
+ namespace = self._client.get_namespace(opts.namespace)
386
+
387
+ value = serialize_worker_list_value(opts.limit)
388
+
389
+ await self._client._send_and_check(
390
+ OpCode.WORKER_LIST,
391
+ namespace,
392
+ b"",
393
+ value,
394
+ )
395
+
396
+ # Parse list response - format TBD based on server implementation
397
+ return WorkerListResult(workers=[])