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 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, *, deduplication_id: Optional[str] = None, priority: Optional[int] = None
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": GlobalParams.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.__name__,
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.__name__,
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>", f"start_workflow: function {func.__name__} is not registered"
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>", f"start_workflow: function {func.__name__} is not registered"
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.__name__} invoked before DBOS initialized"
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.__name__,
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
- reg.register_wf_function(func.__qualname__, wrapped_func, "workflow")
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" = "SERIALIZABLE"
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 {func.__name__} invoked before DBOS initialized"
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 {func.__name__}."
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": func.__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 {func.__qualname__} \033[1m Hint: Do not call commit() or rollback() within a DBOS transaction.\033[0m"
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 {func.__name__} is a coroutine function, but DBOS.transaction does not support coroutine functions"
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>." + func.__qualname__)
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>." + func.__qualname__)
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
- retries_allowed: bool = False,
1032
- interval_seconds: float = 1.0,
1033
- max_attempts: int = 3,
1034
- backoff_rate: float = 2.0,
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 {func.__name__} invoked before DBOS initialized"
1060
+ f"Function {step_name} invoked before DBOS initialized"
1044
1061
  )
1045
1062
  dbos = dbosreg.dbos
1046
1063
 
1047
1064
  attributes: TracedAttributes = {
1048
- "name": func.__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(func.__name__, i, e),
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>." + func.__qualname__)
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>." + func.__qualname__)
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, *, max_recovery_attempts: Optional[int] = DEFAULT_MAX_RECOVERY_ATTEMPTS
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(_get_or_create_dbos_registry(), max_recovery_attempts)
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, isolation_level: IsolationLevel = "SERIALIZABLE"
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(_get_or_create_dbos_registry(), isolation_level)
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.__qualname__, topics)
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
- if hasattr(f, "__qualname__"):
14
- return str(getattr(f, "__qualname__"))
15
- return "<unknown>"
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.__name__} requires a role, but was called in a context without authentication information"
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.__name__} has required roles, but user is not authenticated for any of them"
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.__name__}. Invalid crontab "{cron}"'
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
- with SetWorkflowID(f"sched-{func.__qualname__}-{nextExecTime.isoformat()}"):
35
- try:
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
- except Exception:
38
- dbos_logger.warning(
39
- f"Exception encountered in scheduler thread: {traceback.format_exc()})"
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.__name__}.'
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] = None
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 this status
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 == input.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
- if input.get("status"):
942
- query = query.where(
943
- SystemSchema.workflow_status.c.status == input["status"]
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
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.6.0a1
3
+ Version: 1.6.0a4
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,19 +1,19 @@
1
- dbos-1.6.0a1.dist-info/METADATA,sha256=uefRlKIxEPVJEM0vqwL7TRtTXp48I0YLll8XfJeSFRk,13267
2
- dbos-1.6.0a1.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- dbos-1.6.0a1.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-1.6.0a1.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
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=cQxw1Nbh_vKZ03lONt0EmUhwXBk3B3NczZrmfXXeefY,14667
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=5ajoWAmToAfzzmMLylnJZoL4Ny9rBwZWuG05sXadMIA,24798
14
- dbos/_core.py,sha256=OFxmPjgnOh-uv7-G1f1GrQrSSp-X6WzoIvlX9O0cBqE,48596
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=HHMo-PLUa6m5jFgdA-YYAwaTx2kyA1Te1VslAyd0hIw,47257
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=pz0xZ9F3X9Ky1k-VSbeF3tfPhP3UPr3lUUhUfE41__U,4198
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=CZt1ElqDjCT7hz6iyT-1av76Yu-iuwu_c9lozO87wvM,7303
46
- dbos/_roles.py,sha256=iOsgmIAf1XVzxs3gYWdGRe1B880YfOw5fpU7Jwx8_A8,2271
47
- dbos/_scheduler.py,sha256=SR1oRZRcVzYsj-JauV2LA8JtwTkt8mru7qf6H1AzQ1U,2027
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=now889o6Mlmcdopp8xF5_0LAE67KeVH9Vm-4svIqo5s,80170
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=Fi-sQxQvFkDkMlCv7EyRJIWxqk3fG6DGlgvvwkjWbS4,4485
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.0a1.dist-info/RECORD,,
72
+ dbos-1.6.0a4.dist-info/RECORD,,
File without changes