dbos 0.25.0a7__py3-none-any.whl → 0.25.0a8__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.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

dbos/__init__.py CHANGED
@@ -1,10 +1,11 @@
1
1
  from . import _error as error
2
2
  from ._context import DBOSContextEnsure, DBOSContextSetAuth, SetWorkflowID
3
- from ._dbos import DBOS, DBOSConfiguredInstance, WorkflowHandle, WorkflowStatus
3
+ from ._dbos import DBOS, DBOSConfiguredInstance, WorkflowHandle
4
4
  from ._dbos_config import ConfigFile, DBOSConfig, get_dbos_database_url, load_config
5
5
  from ._kafka_message import KafkaMessage
6
6
  from ._queue import Queue
7
7
  from ._sys_db import GetWorkflowsInput, WorkflowStatusString
8
+ from ._workflow_commands import WorkflowStatus
8
9
 
9
10
  __all__ = [
10
11
  "ConfigFile",
@@ -203,7 +203,7 @@ class ConductorWebsocket(threading.Thread):
203
203
  info = get_workflow(
204
204
  self.dbos._sys_db,
205
205
  get_workflow_message.workflow_id,
206
- getRequest=False,
206
+ get_request=False,
207
207
  )
208
208
  except Exception as e:
209
209
  error_message = f"Exception encountered when getting workflow {get_workflow_message.workflow_id}: {traceback.format_exc()}"
@@ -3,7 +3,7 @@ from dataclasses import asdict, dataclass
3
3
  from enum import Enum
4
4
  from typing import List, Optional, Type, TypedDict, TypeVar
5
5
 
6
- from dbos._workflow_commands import WorkflowInformation
6
+ from dbos._workflow_commands import WorkflowStatus
7
7
 
8
8
 
9
9
  class MessageType(str, Enum):
@@ -141,27 +141,33 @@ class WorkflowsOutput:
141
141
  ExecutorID: Optional[str]
142
142
 
143
143
  @classmethod
144
- def from_workflow_information(cls, info: WorkflowInformation) -> "WorkflowsOutput":
144
+ def from_workflow_information(cls, info: WorkflowStatus) -> "WorkflowsOutput":
145
145
  # Convert fields to strings as needed
146
146
  created_at_str = str(info.created_at) if info.created_at is not None else None
147
147
  updated_at_str = str(info.updated_at) if info.updated_at is not None else None
148
148
  inputs_str = str(info.input) if info.input is not None else None
149
149
  outputs_str = str(info.output) if info.output is not None else None
150
+ error_str = str(info.error) if info.error is not None else None
150
151
  request_str = str(info.request) if info.request is not None else None
152
+ roles_str = (
153
+ str(info.authenticated_roles)
154
+ if info.authenticated_roles is not None
155
+ else None
156
+ )
151
157
 
152
158
  return cls(
153
159
  WorkflowUUID=info.workflow_id,
154
160
  Status=info.status,
155
- WorkflowName=info.workflow_name,
156
- WorkflowClassName=info.workflow_class_name,
157
- WorkflowConfigName=info.workflow_config_name,
161
+ WorkflowName=info.name,
162
+ WorkflowClassName=info.class_name,
163
+ WorkflowConfigName=info.config_name,
158
164
  AuthenticatedUser=info.authenticated_user,
159
165
  AssumedRole=info.assumed_role,
160
- AuthenticatedRoles=info.authenticated_roles,
166
+ AuthenticatedRoles=roles_str,
161
167
  Input=inputs_str,
162
168
  Output=outputs_str,
163
169
  Request=request_str,
164
- Error=info.error,
170
+ Error=error_str,
165
171
  CreatedAt=created_at_str,
166
172
  UpdatedAt=updated_at_str,
167
173
  QueueName=info.queue_name,
dbos/_context.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  import uuid
6
6
  from contextlib import AbstractContextManager
7
7
  from contextvars import ContextVar
8
+ from dataclasses import dataclass
8
9
  from enum import Enum
9
10
  from types import TracebackType
10
11
  from typing import List, Literal, Optional, Type, TypedDict
@@ -48,6 +49,23 @@ class TracedAttributes(TypedDict, total=False):
48
49
  authenticatedUserAssumedRole: Optional[str]
49
50
 
50
51
 
52
+ @dataclass
53
+ class StepStatus:
54
+ """
55
+ Status of a step execution.
56
+
57
+ Attributes:
58
+ step_id: The unique ID of this step in its workflow.
59
+ current_attempt: For steps with automatic retries, which attempt number (zero-indexed) is currently executing.
60
+ max_attempts: For steps with automatic retries, the maximum number of attempts that will be made before the step fails.
61
+
62
+ """
63
+
64
+ step_id: int
65
+ current_attempt: Optional[int]
66
+ max_attempts: Optional[int]
67
+
68
+
51
69
  class DBOSContext:
52
70
  def __init__(self) -> None:
53
71
  self.executor_id = GlobalParams.executor_id
@@ -73,6 +91,7 @@ class DBOSContext:
73
91
  self.authenticated_user: Optional[str] = None
74
92
  self.authenticated_roles: Optional[List[str]] = None
75
93
  self.assumed_role: Optional[str] = None
94
+ self.step_status: Optional[StepStatus] = None
76
95
 
77
96
  def create_child(self) -> DBOSContext:
78
97
  rv = DBOSContext()
@@ -150,10 +169,12 @@ class DBOSContext:
150
169
  attributes: TracedAttributes,
151
170
  ) -> None:
152
171
  self.curr_step_function_id = fid
172
+ self.step_status = StepStatus(fid, None, None)
153
173
  self._start_span(attributes)
154
174
 
155
175
  def end_step(self, exc_value: Optional[BaseException]) -> None:
156
176
  self.curr_step_function_id = -1
177
+ self.step_status = None
157
178
  self._end_span(exc_value)
158
179
 
159
180
  def start_transaction(
@@ -432,6 +453,30 @@ class EnterDBOSStep:
432
453
  return False # Did not handle
433
454
 
434
455
 
456
+ class EnterDBOSStepRetry:
457
+ def __init__(self, current_attempt: int, max_attempts: int) -> None:
458
+ self.current_attempt = current_attempt
459
+ self.max_attempts = max_attempts
460
+
461
+ def __enter__(self) -> None:
462
+ ctx = get_local_dbos_context()
463
+ if ctx is not None and ctx.step_status is not None:
464
+ ctx.step_status.current_attempt = self.current_attempt
465
+ ctx.step_status.max_attempts = self.max_attempts
466
+
467
+ def __exit__(
468
+ self,
469
+ exc_type: Optional[Type[BaseException]],
470
+ exc_value: Optional[BaseException],
471
+ traceback: Optional[TracebackType],
472
+ ) -> Literal[False]:
473
+ ctx = get_local_dbos_context()
474
+ if ctx is not None and ctx.step_status is not None:
475
+ ctx.step_status.current_attempt = None
476
+ ctx.step_status.max_attempts = None
477
+ return False # Did not handle
478
+
479
+
435
480
  class EnterDBOSTransaction:
436
481
  def __init__(self, sqls: Session, attributes: TracedAttributes) -> None:
437
482
  self.sqls = sqls
dbos/_core.py CHANGED
@@ -84,10 +84,10 @@ if TYPE_CHECKING:
84
84
  Workflow,
85
85
  WorkflowHandle,
86
86
  WorkflowHandleAsync,
87
- WorkflowStatus,
88
87
  DBOSRegistry,
89
88
  IsolationLevel,
90
89
  )
90
+ from ._workflow_commands import WorkflowStatus
91
91
 
92
92
  from sqlalchemy.exc import DBAPIError, InvalidRequestError
93
93
 
@@ -515,9 +515,6 @@ def start_workflow(
515
515
  or wf_status == WorkflowStatusString.SUCCESS.value
516
516
  )
517
517
  ):
518
- dbos.logger.debug(
519
- f"Workflow {new_wf_id} already completed with status {wf_status}. Directly returning a workflow handle."
520
- )
521
518
  return WorkflowHandlePolling(new_wf_id, dbos)
522
519
 
523
520
  future = dbos._executor.submit(
@@ -605,9 +602,6 @@ async def start_workflow_async(
605
602
  or wf_status == WorkflowStatusString.SUCCESS.value
606
603
  )
607
604
  ):
608
- dbos.logger.debug(
609
- f"Workflow {new_wf_id} already completed with status {wf_status}. Directly returning a workflow handle."
610
- )
611
605
  return WorkflowHandleAsyncPolling(new_wf_id, dbos)
612
606
 
613
607
  coro = _execute_workflow_async(dbos, status, func, new_wf_ctx, *args, **kwargs)
@@ -946,7 +940,7 @@ def decorate_step(
946
940
 
947
941
  def on_exception(attempt: int, error: BaseException) -> float:
948
942
  dbos.logger.warning(
949
- f"Step being automatically retried. (attempt {attempt} of {attempts}). {traceback.format_exc()}"
943
+ f"Step being automatically retried. (attempt {attempt + 1} of {attempts}). {traceback.format_exc()}"
950
944
  )
951
945
  ctx = assert_current_dbos_context()
952
946
  ctx.get_current_span().add_event(
dbos/_dbos.py CHANGED
@@ -11,7 +11,6 @@ import threading
11
11
  import traceback
12
12
  import uuid
13
13
  from concurrent.futures import ThreadPoolExecutor
14
- from dataclasses import dataclass
15
14
  from logging import Logger
16
15
  from typing import (
17
16
  TYPE_CHECKING,
@@ -28,13 +27,18 @@ from typing import (
28
27
  TypeVar,
29
28
  Union,
30
29
  cast,
31
- overload,
32
30
  )
33
31
 
34
32
  from opentelemetry.trace import Span
35
33
 
34
+ from dbos import _serialization
36
35
  from dbos._conductor.conductor import ConductorWebsocket
37
36
  from dbos._utils import GlobalParams
37
+ from dbos._workflow_commands import (
38
+ WorkflowStatus,
39
+ list_queued_workflows,
40
+ list_workflows,
41
+ )
38
42
 
39
43
  from ._classproperty import classproperty
40
44
  from ._core import (
@@ -86,6 +90,7 @@ from ._admin_server import AdminServer
86
90
  from ._app_db import ApplicationDatabase
87
91
  from ._context import (
88
92
  EnterDBOSStep,
93
+ StepStatus,
89
94
  TracedAttributes,
90
95
  assert_current_dbos_context,
91
96
  get_local_dbos_context,
@@ -108,6 +113,7 @@ from ._error import (
108
113
  )
109
114
  from ._logger import add_otlp_to_all_loggers, config_logger, dbos_logger, init_logger
110
115
  from ._sys_db import SystemDatabase
116
+ from ._workflow_commands import WorkflowStatus, get_workflow
111
117
 
112
118
  # Most DBOS functions are just any callable F, so decorators / wrappers work on F
113
119
  # There are cases where the parameters P and return value R should be separate
@@ -729,72 +735,39 @@ class DBOS:
729
735
  @classmethod
730
736
  def get_workflow_status(cls, workflow_id: str) -> Optional[WorkflowStatus]:
731
737
  """Return the status of a workflow execution."""
738
+ sys_db = _get_dbos_instance()._sys_db
732
739
  ctx = get_local_dbos_context()
733
740
  if ctx and ctx.is_within_workflow():
734
741
  ctx.function_id += 1
735
- stat = _get_dbos_instance()._sys_db.get_workflow_status_within_wf(
736
- workflow_id, ctx.workflow_id, ctx.function_id
742
+ res = sys_db.check_operation_execution(ctx.workflow_id, ctx.function_id)
743
+ if res is not None:
744
+ if res["output"]:
745
+ resstat: WorkflowStatus = _serialization.deserialize(res["output"])
746
+ return resstat
747
+ else:
748
+ raise DBOSException(
749
+ "Workflow status record not found. This should not happen! \033[1m Hint: Check if your workflow is deterministic.\033[0m"
750
+ )
751
+ stat = get_workflow(_get_dbos_instance()._sys_db, workflow_id, True)
752
+
753
+ if ctx and ctx.is_within_workflow():
754
+ sys_db.record_operation_result(
755
+ {
756
+ "workflow_uuid": ctx.workflow_id,
757
+ "function_id": ctx.function_id,
758
+ "function_name": "DBOS.getStatus",
759
+ "output": _serialization.serialize(stat),
760
+ "error": None,
761
+ }
737
762
  )
738
- else:
739
- stat = _get_dbos_instance()._sys_db.get_workflow_status(workflow_id)
740
- if stat is None:
741
- return None
742
-
743
- return WorkflowStatus(
744
- workflow_id=workflow_id,
745
- status=stat["status"],
746
- name=stat["name"],
747
- executor_id=stat["executor_id"],
748
- recovery_attempts=stat["recovery_attempts"],
749
- class_name=stat["class_name"],
750
- config_name=stat["config_name"],
751
- queue_name=stat["queue_name"],
752
- authenticated_user=stat["authenticated_user"],
753
- assumed_role=stat["assumed_role"],
754
- authenticated_roles=(
755
- json.loads(stat["authenticated_roles"])
756
- if stat["authenticated_roles"] is not None
757
- else None
758
- ),
759
- )
763
+ return stat
760
764
 
761
765
  @classmethod
762
766
  async def get_workflow_status_async(
763
767
  cls, workflow_id: str
764
768
  ) -> Optional[WorkflowStatus]:
765
769
  """Return the status of a workflow execution."""
766
- ctx = get_local_dbos_context()
767
- if ctx and ctx.is_within_workflow():
768
- ctx.function_id += 1
769
- stat = await asyncio.to_thread(
770
- lambda: _get_dbos_instance()._sys_db.get_workflow_status_within_wf(
771
- workflow_id, ctx.workflow_id, ctx.function_id
772
- )
773
- )
774
- else:
775
- stat = await asyncio.to_thread(
776
- lambda: _get_dbos_instance()._sys_db.get_workflow_status(workflow_id)
777
- )
778
- if stat is None:
779
- return None
780
-
781
- return WorkflowStatus(
782
- workflow_id=workflow_id,
783
- status=stat["status"],
784
- name=stat["name"],
785
- executor_id=stat["executor_id"],
786
- recovery_attempts=stat["recovery_attempts"],
787
- class_name=stat["class_name"],
788
- config_name=stat["config_name"],
789
- queue_name=stat["queue_name"],
790
- authenticated_user=stat["authenticated_user"],
791
- assumed_role=stat["assumed_role"],
792
- authenticated_roles=(
793
- json.loads(stat["authenticated_roles"])
794
- if stat["authenticated_roles"] is not None
795
- else None
796
- ),
797
- )
770
+ return await asyncio.to_thread(cls.get_workflow_status, workflow_id)
798
771
 
799
772
  @classmethod
800
773
  def retrieve_workflow(
@@ -994,6 +967,60 @@ class DBOS:
994
967
  _get_or_create_dbos_registry().clear_workflow_cancelled(workflow_id)
995
968
  return execute_workflow_by_id(_get_dbos_instance(), workflow_id, False)
996
969
 
970
+ @classmethod
971
+ def list_workflows(
972
+ cls,
973
+ *,
974
+ workflow_ids: Optional[List[str]] = None,
975
+ status: Optional[str] = None,
976
+ start_time: Optional[str] = None,
977
+ end_time: Optional[str] = None,
978
+ name: Optional[str] = None,
979
+ app_version: Optional[str] = None,
980
+ user: Optional[str] = None,
981
+ limit: Optional[int] = None,
982
+ offset: Optional[int] = None,
983
+ sort_desc: bool = False,
984
+ ) -> List[WorkflowStatus]:
985
+ return list_workflows(
986
+ _get_dbos_instance()._sys_db,
987
+ workflow_ids=workflow_ids,
988
+ status=status,
989
+ start_time=start_time,
990
+ end_time=end_time,
991
+ name=name,
992
+ app_version=app_version,
993
+ user=user,
994
+ limit=limit,
995
+ offset=offset,
996
+ sort_desc=sort_desc,
997
+ )
998
+
999
+ @classmethod
1000
+ def list_queued_workflows(
1001
+ cls,
1002
+ *,
1003
+ queue_name: Optional[str] = None,
1004
+ status: Optional[str] = None,
1005
+ start_time: Optional[str] = None,
1006
+ end_time: Optional[str] = None,
1007
+ name: Optional[str] = None,
1008
+ limit: Optional[int] = None,
1009
+ offset: Optional[int] = None,
1010
+ sort_desc: bool = False,
1011
+ ) -> List[WorkflowStatus]:
1012
+ return list_queued_workflows(
1013
+ _get_dbos_instance()._sys_db,
1014
+ queue_name=queue_name,
1015
+ status=status,
1016
+ start_time=start_time,
1017
+ end_time=end_time,
1018
+ name=name,
1019
+ limit=limit,
1020
+ offset=offset,
1021
+ sort_desc=sort_desc,
1022
+ )
1023
+
997
1024
  @classproperty
998
1025
  def logger(cls) -> Logger:
999
1026
  """Return the DBOS `Logger` for the current context."""
@@ -1041,6 +1068,14 @@ class DBOS:
1041
1068
  ), "step_id is only available within a DBOS workflow."
1042
1069
  return ctx.function_id
1043
1070
 
1071
+ @classproperty
1072
+ def step_status(cls) -> StepStatus:
1073
+ """Return the status of the currently executing step."""
1074
+ ctx = assert_current_dbos_context()
1075
+ assert ctx.is_step(), "step_status is only available within a DBOS step."
1076
+ assert ctx.step_status is not None
1077
+ return ctx.step_status
1078
+
1044
1079
  @classproperty
1045
1080
  def parent_workflow_id(cls) -> str:
1046
1081
  """
@@ -1095,41 +1130,6 @@ class DBOS:
1095
1130
  ctx.authenticated_roles = authenticated_roles
1096
1131
 
1097
1132
 
1098
- @dataclass
1099
- class WorkflowStatus:
1100
- """
1101
- Status of workflow execution.
1102
-
1103
- This captures the state of a workflow execution at a point in time.
1104
-
1105
- Attributes:
1106
- workflow_id(str): The ID of the workflow execution
1107
- status(str): The status of the execution, from `WorkflowStatusString`
1108
- name(str): The workflow function name
1109
- executor_id(str): The ID of the executor running the workflow
1110
- class_name(str): For member functions, the name of the class containing the workflow function
1111
- config_name(str): For instance member functions, the name of the class instance for the execution
1112
- queue_name(str): For workflows that are or were queued, the queue name
1113
- authenticated_user(str): The user who invoked the workflow
1114
- assumed_role(str): The access role used by the user to allow access to the workflow function
1115
- authenticated_roles(List[str]): List of all access roles available to the authenticated user
1116
- recovery_attempts(int): Number of times the workflow has been restarted (usually by recovery)
1117
-
1118
- """
1119
-
1120
- workflow_id: str
1121
- status: str
1122
- name: str
1123
- executor_id: Optional[str]
1124
- class_name: Optional[str]
1125
- config_name: Optional[str]
1126
- queue_name: Optional[str]
1127
- authenticated_user: Optional[str]
1128
- assumed_role: Optional[str]
1129
- authenticated_roles: Optional[List[str]]
1130
- recovery_attempts: Optional[int]
1131
-
1132
-
1133
1133
  class WorkflowHandle(Generic[R], Protocol):
1134
1134
  """
1135
1135
  Handle to a workflow function.
dbos/_outcome.py CHANGED
@@ -4,6 +4,8 @@ import inspect
4
4
  import time
5
5
  from typing import Any, Callable, Coroutine, Optional, Protocol, TypeVar, Union, cast
6
6
 
7
+ from dbos._context import EnterDBOSStepRetry
8
+
7
9
  T = TypeVar("T")
8
10
  R = TypeVar("R")
9
11
 
@@ -98,7 +100,8 @@ class Immediate(Outcome[T]):
98
100
  ) -> T:
99
101
  for i in range(attempts):
100
102
  try:
101
- return func()
103
+ with EnterDBOSStepRetry(i, attempts):
104
+ return func()
102
105
  except Exception as exp:
103
106
  wait_time = on_exception(i, exp)
104
107
  time.sleep(wait_time)
@@ -184,7 +187,8 @@ class Pending(Outcome[T]):
184
187
  ) -> T:
185
188
  for i in range(attempts):
186
189
  try:
187
- return await func()
190
+ with EnterDBOSStepRetry(i, attempts):
191
+ return await func()
188
192
  except Exception as exp:
189
193
  wait_time = on_exception(i, exp)
190
194
  await asyncio.sleep(wait_time)
dbos/_sys_db.py CHANGED
@@ -116,7 +116,7 @@ class GetWorkflowsInput:
116
116
  self.authenticated_user: Optional[str] = None # The user who ran the workflow.
117
117
  self.start_time: Optional[str] = None # Timestamp in ISO 8601 format
118
118
  self.end_time: Optional[str] = None # Timestamp in ISO 8601 format
119
- self.status: Optional[WorkflowStatuses] = None
119
+ self.status: Optional[str] = None
120
120
  self.application_version: Optional[str] = (
121
121
  None # The application version that ran this workflow. = None
122
122
  )
@@ -541,32 +541,6 @@ class SystemDatabase:
541
541
  }
542
542
  return status
543
543
 
544
- def get_workflow_status_within_wf(
545
- self, workflow_uuid: str, calling_wf: str, calling_wf_fn: int
546
- ) -> Optional[WorkflowStatusInternal]:
547
- res = self.check_operation_execution(calling_wf, calling_wf_fn)
548
- if res is not None:
549
- if res["output"]:
550
- resstat: WorkflowStatusInternal = _serialization.deserialize(
551
- res["output"]
552
- )
553
- return resstat
554
- else:
555
- raise DBOSException(
556
- "Workflow status record not found. This should not happen! \033[1m Hint: Check if your workflow is deterministic.\033[0m"
557
- )
558
- stat = self.get_workflow_status(workflow_uuid)
559
- self.record_operation_result(
560
- {
561
- "workflow_uuid": calling_wf,
562
- "function_id": calling_wf_fn,
563
- "function_name": "DBOS.getStatus",
564
- "output": _serialization.serialize(stat),
565
- "error": None,
566
- }
567
- )
568
- return stat
569
-
570
544
  def await_workflow_result_internal(self, workflow_uuid: str) -> dict[str, Any]:
571
545
  polling_interval_secs: float = 1.000
572
546
 
@@ -1,4 +1,5 @@
1
- from typing import List, Optional, cast
1
+ import json
2
+ from typing import Any, List, Optional
2
3
 
3
4
  from . import _serialization
4
5
  from ._sys_db import (
@@ -7,54 +8,71 @@ from ._sys_db import (
7
8
  GetWorkflowsOutput,
8
9
  StepInfo,
9
10
  SystemDatabase,
10
- WorkflowStatuses,
11
11
  )
12
12
 
13
13
 
14
- class WorkflowInformation:
14
+ class WorkflowStatus:
15
+ # The workflow ID
15
16
  workflow_id: str
16
- status: WorkflowStatuses
17
- workflow_name: str
18
- workflow_class_name: Optional[str]
19
- workflow_config_name: Optional[str]
17
+ # The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or RETRIES_EXCEEDED
18
+ status: str
19
+ # The name of the workflow function
20
+ name: str
21
+ # The name of the workflow's class, if any
22
+ class_name: Optional[str]
23
+ # The name with which the workflow's class instance was configured, if any
24
+ config_name: Optional[str]
25
+ # The user who ran the workflow, if specified
20
26
  authenticated_user: Optional[str]
27
+ # The role with which the workflow ran, if specified
21
28
  assumed_role: Optional[str]
22
- authenticated_roles: Optional[str] # JSON list of roles.
23
- input: Optional[_serialization.WorkflowInputs] # JSON (jsonpickle)
24
- output: Optional[str] = None # JSON (jsonpickle)
25
- request: Optional[str] # JSON (jsonpickle)
26
- error: Optional[str] = None # JSON (jsonpickle)
27
- created_at: Optional[int] # Unix epoch timestamp in ms
28
- updated_at: Optional[int] # Unix epoch timestamp in ms
29
+ # All roles which the authenticated user could assume
30
+ authenticated_roles: Optional[list[str]]
31
+ # The deserialized workflow input object
32
+ input: Optional[_serialization.WorkflowInputs]
33
+ # The workflow's output, if any
34
+ output: Optional[Any] = None
35
+ # The error the workflow threw, if any
36
+ error: Optional[Exception] = None
37
+ # Workflow start time, as a Unix epoch timestamp in ms
38
+ created_at: Optional[int]
39
+ # Last time the workflow status was updated, as a Unix epoch timestamp in ms
40
+ updated_at: Optional[int]
41
+ # If this workflow was enqueued, on which queue
29
42
  queue_name: Optional[str]
43
+ # The executor to most recently executed this workflow
30
44
  executor_id: Optional[str]
45
+ # The application version on which this workflow was started
31
46
  app_version: Optional[str]
47
+ # The ID of the application executing this workflow
32
48
  app_id: Optional[str]
49
+ # The number of times this workflow's execution has been attempted
33
50
  recovery_attempts: Optional[int]
51
+ # The HTTP request that triggered the workflow, if known
52
+ request: Optional[str]
34
53
 
35
54
 
36
55
  def list_workflows(
37
56
  sys_db: SystemDatabase,
38
57
  *,
39
58
  workflow_ids: Optional[List[str]] = None,
40
- user: Optional[str] = None,
59
+ status: Optional[str] = None,
41
60
  start_time: Optional[str] = None,
42
61
  end_time: Optional[str] = None,
43
- status: Optional[str] = None,
44
- request: bool = False,
45
- app_version: Optional[str] = None,
46
62
  name: Optional[str] = None,
63
+ app_version: Optional[str] = None,
64
+ user: Optional[str] = None,
47
65
  limit: Optional[int] = None,
48
66
  offset: Optional[int] = None,
49
67
  sort_desc: bool = False,
50
- ) -> List[WorkflowInformation]:
68
+ request: bool = False,
69
+ ) -> List[WorkflowStatus]:
51
70
  input = GetWorkflowsInput()
52
71
  input.workflow_ids = workflow_ids
53
72
  input.authenticated_user = user
54
73
  input.start_time = start_time
55
74
  input.end_time = end_time
56
- if status is not None:
57
- input.status = cast(WorkflowStatuses, status)
75
+ input.status = status
58
76
  input.application_version = app_version
59
77
  input.limit = limit
60
78
  input.name = name
@@ -62,7 +80,7 @@ def list_workflows(
62
80
  input.sort_desc = sort_desc
63
81
 
64
82
  output: GetWorkflowsOutput = sys_db.get_workflows(input)
65
- infos: List[WorkflowInformation] = []
83
+ infos: List[WorkflowStatus] = []
66
84
  for workflow_id in output.workflow_uuids:
67
85
  info = get_workflow(sys_db, workflow_id, request) # Call the method for each ID
68
86
  if info is not None:
@@ -73,16 +91,16 @@ def list_workflows(
73
91
  def list_queued_workflows(
74
92
  sys_db: SystemDatabase,
75
93
  *,
76
- limit: Optional[int] = None,
77
- start_time: Optional[str] = None,
78
- end_time: Optional[str] = None,
79
94
  queue_name: Optional[str] = None,
80
95
  status: Optional[str] = None,
96
+ start_time: Optional[str] = None,
97
+ end_time: Optional[str] = None,
81
98
  name: Optional[str] = None,
82
- request: bool = False,
99
+ limit: Optional[int] = None,
83
100
  offset: Optional[int] = None,
84
101
  sort_desc: bool = False,
85
- ) -> List[WorkflowInformation]:
102
+ request: bool = False,
103
+ ) -> List[WorkflowStatus]:
86
104
  input: GetQueuedWorkflowsInput = {
87
105
  "queue_name": queue_name,
88
106
  "start_time": start_time,
@@ -94,7 +112,7 @@ def list_queued_workflows(
94
112
  "sort_desc": sort_desc,
95
113
  }
96
114
  output: GetWorkflowsOutput = sys_db.get_queued_workflows(input)
97
- infos: List[WorkflowInformation] = []
115
+ infos: List[WorkflowStatus] = []
98
116
  for workflow_id in output.workflow_uuids:
99
117
  info = get_workflow(sys_db, workflow_id, request) # Call the method for each ID
100
118
  if info is not None:
@@ -103,52 +121,55 @@ def list_queued_workflows(
103
121
 
104
122
 
105
123
  def get_workflow(
106
- sys_db: SystemDatabase, workflowUUID: str, getRequest: bool
107
- ) -> Optional[WorkflowInformation]:
124
+ sys_db: SystemDatabase, workflow_id: str, get_request: bool
125
+ ) -> Optional[WorkflowStatus]:
108
126
 
109
- info = sys_db.get_workflow_status(workflowUUID)
110
- if info is None:
127
+ internal_status = sys_db.get_workflow_status(workflow_id)
128
+ if internal_status is None:
111
129
  return None
112
130
 
113
- winfo = WorkflowInformation()
114
-
115
- winfo.workflow_id = workflowUUID
116
- winfo.status = info["status"]
117
- winfo.workflow_name = info["name"]
118
- winfo.workflow_class_name = info["class_name"]
119
- winfo.workflow_config_name = info["config_name"]
120
- winfo.authenticated_user = info["authenticated_user"]
121
- winfo.assumed_role = info["assumed_role"]
122
- winfo.authenticated_roles = info["authenticated_roles"]
123
- winfo.request = info["request"]
124
- winfo.created_at = info["created_at"]
125
- winfo.updated_at = info["updated_at"]
126
- winfo.queue_name = info["queue_name"]
127
- winfo.executor_id = info["executor_id"]
128
- winfo.app_version = info["app_version"]
129
- winfo.app_id = info["app_id"]
130
- winfo.recovery_attempts = info["recovery_attempts"]
131
-
132
- # no input field
133
- input_data = sys_db.get_workflow_inputs(workflowUUID)
131
+ info = WorkflowStatus()
132
+
133
+ info.workflow_id = workflow_id
134
+ info.status = internal_status["status"]
135
+ info.name = internal_status["name"]
136
+ info.class_name = internal_status["class_name"]
137
+ info.config_name = internal_status["config_name"]
138
+ info.authenticated_user = internal_status["authenticated_user"]
139
+ info.assumed_role = internal_status["assumed_role"]
140
+ info.authenticated_roles = (
141
+ json.loads(internal_status["authenticated_roles"])
142
+ if internal_status["authenticated_roles"] is not None
143
+ else None
144
+ )
145
+ info.request = internal_status["request"]
146
+ info.created_at = internal_status["created_at"]
147
+ info.updated_at = internal_status["updated_at"]
148
+ info.queue_name = internal_status["queue_name"]
149
+ info.executor_id = internal_status["executor_id"]
150
+ info.app_version = internal_status["app_version"]
151
+ info.app_id = internal_status["app_id"]
152
+ info.recovery_attempts = internal_status["recovery_attempts"]
153
+
154
+ input_data = sys_db.get_workflow_inputs(workflow_id)
134
155
  if input_data is not None:
135
- winfo.input = input_data
156
+ info.input = input_data
136
157
 
137
- if info.get("status") == "SUCCESS":
138
- result = sys_db.await_workflow_result(workflowUUID)
139
- winfo.output = result
140
- elif info.get("status") == "ERROR":
158
+ if internal_status.get("status") == "SUCCESS":
159
+ result = sys_db.await_workflow_result(workflow_id)
160
+ info.output = result
161
+ elif internal_status.get("status") == "ERROR":
141
162
  try:
142
- sys_db.await_workflow_result(workflowUUID)
163
+ sys_db.await_workflow_result(workflow_id)
143
164
  except Exception as e:
144
- winfo.error = str(e)
165
+ info.error = e
145
166
 
146
- if not getRequest:
147
- winfo.request = None
167
+ if not get_request:
168
+ info.request = None
148
169
 
149
- return winfo
170
+ return info
150
171
 
151
172
 
152
- def list_workflow_steps(sys_db: SystemDatabase, workflow_uuid: str) -> List[StepInfo]:
153
- output = sys_db.get_workflow_steps(workflow_uuid)
173
+ def list_workflow_steps(sys_db: SystemDatabase, workflow_id: str) -> List[StepInfo]:
174
+ output = sys_db.get_workflow_steps(workflow_id)
154
175
  return output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.25.0a7
3
+ Version: 0.25.0a8
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,8 +1,8 @@
1
- dbos-0.25.0a7.dist-info/METADATA,sha256=NKp4haVmpJodWhsXeZAnTc6eYquHOJP1PCbw1lyQZOw,5521
2
- dbos-0.25.0a7.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- dbos-0.25.0a7.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-0.25.0a7.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
- dbos/__init__.py,sha256=uq9LP5uY96kIS9N0yKqlvDwADmtg_Hl30uSUhyuUr-4,754
1
+ dbos-0.25.0a8.dist-info/METADATA,sha256=dmBQbDy6O8vsnqvCZnwOA9PqTACcu5N-FD9iM8giQhU,5521
2
+ dbos-0.25.0a8.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ dbos-0.25.0a8.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-0.25.0a8.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
+ dbos/__init__.py,sha256=2Ur8QyNElSVn7CeL9Ovek2Zsye8A_ZCyjb9djF-N4A4,785
6
6
  dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
7
7
  dbos/_admin_server.py,sha256=7kguOf9jEt4vg9LO-QJdh4jYddp6Uqtrt14gh7mKA2Y,6387
8
8
  dbos/_app_db.py,sha256=4EGrYL14rVx96TXn34hoibN9ltf4-2DKcj6nd-HvBxA,6262
@@ -10,13 +10,13 @@ dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
10
10
  dbos/_cloudutils/authentication.py,sha256=V0fCWQN9stCkhbuuxgPTGpvuQcDqfU3KAxPAh01vKW4,5007
11
11
  dbos/_cloudutils/cloudutils.py,sha256=YC7jGsIopT0KveLsqbRpQk2KlRBk-nIRC_UCgep4f3o,7797
12
12
  dbos/_cloudutils/databases.py,sha256=_shqaqSvhY4n2ScgQ8IP5PDZvzvcx3YBKV8fj-cxhSY,8543
13
- dbos/_conductor/conductor.py,sha256=udu8atyYYrs5bCfokie9ttjHlIg0FMo1p0-24IoneFs,15252
14
- dbos/_conductor/protocol.py,sha256=MWY3SuIeY6GN2Vg3wQjcxiT0d4zz4ccGzdEkmYYJ6t0,5633
15
- dbos/_context.py,sha256=Qxb0BLcFQh_sO2akOy-pWD48DjTiojj6twoAK_d2S4g,17821
16
- dbos/_core.py,sha256=0S5EWGO72aMXAvNTrx5bO5nwmMfp21sAaRQshXhaxzs,43598
13
+ dbos/_conductor/conductor.py,sha256=oDlRpGxLT-uLDjEX1JwTwcJiH2FzDsrOTtnrkt_X_1U,15253
14
+ dbos/_conductor/protocol.py,sha256=Bj4dhbAhAfj4IrMs_8OYJda2SdjAv1lcePXXG1MejPM,5800
15
+ dbos/_context.py,sha256=3He4w46OTFbR7h8U1MLcdaU10wNyIPBSRqzLkdggv7U,19368
16
+ dbos/_core.py,sha256=ZBzclYK0fRw-Q3h1flTOUBkWgPQtplw94RygPWZOo8Q,43321
17
17
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
18
18
  dbos/_db_wizard.py,sha256=VnMa6OL87Lc-XPDD1RnXp8NjsJE8YgiQLj3wtWAXp-8,8252
19
- dbos/_dbos.py,sha256=mh2t4jpP-sPGbncaq9vyvHkcC76fkHe1-Xtx2QtcWwk,45911
19
+ dbos/_dbos.py,sha256=0w2JAwT4eqKx7QEZPMge7FcJUDfN26Hc1Y9E_ou_HJY,45524
20
20
  dbos/_dbos_config.py,sha256=7Qm3FARP3lTKZS0gSxDHLbpaDCT30GzfyERxfCde4bc,21566
21
21
  dbos/_debug.py,sha256=mmgvLkqlrljMBBow9wk01PPur9kUf2rI_11dTJXY4gw,1822
22
22
  dbos/_error.py,sha256=B6Y9XLS1f6yrawxB2uAEYFMxFwk9BHhdxPNddKco-Fw,5399
@@ -35,7 +35,7 @@ dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py,sha256=8PyFi8rd6CN-m
35
35
  dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4hGBC02Ptng1715roTjY3xiyzZU4,729
36
36
  dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
37
37
  dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256=rwfMdqL6LYI4RwtxmB-OJbRKP_wQi-OA8fsmxley-OM,1042
38
- dbos/_outcome.py,sha256=FDMgWVjZ06vm9xO-38H17mTqBImUYQxgKs_bDCSIAhE,6648
38
+ dbos/_outcome.py,sha256=EXxBg4jXCVJsByDQ1VOCIedmbeq_03S6d-p1vqQrLFU,6810
39
39
  dbos/_queue.py,sha256=OWUtbBAqdkDAArFWkwlF8STxykV4iQmrZxrF-_lavh4,3341
40
40
  dbos/_recovery.py,sha256=4KyZb0XJEUGH7ekYT1kpx38i6y5vygPeH75Ta7RZjYo,2596
41
41
  dbos/_registrations.py,sha256=_zy6k944Ll8QwqU12Kr3OP23ukVtm8axPNN1TS_kJRc,6717
@@ -46,7 +46,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  dbos/_schemas/application_database.py,sha256=KeyoPrF7hy_ODXV7QNike_VFSD74QBRfQ76D7QyE9HI,966
47
47
  dbos/_schemas/system_database.py,sha256=W9eSpL7SZzQkxcEZ4W07BOcwkkDr35b9oCjUOgfHWek,5336
48
48
  dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
49
- dbos/_sys_db.py,sha256=UXLq_N9qNNDpf_9fln-eBBXwFEjOC94kfbC2ch5HjUU,67813
49
+ dbos/_sys_db.py,sha256=vA8G_r2OpX3lQr0JbxRkpIe6A5Cs7ED1wqVV1BosSf0,66759
50
50
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
51
51
  dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  dbos/_templates/dbos-db-starter/__package/main.py,sha256=nJMN3ZD2lmwg4Dcgmiwqc-tQGuCJuJal2Xl85iA277U,2453
@@ -59,11 +59,11 @@ dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py,sh
59
59
  dbos/_templates/dbos-db-starter/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
60
60
  dbos/_tracer.py,sha256=dFDSFlta-rfA3-ahIRLYwnnoAOmlavdxAGllqwFgnCA,2440
61
61
  dbos/_utils.py,sha256=wjOJzxN66IzL9p4dwcEmQACRQah_V09G6mJI2exQfOM,155
62
- dbos/_workflow_commands.py,sha256=A82fbMG8V1nkfpfjsXm94lx3P6nNIRA7rzO7trJ-4Bo,4865
62
+ dbos/_workflow_commands.py,sha256=SYp2khc9RSf6tjllG9CqT1zjBQnFTFq33ePXpvmRwME,5892
63
63
  dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
64
64
  dbos/cli/_template_init.py,sha256=-WW3kbq0W_Tq4WbMqb1UGJG3xvJb3woEY5VspG95Srk,2857
65
65
  dbos/cli/cli.py,sha256=ut47q-R6A423je0zvBTEgwdxENagaKKoyIvyTeACFIU,15977
66
66
  dbos/dbos-config.schema.json,sha256=HtF_njVTGHLdzBGZ4OrGQz3qbPPT0Go-iwd1PgFVTNg,5847
67
67
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
68
68
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
69
- dbos-0.25.0a7.dist-info/RECORD,,
69
+ dbos-0.25.0a8.dist-info/RECORD,,