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 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
- json_steps = jsonpickle.encode(steps, unpicklable=False).encode("utf-8")
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
- if temp_wf_type != "transaction" or queue is not None:
283
- # Synchronously record the status and inputs for workflows and single-step workflows
284
- # We also have to do this for single-step workflows because of the foreign key constraint on the operation outputs table
285
- # TODO: Make this transactional (and with the queue step below)
286
- wf_status = dbos._sys_db.insert_workflow_status(
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
- else:
296
- # Buffer the inputs for single-transaction workflows, but don't buffer the status
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.buffer_workflow_status(status)
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("reset_system_database has no effect because global DBOS object does not exist")
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 (
@@ -25,7 +25,7 @@ def upgrade() -> None:
25
25
  "function_name",
26
26
  sa.Text(),
27
27
  nullable=False,
28
- default="",
28
+ server_default="",
29
29
  ),
30
30
  schema="dbos",
31
31
  )
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
- if workflow_uuid in self._temp_txn_wf_ids:
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.25.0a12
3
+ Version: 0.25.0a14
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,10 +1,10 @@
1
- dbos-0.25.0a12.dist-info/METADATA,sha256=_7a_sxE2zkGk8RlELQfYhjJ9az9uFS4FUNHA2haL3No,5554
2
- dbos-0.25.0a12.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- dbos-0.25.0a12.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-0.25.0a12.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
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=7kguOf9jEt4vg9LO-QJdh4jYddp6Uqtrt14gh7mKA2Y,6387
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=llBq1lW2spbRDa0ICgVdtgAxVwmGjYNtdXoOVut6xKQ,45732
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=YXQjNLR9SgOr2Y9R3tMBA_DkXBJPECzYRTaz-i9GNWA,45458
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=rwfMdqL6LYI4RwtxmB-OJbRKP_wQi-OA8fsmxley-OM,1042
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=k9fMfg6daAQB012uaPwgkiBhA0wPON8PIwY5Ki9_7Io,68208
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.0a12.dist-info/RECORD,,
69
+ dbos-0.25.0a14.dist-info/RECORD,,