dbos 0.25.0a12__py3-none-any.whl → 0.25.0a14__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.
- dbos/_admin_server.py +11 -3
- dbos/_core.py +20 -19
- dbos/_dbos.py +3 -9
- dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +1 -1
- dbos/_sys_db.py +1 -125
- {dbos-0.25.0a12.dist-info → dbos-0.25.0a14.dist-info}/METADATA +1 -1
- {dbos-0.25.0a12.dist-info → dbos-0.25.0a14.dist-info}/RECORD +10 -10
- {dbos-0.25.0a12.dist-info → dbos-0.25.0a14.dist-info}/WHEEL +0 -0
- {dbos-0.25.0a12.dist-info → dbos-0.25.0a14.dist-info}/entry_points.txt +0 -0
- {dbos-0.25.0a12.dist-info → dbos-0.25.0a14.dist-info}/licenses/LICENSE +0 -0
dbos/_admin_server.py
CHANGED
@@ -7,8 +7,6 @@ from functools import partial
|
|
7
7
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
8
8
|
from typing import TYPE_CHECKING, Any, List, TypedDict
|
9
9
|
|
10
|
-
import jsonpickle # type: ignore
|
11
|
-
|
12
10
|
from ._logger import dbos_logger
|
13
11
|
from ._recovery import recover_pending_workflows
|
14
12
|
|
@@ -162,7 +160,17 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
|
|
162
160
|
|
163
161
|
def _handle_steps(self, workflow_id: str) -> None:
|
164
162
|
steps = self.dbos._sys_db.get_workflow_steps(workflow_id)
|
165
|
-
|
163
|
+
|
164
|
+
updated_steps = [
|
165
|
+
{
|
166
|
+
**step,
|
167
|
+
"output": str(step["output"]) if step["output"] is not None else None,
|
168
|
+
"error": str(step["error"]) if step["error"] is not None else None,
|
169
|
+
}
|
170
|
+
for step in steps
|
171
|
+
]
|
172
|
+
|
173
|
+
json_steps = json.dumps(updated_steps).encode("utf-8")
|
166
174
|
self.send_response(200)
|
167
175
|
self._end_headers()
|
168
176
|
self.wfile.write(json_steps)
|
dbos/_core.py
CHANGED
@@ -279,25 +279,14 @@ def _init_workflow(
|
|
279
279
|
raise DBOSNonExistentWorkflowError(wfid)
|
280
280
|
wf_status = get_status_result["status"]
|
281
281
|
else:
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
status, max_recovery_attempts=max_recovery_attempts
|
288
|
-
)
|
289
|
-
|
290
|
-
# TODO: Modify the inputs if they were changed by `update_workflow_inputs`
|
291
|
-
dbos._sys_db.update_workflow_inputs(
|
292
|
-
wfid, _serialization.serialize_args(inputs)
|
293
|
-
)
|
282
|
+
# Synchronously record the status and inputs for workflows
|
283
|
+
# TODO: Make this transactional (and with the queue step below)
|
284
|
+
wf_status = dbos._sys_db.insert_workflow_status(
|
285
|
+
status, max_recovery_attempts=max_recovery_attempts
|
286
|
+
)
|
294
287
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
dbos._sys_db.buffer_workflow_inputs(
|
299
|
-
wfid, _serialization.serialize_args(inputs)
|
300
|
-
)
|
288
|
+
# TODO: Modify the inputs if they were changed by `update_workflow_inputs`
|
289
|
+
dbos._sys_db.update_workflow_inputs(wfid, _serialization.serialize_args(inputs))
|
301
290
|
|
302
291
|
if queue is not None and wf_status == WorkflowStatusString.ENQUEUED.value:
|
303
292
|
dbos._sys_db.enqueue(wfid, queue)
|
@@ -311,6 +300,18 @@ def _get_wf_invoke_func(
|
|
311
300
|
status: WorkflowStatusInternal,
|
312
301
|
) -> Callable[[Callable[[], R]], R]:
|
313
302
|
def persist(func: Callable[[], R]) -> R:
|
303
|
+
if not dbos.debug_mode and (
|
304
|
+
status["status"] == WorkflowStatusString.ERROR.value
|
305
|
+
or status["status"] == WorkflowStatusString.SUCCESS.value
|
306
|
+
):
|
307
|
+
dbos.logger.debug(
|
308
|
+
f"Workflow {status['workflow_uuid']} is already completed with status {status['status']}"
|
309
|
+
)
|
310
|
+
# Directly return the result if the workflow is already completed
|
311
|
+
recorded_result: R = dbos._sys_db.await_workflow_result(
|
312
|
+
status["workflow_uuid"]
|
313
|
+
)
|
314
|
+
return recorded_result
|
314
315
|
try:
|
315
316
|
output = func()
|
316
317
|
status["status"] = "SUCCESS"
|
@@ -319,7 +320,7 @@ def _get_wf_invoke_func(
|
|
319
320
|
if status["queue_name"] is not None:
|
320
321
|
queue = dbos._registry.queue_info_map[status["queue_name"]]
|
321
322
|
dbos._sys_db.remove_from_queue(status["workflow_uuid"], queue)
|
322
|
-
dbos._sys_db.
|
323
|
+
dbos._sys_db.update_workflow_status(status)
|
323
324
|
return output
|
324
325
|
except DBOSWorkflowConflictIDError:
|
325
326
|
# Await the workflow result
|
dbos/_dbos.py
CHANGED
@@ -485,14 +485,6 @@ class DBOS:
|
|
485
485
|
notification_listener_thread.start()
|
486
486
|
self._background_threads.append(notification_listener_thread)
|
487
487
|
|
488
|
-
# Start flush workflow buffers thread
|
489
|
-
flush_workflow_buffers_thread = threading.Thread(
|
490
|
-
target=self._sys_db.flush_workflow_buffers,
|
491
|
-
daemon=True,
|
492
|
-
)
|
493
|
-
flush_workflow_buffers_thread.start()
|
494
|
-
self._background_threads.append(flush_workflow_buffers_thread)
|
495
|
-
|
496
488
|
# Start the queue thread
|
497
489
|
evt = threading.Event()
|
498
490
|
self.stop_events.append(evt)
|
@@ -549,7 +541,9 @@ class DBOS:
|
|
549
541
|
if _dbos_global_instance is not None:
|
550
542
|
_dbos_global_instance._reset_system_database()
|
551
543
|
else:
|
552
|
-
dbos_logger.warning(
|
544
|
+
dbos_logger.warning(
|
545
|
+
"reset_system_database has no effect because global DBOS object does not exist"
|
546
|
+
)
|
553
547
|
|
554
548
|
def _reset_system_database(self) -> None:
|
555
549
|
assert (
|
dbos/_sys_db.py
CHANGED
@@ -166,8 +166,6 @@ class StepInfo(TypedDict):
|
|
166
166
|
|
167
167
|
|
168
168
|
_dbos_null_topic = "__null__topic__"
|
169
|
-
_buffer_flush_batch_size = 100
|
170
|
-
_buffer_flush_interval_secs = 1.0
|
171
169
|
|
172
170
|
|
173
171
|
class SystemDatabase:
|
@@ -264,32 +262,17 @@ class SystemDatabase:
|
|
264
262
|
self.notifications_map: Dict[str, threading.Condition] = {}
|
265
263
|
self.workflow_events_map: Dict[str, threading.Condition] = {}
|
266
264
|
|
267
|
-
# Initialize the workflow status and inputs buffers
|
268
|
-
self._workflow_status_buffer: Dict[str, WorkflowStatusInternal] = {}
|
269
|
-
self._workflow_inputs_buffer: Dict[str, str] = {}
|
270
|
-
# Two sets for tracking which single-transaction workflows have been exported to the status table
|
271
|
-
self._exported_temp_txn_wf_status: Set[str] = set()
|
272
|
-
self._temp_txn_wf_ids: Set[str] = set()
|
273
|
-
self._is_flushing_status_buffer = False
|
274
|
-
|
275
265
|
# Now we can run background processes
|
276
266
|
self._run_background_processes = True
|
277
267
|
self._debug_mode = debug_mode
|
278
268
|
|
279
269
|
# Destroy the pool when finished
|
280
270
|
def destroy(self) -> None:
|
281
|
-
self.wait_for_buffer_flush()
|
282
271
|
self._run_background_processes = False
|
283
272
|
if self.notification_conn is not None:
|
284
273
|
self.notification_conn.close()
|
285
274
|
self.engine.dispose()
|
286
275
|
|
287
|
-
def wait_for_buffer_flush(self) -> None:
|
288
|
-
# Wait until the buffers are flushed.
|
289
|
-
while self._is_flushing_status_buffer or not self._is_buffers_empty:
|
290
|
-
dbos_logger.debug("Waiting for system buffers to be exported")
|
291
|
-
time.sleep(1)
|
292
|
-
|
293
276
|
def insert_workflow_status(
|
294
277
|
self,
|
295
278
|
status: WorkflowStatusInternal,
|
@@ -440,10 +423,6 @@ class SystemDatabase:
|
|
440
423
|
with self.engine.begin() as c:
|
441
424
|
c.execute(cmd)
|
442
425
|
|
443
|
-
# If this is a single-transaction workflow, record that its status has been exported
|
444
|
-
if status["workflow_uuid"] in self._temp_txn_wf_ids:
|
445
|
-
self._exported_temp_txn_wf_status.add(status["workflow_uuid"])
|
446
|
-
|
447
426
|
def cancel_workflow(
|
448
427
|
self,
|
449
428
|
workflow_id: str,
|
@@ -621,10 +600,7 @@ class SystemDatabase:
|
|
621
600
|
f"Workflow {workflow_uuid} has been called multiple times with different inputs"
|
622
601
|
)
|
623
602
|
# TODO: actually changing the input
|
624
|
-
|
625
|
-
# Clean up the single-transaction tracking sets
|
626
|
-
self._exported_temp_txn_wf_status.discard(workflow_uuid)
|
627
|
-
self._temp_txn_wf_ids.discard(workflow_uuid)
|
603
|
+
|
628
604
|
return
|
629
605
|
|
630
606
|
def get_workflow_inputs(
|
@@ -1275,106 +1251,6 @@ class SystemDatabase:
|
|
1275
1251
|
)
|
1276
1252
|
return value
|
1277
1253
|
|
1278
|
-
def _flush_workflow_status_buffer(self) -> None:
|
1279
|
-
if self._debug_mode:
|
1280
|
-
raise Exception("called _flush_workflow_status_buffer in debug mode")
|
1281
|
-
|
1282
|
-
"""Export the workflow status buffer to the database, up to the batch size."""
|
1283
|
-
if len(self._workflow_status_buffer) == 0:
|
1284
|
-
return
|
1285
|
-
|
1286
|
-
# Record the exported status so far, and add them back on errors.
|
1287
|
-
exported_status: Dict[str, WorkflowStatusInternal] = {}
|
1288
|
-
with self.engine.begin() as c:
|
1289
|
-
exported = 0
|
1290
|
-
status_iter = iter(list(self._workflow_status_buffer))
|
1291
|
-
wf_id: Optional[str] = None
|
1292
|
-
while (
|
1293
|
-
exported < _buffer_flush_batch_size
|
1294
|
-
and (wf_id := next(status_iter, None)) is not None
|
1295
|
-
):
|
1296
|
-
# Pop the first key in the buffer (FIFO)
|
1297
|
-
status = self._workflow_status_buffer.pop(wf_id, None)
|
1298
|
-
if status is None:
|
1299
|
-
continue
|
1300
|
-
exported_status[wf_id] = status
|
1301
|
-
try:
|
1302
|
-
self.update_workflow_status(status, conn=c)
|
1303
|
-
exported += 1
|
1304
|
-
except Exception as e:
|
1305
|
-
dbos_logger.error(f"Error while flushing status buffer: {e}")
|
1306
|
-
c.rollback()
|
1307
|
-
# Add the exported status back to the buffer, so they can be retried next time
|
1308
|
-
self._workflow_status_buffer.update(exported_status)
|
1309
|
-
break
|
1310
|
-
|
1311
|
-
def _flush_workflow_inputs_buffer(self) -> None:
|
1312
|
-
if self._debug_mode:
|
1313
|
-
raise Exception("called _flush_workflow_inputs_buffer in debug mode")
|
1314
|
-
|
1315
|
-
"""Export the workflow inputs buffer to the database, up to the batch size."""
|
1316
|
-
if len(self._workflow_inputs_buffer) == 0:
|
1317
|
-
return
|
1318
|
-
|
1319
|
-
# Record exported inputs so far, and add them back on errors.
|
1320
|
-
exported_inputs: Dict[str, str] = {}
|
1321
|
-
with self.engine.begin() as c:
|
1322
|
-
exported = 0
|
1323
|
-
input_iter = iter(list(self._workflow_inputs_buffer))
|
1324
|
-
wf_id: Optional[str] = None
|
1325
|
-
while (
|
1326
|
-
exported < _buffer_flush_batch_size
|
1327
|
-
and (wf_id := next(input_iter, None)) is not None
|
1328
|
-
):
|
1329
|
-
if wf_id not in self._exported_temp_txn_wf_status:
|
1330
|
-
# Skip exporting inputs if the status has not been exported yet
|
1331
|
-
continue
|
1332
|
-
inputs = self._workflow_inputs_buffer.pop(wf_id, None)
|
1333
|
-
if inputs is None:
|
1334
|
-
continue
|
1335
|
-
exported_inputs[wf_id] = inputs
|
1336
|
-
try:
|
1337
|
-
self.update_workflow_inputs(wf_id, inputs, conn=c)
|
1338
|
-
exported += 1
|
1339
|
-
except Exception as e:
|
1340
|
-
dbos_logger.error(f"Error while flushing inputs buffer: {e}")
|
1341
|
-
c.rollback()
|
1342
|
-
# Add the exported inputs back to the buffer, so they can be retried next time
|
1343
|
-
self._workflow_inputs_buffer.update(exported_inputs)
|
1344
|
-
break
|
1345
|
-
|
1346
|
-
def flush_workflow_buffers(self) -> None:
|
1347
|
-
"""Flush the workflow status and inputs buffers periodically, via a background thread."""
|
1348
|
-
while self._run_background_processes:
|
1349
|
-
try:
|
1350
|
-
self._is_flushing_status_buffer = True
|
1351
|
-
# Must flush the status buffer first, as the inputs table has a foreign key constraint on the status table.
|
1352
|
-
self._flush_workflow_status_buffer()
|
1353
|
-
self._flush_workflow_inputs_buffer()
|
1354
|
-
self._is_flushing_status_buffer = False
|
1355
|
-
if self._is_buffers_empty:
|
1356
|
-
# Only sleep if both buffers are empty
|
1357
|
-
time.sleep(_buffer_flush_interval_secs)
|
1358
|
-
except Exception as e:
|
1359
|
-
dbos_logger.error(f"Error while flushing buffers: {e}")
|
1360
|
-
time.sleep(_buffer_flush_interval_secs)
|
1361
|
-
# Will retry next time
|
1362
|
-
|
1363
|
-
def buffer_workflow_status(self, status: WorkflowStatusInternal) -> None:
|
1364
|
-
self._workflow_status_buffer[status["workflow_uuid"]] = status
|
1365
|
-
|
1366
|
-
def buffer_workflow_inputs(self, workflow_id: str, inputs: str) -> None:
|
1367
|
-
# inputs is a serialized WorkflowInputs string
|
1368
|
-
self._workflow_inputs_buffer[workflow_id] = inputs
|
1369
|
-
self._temp_txn_wf_ids.add(workflow_id)
|
1370
|
-
|
1371
|
-
@property
|
1372
|
-
def _is_buffers_empty(self) -> bool:
|
1373
|
-
return (
|
1374
|
-
len(self._workflow_status_buffer) == 0
|
1375
|
-
and len(self._workflow_inputs_buffer) == 0
|
1376
|
-
)
|
1377
|
-
|
1378
1254
|
def enqueue(self, workflow_id: str, queue_name: str) -> None:
|
1379
1255
|
if self._debug_mode:
|
1380
1256
|
raise Exception("called enqueue in debug mode")
|
@@ -1,10 +1,10 @@
|
|
1
|
-
dbos-0.25.
|
2
|
-
dbos-0.25.
|
3
|
-
dbos-0.25.
|
4
|
-
dbos-0.25.
|
1
|
+
dbos-0.25.0a14.dist-info/METADATA,sha256=47yo_N6VbWT0oM9z9vavQ9nIVGLzzerDaIJXqeAyI_Q,5554
|
2
|
+
dbos-0.25.0a14.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
3
|
+
dbos-0.25.0a14.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
4
|
+
dbos-0.25.0a14.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
5
5
|
dbos/__init__.py,sha256=2Ur8QyNElSVn7CeL9Ovek2Zsye8A_ZCyjb9djF-N4A4,785
|
6
6
|
dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
|
7
|
-
dbos/_admin_server.py,sha256=
|
7
|
+
dbos/_admin_server.py,sha256=FLUacm9WGIPjB5s3QhdpMCilc8JHJOF0KMNStF82qs0,6625
|
8
8
|
dbos/_app_db.py,sha256=4EGrYL14rVx96TXn34hoibN9ltf4-2DKcj6nd-HvBxA,6262
|
9
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
10
10
|
dbos/_cloudutils/authentication.py,sha256=V0fCWQN9stCkhbuuxgPTGpvuQcDqfU3KAxPAh01vKW4,5007
|
@@ -13,10 +13,10 @@ dbos/_cloudutils/databases.py,sha256=_shqaqSvhY4n2ScgQ8IP5PDZvzvcx3YBKV8fj-cxhSY
|
|
13
13
|
dbos/_conductor/conductor.py,sha256=7elKINsgl4s1Tg5DwrU-K7xQ5vQvmDAIfAvUgfwpGN0,16784
|
14
14
|
dbos/_conductor/protocol.py,sha256=xN7pmooyF1pqbH1b6WhllU5718P7zSb_b0KCwA6bzcs,6716
|
15
15
|
dbos/_context.py,sha256=3He4w46OTFbR7h8U1MLcdaU10wNyIPBSRqzLkdggv7U,19368
|
16
|
-
dbos/_core.py,sha256=
|
16
|
+
dbos/_core.py,sha256=kIj_4wlIff8ptlACJKXAPSNoyJIt2h44swjMKxfwv0k,45789
|
17
17
|
dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
|
18
18
|
dbos/_db_wizard.py,sha256=VnMa6OL87Lc-XPDD1RnXp8NjsJE8YgiQLj3wtWAXp-8,8252
|
19
|
-
dbos/_dbos.py,sha256=
|
19
|
+
dbos/_dbos.py,sha256=IZ3Qj8UBUqYYJiG6HVz35A0GQnQyI1Po_-E6DDvWlQg,45147
|
20
20
|
dbos/_dbos_config.py,sha256=7Qm3FARP3lTKZS0gSxDHLbpaDCT30GzfyERxfCde4bc,21566
|
21
21
|
dbos/_debug.py,sha256=mmgvLkqlrljMBBow9wk01PPur9kUf2rI_11dTJXY4gw,1822
|
22
22
|
dbos/_error.py,sha256=B6Y9XLS1f6yrawxB2uAEYFMxFwk9BHhdxPNddKco-Fw,5399
|
@@ -34,7 +34,7 @@ dbos/_migrations/versions/a3b18ad34abe_added_triggers.py,sha256=Rv0ZsZYZ_WdgGEUL
|
|
34
34
|
dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py,sha256=8PyFi8rd6CN-mUro43wGhsg5wcQWKZPRHD6jw8R5pVc,986
|
35
35
|
dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4hGBC02Ptng1715roTjY3xiyzZU4,729
|
36
36
|
dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
|
37
|
-
dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256=
|
37
|
+
dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256=m90Lc5YH0ZISSq1MyxND6oq3RZrZKrIqEsZtwJ1jWxA,1049
|
38
38
|
dbos/_outcome.py,sha256=EXxBg4jXCVJsByDQ1VOCIedmbeq_03S6d-p1vqQrLFU,6810
|
39
39
|
dbos/_queue.py,sha256=yYwKCjxCSFjtD63vpnRQmb835BZAe9UATgWjMW6dcnY,3341
|
40
40
|
dbos/_recovery.py,sha256=4KyZb0XJEUGH7ekYT1kpx38i6y5vygPeH75Ta7RZjYo,2596
|
@@ -46,7 +46,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
dbos/_schemas/application_database.py,sha256=KeyoPrF7hy_ODXV7QNike_VFSD74QBRfQ76D7QyE9HI,966
|
47
47
|
dbos/_schemas/system_database.py,sha256=W9eSpL7SZzQkxcEZ4W07BOcwkkDr35b9oCjUOgfHWek,5336
|
48
48
|
dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
|
49
|
-
dbos/_sys_db.py,sha256=
|
49
|
+
dbos/_sys_db.py,sha256=33euvXfpbpVaCpR0Sx5eQ4yBt1gRKGdvfGQUugqoJBY,62320
|
50
50
|
dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
|
51
51
|
dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
52
|
dbos/_templates/dbos-db-starter/__package/main.py,sha256=nJMN3ZD2lmwg4Dcgmiwqc-tQGuCJuJal2Xl85iA277U,2453
|
@@ -66,4 +66,4 @@ dbos/cli/cli.py,sha256=ut47q-R6A423je0zvBTEgwdxENagaKKoyIvyTeACFIU,15977
|
|
66
66
|
dbos/dbos-config.schema.json,sha256=HtF_njVTGHLdzBGZ4OrGQz3qbPPT0Go-iwd1PgFVTNg,5847
|
67
67
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
68
68
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
69
|
-
dbos-0.25.
|
69
|
+
dbos-0.25.0a14.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|