dbos 0.25.0a7__tar.gz → 0.25.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.
Potentially problematic release.
This version of dbos might be problematic. Click here for more details.
- {dbos-0.25.0a7 → dbos-0.25.0a8}/PKG-INFO +1 -1
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/__init__.py +2 -1
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_conductor/conductor.py +1 -1
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_conductor/protocol.py +13 -7
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_context.py +45 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_core.py +2 -8
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_dbos.py +93 -93
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_outcome.py +6 -2
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_sys_db.py +1 -27
- dbos-0.25.0a8/dbos/_workflow_commands.py +175 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/pyproject.toml +1 -1
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_admin_server.py +3 -3
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_dbos.py +23 -2
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_failures.py +24 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_workflow_cmds.py +49 -73
- dbos-0.25.0a7/dbos/_workflow_commands.py +0 -154
- {dbos-0.25.0a7 → dbos-0.25.0a8}/LICENSE +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/README.md +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/__main__.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_admin_server.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_app_db.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_classproperty.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_cloudutils/authentication.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_cloudutils/cloudutils.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_cloudutils/databases.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_croniter.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_db_wizard.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_dbos_config.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_debug.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_error.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_fastapi.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_flask.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_kafka.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_kafka_message.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_logger.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/env.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/script.py.mako +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_queue.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_recovery.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_registrations.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_request.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_roles.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_scheduler.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_schemas/__init__.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_schemas/application_database.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_schemas/system_database.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_serialization.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_tracer.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/_utils.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/cli/_github_init.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/cli/_template_init.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/cli/cli.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/dbos-config.schema.json +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/dbos/py.typed +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/__init__.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/atexit_no_ctor.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/atexit_no_launch.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/classdefs.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/conftest.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/more_classdefs.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/queuedworkflow.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_async.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_classdecorators.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_concurrency.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_config.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_croniter.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_dbwizard.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_debug.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_fastapi.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_fastapi_roles.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_flask.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_kafka.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_outcome.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_package.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_queue.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_scheduler.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_schema_migration.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_singleton.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_spans.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_sqlalchemy.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/tests/test_workflow_cancel.py +0 -0
- {dbos-0.25.0a7 → dbos-0.25.0a8}/version/__init__.py +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
156
|
-
WorkflowClassName=info.
|
|
157
|
-
WorkflowConfigName=info.
|
|
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=
|
|
166
|
+
AuthenticatedRoles=roles_str,
|
|
161
167
|
Input=inputs_str,
|
|
162
168
|
Output=outputs_str,
|
|
163
169
|
Request=request_str,
|
|
164
|
-
Error=
|
|
170
|
+
Error=error_str,
|
|
165
171
|
CreatedAt=created_at_str,
|
|
166
172
|
UpdatedAt=updated_at_str,
|
|
167
173
|
QueueName=info.queue_name,
|
|
@@ -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
|
|
@@ -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(
|
|
@@ -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
|
-
|
|
736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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[
|
|
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
|
|