dbos 0.26.0a21__py3-none-any.whl → 0.26.0a23__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/_client.py +9 -0
 - dbos/_context.py +62 -0
 - dbos/_core.py +88 -19
 - dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +44 -0
 - dbos/_schemas/system_database.py +3 -1
 - dbos/_sys_db.py +97 -50
 - dbos/dbos-config.schema.json +25 -16
 - {dbos-0.26.0a21.dist-info → dbos-0.26.0a23.dist-info}/METADATA +1 -1
 - {dbos-0.26.0a21.dist-info → dbos-0.26.0a23.dist-info}/RECORD +12 -11
 - {dbos-0.26.0a21.dist-info → dbos-0.26.0a23.dist-info}/WHEEL +0 -0
 - {dbos-0.26.0a21.dist-info → dbos-0.26.0a23.dist-info}/entry_points.txt +0 -0
 - {dbos-0.26.0a21.dist-info → dbos-0.26.0a23.dist-info}/licenses/LICENSE +0 -0
 
    
        dbos/_client.py
    CHANGED
    
    | 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import asyncio
         
     | 
| 
       2 
2 
     | 
    
         
             
            import sys
         
     | 
| 
      
 3 
     | 
    
         
            +
            import time
         
     | 
| 
       3 
4 
     | 
    
         
             
            import uuid
         
     | 
| 
       4 
5 
     | 
    
         
             
            from typing import Any, Generic, List, Optional, TypedDict, TypeVar
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
         @@ -39,6 +40,7 @@ class EnqueueOptions(TypedDict): 
     | 
|
| 
       39 
40 
     | 
    
         
             
                queue_name: str
         
     | 
| 
       40 
41 
     | 
    
         
             
                workflow_id: NotRequired[str]
         
     | 
| 
       41 
42 
     | 
    
         
             
                app_version: NotRequired[str]
         
     | 
| 
      
 43 
     | 
    
         
            +
                workflow_timeout: NotRequired[float]
         
     | 
| 
       42 
44 
     | 
    
         | 
| 
       43 
45 
     | 
    
         | 
| 
       44 
46 
     | 
    
         
             
            class WorkflowHandleClientPolling(Generic[R]):
         
     | 
| 
         @@ -107,6 +109,7 @@ class DBOSClient: 
     | 
|
| 
       107 
109 
     | 
    
         
             
                    workflow_id = options.get("workflow_id")
         
     | 
| 
       108 
110 
     | 
    
         
             
                    if workflow_id is None:
         
     | 
| 
       109 
111 
     | 
    
         
             
                        workflow_id = str(uuid.uuid4())
         
     | 
| 
      
 112 
     | 
    
         
            +
                    workflow_timeout = options.get("workflow_timeout", None)
         
     | 
| 
       110 
113 
     | 
    
         | 
| 
       111 
114 
     | 
    
         
             
                    status: WorkflowStatusInternal = {
         
     | 
| 
       112 
115 
     | 
    
         
             
                        "workflow_uuid": workflow_id,
         
     | 
| 
         @@ -127,6 +130,10 @@ class DBOSClient: 
     | 
|
| 
       127 
130 
     | 
    
         
             
                        "executor_id": None,
         
     | 
| 
       128 
131 
     | 
    
         
             
                        "recovery_attempts": None,
         
     | 
| 
       129 
132 
     | 
    
         
             
                        "app_id": None,
         
     | 
| 
      
 133 
     | 
    
         
            +
                        "workflow_timeout_ms": (
         
     | 
| 
      
 134 
     | 
    
         
            +
                            int(workflow_timeout * 1000) if workflow_timeout is not None else None
         
     | 
| 
      
 135 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 136 
     | 
    
         
            +
                        "workflow_deadline_epoch_ms": None,
         
     | 
| 
       130 
137 
     | 
    
         
             
                    }
         
     | 
| 
       131 
138 
     | 
    
         | 
| 
       132 
139 
     | 
    
         
             
                    inputs: WorkflowInputs = {
         
     | 
| 
         @@ -190,6 +197,8 @@ class DBOSClient: 
     | 
|
| 
       190 
197 
     | 
    
         
             
                        "recovery_attempts": None,
         
     | 
| 
       191 
198 
     | 
    
         
             
                        "app_id": None,
         
     | 
| 
       192 
199 
     | 
    
         
             
                        "app_version": None,
         
     | 
| 
      
 200 
     | 
    
         
            +
                        "workflow_timeout_ms": None,
         
     | 
| 
      
 201 
     | 
    
         
            +
                        "workflow_deadline_epoch_ms": None,
         
     | 
| 
       193 
202 
     | 
    
         
             
                    }
         
     | 
| 
       194 
203 
     | 
    
         
             
                    with self._sys_db.engine.begin() as conn:
         
     | 
| 
       195 
204 
     | 
    
         
             
                        self._sys_db.insert_workflow_status(
         
     | 
    
        dbos/_context.py
    CHANGED
    
    | 
         @@ -93,6 +93,11 @@ class DBOSContext: 
     | 
|
| 
       93 
93 
     | 
    
         
             
                    self.assumed_role: Optional[str] = None
         
     | 
| 
       94 
94 
     | 
    
         
             
                    self.step_status: Optional[StepStatus] = None
         
     | 
| 
       95 
95 
     | 
    
         | 
| 
      
 96 
     | 
    
         
            +
                    # A user-specified workflow timeout. Takes priority over a propagated deadline.
         
     | 
| 
      
 97 
     | 
    
         
            +
                    self.workflow_timeout_ms: Optional[int] = None
         
     | 
| 
      
 98 
     | 
    
         
            +
                    # A propagated workflow deadline.
         
     | 
| 
      
 99 
     | 
    
         
            +
                    self.workflow_deadline_epoch_ms: Optional[int] = None
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
       96 
101 
     | 
    
         
             
                def create_child(self) -> DBOSContext:
         
     | 
| 
       97 
102 
     | 
    
         
             
                    rv = DBOSContext()
         
     | 
| 
       98 
103 
     | 
    
         
             
                    rv.logger = self.logger
         
     | 
| 
         @@ -360,11 +365,60 @@ class SetWorkflowID: 
     | 
|
| 
       360 
365 
     | 
    
         
             
                    return False  # Did not handle
         
     | 
| 
       361 
366 
     | 
    
         | 
| 
       362 
367 
     | 
    
         | 
| 
      
 368 
     | 
    
         
            +
            class SetWorkflowTimeout:
         
     | 
| 
      
 369 
     | 
    
         
            +
                """
         
     | 
| 
      
 370 
     | 
    
         
            +
                Set the workflow timeout (in seconds) to be used for the enclosed workflow invocations.
         
     | 
| 
      
 371 
     | 
    
         
            +
             
     | 
| 
      
 372 
     | 
    
         
            +
                Typical Usage
         
     | 
| 
      
 373 
     | 
    
         
            +
                    ```
         
     | 
| 
      
 374 
     | 
    
         
            +
                    with SetWorkflowTimeout(<timeout in seconds>):
         
     | 
| 
      
 375 
     | 
    
         
            +
                        result = workflow_function(...)
         
     | 
| 
      
 376 
     | 
    
         
            +
                    ```
         
     | 
| 
      
 377 
     | 
    
         
            +
                """
         
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
      
 379 
     | 
    
         
            +
                def __init__(self, workflow_timeout_sec: Optional[float]) -> None:
         
     | 
| 
      
 380 
     | 
    
         
            +
                    if workflow_timeout_sec and not workflow_timeout_sec > 0:
         
     | 
| 
      
 381 
     | 
    
         
            +
                        raise Exception(
         
     | 
| 
      
 382 
     | 
    
         
            +
                            f"Invalid workflow timeout {workflow_timeout_sec}. Timeouts must be positive."
         
     | 
| 
      
 383 
     | 
    
         
            +
                        )
         
     | 
| 
      
 384 
     | 
    
         
            +
                    self.created_ctx = False
         
     | 
| 
      
 385 
     | 
    
         
            +
                    self.workflow_timeout_ms = (
         
     | 
| 
      
 386 
     | 
    
         
            +
                        int(workflow_timeout_sec * 1000)
         
     | 
| 
      
 387 
     | 
    
         
            +
                        if workflow_timeout_sec is not None
         
     | 
| 
      
 388 
     | 
    
         
            +
                        else None
         
     | 
| 
      
 389 
     | 
    
         
            +
                    )
         
     | 
| 
      
 390 
     | 
    
         
            +
                    self.saved_workflow_timeout: Optional[int] = None
         
     | 
| 
      
 391 
     | 
    
         
            +
             
     | 
| 
      
 392 
     | 
    
         
            +
                def __enter__(self) -> SetWorkflowTimeout:
         
     | 
| 
      
 393 
     | 
    
         
            +
                    # Code to create a basic context
         
     | 
| 
      
 394 
     | 
    
         
            +
                    ctx = get_local_dbos_context()
         
     | 
| 
      
 395 
     | 
    
         
            +
                    if ctx is None:
         
     | 
| 
      
 396 
     | 
    
         
            +
                        self.created_ctx = True
         
     | 
| 
      
 397 
     | 
    
         
            +
                        _set_local_dbos_context(DBOSContext())
         
     | 
| 
      
 398 
     | 
    
         
            +
                    ctx = assert_current_dbos_context()
         
     | 
| 
      
 399 
     | 
    
         
            +
                    self.saved_workflow_timeout = ctx.workflow_timeout_ms
         
     | 
| 
      
 400 
     | 
    
         
            +
                    ctx.workflow_timeout_ms = self.workflow_timeout_ms
         
     | 
| 
      
 401 
     | 
    
         
            +
                    return self
         
     | 
| 
      
 402 
     | 
    
         
            +
             
     | 
| 
      
 403 
     | 
    
         
            +
                def __exit__(
         
     | 
| 
      
 404 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 405 
     | 
    
         
            +
                    exc_type: Optional[Type[BaseException]],
         
     | 
| 
      
 406 
     | 
    
         
            +
                    exc_value: Optional[BaseException],
         
     | 
| 
      
 407 
     | 
    
         
            +
                    traceback: Optional[TracebackType],
         
     | 
| 
      
 408 
     | 
    
         
            +
                ) -> Literal[False]:
         
     | 
| 
      
 409 
     | 
    
         
            +
                    assert_current_dbos_context().workflow_timeout_ms = self.saved_workflow_timeout
         
     | 
| 
      
 410 
     | 
    
         
            +
                    # Code to clean up the basic context if we created it
         
     | 
| 
      
 411 
     | 
    
         
            +
                    if self.created_ctx:
         
     | 
| 
      
 412 
     | 
    
         
            +
                        _clear_local_dbos_context()
         
     | 
| 
      
 413 
     | 
    
         
            +
                    return False  # Did not handle
         
     | 
| 
      
 414 
     | 
    
         
            +
             
     | 
| 
      
 415 
     | 
    
         
            +
             
     | 
| 
       363 
416 
     | 
    
         
             
            class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]):
         
     | 
| 
       364 
417 
     | 
    
         
             
                def __init__(self, attributes: TracedAttributes) -> None:
         
     | 
| 
       365 
418 
     | 
    
         
             
                    self.created_ctx = False
         
     | 
| 
       366 
419 
     | 
    
         
             
                    self.attributes = attributes
         
     | 
| 
       367 
420 
     | 
    
         
             
                    self.is_temp_workflow = attributes["name"] == "temp_wf"
         
     | 
| 
      
 421 
     | 
    
         
            +
                    self.saved_workflow_timeout: Optional[int] = None
         
     | 
| 
       368 
422 
     | 
    
         | 
| 
       369 
423 
     | 
    
         
             
                def __enter__(self) -> DBOSContext:
         
     | 
| 
       370 
424 
     | 
    
         
             
                    # Code to create a basic context
         
     | 
| 
         @@ -374,6 +428,10 @@ class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]): 
     | 
|
| 
       374 
428 
     | 
    
         
             
                        ctx = DBOSContext()
         
     | 
| 
       375 
429 
     | 
    
         
             
                        _set_local_dbos_context(ctx)
         
     | 
| 
       376 
430 
     | 
    
         
             
                    assert not ctx.is_within_workflow()
         
     | 
| 
      
 431 
     | 
    
         
            +
                    # Unset the workflow_timeout_ms context var so it is not applied to this
         
     | 
| 
      
 432 
     | 
    
         
            +
                    # workflow's children (instead we propagate the deadline)
         
     | 
| 
      
 433 
     | 
    
         
            +
                    self.saved_workflow_timeout = ctx.workflow_timeout_ms
         
     | 
| 
      
 434 
     | 
    
         
            +
                    ctx.workflow_timeout_ms = None
         
     | 
| 
       377 
435 
     | 
    
         
             
                    ctx.start_workflow(
         
     | 
| 
       378 
436 
     | 
    
         
             
                        None, self.attributes, self.is_temp_workflow
         
     | 
| 
       379 
437 
     | 
    
         
             
                    )  # Will get from the context's next workflow ID
         
     | 
| 
         @@ -388,6 +446,10 @@ class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]): 
     | 
|
| 
       388 
446 
     | 
    
         
             
                    ctx = assert_current_dbos_context()
         
     | 
| 
       389 
447 
     | 
    
         
             
                    assert ctx.is_within_workflow()
         
     | 
| 
       390 
448 
     | 
    
         
             
                    ctx.end_workflow(exc_value, self.is_temp_workflow)
         
     | 
| 
      
 449 
     | 
    
         
            +
                    # Restore the saved workflow timeout
         
     | 
| 
      
 450 
     | 
    
         
            +
                    ctx.workflow_timeout_ms = self.saved_workflow_timeout
         
     | 
| 
      
 451 
     | 
    
         
            +
                    # Clear any propagating timeout
         
     | 
| 
      
 452 
     | 
    
         
            +
                    ctx.workflow_deadline_epoch_ms = None
         
     | 
| 
       391 
453 
     | 
    
         
             
                    # Code to clean up the basic context if we created it
         
     | 
| 
       392 
454 
     | 
    
         
             
                    if self.created_ctx:
         
     | 
| 
       393 
455 
     | 
    
         
             
                        _clear_local_dbos_context()
         
     | 
    
        dbos/_core.py
    CHANGED
    
    | 
         @@ -3,6 +3,7 @@ import functools 
     | 
|
| 
       3 
3 
     | 
    
         
             
            import inspect
         
     | 
| 
       4 
4 
     | 
    
         
             
            import json
         
     | 
| 
       5 
5 
     | 
    
         
             
            import sys
         
     | 
| 
      
 6 
     | 
    
         
            +
            import threading
         
     | 
| 
       6 
7 
     | 
    
         
             
            import time
         
     | 
| 
       7 
8 
     | 
    
         
             
            import traceback
         
     | 
| 
       8 
9 
     | 
    
         
             
            from concurrent.futures import Future
         
     | 
| 
         @@ -14,11 +15,9 @@ from typing import ( 
     | 
|
| 
       14 
15 
     | 
    
         
             
                Coroutine,
         
     | 
| 
       15 
16 
     | 
    
         
             
                Generic,
         
     | 
| 
       16 
17 
     | 
    
         
             
                Optional,
         
     | 
| 
       17 
     | 
    
         
            -
                Tuple,
         
     | 
| 
       18 
18 
     | 
    
         
             
                TypeVar,
         
     | 
| 
       19 
19 
     | 
    
         
             
                Union,
         
     | 
| 
       20 
20 
     | 
    
         
             
                cast,
         
     | 
| 
       21 
     | 
    
         
            -
                overload,
         
     | 
| 
       22 
21 
     | 
    
         
             
            )
         
     | 
| 
       23 
22 
     | 
    
         | 
| 
       24 
23 
     | 
    
         
             
            from dbos._outcome import Immediate, NoResult, Outcome, Pending
         
     | 
| 
         @@ -59,7 +58,6 @@ from ._error import ( 
     | 
|
| 
       59 
58 
     | 
    
         
             
            )
         
     | 
| 
       60 
59 
     | 
    
         
             
            from ._registrations import (
         
     | 
| 
       61 
60 
     | 
    
         
             
                DEFAULT_MAX_RECOVERY_ATTEMPTS,
         
     | 
| 
       62 
     | 
    
         
            -
                DBOSFuncInfo,
         
     | 
| 
       63 
61 
     | 
    
         
             
                get_config_name,
         
     | 
| 
       64 
62 
     | 
    
         
             
                get_dbos_class_name,
         
     | 
| 
       65 
63 
     | 
    
         
             
                get_dbos_func_name,
         
     | 
| 
         @@ -227,12 +225,14 @@ class WorkflowHandleAsyncPolling(Generic[R]): 
     | 
|
| 
       227 
225 
     | 
    
         
             
            def _init_workflow(
         
     | 
| 
       228 
226 
     | 
    
         
             
                dbos: "DBOS",
         
     | 
| 
       229 
227 
     | 
    
         
             
                ctx: DBOSContext,
         
     | 
| 
      
 228 
     | 
    
         
            +
                *,
         
     | 
| 
       230 
229 
     | 
    
         
             
                inputs: WorkflowInputs,
         
     | 
| 
       231 
230 
     | 
    
         
             
                wf_name: str,
         
     | 
| 
       232 
231 
     | 
    
         
             
                class_name: Optional[str],
         
     | 
| 
       233 
232 
     | 
    
         
             
                config_name: Optional[str],
         
     | 
| 
       234 
     | 
    
         
            -
                temp_wf_type: Optional[str],
         
     | 
| 
       235 
233 
     | 
    
         
             
                queue: Optional[str],
         
     | 
| 
      
 234 
     | 
    
         
            +
                workflow_timeout_ms: Optional[int],
         
     | 
| 
      
 235 
     | 
    
         
            +
                workflow_deadline_epoch_ms: Optional[int],
         
     | 
| 
       236 
236 
     | 
    
         
             
                max_recovery_attempts: Optional[int],
         
     | 
| 
       237 
237 
     | 
    
         
             
            ) -> WorkflowStatusInternal:
         
     | 
| 
       238 
238 
     | 
    
         
             
                wfid = (
         
     | 
| 
         @@ -240,6 +240,15 @@ def _init_workflow( 
     | 
|
| 
       240 
240 
     | 
    
         
             
                    if len(ctx.workflow_id) > 0
         
     | 
| 
       241 
241 
     | 
    
         
             
                    else ctx.id_assigned_for_next_workflow
         
     | 
| 
       242 
242 
     | 
    
         
             
                )
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                # In debug mode, just return the existing status
         
     | 
| 
      
 245 
     | 
    
         
            +
                if dbos.debug_mode:
         
     | 
| 
      
 246 
     | 
    
         
            +
                    get_status_result = dbos._sys_db.get_workflow_status(wfid)
         
     | 
| 
      
 247 
     | 
    
         
            +
                    if get_status_result is None:
         
     | 
| 
      
 248 
     | 
    
         
            +
                        raise DBOSNonExistentWorkflowError(wfid)
         
     | 
| 
      
 249 
     | 
    
         
            +
                    return get_status_result
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                # Initialize a workflow status object from the context
         
     | 
| 
       243 
252 
     | 
    
         
             
                status: WorkflowStatusInternal = {
         
     | 
| 
       244 
253 
     | 
    
         
             
                    "workflow_uuid": wfid,
         
     | 
| 
       245 
254 
     | 
    
         
             
                    "status": (
         
     | 
| 
         @@ -267,25 +276,47 @@ def _init_workflow( 
     | 
|
| 
       267 
276 
     | 
    
         
             
                    "queue_name": queue,
         
     | 
| 
       268 
277 
     | 
    
         
             
                    "created_at": None,
         
     | 
| 
       269 
278 
     | 
    
         
             
                    "updated_at": None,
         
     | 
| 
      
 279 
     | 
    
         
            +
                    "workflow_timeout_ms": workflow_timeout_ms,
         
     | 
| 
      
 280 
     | 
    
         
            +
                    "workflow_deadline_epoch_ms": workflow_deadline_epoch_ms,
         
     | 
| 
       270 
281 
     | 
    
         
             
                }
         
     | 
| 
       271 
282 
     | 
    
         | 
| 
       272 
283 
     | 
    
         
             
                # If we have a class name, the first arg is the instance and do not serialize
         
     | 
| 
       273 
284 
     | 
    
         
             
                if class_name is not None:
         
     | 
| 
       274 
285 
     | 
    
         
             
                    inputs = {"args": inputs["args"][1:], "kwargs": inputs["kwargs"]}
         
     | 
| 
       275 
286 
     | 
    
         | 
| 
       276 
     | 
    
         
            -
                 
     | 
| 
       277 
     | 
    
         
            -
                 
     | 
| 
       278 
     | 
    
         
            -
                     
     | 
| 
       279 
     | 
    
         
            -
                     
     | 
| 
       280 
     | 
    
         
            -
             
     | 
| 
       281 
     | 
    
         
            -
             
     | 
| 
       282 
     | 
    
         
            -
                else:
         
     | 
| 
       283 
     | 
    
         
            -
                    wf_status = dbos._sys_db.init_workflow(
         
     | 
| 
       284 
     | 
    
         
            -
                        status,
         
     | 
| 
       285 
     | 
    
         
            -
                        _serialization.serialize_args(inputs),
         
     | 
| 
       286 
     | 
    
         
            -
                        max_recovery_attempts=max_recovery_attempts,
         
     | 
| 
       287 
     | 
    
         
            -
                    )
         
     | 
| 
      
 287 
     | 
    
         
            +
                # Synchronously record the status and inputs for workflows
         
     | 
| 
      
 288 
     | 
    
         
            +
                wf_status, workflow_deadline_epoch_ms = dbos._sys_db.init_workflow(
         
     | 
| 
      
 289 
     | 
    
         
            +
                    status,
         
     | 
| 
      
 290 
     | 
    
         
            +
                    _serialization.serialize_args(inputs),
         
     | 
| 
      
 291 
     | 
    
         
            +
                    max_recovery_attempts=max_recovery_attempts,
         
     | 
| 
      
 292 
     | 
    
         
            +
                )
         
     | 
| 
       288 
293 
     | 
    
         | 
| 
      
 294 
     | 
    
         
            +
                if workflow_deadline_epoch_ms is not None:
         
     | 
| 
      
 295 
     | 
    
         
            +
                    evt = threading.Event()
         
     | 
| 
      
 296 
     | 
    
         
            +
                    dbos.stop_events.append(evt)
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                    def timeout_func() -> None:
         
     | 
| 
      
 299 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 300 
     | 
    
         
            +
                            assert workflow_deadline_epoch_ms is not None
         
     | 
| 
      
 301 
     | 
    
         
            +
                            time_to_wait_sec = (
         
     | 
| 
      
 302 
     | 
    
         
            +
                                workflow_deadline_epoch_ms - (time.time() * 1000)
         
     | 
| 
      
 303 
     | 
    
         
            +
                            ) / 1000
         
     | 
| 
      
 304 
     | 
    
         
            +
                            if time_to_wait_sec > 0:
         
     | 
| 
      
 305 
     | 
    
         
            +
                                was_stopped = evt.wait(time_to_wait_sec)
         
     | 
| 
      
 306 
     | 
    
         
            +
                                if was_stopped:
         
     | 
| 
      
 307 
     | 
    
         
            +
                                    return
         
     | 
| 
      
 308 
     | 
    
         
            +
                            dbos._sys_db.cancel_workflow(wfid)
         
     | 
| 
      
 309 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 310 
     | 
    
         
            +
                            dbos.logger.warning(
         
     | 
| 
      
 311 
     | 
    
         
            +
                                f"Exception in timeout thread for workflow {wfid}: {e}"
         
     | 
| 
      
 312 
     | 
    
         
            +
                            )
         
     | 
| 
      
 313 
     | 
    
         
            +
             
     | 
| 
      
 314 
     | 
    
         
            +
                    timeout_thread = threading.Thread(target=timeout_func, daemon=True)
         
     | 
| 
      
 315 
     | 
    
         
            +
                    timeout_thread.start()
         
     | 
| 
      
 316 
     | 
    
         
            +
                    dbos._background_threads.append(timeout_thread)
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
                ctx.workflow_deadline_epoch_ms = workflow_deadline_epoch_ms
         
     | 
| 
      
 319 
     | 
    
         
            +
                status["workflow_deadline_epoch_ms"] = workflow_deadline_epoch_ms
         
     | 
| 
       289 
320 
     | 
    
         
             
                status["status"] = wf_status
         
     | 
| 
       290 
321 
     | 
    
         
             
                return status
         
     | 
| 
       291 
322 
     | 
    
         | 
| 
         @@ -501,6 +532,13 @@ def start_workflow( 
     | 
|
| 
       501 
532 
     | 
    
         
             
                    "kwargs": kwargs,
         
     | 
| 
       502 
533 
     | 
    
         
             
                }
         
     | 
| 
       503 
534 
     | 
    
         | 
| 
      
 535 
     | 
    
         
            +
                local_ctx = get_local_dbos_context()
         
     | 
| 
      
 536 
     | 
    
         
            +
                workflow_timeout_ms, workflow_deadline_epoch_ms = _get_timeout_deadline(
         
     | 
| 
      
 537 
     | 
    
         
            +
                    local_ctx, queue_name
         
     | 
| 
      
 538 
     | 
    
         
            +
                )
         
     | 
| 
      
 539 
     | 
    
         
            +
                workflow_timeout_ms = (
         
     | 
| 
      
 540 
     | 
    
         
            +
                    local_ctx.workflow_timeout_ms if local_ctx is not None else None
         
     | 
| 
      
 541 
     | 
    
         
            +
                )
         
     | 
| 
       504 
542 
     | 
    
         
             
                new_wf_id, new_wf_ctx = _get_new_wf()
         
     | 
| 
       505 
543 
     | 
    
         | 
| 
       506 
544 
     | 
    
         
             
                ctx = new_wf_ctx
         
     | 
| 
         @@ -519,8 +557,9 @@ def start_workflow( 
     | 
|
| 
       519 
557 
     | 
    
         
             
                    wf_name=get_dbos_func_name(func),
         
     | 
| 
       520 
558 
     | 
    
         
             
                    class_name=get_dbos_class_name(fi, func, args),
         
     | 
| 
       521 
559 
     | 
    
         
             
                    config_name=get_config_name(fi, func, args),
         
     | 
| 
       522 
     | 
    
         
            -
                    temp_wf_type=get_temp_workflow_type(func),
         
     | 
| 
       523 
560 
     | 
    
         
             
                    queue=queue_name,
         
     | 
| 
      
 561 
     | 
    
         
            +
                    workflow_timeout_ms=workflow_timeout_ms,
         
     | 
| 
      
 562 
     | 
    
         
            +
                    workflow_deadline_epoch_ms=workflow_deadline_epoch_ms,
         
     | 
| 
       524 
563 
     | 
    
         
             
                    max_recovery_attempts=fi.max_recovery_attempts,
         
     | 
| 
       525 
564 
     | 
    
         
             
                )
         
     | 
| 
       526 
565 
     | 
    
         | 
| 
         @@ -583,6 +622,10 @@ async def start_workflow_async( 
     | 
|
| 
       583 
622 
     | 
    
         
             
                    "kwargs": kwargs,
         
     | 
| 
       584 
623 
     | 
    
         
             
                }
         
     | 
| 
       585 
624 
     | 
    
         | 
| 
      
 625 
     | 
    
         
            +
                local_ctx = get_local_dbos_context()
         
     | 
| 
      
 626 
     | 
    
         
            +
                workflow_timeout_ms, workflow_deadline_epoch_ms = _get_timeout_deadline(
         
     | 
| 
      
 627 
     | 
    
         
            +
                    local_ctx, queue_name
         
     | 
| 
      
 628 
     | 
    
         
            +
                )
         
     | 
| 
       586 
629 
     | 
    
         
             
                new_wf_id, new_wf_ctx = _get_new_wf()
         
     | 
| 
       587 
630 
     | 
    
         | 
| 
       588 
631 
     | 
    
         
             
                ctx = new_wf_ctx
         
     | 
| 
         @@ -604,8 +647,9 @@ async def start_workflow_async( 
     | 
|
| 
       604 
647 
     | 
    
         
             
                    wf_name=get_dbos_func_name(func),
         
     | 
| 
       605 
648 
     | 
    
         
             
                    class_name=get_dbos_class_name(fi, func, args),
         
     | 
| 
       606 
649 
     | 
    
         
             
                    config_name=get_config_name(fi, func, args),
         
     | 
| 
       607 
     | 
    
         
            -
                    temp_wf_type=get_temp_workflow_type(func),
         
     | 
| 
       608 
650 
     | 
    
         
             
                    queue=queue_name,
         
     | 
| 
      
 651 
     | 
    
         
            +
                    workflow_timeout_ms=workflow_timeout_ms,
         
     | 
| 
      
 652 
     | 
    
         
            +
                    workflow_deadline_epoch_ms=workflow_deadline_epoch_ms,
         
     | 
| 
       609 
653 
     | 
    
         
             
                    max_recovery_attempts=fi.max_recovery_attempts,
         
     | 
| 
       610 
654 
     | 
    
         
             
                )
         
     | 
| 
       611 
655 
     | 
    
         | 
| 
         @@ -680,6 +724,9 @@ def workflow_wrapper( 
     | 
|
| 
       680 
724 
     | 
    
         
             
                        "kwargs": kwargs,
         
     | 
| 
       681 
725 
     | 
    
         
             
                    }
         
     | 
| 
       682 
726 
     | 
    
         
             
                    ctx = get_local_dbos_context()
         
     | 
| 
      
 727 
     | 
    
         
            +
                    workflow_timeout_ms, workflow_deadline_epoch_ms = _get_timeout_deadline(
         
     | 
| 
      
 728 
     | 
    
         
            +
                        ctx, queue=None
         
     | 
| 
      
 729 
     | 
    
         
            +
                    )
         
     | 
| 
       683 
730 
     | 
    
         
             
                    enterWorkflowCtxMgr = (
         
     | 
| 
       684 
731 
     | 
    
         
             
                        EnterDBOSChildWorkflow if ctx and ctx.is_workflow() else EnterDBOSWorkflow
         
     | 
| 
       685 
732 
     | 
    
         
             
                    )
         
     | 
| 
         @@ -717,8 +764,9 @@ def workflow_wrapper( 
     | 
|
| 
       717 
764 
     | 
    
         
             
                            wf_name=get_dbos_func_name(func),
         
     | 
| 
       718 
765 
     | 
    
         
             
                            class_name=get_dbos_class_name(fi, func, args),
         
     | 
| 
       719 
766 
     | 
    
         
             
                            config_name=get_config_name(fi, func, args),
         
     | 
| 
       720 
     | 
    
         
            -
                            temp_wf_type=get_temp_workflow_type(func),
         
     | 
| 
       721 
767 
     | 
    
         
             
                            queue=None,
         
     | 
| 
      
 768 
     | 
    
         
            +
                            workflow_timeout_ms=workflow_timeout_ms,
         
     | 
| 
      
 769 
     | 
    
         
            +
                            workflow_deadline_epoch_ms=workflow_deadline_epoch_ms,
         
     | 
| 
       722 
770 
     | 
    
         
             
                            max_recovery_attempts=max_recovery_attempts,
         
     | 
| 
       723 
771 
     | 
    
         
             
                        )
         
     | 
| 
       724 
772 
     | 
    
         | 
| 
         @@ -1212,3 +1260,24 @@ def get_event( 
     | 
|
| 
       1212 
1260 
     | 
    
         
             
                else:
         
     | 
| 
       1213 
1261 
     | 
    
         
             
                    # Directly call it outside of a workflow
         
     | 
| 
       1214 
1262 
     | 
    
         
             
                    return dbos._sys_db.get_event(workflow_id, key, timeout_seconds)
         
     | 
| 
      
 1263 
     | 
    
         
            +
             
     | 
| 
      
 1264 
     | 
    
         
            +
             
     | 
| 
      
 1265 
     | 
    
         
            +
            def _get_timeout_deadline(
         
     | 
| 
      
 1266 
     | 
    
         
            +
                ctx: Optional[DBOSContext], queue: Optional[str]
         
     | 
| 
      
 1267 
     | 
    
         
            +
            ) -> tuple[Optional[int], Optional[int]]:
         
     | 
| 
      
 1268 
     | 
    
         
            +
                if ctx is None:
         
     | 
| 
      
 1269 
     | 
    
         
            +
                    return None, None
         
     | 
| 
      
 1270 
     | 
    
         
            +
                # If a timeout is explicitly specified, use it over any propagated deadline
         
     | 
| 
      
 1271 
     | 
    
         
            +
                if ctx.workflow_timeout_ms:
         
     | 
| 
      
 1272 
     | 
    
         
            +
                    if queue:
         
     | 
| 
      
 1273 
     | 
    
         
            +
                        # Queued workflows are assigned a deadline on dequeue
         
     | 
| 
      
 1274 
     | 
    
         
            +
                        return ctx.workflow_timeout_ms, None
         
     | 
| 
      
 1275 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 1276 
     | 
    
         
            +
                        # Otherwise, compute the deadline immediately
         
     | 
| 
      
 1277 
     | 
    
         
            +
                        return (
         
     | 
| 
      
 1278 
     | 
    
         
            +
                            ctx.workflow_timeout_ms,
         
     | 
| 
      
 1279 
     | 
    
         
            +
                            int(time.time() * 1000) + ctx.workflow_timeout_ms,
         
     | 
| 
      
 1280 
     | 
    
         
            +
                        )
         
     | 
| 
      
 1281 
     | 
    
         
            +
                # Otherwise, return the propagated deadline, if any
         
     | 
| 
      
 1282 
     | 
    
         
            +
                else:
         
     | 
| 
      
 1283 
     | 
    
         
            +
                    return None, ctx.workflow_deadline_epoch_ms
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """workflow_timeout
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Revision ID: 83f3732ae8e7
         
     | 
| 
      
 4 
     | 
    
         
            +
            Revises: f4b9b32ba814
         
     | 
| 
      
 5 
     | 
    
         
            +
            Create Date: 2025-04-16 17:05:36.642395
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            """
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            from typing import Sequence, Union
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            import sqlalchemy as sa
         
     | 
| 
      
 12 
     | 
    
         
            +
            from alembic import op
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            # revision identifiers, used by Alembic.
         
     | 
| 
      
 15 
     | 
    
         
            +
            revision: str = "83f3732ae8e7"
         
     | 
| 
      
 16 
     | 
    
         
            +
            down_revision: Union[str, None] = "f4b9b32ba814"
         
     | 
| 
      
 17 
     | 
    
         
            +
            branch_labels: Union[str, Sequence[str], None] = None
         
     | 
| 
      
 18 
     | 
    
         
            +
            depends_on: Union[str, Sequence[str], None] = None
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            def upgrade() -> None:
         
     | 
| 
      
 22 
     | 
    
         
            +
                op.add_column(
         
     | 
| 
      
 23 
     | 
    
         
            +
                    "workflow_status",
         
     | 
| 
      
 24 
     | 
    
         
            +
                    sa.Column(
         
     | 
| 
      
 25 
     | 
    
         
            +
                        "workflow_timeout_ms",
         
     | 
| 
      
 26 
     | 
    
         
            +
                        sa.BigInteger(),
         
     | 
| 
      
 27 
     | 
    
         
            +
                        nullable=True,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    ),
         
     | 
| 
      
 29 
     | 
    
         
            +
                    schema="dbos",
         
     | 
| 
      
 30 
     | 
    
         
            +
                )
         
     | 
| 
      
 31 
     | 
    
         
            +
                op.add_column(
         
     | 
| 
      
 32 
     | 
    
         
            +
                    "workflow_status",
         
     | 
| 
      
 33 
     | 
    
         
            +
                    sa.Column(
         
     | 
| 
      
 34 
     | 
    
         
            +
                        "workflow_deadline_epoch_ms",
         
     | 
| 
      
 35 
     | 
    
         
            +
                        sa.BigInteger(),
         
     | 
| 
      
 36 
     | 
    
         
            +
                        nullable=True,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    ),
         
     | 
| 
      
 38 
     | 
    
         
            +
                    schema="dbos",
         
     | 
| 
      
 39 
     | 
    
         
            +
                )
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            def downgrade() -> None:
         
     | 
| 
      
 43 
     | 
    
         
            +
                op.drop_column("workflow_status", "workflow_deadline_epoch_ms", schema="dbos")
         
     | 
| 
      
 44 
     | 
    
         
            +
                op.drop_column("workflow_status", "workflow_timeout_ms", schema="dbos")
         
     | 
    
        dbos/_schemas/system_database.py
    CHANGED
    
    | 
         @@ -54,7 +54,9 @@ class SystemSchema: 
     | 
|
| 
       54 
54 
     | 
    
         
             
                        nullable=True,
         
     | 
| 
       55 
55 
     | 
    
         
             
                        server_default=text("'0'::bigint"),
         
     | 
| 
       56 
56 
     | 
    
         
             
                    ),
         
     | 
| 
       57 
     | 
    
         
            -
                    Column("queue_name", Text),
         
     | 
| 
      
 57 
     | 
    
         
            +
                    Column("queue_name", Text, nullable=True),
         
     | 
| 
      
 58 
     | 
    
         
            +
                    Column("workflow_timeout_ms", BigInteger, nullable=True),
         
     | 
| 
      
 59 
     | 
    
         
            +
                    Column("workflow_deadline_epoch_ms", BigInteger, nullable=True),
         
     | 
| 
       58 
60 
     | 
    
         
             
                    Index("workflow_status_created_at_index", "created_at"),
         
     | 
| 
       59 
61 
     | 
    
         
             
                    Index("workflow_status_executor_id_index", "executor_id"),
         
     | 
| 
       60 
62 
     | 
    
         
             
                )
         
     | 
    
        dbos/_sys_db.py
    CHANGED
    
    | 
         @@ -128,6 +128,11 @@ class WorkflowStatusInternal(TypedDict): 
     | 
|
| 
       128 
128 
     | 
    
         
             
                app_version: Optional[str]
         
     | 
| 
       129 
129 
     | 
    
         
             
                app_id: Optional[str]
         
     | 
| 
       130 
130 
     | 
    
         
             
                recovery_attempts: Optional[int]
         
     | 
| 
      
 131 
     | 
    
         
            +
                # The start-to-close timeout of the workflow in ms
         
     | 
| 
      
 132 
     | 
    
         
            +
                workflow_timeout_ms: Optional[int]
         
     | 
| 
      
 133 
     | 
    
         
            +
                # The deadline of a workflow, computed by adding its timeout to its start time.
         
     | 
| 
      
 134 
     | 
    
         
            +
                # Deadlines propagate to children. When the deadline is reached, the workflow is cancelled.
         
     | 
| 
      
 135 
     | 
    
         
            +
                workflow_deadline_epoch_ms: Optional[int]
         
     | 
| 
       131 
136 
     | 
    
         | 
| 
       132 
137 
     | 
    
         | 
| 
       133 
138 
     | 
    
         
             
            class RecordedResult(TypedDict):
         
     | 
| 
         @@ -328,10 +333,11 @@ class SystemDatabase: 
     | 
|
| 
       328 
333 
     | 
    
         
             
                    conn: sa.Connection,
         
     | 
| 
       329 
334 
     | 
    
         
             
                    *,
         
     | 
| 
       330 
335 
     | 
    
         
             
                    max_recovery_attempts: Optional[int],
         
     | 
| 
       331 
     | 
    
         
            -
                ) -> WorkflowStatuses:
         
     | 
| 
      
 336 
     | 
    
         
            +
                ) -> tuple[WorkflowStatuses, Optional[int]]:
         
     | 
| 
       332 
337 
     | 
    
         
             
                    if self._debug_mode:
         
     | 
| 
       333 
338 
     | 
    
         
             
                        raise Exception("called insert_workflow_status in debug mode")
         
     | 
| 
       334 
339 
     | 
    
         
             
                    wf_status: WorkflowStatuses = status["status"]
         
     | 
| 
      
 340 
     | 
    
         
            +
                    workflow_deadline_epoch_ms: Optional[int] = status["workflow_deadline_epoch_ms"]
         
     | 
| 
       335 
341 
     | 
    
         | 
| 
       336 
342 
     | 
    
         
             
                    cmd = (
         
     | 
| 
       337 
343 
     | 
    
         
             
                        pg.insert(SystemSchema.workflow_status)
         
     | 
| 
         @@ -354,6 +360,8 @@ class SystemDatabase: 
     | 
|
| 
       354 
360 
     | 
    
         
             
                            recovery_attempts=(
         
     | 
| 
       355 
361 
     | 
    
         
             
                                1 if wf_status != WorkflowStatusString.ENQUEUED.value else 0
         
     | 
| 
       356 
362 
     | 
    
         
             
                            ),
         
     | 
| 
      
 363 
     | 
    
         
            +
                            workflow_timeout_ms=status["workflow_timeout_ms"],
         
     | 
| 
      
 364 
     | 
    
         
            +
                            workflow_deadline_epoch_ms=status["workflow_deadline_epoch_ms"],
         
     | 
| 
       357 
365 
     | 
    
         
             
                        )
         
     | 
| 
       358 
366 
     | 
    
         
             
                        .on_conflict_do_update(
         
     | 
| 
       359 
367 
     | 
    
         
             
                            index_elements=["workflow_uuid"],
         
     | 
| 
         @@ -367,7 +375,7 @@ class SystemDatabase: 
     | 
|
| 
       367 
375 
     | 
    
         
             
                        )
         
     | 
| 
       368 
376 
     | 
    
         
             
                    )
         
     | 
| 
       369 
377 
     | 
    
         | 
| 
       370 
     | 
    
         
            -
                    cmd = cmd.returning(SystemSchema.workflow_status.c.recovery_attempts, SystemSchema.workflow_status.c.status, SystemSchema.workflow_status.c.name, SystemSchema.workflow_status.c.class_name, SystemSchema.workflow_status.c.config_name, SystemSchema.workflow_status.c.queue_name)  # type: ignore
         
     | 
| 
      
 378 
     | 
    
         
            +
                    cmd = cmd.returning(SystemSchema.workflow_status.c.recovery_attempts, SystemSchema.workflow_status.c.status, SystemSchema.workflow_status.c.workflow_deadline_epoch_ms, SystemSchema.workflow_status.c.name, SystemSchema.workflow_status.c.class_name, SystemSchema.workflow_status.c.config_name, SystemSchema.workflow_status.c.queue_name)  # type: ignore
         
     | 
| 
       371 
379 
     | 
    
         | 
| 
       372 
380 
     | 
    
         
             
                    results = conn.execute(cmd)
         
     | 
| 
       373 
381 
     | 
    
         | 
| 
         @@ -377,17 +385,18 @@ class SystemDatabase: 
     | 
|
| 
       377 
385 
     | 
    
         
             
                        # A mismatch indicates a workflow starting with the same UUID but different functions, which would throw an exception.
         
     | 
| 
       378 
386 
     | 
    
         
             
                        recovery_attempts: int = row[0]
         
     | 
| 
       379 
387 
     | 
    
         
             
                        wf_status = row[1]
         
     | 
| 
      
 388 
     | 
    
         
            +
                        workflow_deadline_epoch_ms = row[2]
         
     | 
| 
       380 
389 
     | 
    
         
             
                        err_msg: Optional[str] = None
         
     | 
| 
       381 
     | 
    
         
            -
                        if row[ 
     | 
| 
       382 
     | 
    
         
            -
                            err_msg = f"Workflow already exists with a different function name: {row[ 
     | 
| 
       383 
     | 
    
         
            -
                        elif row[ 
     | 
| 
       384 
     | 
    
         
            -
                            err_msg = f"Workflow already exists with a different class name: {row[ 
     | 
| 
       385 
     | 
    
         
            -
                        elif row[ 
     | 
| 
       386 
     | 
    
         
            -
                            err_msg = f"Workflow already exists with a different config name: {row[ 
     | 
| 
       387 
     | 
    
         
            -
                        elif row[ 
     | 
| 
      
 390 
     | 
    
         
            +
                        if row[3] != status["name"]:
         
     | 
| 
      
 391 
     | 
    
         
            +
                            err_msg = f"Workflow already exists with a different function name: {row[3]}, but the provided function name is: {status['name']}"
         
     | 
| 
      
 392 
     | 
    
         
            +
                        elif row[4] != status["class_name"]:
         
     | 
| 
      
 393 
     | 
    
         
            +
                            err_msg = f"Workflow already exists with a different class name: {row[4]}, but the provided class name is: {status['class_name']}"
         
     | 
| 
      
 394 
     | 
    
         
            +
                        elif row[5] != status["config_name"]:
         
     | 
| 
      
 395 
     | 
    
         
            +
                            err_msg = f"Workflow already exists with a different config name: {row[5]}, but the provided config name is: {status['config_name']}"
         
     | 
| 
      
 396 
     | 
    
         
            +
                        elif row[6] != status["queue_name"]:
         
     | 
| 
       388 
397 
     | 
    
         
             
                            # This is a warning because a different queue name is not necessarily an error.
         
     | 
| 
       389 
398 
     | 
    
         
             
                            dbos_logger.warning(
         
     | 
| 
       390 
     | 
    
         
            -
                                f"Workflow already exists in queue: {row[ 
     | 
| 
      
 399 
     | 
    
         
            +
                                f"Workflow already exists in queue: {row[6]}, but the provided queue name is: {status['queue_name']}. The queue is not updated."
         
     | 
| 
       391 
400 
     | 
    
         
             
                            )
         
     | 
| 
       392 
401 
     | 
    
         
             
                        if err_msg is not None:
         
     | 
| 
       393 
402 
     | 
    
         
             
                            raise DBOSConflictingWorkflowError(status["workflow_uuid"], err_msg)
         
     | 
| 
         @@ -427,7 +436,7 @@ class SystemDatabase: 
     | 
|
| 
       427 
436 
     | 
    
         
             
                                status["workflow_uuid"], max_recovery_attempts
         
     | 
| 
       428 
437 
     | 
    
         
             
                            )
         
     | 
| 
       429 
438 
     | 
    
         | 
| 
       430 
     | 
    
         
            -
                    return wf_status
         
     | 
| 
      
 439 
     | 
    
         
            +
                    return wf_status, workflow_deadline_epoch_ms
         
     | 
| 
       431 
440 
     | 
    
         | 
| 
       432 
441 
     | 
    
         
             
                def update_workflow_status(
         
     | 
| 
       433 
442 
     | 
    
         
             
                    self,
         
     | 
| 
         @@ -485,6 +494,18 @@ class SystemDatabase: 
     | 
|
| 
       485 
494 
     | 
    
         
             
                    if self._debug_mode:
         
     | 
| 
       486 
495 
     | 
    
         
             
                        raise Exception("called cancel_workflow in debug mode")
         
     | 
| 
       487 
496 
     | 
    
         
             
                    with self.engine.begin() as c:
         
     | 
| 
      
 497 
     | 
    
         
            +
                        # Check the status of the workflow. If it is complete, do nothing.
         
     | 
| 
      
 498 
     | 
    
         
            +
                        row = c.execute(
         
     | 
| 
      
 499 
     | 
    
         
            +
                            sa.select(
         
     | 
| 
      
 500 
     | 
    
         
            +
                                SystemSchema.workflow_status.c.status,
         
     | 
| 
      
 501 
     | 
    
         
            +
                            ).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
         
     | 
| 
      
 502 
     | 
    
         
            +
                        ).fetchone()
         
     | 
| 
      
 503 
     | 
    
         
            +
                        if (
         
     | 
| 
      
 504 
     | 
    
         
            +
                            row is None
         
     | 
| 
      
 505 
     | 
    
         
            +
                            or row[0] == WorkflowStatusString.SUCCESS.value
         
     | 
| 
      
 506 
     | 
    
         
            +
                            or row[0] == WorkflowStatusString.ERROR.value
         
     | 
| 
      
 507 
     | 
    
         
            +
                        ):
         
     | 
| 
      
 508 
     | 
    
         
            +
                            return
         
     | 
| 
       488 
509 
     | 
    
         
             
                        # Remove the workflow from the queues table so it does not block the table
         
     | 
| 
       489 
510 
     | 
    
         
             
                        c.execute(
         
     | 
| 
       490 
511 
     | 
    
         
             
                            sa.delete(SystemSchema.workflow_queue).where(
         
     | 
| 
         @@ -531,11 +552,15 @@ class SystemDatabase: 
     | 
|
| 
       531 
552 
     | 
    
         
             
                                queue_name=INTERNAL_QUEUE_NAME,
         
     | 
| 
       532 
553 
     | 
    
         
             
                            )
         
     | 
| 
       533 
554 
     | 
    
         
             
                        )
         
     | 
| 
       534 
     | 
    
         
            -
                        # Set the workflow's status to ENQUEUED and clear its recovery attempts.
         
     | 
| 
      
 555 
     | 
    
         
            +
                        # Set the workflow's status to ENQUEUED and clear its recovery attempts and deadline.
         
     | 
| 
       535 
556 
     | 
    
         
             
                        c.execute(
         
     | 
| 
       536 
557 
     | 
    
         
             
                            sa.update(SystemSchema.workflow_status)
         
     | 
| 
       537 
558 
     | 
    
         
             
                            .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
         
     | 
| 
       538 
     | 
    
         
            -
                            .values( 
     | 
| 
      
 559 
     | 
    
         
            +
                            .values(
         
     | 
| 
      
 560 
     | 
    
         
            +
                                status=WorkflowStatusString.ENQUEUED.value,
         
     | 
| 
      
 561 
     | 
    
         
            +
                                recovery_attempts=0,
         
     | 
| 
      
 562 
     | 
    
         
            +
                                workflow_deadline_epoch_ms=None,
         
     | 
| 
      
 563 
     | 
    
         
            +
                            )
         
     | 
| 
       539 
564 
     | 
    
         
             
                        )
         
     | 
| 
       540 
565 
     | 
    
         | 
| 
       541 
566 
     | 
    
         
             
                def get_max_function_id(self, workflow_uuid: str) -> Optional[int]:
         
     | 
| 
         @@ -648,6 +673,8 @@ class SystemDatabase: 
     | 
|
| 
       648 
673 
     | 
    
         
             
                                SystemSchema.workflow_status.c.updated_at,
         
     | 
| 
       649 
674 
     | 
    
         
             
                                SystemSchema.workflow_status.c.application_version,
         
     | 
| 
       650 
675 
     | 
    
         
             
                                SystemSchema.workflow_status.c.application_id,
         
     | 
| 
      
 676 
     | 
    
         
            +
                                SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
         
     | 
| 
      
 677 
     | 
    
         
            +
                                SystemSchema.workflow_status.c.workflow_timeout_ms,
         
     | 
| 
       651 
678 
     | 
    
         
             
                            ).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
         
     | 
| 
       652 
679 
     | 
    
         
             
                        ).fetchone()
         
     | 
| 
       653 
680 
     | 
    
         
             
                        if row is None:
         
     | 
| 
         @@ -671,6 +698,8 @@ class SystemDatabase: 
     | 
|
| 
       671 
698 
     | 
    
         
             
                            "updated_at": row[12],
         
     | 
| 
       672 
699 
     | 
    
         
             
                            "app_version": row[13],
         
     | 
| 
       673 
700 
     | 
    
         
             
                            "app_id": row[14],
         
     | 
| 
      
 701 
     | 
    
         
            +
                            "workflow_deadline_epoch_ms": row[15],
         
     | 
| 
      
 702 
     | 
    
         
            +
                            "workflow_timeout_ms": row[16],
         
     | 
| 
       674 
703 
     | 
    
         
             
                        }
         
     | 
| 
       675 
704 
     | 
    
         
             
                        return status
         
     | 
| 
       676 
705 
     | 
    
         | 
| 
         @@ -1100,50 +1129,56 @@ class SystemDatabase: 
     | 
|
| 
       1100 
1129 
     | 
    
         
             
                    *,
         
     | 
| 
       1101 
1130 
     | 
    
         
             
                    conn: Optional[sa.Connection] = None,
         
     | 
| 
       1102 
1131 
     | 
    
         
             
                ) -> Optional[RecordedResult]:
         
     | 
| 
       1103 
     | 
    
         
            -
                    #  
     | 
| 
       1104 
     | 
    
         
            -
                     
     | 
| 
       1105 
     | 
    
         
            -
             
     | 
| 
       1106 
     | 
    
         
            -
             
     | 
| 
       1107 
     | 
    
         
            -
             
     | 
| 
       1108 
     | 
    
         
            -
             
     | 
| 
       1109 
     | 
    
         
            -
             
     | 
| 
       1110 
     | 
    
         
            -
             
     | 
| 
       1111 
     | 
    
         
            -
                         
     | 
| 
       1112 
     | 
    
         
            -
                        . 
     | 
| 
       1113 
     | 
    
         
            -
             
     | 
| 
       1114 
     | 
    
         
            -
             
     | 
| 
       1115 
     | 
    
         
            -
             
     | 
| 
       1116 
     | 
    
         
            -
                                    SystemSchema.workflow_status.c.workflow_uuid
         
     | 
| 
       1117 
     | 
    
         
            -
                                    == SystemSchema.operation_outputs.c.workflow_uuid
         
     | 
| 
       1118 
     | 
    
         
            -
                                )
         
     | 
| 
       1119 
     | 
    
         
            -
                                & (SystemSchema.operation_outputs.c.function_id == function_id),
         
     | 
| 
       1120 
     | 
    
         
            -
                            )
         
     | 
| 
       1121 
     | 
    
         
            -
                        )
         
     | 
| 
       1122 
     | 
    
         
            -
                        .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
         
     | 
| 
      
 1132 
     | 
    
         
            +
                    # First query: Retrieve the workflow status
         
     | 
| 
      
 1133 
     | 
    
         
            +
                    workflow_status_sql = sa.select(
         
     | 
| 
      
 1134 
     | 
    
         
            +
                        SystemSchema.workflow_status.c.status,
         
     | 
| 
      
 1135 
     | 
    
         
            +
                    ).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
         
     | 
| 
      
 1136 
     | 
    
         
            +
             
     | 
| 
      
 1137 
     | 
    
         
            +
                    # Second query: Retrieve operation outputs if they exist
         
     | 
| 
      
 1138 
     | 
    
         
            +
                    operation_output_sql = sa.select(
         
     | 
| 
      
 1139 
     | 
    
         
            +
                        SystemSchema.operation_outputs.c.output,
         
     | 
| 
      
 1140 
     | 
    
         
            +
                        SystemSchema.operation_outputs.c.error,
         
     | 
| 
      
 1141 
     | 
    
         
            +
                        SystemSchema.operation_outputs.c.function_name,
         
     | 
| 
      
 1142 
     | 
    
         
            +
                    ).where(
         
     | 
| 
      
 1143 
     | 
    
         
            +
                        (SystemSchema.operation_outputs.c.workflow_uuid == workflow_id)
         
     | 
| 
      
 1144 
     | 
    
         
            +
                        & (SystemSchema.operation_outputs.c.function_id == function_id)
         
     | 
| 
       1123 
1145 
     | 
    
         
             
                    )
         
     | 
| 
       1124 
     | 
    
         
            -
             
     | 
| 
       1125 
     | 
    
         
            -
                     
     | 
| 
      
 1146 
     | 
    
         
            +
             
     | 
| 
      
 1147 
     | 
    
         
            +
                    # Execute both queries
         
     | 
| 
       1126 
1148 
     | 
    
         
             
                    if conn is not None:
         
     | 
| 
       1127 
     | 
    
         
            -
                         
     | 
| 
      
 1149 
     | 
    
         
            +
                        workflow_status_rows = conn.execute(workflow_status_sql).all()
         
     | 
| 
      
 1150 
     | 
    
         
            +
                        operation_output_rows = conn.execute(operation_output_sql).all()
         
     | 
| 
       1128 
1151 
     | 
    
         
             
                    else:
         
     | 
| 
       1129 
1152 
     | 
    
         
             
                        with self.engine.begin() as c:
         
     | 
| 
       1130 
     | 
    
         
            -
                             
     | 
| 
       1131 
     | 
    
         
            -
             
     | 
| 
       1132 
     | 
    
         
            -
             
     | 
| 
       1133 
     | 
    
         
            -
             
     | 
| 
       1134 
     | 
    
         
            -
             
     | 
| 
       1135 
     | 
    
         
            -
                         
     | 
| 
       1136 
     | 
    
         
            -
             
     | 
| 
       1137 
     | 
    
         
            -
             
     | 
| 
      
 1153 
     | 
    
         
            +
                            workflow_status_rows = c.execute(workflow_status_sql).all()
         
     | 
| 
      
 1154 
     | 
    
         
            +
                            operation_output_rows = c.execute(operation_output_sql).all()
         
     | 
| 
      
 1155 
     | 
    
         
            +
             
     | 
| 
      
 1156 
     | 
    
         
            +
                    # Check if the workflow exists
         
     | 
| 
      
 1157 
     | 
    
         
            +
                    assert (
         
     | 
| 
      
 1158 
     | 
    
         
            +
                        len(workflow_status_rows) > 0
         
     | 
| 
      
 1159 
     | 
    
         
            +
                    ), f"Error: Workflow {workflow_id} does not exist"
         
     | 
| 
      
 1160 
     | 
    
         
            +
             
     | 
| 
      
 1161 
     | 
    
         
            +
                    # Get workflow status
         
     | 
| 
      
 1162 
     | 
    
         
            +
                    workflow_status = workflow_status_rows[0][0]
         
     | 
| 
      
 1163 
     | 
    
         
            +
             
     | 
| 
       1138 
1164 
     | 
    
         
             
                    # If the workflow is cancelled, raise the exception
         
     | 
| 
       1139 
1165 
     | 
    
         
             
                    if workflow_status == WorkflowStatusString.CANCELLED.value:
         
     | 
| 
       1140 
1166 
     | 
    
         
             
                        raise DBOSWorkflowCancelledError(
         
     | 
| 
       1141 
1167 
     | 
    
         
             
                            f"Workflow {workflow_id} is cancelled. Aborting function."
         
     | 
| 
       1142 
1168 
     | 
    
         
             
                        )
         
     | 
| 
       1143 
     | 
    
         
            -
             
     | 
| 
       1144 
     | 
    
         
            -
                     
     | 
| 
      
 1169 
     | 
    
         
            +
             
     | 
| 
      
 1170 
     | 
    
         
            +
                    # If there are no operation outputs, return None
         
     | 
| 
      
 1171 
     | 
    
         
            +
                    if not operation_output_rows:
         
     | 
| 
       1145 
1172 
     | 
    
         
             
                        return None
         
     | 
| 
       1146 
     | 
    
         
            -
             
     | 
| 
      
 1173 
     | 
    
         
            +
             
     | 
| 
      
 1174 
     | 
    
         
            +
                    # Extract operation output data
         
     | 
| 
      
 1175 
     | 
    
         
            +
                    output, error, recorded_function_name = (
         
     | 
| 
      
 1176 
     | 
    
         
            +
                        operation_output_rows[0][0],
         
     | 
| 
      
 1177 
     | 
    
         
            +
                        operation_output_rows[0][1],
         
     | 
| 
      
 1178 
     | 
    
         
            +
                        operation_output_rows[0][2],
         
     | 
| 
      
 1179 
     | 
    
         
            +
                    )
         
     | 
| 
      
 1180 
     | 
    
         
            +
             
     | 
| 
      
 1181 
     | 
    
         
            +
                    # If the provided and recorded function name are different, throw an exception
         
     | 
| 
       1147 
1182 
     | 
    
         
             
                    if function_name != recorded_function_name:
         
     | 
| 
       1148 
1183 
     | 
    
         
             
                        raise DBOSUnexpectedStepError(
         
     | 
| 
       1149 
1184 
     | 
    
         
             
                            workflow_id=workflow_id,
         
     | 
| 
         @@ -1151,6 +1186,7 @@ class SystemDatabase: 
     | 
|
| 
       1151 
1186 
     | 
    
         
             
                            expected_name=function_name,
         
     | 
| 
       1152 
1187 
     | 
    
         
             
                            recorded_name=recorded_function_name,
         
     | 
| 
       1153 
1188 
     | 
    
         
             
                        )
         
     | 
| 
      
 1189 
     | 
    
         
            +
             
     | 
| 
       1154 
1190 
     | 
    
         
             
                    result: RecordedResult = {
         
     | 
| 
       1155 
1191 
     | 
    
         
             
                        "output": output,
         
     | 
| 
       1156 
1192 
     | 
    
         
             
                        "error": error,
         
     | 
| 
         @@ -1699,6 +1735,17 @@ class SystemDatabase: 
     | 
|
| 
       1699 
1735 
     | 
    
         
             
                                    status=WorkflowStatusString.PENDING.value,
         
     | 
| 
       1700 
1736 
     | 
    
         
             
                                    application_version=app_version,
         
     | 
| 
       1701 
1737 
     | 
    
         
             
                                    executor_id=executor_id,
         
     | 
| 
      
 1738 
     | 
    
         
            +
                                    # If a timeout is set, set the deadline on dequeue
         
     | 
| 
      
 1739 
     | 
    
         
            +
                                    workflow_deadline_epoch_ms=sa.case(
         
     | 
| 
      
 1740 
     | 
    
         
            +
                                        (
         
     | 
| 
      
 1741 
     | 
    
         
            +
                                            SystemSchema.workflow_status.c.workflow_timeout_ms.isnot(
         
     | 
| 
      
 1742 
     | 
    
         
            +
                                                None
         
     | 
| 
      
 1743 
     | 
    
         
            +
                                            ),
         
     | 
| 
      
 1744 
     | 
    
         
            +
                                            sa.func.extract("epoch", sa.func.now()) * 1000
         
     | 
| 
      
 1745 
     | 
    
         
            +
                                            + SystemSchema.workflow_status.c.workflow_timeout_ms,
         
     | 
| 
      
 1746 
     | 
    
         
            +
                                        ),
         
     | 
| 
      
 1747 
     | 
    
         
            +
                                        else_=SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
         
     | 
| 
      
 1748 
     | 
    
         
            +
                                    ),
         
     | 
| 
       1702 
1749 
     | 
    
         
             
                                )
         
     | 
| 
       1703 
1750 
     | 
    
         
             
                            )
         
     | 
| 
       1704 
1751 
     | 
    
         
             
                            if res.rowcount > 0:
         
     | 
| 
         @@ -1821,12 +1868,12 @@ class SystemDatabase: 
     | 
|
| 
       1821 
1868 
     | 
    
         
             
                    inputs: str,
         
     | 
| 
       1822 
1869 
     | 
    
         
             
                    *,
         
     | 
| 
       1823 
1870 
     | 
    
         
             
                    max_recovery_attempts: Optional[int],
         
     | 
| 
       1824 
     | 
    
         
            -
                ) -> WorkflowStatuses:
         
     | 
| 
      
 1871 
     | 
    
         
            +
                ) -> tuple[WorkflowStatuses, Optional[int]]:
         
     | 
| 
       1825 
1872 
     | 
    
         
             
                    """
         
     | 
| 
       1826 
1873 
     | 
    
         
             
                    Synchronously record the status and inputs for workflows in a single transaction
         
     | 
| 
       1827 
1874 
     | 
    
         
             
                    """
         
     | 
| 
       1828 
1875 
     | 
    
         
             
                    with self.engine.begin() as conn:
         
     | 
| 
       1829 
     | 
    
         
            -
                        wf_status = self.insert_workflow_status(
         
     | 
| 
      
 1876 
     | 
    
         
            +
                        wf_status, workflow_deadline_epoch_ms = self.insert_workflow_status(
         
     | 
| 
       1830 
1877 
     | 
    
         
             
                            status, conn, max_recovery_attempts=max_recovery_attempts
         
     | 
| 
       1831 
1878 
     | 
    
         
             
                        )
         
     | 
| 
       1832 
1879 
     | 
    
         
             
                        # TODO: Modify the inputs if they were changed by `update_workflow_inputs`
         
     | 
| 
         @@ -1837,7 +1884,7 @@ class SystemDatabase: 
     | 
|
| 
       1837 
1884 
     | 
    
         
             
                            and wf_status == WorkflowStatusString.ENQUEUED.value
         
     | 
| 
       1838 
1885 
     | 
    
         
             
                        ):
         
     | 
| 
       1839 
1886 
     | 
    
         
             
                            self.enqueue(status["workflow_uuid"], status["queue_name"], conn)
         
     | 
| 
       1840 
     | 
    
         
            -
                    return wf_status
         
     | 
| 
      
 1887 
     | 
    
         
            +
                    return wf_status, workflow_deadline_epoch_ms
         
     | 
| 
       1841 
1888 
     | 
    
         | 
| 
       1842 
1889 
     | 
    
         | 
| 
       1843 
1890 
     | 
    
         
             
            def reset_system_database(config: ConfigFile) -> None:
         
     | 
    
        dbos/dbos-config.schema.json
    CHANGED
    
    | 
         @@ -24,43 +24,51 @@ 
     | 
|
| 
       24 
24 
     | 
    
         
             
                    "additionalProperties": false,
         
     | 
| 
       25 
25 
     | 
    
         
             
                    "properties": {
         
     | 
| 
       26 
26 
     | 
    
         
             
                      "hostname": {
         
     | 
| 
       27 
     | 
    
         
            -
                        "type": "string",
         
     | 
| 
       28 
     | 
    
         
            -
                        "description": "The hostname or IP address of the application database"
         
     | 
| 
      
 27 
     | 
    
         
            +
                        "type": ["string", "null"],
         
     | 
| 
      
 28 
     | 
    
         
            +
                        "description": "The hostname or IP address of the application database",
         
     | 
| 
      
 29 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       29 
30 
     | 
    
         
             
                      },
         
     | 
| 
       30 
31 
     | 
    
         
             
                      "port": {
         
     | 
| 
       31 
     | 
    
         
            -
                        "type": "number",
         
     | 
| 
       32 
     | 
    
         
            -
                        "description": "The port number of the application database"
         
     | 
| 
      
 32 
     | 
    
         
            +
                        "type": ["number", "null"],
         
     | 
| 
      
 33 
     | 
    
         
            +
                        "description": "The port number of the application database",
         
     | 
| 
      
 34 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       33 
35 
     | 
    
         
             
                      },
         
     | 
| 
       34 
36 
     | 
    
         
             
                      "username": {
         
     | 
| 
       35 
     | 
    
         
            -
                        "type": "string",
         
     | 
| 
      
 37 
     | 
    
         
            +
                        "type": ["string", "null"],
         
     | 
| 
       36 
38 
     | 
    
         
             
                        "description": "The username to use when connecting to the application database",
         
     | 
| 
       37 
39 
     | 
    
         
             
                        "not": {
         
     | 
| 
       38 
40 
     | 
    
         
             
                          "enum": ["dbos"]
         
     | 
| 
       39 
     | 
    
         
            -
                        }
         
     | 
| 
      
 41 
     | 
    
         
            +
                        },
         
     | 
| 
      
 42 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       40 
43 
     | 
    
         
             
                      },
         
     | 
| 
       41 
44 
     | 
    
         
             
                      "password": {
         
     | 
| 
       42 
45 
     | 
    
         
             
                        "type": ["string", "null"],
         
     | 
| 
       43 
     | 
    
         
            -
                        "description": "The password to use when connecting to the application database. Developers are strongly encouraged to use environment variable substitution (${VAR_NAME}) or Docker secrets (${DOCKER_SECRET:SECRET_NAME}) to avoid storing secrets in source."
         
     | 
| 
      
 46 
     | 
    
         
            +
                        "description": "The password to use when connecting to the application database. Developers are strongly encouraged to use environment variable substitution (${VAR_NAME}) or Docker secrets (${DOCKER_SECRET:SECRET_NAME}) to avoid storing secrets in source.",
         
     | 
| 
      
 47 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       44 
48 
     | 
    
         
             
                      },
         
     | 
| 
       45 
49 
     | 
    
         
             
                      "connectionTimeoutMillis": {
         
     | 
| 
       46 
     | 
    
         
            -
                        "type": "number",
         
     | 
| 
       47 
     | 
    
         
            -
                        "description": "The number of milliseconds the system waits before timing out when connecting to the application database"
         
     | 
| 
      
 50 
     | 
    
         
            +
                        "type": ["number", "null"],
         
     | 
| 
      
 51 
     | 
    
         
            +
                        "description": "The number of milliseconds the system waits before timing out when connecting to the application database",
         
     | 
| 
      
 52 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       48 
53 
     | 
    
         
             
                      },
         
     | 
| 
       49 
54 
     | 
    
         
             
                      "app_db_name": {
         
     | 
| 
       50 
     | 
    
         
            -
                        "type": "string",
         
     | 
| 
       51 
     | 
    
         
            -
                        "description": "The name of the application database"
         
     | 
| 
      
 55 
     | 
    
         
            +
                        "type": ["string", "null"],
         
     | 
| 
      
 56 
     | 
    
         
            +
                        "description": "The name of the application database",
         
     | 
| 
      
 57 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       52 
58 
     | 
    
         
             
                      },
         
     | 
| 
       53 
59 
     | 
    
         
             
                      "sys_db_name": {
         
     | 
| 
       54 
60 
     | 
    
         
             
                        "type": "string",
         
     | 
| 
       55 
61 
     | 
    
         
             
                        "description": "The name of the system database"
         
     | 
| 
       56 
62 
     | 
    
         
             
                      },
         
     | 
| 
       57 
63 
     | 
    
         
             
                      "ssl": {
         
     | 
| 
       58 
     | 
    
         
            -
                        "type": "boolean",
         
     | 
| 
       59 
     | 
    
         
            -
                        "description": "Use SSL/TLS to securely connect to the database (default: true)"
         
     | 
| 
      
 64 
     | 
    
         
            +
                        "type": ["boolean", "null"],
         
     | 
| 
      
 65 
     | 
    
         
            +
                        "description": "Use SSL/TLS to securely connect to the database (default: true)",
         
     | 
| 
      
 66 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       60 
67 
     | 
    
         
             
                      },
         
     | 
| 
       61 
68 
     | 
    
         
             
                      "ssl_ca": {
         
     | 
| 
       62 
     | 
    
         
            -
                        "type": "string",
         
     | 
| 
       63 
     | 
    
         
            -
                        "description": "If using SSL/TLS to securely connect to a database, path to an SSL root certificate file"
         
     | 
| 
      
 69 
     | 
    
         
            +
                        "type": ["string", "null"],
         
     | 
| 
      
 70 
     | 
    
         
            +
                        "description": "If using SSL/TLS to securely connect to a database, path to an SSL root certificate file",
         
     | 
| 
      
 71 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       64 
72 
     | 
    
         
             
                      },
         
     | 
| 
       65 
73 
     | 
    
         
             
                      "app_db_client": {
         
     | 
| 
       66 
74 
     | 
    
         
             
                        "type": "string",
         
     | 
| 
         @@ -78,7 +86,8 @@ 
     | 
|
| 
       78 
86 
     | 
    
         
             
                      },
         
     | 
| 
       79 
87 
     | 
    
         
             
                      "rollback": {
         
     | 
| 
       80 
88 
     | 
    
         
             
                        "type": "array",
         
     | 
| 
       81 
     | 
    
         
            -
                        "description": "Specify a list of user DB rollback commands to run"
         
     | 
| 
      
 89 
     | 
    
         
            +
                        "description": "Specify a list of user DB rollback commands to run",
         
     | 
| 
      
 90 
     | 
    
         
            +
                        "deprecated": true
         
     | 
| 
       82 
91 
     | 
    
         
             
                      }
         
     | 
| 
       83 
92 
     | 
    
         
             
                    }
         
     | 
| 
       84 
93 
     | 
    
         
             
                  },
         
     | 
| 
         @@ -1,17 +1,17 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            dbos-0.26. 
     | 
| 
       2 
     | 
    
         
            -
            dbos-0.26. 
     | 
| 
       3 
     | 
    
         
            -
            dbos-0.26. 
     | 
| 
       4 
     | 
    
         
            -
            dbos-0.26. 
     | 
| 
      
 1 
     | 
    
         
            +
            dbos-0.26.0a23.dist-info/METADATA,sha256=jXiNGE_gmy2H6gw4CoiC3fczU-7acoz-QxJ1EWUnnjQ,5554
         
     | 
| 
      
 2 
     | 
    
         
            +
            dbos-0.26.0a23.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
         
     | 
| 
      
 3 
     | 
    
         
            +
            dbos-0.26.0a23.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
         
     | 
| 
      
 4 
     | 
    
         
            +
            dbos-0.26.0a23.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
         
     | 
| 
       5 
5 
     | 
    
         
             
            dbos/__init__.py,sha256=VoGS7H9GVtNAnD2S4zseIEioS1dNIJXRovQ4oHlg8og,842
         
     | 
| 
       6 
6 
     | 
    
         
             
            dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
         
     | 
| 
       7 
7 
     | 
    
         
             
            dbos/_admin_server.py,sha256=RrbABfR1D3p9c_QLrCSrgFuYce6FKi0fjMRIYLjO_Y8,9038
         
     | 
| 
       8 
8 
     | 
    
         
             
            dbos/_app_db.py,sha256=obNlgC9IZ20y8tqQeA1q4TjceG3jBFalxz70ieDOWCA,11332
         
     | 
| 
       9 
9 
     | 
    
         
             
            dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
         
     | 
| 
       10 
     | 
    
         
            -
            dbos/_client.py,sha256= 
     | 
| 
      
 10 
     | 
    
         
            +
            dbos/_client.py,sha256=f1n5bbtVO-Mf5dDvI3sNlozxHSUfstWtgPirSqv1kpE,12518
         
     | 
| 
       11 
11 
     | 
    
         
             
            dbos/_conductor/conductor.py,sha256=HYzVL29IMMrs2Mnms_7cHJynCnmmEN5SDQOMjzn3UoU,16840
         
     | 
| 
       12 
12 
     | 
    
         
             
            dbos/_conductor/protocol.py,sha256=zEKIuOQdIaSduNqfZKpo8PSD9_1oNpKIPnBNCu3RUyE,6681
         
     | 
| 
       13 
     | 
    
         
            -
            dbos/_context.py,sha256= 
     | 
| 
       14 
     | 
    
         
            -
            dbos/_core.py,sha256= 
     | 
| 
      
 13 
     | 
    
         
            +
            dbos/_context.py,sha256=aHzJxO7LLAz9w3G2dkZnOcFW_GG-Yaxd02AaoLu4Et8,21861
         
     | 
| 
      
 14 
     | 
    
         
            +
            dbos/_core.py,sha256=ylTVSv02h2M5SmDgYEJAZmNiKX35zPq0z-9WA-f4byY,47900
         
     | 
| 
       15 
15 
     | 
    
         
             
            dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
         
     | 
| 
       16 
16 
     | 
    
         
             
            dbos/_dbos.py,sha256=bbio_FjBfU__Zk1BFegfS16IrPPejFxOKm5rUg5nW1o,47185
         
     | 
| 
       17 
17 
     | 
    
         
             
            dbos/_dbos_config.py,sha256=m05IFjM0jSwZBsnFMF_4qP2JkjVFc0gqyM2tnotXq20,20636
         
     | 
| 
         @@ -29,6 +29,7 @@ dbos/_migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dM 
     | 
|
| 
       29 
29 
     | 
    
         
             
            dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py,sha256=ICLPl8CN9tQXMsLDsAj8z1TsL831-Z3F8jSBvrR-wyw,736
         
     | 
| 
       30 
30 
     | 
    
         
             
            dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py,sha256=ZBYrtTdxy64HxIAlOes89fVIk2P1gNaJack7wuC_epg,873
         
     | 
| 
       31 
31 
     | 
    
         
             
            dbos/_migrations/versions/5c361fc04708_added_system_tables.py,sha256=Xr9hBDJjkAtymlauOmAy00yUHj0VVUaEz7kNwEM9IwE,6403
         
     | 
| 
      
 32 
     | 
    
         
            +
            dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py,sha256=Q_R35pb8AfVI3sg5mzKwyoPfYB88Ychcc8gwxpM9R7A,1035
         
     | 
| 
       32 
33 
     | 
    
         
             
            dbos/_migrations/versions/a3b18ad34abe_added_triggers.py,sha256=Rv0ZsZYZ_WdgGEULYsPfnp4YzaO5L198gDTgYY39AVA,2022
         
     | 
| 
       33 
34 
     | 
    
         
             
            dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py,sha256=8PyFi8rd6CN-mUro43wGhsg5wcQWKZPRHD6jw8R5pVc,986
         
     | 
| 
       34 
35 
     | 
    
         
             
            dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4hGBC02Ptng1715roTjY3xiyzZU4,729
         
     | 
| 
         @@ -43,9 +44,9 @@ dbos/_roles.py,sha256=iOsgmIAf1XVzxs3gYWdGRe1B880YfOw5fpU7Jwx8_A8,2271 
     | 
|
| 
       43 
44 
     | 
    
         
             
            dbos/_scheduler.py,sha256=SR1oRZRcVzYsj-JauV2LA8JtwTkt8mru7qf6H1AzQ1U,2027
         
     | 
| 
       44 
45 
     | 
    
         
             
            dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       45 
46 
     | 
    
         
             
            dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
         
     | 
| 
       46 
     | 
    
         
            -
            dbos/_schemas/system_database.py,sha256= 
     | 
| 
      
 47 
     | 
    
         
            +
            dbos/_schemas/system_database.py,sha256=aChSK7uLECD-v-7BZeOfuZFbtWayllaS3PaowaKDHwY,5490
         
     | 
| 
       47 
48 
     | 
    
         
             
            dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
         
     | 
| 
       48 
     | 
    
         
            -
            dbos/_sys_db.py,sha256= 
     | 
| 
      
 49 
     | 
    
         
            +
            dbos/_sys_db.py,sha256=SjYTleSEPtZVrPRimgXKeIvTjY8VN9G9jlgbcPT8ghg,80631
         
     | 
| 
       49 
50 
     | 
    
         
             
            dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
         
     | 
| 
       50 
51 
     | 
    
         
             
            dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         
     | 
| 
       51 
52 
     | 
    
         
             
            dbos/_templates/dbos-db-starter/__package/main.py,sha256=nJMN3ZD2lmwg4Dcgmiwqc-tQGuCJuJal2Xl85iA277U,2453
         
     | 
| 
         @@ -62,7 +63,7 @@ dbos/_workflow_commands.py,sha256=7wyxTfIyh2IVIqlkaTr8CMBq8yxWP3Hhddyv1YJY8zE,35 
     | 
|
| 
       62 
63 
     | 
    
         
             
            dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
         
     | 
| 
       63 
64 
     | 
    
         
             
            dbos/cli/_template_init.py,sha256=-WW3kbq0W_Tq4WbMqb1UGJG3xvJb3woEY5VspG95Srk,2857
         
     | 
| 
       64 
65 
     | 
    
         
             
            dbos/cli/cli.py,sha256=1qCTs__A9LOEfU44XZ6TufwmRwe68ZEwbWEPli3vnVM,17873
         
     | 
| 
       65 
     | 
    
         
            -
            dbos/dbos-config.schema.json,sha256= 
     | 
| 
      
 66 
     | 
    
         
            +
            dbos/dbos-config.schema.json,sha256=3EfMhI83kmQmhRNIYgtbila0zL28GX9huYALzbQyABw,6052
         
     | 
| 
       66 
67 
     | 
    
         
             
            dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
         
     | 
| 
       67 
68 
     | 
    
         
             
            version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
         
     | 
| 
       68 
     | 
    
         
            -
            dbos-0.26. 
     | 
| 
      
 69 
     | 
    
         
            +
            dbos-0.26.0a23.dist-info/RECORD,,
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |