dbos 0.26.0a7__tar.gz → 0.26.0a8__tar.gz

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.
Files changed (104) hide show
  1. {dbos-0.26.0a7 → dbos-0.26.0a8}/PKG-INFO +1 -1
  2. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_app_db.py +14 -5
  3. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_conductor/conductor.py +1 -1
  4. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_core.py +12 -20
  5. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_dbos.py +61 -67
  6. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_debug.py +1 -1
  7. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_error.py +13 -0
  8. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_sys_db.py +120 -55
  9. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_workflow_commands.py +3 -0
  10. {dbos-0.26.0a7 → dbos-0.26.0a8}/pyproject.toml +1 -1
  11. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/queuedworkflow.py +1 -1
  12. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_classdecorators.py +8 -8
  13. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_client.py +2 -2
  14. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_dbos.py +8 -8
  15. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_debug.py +2 -2
  16. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_failures.py +81 -38
  17. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_fastapi.py +1 -1
  18. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_flask.py +1 -1
  19. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_queue.py +8 -6
  20. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_scheduler.py +1 -1
  21. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_workflow_introspection.py +34 -0
  22. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_workflow_management.py +2 -0
  23. {dbos-0.26.0a7 → dbos-0.26.0a8}/LICENSE +0 -0
  24. {dbos-0.26.0a7 → dbos-0.26.0a8}/README.md +0 -0
  25. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/__init__.py +0 -0
  26. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/__main__.py +0 -0
  27. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_admin_server.py +0 -0
  28. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_classproperty.py +0 -0
  29. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_client.py +0 -0
  30. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/authentication.py +0 -0
  31. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/cloudutils.py +0 -0
  32. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/databases.py +0 -0
  33. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_conductor/protocol.py +0 -0
  34. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_context.py +0 -0
  35. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_croniter.py +0 -0
  36. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_db_wizard.py +0 -0
  37. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_dbos_config.py +0 -0
  38. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_fastapi.py +0 -0
  39. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_flask.py +0 -0
  40. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_kafka.py +0 -0
  41. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_kafka_message.py +0 -0
  42. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_logger.py +0 -0
  43. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/env.py +0 -0
  44. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/script.py.mako +0 -0
  45. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  46. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  47. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  48. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  49. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  50. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  51. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  52. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
  53. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_outcome.py +0 -0
  54. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_queue.py +0 -0
  55. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_recovery.py +0 -0
  56. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_registrations.py +0 -0
  57. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_request.py +0 -0
  58. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_roles.py +0 -0
  59. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_scheduler.py +0 -0
  60. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/__init__.py +0 -0
  61. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/application_database.py +0 -0
  62. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/system_database.py +0 -0
  63. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_serialization.py +0 -0
  64. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
  65. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  66. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  67. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  68. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  69. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  70. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  71. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  72. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  73. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  74. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_tracer.py +0 -0
  75. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_utils.py +0 -0
  76. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/_github_init.py +0 -0
  77. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/_template_init.py +0 -0
  78. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/cli.py +0 -0
  79. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/dbos-config.schema.json +0 -0
  80. {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/py.typed +0 -0
  81. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/__init__.py +0 -0
  82. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/atexit_no_ctor.py +0 -0
  83. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/atexit_no_launch.py +0 -0
  84. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/classdefs.py +0 -0
  85. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/client_collateral.py +0 -0
  86. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/client_worker.py +0 -0
  87. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/conftest.py +0 -0
  88. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/more_classdefs.py +0 -0
  89. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_admin_server.py +0 -0
  90. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_async.py +0 -0
  91. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_concurrency.py +0 -0
  92. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_config.py +0 -0
  93. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_croniter.py +0 -0
  94. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_dbwizard.py +0 -0
  95. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_docker_secrets.py +0 -0
  96. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_fastapi_roles.py +0 -0
  97. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_kafka.py +0 -0
  98. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_outcome.py +0 -0
  99. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_package.py +0 -0
  100. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_schema_migration.py +0 -0
  101. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_singleton.py +0 -0
  102. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_spans.py +0 -0
  103. {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_sqlalchemy.py +0 -0
  104. {dbos-0.26.0a7 → dbos-0.26.0a8}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.26.0a7
3
+ Version: 0.26.0a8
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session, sessionmaker
8
8
 
9
9
  from . import _serialization
10
10
  from ._dbos_config import ConfigFile, DatabaseConfig
11
- from ._error import DBOSWorkflowConflictIDError
11
+ from ._error import DBOSUnexpectedStepError, DBOSWorkflowConflictIDError
12
12
  from ._schemas.application_database import ApplicationSchema
13
13
  from ._sys_db import StepInfo
14
14
 
@@ -171,22 +171,31 @@ class ApplicationDatabase:
171
171
 
172
172
  @staticmethod
173
173
  def check_transaction_execution(
174
- session: Session, workflow_uuid: str, function_id: int
174
+ session: Session, workflow_id: str, function_id: int, function_name: str
175
175
  ) -> Optional[RecordedResult]:
176
176
  rows = session.execute(
177
177
  sa.select(
178
178
  ApplicationSchema.transaction_outputs.c.output,
179
179
  ApplicationSchema.transaction_outputs.c.error,
180
+ ApplicationSchema.transaction_outputs.c.function_name,
180
181
  ).where(
181
- ApplicationSchema.transaction_outputs.c.workflow_uuid == workflow_uuid,
182
+ ApplicationSchema.transaction_outputs.c.workflow_uuid == workflow_id,
182
183
  ApplicationSchema.transaction_outputs.c.function_id == function_id,
183
184
  )
184
185
  ).all()
185
186
  if len(rows) == 0:
186
187
  return None
188
+ output, error, recorded_function_name = rows[0][0], rows[0][1], rows[0][2]
189
+ if function_name != recorded_function_name:
190
+ raise DBOSUnexpectedStepError(
191
+ workflow_id=workflow_id,
192
+ step_id=function_id,
193
+ expected_name=function_name,
194
+ recorded_name=recorded_function_name,
195
+ )
187
196
  result: RecordedResult = {
188
- "output": rows[0][0],
189
- "error": rows[0][1],
197
+ "output": output,
198
+ "error": error,
190
199
  }
191
200
  return result
192
201
 
@@ -67,7 +67,7 @@ class ConductorWebsocket(threading.Thread):
67
67
  recovery_message = p.RecoveryRequest.from_json(message)
68
68
  success = True
69
69
  try:
70
- self.dbos.recover_pending_workflows(
70
+ self.dbos._recover_pending_workflows(
71
71
  recovery_message.executor_ids
72
72
  )
73
73
  except Exception as e:
@@ -52,6 +52,7 @@ from ._error import (
52
52
  DBOSMaxStepRetriesExceeded,
53
53
  DBOSNonExistentWorkflowError,
54
54
  DBOSRecoveryError,
55
+ DBOSUnexpectedStepError,
55
56
  DBOSWorkflowCancelledError,
56
57
  DBOSWorkflowConflictIDError,
57
58
  DBOSWorkflowFunctionNotFoundError,
@@ -783,7 +784,7 @@ def decorate_transaction(
783
784
  ) -> Callable[[F], F]:
784
785
  def decorator(func: F) -> F:
785
786
 
786
- transactionName = func.__qualname__
787
+ transaction_name = func.__qualname__
787
788
 
788
789
  def invoke_tx(*args: Any, **kwargs: Any) -> Any:
789
790
  if dbosreg.dbos is None:
@@ -791,13 +792,14 @@ def decorate_transaction(
791
792
  f"Function {func.__name__} invoked before DBOS initialized"
792
793
  )
793
794
 
795
+ dbos = dbosreg.dbos
794
796
  ctx = assert_current_dbos_context()
795
- if dbosreg.is_workflow_cancelled(ctx.workflow_id):
797
+ status = dbos._sys_db.get_workflow_status(ctx.workflow_id)
798
+ if status and status["status"] == WorkflowStatusString.CANCELLED.value:
796
799
  raise DBOSWorkflowCancelledError(
797
800
  f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {func.__name__}."
798
801
  )
799
802
 
800
- dbos = dbosreg.dbos
801
803
  with dbos._app_db.sessionmaker() as session:
802
804
  attributes: TracedAttributes = {
803
805
  "name": func.__name__,
@@ -813,18 +815,12 @@ def decorate_transaction(
813
815
  "txn_snapshot": "", # TODO: add actual snapshot
814
816
  "executor_id": None,
815
817
  "txn_id": None,
816
- "function_name": transactionName,
818
+ "function_name": transaction_name,
817
819
  }
818
820
  retry_wait_seconds = 0.001
819
821
  backoff_factor = 1.5
820
822
  max_retry_wait_seconds = 2.0
821
823
  while True:
822
-
823
- if dbosreg.is_workflow_cancelled(ctx.workflow_id):
824
- raise DBOSWorkflowCancelledError(
825
- f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {func.__name__}."
826
- )
827
-
828
824
  has_recorded_error = False
829
825
  txn_error: Optional[Exception] = None
830
826
  try:
@@ -841,6 +837,7 @@ def decorate_transaction(
841
837
  session,
842
838
  ctx.workflow_id,
843
839
  ctx.function_id,
840
+ transaction_name,
844
841
  )
845
842
  )
846
843
  if dbos.debug_mode and recorded_output is None:
@@ -904,6 +901,8 @@ def decorate_transaction(
904
901
  )
905
902
  txn_error = invalid_request_error
906
903
  raise
904
+ except DBOSUnexpectedStepError:
905
+ raise
907
906
  except Exception as error:
908
907
  txn_error = error
909
908
  raise
@@ -969,7 +968,7 @@ def decorate_step(
969
968
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
970
969
  def decorator(func: Callable[P, R]) -> Callable[P, R]:
971
970
 
972
- stepName = func.__qualname__
971
+ step_name = func.__qualname__
973
972
 
974
973
  def invoke_step(*args: Any, **kwargs: Any) -> Any:
975
974
  if dbosreg.dbos is None:
@@ -983,13 +982,6 @@ def decorate_step(
983
982
  "operationType": OperationType.STEP.value,
984
983
  }
985
984
 
986
- # Check if the workflow is cancelled
987
- ctx = assert_current_dbos_context()
988
- if dbosreg.is_workflow_cancelled(ctx.workflow_id):
989
- raise DBOSWorkflowCancelledError(
990
- f"Workflow {ctx.workflow_id} is cancelled. Aborting step {func.__name__}."
991
- )
992
-
993
985
  attempts = max_attempts if retries_allowed else 1
994
986
  max_retry_interval_seconds: float = 3600 # 1 Hour
995
987
 
@@ -1017,7 +1009,7 @@ def decorate_step(
1017
1009
  step_output: OperationResultInternal = {
1018
1010
  "workflow_uuid": ctx.workflow_id,
1019
1011
  "function_id": ctx.function_id,
1020
- "function_name": stepName,
1012
+ "function_name": step_name,
1021
1013
  "output": None,
1022
1014
  "error": None,
1023
1015
  }
@@ -1035,7 +1027,7 @@ def decorate_step(
1035
1027
  def check_existing_result() -> Union[NoResult, R]:
1036
1028
  ctx = assert_current_dbos_context()
1037
1029
  recorded_output = dbos._sys_db.check_operation_execution(
1038
- ctx.workflow_id, ctx.function_id
1030
+ ctx.workflow_id, ctx.function_id, step_name
1039
1031
  )
1040
1032
  if dbos.debug_mode and recorded_output is None:
1041
1033
  raise DBOSException("Step output not found in debug mode")
@@ -166,7 +166,6 @@ class DBOSRegistry:
166
166
  self.pollers: list[RegisteredJob] = []
167
167
  self.dbos: Optional[DBOS] = None
168
168
  self.config: Optional[ConfigFile] = None
169
- self.workflow_cancelled_map: dict[str, bool] = {}
170
169
 
171
170
  def register_wf_function(self, name: str, wrapped_func: F, functype: str) -> None:
172
171
  if name in self.function_type_map:
@@ -215,15 +214,6 @@ class DBOSRegistry:
215
214
  else:
216
215
  self.instance_info_map[fn] = inst
217
216
 
218
- def cancel_workflow(self, workflow_id: str) -> None:
219
- self.workflow_cancelled_map[workflow_id] = True
220
-
221
- def is_workflow_cancelled(self, workflow_id: str) -> bool:
222
- return self.workflow_cancelled_map.get(workflow_id, False)
223
-
224
- def clear_workflow_cancelled(self, workflow_id: str) -> None:
225
- self.workflow_cancelled_map.pop(workflow_id, None)
226
-
227
217
  def compute_app_version(self) -> str:
228
218
  """
229
219
  An application's version is computed from a hash of the source of its workflows.
@@ -740,32 +730,11 @@ class DBOS:
740
730
  @classmethod
741
731
  def get_workflow_status(cls, workflow_id: str) -> Optional[WorkflowStatus]:
742
732
  """Return the status of a workflow execution."""
743
- sys_db = _get_dbos_instance()._sys_db
744
- ctx = get_local_dbos_context()
745
- if ctx and ctx.is_within_workflow():
746
- ctx.function_id += 1
747
- res = sys_db.check_operation_execution(ctx.workflow_id, ctx.function_id)
748
- if res is not None:
749
- if res["output"]:
750
- resstat: WorkflowStatus = _serialization.deserialize(res["output"])
751
- return resstat
752
- else:
753
- raise DBOSException(
754
- "Workflow status record not found. This should not happen! \033[1m Hint: Check if your workflow is deterministic.\033[0m"
755
- )
756
- stat = get_workflow(_get_dbos_instance()._sys_db, workflow_id, True)
757
-
758
- if ctx and ctx.is_within_workflow():
759
- sys_db.record_operation_result(
760
- {
761
- "workflow_uuid": ctx.workflow_id,
762
- "function_id": ctx.function_id,
763
- "function_name": "DBOS.getStatus",
764
- "output": _serialization.serialize(stat),
765
- "error": None,
766
- }
767
- )
768
- return stat
733
+
734
+ def fn() -> Optional[WorkflowStatus]:
735
+ return get_workflow(_get_dbos_instance()._sys_db, workflow_id, True)
736
+
737
+ return _get_dbos_instance()._sys_db.call_function_as_step(fn, "DBOS.getStatus")
769
738
 
770
739
  @classmethod
771
740
  async def get_workflow_status_async(
@@ -941,12 +910,12 @@ class DBOS:
941
910
  )
942
911
 
943
912
  @classmethod
944
- def execute_workflow_id(cls, workflow_id: str) -> WorkflowHandle[Any]:
913
+ def _execute_workflow_id(cls, workflow_id: str) -> WorkflowHandle[Any]:
945
914
  """Execute a workflow by ID (for recovery)."""
946
915
  return execute_workflow_by_id(_get_dbos_instance(), workflow_id)
947
916
 
948
917
  @classmethod
949
- def recover_pending_workflows(
918
+ def _recover_pending_workflows(
950
919
  cls, executor_ids: List[str] = ["local"]
951
920
  ) -> List[WorkflowHandle[Any]]:
952
921
  """Find all PENDING workflows and execute them."""
@@ -955,22 +924,37 @@ class DBOS:
955
924
  @classmethod
956
925
  def cancel_workflow(cls, workflow_id: str) -> None:
957
926
  """Cancel a workflow by ID."""
958
- dbos_logger.info(f"Cancelling workflow: {workflow_id}")
959
- _get_dbos_instance()._sys_db.cancel_workflow(workflow_id)
960
- _get_or_create_dbos_registry().cancel_workflow(workflow_id)
927
+
928
+ def fn() -> None:
929
+ dbos_logger.info(f"Cancelling workflow: {workflow_id}")
930
+ _get_dbos_instance()._sys_db.cancel_workflow(workflow_id)
931
+
932
+ return _get_dbos_instance()._sys_db.call_function_as_step(
933
+ fn, "DBOS.cancelWorkflow"
934
+ )
961
935
 
962
936
  @classmethod
963
937
  def resume_workflow(cls, workflow_id: str) -> WorkflowHandle[Any]:
964
938
  """Resume a workflow by ID."""
965
- dbos_logger.info(f"Resuming workflow: {workflow_id}")
966
- _get_dbos_instance()._sys_db.resume_workflow(workflow_id)
967
- _get_or_create_dbos_registry().clear_workflow_cancelled(workflow_id)
939
+
940
+ def fn() -> None:
941
+ dbos_logger.info(f"Resuming workflow: {workflow_id}")
942
+ _get_dbos_instance()._sys_db.resume_workflow(workflow_id)
943
+
944
+ _get_dbos_instance()._sys_db.call_function_as_step(fn, "DBOS.resumeWorkflow")
968
945
  return cls.retrieve_workflow(workflow_id)
969
946
 
970
947
  @classmethod
971
948
  def restart_workflow(cls, workflow_id: str) -> WorkflowHandle[Any]:
972
949
  """Restart a workflow with a new workflow ID"""
973
- forked_workflow_id = _get_dbos_instance()._sys_db.fork_workflow(workflow_id)
950
+
951
+ def fn() -> str:
952
+ dbos_logger.info(f"Restarting workflow: {workflow_id}")
953
+ return _get_dbos_instance()._sys_db.fork_workflow(workflow_id)
954
+
955
+ forked_workflow_id = _get_dbos_instance()._sys_db.call_function_as_step(
956
+ fn, "DBOS.restartWorkflow"
957
+ )
974
958
  return cls.retrieve_workflow(forked_workflow_id)
975
959
 
976
960
  @classmethod
@@ -988,18 +972,23 @@ class DBOS:
988
972
  offset: Optional[int] = None,
989
973
  sort_desc: bool = False,
990
974
  ) -> List[WorkflowStatus]:
991
- return list_workflows(
992
- _get_dbos_instance()._sys_db,
993
- workflow_ids=workflow_ids,
994
- status=status,
995
- start_time=start_time,
996
- end_time=end_time,
997
- name=name,
998
- app_version=app_version,
999
- user=user,
1000
- limit=limit,
1001
- offset=offset,
1002
- sort_desc=sort_desc,
975
+ def fn() -> List[WorkflowStatus]:
976
+ return list_workflows(
977
+ _get_dbos_instance()._sys_db,
978
+ workflow_ids=workflow_ids,
979
+ status=status,
980
+ start_time=start_time,
981
+ end_time=end_time,
982
+ name=name,
983
+ app_version=app_version,
984
+ user=user,
985
+ limit=limit,
986
+ offset=offset,
987
+ sort_desc=sort_desc,
988
+ )
989
+
990
+ return _get_dbos_instance()._sys_db.call_function_as_step(
991
+ fn, "DBOS.listWorkflows"
1003
992
  )
1004
993
 
1005
994
  @classmethod
@@ -1015,16 +1004,21 @@ class DBOS:
1015
1004
  offset: Optional[int] = None,
1016
1005
  sort_desc: bool = False,
1017
1006
  ) -> List[WorkflowStatus]:
1018
- return list_queued_workflows(
1019
- _get_dbos_instance()._sys_db,
1020
- queue_name=queue_name,
1021
- status=status,
1022
- start_time=start_time,
1023
- end_time=end_time,
1024
- name=name,
1025
- limit=limit,
1026
- offset=offset,
1027
- sort_desc=sort_desc,
1007
+ def fn() -> List[WorkflowStatus]:
1008
+ return list_queued_workflows(
1009
+ _get_dbos_instance()._sys_db,
1010
+ queue_name=queue_name,
1011
+ status=status,
1012
+ start_time=start_time,
1013
+ end_time=end_time,
1014
+ name=name,
1015
+ limit=limit,
1016
+ offset=offset,
1017
+ sort_desc=sort_desc,
1018
+ )
1019
+
1020
+ return _get_dbos_instance()._sys_db.call_function_as_step(
1021
+ fn, "DBOS.listQueuedWorkflows"
1028
1022
  )
1029
1023
 
1030
1024
  @classproperty
@@ -28,7 +28,7 @@ def debug_workflow(workflow_id: str, entrypoint: Union[str, PythonModule]) -> No
28
28
 
29
29
  DBOS.logger.info(f"Debugging workflow {workflow_id}...")
30
30
  DBOS.launch(debug_mode=True)
31
- handle = DBOS.execute_workflow_id(workflow_id)
31
+ handle = DBOS._execute_workflow_id(workflow_id)
32
32
  handle.get_result()
33
33
  DBOS.logger.info("Workflow Debugging complete. Exiting process.")
34
34
 
@@ -37,6 +37,7 @@ class DBOSErrorCode(Enum):
37
37
  NotAuthorized = 8
38
38
  ConflictingWorkflowError = 9
39
39
  WorkflowCancelled = 10
40
+ UnexpectedStep = 11
40
41
  ConflictingRegistrationError = 25
41
42
 
42
43
 
@@ -155,3 +156,15 @@ class DBOSConflictingRegistrationError(DBOSException):
155
156
  f"Operation (Name: {name}) is already registered with a conflicting function type",
156
157
  dbos_error_code=DBOSErrorCode.ConflictingRegistrationError.value,
157
158
  )
159
+
160
+
161
+ class DBOSUnexpectedStepError(DBOSException):
162
+ """Exception raised when a step has an unexpected recorded name."""
163
+
164
+ def __init__(
165
+ self, workflow_id: str, step_id: int, expected_name: str, recorded_name: str
166
+ ) -> None:
167
+ super().__init__(
168
+ f"During execution of workflow {workflow_id} step {step_id}, function {recorded_name} was recorded when {expected_name} was expected. Check that your workflow is deterministic.",
169
+ dbos_error_code=DBOSErrorCode.UnexpectedStep.value,
170
+ )