dbos 0.5.0a3__tar.gz → 0.5.0a4__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.5.0a3 → dbos-0.5.0a4}/PKG-INFO +1 -1
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/context.py +16 -18
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/core.py +31 -36
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/dbos.py +14 -17
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/error.py +5 -5
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/registrations.py +1 -1
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/system_database.py +1 -14
- {dbos-0.5.0a3 → dbos-0.5.0a4}/pyproject.toml +1 -1
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/classdefs.py +10 -10
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_admin_server.py +6 -6
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_classdecorators.py +22 -22
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_concurrency.py +9 -9
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_dbos.py +50 -50
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_fastapi.py +6 -6
- {dbos-0.5.0a3 → dbos-0.5.0a4}/LICENSE +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/README.md +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/__init__.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/admin_sever.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/application_database.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/cli.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/dbos-config.schema.json +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/dbos_config.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/decorators.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/fastapi.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/logger.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/migrations/env.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/migrations/script.py.mako +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/py.typed +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/recovery.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/roles.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/scheduler/croniter.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/scheduler/scheduler.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/schemas/__init__.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/schemas/application_database.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/schemas/system_database.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/README.md +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/__package/__init__.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/__package/main.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/__package/schema.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/alembic.ini +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/dbos-config.yaml.dbos +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/migrations/env.py.dbos +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/migrations/script.py.mako +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/start_postgres_docker.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/tracer.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/utils.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/__init__.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/atexit_no_ctor.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/atexit_no_launch.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/conftest.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/more_classdefs.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/scheduler/test_croniter.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/scheduler/test_scheduler.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_config.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_failures.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_package.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_schema_migration.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/tests/test_singleton.py +0 -0
- {dbos-0.5.0a3 → dbos-0.5.0a4}/version/__init__.py +0 -0
|
@@ -18,18 +18,16 @@ from .logger import dbos_logger
|
|
|
18
18
|
from .tracer import dbos_tracer
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# These are used to tag OTel traces
|
|
22
22
|
class OperationType(Enum):
|
|
23
23
|
HANDLER = "handler"
|
|
24
24
|
WORKFLOW = "workflow"
|
|
25
25
|
TRANSACTION = "transaction"
|
|
26
|
-
|
|
26
|
+
STEP = "step"
|
|
27
27
|
PROCEDURE = "procedure"
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
OperationTypes = Literal[
|
|
31
|
-
"handler", "workflow", "transaction", "communicator", "procedure"
|
|
32
|
-
]
|
|
30
|
+
OperationTypes = Literal["handler", "workflow", "transaction", "step", "procedure"]
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
# Keys must be the same as in TypeScript Transact
|
|
@@ -64,7 +62,7 @@ class DBOSContext:
|
|
|
64
62
|
self.function_id: int = -1
|
|
65
63
|
self.in_recovery: bool = False
|
|
66
64
|
|
|
67
|
-
self.
|
|
65
|
+
self.curr_step_function_id: int = -1
|
|
68
66
|
self.curr_tx_function_id: int = -1
|
|
69
67
|
self.sql_session: Optional[Session] = None
|
|
70
68
|
self.spans: list[Span] = []
|
|
@@ -117,26 +115,26 @@ class DBOSContext:
|
|
|
117
115
|
def is_workflow(self) -> bool:
|
|
118
116
|
return (
|
|
119
117
|
len(self.workflow_id) > 0
|
|
120
|
-
and not self.
|
|
118
|
+
and not self.is_step()
|
|
121
119
|
and not self.is_transaction()
|
|
122
120
|
)
|
|
123
121
|
|
|
124
122
|
def is_transaction(self) -> bool:
|
|
125
123
|
return self.sql_session is not None
|
|
126
124
|
|
|
127
|
-
def
|
|
128
|
-
return self.
|
|
125
|
+
def is_step(self) -> bool:
|
|
126
|
+
return self.curr_step_function_id >= 0
|
|
129
127
|
|
|
130
|
-
def
|
|
128
|
+
def start_step(
|
|
131
129
|
self,
|
|
132
130
|
fid: int,
|
|
133
131
|
attributes: TracedAttributes,
|
|
134
132
|
) -> None:
|
|
135
|
-
self.
|
|
133
|
+
self.curr_step_function_id = fid
|
|
136
134
|
self._start_span(attributes)
|
|
137
135
|
|
|
138
|
-
def
|
|
139
|
-
self.
|
|
136
|
+
def end_step(self, exc_value: Optional[BaseException]) -> None:
|
|
137
|
+
self.curr_step_function_id = -1
|
|
140
138
|
self._end_span(exc_value)
|
|
141
139
|
|
|
142
140
|
def start_transaction(
|
|
@@ -375,7 +373,7 @@ class EnterDBOSChildWorkflow:
|
|
|
375
373
|
def __enter__(self) -> DBOSContext:
|
|
376
374
|
ctx = assert_current_dbos_context()
|
|
377
375
|
self.parent_ctx = ctx
|
|
378
|
-
assert ctx.is_workflow() # Is in a workflow and not in
|
|
376
|
+
assert ctx.is_workflow() # Is in a workflow and not in a step
|
|
379
377
|
ctx.function_id += 1
|
|
380
378
|
if len(ctx.id_assigned_for_next_workflow) == 0:
|
|
381
379
|
ctx.id_assigned_for_next_workflow = (
|
|
@@ -401,7 +399,7 @@ class EnterDBOSChildWorkflow:
|
|
|
401
399
|
return False # Did not handle
|
|
402
400
|
|
|
403
401
|
|
|
404
|
-
class
|
|
402
|
+
class EnterDBOSStep:
|
|
405
403
|
def __init__(
|
|
406
404
|
self,
|
|
407
405
|
attributes: TracedAttributes,
|
|
@@ -412,7 +410,7 @@ class EnterDBOSCommunicator:
|
|
|
412
410
|
ctx = assert_current_dbos_context()
|
|
413
411
|
assert ctx.is_workflow()
|
|
414
412
|
ctx.function_id += 1
|
|
415
|
-
ctx.
|
|
413
|
+
ctx.start_step(ctx.function_id, attributes=self.attributes)
|
|
416
414
|
return ctx
|
|
417
415
|
|
|
418
416
|
def __exit__(
|
|
@@ -422,8 +420,8 @@ class EnterDBOSCommunicator:
|
|
|
422
420
|
traceback: Optional[TracebackType],
|
|
423
421
|
) -> Literal[False]:
|
|
424
422
|
ctx = assert_current_dbos_context()
|
|
425
|
-
assert ctx.
|
|
426
|
-
ctx.
|
|
423
|
+
assert ctx.is_step()
|
|
424
|
+
ctx.end_step(exc_value)
|
|
427
425
|
return False # Did not handle
|
|
428
426
|
|
|
429
427
|
|
|
@@ -19,20 +19,18 @@ from dbos.context import (
|
|
|
19
19
|
DBOSContextEnsure,
|
|
20
20
|
DBOSContextSwap,
|
|
21
21
|
EnterDBOSChildWorkflow,
|
|
22
|
-
|
|
22
|
+
EnterDBOSStep,
|
|
23
23
|
EnterDBOSTransaction,
|
|
24
24
|
EnterDBOSWorkflow,
|
|
25
25
|
OperationType,
|
|
26
26
|
SetWorkflowID,
|
|
27
27
|
TracedAttributes,
|
|
28
28
|
assert_current_dbos_context,
|
|
29
|
-
clear_local_dbos_context,
|
|
30
29
|
get_local_dbos_context,
|
|
31
|
-
set_local_dbos_context,
|
|
32
30
|
)
|
|
33
31
|
from dbos.error import (
|
|
34
|
-
DBOSCommunicatorMaxRetriesExceededError,
|
|
35
32
|
DBOSException,
|
|
33
|
+
DBOSMaxStepRetriesExceeded,
|
|
36
34
|
DBOSNonExistentWorkflowError,
|
|
37
35
|
DBOSRecoveryError,
|
|
38
36
|
DBOSWorkflowConflictIDError,
|
|
@@ -61,7 +59,6 @@ if TYPE_CHECKING:
|
|
|
61
59
|
from dbos.dbos import IsolationLevel
|
|
62
60
|
|
|
63
61
|
from sqlalchemy.exc import DBAPIError
|
|
64
|
-
from sqlalchemy.orm import Session
|
|
65
62
|
|
|
66
63
|
P = ParamSpec("P") # A generic type for workflow parameters
|
|
67
64
|
R = TypeVar("R", covariant=True) # A generic type for workflow return values
|
|
@@ -362,7 +359,7 @@ def _start_workflow(
|
|
|
362
359
|
|
|
363
360
|
# Sequence of events for starting a workflow:
|
|
364
361
|
# First - is there a WF already running?
|
|
365
|
-
# (and not in
|
|
362
|
+
# (and not in step as that is an error)
|
|
366
363
|
# Assign an ID to the workflow, if it doesn't have an app-assigned one
|
|
367
364
|
# If this is a root workflow, assign a new ID
|
|
368
365
|
# If this is a child workflow, assign parent wf id with call# suffix
|
|
@@ -370,7 +367,7 @@ def _start_workflow(
|
|
|
370
367
|
# Pass the new context to a worker thread that will run the wf function
|
|
371
368
|
cur_ctx = get_local_dbos_context()
|
|
372
369
|
if cur_ctx is not None and cur_ctx.is_within_workflow():
|
|
373
|
-
assert cur_ctx.is_workflow() # Not in
|
|
370
|
+
assert cur_ctx.is_workflow() # Not in a step
|
|
374
371
|
cur_ctx.function_id += 1
|
|
375
372
|
if len(cur_ctx.id_assigned_for_next_workflow) == 0:
|
|
376
373
|
cur_ctx.id_assigned_for_next_workflow = (
|
|
@@ -517,7 +514,7 @@ def _transaction(
|
|
|
517
514
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
518
515
|
rr: Optional[str] = check_required_roles(func, fi)
|
|
519
516
|
# Entering transaction is allowed:
|
|
520
|
-
# In a workflow (that is not in a
|
|
517
|
+
# In a workflow (that is not in a step already)
|
|
521
518
|
# Not in a workflow (we will start the single op workflow)
|
|
522
519
|
ctx = get_local_dbos_context()
|
|
523
520
|
if ctx and ctx.is_within_workflow():
|
|
@@ -544,7 +541,7 @@ def _transaction(
|
|
|
544
541
|
return decorator
|
|
545
542
|
|
|
546
543
|
|
|
547
|
-
def
|
|
544
|
+
def _step(
|
|
548
545
|
dbosreg: "_DBOSRegistry",
|
|
549
546
|
*,
|
|
550
547
|
retries_allowed: bool = False,
|
|
@@ -554,7 +551,7 @@ def _communicator(
|
|
|
554
551
|
) -> Callable[[F], F]:
|
|
555
552
|
def decorator(func: F) -> F:
|
|
556
553
|
|
|
557
|
-
def
|
|
554
|
+
def invoke_step(*args: Any, **kwargs: Any) -> Any:
|
|
558
555
|
if dbosreg.dbos is None:
|
|
559
556
|
raise DBOSException(
|
|
560
557
|
f"Function {func.__name__} invoked before DBOS initialized"
|
|
@@ -563,10 +560,10 @@ def _communicator(
|
|
|
563
560
|
|
|
564
561
|
attributes: TracedAttributes = {
|
|
565
562
|
"name": func.__name__,
|
|
566
|
-
"operationType": OperationType.
|
|
563
|
+
"operationType": OperationType.STEP.value,
|
|
567
564
|
}
|
|
568
|
-
with
|
|
569
|
-
|
|
565
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
566
|
+
step_output: OperationResultInternal = {
|
|
570
567
|
"workflow_uuid": ctx.workflow_id,
|
|
571
568
|
"function_id": ctx.function_id,
|
|
572
569
|
"output": None,
|
|
@@ -591,24 +588,24 @@ def _communicator(
|
|
|
591
588
|
for attempt in range(1, local_max_attempts + 1):
|
|
592
589
|
try:
|
|
593
590
|
output = func(*args, **kwargs)
|
|
594
|
-
|
|
591
|
+
step_output["output"] = utils.serialize(output)
|
|
595
592
|
error = None
|
|
596
593
|
break
|
|
597
594
|
except Exception as err:
|
|
598
595
|
error = err
|
|
599
596
|
if retries_allowed:
|
|
600
597
|
dbos.logger.warning(
|
|
601
|
-
f"
|
|
598
|
+
f"Step being automatically retried. (attempt {attempt} of {local_max_attempts}). {traceback.format_exc()}"
|
|
602
599
|
)
|
|
603
600
|
ctx.get_current_span().add_event(
|
|
604
|
-
f"
|
|
601
|
+
f"Step attempt {attempt} failed",
|
|
605
602
|
{
|
|
606
603
|
"error": str(error),
|
|
607
604
|
"retryIntervalSeconds": local_interval_seconds,
|
|
608
605
|
},
|
|
609
606
|
)
|
|
610
607
|
if attempt == local_max_attempts:
|
|
611
|
-
error =
|
|
608
|
+
error = DBOSMaxStepRetriesExceeded()
|
|
612
609
|
else:
|
|
613
610
|
time.sleep(local_interval_seconds)
|
|
614
611
|
local_interval_seconds = min(
|
|
@@ -616,10 +613,10 @@ def _communicator(
|
|
|
616
613
|
max_retry_interval_seconds,
|
|
617
614
|
)
|
|
618
615
|
|
|
619
|
-
|
|
616
|
+
step_output["error"] = (
|
|
620
617
|
utils.serialize(error) if error is not None else None
|
|
621
618
|
)
|
|
622
|
-
dbos.sys_db.record_operation_result(
|
|
619
|
+
dbos.sys_db.record_operation_result(step_output)
|
|
623
620
|
if error is not None:
|
|
624
621
|
raise error
|
|
625
622
|
return output
|
|
@@ -629,20 +626,18 @@ def _communicator(
|
|
|
629
626
|
@wraps(func)
|
|
630
627
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
631
628
|
rr: Optional[str] = check_required_roles(func, fi)
|
|
632
|
-
# Entering
|
|
633
|
-
# In a
|
|
634
|
-
# In a workflow (that is not in a
|
|
629
|
+
# Entering step is allowed:
|
|
630
|
+
# In a step already, just call the original function directly.
|
|
631
|
+
# In a workflow (that is not in a step already)
|
|
635
632
|
# Not in a workflow (we will start the single op workflow)
|
|
636
633
|
ctx = get_local_dbos_context()
|
|
637
|
-
if ctx and ctx.
|
|
634
|
+
if ctx and ctx.is_step():
|
|
638
635
|
# Call the original function directly
|
|
639
636
|
return func(*args, **kwargs)
|
|
640
637
|
if ctx and ctx.is_within_workflow():
|
|
641
|
-
assert (
|
|
642
|
-
ctx.is_workflow()
|
|
643
|
-
), "Communicators must be called from within workflows"
|
|
638
|
+
assert ctx.is_workflow(), "Steps must be called from within workflows"
|
|
644
639
|
with DBOSAssumeRole(rr):
|
|
645
|
-
return
|
|
640
|
+
return invoke_step(*args, **kwargs)
|
|
646
641
|
else:
|
|
647
642
|
tempwf = dbosreg.workflow_info_map.get("<temp>." + func.__qualname__)
|
|
648
643
|
assert tempwf
|
|
@@ -653,7 +648,7 @@ def _communicator(
|
|
|
653
648
|
|
|
654
649
|
wrapped_wf = _workflow_wrapper(dbosreg, temp_wf)
|
|
655
650
|
set_dbos_func_name(temp_wf, "<temp>." + func.__qualname__)
|
|
656
|
-
set_temp_workflow_type(temp_wf, "
|
|
651
|
+
set_temp_workflow_type(temp_wf, "step")
|
|
657
652
|
dbosreg.register_wf_function(get_dbos_func_name(temp_wf), wrapped_wf)
|
|
658
653
|
|
|
659
654
|
return cast(F, wrapper)
|
|
@@ -668,10 +663,10 @@ def _send(
|
|
|
668
663
|
attributes: TracedAttributes = {
|
|
669
664
|
"name": "send",
|
|
670
665
|
}
|
|
671
|
-
with
|
|
666
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
672
667
|
dbos.sys_db.send(
|
|
673
668
|
ctx.workflow_id,
|
|
674
|
-
ctx.
|
|
669
|
+
ctx.curr_step_function_id,
|
|
675
670
|
destination_id,
|
|
676
671
|
message,
|
|
677
672
|
topic,
|
|
@@ -697,12 +692,12 @@ def _recv(
|
|
|
697
692
|
attributes: TracedAttributes = {
|
|
698
693
|
"name": "recv",
|
|
699
694
|
}
|
|
700
|
-
with
|
|
695
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
701
696
|
ctx.function_id += 1 # Reserve for the sleep
|
|
702
697
|
timeout_function_id = ctx.function_id
|
|
703
698
|
return dbos.sys_db.recv(
|
|
704
699
|
ctx.workflow_id,
|
|
705
|
-
ctx.
|
|
700
|
+
ctx.curr_step_function_id,
|
|
706
701
|
timeout_function_id,
|
|
707
702
|
topic,
|
|
708
703
|
timeout_seconds,
|
|
@@ -722,9 +717,9 @@ def _set_event(dbos: "DBOS", key: str, value: Any) -> None:
|
|
|
722
717
|
attributes: TracedAttributes = {
|
|
723
718
|
"name": "set_event",
|
|
724
719
|
}
|
|
725
|
-
with
|
|
720
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
726
721
|
dbos.sys_db.set_event(
|
|
727
|
-
ctx.workflow_id, ctx.
|
|
722
|
+
ctx.workflow_id, ctx.curr_step_function_id, key, value
|
|
728
723
|
)
|
|
729
724
|
else:
|
|
730
725
|
# Cannot call it from outside of a workflow
|
|
@@ -743,12 +738,12 @@ def _get_event(
|
|
|
743
738
|
attributes: TracedAttributes = {
|
|
744
739
|
"name": "get_event",
|
|
745
740
|
}
|
|
746
|
-
with
|
|
741
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
747
742
|
ctx.function_id += 1
|
|
748
743
|
timeout_function_id = ctx.function_id
|
|
749
744
|
caller_ctx: GetEventWorkflowContext = {
|
|
750
745
|
"workflow_uuid": ctx.workflow_id,
|
|
751
|
-
"function_id": ctx.
|
|
746
|
+
"function_id": ctx.curr_step_function_id,
|
|
752
747
|
"timeout_function_id": timeout_function_id,
|
|
753
748
|
}
|
|
754
749
|
return dbos.sys_db.get_event(workflow_id, key, timeout_seconds, caller_ctx)
|
|
@@ -25,13 +25,13 @@ from opentelemetry.trace import Span
|
|
|
25
25
|
|
|
26
26
|
from dbos.core import (
|
|
27
27
|
TEMP_SEND_WF_NAME,
|
|
28
|
-
_communicator,
|
|
29
28
|
_execute_workflow_id,
|
|
30
29
|
_get_event,
|
|
31
30
|
_recv,
|
|
32
31
|
_send,
|
|
33
32
|
_set_event,
|
|
34
33
|
_start_workflow,
|
|
34
|
+
_step,
|
|
35
35
|
_transaction,
|
|
36
36
|
_workflow,
|
|
37
37
|
_workflow_wrapper,
|
|
@@ -63,7 +63,7 @@ else:
|
|
|
63
63
|
|
|
64
64
|
from dbos.admin_sever import AdminServer
|
|
65
65
|
from dbos.context import (
|
|
66
|
-
|
|
66
|
+
EnterDBOSStep,
|
|
67
67
|
TracedAttributes,
|
|
68
68
|
assert_current_dbos_context,
|
|
69
69
|
get_local_dbos_context,
|
|
@@ -179,7 +179,7 @@ class DBOS:
|
|
|
179
179
|
Main access class for DBOS functionality.
|
|
180
180
|
|
|
181
181
|
`DBOS` contains functions and properties for:
|
|
182
|
-
1. Decorating classes, workflows,
|
|
182
|
+
1. Decorating classes, workflows, and steps
|
|
183
183
|
2. Starting workflow functions
|
|
184
184
|
3. Retrieving workflow status information
|
|
185
185
|
4. Interacting with workflows via events and messages
|
|
@@ -388,9 +388,8 @@ class DBOS:
|
|
|
388
388
|
"""
|
|
389
389
|
return _transaction(_get_or_create_dbos_registry(), isolation_level)
|
|
390
390
|
|
|
391
|
-
# Mirror the CommunicatorConfig from TS. However, we disable retries by default.
|
|
392
391
|
@classmethod
|
|
393
|
-
def
|
|
392
|
+
def step(
|
|
394
393
|
cls,
|
|
395
394
|
*,
|
|
396
395
|
retries_allowed: bool = False,
|
|
@@ -399,17 +398,17 @@ class DBOS:
|
|
|
399
398
|
backoff_rate: float = 2.0,
|
|
400
399
|
) -> Callable[[F], F]:
|
|
401
400
|
"""
|
|
402
|
-
Decorate and configure a function for use as a DBOS
|
|
401
|
+
Decorate and configure a function for use as a DBOS step.
|
|
403
402
|
|
|
404
403
|
Args:
|
|
405
404
|
retries_allowed(bool): If true, enable retries on thrown exceptions
|
|
406
405
|
interval_seconds(float): Time between retry attempts
|
|
407
406
|
backoff_rate(float): Multiplier for exponentially increasing `interval_seconds` between retries
|
|
408
|
-
max_attempts(int): Maximum number of
|
|
407
|
+
max_attempts(int): Maximum number of retries before raising an exception
|
|
409
408
|
|
|
410
409
|
"""
|
|
411
410
|
|
|
412
|
-
return
|
|
411
|
+
return _step(
|
|
413
412
|
_get_or_create_dbos_registry(),
|
|
414
413
|
retries_allowed=retries_allowed,
|
|
415
414
|
interval_seconds=interval_seconds,
|
|
@@ -542,9 +541,9 @@ class DBOS:
|
|
|
542
541
|
}
|
|
543
542
|
if seconds <= 0:
|
|
544
543
|
return
|
|
545
|
-
with
|
|
544
|
+
with EnterDBOSStep(attributes) as ctx:
|
|
546
545
|
_get_dbos_instance().sys_db.sleep(
|
|
547
|
-
ctx.workflow_id, ctx.
|
|
546
|
+
ctx.workflow_id, ctx.curr_step_function_id, seconds
|
|
548
547
|
)
|
|
549
548
|
|
|
550
549
|
@classmethod
|
|
@@ -615,9 +614,7 @@ class DBOS:
|
|
|
615
614
|
def sql_session(cls) -> Session:
|
|
616
615
|
"""Return the SQLAlchemy `Session` for the current context, which must be within a transaction function."""
|
|
617
616
|
ctx = assert_current_dbos_context()
|
|
618
|
-
assert (
|
|
619
|
-
ctx.is_transaction()
|
|
620
|
-
), "sql_session is only available within a transaction."
|
|
617
|
+
assert ctx.is_transaction(), "db is only available within a transaction."
|
|
621
618
|
rv = ctx.sql_session
|
|
622
619
|
assert rv
|
|
623
620
|
return rv
|
|
@@ -628,7 +625,7 @@ class DBOS:
|
|
|
628
625
|
ctx = assert_current_dbos_context()
|
|
629
626
|
assert (
|
|
630
627
|
ctx.is_within_workflow()
|
|
631
|
-
), "workflow_id is only available within a
|
|
628
|
+
), "workflow_id is only available within a DBOS operation."
|
|
632
629
|
return ctx.workflow_id
|
|
633
630
|
|
|
634
631
|
@classproperty
|
|
@@ -722,9 +719,9 @@ class DBOSConfiguredInstance:
|
|
|
722
719
|
"""
|
|
723
720
|
Base class for classes containing DBOS member functions.
|
|
724
721
|
|
|
725
|
-
When a class contains
|
|
726
|
-
|
|
727
|
-
|
|
722
|
+
When a class contains DBOS functions that access instance state, the DBOS workflow
|
|
723
|
+
executor needs a name for the instance. This name is recorded in the database, and
|
|
724
|
+
used to refer to the proper instance upon recovery.
|
|
728
725
|
|
|
729
726
|
Use `DBOSConfiguredInstance` to specify the instance name and register the instance
|
|
730
727
|
with the DBOS workflow executor.
|
|
@@ -32,7 +32,7 @@ class DBOSErrorCode(Enum):
|
|
|
32
32
|
WorkflowFunctionNotFound = 4
|
|
33
33
|
NonExistentWorkflowError = 5
|
|
34
34
|
DuplicateWorkflowEventError = 6
|
|
35
|
-
|
|
35
|
+
MaxStepRetriesExceeded = 7
|
|
36
36
|
NotAuthorized = 8
|
|
37
37
|
|
|
38
38
|
|
|
@@ -106,11 +106,11 @@ class DBOSNotAuthorizedError(DBOSException):
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
class
|
|
110
|
-
"""Exception raised when a
|
|
109
|
+
class DBOSMaxStepRetriesExceeded(DBOSException):
|
|
110
|
+
"""Exception raised when a step was retried the maximimum number of times without success."""
|
|
111
111
|
|
|
112
112
|
def __init__(self) -> None:
|
|
113
113
|
super().__init__(
|
|
114
|
-
"
|
|
115
|
-
dbos_error_code=DBOSErrorCode.
|
|
114
|
+
"Step reached maximum retries.",
|
|
115
|
+
dbos_error_code=DBOSErrorCode.MaxStepRetriesExceeded.value,
|
|
116
116
|
)
|
|
@@ -16,7 +16,7 @@ def set_dbos_func_name(f: Any, name: str) -> None:
|
|
|
16
16
|
setattr(f, "dbos_function_name", name)
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
TempWorkflowType = Literal["transaction", "
|
|
19
|
+
TempWorkflowType = Literal["transaction", "step", "send", None]
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def get_temp_workflow_type(f: Any) -> TempWorkflowType:
|
|
@@ -4,17 +4,7 @@ import select
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import
|
|
8
|
-
TYPE_CHECKING,
|
|
9
|
-
Any,
|
|
10
|
-
Dict,
|
|
11
|
-
List,
|
|
12
|
-
Literal,
|
|
13
|
-
Optional,
|
|
14
|
-
Sequence,
|
|
15
|
-
TypedDict,
|
|
16
|
-
cast,
|
|
17
|
-
)
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Sequence, TypedDict, cast
|
|
18
8
|
|
|
19
9
|
import psycopg2
|
|
20
10
|
import sqlalchemy as sa
|
|
@@ -33,9 +23,6 @@ from .dbos_config import ConfigFile
|
|
|
33
23
|
from .logger import dbos_logger
|
|
34
24
|
from .schemas.system_database import SystemSchema
|
|
35
25
|
|
|
36
|
-
if TYPE_CHECKING:
|
|
37
|
-
from dbos.dbos import DBOS
|
|
38
|
-
|
|
39
26
|
|
|
40
27
|
class WorkflowStatusString(Enum):
|
|
41
28
|
"""Enumeration of values allowed for `WorkflowSatusInternal.status`."""
|
|
@@ -11,20 +11,20 @@ from dbos.context import assert_current_dbos_context
|
|
|
11
11
|
class DBOSTestClass(DBOSConfiguredInstance):
|
|
12
12
|
txn_counter_c = 0
|
|
13
13
|
wf_counter_c = 0
|
|
14
|
-
|
|
14
|
+
step_counter_c = 0
|
|
15
15
|
|
|
16
16
|
def __init__(self) -> None:
|
|
17
17
|
super().__init__("myconfig")
|
|
18
18
|
self.txn_counter: int = 0
|
|
19
19
|
self.wf_counter: int = 0
|
|
20
|
-
self.
|
|
20
|
+
self.step_counter: int = 0
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
23
|
@DBOS.workflow()
|
|
24
24
|
def test_workflow_cls(cls, var: str, var2: str) -> str:
|
|
25
25
|
cls.wf_counter_c += 1
|
|
26
26
|
res = DBOSTestClass.test_transaction_cls(var2)
|
|
27
|
-
res2 = DBOSTestClass.
|
|
27
|
+
res2 = DBOSTestClass.test_step_cls(var)
|
|
28
28
|
return res + res2
|
|
29
29
|
|
|
30
30
|
@classmethod
|
|
@@ -35,16 +35,16 @@ class DBOSTestClass(DBOSConfiguredInstance):
|
|
|
35
35
|
return var2 + str(rows[0][0])
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
|
-
@DBOS.
|
|
39
|
-
def
|
|
40
|
-
cls.
|
|
38
|
+
@DBOS.step()
|
|
39
|
+
def test_step_cls(cls, var: str) -> str:
|
|
40
|
+
cls.step_counter_c += 1
|
|
41
41
|
return var
|
|
42
42
|
|
|
43
43
|
@DBOS.workflow()
|
|
44
44
|
def test_workflow(self, var: str, var2: str) -> str:
|
|
45
45
|
self.wf_counter += 1
|
|
46
46
|
res = self.test_transaction(var2)
|
|
47
|
-
res2 = self.
|
|
47
|
+
res2 = self.test_step(var)
|
|
48
48
|
return res + res2
|
|
49
49
|
|
|
50
50
|
@DBOS.transaction()
|
|
@@ -53,9 +53,9 @@ class DBOSTestClass(DBOSConfiguredInstance):
|
|
|
53
53
|
self.txn_counter += 1
|
|
54
54
|
return var2 + str(rows[0][0])
|
|
55
55
|
|
|
56
|
-
@DBOS.
|
|
57
|
-
def
|
|
58
|
-
self.
|
|
56
|
+
@DBOS.step()
|
|
57
|
+
def test_step(self, var: str) -> str:
|
|
58
|
+
self.step_counter += 1
|
|
59
59
|
return var
|
|
60
60
|
|
|
61
61
|
@DBOS.workflow()
|
|
@@ -54,7 +54,7 @@ def test_admin_recovery(dbos: DBOS) -> None:
|
|
|
54
54
|
os.environ["DBOS__APPVERSION"] = "testversion"
|
|
55
55
|
os.environ["DBOS__APPID"] = "testappid"
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
step_counter: int = 0
|
|
58
58
|
wf_counter: int = 0
|
|
59
59
|
|
|
60
60
|
@DBOS.workflow()
|
|
@@ -62,13 +62,13 @@ def test_admin_recovery(dbos: DBOS) -> None:
|
|
|
62
62
|
DBOS.logger.info("WFID: " + DBOS.workflow_id)
|
|
63
63
|
nonlocal wf_counter
|
|
64
64
|
wf_counter += 1
|
|
65
|
-
res =
|
|
65
|
+
res = test_step(var2)
|
|
66
66
|
return res + var
|
|
67
67
|
|
|
68
|
-
@DBOS.
|
|
69
|
-
def
|
|
70
|
-
nonlocal
|
|
71
|
-
|
|
68
|
+
@DBOS.step()
|
|
69
|
+
def test_step(var2: str) -> str:
|
|
70
|
+
nonlocal step_counter
|
|
71
|
+
step_counter += 1
|
|
72
72
|
return var2 + "1"
|
|
73
73
|
|
|
74
74
|
wfuuid = str(uuid.uuid4())
|
|
@@ -190,14 +190,14 @@ def test_simple_workflow_static(dbos: DBOS) -> None:
|
|
|
190
190
|
class DBOSTestClassStatic:
|
|
191
191
|
txn_counter: int = 0
|
|
192
192
|
wf_counter: int = 0
|
|
193
|
-
|
|
193
|
+
step_counter: int = 0
|
|
194
194
|
|
|
195
195
|
@staticmethod
|
|
196
196
|
@DBOS.workflow()
|
|
197
197
|
def test_workflow(var: str, var2: str) -> str:
|
|
198
198
|
DBOSTestClassStatic.wf_counter += 1
|
|
199
199
|
res = DBOSTestClassStatic.test_transaction(var2)
|
|
200
|
-
res2 = DBOSTestClassStatic.
|
|
200
|
+
res2 = DBOSTestClassStatic.test_step(var)
|
|
201
201
|
DBOS.logger.info("I'm test_workflow")
|
|
202
202
|
return res + res2
|
|
203
203
|
|
|
@@ -210,10 +210,10 @@ def test_simple_workflow_static(dbos: DBOS) -> None:
|
|
|
210
210
|
return var2 + str(rows[0][0])
|
|
211
211
|
|
|
212
212
|
@staticmethod
|
|
213
|
-
@DBOS.
|
|
214
|
-
def
|
|
215
|
-
DBOSTestClassStatic.
|
|
216
|
-
DBOS.logger.info("I'm
|
|
213
|
+
@DBOS.step()
|
|
214
|
+
def test_step(var: str) -> str:
|
|
215
|
+
DBOSTestClassStatic.step_counter += 1
|
|
216
|
+
DBOS.logger.info("I'm test_step")
|
|
217
217
|
return var
|
|
218
218
|
|
|
219
219
|
assert DBOSTestClassStatic.test_workflow("bob", "bob") == "bob1bob"
|
|
@@ -221,7 +221,7 @@ def test_simple_workflow_static(dbos: DBOS) -> None:
|
|
|
221
221
|
assert wfh.get_result() == "bob1bob"
|
|
222
222
|
assert DBOSTestClassStatic.txn_counter == 2
|
|
223
223
|
assert DBOSTestClassStatic.wf_counter == 2
|
|
224
|
-
assert DBOSTestClassStatic.
|
|
224
|
+
assert DBOSTestClassStatic.step_counter == 2
|
|
225
225
|
|
|
226
226
|
|
|
227
227
|
def test_simple_workflow_class(dbos: DBOS) -> None:
|
|
@@ -229,14 +229,14 @@ def test_simple_workflow_class(dbos: DBOS) -> None:
|
|
|
229
229
|
class DBOSTestClassClass:
|
|
230
230
|
txn_counter: int = 0
|
|
231
231
|
wf_counter: int = 0
|
|
232
|
-
|
|
232
|
+
step_counter: int = 0
|
|
233
233
|
|
|
234
234
|
@classmethod
|
|
235
235
|
@DBOS.workflow()
|
|
236
236
|
def test_workflow(cls, var: str, var2: str) -> str:
|
|
237
237
|
DBOSTestClassClass.wf_counter += 1
|
|
238
238
|
res = DBOSTestClassClass.test_transaction(var2)
|
|
239
|
-
res2 = DBOSTestClassClass.
|
|
239
|
+
res2 = DBOSTestClassClass.test_step(var)
|
|
240
240
|
DBOS.logger.info("I'm test_workflow")
|
|
241
241
|
return res + res2
|
|
242
242
|
|
|
@@ -249,10 +249,10 @@ def test_simple_workflow_class(dbos: DBOS) -> None:
|
|
|
249
249
|
return var2 + str(rows[0][0])
|
|
250
250
|
|
|
251
251
|
@classmethod
|
|
252
|
-
@DBOS.
|
|
253
|
-
def
|
|
254
|
-
DBOSTestClassClass.
|
|
255
|
-
DBOS.logger.info("I'm
|
|
252
|
+
@DBOS.step()
|
|
253
|
+
def test_step(cls, var: str) -> str:
|
|
254
|
+
DBOSTestClassClass.step_counter += 1
|
|
255
|
+
DBOS.logger.info("I'm test_step")
|
|
256
256
|
return var
|
|
257
257
|
|
|
258
258
|
assert DBOSTestClassClass.test_workflow("bob", "bob") == "bob1bob"
|
|
@@ -260,7 +260,7 @@ def test_simple_workflow_class(dbos: DBOS) -> None:
|
|
|
260
260
|
assert wfh.get_result() == "bob1bob"
|
|
261
261
|
assert DBOSTestClassClass.txn_counter == 2
|
|
262
262
|
assert DBOSTestClassClass.wf_counter == 2
|
|
263
|
-
assert DBOSTestClassClass.
|
|
263
|
+
assert DBOSTestClassClass.step_counter == 2
|
|
264
264
|
|
|
265
265
|
|
|
266
266
|
def test_no_instname(dbos: DBOS) -> None:
|
|
@@ -282,13 +282,13 @@ def test_simple_workflow_inst(dbos: DBOS) -> None:
|
|
|
282
282
|
super().__init__("bob", dbos)
|
|
283
283
|
self.txn_counter: int = 0
|
|
284
284
|
self.wf_counter: int = 0
|
|
285
|
-
self.
|
|
285
|
+
self.step_counter: int = 0
|
|
286
286
|
|
|
287
287
|
@DBOS.workflow()
|
|
288
288
|
def test_workflow(self, var: str, var2: str) -> str:
|
|
289
289
|
self.wf_counter += 1
|
|
290
290
|
res = self.test_transaction(var2)
|
|
291
|
-
res2 = self.
|
|
291
|
+
res2 = self.test_step(var)
|
|
292
292
|
DBOS.logger.info("I'm test_workflow")
|
|
293
293
|
return res + res2
|
|
294
294
|
|
|
@@ -299,10 +299,10 @@ def test_simple_workflow_inst(dbos: DBOS) -> None:
|
|
|
299
299
|
DBOS.logger.info("I'm test_transaction")
|
|
300
300
|
return var2 + str(rows[0][0])
|
|
301
301
|
|
|
302
|
-
@DBOS.
|
|
303
|
-
def
|
|
304
|
-
self.
|
|
305
|
-
DBOS.logger.info("I'm
|
|
302
|
+
@DBOS.step()
|
|
303
|
+
def test_step(self, var: str) -> str:
|
|
304
|
+
self.step_counter += 1
|
|
305
|
+
DBOS.logger.info("I'm test_step")
|
|
306
306
|
return var
|
|
307
307
|
|
|
308
308
|
inst = DBOSTestClassInst()
|
|
@@ -321,7 +321,7 @@ def test_simple_workflow_inst(dbos: DBOS) -> None:
|
|
|
321
321
|
assert wfh.get_result() == "bob1bob"
|
|
322
322
|
assert inst.txn_counter == 2
|
|
323
323
|
assert inst.wf_counter == 2
|
|
324
|
-
assert inst.
|
|
324
|
+
assert inst.step_counter == 2
|
|
325
325
|
|
|
326
326
|
|
|
327
327
|
def test_forgotten_decorator(dbos: DBOS) -> None:
|
|
@@ -330,7 +330,7 @@ def test_forgotten_decorator(dbos: DBOS) -> None:
|
|
|
330
330
|
super().__init__("bob", dbos)
|
|
331
331
|
self.txn_counter: int = 0
|
|
332
332
|
self.wf_counter: int = 0
|
|
333
|
-
self.
|
|
333
|
+
self.step_counter: int = 0
|
|
334
334
|
|
|
335
335
|
@DBOS.workflow()
|
|
336
336
|
def test_workflow1(self) -> str:
|
|
@@ -32,15 +32,15 @@ def test_concurrent_workflows(dbos: DBOS) -> None:
|
|
|
32
32
|
|
|
33
33
|
def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
|
|
34
34
|
condition = threading.Condition()
|
|
35
|
-
|
|
35
|
+
step_count = 0
|
|
36
36
|
txn_count = 0
|
|
37
37
|
|
|
38
|
-
@DBOS.
|
|
39
|
-
def
|
|
40
|
-
nonlocal
|
|
41
|
-
|
|
38
|
+
@DBOS.step()
|
|
39
|
+
def test_step() -> str:
|
|
40
|
+
nonlocal step_count
|
|
41
|
+
step_count += 1
|
|
42
42
|
condition.acquire()
|
|
43
|
-
if
|
|
43
|
+
if step_count == 1:
|
|
44
44
|
# Wait for the other one to notify
|
|
45
45
|
condition.wait()
|
|
46
46
|
else:
|
|
@@ -50,12 +50,12 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
|
|
|
50
50
|
|
|
51
51
|
@DBOS.workflow()
|
|
52
52
|
def test_workflow() -> str:
|
|
53
|
-
res =
|
|
53
|
+
res = test_step()
|
|
54
54
|
return res
|
|
55
55
|
|
|
56
56
|
def test_comm_thread(id: str) -> str:
|
|
57
57
|
with SetWorkflowID(id):
|
|
58
|
-
return
|
|
58
|
+
return test_step()
|
|
59
59
|
|
|
60
60
|
# Need to set isolation level to a lower one, otherwise it gets serialization error instead (we already handle it correctly by automatic retries).
|
|
61
61
|
@DBOS.transaction(isolation_level="REPEATABLE READ")
|
|
@@ -88,7 +88,7 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
|
|
|
88
88
|
assert wf_handle2.get_result() == wfuuid
|
|
89
89
|
|
|
90
90
|
# Make sure temp workflows can handle conflicts as well.
|
|
91
|
-
|
|
91
|
+
step_count = 0
|
|
92
92
|
wfuuid = str(uuid.uuid4())
|
|
93
93
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
94
94
|
future1 = executor.submit(test_comm_thread, wfuuid)
|
|
@@ -15,7 +15,7 @@ from dbos import DBOS, ConfigFile, SetWorkflowID, WorkflowHandle, WorkflowStatus
|
|
|
15
15
|
|
|
16
16
|
# Private API because this is a test
|
|
17
17
|
from dbos.context import assert_current_dbos_context, get_local_dbos_context
|
|
18
|
-
from dbos.error import
|
|
18
|
+
from dbos.error import DBOSMaxStepRetriesExceeded
|
|
19
19
|
from dbos.system_database import GetWorkflowsInput
|
|
20
20
|
from tests.conftest import default_config
|
|
21
21
|
|
|
@@ -23,14 +23,14 @@ from tests.conftest import default_config
|
|
|
23
23
|
def test_simple_workflow(dbos: DBOS) -> None:
|
|
24
24
|
txn_counter: int = 0
|
|
25
25
|
wf_counter: int = 0
|
|
26
|
-
|
|
26
|
+
step_counter: int = 0
|
|
27
27
|
|
|
28
28
|
@DBOS.workflow()
|
|
29
29
|
def test_workflow(var: str, var2: str) -> str:
|
|
30
30
|
nonlocal wf_counter
|
|
31
31
|
wf_counter += 1
|
|
32
32
|
res = test_transaction(var2)
|
|
33
|
-
res2 =
|
|
33
|
+
res2 = test_step(var)
|
|
34
34
|
DBOS.logger.info("I'm test_workflow")
|
|
35
35
|
return res + res2
|
|
36
36
|
|
|
@@ -42,11 +42,11 @@ def test_simple_workflow(dbos: DBOS) -> None:
|
|
|
42
42
|
DBOS.logger.info("I'm test_transaction")
|
|
43
43
|
return var2 + str(rows[0][0])
|
|
44
44
|
|
|
45
|
-
@DBOS.
|
|
46
|
-
def
|
|
47
|
-
nonlocal
|
|
48
|
-
|
|
49
|
-
DBOS.logger.info("I'm
|
|
45
|
+
@DBOS.step()
|
|
46
|
+
def test_step(var: str) -> str:
|
|
47
|
+
nonlocal step_counter
|
|
48
|
+
step_counter += 1
|
|
49
|
+
DBOS.logger.info("I'm test_step")
|
|
50
50
|
return var
|
|
51
51
|
|
|
52
52
|
assert test_workflow("bob", "bob") == "bob1bob"
|
|
@@ -58,7 +58,7 @@ def test_simple_workflow(dbos: DBOS) -> None:
|
|
|
58
58
|
with SetWorkflowID(wfuuid):
|
|
59
59
|
assert test_workflow("alice", "alice") == "alice1alice"
|
|
60
60
|
assert txn_counter == 2 # Only increment once
|
|
61
|
-
assert
|
|
61
|
+
assert step_counter == 2 # Only increment once
|
|
62
62
|
|
|
63
63
|
# Test we can execute the workflow by uuid
|
|
64
64
|
handle = DBOS.execute_workflow_id(wfuuid)
|
|
@@ -69,7 +69,7 @@ def test_simple_workflow(dbos: DBOS) -> None:
|
|
|
69
69
|
def test_child_workflow(dbos: DBOS) -> None:
|
|
70
70
|
txn_counter: int = 0
|
|
71
71
|
wf_counter: int = 0
|
|
72
|
-
|
|
72
|
+
step_counter: int = 0
|
|
73
73
|
|
|
74
74
|
@DBOS.transaction()
|
|
75
75
|
def test_transaction(var2: str) -> str:
|
|
@@ -79,11 +79,11 @@ def test_child_workflow(dbos: DBOS) -> None:
|
|
|
79
79
|
DBOS.logger.info("I'm test_transaction")
|
|
80
80
|
return var2 + str(rows[0][0])
|
|
81
81
|
|
|
82
|
-
@DBOS.
|
|
83
|
-
def
|
|
84
|
-
nonlocal
|
|
85
|
-
|
|
86
|
-
DBOS.logger.info("I'm
|
|
82
|
+
@DBOS.step()
|
|
83
|
+
def test_step(var: str) -> str:
|
|
84
|
+
nonlocal step_counter
|
|
85
|
+
step_counter += 1
|
|
86
|
+
DBOS.logger.info("I'm test_step")
|
|
87
87
|
return var
|
|
88
88
|
|
|
89
89
|
@DBOS.workflow()
|
|
@@ -96,7 +96,7 @@ def test_child_workflow(dbos: DBOS) -> None:
|
|
|
96
96
|
nonlocal wf_counter
|
|
97
97
|
wf_counter += 1
|
|
98
98
|
res = test_transaction(var2)
|
|
99
|
-
res2 =
|
|
99
|
+
res2 = test_step(var)
|
|
100
100
|
return res + res2
|
|
101
101
|
|
|
102
102
|
@DBOS.workflow()
|
|
@@ -159,7 +159,7 @@ def test_child_workflow(dbos: DBOS) -> None:
|
|
|
159
159
|
def test_exception_workflow(dbos: DBOS) -> None:
|
|
160
160
|
txn_counter: int = 0
|
|
161
161
|
wf_counter: int = 0
|
|
162
|
-
|
|
162
|
+
step_counter: int = 0
|
|
163
163
|
|
|
164
164
|
@DBOS.transaction()
|
|
165
165
|
def exception_transaction(var: str) -> str:
|
|
@@ -167,10 +167,10 @@ def test_exception_workflow(dbos: DBOS) -> None:
|
|
|
167
167
|
txn_counter += 1
|
|
168
168
|
raise Exception(var)
|
|
169
169
|
|
|
170
|
-
@DBOS.
|
|
171
|
-
def
|
|
172
|
-
nonlocal
|
|
173
|
-
|
|
170
|
+
@DBOS.step()
|
|
171
|
+
def exception_step(var: str) -> str:
|
|
172
|
+
nonlocal step_counter
|
|
173
|
+
step_counter += 1
|
|
174
174
|
raise Exception(var)
|
|
175
175
|
|
|
176
176
|
@DBOS.workflow()
|
|
@@ -185,7 +185,7 @@ def test_exception_workflow(dbos: DBOS) -> None:
|
|
|
185
185
|
err1 = e
|
|
186
186
|
|
|
187
187
|
try:
|
|
188
|
-
|
|
188
|
+
exception_step("test error")
|
|
189
189
|
except Exception as e:
|
|
190
190
|
err2 = e
|
|
191
191
|
assert err1 is not None and err2 is not None
|
|
@@ -209,7 +209,7 @@ def test_exception_workflow(dbos: DBOS) -> None:
|
|
|
209
209
|
exception_workflow()
|
|
210
210
|
assert "test error" == str(exc_info.value)
|
|
211
211
|
assert txn_counter == 2 # Only increment once
|
|
212
|
-
assert
|
|
212
|
+
assert step_counter == 2 # Only increment once
|
|
213
213
|
|
|
214
214
|
# Test we can execute the workflow by uuid, shouldn't throw errors
|
|
215
215
|
handle = DBOS.execute_workflow_id(wfuuid)
|
|
@@ -221,7 +221,7 @@ def test_exception_workflow(dbos: DBOS) -> None:
|
|
|
221
221
|
|
|
222
222
|
def test_temp_workflow(dbos: DBOS) -> None:
|
|
223
223
|
txn_counter: int = 0
|
|
224
|
-
|
|
224
|
+
step_counter: int = 0
|
|
225
225
|
|
|
226
226
|
cur_time: str = datetime.datetime.now().isoformat()
|
|
227
227
|
gwi: GetWorkflowsInput = GetWorkflowsInput()
|
|
@@ -234,21 +234,21 @@ def test_temp_workflow(dbos: DBOS) -> None:
|
|
|
234
234
|
txn_counter += 1
|
|
235
235
|
return var2 + str(rows[0][0])
|
|
236
236
|
|
|
237
|
-
@DBOS.
|
|
238
|
-
def
|
|
239
|
-
nonlocal
|
|
240
|
-
|
|
237
|
+
@DBOS.step()
|
|
238
|
+
def test_step(var: str) -> str:
|
|
239
|
+
nonlocal step_counter
|
|
240
|
+
step_counter += 1
|
|
241
241
|
return var
|
|
242
242
|
|
|
243
|
-
@DBOS.
|
|
244
|
-
def
|
|
245
|
-
return
|
|
243
|
+
@DBOS.step()
|
|
244
|
+
def call_step(var: str) -> str:
|
|
245
|
+
return test_step(var)
|
|
246
246
|
|
|
247
247
|
assert get_local_dbos_context() is None
|
|
248
248
|
res = test_transaction("var2")
|
|
249
249
|
assert res == "var21"
|
|
250
250
|
assert get_local_dbos_context() is None
|
|
251
|
-
res =
|
|
251
|
+
res = test_step("var")
|
|
252
252
|
assert res == "var"
|
|
253
253
|
|
|
254
254
|
# Wait for buffers to flush
|
|
@@ -265,17 +265,17 @@ def test_temp_workflow(dbos: DBOS) -> None:
|
|
|
265
265
|
assert wfi2["name"].startswith("<temp>")
|
|
266
266
|
|
|
267
267
|
assert txn_counter == 1
|
|
268
|
-
assert
|
|
268
|
+
assert step_counter == 1
|
|
269
269
|
|
|
270
|
-
res =
|
|
270
|
+
res = call_step("var2")
|
|
271
271
|
assert res == "var2"
|
|
272
|
-
assert
|
|
272
|
+
assert step_counter == 2
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
def test_temp_workflow_errors(dbos: DBOS) -> None:
|
|
276
276
|
txn_counter: int = 0
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
step_counter: int = 0
|
|
278
|
+
retried_step_counter: int = 0
|
|
279
279
|
|
|
280
280
|
cur_time: str = datetime.datetime.now().isoformat()
|
|
281
281
|
gwi: GetWorkflowsInput = GetWorkflowsInput()
|
|
@@ -287,16 +287,16 @@ def test_temp_workflow_errors(dbos: DBOS) -> None:
|
|
|
287
287
|
txn_counter += 1
|
|
288
288
|
raise Exception(var2)
|
|
289
289
|
|
|
290
|
-
@DBOS.
|
|
291
|
-
def
|
|
292
|
-
nonlocal
|
|
293
|
-
|
|
290
|
+
@DBOS.step()
|
|
291
|
+
def test_step(var: str) -> str:
|
|
292
|
+
nonlocal step_counter
|
|
293
|
+
step_counter += 1
|
|
294
294
|
raise Exception(var)
|
|
295
295
|
|
|
296
|
-
@DBOS.
|
|
297
|
-
def
|
|
298
|
-
nonlocal
|
|
299
|
-
|
|
296
|
+
@DBOS.step(retries_allowed=True)
|
|
297
|
+
def test_retried_step(var: str) -> str:
|
|
298
|
+
nonlocal retried_step_counter
|
|
299
|
+
retried_step_counter += 1
|
|
300
300
|
raise Exception(var)
|
|
301
301
|
|
|
302
302
|
with pytest.raises(Exception) as exc_info:
|
|
@@ -304,15 +304,15 @@ def test_temp_workflow_errors(dbos: DBOS) -> None:
|
|
|
304
304
|
assert "tval" == str(exc_info.value)
|
|
305
305
|
|
|
306
306
|
with pytest.raises(Exception) as exc_info:
|
|
307
|
-
|
|
307
|
+
test_step("cval")
|
|
308
308
|
assert "cval" == str(exc_info.value)
|
|
309
309
|
|
|
310
|
-
with pytest.raises(
|
|
311
|
-
|
|
310
|
+
with pytest.raises(DBOSMaxStepRetriesExceeded) as exc_info:
|
|
311
|
+
test_retried_step("rval")
|
|
312
312
|
|
|
313
313
|
assert txn_counter == 1
|
|
314
|
-
assert
|
|
315
|
-
assert
|
|
314
|
+
assert step_counter == 1
|
|
315
|
+
assert retried_step_counter == 3
|
|
316
316
|
|
|
317
317
|
|
|
318
318
|
def test_recovery_workflow(dbos: DBOS) -> None:
|
|
@@ -29,7 +29,7 @@ def test_simple_endpoint(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
29
29
|
DBOS.span.set_attribute("test_key", "test_value")
|
|
30
30
|
assert DBOS.request is not None
|
|
31
31
|
res1 = test_transaction(var1)
|
|
32
|
-
res2 =
|
|
32
|
+
res2 = test_step(var2)
|
|
33
33
|
return res1 + res2
|
|
34
34
|
|
|
35
35
|
@DBOS.transaction()
|
|
@@ -37,8 +37,8 @@ def test_simple_endpoint(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
37
37
|
rows = DBOS.sql_session.execute(sa.text("SELECT 1")).fetchall()
|
|
38
38
|
return var + str(rows[0][0])
|
|
39
39
|
|
|
40
|
-
@DBOS.
|
|
41
|
-
def
|
|
40
|
+
@DBOS.step()
|
|
41
|
+
def test_step(var: str) -> str:
|
|
42
42
|
return var
|
|
43
43
|
|
|
44
44
|
response = client.get("/workflow/bob/bob")
|
|
@@ -66,7 +66,7 @@ def test_start_workflow(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
66
66
|
DBOS.span.set_attribute("test_key", "test_value")
|
|
67
67
|
assert DBOS.request is not None
|
|
68
68
|
res1 = test_transaction(var1)
|
|
69
|
-
res2 =
|
|
69
|
+
res2 = test_step(var2)
|
|
70
70
|
return res1 + res2
|
|
71
71
|
|
|
72
72
|
@DBOS.transaction()
|
|
@@ -74,8 +74,8 @@ def test_start_workflow(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
74
74
|
rows = DBOS.sql_session.execute(sa.text("SELECT 1")).fetchall()
|
|
75
75
|
return var + str(rows[0][0])
|
|
76
76
|
|
|
77
|
-
@DBOS.
|
|
78
|
-
def
|
|
77
|
+
@DBOS.step()
|
|
78
|
+
def test_step(var: str) -> str:
|
|
79
79
|
return var
|
|
80
80
|
|
|
81
81
|
response = client.get("/bob/bob")
|
|
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
|
{dbos-0.5.0a3 → dbos-0.5.0a4}/dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py
RENAMED
|
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
|