dbos 0.23.0a11__tar.gz → 0.23.0a13__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.

Files changed (97) hide show
  1. {dbos-0.23.0a11 → dbos-0.23.0a13}/PKG-INFO +1 -1
  2. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_conductor/conductor.py +20 -1
  3. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_conductor/protocol.py +11 -0
  4. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_recovery.py +18 -27
  5. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_sys_db.py +4 -2
  6. {dbos-0.23.0a11 → dbos-0.23.0a13}/pyproject.toml +1 -1
  7. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_queue.py +17 -8
  8. {dbos-0.23.0a11 → dbos-0.23.0a13}/LICENSE +0 -0
  9. {dbos-0.23.0a11 → dbos-0.23.0a13}/README.md +0 -0
  10. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/__init__.py +0 -0
  11. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/__main__.py +0 -0
  12. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_admin_server.py +0 -0
  13. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_app_db.py +0 -0
  14. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_classproperty.py +0 -0
  15. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_cloudutils/authentication.py +0 -0
  16. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_cloudutils/cloudutils.py +0 -0
  17. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_cloudutils/databases.py +0 -0
  18. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_context.py +0 -0
  19. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_core.py +0 -0
  20. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_croniter.py +0 -0
  21. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_db_wizard.py +0 -0
  22. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_dbos.py +0 -0
  23. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_dbos_config.py +0 -0
  24. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_debug.py +0 -0
  25. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_error.py +0 -0
  26. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_fastapi.py +0 -0
  27. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_flask.py +0 -0
  28. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_kafka.py +0 -0
  29. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_kafka_message.py +0 -0
  30. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_logger.py +0 -0
  31. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/env.py +0 -0
  32. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/script.py.mako +0 -0
  33. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  34. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  35. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  36. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  37. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  38. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  39. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  40. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_outcome.py +0 -0
  41. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_queue.py +0 -0
  42. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_registrations.py +0 -0
  43. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_request.py +0 -0
  44. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_roles.py +0 -0
  45. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_scheduler.py +0 -0
  46. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_schemas/__init__.py +0 -0
  47. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_schemas/application_database.py +0 -0
  48. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_schemas/system_database.py +0 -0
  49. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_serialization.py +0 -0
  50. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/README.md +0 -0
  51. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  52. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  53. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  54. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  55. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  56. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  57. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  58. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  59. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  60. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_tracer.py +0 -0
  61. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_utils.py +0 -0
  62. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/_workflow_commands.py +0 -0
  63. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/cli/_github_init.py +0 -0
  64. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/cli/_template_init.py +0 -0
  65. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/cli/cli.py +0 -0
  66. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/dbos-config.schema.json +0 -0
  67. {dbos-0.23.0a11 → dbos-0.23.0a13}/dbos/py.typed +0 -0
  68. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/__init__.py +0 -0
  69. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/atexit_no_ctor.py +0 -0
  70. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/atexit_no_launch.py +0 -0
  71. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/classdefs.py +0 -0
  72. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/conftest.py +0 -0
  73. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/more_classdefs.py +0 -0
  74. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/queuedworkflow.py +0 -0
  75. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_admin_server.py +0 -0
  76. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_async.py +0 -0
  77. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_classdecorators.py +0 -0
  78. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_concurrency.py +0 -0
  79. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_config.py +0 -0
  80. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_croniter.py +0 -0
  81. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_dbos.py +0 -0
  82. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_debug.py +0 -0
  83. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_failures.py +0 -0
  84. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_fastapi.py +0 -0
  85. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_fastapi_roles.py +0 -0
  86. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_flask.py +0 -0
  87. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_kafka.py +0 -0
  88. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_outcome.py +0 -0
  89. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_package.py +0 -0
  90. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_scheduler.py +0 -0
  91. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_schema_migration.py +0 -0
  92. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_singleton.py +0 -0
  93. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_spans.py +0 -0
  94. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_sqlalchemy.py +0 -0
  95. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_workflow_cancel.py +0 -0
  96. {dbos-0.23.0a11 → dbos-0.23.0a13}/tests/test_workflow_cmds.py +0 -0
  97. {dbos-0.23.0a11 → dbos-0.23.0a13}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.23.0a11
3
+ Version: 0.23.0a13
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -8,7 +8,7 @@ from websockets.sync.client import connect
8
8
  from websockets.sync.connection import Connection
9
9
 
10
10
  from dbos._utils import GlobalParams
11
- from dbos._workflow_commands import list_queued_workflows, list_workflows
11
+ from dbos._workflow_commands import get_workflow, list_queued_workflows, list_workflows
12
12
 
13
13
  from . import protocol as p
14
14
 
@@ -175,6 +175,25 @@ class ConductorWebsocket(threading.Thread):
175
175
  )
176
176
  )
177
177
  websocket.send(list_queued_workflows_response.to_json())
178
+ elif type == p.MessageType.GET_WORKFLOW:
179
+ get_workflow_message = p.GetWorkflowRequest.from_json(
180
+ message
181
+ )
182
+ info = get_workflow(
183
+ self.dbos._sys_db,
184
+ get_workflow_message.workflow_id,
185
+ getRequest=False,
186
+ )
187
+ get_workflow_response = p.GetWorkflowResponse(
188
+ type=p.MessageType.GET_WORKFLOW,
189
+ request_id=base_message.request_id,
190
+ output=(
191
+ p.WorkflowsOutput.from_workflow_information(info)
192
+ if info is not None
193
+ else None
194
+ ),
195
+ )
196
+ websocket.send(get_workflow_response.to_json())
178
197
  else:
179
198
  self.dbos.logger.warning(f"Unexpected message type: {type}")
180
199
  except ConnectionClosedOK:
@@ -14,6 +14,7 @@ class MessageType(str, Enum):
14
14
  LIST_QUEUED_WORKFLOWS = "list_queued_workflows"
15
15
  RESUME = "resume"
16
16
  RESTART = "restart"
17
+ GET_WORKFLOW = "get_workflow"
17
18
 
18
19
 
19
20
  T = TypeVar("T", bound="BaseMessage")
@@ -184,3 +185,13 @@ class ListQueuedWorkflowsRequest(BaseMessage):
184
185
  @dataclass
185
186
  class ListQueuedWorkflowsResponse(BaseMessage):
186
187
  output: List[WorkflowsOutput]
188
+
189
+
190
+ @dataclass
191
+ class GetWorkflowRequest(BaseMessage):
192
+ workflow_id: str
193
+
194
+
195
+ @dataclass
196
+ class GetWorkflowResponse(BaseMessage):
197
+ output: Optional[WorkflowsOutput]
@@ -14,6 +14,16 @@ if TYPE_CHECKING:
14
14
  from ._dbos import DBOS, WorkflowHandle
15
15
 
16
16
 
17
+ def _recover_workflow(
18
+ dbos: "DBOS", workflow: GetPendingWorkflowsOutput
19
+ ) -> "WorkflowHandle[Any]":
20
+ if workflow.queue_name and workflow.queue_name != "_dbos_internal_queue":
21
+ cleared = dbos._sys_db.clear_queue_assignment(workflow.workflow_uuid)
22
+ if cleared:
23
+ return dbos.retrieve_workflow(workflow.workflow_uuid)
24
+ return execute_workflow_by_id(dbos, workflow.workflow_uuid)
25
+
26
+
17
27
  def startup_recovery_thread(
18
28
  dbos: "DBOS", pending_workflows: List[GetPendingWorkflowsOutput]
19
29
  ) -> None:
@@ -23,14 +33,7 @@ def startup_recovery_thread(
23
33
  while not stop_event.is_set() and len(pending_workflows) > 0:
24
34
  try:
25
35
  for pending_workflow in list(pending_workflows):
26
- if (
27
- pending_workflow.queue_name
28
- and pending_workflow.queue_name != "_dbos_internal_queue"
29
- ):
30
- cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
31
- if cleared:
32
- continue
33
- execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
36
+ _recover_workflow(dbos, pending_workflow)
34
37
  pending_workflows.remove(pending_workflow)
35
38
  except DBOSWorkflowFunctionNotFoundError:
36
39
  time.sleep(1)
@@ -51,26 +54,14 @@ def recover_pending_workflows(
51
54
  executor_id, GlobalParams.app_version
52
55
  )
53
56
  for pending_workflow in pending_workflows:
54
- if (
55
- pending_workflow.queue_name
56
- and pending_workflow.queue_name != "_dbos_internal_queue"
57
- ):
58
- try:
59
- cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
60
- if cleared:
61
- workflow_handles.append(
62
- dbos.retrieve_workflow(pending_workflow.workflow_uuid)
63
- )
64
- else:
65
- workflow_handles.append(
66
- execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
67
- )
68
- except Exception as e:
69
- dbos.logger.error(e)
70
- else:
71
- workflow_handles.append(
72
- execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
57
+ try:
58
+ handle = _recover_workflow(dbos, pending_workflow)
59
+ workflow_handles.append(handle)
60
+ except Exception as e:
61
+ dbos.logger.error(
62
+ f"Exception encountered when recovering workflows: {traceback.format_exc()}"
73
63
  )
64
+ raise e
74
65
  dbos.logger.info(
75
66
  f"Recovering {len(pending_workflows)} workflows for executor {executor_id} from version {GlobalParams.app_version}"
76
67
  )
@@ -1462,7 +1462,6 @@ class SystemDatabase:
1462
1462
  .values(completed_at_epoch_ms=int(time.time() * 1000))
1463
1463
  )
1464
1464
 
1465
-
1466
1465
  def clear_queue_assignment(self, workflow_id: str) -> bool:
1467
1466
  if self._debug_mode:
1468
1467
  raise Exception("called clear_queue_assignment in debug mode")
@@ -1483,7 +1482,9 @@ class SystemDatabase:
1483
1482
  res = conn.execute(
1484
1483
  sa.update(SystemSchema.workflow_status)
1485
1484
  .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
1486
- .values(executor_id=None, status=WorkflowStatusString.ENQUEUED.value)
1485
+ .values(
1486
+ executor_id=None, status=WorkflowStatusString.ENQUEUED.value
1487
+ )
1487
1488
  )
1488
1489
  if res.rowcount == 0:
1489
1490
  # This should never happen
@@ -1492,6 +1493,7 @@ class SystemDatabase:
1492
1493
  )
1493
1494
  return True
1494
1495
 
1496
+
1495
1497
  def reset_system_database(config: ConfigFile) -> None:
1496
1498
  sysdb_name = (
1497
1499
  config["database"]["sys_db_name"]
@@ -28,7 +28,7 @@ dependencies = [
28
28
  ]
29
29
  requires-python = ">=3.9"
30
30
  readme = "README.md"
31
- version = "0.23.0a11"
31
+ version = "0.23.0a13"
32
32
 
33
33
  [project.license]
34
34
  text = "MIT"
@@ -902,10 +902,11 @@ def test_resuming_queued_workflows(dbos: DBOS) -> None:
902
902
 
903
903
  # Test a race condition between removing a task from the queue and flushing the status buffer
904
904
  def test_resuming_already_completed_queue_workflow(dbos: DBOS) -> None:
905
- dbos._sys_db._run_background_processes = False # Disable buffer flush
905
+ dbos._sys_db._run_background_processes = False # Disable buffer flush
906
906
 
907
907
  start_event = threading.Event()
908
908
  counter = 0
909
+
909
910
  @DBOS.workflow()
910
911
  def test_step() -> None:
911
912
  start_event.set()
@@ -917,19 +918,27 @@ def test_resuming_already_completed_queue_workflow(dbos: DBOS) -> None:
917
918
  start_event.wait()
918
919
  start_event.clear()
919
920
  time.sleep(_buffer_flush_interval_secs)
920
- assert handle.get_status().status == WorkflowStatusString.PENDING.value # Not flushed
921
- assert counter == 1 # But, really, it's completed
922
- dbos._sys_db._workflow_status_buffer = {} # Clear buffer (simulates a process restart)
921
+ assert (
922
+ handle.get_status().status == WorkflowStatusString.PENDING.value
923
+ ) # Not flushed
924
+ assert counter == 1 # But, really, it's completed
925
+ dbos._sys_db._workflow_status_buffer = (
926
+ {}
927
+ ) # Clear buffer (simulates a process restart)
923
928
 
924
929
  # Recovery picks up on the workflow and recovers it
925
930
  recovered_ids = DBOS.recover_pending_workflows()
926
931
  assert len(recovered_ids) == 1
927
932
  assert recovered_ids[0].get_workflow_id() == handle.get_workflow_id()
928
933
  start_event.wait()
929
- assert counter == 2 # The workflow ran again
930
- time.sleep(_buffer_flush_interval_secs) # This is actually to wait that _get_wf_invoke_func buffers the status
931
- dbos._sys_db._flush_workflow_status_buffer() # Manually flush
932
- assert handle.get_status().status == WorkflowStatusString.SUCCESS.value # Is recovered
934
+ assert counter == 2 # The workflow ran again
935
+ time.sleep(
936
+ _buffer_flush_interval_secs
937
+ ) # This is actually to wait that _get_wf_invoke_func buffers the status
938
+ dbos._sys_db._flush_workflow_status_buffer() # Manually flush
939
+ assert (
940
+ handle.get_status().status == WorkflowStatusString.SUCCESS.value
941
+ ) # Is recovered
933
942
  assert handle.get_status().executor_id == "local"
934
943
  assert handle.get_status().recovery_attempts == 2
935
944
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes