dbos 2.0.0__tar.gz → 2.1.0__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-2.0.0 → dbos-2.1.0}/PKG-INFO +1 -1
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_core.py +16 -13
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_debouncer.py +5 -1
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_scheduler.py +24 -14
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_sys_db.py +34 -15
- {dbos-2.0.0 → dbos-2.1.0}/pyproject.toml +1 -1
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_dbos.py +31 -15
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_failures.py +31 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_scheduler.py +7 -7
- {dbos-2.0.0 → dbos-2.1.0}/LICENSE +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/README.md +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/__init__.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/__main__.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_admin_server.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_app_db.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_classproperty.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_client.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_conductor/conductor.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_conductor/protocol.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_context.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_croniter.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_dbos.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_dbos_config.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_debug.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_docker_pg_helper.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_error.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_event_loop.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_fastapi.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_flask.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_kafka.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_kafka_message.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_logger.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_migration.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_outcome.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_queue.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_recovery.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_registrations.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_roles.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_schemas/__init__.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_schemas/application_database.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_schemas/system_database.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_serialization.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_sys_db_postgres.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_sys_db_sqlite.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/migrations/create_table.py.dbos +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_tracer.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_utils.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/_workflow_commands.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/cli/_github_init.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/cli/_template_init.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/cli/cli.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/cli/migration.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/dbos-config.schema.json +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/dbos/py.typed +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/__init__.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/atexit_no_ctor.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/atexit_no_launch.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/classdefs.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/client_collateral.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/client_worker.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/conftest.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/dupname_classdefs1.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/dupname_classdefsa.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/more_classdefs.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/queuedworkflow.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/script_without_fastapi.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_admin_server.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_async.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_async_workflow_management.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_classdecorators.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_cli.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_client.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_concurrency.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_config.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_croniter.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_debouncer.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_debug.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_docker_secrets.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_fastapi.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_fastapi_roles.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_flask.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_kafka.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_outcome.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_package.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_queue.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_schema_migration.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_singleton.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_spans.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_sqlalchemy.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_streaming.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_workflow_introspection.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/tests/test_workflow_management.py +0 -0
- {dbos-2.0.0 → dbos-2.1.0}/version/__init__.py +0 -0
@@ -1278,21 +1278,24 @@ def recv(dbos: "DBOS", topic: Optional[str] = None, timeout_seconds: float = 60)
|
|
1278
1278
|
def set_event(dbos: "DBOS", key: str, value: Any) -> None:
|
1279
1279
|
cur_ctx = get_local_dbos_context()
|
1280
1280
|
if cur_ctx is not None:
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1281
|
+
if cur_ctx.is_workflow():
|
1282
|
+
# If called from a workflow function, run as a step
|
1283
|
+
attributes: TracedAttributes = {
|
1284
|
+
"name": "set_event",
|
1285
|
+
}
|
1286
|
+
with EnterDBOSStep(attributes):
|
1287
|
+
ctx = assert_current_dbos_context()
|
1288
|
+
dbos._sys_db.set_event_from_workflow(
|
1289
|
+
ctx.workflow_id, ctx.curr_step_function_id, key, value
|
1290
|
+
)
|
1291
|
+
elif cur_ctx.is_step():
|
1292
|
+
dbos._sys_db.set_event_from_step(cur_ctx.workflow_id, key, value)
|
1293
|
+
else:
|
1294
|
+
raise DBOSException(
|
1295
|
+
"set_event() must be called from within a workflow or step"
|
1292
1296
|
)
|
1293
1297
|
else:
|
1294
|
-
|
1295
|
-
raise DBOSException("set_event() must be called from within a workflow")
|
1298
|
+
raise DBOSException("set_event() must be called from within a workflow or step")
|
1296
1299
|
|
1297
1300
|
|
1298
1301
|
def get_event(
|
@@ -86,6 +86,7 @@ def debouncer_workflow(
|
|
86
86
|
dbos = _get_dbos_instance()
|
87
87
|
|
88
88
|
workflow_inputs: WorkflowInputs = {"args": args, "kwargs": kwargs}
|
89
|
+
|
89
90
|
# Every time the debounced workflow is called, a message is sent to this workflow.
|
90
91
|
# It waits until debounce_period_sec have passed since the last message or until
|
91
92
|
# debounce_timeout_sec has elapsed.
|
@@ -95,7 +96,10 @@ def debouncer_workflow(
|
|
95
96
|
if options["debounce_timeout_sec"]
|
96
97
|
else math.inf
|
97
98
|
)
|
98
|
-
|
99
|
+
|
100
|
+
debounce_deadline_epoch_sec = dbos._sys_db.call_function_as_step(
|
101
|
+
get_debounce_deadline_epoch_sec, "get_debounce_deadline_epoch_sec"
|
102
|
+
)
|
99
103
|
debounce_period_sec = initial_debounce_period_sec
|
100
104
|
while time.time() < debounce_deadline_epoch_sec:
|
101
105
|
time_until_deadline = max(debounce_deadline_epoch_sec - time.time(), 0)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import random
|
1
2
|
import threading
|
2
3
|
import traceback
|
3
4
|
from datetime import datetime, timezone
|
@@ -15,28 +16,40 @@ from ._registrations import get_dbos_func_name
|
|
15
16
|
|
16
17
|
ScheduledWorkflow = Callable[[datetime, datetime], None]
|
17
18
|
|
18
|
-
scheduler_queue: Queue
|
19
|
-
|
20
19
|
|
21
20
|
def scheduler_loop(
|
22
21
|
func: ScheduledWorkflow, cron: str, stop_event: threading.Event
|
23
22
|
) -> None:
|
23
|
+
from dbos._dbos import _get_dbos_instance
|
24
|
+
|
25
|
+
dbos = _get_dbos_instance()
|
26
|
+
scheduler_queue = dbos._registry.get_internal_queue()
|
24
27
|
try:
|
25
28
|
iter = croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
|
26
|
-
except Exception
|
29
|
+
except Exception:
|
27
30
|
dbos_logger.error(
|
28
31
|
f'Cannot run scheduled function {get_dbos_func_name(func)}. Invalid crontab "{cron}"'
|
29
32
|
)
|
33
|
+
raise
|
30
34
|
while not stop_event.is_set():
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
next_exec_time = iter.get_next(datetime)
|
36
|
+
sleep_time = (next_exec_time - datetime.now(timezone.utc)).total_seconds()
|
37
|
+
sleep_time = max(0, sleep_time)
|
38
|
+
# To prevent a "thundering herd" problem in a distributed setting,
|
39
|
+
# apply jitter of up to 10% the sleep time, capped at 10 seconds
|
40
|
+
max_jitter = min(sleep_time / 10, 10)
|
41
|
+
jitter = random.uniform(0, max_jitter)
|
42
|
+
if stop_event.wait(timeout=sleep_time + jitter):
|
34
43
|
return
|
35
44
|
try:
|
36
|
-
|
37
|
-
f"sched-{get_dbos_func_name(func)}-{
|
38
|
-
)
|
39
|
-
|
45
|
+
workflowID = (
|
46
|
+
f"sched-{get_dbos_func_name(func)}-{next_exec_time.isoformat()}"
|
47
|
+
)
|
48
|
+
if not dbos._sys_db.get_workflow_status(workflowID):
|
49
|
+
with SetWorkflowID(workflowID):
|
50
|
+
scheduler_queue.enqueue(
|
51
|
+
func, next_exec_time, datetime.now(timezone.utc)
|
52
|
+
)
|
40
53
|
except Exception:
|
41
54
|
dbos_logger.warning(
|
42
55
|
f"Exception encountered in scheduler thread: {traceback.format_exc()})"
|
@@ -49,13 +62,10 @@ def scheduled(
|
|
49
62
|
def decorator(func: ScheduledWorkflow) -> ScheduledWorkflow:
|
50
63
|
try:
|
51
64
|
croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
|
52
|
-
except Exception
|
65
|
+
except Exception:
|
53
66
|
raise ValueError(
|
54
67
|
f'Invalid crontab "{cron}" for scheduled function function {get_dbos_func_name(func)}.'
|
55
68
|
)
|
56
|
-
|
57
|
-
global scheduler_queue
|
58
|
-
scheduler_queue = dbosreg.get_internal_queue()
|
59
69
|
stop_event = threading.Event()
|
60
70
|
dbosreg.register_poller(stop_event, scheduler_loop, func, cron, stop_event)
|
61
71
|
return func
|
@@ -1077,24 +1077,23 @@ class SystemDatabase(ABC):
|
|
1077
1077
|
SystemSchema.operation_outputs.c.child_workflow_id,
|
1078
1078
|
).where(SystemSchema.operation_outputs.c.workflow_uuid == workflow_id)
|
1079
1079
|
).fetchall()
|
1080
|
-
|
1081
|
-
|
1080
|
+
steps = []
|
1081
|
+
for row in rows:
|
1082
|
+
_, output, exception = _serialization.safe_deserialize(
|
1083
|
+
workflow_id,
|
1084
|
+
serialized_input=None,
|
1085
|
+
serialized_output=row[2],
|
1086
|
+
serialized_exception=row[3],
|
1087
|
+
)
|
1088
|
+
step = StepInfo(
|
1082
1089
|
function_id=row[0],
|
1083
1090
|
function_name=row[1],
|
1084
|
-
output=
|
1085
|
-
|
1086
|
-
if row[2] is not None
|
1087
|
-
else row[2]
|
1088
|
-
),
|
1089
|
-
error=(
|
1090
|
-
_serialization.deserialize_exception(row[3])
|
1091
|
-
if row[3] is not None
|
1092
|
-
else row[3]
|
1093
|
-
),
|
1091
|
+
output=output,
|
1092
|
+
error=exception,
|
1094
1093
|
child_workflow_id=row[4],
|
1095
1094
|
)
|
1096
|
-
|
1097
|
-
|
1095
|
+
steps.append(step)
|
1096
|
+
return steps
|
1098
1097
|
|
1099
1098
|
def _record_operation_result_txn(
|
1100
1099
|
self, result: OperationResultInternal, conn: sa.Connection
|
@@ -1525,7 +1524,7 @@ class SystemDatabase(ABC):
|
|
1525
1524
|
return duration
|
1526
1525
|
|
1527
1526
|
@db_retry()
|
1528
|
-
def
|
1527
|
+
def set_event_from_workflow(
|
1529
1528
|
self,
|
1530
1529
|
workflow_uuid: str,
|
1531
1530
|
function_id: int,
|
@@ -1567,6 +1566,26 @@ class SystemDatabase(ABC):
|
|
1567
1566
|
}
|
1568
1567
|
self._record_operation_result_txn(output, conn=c)
|
1569
1568
|
|
1569
|
+
def set_event_from_step(
|
1570
|
+
self,
|
1571
|
+
workflow_uuid: str,
|
1572
|
+
key: str,
|
1573
|
+
message: Any,
|
1574
|
+
) -> None:
|
1575
|
+
with self.engine.begin() as c:
|
1576
|
+
c.execute(
|
1577
|
+
self.dialect.insert(SystemSchema.workflow_events)
|
1578
|
+
.values(
|
1579
|
+
workflow_uuid=workflow_uuid,
|
1580
|
+
key=key,
|
1581
|
+
value=_serialization.serialize(message),
|
1582
|
+
)
|
1583
|
+
.on_conflict_do_update(
|
1584
|
+
index_elements=["workflow_uuid", "key"],
|
1585
|
+
set_={"value": _serialization.serialize(message)},
|
1586
|
+
)
|
1587
|
+
)
|
1588
|
+
|
1570
1589
|
def get_all_events(self, workflow_id: str) -> Dict[str, Any]:
|
1571
1590
|
"""
|
1572
1591
|
Get all events currently present for a workflow ID.
|
@@ -965,41 +965,57 @@ def test_send_recv_temp_wf(dbos: DBOS) -> None:
|
|
965
965
|
def test_set_get_events(dbos: DBOS) -> None:
|
966
966
|
@DBOS.workflow()
|
967
967
|
def test_setevent_workflow() -> None:
|
968
|
-
|
969
|
-
|
970
|
-
|
968
|
+
DBOS.set_event("key1", "value1")
|
969
|
+
DBOS.set_event("key2", "value2")
|
970
|
+
DBOS.set_event("key3", None)
|
971
|
+
set_event_step()
|
972
|
+
|
973
|
+
@DBOS.step()
|
974
|
+
def set_event_step() -> None:
|
975
|
+
DBOS.set_event("key4", "value4")
|
971
976
|
|
972
977
|
@DBOS.workflow()
|
973
978
|
def test_getevent_workflow(
|
974
|
-
target_uuid: str, key: str,
|
979
|
+
target_uuid: str, key: str, timeout: float = 0.0
|
975
980
|
) -> Optional[str]:
|
976
|
-
msg = dbos.get_event(target_uuid, key,
|
981
|
+
msg = dbos.get_event(target_uuid, key, timeout)
|
977
982
|
return str(msg) if msg is not None else None
|
978
983
|
|
979
|
-
|
980
|
-
with SetWorkflowID(
|
984
|
+
wfid = str(uuid.uuid4())
|
985
|
+
with SetWorkflowID(wfid):
|
981
986
|
test_setevent_workflow()
|
982
|
-
with SetWorkflowID(
|
987
|
+
with SetWorkflowID(wfid):
|
983
988
|
test_setevent_workflow()
|
984
989
|
|
985
|
-
value1 = test_getevent_workflow(
|
990
|
+
value1 = test_getevent_workflow(wfid, "key1")
|
986
991
|
assert value1 == "value1"
|
987
992
|
|
988
|
-
value2 = test_getevent_workflow(
|
993
|
+
value2 = test_getevent_workflow(wfid, "key2")
|
989
994
|
assert value2 == "value2"
|
990
995
|
|
991
996
|
# Run getEvent outside of a workflow
|
992
|
-
value1 =
|
997
|
+
value1 = DBOS.get_event(wfid, "key1", 0)
|
993
998
|
assert value1 == "value1"
|
994
999
|
|
995
|
-
value2 =
|
1000
|
+
value2 = DBOS.get_event(wfid, "key2", 0)
|
996
1001
|
assert value2 == "value2"
|
997
1002
|
|
998
1003
|
begin_time = time.time()
|
999
|
-
value3 = test_getevent_workflow(
|
1004
|
+
value3 = test_getevent_workflow(wfid, "key3")
|
1000
1005
|
assert value3 is None
|
1001
|
-
|
1002
|
-
|
1006
|
+
|
1007
|
+
value4 = DBOS.get_event(wfid, "key4", 0)
|
1008
|
+
assert value4 == "value4"
|
1009
|
+
|
1010
|
+
steps = DBOS.list_workflow_steps(wfid)
|
1011
|
+
assert len(steps) == 4
|
1012
|
+
assert (
|
1013
|
+
steps[0]["function_name"]
|
1014
|
+
== steps[1]["function_name"]
|
1015
|
+
== steps[2]["function_name"]
|
1016
|
+
== "DBOS.setEvent"
|
1017
|
+
)
|
1018
|
+
assert steps[3]["function_name"] == set_event_step.__qualname__
|
1003
1019
|
|
1004
1020
|
# Test OAOO
|
1005
1021
|
timeout_uuid = str(uuid.uuid4())
|
@@ -9,6 +9,7 @@ from psycopg.errors import SerializationFailure
|
|
9
9
|
from sqlalchemy.exc import InvalidRequestError, OperationalError
|
10
10
|
|
11
11
|
from dbos import DBOS, Queue, SetWorkflowID
|
12
|
+
from dbos._client import DBOSClient
|
12
13
|
from dbos._dbos_config import DBOSConfig
|
13
14
|
from dbos._error import (
|
14
15
|
DBOSAwaitedWorkflowCancelledError,
|
@@ -502,6 +503,36 @@ def test_error_serialization() -> None:
|
|
502
503
|
assert isinstance(exception, str)
|
503
504
|
|
504
505
|
|
506
|
+
def test_workflow_error_serialization(dbos: DBOS, client: DBOSClient) -> None:
|
507
|
+
|
508
|
+
@DBOS.step()
|
509
|
+
def step() -> None:
|
510
|
+
raise BadException(1, 2)
|
511
|
+
|
512
|
+
@DBOS.workflow()
|
513
|
+
def workflow() -> None:
|
514
|
+
step()
|
515
|
+
|
516
|
+
handle = DBOS.start_workflow(workflow)
|
517
|
+
|
518
|
+
with pytest.raises(BadException):
|
519
|
+
handle.get_result()
|
520
|
+
|
521
|
+
workflows = DBOS.list_workflows()
|
522
|
+
assert len(workflows) == 1
|
523
|
+
assert workflows[0].error is not None
|
524
|
+
|
525
|
+
steps = DBOS.list_workflow_steps(handle.workflow_id)
|
526
|
+
assert len(steps) == 1
|
527
|
+
assert steps[0]["error"] is not None
|
528
|
+
|
529
|
+
status = handle.get_status()
|
530
|
+
assert status.error is not None
|
531
|
+
|
532
|
+
status = client.retrieve_workflow(handle.workflow_id).get_status()
|
533
|
+
assert status.error is not None
|
534
|
+
|
535
|
+
|
505
536
|
def test_unregistered_workflow(dbos: DBOS, config: DBOSConfig) -> None:
|
506
537
|
|
507
538
|
@DBOS.workflow()
|
@@ -102,7 +102,7 @@ def test_scheduled_workflow(dbos: DBOS) -> None:
|
|
102
102
|
wf_counter += 1
|
103
103
|
|
104
104
|
time.sleep(5)
|
105
|
-
assert wf_counter >
|
105
|
+
assert wf_counter > 1 and wf_counter <= 5
|
106
106
|
|
107
107
|
|
108
108
|
def test_appdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
|
@@ -123,7 +123,7 @@ def test_appdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
|
|
123
123
|
time.sleep(2)
|
124
124
|
assert dbos._app_db
|
125
125
|
simulate_db_restart(dbos._app_db.engine, 2)
|
126
|
-
time.sleep(
|
126
|
+
time.sleep(3)
|
127
127
|
assert wf_counter > 2
|
128
128
|
|
129
129
|
|
@@ -138,7 +138,7 @@ def test_sysdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
|
|
138
138
|
|
139
139
|
time.sleep(2)
|
140
140
|
simulate_db_restart(dbos._sys_db.engine, 2)
|
141
|
-
time.sleep(
|
141
|
+
time.sleep(3)
|
142
142
|
# We know there should be at least 2 occurrences from the 4 seconds when the DB was up.
|
143
143
|
# There could be more than 4, depending on the pace the machine...
|
144
144
|
assert wf_counter >= 2
|
@@ -154,7 +154,7 @@ def test_scheduled_transaction(dbos: DBOS) -> None:
|
|
154
154
|
txn_counter += 1
|
155
155
|
|
156
156
|
time.sleep(5)
|
157
|
-
assert txn_counter >
|
157
|
+
assert txn_counter > 1 and txn_counter <= 5
|
158
158
|
|
159
159
|
|
160
160
|
def test_scheduled_step(dbos: DBOS) -> None:
|
@@ -205,8 +205,8 @@ def test_scheduler_oaoo(dbos: DBOS) -> None:
|
|
205
205
|
nonlocal txn_counter
|
206
206
|
txn_counter += 1
|
207
207
|
|
208
|
-
time.sleep(
|
209
|
-
assert wf_counter >= 1 and wf_counter <=
|
208
|
+
time.sleep(4)
|
209
|
+
assert wf_counter >= 1 and wf_counter <= 4
|
210
210
|
max_tries = 10
|
211
211
|
for i in range(max_tries):
|
212
212
|
try:
|
@@ -223,7 +223,7 @@ def test_scheduler_oaoo(dbos: DBOS) -> None:
|
|
223
223
|
evt.set()
|
224
224
|
|
225
225
|
# Wait for workflows to finish
|
226
|
-
time.sleep(
|
226
|
+
time.sleep(3)
|
227
227
|
|
228
228
|
dbos._sys_db.update_workflow_outcome(workflow_id, "PENDING")
|
229
229
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|