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.
- {dbos-0.26.0a7 → dbos-0.26.0a8}/PKG-INFO +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_app_db.py +14 -5
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_conductor/conductor.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_core.py +12 -20
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_dbos.py +61 -67
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_debug.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_error.py +13 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_sys_db.py +120 -55
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_workflow_commands.py +3 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/pyproject.toml +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/queuedworkflow.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_classdecorators.py +8 -8
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_client.py +2 -2
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_dbos.py +8 -8
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_debug.py +2 -2
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_failures.py +81 -38
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_fastapi.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_flask.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_queue.py +8 -6
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_scheduler.py +1 -1
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_workflow_introspection.py +34 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_workflow_management.py +2 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/LICENSE +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/README.md +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/__init__.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/__main__.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_admin_server.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_classproperty.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_client.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/authentication.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/cloudutils.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_cloudutils/databases.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_conductor/protocol.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_context.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_croniter.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_db_wizard.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_dbos_config.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_fastapi.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_flask.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_kafka.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_kafka_message.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_logger.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/env.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/script.py.mako +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_outcome.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_queue.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_recovery.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_registrations.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_request.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_roles.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_scheduler.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/__init__.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/application_database.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_schemas/system_database.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_serialization.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_tracer.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/_utils.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/_github_init.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/_template_init.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/cli/cli.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/dbos-config.schema.json +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/dbos/py.typed +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/__init__.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/atexit_no_ctor.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/atexit_no_launch.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/classdefs.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/client_collateral.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/client_worker.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/conftest.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/more_classdefs.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_admin_server.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_async.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_concurrency.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_config.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_croniter.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_dbwizard.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_docker_secrets.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_fastapi_roles.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_kafka.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_outcome.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_package.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_schema_migration.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_singleton.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_spans.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/tests/test_sqlalchemy.py +0 -0
- {dbos-0.26.0a7 → dbos-0.26.0a8}/version/__init__.py +0 -0
@@ -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,
|
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 ==
|
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":
|
189
|
-
"error":
|
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.
|
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
|
-
|
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
|
-
|
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":
|
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
|
-
|
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":
|
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
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
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
|
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
|
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
|
-
|
959
|
-
|
960
|
-
|
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
|
-
|
966
|
-
|
967
|
-
|
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
|
-
|
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
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
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
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
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.
|
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
|
+
)
|