dbos 1.6.0a1__py3-none-any.whl → 1.6.0a4__py3-none-any.whl
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/_client.py +7 -7
- dbos/_context.py +12 -1
- dbos/_core.py +49 -28
- dbos/_dbos.py +20 -6
- dbos/_kafka.py +2 -1
- dbos/_registrations.py +5 -3
- dbos/_roles.py +3 -2
- dbos/_scheduler.py +11 -8
- dbos/_sys_db.py +10 -7
- dbos/_workflow_commands.py +5 -5
- {dbos-1.6.0a1.dist-info → dbos-1.6.0a4.dist-info}/METADATA +1 -1
- {dbos-1.6.0a1.dist-info → dbos-1.6.0a4.dist-info}/RECORD +15 -15
- {dbos-1.6.0a1.dist-info → dbos-1.6.0a4.dist-info}/WHEEL +0 -0
- {dbos-1.6.0a1.dist-info → dbos-1.6.0a4.dist-info}/entry_points.txt +0 -0
- {dbos-1.6.0a1.dist-info → dbos-1.6.0a4.dist-info}/licenses/LICENSE +0 -0
dbos/_client.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import sys
|
3
3
|
import uuid
|
4
|
-
from typing import Any, Generic, List, Optional, TypedDict, TypeVar
|
4
|
+
from typing import Any, Generic, List, Optional, TypedDict, TypeVar, Union
|
5
5
|
|
6
6
|
from dbos._app_db import ApplicationDatabase
|
7
7
|
from dbos._context import MaxPriority, MinPriority
|
@@ -128,7 +128,6 @@ class DBOSClient:
|
|
128
128
|
workflow_name = options["workflow_name"]
|
129
129
|
queue_name = options["queue_name"]
|
130
130
|
|
131
|
-
app_version = options.get("app_version")
|
132
131
|
max_recovery_attempts = options.get("max_recovery_attempts")
|
133
132
|
if max_recovery_attempts is None:
|
134
133
|
max_recovery_attempts = DEFAULT_MAX_RECOVERY_ATTEMPTS
|
@@ -139,6 +138,7 @@ class DBOSClient:
|
|
139
138
|
enqueue_options_internal: EnqueueOptionsInternal = {
|
140
139
|
"deduplication_id": options.get("deduplication_id"),
|
141
140
|
"priority": options.get("priority"),
|
141
|
+
"app_version": options.get("app_version"),
|
142
142
|
}
|
143
143
|
|
144
144
|
inputs: WorkflowInputs = {
|
@@ -152,7 +152,7 @@ class DBOSClient:
|
|
152
152
|
"name": workflow_name,
|
153
153
|
"class_name": None,
|
154
154
|
"queue_name": queue_name,
|
155
|
-
"app_version": app_version,
|
155
|
+
"app_version": enqueue_options_internal["app_version"],
|
156
156
|
"config_name": None,
|
157
157
|
"authenticated_user": None,
|
158
158
|
"assumed_role": None,
|
@@ -284,7 +284,7 @@ class DBOSClient:
|
|
284
284
|
self,
|
285
285
|
*,
|
286
286
|
workflow_ids: Optional[List[str]] = None,
|
287
|
-
status: Optional[str] = None,
|
287
|
+
status: Optional[Union[str, List[str]]] = None,
|
288
288
|
start_time: Optional[str] = None,
|
289
289
|
end_time: Optional[str] = None,
|
290
290
|
name: Optional[str] = None,
|
@@ -314,7 +314,7 @@ class DBOSClient:
|
|
314
314
|
self,
|
315
315
|
*,
|
316
316
|
workflow_ids: Optional[List[str]] = None,
|
317
|
-
status: Optional[str] = None,
|
317
|
+
status: Optional[Union[str, List[str]]] = None,
|
318
318
|
start_time: Optional[str] = None,
|
319
319
|
end_time: Optional[str] = None,
|
320
320
|
name: Optional[str] = None,
|
@@ -344,7 +344,7 @@ class DBOSClient:
|
|
344
344
|
self,
|
345
345
|
*,
|
346
346
|
queue_name: Optional[str] = None,
|
347
|
-
status: Optional[str] = None,
|
347
|
+
status: Optional[Union[str, List[str]]] = None,
|
348
348
|
start_time: Optional[str] = None,
|
349
349
|
end_time: Optional[str] = None,
|
350
350
|
name: Optional[str] = None,
|
@@ -368,7 +368,7 @@ class DBOSClient:
|
|
368
368
|
self,
|
369
369
|
*,
|
370
370
|
queue_name: Optional[str] = None,
|
371
|
-
status: Optional[str] = None,
|
371
|
+
status: Optional[Union[str, List[str]]] = None,
|
372
372
|
start_time: Optional[str] = None,
|
373
373
|
end_time: Optional[str] = None,
|
374
374
|
name: Optional[str] = None,
|
dbos/_context.py
CHANGED
@@ -93,6 +93,8 @@ class DBOSContext:
|
|
93
93
|
self.assumed_role: Optional[str] = None
|
94
94
|
self.step_status: Optional[StepStatus] = None
|
95
95
|
|
96
|
+
self.app_version: Optional[str] = None
|
97
|
+
|
96
98
|
# A user-specified workflow timeout. Takes priority over a propagated deadline.
|
97
99
|
self.workflow_timeout_ms: Optional[int] = None
|
98
100
|
# A propagated workflow deadline.
|
@@ -435,7 +437,11 @@ class SetEnqueueOptions:
|
|
435
437
|
"""
|
436
438
|
|
437
439
|
def __init__(
|
438
|
-
self,
|
440
|
+
self,
|
441
|
+
*,
|
442
|
+
deduplication_id: Optional[str] = None,
|
443
|
+
priority: Optional[int] = None,
|
444
|
+
app_version: Optional[str] = None,
|
439
445
|
) -> None:
|
440
446
|
self.created_ctx = False
|
441
447
|
self.deduplication_id: Optional[str] = deduplication_id
|
@@ -446,6 +452,8 @@ class SetEnqueueOptions:
|
|
446
452
|
)
|
447
453
|
self.priority: Optional[int] = priority
|
448
454
|
self.saved_priority: Optional[int] = None
|
455
|
+
self.app_version: Optional[str] = app_version
|
456
|
+
self.saved_app_version: Optional[str] = None
|
449
457
|
|
450
458
|
def __enter__(self) -> SetEnqueueOptions:
|
451
459
|
# Code to create a basic context
|
@@ -458,6 +466,8 @@ class SetEnqueueOptions:
|
|
458
466
|
ctx.deduplication_id = self.deduplication_id
|
459
467
|
self.saved_priority = ctx.priority
|
460
468
|
ctx.priority = self.priority
|
469
|
+
self.saved_app_version = ctx.app_version
|
470
|
+
ctx.app_version = self.app_version
|
461
471
|
return self
|
462
472
|
|
463
473
|
def __exit__(
|
@@ -469,6 +479,7 @@ class SetEnqueueOptions:
|
|
469
479
|
curr_ctx = assert_current_dbos_context()
|
470
480
|
curr_ctx.deduplication_id = self.saved_deduplication_id
|
471
481
|
curr_ctx.priority = self.saved_priority
|
482
|
+
curr_ctx.app_version = self.saved_app_version
|
472
483
|
# Code to clean up the basic context if we created it
|
473
484
|
if self.created_ctx:
|
474
485
|
_clear_local_dbos_context()
|
dbos/_core.py
CHANGED
@@ -270,7 +270,12 @@ def _init_workflow(
|
|
270
270
|
"output": None,
|
271
271
|
"error": None,
|
272
272
|
"app_id": ctx.app_id,
|
273
|
-
"app_version":
|
273
|
+
"app_version": (
|
274
|
+
enqueue_options["app_version"]
|
275
|
+
if enqueue_options is not None
|
276
|
+
and enqueue_options["app_version"] is not None
|
277
|
+
else GlobalParams.app_version
|
278
|
+
),
|
274
279
|
"executor_id": ctx.executor_id,
|
275
280
|
"recovery_attempts": None,
|
276
281
|
"authenticated_user": ctx.authenticated_user,
|
@@ -387,7 +392,7 @@ def _execute_workflow_wthread(
|
|
387
392
|
**kwargs: Any,
|
388
393
|
) -> R:
|
389
394
|
attributes: TracedAttributes = {
|
390
|
-
"name": func
|
395
|
+
"name": get_dbos_func_name(func),
|
391
396
|
"operationType": OperationType.WORKFLOW.value,
|
392
397
|
}
|
393
398
|
with DBOSContextSwap(ctx):
|
@@ -420,7 +425,7 @@ async def _execute_workflow_async(
|
|
420
425
|
**kwargs: Any,
|
421
426
|
) -> R:
|
422
427
|
attributes: TracedAttributes = {
|
423
|
-
"name": func
|
428
|
+
"name": get_dbos_func_name(func),
|
424
429
|
"operationType": OperationType.WORKFLOW.value,
|
425
430
|
}
|
426
431
|
with DBOSContextSwap(ctx):
|
@@ -527,7 +532,8 @@ def start_workflow(
|
|
527
532
|
fi = get_func_info(func)
|
528
533
|
if fi is None:
|
529
534
|
raise DBOSWorkflowFunctionNotFoundError(
|
530
|
-
"<NONE>",
|
535
|
+
"<NONE>",
|
536
|
+
f"start_workflow: function {func.__name__} is not registered",
|
531
537
|
)
|
532
538
|
|
533
539
|
func = cast("Workflow[P, R]", func.__orig_func) # type: ignore
|
@@ -547,6 +553,7 @@ def start_workflow(
|
|
547
553
|
enqueue_options = EnqueueOptionsInternal(
|
548
554
|
deduplication_id=local_ctx.deduplication_id if local_ctx is not None else None,
|
549
555
|
priority=local_ctx.priority if local_ctx is not None else None,
|
556
|
+
app_version=local_ctx.app_version if local_ctx is not None else None,
|
550
557
|
)
|
551
558
|
new_wf_id, new_wf_ctx = _get_new_wf()
|
552
559
|
|
@@ -621,7 +628,8 @@ async def start_workflow_async(
|
|
621
628
|
fi = get_func_info(func)
|
622
629
|
if fi is None:
|
623
630
|
raise DBOSWorkflowFunctionNotFoundError(
|
624
|
-
"<NONE>",
|
631
|
+
"<NONE>",
|
632
|
+
f"start_workflow: function {func.__name__} is not registered",
|
625
633
|
)
|
626
634
|
|
627
635
|
func = cast("Workflow[P, R]", func.__orig_func) # type: ignore
|
@@ -638,6 +646,7 @@ async def start_workflow_async(
|
|
638
646
|
enqueue_options = EnqueueOptionsInternal(
|
639
647
|
deduplication_id=local_ctx.deduplication_id if local_ctx is not None else None,
|
640
648
|
priority=local_ctx.priority if local_ctx is not None else None,
|
649
|
+
app_version=local_ctx.app_version if local_ctx is not None else None,
|
641
650
|
)
|
642
651
|
new_wf_id, new_wf_ctx = _get_new_wf()
|
643
652
|
|
@@ -724,13 +733,13 @@ def workflow_wrapper(
|
|
724
733
|
assert fi is not None
|
725
734
|
if dbosreg.dbos is None:
|
726
735
|
raise DBOSException(
|
727
|
-
f"Function {func
|
736
|
+
f"Function {get_dbos_func_name(func)} invoked before DBOS initialized"
|
728
737
|
)
|
729
738
|
dbos = dbosreg.dbos
|
730
739
|
|
731
740
|
rr: Optional[str] = check_required_roles(func, fi)
|
732
741
|
attributes: TracedAttributes = {
|
733
|
-
"name": func
|
742
|
+
"name": get_dbos_func_name(func),
|
734
743
|
"operationType": OperationType.WORKFLOW.value,
|
735
744
|
}
|
736
745
|
inputs: WorkflowInputs = {
|
@@ -830,27 +839,30 @@ def workflow_wrapper(
|
|
830
839
|
|
831
840
|
|
832
841
|
def decorate_workflow(
|
833
|
-
reg: "DBOSRegistry", max_recovery_attempts: Optional[int]
|
842
|
+
reg: "DBOSRegistry", name: Optional[str], max_recovery_attempts: Optional[int]
|
834
843
|
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
835
844
|
def _workflow_decorator(func: Callable[P, R]) -> Callable[P, R]:
|
836
845
|
wrapped_func = workflow_wrapper(reg, func, max_recovery_attempts)
|
837
|
-
|
846
|
+
func_name = name if name is not None else func.__qualname__
|
847
|
+
set_dbos_func_name(func, func_name)
|
848
|
+
set_dbos_func_name(wrapped_func, func_name)
|
849
|
+
reg.register_wf_function(func_name, wrapped_func, "workflow")
|
838
850
|
return wrapped_func
|
839
851
|
|
840
852
|
return _workflow_decorator
|
841
853
|
|
842
854
|
|
843
855
|
def decorate_transaction(
|
844
|
-
dbosreg: "DBOSRegistry", isolation_level: "IsolationLevel"
|
856
|
+
dbosreg: "DBOSRegistry", name: Optional[str], isolation_level: "IsolationLevel"
|
845
857
|
) -> Callable[[F], F]:
|
846
858
|
def decorator(func: F) -> F:
|
847
859
|
|
848
|
-
transaction_name = func.__qualname__
|
860
|
+
transaction_name = name if name is not None else func.__qualname__
|
849
861
|
|
850
862
|
def invoke_tx(*args: Any, **kwargs: Any) -> Any:
|
851
863
|
if dbosreg.dbos is None:
|
852
864
|
raise DBOSException(
|
853
|
-
f"Function {
|
865
|
+
f"Function {transaction_name} invoked before DBOS initialized"
|
854
866
|
)
|
855
867
|
|
856
868
|
dbos = dbosreg.dbos
|
@@ -858,12 +870,12 @@ def decorate_transaction(
|
|
858
870
|
status = dbos._sys_db.get_workflow_status(ctx.workflow_id)
|
859
871
|
if status and status["status"] == WorkflowStatusString.CANCELLED.value:
|
860
872
|
raise DBOSWorkflowCancelledError(
|
861
|
-
f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {
|
873
|
+
f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {transaction_name}."
|
862
874
|
)
|
863
875
|
|
864
876
|
with dbos._app_db.sessionmaker() as session:
|
865
877
|
attributes: TracedAttributes = {
|
866
|
-
"name":
|
878
|
+
"name": transaction_name,
|
867
879
|
"operationType": OperationType.TRANSACTION.value,
|
868
880
|
}
|
869
881
|
with EnterDBOSTransaction(session, attributes=attributes):
|
@@ -964,7 +976,7 @@ def decorate_transaction(
|
|
964
976
|
raise
|
965
977
|
except InvalidRequestError as invalid_request_error:
|
966
978
|
dbos.logger.error(
|
967
|
-
f"InvalidRequestError in transaction {
|
979
|
+
f"InvalidRequestError in transaction {transaction_name} \033[1m Hint: Do not call commit() or rollback() within a DBOS transaction.\033[0m"
|
968
980
|
)
|
969
981
|
txn_error = invalid_request_error
|
970
982
|
raise
|
@@ -984,7 +996,7 @@ def decorate_transaction(
|
|
984
996
|
|
985
997
|
if inspect.iscoroutinefunction(func):
|
986
998
|
raise DBOSException(
|
987
|
-
f"Function {
|
999
|
+
f"Function {transaction_name} is a coroutine function, but DBOS.transaction does not support coroutine functions"
|
988
1000
|
)
|
989
1001
|
|
990
1002
|
fi = get_or_create_func_info(func)
|
@@ -1003,15 +1015,19 @@ def decorate_transaction(
|
|
1003
1015
|
with DBOSAssumeRole(rr):
|
1004
1016
|
return invoke_tx(*args, **kwargs)
|
1005
1017
|
else:
|
1006
|
-
tempwf = dbosreg.workflow_info_map.get("<temp>." +
|
1018
|
+
tempwf = dbosreg.workflow_info_map.get("<temp>." + transaction_name)
|
1007
1019
|
assert tempwf
|
1008
1020
|
return tempwf(*args, **kwargs)
|
1009
1021
|
|
1022
|
+
set_dbos_func_name(func, transaction_name)
|
1023
|
+
set_dbos_func_name(wrapper, transaction_name)
|
1024
|
+
|
1010
1025
|
def temp_wf(*args: Any, **kwargs: Any) -> Any:
|
1011
1026
|
return wrapper(*args, **kwargs)
|
1012
1027
|
|
1013
1028
|
wrapped_wf = workflow_wrapper(dbosreg, temp_wf)
|
1014
|
-
set_dbos_func_name(temp_wf, "<temp>." +
|
1029
|
+
set_dbos_func_name(temp_wf, "<temp>." + transaction_name)
|
1030
|
+
set_dbos_func_name(wrapped_wf, "<temp>." + transaction_name)
|
1015
1031
|
set_temp_workflow_type(temp_wf, "transaction")
|
1016
1032
|
dbosreg.register_wf_function(
|
1017
1033
|
get_dbos_func_name(temp_wf), wrapped_wf, "transaction"
|
@@ -1028,24 +1044,25 @@ def decorate_transaction(
|
|
1028
1044
|
def decorate_step(
|
1029
1045
|
dbosreg: "DBOSRegistry",
|
1030
1046
|
*,
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1047
|
+
name: Optional[str],
|
1048
|
+
retries_allowed: bool,
|
1049
|
+
interval_seconds: float,
|
1050
|
+
max_attempts: int,
|
1051
|
+
backoff_rate: float,
|
1035
1052
|
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
1036
1053
|
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
1037
1054
|
|
1038
|
-
step_name = func.__qualname__
|
1055
|
+
step_name = name if name is not None else func.__qualname__
|
1039
1056
|
|
1040
1057
|
def invoke_step(*args: Any, **kwargs: Any) -> Any:
|
1041
1058
|
if dbosreg.dbos is None:
|
1042
1059
|
raise DBOSException(
|
1043
|
-
f"Function {
|
1060
|
+
f"Function {step_name} invoked before DBOS initialized"
|
1044
1061
|
)
|
1045
1062
|
dbos = dbosreg.dbos
|
1046
1063
|
|
1047
1064
|
attributes: TracedAttributes = {
|
1048
|
-
"name":
|
1065
|
+
"name": step_name,
|
1049
1066
|
"operationType": OperationType.STEP.value,
|
1050
1067
|
}
|
1051
1068
|
|
@@ -1124,7 +1141,7 @@ def decorate_step(
|
|
1124
1141
|
stepOutcome = stepOutcome.retry(
|
1125
1142
|
max_attempts,
|
1126
1143
|
on_exception,
|
1127
|
-
lambda i, e: DBOSMaxStepRetriesExceeded(
|
1144
|
+
lambda i, e: DBOSMaxStepRetriesExceeded(step_name, i, e),
|
1128
1145
|
)
|
1129
1146
|
|
1130
1147
|
outcome = (
|
@@ -1153,7 +1170,7 @@ def decorate_step(
|
|
1153
1170
|
with DBOSAssumeRole(rr):
|
1154
1171
|
return invoke_step(*args, **kwargs)
|
1155
1172
|
else:
|
1156
|
-
tempwf = dbosreg.workflow_info_map.get("<temp>." +
|
1173
|
+
tempwf = dbosreg.workflow_info_map.get("<temp>." + step_name)
|
1157
1174
|
assert tempwf
|
1158
1175
|
return tempwf(*args, **kwargs)
|
1159
1176
|
|
@@ -1161,6 +1178,9 @@ def decorate_step(
|
|
1161
1178
|
_mark_coroutine(wrapper) if inspect.iscoroutinefunction(func) else wrapper # type: ignore
|
1162
1179
|
)
|
1163
1180
|
|
1181
|
+
set_dbos_func_name(func, step_name)
|
1182
|
+
set_dbos_func_name(wrapper, step_name)
|
1183
|
+
|
1164
1184
|
def temp_wf_sync(*args: Any, **kwargs: Any) -> Any:
|
1165
1185
|
return wrapper(*args, **kwargs)
|
1166
1186
|
|
@@ -1174,7 +1194,8 @@ def decorate_step(
|
|
1174
1194
|
|
1175
1195
|
temp_wf = temp_wf_async if inspect.iscoroutinefunction(func) else temp_wf_sync
|
1176
1196
|
wrapped_wf = workflow_wrapper(dbosreg, temp_wf)
|
1177
|
-
set_dbos_func_name(temp_wf, "<temp>." +
|
1197
|
+
set_dbos_func_name(temp_wf, "<temp>." + step_name)
|
1198
|
+
set_dbos_func_name(wrapped_wf, "<temp>." + step_name)
|
1178
1199
|
set_temp_workflow_type(temp_wf, "step")
|
1179
1200
|
dbosreg.register_wf_function(get_dbos_func_name(temp_wf), wrapped_wf, "step")
|
1180
1201
|
wrapper.__orig_func = temp_wf # type: ignore
|
dbos/_dbos.py
CHANGED
@@ -24,6 +24,7 @@ from typing import (
|
|
24
24
|
Tuple,
|
25
25
|
Type,
|
26
26
|
TypeVar,
|
27
|
+
Union,
|
27
28
|
)
|
28
29
|
|
29
30
|
from opentelemetry.trace import Span
|
@@ -359,6 +360,7 @@ class DBOS:
|
|
359
360
|
|
360
361
|
temp_send_wf = workflow_wrapper(self._registry, send_temp_workflow)
|
361
362
|
set_dbos_func_name(send_temp_workflow, TEMP_SEND_WF_NAME)
|
363
|
+
set_dbos_func_name(temp_send_wf, TEMP_SEND_WF_NAME)
|
362
364
|
set_temp_workflow_type(send_temp_workflow, "send")
|
363
365
|
self._registry.register_wf_function(TEMP_SEND_WF_NAME, temp_send_wf, "send")
|
364
366
|
|
@@ -588,14 +590,22 @@ class DBOS:
|
|
588
590
|
# Decorators for DBOS functionality
|
589
591
|
@classmethod
|
590
592
|
def workflow(
|
591
|
-
cls,
|
593
|
+
cls,
|
594
|
+
*,
|
595
|
+
name: Optional[str] = None,
|
596
|
+
max_recovery_attempts: Optional[int] = DEFAULT_MAX_RECOVERY_ATTEMPTS,
|
592
597
|
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
593
598
|
"""Decorate a function for use as a DBOS workflow."""
|
594
|
-
return decorate_workflow(
|
599
|
+
return decorate_workflow(
|
600
|
+
_get_or_create_dbos_registry(), name, max_recovery_attempts
|
601
|
+
)
|
595
602
|
|
596
603
|
@classmethod
|
597
604
|
def transaction(
|
598
|
-
cls,
|
605
|
+
cls,
|
606
|
+
isolation_level: IsolationLevel = "SERIALIZABLE",
|
607
|
+
*,
|
608
|
+
name: Optional[str] = None,
|
599
609
|
) -> Callable[[F], F]:
|
600
610
|
"""
|
601
611
|
Decorate a function for use as a DBOS transaction.
|
@@ -604,12 +614,15 @@ class DBOS:
|
|
604
614
|
isolation_level(IsolationLevel): Transaction isolation level
|
605
615
|
|
606
616
|
"""
|
607
|
-
return decorate_transaction(
|
617
|
+
return decorate_transaction(
|
618
|
+
_get_or_create_dbos_registry(), name, isolation_level
|
619
|
+
)
|
608
620
|
|
609
621
|
@classmethod
|
610
622
|
def step(
|
611
623
|
cls,
|
612
624
|
*,
|
625
|
+
name: Optional[str] = None,
|
613
626
|
retries_allowed: bool = False,
|
614
627
|
interval_seconds: float = 1.0,
|
615
628
|
max_attempts: int = 3,
|
@@ -628,6 +641,7 @@ class DBOS:
|
|
628
641
|
|
629
642
|
return decorate_step(
|
630
643
|
_get_or_create_dbos_registry(),
|
644
|
+
name=name,
|
631
645
|
retries_allowed=retries_allowed,
|
632
646
|
interval_seconds=interval_seconds,
|
633
647
|
max_attempts=max_attempts,
|
@@ -998,7 +1012,7 @@ class DBOS:
|
|
998
1012
|
cls,
|
999
1013
|
*,
|
1000
1014
|
workflow_ids: Optional[List[str]] = None,
|
1001
|
-
status: Optional[str] = None,
|
1015
|
+
status: Optional[Union[str, List[str]]] = None,
|
1002
1016
|
start_time: Optional[str] = None,
|
1003
1017
|
end_time: Optional[str] = None,
|
1004
1018
|
name: Optional[str] = None,
|
@@ -1034,7 +1048,7 @@ class DBOS:
|
|
1034
1048
|
cls,
|
1035
1049
|
*,
|
1036
1050
|
queue_name: Optional[str] = None,
|
1037
|
-
status: Optional[str] = None,
|
1051
|
+
status: Optional[Union[str, List[str]]] = None,
|
1038
1052
|
start_time: Optional[str] = None,
|
1039
1053
|
end_time: Optional[str] = None,
|
1040
1054
|
name: Optional[str] = None,
|
dbos/_kafka.py
CHANGED
@@ -13,6 +13,7 @@ from ._context import SetWorkflowID
|
|
13
13
|
from ._error import DBOSInitializationError
|
14
14
|
from ._kafka_message import KafkaMessage
|
15
15
|
from ._logger import dbos_logger
|
16
|
+
from ._registrations import get_dbos_func_name
|
16
17
|
|
17
18
|
_KafkaConsumerWorkflow = Callable[[KafkaMessage], None]
|
18
19
|
|
@@ -44,7 +45,7 @@ def _kafka_consumer_loop(
|
|
44
45
|
config["auto.offset.reset"] = "earliest"
|
45
46
|
|
46
47
|
if config.get("group.id") is None:
|
47
|
-
config["group.id"] = safe_group_name(func
|
48
|
+
config["group.id"] = safe_group_name(get_dbos_func_name(func), topics)
|
48
49
|
dbos_logger.warning(
|
49
50
|
f"Consumer group ID not found. Using generated group.id {config['group.id']}"
|
50
51
|
)
|
dbos/_registrations.py
CHANGED
@@ -4,15 +4,17 @@ from enum import Enum
|
|
4
4
|
from types import FunctionType
|
5
5
|
from typing import Any, Callable, List, Literal, Optional, Tuple, Type, cast
|
6
6
|
|
7
|
+
from dbos._error import DBOSWorkflowFunctionNotFoundError
|
8
|
+
|
7
9
|
DEFAULT_MAX_RECOVERY_ATTEMPTS = 100
|
8
10
|
|
9
11
|
|
10
12
|
def get_dbos_func_name(f: Any) -> str:
|
11
13
|
if hasattr(f, "dbos_function_name"):
|
12
14
|
return str(getattr(f, "dbos_function_name"))
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
raise DBOSWorkflowFunctionNotFoundError(
|
16
|
+
"<NONE>", f"function {f.__name__} is not registered"
|
17
|
+
)
|
16
18
|
|
17
19
|
|
18
20
|
def set_dbos_func_name(f: Any, name: str) -> None:
|
dbos/_roles.py
CHANGED
@@ -10,6 +10,7 @@ from ._context import DBOSAssumeRole, get_local_dbos_context
|
|
10
10
|
from ._registrations import (
|
11
11
|
DBOSFuncInfo,
|
12
12
|
get_class_info_for_func,
|
13
|
+
get_dbos_func_name,
|
13
14
|
get_or_create_class_info,
|
14
15
|
get_or_create_func_info,
|
15
16
|
)
|
@@ -36,7 +37,7 @@ def check_required_roles(
|
|
36
37
|
ctx = get_local_dbos_context()
|
37
38
|
if ctx is None or ctx.authenticated_roles is None:
|
38
39
|
raise DBOSNotAuthorizedError(
|
39
|
-
f"Function {func
|
40
|
+
f"Function {get_dbos_func_name(func)} requires a role, but was called in a context without authentication information"
|
40
41
|
)
|
41
42
|
|
42
43
|
for r in required_roles:
|
@@ -44,7 +45,7 @@ def check_required_roles(
|
|
44
45
|
return r
|
45
46
|
|
46
47
|
raise DBOSNotAuthorizedError(
|
47
|
-
f"Function {func
|
48
|
+
f"Function {get_dbos_func_name(func)} has required roles, but user is not authenticated for any of them"
|
48
49
|
)
|
49
50
|
|
50
51
|
|
dbos/_scheduler.py
CHANGED
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
|
11
11
|
|
12
12
|
from ._context import SetWorkflowID
|
13
13
|
from ._croniter import croniter # type: ignore
|
14
|
+
from ._registrations import get_dbos_func_name
|
14
15
|
|
15
16
|
ScheduledWorkflow = Callable[[datetime, datetime], None]
|
16
17
|
|
@@ -24,20 +25,22 @@ def scheduler_loop(
|
|
24
25
|
iter = croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
|
25
26
|
except Exception as e:
|
26
27
|
dbos_logger.error(
|
27
|
-
f'Cannot run scheduled function {func
|
28
|
+
f'Cannot run scheduled function {get_dbos_func_name(func)}. Invalid crontab "{cron}"'
|
28
29
|
)
|
29
30
|
while not stop_event.is_set():
|
30
31
|
nextExecTime = iter.get_next(datetime)
|
31
32
|
sleepTime = nextExecTime - datetime.now(timezone.utc)
|
32
33
|
if stop_event.wait(timeout=sleepTime.total_seconds()):
|
33
34
|
return
|
34
|
-
|
35
|
-
|
35
|
+
try:
|
36
|
+
with SetWorkflowID(
|
37
|
+
f"sched-{get_dbos_func_name(func)}-{nextExecTime.isoformat()}"
|
38
|
+
):
|
36
39
|
scheduler_queue.enqueue(func, nextExecTime, datetime.now(timezone.utc))
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
except Exception:
|
41
|
+
dbos_logger.warning(
|
42
|
+
f"Exception encountered in scheduler thread: {traceback.format_exc()})"
|
43
|
+
)
|
41
44
|
|
42
45
|
|
43
46
|
def scheduled(
|
@@ -48,7 +51,7 @@ def scheduled(
|
|
48
51
|
croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
|
49
52
|
except Exception as e:
|
50
53
|
raise ValueError(
|
51
|
-
f'Invalid crontab "{cron}" for scheduled function function {func
|
54
|
+
f'Invalid crontab "{cron}" for scheduled function function {get_dbos_func_name(func)}.'
|
52
55
|
)
|
53
56
|
|
54
57
|
global scheduler_queue
|
dbos/_sys_db.py
CHANGED
@@ -149,6 +149,8 @@ class EnqueueOptionsInternal(TypedDict):
|
|
149
149
|
deduplication_id: Optional[str]
|
150
150
|
# Priority of the workflow on the queue, starting from 1 ~ 2,147,483,647. Default 0 (highest priority).
|
151
151
|
priority: Optional[int]
|
152
|
+
# On what version the workflow is enqueued. Current version if not specified.
|
153
|
+
app_version: Optional[str]
|
152
154
|
|
153
155
|
|
154
156
|
class RecordedResult(TypedDict):
|
@@ -185,7 +187,9 @@ class GetWorkflowsInput:
|
|
185
187
|
self.authenticated_user: Optional[str] = None # The user who ran the workflow.
|
186
188
|
self.start_time: Optional[str] = None # Timestamp in ISO 8601 format
|
187
189
|
self.end_time: Optional[str] = None # Timestamp in ISO 8601 format
|
188
|
-
self.status: Optional[str] =
|
190
|
+
self.status: Optional[List[str]] = (
|
191
|
+
None # Get workflows with one of these statuses
|
192
|
+
)
|
189
193
|
self.application_version: Optional[str] = (
|
190
194
|
None # The application version that ran this workflow. = None
|
191
195
|
)
|
@@ -205,7 +209,7 @@ class GetWorkflowsInput:
|
|
205
209
|
|
206
210
|
class GetQueuedWorkflowsInput(TypedDict):
|
207
211
|
queue_name: Optional[str] # Get workflows belonging to this queue
|
208
|
-
status: Optional[str] # Get workflows with
|
212
|
+
status: Optional[list[str]] # Get workflows with one of these statuses
|
209
213
|
start_time: Optional[str] # Timestamp in ISO 8601 format
|
210
214
|
end_time: Optional[str] # Timestamp in ISO 8601 format
|
211
215
|
limit: Optional[int] # Return up to this many workflows IDs.
|
@@ -832,7 +836,7 @@ class SystemDatabase:
|
|
832
836
|
<= datetime.datetime.fromisoformat(input.end_time).timestamp() * 1000
|
833
837
|
)
|
834
838
|
if input.status:
|
835
|
-
query = query.where(SystemSchema.workflow_status.c.status
|
839
|
+
query = query.where(SystemSchema.workflow_status.c.status.in_(input.status))
|
836
840
|
if input.application_version:
|
837
841
|
query = query.where(
|
838
842
|
SystemSchema.workflow_status.c.application_version
|
@@ -938,10 +942,9 @@ class SystemDatabase:
|
|
938
942
|
SystemSchema.workflow_status.c.queue_name == input["queue_name"]
|
939
943
|
)
|
940
944
|
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
)
|
945
|
+
status = input.get("status", None)
|
946
|
+
if status:
|
947
|
+
query = query.where(SystemSchema.workflow_status.c.status.in_(status))
|
945
948
|
if "start_time" in input and input["start_time"] is not None:
|
946
949
|
query = query.where(
|
947
950
|
SystemSchema.workflow_status.c.created_at
|
dbos/_workflow_commands.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import time
|
2
2
|
import uuid
|
3
3
|
from datetime import datetime
|
4
|
-
from typing import TYPE_CHECKING, List, Optional
|
4
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
5
5
|
|
6
6
|
from dbos._context import get_local_dbos_context
|
7
7
|
|
@@ -23,7 +23,7 @@ def list_workflows(
|
|
23
23
|
sys_db: SystemDatabase,
|
24
24
|
*,
|
25
25
|
workflow_ids: Optional[List[str]] = None,
|
26
|
-
status: Optional[str] = None,
|
26
|
+
status: Optional[Union[str, List[str]]] = None,
|
27
27
|
start_time: Optional[str] = None,
|
28
28
|
end_time: Optional[str] = None,
|
29
29
|
name: Optional[str] = None,
|
@@ -39,7 +39,7 @@ def list_workflows(
|
|
39
39
|
input.authenticated_user = user
|
40
40
|
input.start_time = start_time
|
41
41
|
input.end_time = end_time
|
42
|
-
input.status = status
|
42
|
+
input.status = status if status is None or isinstance(status, list) else [status]
|
43
43
|
input.application_version = app_version
|
44
44
|
input.limit = limit
|
45
45
|
input.name = name
|
@@ -56,7 +56,7 @@ def list_queued_workflows(
|
|
56
56
|
sys_db: SystemDatabase,
|
57
57
|
*,
|
58
58
|
queue_name: Optional[str] = None,
|
59
|
-
status: Optional[str] = None,
|
59
|
+
status: Optional[Union[str, List[str]]] = None,
|
60
60
|
start_time: Optional[str] = None,
|
61
61
|
end_time: Optional[str] = None,
|
62
62
|
name: Optional[str] = None,
|
@@ -68,7 +68,7 @@ def list_queued_workflows(
|
|
68
68
|
"queue_name": queue_name,
|
69
69
|
"start_time": start_time,
|
70
70
|
"end_time": end_time,
|
71
|
-
"status": status,
|
71
|
+
"status": status if status is None or isinstance(status, list) else [status],
|
72
72
|
"limit": limit,
|
73
73
|
"name": name,
|
74
74
|
"offset": offset,
|
@@ -1,19 +1,19 @@
|
|
1
|
-
dbos-1.6.
|
2
|
-
dbos-1.6.
|
3
|
-
dbos-1.6.
|
4
|
-
dbos-1.6.
|
1
|
+
dbos-1.6.0a4.dist-info/METADATA,sha256=qmxH1Y187IZR58n2_V79zrLcguOtrLG_A9ufqJluIZU,13267
|
2
|
+
dbos-1.6.0a4.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
dbos-1.6.0a4.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
4
|
+
dbos-1.6.0a4.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
5
5
|
dbos/__init__.py,sha256=NssPCubaBxdiKarOWa-wViz1hdJSkmBGcpLX_gQ4NeA,891
|
6
6
|
dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
|
7
7
|
dbos/_admin_server.py,sha256=l46ZX4NpvBP9W8cl9gE7OqMNwUCevLMt2VztM7crBv0,15465
|
8
8
|
dbos/_app_db.py,sha256=htblDPfqrpb_uZoFcvaud7cgQ-PDyn6Bn-cBidxdCTA,10603
|
9
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
10
|
-
dbos/_client.py,sha256=
|
10
|
+
dbos/_client.py,sha256=DeiJHo5fTedWsipr7qlQQIcDmVAPjzzX94X01121oQM,14780
|
11
11
|
dbos/_conductor/conductor.py,sha256=y_T-8kEHwKWt6W8LtcFMctB_6EvYFWsuGLxiFuuKKBU,23702
|
12
12
|
dbos/_conductor/protocol.py,sha256=DOTprPSd7oHDcvwWSyZpnlPds_JfILtcKzHZa-qBsF4,7330
|
13
|
-
dbos/_context.py,sha256=
|
14
|
-
dbos/_core.py,sha256=
|
13
|
+
dbos/_context.py,sha256=5VrCnxSBVq2iOm-Kq_zUeQpEHi5lx2VN8AhMTMG0brQ,25167
|
14
|
+
dbos/_core.py,sha256=rwibo6K7rmg9LScTw3NEXEzgzUQTuAoNrtoRIigGwDQ,49548
|
15
15
|
dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
|
16
|
-
dbos/_dbos.py,sha256=
|
16
|
+
dbos/_dbos.py,sha256=qzXD55bGJJW2SxI6HESykDRIpBmODNwIUt_jRkcRBVw,47588
|
17
17
|
dbos/_dbos_config.py,sha256=JUG4V1rrP0p1AYESgih4ea80qOH_13UsgoIIm8X84pw,20562
|
18
18
|
dbos/_debug.py,sha256=99j2SChWmCPAlZoDmjsJGe77tpU2LEa8E2TtLAnnh7o,1831
|
19
19
|
dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
|
@@ -21,7 +21,7 @@ dbos/_error.py,sha256=nS7KuXJHhuNXZRErxdEUGT38Hb0VPyxNwSyADiVpHcE,8581
|
|
21
21
|
dbos/_event_loop.py,sha256=cvaFN9-II3MsHEOq8QoICc_8qSKrjikMlLfuhC3Y8Dk,2923
|
22
22
|
dbos/_fastapi.py,sha256=T7YlVY77ASqyTqq0aAPclZ9YzlXdGTT0lEYSwSgt1EE,3151
|
23
23
|
dbos/_flask.py,sha256=Npnakt-a3W5OykONFRkDRnumaDhTQmA0NPdUCGRYKXE,1652
|
24
|
-
dbos/_kafka.py,sha256=
|
24
|
+
dbos/_kafka.py,sha256=Gm4fHWl7gYb-i5BMvwNwm5Km3z8zQpseqdMgqgFjlGI,4252
|
25
25
|
dbos/_kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
|
26
26
|
dbos/_logger.py,sha256=Dp6bHZKUtcm5gWwYHj_HA5Wj5OMuJGUrpl2g2i4xDZg,4620
|
27
27
|
dbos/_migrations/env.py,sha256=38SIGVbmn_VV2x2u1aHLcPOoWgZ84eCymf3g_NljmbU,1626
|
@@ -42,14 +42,14 @@ dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256
|
|
42
42
|
dbos/_outcome.py,sha256=Kz3aL7517q9UEFTx3Cq9zzztjWyWVOx_08fZyHo9dvg,7035
|
43
43
|
dbos/_queue.py,sha256=Kq7aldTDLRF7cZtkXmsCy6wV2PR24enkhghEG25NtaU,4080
|
44
44
|
dbos/_recovery.py,sha256=TBNjkmSEqBU-g5YXExsLJ9XoCe4iekqtREsskXZECEg,2507
|
45
|
-
dbos/_registrations.py,sha256=
|
46
|
-
dbos/_roles.py,sha256=
|
47
|
-
dbos/_scheduler.py,sha256=
|
45
|
+
dbos/_registrations.py,sha256=U-PwDZBuyuJjA2LYtud7D3VxDR440mVpMYE-S11BWDo,7369
|
46
|
+
dbos/_roles.py,sha256=kCuhhg8XLtrHCgKgm44I0abIRTGHltf88OwjEKAUggk,2317
|
47
|
+
dbos/_scheduler.py,sha256=CWeGVfl9h51VXfxt80y5Da_5pE8SPty_AYkfpJkkMxQ,2117
|
48
48
|
dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
49
|
dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
|
50
50
|
dbos/_schemas/system_database.py,sha256=rbFKggONdvvbb45InvGz0TM6a7c-Ux9dcaL-h_7Z7pU,4438
|
51
51
|
dbos/_serialization.py,sha256=bWuwhXSQcGmiazvhJHA5gwhrRWxtmFmcCFQSDJnqqkU,3666
|
52
|
-
dbos/_sys_db.py,sha256=
|
52
|
+
dbos/_sys_db.py,sha256=yhwhH23QvehbhPW3k6f4TRQ6mDjmvMILqsR8YffFZBg,80368
|
53
53
|
dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
|
54
54
|
dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
dbos/_templates/dbos-db-starter/__package/main.py.dbos,sha256=aQnBPSSQpkB8ERfhf7gB7P9tsU6OPKhZscfeh0yiaD8,2702
|
@@ -62,11 +62,11 @@ dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py,sh
|
|
62
62
|
dbos/_templates/dbos-db-starter/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
|
63
63
|
dbos/_tracer.py,sha256=RnlcaOJEx_58hr2J9L9g6E7gjAHAeEtEGugJZmCwNfQ,2963
|
64
64
|
dbos/_utils.py,sha256=uywq1QrjMwy17btjxW4bES49povlQwYwYbvKwMT6C2U,1575
|
65
|
-
dbos/_workflow_commands.py,sha256=
|
65
|
+
dbos/_workflow_commands.py,sha256=4QCs7ziQ9T457tqfaNFwiXd6mDisr-ZK__skz1Uteyg,4648
|
66
66
|
dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
|
67
67
|
dbos/cli/_template_init.py,sha256=7JBcpMqP1r2mfCnvWatu33z8ctEGHJarlZYKgB83cXE,2972
|
68
68
|
dbos/cli/cli.py,sha256=IcfaX4rrSrk6f24S2jrlR33snYMyNyEIx_lNQtuVr2E,22081
|
69
69
|
dbos/dbos-config.schema.json,sha256=CjaspeYmOkx6Ip_pcxtmfXJTn_YGdSx_0pcPBF7KZmo,6060
|
70
70
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
71
71
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
72
|
-
dbos-1.6.
|
72
|
+
dbos-1.6.0a4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|