flo-python 0.1.1__py3-none-any.whl → 0.1.3__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 +46 -17
- flo/actions.py +74 -29
- flo/client.py +169 -47
- flo/exceptions.py +109 -24
- flo/kv.py +293 -371
- flo/kv_body.py +598 -0
- flo/kv_txn.py +57 -159
- flo/processing.py +30 -91
- flo/queue.py +25 -101
- flo/retry.py +146 -0
- flo/streams.py +32 -28
- flo/types.py +365 -231
- flo/wire.py +133 -240
- flo/worker.py +1107 -389
- flo_python-0.1.3.dist-info/METADATA +712 -0
- flo_python-0.1.3.dist-info/RECORD +19 -0
- {flo_python-0.1.1.dist-info → flo_python-0.1.3.dist-info}/WHEEL +1 -1
- flo_python-0.1.3.dist-info/licenses/LICENSE +202 -0
- flo_python-0.1.1.dist-info/METADATA +0 -568
- flo_python-0.1.1.dist-info/RECORD +0 -17
- flo_python-0.1.1.dist-info/licenses/LICENSE +0 -21
flo/__init__.py
CHANGED
|
@@ -34,6 +34,7 @@ Example:
|
|
|
34
34
|
from .client import FloClient
|
|
35
35
|
from .exceptions import (
|
|
36
36
|
BadRequestError,
|
|
37
|
+
BlockTooLongError,
|
|
37
38
|
ConflictError,
|
|
38
39
|
ConnectionFailedError,
|
|
39
40
|
FloError,
|
|
@@ -48,20 +49,25 @@ from .exceptions import (
|
|
|
48
49
|
NonRetryableError,
|
|
49
50
|
NotConnectedError,
|
|
50
51
|
NotFoundError,
|
|
52
|
+
OutcomePendingError,
|
|
51
53
|
OverloadedError,
|
|
52
54
|
PayloadTooLargeError,
|
|
53
55
|
ProtocolError,
|
|
54
56
|
RateLimitedError,
|
|
57
|
+
RequestTimeoutError,
|
|
55
58
|
ServerError,
|
|
59
|
+
StaleNotFoundError,
|
|
60
|
+
TableMismatchError,
|
|
56
61
|
UnauthorizedError,
|
|
62
|
+
UnavailableError,
|
|
57
63
|
UnexpectedEofError,
|
|
58
|
-
UnsupportedVersionError,
|
|
59
64
|
ValidationError,
|
|
60
65
|
ValueTooLargeError,
|
|
61
66
|
is_connection_error,
|
|
62
67
|
)
|
|
63
68
|
from .kv_txn import Transaction, TxnFinishedError, TxnUnsupportedOpError
|
|
64
69
|
from .processing import ProcessingOperations
|
|
70
|
+
from .retry import DEFAULT_RETRY_POLICY, RetryPolicy
|
|
65
71
|
from .types import (
|
|
66
72
|
AckOptions,
|
|
67
73
|
ActionDeleteOptions,
|
|
@@ -74,24 +80,27 @@ from .types import (
|
|
|
74
80
|
ActionRunStatus,
|
|
75
81
|
ActionStatusOptions,
|
|
76
82
|
ActionType,
|
|
83
|
+
Condition,
|
|
77
84
|
DeleteOptions,
|
|
78
85
|
DequeueOptions,
|
|
79
86
|
DequeueResult,
|
|
80
87
|
DlqListOptions,
|
|
81
88
|
DlqRequeueOptions,
|
|
82
89
|
EnqueueOptions,
|
|
90
|
+
ExistsResult,
|
|
83
91
|
GetJsonResult,
|
|
84
92
|
GetOptions,
|
|
85
93
|
GetResult,
|
|
86
94
|
HistoryOptions,
|
|
95
|
+
HistoryResult,
|
|
96
|
+
IncrResult,
|
|
87
97
|
KVBeginResult,
|
|
88
98
|
KVCommitResult,
|
|
89
|
-
KVEntry,
|
|
90
99
|
KVExistsOptions,
|
|
100
|
+
KVExpireOptions,
|
|
91
101
|
KVIncrOptions,
|
|
92
102
|
KVJsonOptions,
|
|
93
103
|
KVMGetOptions,
|
|
94
|
-
KVTouchOptions,
|
|
95
104
|
Message,
|
|
96
105
|
MGetEntry,
|
|
97
106
|
NackOptions,
|
|
@@ -102,17 +111,18 @@ from .types import (
|
|
|
102
111
|
ProcessingCancelOptions,
|
|
103
112
|
ProcessingListEntry,
|
|
104
113
|
ProcessingListOptions,
|
|
105
|
-
ProcessingRescaleOptions,
|
|
106
|
-
ProcessingRestoreOptions,
|
|
107
|
-
ProcessingSavepointOptions,
|
|
108
114
|
ProcessingStatusOptions,
|
|
109
115
|
ProcessingStatusResult,
|
|
110
116
|
ProcessingStopOptions,
|
|
111
117
|
ProcessingSubmitOptions,
|
|
112
118
|
ProcessingSyncOptions,
|
|
113
119
|
ProcessingSyncResult,
|
|
120
|
+
ProcessKind,
|
|
114
121
|
PutOptions,
|
|
115
122
|
PutResult,
|
|
123
|
+
Ran,
|
|
124
|
+
Reason,
|
|
125
|
+
RunStatus,
|
|
116
126
|
ScanOptions,
|
|
117
127
|
ScanResult,
|
|
118
128
|
StatusCode,
|
|
@@ -131,19 +141,23 @@ from .types import (
|
|
|
131
141
|
StreamReadResult,
|
|
132
142
|
StreamRecord,
|
|
133
143
|
StreamTrimOptions,
|
|
144
|
+
StreamTrimResult,
|
|
134
145
|
TaskAssignment,
|
|
135
|
-
TouchOptions,
|
|
136
146
|
VersionEntry,
|
|
147
|
+
WaitFor,
|
|
137
148
|
WorkerAwaitOptions,
|
|
138
149
|
WorkerAwaitResult,
|
|
139
150
|
WorkerCompleteOptions,
|
|
151
|
+
WorkerDetails,
|
|
140
152
|
WorkerFailOptions,
|
|
141
153
|
WorkerInfo,
|
|
142
154
|
WorkerListOptions,
|
|
143
155
|
WorkerListResult,
|
|
156
|
+
WorkerProcess,
|
|
144
157
|
WorkerRegisterOptions,
|
|
145
|
-
|
|
158
|
+
WorkerStatus,
|
|
146
159
|
WorkerTouchOptions,
|
|
160
|
+
WorkerType,
|
|
147
161
|
WorkflowCancelOptions,
|
|
148
162
|
WorkflowCreateOptions,
|
|
149
163
|
WorkflowDisableOptions,
|
|
@@ -170,7 +184,7 @@ from .worker import (
|
|
|
170
184
|
)
|
|
171
185
|
from .workflows import WorkflowOperations
|
|
172
186
|
|
|
173
|
-
__version__ = "0.1.
|
|
187
|
+
__version__ = "0.1.3"
|
|
174
188
|
|
|
175
189
|
__all__ = [
|
|
176
190
|
"Transaction",
|
|
@@ -182,13 +196,18 @@ __all__ = [
|
|
|
182
196
|
"KVIncrOptions",
|
|
183
197
|
"KVJsonOptions",
|
|
184
198
|
"KVMGetOptions",
|
|
185
|
-
"
|
|
199
|
+
"KVExpireOptions",
|
|
186
200
|
"GetResult",
|
|
201
|
+
"HistoryResult",
|
|
202
|
+
"ExistsResult",
|
|
187
203
|
"GetJsonResult",
|
|
188
204
|
"MGetEntry",
|
|
189
205
|
"PutResult",
|
|
206
|
+
"IncrResult",
|
|
190
207
|
# Client
|
|
191
208
|
"FloClient",
|
|
209
|
+
"RetryPolicy",
|
|
210
|
+
"DEFAULT_RETRY_POLICY",
|
|
192
211
|
# High-level Worker API
|
|
193
212
|
"ActionWorker",
|
|
194
213
|
"ActionWorkerOptions",
|
|
@@ -207,9 +226,10 @@ __all__ = [
|
|
|
207
226
|
"ConnectionFailedError",
|
|
208
227
|
"InvalidEndpointError",
|
|
209
228
|
"UnexpectedEofError",
|
|
229
|
+
"RequestTimeoutError",
|
|
210
230
|
"ProtocolError",
|
|
211
231
|
"InvalidMagicError",
|
|
212
|
-
"
|
|
232
|
+
"TableMismatchError",
|
|
213
233
|
"InvalidChecksumError",
|
|
214
234
|
"PayloadTooLargeError",
|
|
215
235
|
"IncompleteResponseError",
|
|
@@ -217,28 +237,35 @@ __all__ = [
|
|
|
217
237
|
"NamespaceTooLargeError",
|
|
218
238
|
"KeyTooLargeError",
|
|
219
239
|
"ValueTooLargeError",
|
|
240
|
+
"BlockTooLongError",
|
|
220
241
|
"ServerError",
|
|
221
242
|
"NotFoundError",
|
|
222
243
|
"BadRequestError",
|
|
223
244
|
"ConflictError",
|
|
224
245
|
"UnauthorizedError",
|
|
225
246
|
"OverloadedError",
|
|
247
|
+
"OutcomePendingError",
|
|
248
|
+
"StaleNotFoundError",
|
|
249
|
+
"UnavailableError",
|
|
226
250
|
"RateLimitedError",
|
|
227
251
|
"InternalServerError",
|
|
228
252
|
"GenericServerError",
|
|
229
253
|
"is_connection_error",
|
|
230
254
|
# Types
|
|
231
255
|
"OpCode",
|
|
256
|
+
"Ran",
|
|
257
|
+
"Reason",
|
|
232
258
|
"StatusCode",
|
|
233
259
|
"OptionTag",
|
|
234
260
|
# Result types
|
|
235
|
-
"KVEntry",
|
|
236
261
|
"ScanResult",
|
|
237
262
|
"VersionEntry",
|
|
238
263
|
"Message",
|
|
239
264
|
"DequeueResult",
|
|
240
265
|
# Options
|
|
266
|
+
"Condition",
|
|
241
267
|
"GetOptions",
|
|
268
|
+
"WaitFor",
|
|
242
269
|
"PutOptions",
|
|
243
270
|
"DeleteOptions",
|
|
244
271
|
"ScanOptions",
|
|
@@ -250,7 +277,6 @@ __all__ = [
|
|
|
250
277
|
"DlqListOptions",
|
|
251
278
|
"DlqRequeueOptions",
|
|
252
279
|
"PeekOptions",
|
|
253
|
-
"TouchOptions",
|
|
254
280
|
# Stream types
|
|
255
281
|
"StreamID",
|
|
256
282
|
"StorageTier",
|
|
@@ -264,6 +290,7 @@ __all__ = [
|
|
|
264
290
|
"StreamAppendOptions",
|
|
265
291
|
"StreamReadOptions",
|
|
266
292
|
"StreamTrimOptions",
|
|
293
|
+
"StreamTrimResult",
|
|
267
294
|
"StreamInfoOptions",
|
|
268
295
|
"StreamGroupJoinOptions",
|
|
269
296
|
"StreamGroupReadOptions",
|
|
@@ -271,6 +298,7 @@ __all__ = [
|
|
|
271
298
|
"StreamGroupNackOptions",
|
|
272
299
|
# Action types
|
|
273
300
|
"ActionType",
|
|
301
|
+
"RunStatus",
|
|
274
302
|
"ActionInfo",
|
|
275
303
|
"ActionRunStatus",
|
|
276
304
|
"ActionInvokeResult",
|
|
@@ -283,10 +311,14 @@ __all__ = [
|
|
|
283
311
|
"ActionDeleteOptions",
|
|
284
312
|
# Worker types
|
|
285
313
|
"TaskAssignment",
|
|
286
|
-
"WorkerTask",
|
|
287
314
|
"WorkerAwaitResult",
|
|
315
|
+
"ProcessKind",
|
|
316
|
+
"WorkerDetails",
|
|
288
317
|
"WorkerInfo",
|
|
318
|
+
"WorkerProcess",
|
|
289
319
|
"WorkerListResult",
|
|
320
|
+
"WorkerStatus",
|
|
321
|
+
"WorkerType",
|
|
290
322
|
# Worker options
|
|
291
323
|
"WorkerRegisterOptions",
|
|
292
324
|
"WorkerAwaitOptions",
|
|
@@ -316,9 +348,6 @@ __all__ = [
|
|
|
316
348
|
"ProcessingListOptions",
|
|
317
349
|
"ProcessingStopOptions",
|
|
318
350
|
"ProcessingCancelOptions",
|
|
319
|
-
"ProcessingSavepointOptions",
|
|
320
|
-
"ProcessingRestoreOptions",
|
|
321
|
-
"ProcessingRescaleOptions",
|
|
322
351
|
"ProcessingSyncOptions",
|
|
323
352
|
"ProcessingStatusResult",
|
|
324
353
|
"ProcessingListEntry",
|
flo/actions.py
CHANGED
|
@@ -9,6 +9,13 @@ For a higher-level API, see `Worker` in worker.py.
|
|
|
9
9
|
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
|
+
from .kv_body import (
|
|
13
|
+
decode_action_invoke_answer,
|
|
14
|
+
decode_action_status_answer,
|
|
15
|
+
decode_worker_info_answer,
|
|
16
|
+
decode_worker_list_answer,
|
|
17
|
+
encode_worker_list_body,
|
|
18
|
+
)
|
|
12
19
|
from .types import (
|
|
13
20
|
ActionDeleteOptions,
|
|
14
21
|
ActionInvokeOptions,
|
|
@@ -24,10 +31,12 @@ from .types import (
|
|
|
24
31
|
WorkerAwaitOptions,
|
|
25
32
|
WorkerAwaitResult,
|
|
26
33
|
WorkerCompleteOptions,
|
|
34
|
+
WorkerDetails,
|
|
27
35
|
WorkerFailOptions,
|
|
28
36
|
WorkerListOptions,
|
|
29
37
|
WorkerListResult,
|
|
30
38
|
WorkerRegisterOptions,
|
|
39
|
+
WorkerStatus,
|
|
31
40
|
WorkerTouchOptions,
|
|
32
41
|
)
|
|
33
42
|
from .wire import (
|
|
@@ -39,7 +48,6 @@ from .wire import (
|
|
|
39
48
|
serialize_worker_await_value,
|
|
40
49
|
serialize_worker_complete_value,
|
|
41
50
|
serialize_worker_fail_value,
|
|
42
|
-
serialize_worker_list_value,
|
|
43
51
|
serialize_worker_register_value,
|
|
44
52
|
serialize_worker_touch_value,
|
|
45
53
|
)
|
|
@@ -78,7 +86,7 @@ class ActionOperations:
|
|
|
78
86
|
action_type=action_type,
|
|
79
87
|
timeout_ms=opts.timeout_ms,
|
|
80
88
|
max_retries=opts.max_retries,
|
|
81
|
-
|
|
89
|
+
owner=opts.owner,
|
|
82
90
|
)
|
|
83
91
|
|
|
84
92
|
await self._client._send_and_check(
|
|
@@ -109,8 +117,7 @@ class ActionOperations:
|
|
|
109
117
|
|
|
110
118
|
value = serialize_action_invoke_value(
|
|
111
119
|
input_data=input_data,
|
|
112
|
-
|
|
113
|
-
idempotency_key=opts.idempotency_key,
|
|
120
|
+
labels=opts.labels,
|
|
114
121
|
)
|
|
115
122
|
|
|
116
123
|
response = await self._client._send_and_check(
|
|
@@ -120,9 +127,7 @@ class ActionOperations:
|
|
|
120
127
|
value,
|
|
121
128
|
)
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
run_id = response.data.decode("utf-8") if response.data else ""
|
|
125
|
-
return ActionInvokeResult(run_id=run_id)
|
|
130
|
+
return ActionInvokeResult(run_id=decode_action_invoke_answer(response.data))
|
|
126
131
|
|
|
127
132
|
async def status(
|
|
128
133
|
self,
|
|
@@ -148,13 +153,7 @@ class ActionOperations:
|
|
|
148
153
|
b"",
|
|
149
154
|
)
|
|
150
155
|
|
|
151
|
-
|
|
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
|
-
)
|
|
156
|
+
return decode_action_status_answer(run_id, response.data)
|
|
158
157
|
|
|
159
158
|
async def list(
|
|
160
159
|
self,
|
|
@@ -233,6 +232,7 @@ class WorkerOperations:
|
|
|
233
232
|
|
|
234
233
|
value = serialize_worker_register_value(
|
|
235
234
|
task_types,
|
|
235
|
+
worker_type=int(opts.worker_type),
|
|
236
236
|
max_concurrency=opts.concurrency,
|
|
237
237
|
metadata=opts.metadata,
|
|
238
238
|
machine_id=opts.machine_id,
|
|
@@ -250,24 +250,57 @@ class WorkerOperations:
|
|
|
250
250
|
worker_id: str,
|
|
251
251
|
current_load: int = 0,
|
|
252
252
|
namespace: str | None = None,
|
|
253
|
-
) ->
|
|
253
|
+
) -> WorkerStatus:
|
|
254
254
|
"""Send a worker heartbeat to keep the registration alive.
|
|
255
255
|
|
|
256
256
|
Args:
|
|
257
257
|
worker_id: Worker identifier.
|
|
258
258
|
current_load: Current number of active tasks.
|
|
259
259
|
namespace: Optional namespace override.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
The worker's status on the server: DRAINING once it is drained.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
NotFoundError: The server doesn't know the worker (it never
|
|
266
|
+
registered, or was removed after going silent).
|
|
260
267
|
"""
|
|
261
268
|
import struct
|
|
262
269
|
|
|
263
270
|
ns = self._client.get_namespace(namespace)
|
|
264
271
|
value = struct.pack("<I", current_load)
|
|
265
|
-
await self._client._send_and_check(
|
|
272
|
+
response = await self._client._send_and_check(
|
|
266
273
|
OpCode.WORKER_HEARTBEAT,
|
|
267
274
|
ns,
|
|
268
275
|
worker_id.encode("utf-8"),
|
|
269
276
|
value,
|
|
270
277
|
)
|
|
278
|
+
if len(response.data) != 1:
|
|
279
|
+
raise ValueError(f"heartbeat answer is {len(response.data)} bytes, want 1")
|
|
280
|
+
return WorkerStatus(response.data[0])
|
|
281
|
+
|
|
282
|
+
async def drain(self, worker_id: str, namespace: str | None = None) -> None:
|
|
283
|
+
"""Mark a worker draining: the server gives it no more work, and
|
|
284
|
+
its heartbeats answer DRAINING until it deregisters.
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
NotFoundError: The server doesn't know the worker.
|
|
288
|
+
"""
|
|
289
|
+
await self._client._send_and_check(
|
|
290
|
+
OpCode.WORKER_DRAIN,
|
|
291
|
+
self._client.get_namespace(namespace),
|
|
292
|
+
worker_id.encode("utf-8"),
|
|
293
|
+
b"",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async def deregister(self, worker_id: str, namespace: str | None = None) -> None:
|
|
297
|
+
"""Remove a worker from the registry."""
|
|
298
|
+
await self._client._send_and_check(
|
|
299
|
+
OpCode.WORKER_DEREGISTER,
|
|
300
|
+
self._client.get_namespace(namespace),
|
|
301
|
+
worker_id.encode("utf-8"),
|
|
302
|
+
b"",
|
|
303
|
+
)
|
|
271
304
|
|
|
272
305
|
async def await_task(
|
|
273
306
|
self,
|
|
@@ -280,7 +313,7 @@ class WorkerOperations:
|
|
|
280
313
|
Args:
|
|
281
314
|
worker_id: Worker identifier.
|
|
282
315
|
task_types: Task types to listen for.
|
|
283
|
-
options: Optional await options (block_ms
|
|
316
|
+
options: Optional await options (block_ms).
|
|
284
317
|
|
|
285
318
|
Returns:
|
|
286
319
|
WorkerAwaitResult with task if available.
|
|
@@ -290,12 +323,9 @@ class WorkerOperations:
|
|
|
290
323
|
|
|
291
324
|
value = serialize_worker_await_value(task_types)
|
|
292
325
|
|
|
293
|
-
# Build options for block_ms and timeout_ms
|
|
294
326
|
options_builder = OptionsBuilder()
|
|
295
327
|
if opts.block_ms is not None:
|
|
296
328
|
options_builder.add_u32(OptionTag.BLOCK_MS, opts.block_ms)
|
|
297
|
-
if opts.timeout_ms is not None:
|
|
298
|
-
options_builder.add_u32(OptionTag.TIMEOUT_MS, opts.timeout_ms)
|
|
299
329
|
|
|
300
330
|
response = await self._client._send_and_check(
|
|
301
331
|
OpCode.ACTION_AWAIT,
|
|
@@ -402,25 +432,40 @@ class WorkerOperations:
|
|
|
402
432
|
self,
|
|
403
433
|
options: WorkerListOptions | None = None,
|
|
404
434
|
) -> WorkerListResult:
|
|
405
|
-
"""List
|
|
435
|
+
"""List a page of the namespace's workers, in id order.
|
|
406
436
|
|
|
407
437
|
Args:
|
|
408
|
-
options:
|
|
438
|
+
options: The page's limit and cursor (the last page's ``cursor``;
|
|
439
|
+
None starts).
|
|
409
440
|
|
|
410
441
|
Returns:
|
|
411
|
-
WorkerListResult with
|
|
442
|
+
WorkerListResult with the page's workers and the next page's
|
|
443
|
+
cursor, None on the last page.
|
|
412
444
|
"""
|
|
413
445
|
opts = options or WorkerListOptions()
|
|
414
446
|
namespace = self._client.get_namespace(opts.namespace)
|
|
415
447
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
await self._client._send_and_check(
|
|
448
|
+
response = await self._client._send_and_check(
|
|
419
449
|
OpCode.WORKER_LIST,
|
|
420
450
|
namespace,
|
|
421
451
|
b"",
|
|
422
|
-
|
|
452
|
+
encode_worker_list_body(opts.limit, opts.cursor, opts.action),
|
|
423
453
|
)
|
|
424
454
|
|
|
425
|
-
|
|
426
|
-
return WorkerListResult(workers=
|
|
455
|
+
workers, cursor = decode_worker_list_answer(response.data)
|
|
456
|
+
return WorkerListResult(workers=workers, cursor=cursor)
|
|
457
|
+
|
|
458
|
+
async def info(self, worker_id: str, namespace: str | None = None) -> WorkerDetails:
|
|
459
|
+
"""A worker's record: its row, as :meth:`list` shows it, the metadata
|
|
460
|
+
it registered with, and its processes with their run and fail counts.
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
NotFoundError: The server doesn't know the worker.
|
|
464
|
+
"""
|
|
465
|
+
response = await self._client._send_and_check(
|
|
466
|
+
OpCode.WORKER_INFO,
|
|
467
|
+
self._client.get_namespace(namespace),
|
|
468
|
+
worker_id.encode("utf-8"),
|
|
469
|
+
b"",
|
|
470
|
+
)
|
|
471
|
+
return decode_worker_info_answer(response.data)
|