dbos 1.11.0a6__py3-none-any.whl → 1.12.0__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.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

dbos/_context.py CHANGED
@@ -753,3 +753,34 @@ class DBOSAssumeRole:
753
753
  assert ctx.assumed_role == self.assume_role
754
754
  ctx.assumed_role = self.prior_role
755
755
  return False # Did not handle
756
+
757
+
758
+ class UseLogAttributes:
759
+ """Temporarily set context attributes for logging"""
760
+
761
+ def __init__(self, *, workflow_id: str = "") -> None:
762
+ self.workflow_id = workflow_id
763
+ self.created_ctx = False
764
+
765
+ def __enter__(self) -> UseLogAttributes:
766
+ ctx = get_local_dbos_context()
767
+ if ctx is None:
768
+ self.created_ctx = True
769
+ _set_local_dbos_context(DBOSContext())
770
+ ctx = assert_current_dbos_context()
771
+ self.saved_workflow_id = ctx.workflow_id
772
+ ctx.workflow_id = self.workflow_id
773
+ return self
774
+
775
+ def __exit__(
776
+ self,
777
+ exc_type: Optional[Type[BaseException]],
778
+ exc_value: Optional[BaseException],
779
+ traceback: Optional[TracebackType],
780
+ ) -> Literal[False]:
781
+ ctx = assert_current_dbos_context()
782
+ ctx.workflow_id = self.saved_workflow_id
783
+ # Clean up the basic context if we created it
784
+ if self.created_ctx:
785
+ _clear_local_dbos_context()
786
+ return False # Did not handle
dbos/_core.py CHANGED
@@ -5,7 +5,6 @@ import json
5
5
  import sys
6
6
  import threading
7
7
  import time
8
- import traceback
9
8
  from concurrent.futures import Future
10
9
  from functools import wraps
11
10
  from typing import (
@@ -66,7 +65,6 @@ from ._registrations import (
66
65
  get_dbos_func_name,
67
66
  get_func_info,
68
67
  get_or_create_func_info,
69
- get_temp_workflow_type,
70
68
  set_dbos_func_name,
71
69
  set_func_info,
72
70
  set_temp_workflow_type,
@@ -452,7 +450,7 @@ def execute_workflow_by_id(dbos: "DBOS", workflow_id: str) -> "WorkflowHandle[An
452
450
  if not wf_func:
453
451
  raise DBOSWorkflowFunctionNotFoundError(
454
452
  workflow_id,
455
- f"Cannot execute workflow because {status['name']} is not a registered workflow function",
453
+ f"{status['name']} is not a registered workflow function",
456
454
  )
457
455
  with DBOSContextEnsure():
458
456
  # If this function belongs to a configured class, add that class instance as its first argument
@@ -463,7 +461,7 @@ def execute_workflow_by_id(dbos: "DBOS", workflow_id: str) -> "WorkflowHandle[An
463
461
  if iname not in dbos._registry.instance_info_map:
464
462
  raise DBOSWorkflowFunctionNotFoundError(
465
463
  workflow_id,
466
- f"Cannot execute workflow because instance '{iname}' is not registered",
464
+ f"configured class instance '{iname}' is not registered",
467
465
  )
468
466
  class_instance = dbos._registry.instance_info_map[iname]
469
467
  inputs["args"] = (class_instance,) + inputs["args"]
@@ -473,7 +471,7 @@ def execute_workflow_by_id(dbos: "DBOS", workflow_id: str) -> "WorkflowHandle[An
473
471
  if class_name not in dbos._registry.class_info_map:
474
472
  raise DBOSWorkflowFunctionNotFoundError(
475
473
  workflow_id,
476
- f"Cannot execute workflow because class '{class_name}' is not registered",
474
+ f"class '{class_name}' is not registered",
477
475
  )
478
476
  class_object = dbos._registry.class_info_map[class_name]
479
477
  inputs["args"] = (class_object,) + inputs["args"]
@@ -534,7 +532,7 @@ def start_workflow(
534
532
  if fi is None:
535
533
  raise DBOSWorkflowFunctionNotFoundError(
536
534
  "<NONE>",
537
- f"start_workflow: function {func.__name__} is not registered",
535
+ f"{func.__name__} is not a registered workflow function",
538
536
  )
539
537
 
540
538
  func = cast("Workflow[P, R]", func.__orig_func) # type: ignore
@@ -630,7 +628,7 @@ async def start_workflow_async(
630
628
  if fi is None:
631
629
  raise DBOSWorkflowFunctionNotFoundError(
632
630
  "<NONE>",
633
- f"start_workflow: function {func.__name__} is not registered",
631
+ f"{func.__name__} is not a registered workflow function",
634
632
  )
635
633
 
636
634
  func = cast("Workflow[P, R]", func.__orig_func) # type: ignore
@@ -1158,27 +1156,15 @@ def decorate_step(
1158
1156
 
1159
1157
  @wraps(func)
1160
1158
  def wrapper(*args: Any, **kwargs: Any) -> Any:
1161
- rr: Optional[str] = check_required_roles(func, fi)
1162
- # Entering step is allowed:
1163
- # No DBOS, just call the original function directly
1164
- # In a step already, just call the original function directly.
1165
- # In a workflow (that is not in a step already)
1166
- # Not in a workflow (we will start the single op workflow)
1167
- if not dbosreg.dbos or not dbosreg.dbos._launched:
1168
- # Call the original function directly
1169
- return func(*args, **kwargs)
1159
+ # If the step is called from a workflow, run it as a step.
1160
+ # Otherwise, run it as a normal function.
1170
1161
  ctx = get_local_dbos_context()
1171
- if ctx and ctx.is_step():
1172
- # Call the original function directly
1173
- return func(*args, **kwargs)
1174
- if ctx and ctx.is_within_workflow():
1175
- assert ctx.is_workflow(), "Steps must be called from within workflows"
1162
+ if ctx and ctx.is_workflow():
1163
+ rr: Optional[str] = check_required_roles(func, fi)
1176
1164
  with DBOSAssumeRole(rr):
1177
1165
  return invoke_step(*args, **kwargs)
1178
1166
  else:
1179
- tempwf = dbosreg.workflow_info_map.get("<temp>." + step_name)
1180
- assert tempwf
1181
- return tempwf(*args, **kwargs)
1167
+ return func(*args, **kwargs)
1182
1168
 
1183
1169
  wrapper = (
1184
1170
  _mark_coroutine(wrapper) if inspect.iscoroutinefunction(func) else wrapper # type: ignore
dbos/_dbos.py CHANGED
@@ -211,6 +211,10 @@ class DBOSRegistry:
211
211
  def register_instance(self, inst: object) -> None:
212
212
  config_name = getattr(inst, "config_name")
213
213
  class_name = _class_fqn(inst.__class__)
214
+ if self.dbos and self.dbos._launched:
215
+ dbos_logger.warning(
216
+ f"Configured instance {config_name} of class {class_name} was registered after DBOS was launched. This may cause errors during workflow recovery. All configured instances should be instantiated before DBOS is launched."
217
+ )
214
218
  fn = f"{class_name}/{config_name}"
215
219
  if fn in self.instance_info_map:
216
220
  if self.instance_info_map[fn] is not inst:
dbos/_error.py CHANGED
@@ -106,7 +106,7 @@ class DBOSWorkflowFunctionNotFoundError(DBOSException):
106
106
 
107
107
  def __init__(self, workflow_id: str, message: Optional[str] = None):
108
108
  super().__init__(
109
- f"Workflow function not found for workflow ID {workflow_id}: {message}",
109
+ f"Could not execute workflow {workflow_id}: {message}",
110
110
  dbos_error_code=DBOSErrorCode.WorkflowFunctionNotFound.value,
111
111
  )
112
112
 
dbos/_fastapi.py CHANGED
@@ -83,4 +83,6 @@ def setup_fastapi_middleware(app: FastAPI, dbos: DBOS) -> None:
83
83
  response = await call_next(request)
84
84
  else:
85
85
  response = await call_next(request)
86
+ if hasattr(response, "status_code"):
87
+ DBOS.span.set_attribute("responseCode", response.status_code)
86
88
  return response
dbos/_recovery.py CHANGED
@@ -2,6 +2,7 @@ import threading
2
2
  import time
3
3
  from typing import TYPE_CHECKING, Any, List
4
4
 
5
+ from dbos._context import UseLogAttributes
5
6
  from dbos._utils import GlobalParams
6
7
 
7
8
  from ._core import execute_workflow_by_id
@@ -29,17 +30,19 @@ def startup_recovery_thread(
29
30
  stop_event = threading.Event()
30
31
  dbos.background_thread_stop_events.append(stop_event)
31
32
  while not stop_event.is_set() and len(pending_workflows) > 0:
32
- try:
33
- for pending_workflow in list(pending_workflows):
33
+ for pending_workflow in list(pending_workflows):
34
+ try:
34
35
  _recover_workflow(dbos, pending_workflow)
35
36
  pending_workflows.remove(pending_workflow)
36
- except DBOSWorkflowFunctionNotFoundError:
37
- time.sleep(1)
38
- except Exception as e:
39
- dbos.logger.error(
40
- f"Exception encountered when recovering workflows:", exc_info=e
41
- )
42
- raise
37
+ except DBOSWorkflowFunctionNotFoundError:
38
+ time.sleep(1)
39
+ except Exception as e:
40
+ with UseLogAttributes(workflow_id=pending_workflow.workflow_uuid):
41
+ dbos.logger.error(
42
+ f"Exception encountered when recovering workflow {pending_workflow.workflow_uuid}:",
43
+ exc_info=e,
44
+ )
45
+ raise
43
46
 
44
47
 
45
48
  def recover_pending_workflows(
@@ -56,9 +59,11 @@ def recover_pending_workflows(
56
59
  handle = _recover_workflow(dbos, pending_workflow)
57
60
  workflow_handles.append(handle)
58
61
  except Exception as e:
59
- dbos.logger.error(
60
- f"Exception encountered when recovering workflows:", exc_info=e
61
- )
62
+ with UseLogAttributes(workflow_id=pending_workflow.workflow_uuid):
63
+ dbos.logger.error(
64
+ f"Exception encountered when recovering workflow {pending_workflow.workflow_uuid}:",
65
+ exc_info=e,
66
+ )
62
67
  raise
63
68
  dbos.logger.info(
64
69
  f"Recovering {len(pending_workflows)} workflows for executor {executor_id} from version {GlobalParams.app_version}"
dbos/_registrations.py CHANGED
@@ -13,7 +13,7 @@ def get_dbos_func_name(f: Any) -> str:
13
13
  if hasattr(f, "dbos_function_name"):
14
14
  return str(getattr(f, "dbos_function_name"))
15
15
  raise DBOSWorkflowFunctionNotFoundError(
16
- "<NONE>", f"function {f.__name__} is not registered"
16
+ "<NONE>", f"{f.__name__} is not a registered workflow function"
17
17
  )
18
18
 
19
19
 
dbos/cli/cli.py CHANGED
@@ -14,6 +14,7 @@ from rich import print as richprint
14
14
  from rich.prompt import IntPrompt
15
15
  from typing_extensions import Annotated, List
16
16
 
17
+ from dbos._context import SetWorkflowID
17
18
  from dbos._debug import debug_workflow, parse_start_command
18
19
  from dbos.cli.migration import grant_dbos_schema_permissions, migrate_dbos_databases
19
20
 
@@ -567,7 +568,9 @@ def resume(
567
568
  start_client(db_url=db_url).resume_workflow(workflow_id=workflow_id)
568
569
 
569
570
 
570
- @workflow.command(help="Restart a workflow from the beginning with a new id")
571
+ @workflow.command(
572
+ help="[DEPRECATED - Use fork instead] Restart a workflow from the beginning with a new id"
573
+ )
571
574
  def restart(
572
575
  workflow_id: Annotated[str, typer.Argument()],
573
576
  db_url: Annotated[
@@ -600,6 +603,22 @@ def fork(
600
603
  help="Restart from this step",
601
604
  ),
602
605
  ] = 1,
606
+ forked_workflow_id: Annotated[
607
+ typing.Optional[str],
608
+ typer.Option(
609
+ "--forked-workflow-id",
610
+ "-f",
611
+ help="Custom ID for the forked workflow",
612
+ ),
613
+ ] = None,
614
+ application_version: Annotated[
615
+ typing.Optional[str],
616
+ typer.Option(
617
+ "--application-version",
618
+ "-v",
619
+ help="Custom application version for the forked workflow",
620
+ ),
621
+ ] = None,
603
622
  db_url: Annotated[
604
623
  typing.Optional[str],
605
624
  typer.Option(
@@ -609,11 +628,21 @@ def fork(
609
628
  ),
610
629
  ] = None,
611
630
  ) -> None:
612
- status = (
613
- start_client(db_url=db_url)
614
- .fork_workflow(workflow_id=workflow_id, start_step=step)
615
- .get_status()
616
- )
631
+ client = start_client(db_url=db_url)
632
+
633
+ if forked_workflow_id is not None:
634
+ with SetWorkflowID(forked_workflow_id):
635
+ status = client.fork_workflow(
636
+ workflow_id=workflow_id,
637
+ start_step=step,
638
+ application_version=application_version,
639
+ ).get_status()
640
+ else:
641
+ status = client.fork_workflow(
642
+ workflow_id=workflow_id,
643
+ start_step=step,
644
+ application_version=application_version,
645
+ ).get_status()
617
646
  print(jsonpickle.encode(status, unpicklable=False))
618
647
 
619
648
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.11.0a6
3
+ Version: 1.12.0
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- dbos-1.11.0a6.dist-info/METADATA,sha256=O9219TX0-H9Om5McaJsl8XC3AAcWas3Qp5yj_ymVc0Y,13268
2
- dbos-1.11.0a6.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- dbos-1.11.0a6.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-1.11.0a6.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-1.12.0.dist-info/METADATA,sha256=huvKLTHqRW0T_zbVep_04IpvbfFsusCLJD6gY-rIKLQ,13266
2
+ dbos-1.12.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ dbos-1.12.0.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-1.12.0.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=e8ELhcDWqR3_PNobnNgUvLGh5lzZq0yFSF6dvtzoQRI,16267
@@ -10,16 +10,16 @@ dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
10
10
  dbos/_client.py,sha256=_wMe4qnRSwiRZo74xdqTBetbHlIVy3vQifdSd7os1ZY,18213
11
11
  dbos/_conductor/conductor.py,sha256=3E_hL3c9g9yWqKZkvI6KA0-ZzPMPRo06TOzT1esMiek,24114
12
12
  dbos/_conductor/protocol.py,sha256=q3rgLxINFtWFigdOONc-4gX4vn66UmMlJQD6Kj8LnL4,7420
13
- dbos/_context.py,sha256=0vFtLAk3WF5BQYIYNFImDRBppKO2CTKOSy51zQC-Cu8,25723
14
- dbos/_core.py,sha256=kpEOK9CclOug3zB7tyImnUOTLKKyfw7DVg1qFPJanwM,49670
13
+ dbos/_context.py,sha256=8yZOTM1ehhk6URLa0EP9_20aOd6SZLhXBmcPwFEZDlA,26739
14
+ dbos/_core.py,sha256=tKAahVW7StJ_KuW4e1fWnCSE5-2SaI7RZIMWZWcuzm8,48848
15
15
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
16
- dbos/_dbos.py,sha256=h0ZtJNElMB4R2T1320jYD3PXKenn-xCLxnSkIiqpFVg,57386
16
+ dbos/_dbos.py,sha256=bn5S_T6HZnDipYEVqhLq_F2Zo904fjT3J5oO15Frhrs,57715
17
17
  dbos/_dbos_config.py,sha256=er8oF3e9zGlEG9KntX7uBSXrDuVvROtkzVidzXjOwUU,21746
18
18
  dbos/_debug.py,sha256=99j2SChWmCPAlZoDmjsJGe77tpU2LEa8E2TtLAnnh7o,1831
19
19
  dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
20
- dbos/_error.py,sha256=2_2ve3qN0BLhFWMmd3aD9At54tIMCeh8TqHtVcEEVQo,8705
20
+ dbos/_error.py,sha256=GwO0Ng4d4iB52brY09-Ss6Cz_V28Xc0D0cRCzZ6XmNM,8688
21
21
  dbos/_event_loop.py,sha256=cvaFN9-II3MsHEOq8QoICc_8qSKrjikMlLfuhC3Y8Dk,2923
22
- dbos/_fastapi.py,sha256=T7YlVY77ASqyTqq0aAPclZ9YzlXdGTT0lEYSwSgt1EE,3151
22
+ dbos/_fastapi.py,sha256=cXqXAf0pM0wMM6YZPkRqHVToeRszzRasmk01HDj1aNc,3278
23
23
  dbos/_flask.py,sha256=Npnakt-a3W5OykONFRkDRnumaDhTQmA0NPdUCGRYKXE,1652
24
24
  dbos/_kafka.py,sha256=Gm4fHWl7gYb-i5BMvwNwm5Km3z8zQpseqdMgqgFjlGI,4252
25
25
  dbos/_kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
@@ -42,8 +42,8 @@ dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT
42
42
  dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256=m90Lc5YH0ZISSq1MyxND6oq3RZrZKrIqEsZtwJ1jWxA,1049
43
43
  dbos/_outcome.py,sha256=Kz3aL7517q9UEFTx3Cq9zzztjWyWVOx_08fZyHo9dvg,7035
44
44
  dbos/_queue.py,sha256=0kJTPwXy3nZ4Epzt-lHky9M9S4L31645drPGFR8fIJY,4854
45
- dbos/_recovery.py,sha256=TBNjkmSEqBU-g5YXExsLJ9XoCe4iekqtREsskXZECEg,2507
46
- dbos/_registrations.py,sha256=U-PwDZBuyuJjA2LYtud7D3VxDR440mVpMYE-S11BWDo,7369
45
+ dbos/_recovery.py,sha256=K-wlFhdf4yGRm6cUzyhcTjQUS0xp2T5rdNMLiiBErYg,2882
46
+ dbos/_registrations.py,sha256=bEOntObnWaBylnebr5ZpcX2hk7OVLDd1z4BvW4_y3zA,7380
47
47
  dbos/_roles.py,sha256=kCuhhg8XLtrHCgKgm44I0abIRTGHltf88OwjEKAUggk,2317
48
48
  dbos/_scheduler.py,sha256=CWeGVfl9h51VXfxt80y5Da_5pE8SPty_AYkfpJkkMxQ,2117
49
49
  dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -66,9 +66,9 @@ dbos/_utils.py,sha256=uywq1QrjMwy17btjxW4bES49povlQwYwYbvKwMT6C2U,1575
66
66
  dbos/_workflow_commands.py,sha256=EmmAaQfRWeOZm_WPTznuU-O3he3jiSzzT9VpYrhxugE,4835
67
67
  dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
68
68
  dbos/cli/_template_init.py,sha256=7JBcpMqP1r2mfCnvWatu33z8ctEGHJarlZYKgB83cXE,2972
69
- dbos/cli/cli.py,sha256=ey7E-lNFgvUWhsd-mkFwZvTdYorv6hU2zsMOS23n1yQ,22214
69
+ dbos/cli/cli.py,sha256=btUbl0L_1cf46W8z0Hi6nPBCLaSqY9I4c1eZCG7obow,23128
70
70
  dbos/cli/migration.py,sha256=eI0sc0vYq2iUP3cBHPfTa6WHCyDBr8ld9nRxEZZzFrU,3316
71
71
  dbos/dbos-config.schema.json,sha256=CjaspeYmOkx6Ip_pcxtmfXJTn_YGdSx_0pcPBF7KZmo,6060
72
72
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
73
73
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
74
- dbos-1.11.0a6.dist-info/RECORD,,
74
+ dbos-1.12.0.dist-info/RECORD,,
File without changes