dbos 1.14.0a6__tar.gz → 1.14.0a8__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.
Files changed (118) hide show
  1. {dbos-1.14.0a6 → dbos-1.14.0a8}/PKG-INFO +1 -1
  2. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_debouncer.py +10 -4
  3. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_sys_db.py +6 -1
  4. {dbos-1.14.0a6 → dbos-1.14.0a8}/pyproject.toml +1 -1
  5. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_debouncer.py +56 -17
  6. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_streaming.py +15 -7
  7. {dbos-1.14.0a6 → dbos-1.14.0a8}/LICENSE +0 -0
  8. {dbos-1.14.0a6 → dbos-1.14.0a8}/README.md +0 -0
  9. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/__init__.py +0 -0
  10. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/__main__.py +0 -0
  11. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_admin_server.py +0 -0
  12. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/env.py +0 -0
  13. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/script.py.mako +0 -0
  14. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/01ce9f07bd10_streaming.py +0 -0
  15. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  16. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
  17. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py +0 -0
  18. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  19. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  20. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
  21. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
  22. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
  23. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  24. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  25. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  26. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
  27. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  28. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
  29. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_app_db.py +0 -0
  30. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_classproperty.py +0 -0
  31. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_client.py +0 -0
  32. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_conductor/conductor.py +0 -0
  33. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_conductor/protocol.py +0 -0
  34. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_context.py +0 -0
  35. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_core.py +0 -0
  36. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_croniter.py +0 -0
  37. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_dbos.py +0 -0
  38. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_dbos_config.py +0 -0
  39. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_debug.py +0 -0
  40. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_docker_pg_helper.py +0 -0
  41. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_error.py +0 -0
  42. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_event_loop.py +0 -0
  43. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_fastapi.py +0 -0
  44. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_flask.py +0 -0
  45. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_kafka.py +0 -0
  46. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_kafka_message.py +0 -0
  47. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_logger.py +0 -0
  48. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_migration.py +0 -0
  49. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_outcome.py +0 -0
  50. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_queue.py +0 -0
  51. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_recovery.py +0 -0
  52. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_registrations.py +0 -0
  53. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_roles.py +0 -0
  54. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_scheduler.py +0 -0
  55. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_schemas/__init__.py +0 -0
  56. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_schemas/application_database.py +0 -0
  57. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_schemas/system_database.py +0 -0
  58. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_serialization.py +0 -0
  59. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_sys_db_postgres.py +0 -0
  60. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_sys_db_sqlite.py +0 -0
  61. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
  62. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  63. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
  64. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  65. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  66. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  67. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  68. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  69. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  70. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  71. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_tracer.py +0 -0
  72. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_utils.py +0 -0
  73. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/_workflow_commands.py +0 -0
  74. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/cli/_github_init.py +0 -0
  75. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/cli/_template_init.py +0 -0
  76. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/cli/cli.py +0 -0
  77. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/cli/migration.py +0 -0
  78. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/dbos-config.schema.json +0 -0
  79. {dbos-1.14.0a6 → dbos-1.14.0a8}/dbos/py.typed +0 -0
  80. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/__init__.py +0 -0
  81. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/atexit_no_ctor.py +0 -0
  82. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/atexit_no_launch.py +0 -0
  83. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/classdefs.py +0 -0
  84. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/client_collateral.py +0 -0
  85. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/client_worker.py +0 -0
  86. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/conftest.py +0 -0
  87. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/dupname_classdefs1.py +0 -0
  88. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/dupname_classdefsa.py +0 -0
  89. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/more_classdefs.py +0 -0
  90. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/queuedworkflow.py +0 -0
  91. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_admin_server.py +0 -0
  92. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_async.py +0 -0
  93. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_async_workflow_management.py +0 -0
  94. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_classdecorators.py +0 -0
  95. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_cli.py +0 -0
  96. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_client.py +0 -0
  97. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_concurrency.py +0 -0
  98. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_config.py +0 -0
  99. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_croniter.py +0 -0
  100. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_dbos.py +0 -0
  101. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_debug.py +0 -0
  102. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_docker_secrets.py +0 -0
  103. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_failures.py +0 -0
  104. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_fastapi.py +0 -0
  105. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_fastapi_roles.py +0 -0
  106. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_flask.py +0 -0
  107. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_kafka.py +0 -0
  108. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_outcome.py +0 -0
  109. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_package.py +0 -0
  110. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_queue.py +0 -0
  111. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_scheduler.py +0 -0
  112. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_schema_migration.py +0 -0
  113. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_singleton.py +0 -0
  114. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_spans.py +0 -0
  115. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_sqlalchemy.py +0 -0
  116. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_workflow_introspection.py +0 -0
  117. {dbos-1.14.0a6 → dbos-1.14.0a8}/tests/test_workflow_management.py +0 -0
  118. {dbos-1.14.0a6 → dbos-1.14.0a8}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.14.0a6
3
+ Version: 1.14.0a8
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -232,7 +232,10 @@ class Debouncer(Generic[P, R]):
232
232
  while True:
233
233
  try:
234
234
  # Attempt to enqueue a debouncer for this workflow.
235
- with SetEnqueueOptions(deduplication_id=self.debounce_key):
235
+ deduplication_id = (
236
+ f"{self.options['workflow_name']}-{self.debounce_key}"
237
+ )
238
+ with SetEnqueueOptions(deduplication_id=deduplication_id):
236
239
  with SetWorkflowTimeout(None):
237
240
  internal_queue.enqueue(
238
241
  debouncer_workflow,
@@ -249,7 +252,7 @@ class Debouncer(Generic[P, R]):
249
252
  def get_deduplicated_workflow() -> Optional[str]:
250
253
  return dbos._sys_db.get_deduplicated_workflow(
251
254
  queue_name=internal_queue.name,
252
- deduplication_id=self.debounce_key,
255
+ deduplication_id=deduplication_id,
253
256
  )
254
257
 
255
258
  dedup_wfid = dbos._sys_db.call_function_as_step(
@@ -333,10 +336,13 @@ class DebouncerClient:
333
336
  while True:
334
337
  try:
335
338
  # Attempt to enqueue a debouncer for this workflow.
339
+ deduplication_id = (
340
+ f"{self.debouncer_options['workflow_name']}-{self.debounce_key}"
341
+ )
336
342
  debouncer_options: EnqueueOptions = {
337
343
  "workflow_name": DEBOUNCER_WORKFLOW_NAME,
338
344
  "queue_name": INTERNAL_QUEUE_NAME,
339
- "deduplication_id": self.debounce_key,
345
+ "deduplication_id": deduplication_id,
340
346
  }
341
347
  self.client.enqueue(
342
348
  debouncer_options,
@@ -353,7 +359,7 @@ class DebouncerClient:
353
359
  # If there is already a debouncer, send a message to it.
354
360
  dedup_wfid = self.client._sys_db.get_deduplicated_workflow(
355
361
  queue_name=INTERNAL_QUEUE_NAME,
356
- deduplication_id=self.debounce_key,
362
+ deduplication_id=deduplication_id,
357
363
  )
358
364
  if dedup_wfid is None:
359
365
  continue
@@ -1934,8 +1934,13 @@ class SystemDatabase(ABC):
1934
1934
  )
1935
1935
  if self._debug_mode and recorded_output is None:
1936
1936
  raise Exception(
1937
- "called set_event in debug mode without a previous execution"
1937
+ "called writeStream in debug mode without a previous execution"
1938
1938
  )
1939
+ if recorded_output is not None:
1940
+ dbos_logger.debug(
1941
+ f"Replaying writeStream, id: {function_id}, key: {key}"
1942
+ )
1943
+ return
1939
1944
  # Find the maximum offset for this workflow_uuid and key combination
1940
1945
  max_offset_result = c.execute(
1941
1946
  sa.select(sa.func.max(SystemSchema.streams.c.offset)).where(
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "1.14.0a6"
30
+ version = "1.14.0a8"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -17,17 +17,12 @@ from dbos._queue import Queue
17
17
  from dbos._utils import GlobalParams
18
18
 
19
19
 
20
- def workflow(x: int) -> int:
21
- return x
22
-
23
-
24
- async def workflow_async(x: int) -> int:
25
- return x
26
-
27
-
28
20
  def test_debouncer(dbos: DBOS) -> None:
29
21
 
30
- DBOS.workflow()(workflow)
22
+ @DBOS.workflow()
23
+ def workflow(x: int) -> int:
24
+ return x
25
+
31
26
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
32
27
 
33
28
  @DBOS.step()
@@ -35,17 +30,20 @@ def test_debouncer(dbos: DBOS) -> None:
35
30
  return str(uuid.uuid4())
36
31
 
37
32
  def debouncer_test() -> None:
38
- debouncer = Debouncer.create(workflow, debounce_key="key")
39
33
 
40
34
  debounce_period = 2
41
35
 
36
+ debouncer = Debouncer.create(workflow, debounce_key="key")
42
37
  first_handle = debouncer.debounce(debounce_period, first_value)
38
+ debouncer = Debouncer.create(workflow, debounce_key="key")
43
39
  second_handle = debouncer.debounce(debounce_period, second_value)
44
40
  assert first_handle.workflow_id == second_handle.workflow_id
45
41
  assert first_handle.get_result() == second_value
46
42
  assert second_handle.get_result() == second_value
47
43
 
44
+ debouncer = Debouncer.create(workflow, debounce_key="key")
48
45
  third_handle = debouncer.debounce(debounce_period, third_value)
46
+ debouncer = Debouncer.create(workflow, debounce_key="key")
49
47
  fourth_handle = debouncer.debounce(debounce_period, fourth_value)
50
48
  assert third_handle.workflow_id != first_handle.workflow_id
51
49
  assert third_handle.workflow_id == fourth_handle.workflow_id
@@ -76,7 +74,10 @@ def test_debouncer(dbos: DBOS) -> None:
76
74
 
77
75
  def test_debouncer_timeout(dbos: DBOS) -> None:
78
76
 
79
- DBOS.workflow()(workflow)
77
+ @DBOS.workflow()
78
+ def workflow(x: int) -> int:
79
+ return x
80
+
80
81
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
81
82
 
82
83
  # Set a huge period but small timeout, verify workflows start after the timeout
@@ -115,9 +116,38 @@ def test_debouncer_timeout(dbos: DBOS) -> None:
115
116
  assert second_handle.get_result() == second_value
116
117
 
117
118
 
119
+ def test_multiple_debouncers(dbos: DBOS) -> None:
120
+
121
+ @DBOS.workflow()
122
+ def workflow(x: int) -> int:
123
+ return x
124
+
125
+ first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
126
+
127
+ # Set a huge period but small timeout, verify workflows start after the timeout
128
+ debouncer_one = Debouncer.create(workflow, debounce_key="key_one")
129
+ debouncer_two = Debouncer.create(workflow, debounce_key="key_two")
130
+ debounce_period = 2
131
+
132
+ first_handle = debouncer_one.debounce(debounce_period, first_value)
133
+ second_handle = debouncer_one.debounce(debounce_period, second_value)
134
+ third_handle = debouncer_two.debounce(debounce_period, third_value)
135
+ fourth_handle = debouncer_two.debounce(debounce_period, fourth_value)
136
+ assert first_handle.workflow_id == second_handle.workflow_id
137
+ assert first_handle.workflow_id != third_handle.workflow_id
138
+ assert third_handle.workflow_id == fourth_handle.workflow_id
139
+ assert first_handle.get_result() == second_value
140
+ assert second_handle.get_result() == second_value
141
+ assert third_handle.get_result() == fourth_value
142
+ assert fourth_handle.get_result() == fourth_value
143
+
144
+
118
145
  def test_debouncer_queue(dbos: DBOS) -> None:
119
146
 
120
- DBOS.workflow()(workflow)
147
+ @DBOS.workflow()
148
+ def workflow(x: int) -> int:
149
+ return x
150
+
121
151
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
122
152
  queue = Queue("test-queue")
123
153
 
@@ -166,7 +196,10 @@ def test_debouncer_queue(dbos: DBOS) -> None:
166
196
  @pytest.mark.asyncio
167
197
  async def test_debouncer_async(dbos: DBOS) -> None:
168
198
 
169
- DBOS.workflow()(workflow_async)
199
+ @DBOS.workflow()
200
+ async def workflow_async(x: int) -> int:
201
+ return x
202
+
170
203
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
171
204
 
172
205
  debouncer = Debouncer.create_async(workflow_async, debounce_key="key")
@@ -188,12 +221,15 @@ async def test_debouncer_async(dbos: DBOS) -> None:
188
221
 
189
222
  def test_debouncer_client(dbos: DBOS, client: DBOSClient) -> None:
190
223
 
191
- DBOS.workflow()(workflow)
224
+ @DBOS.workflow()
225
+ def workflow(x: int) -> int:
226
+ return x
227
+
192
228
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
193
229
  queue = Queue("test-queue")
194
230
 
195
231
  options: EnqueueOptions = {
196
- "workflow_name": workflow.__name__,
232
+ "workflow_name": workflow.__qualname__,
197
233
  "queue_name": queue.name,
198
234
  }
199
235
  debouncer = DebouncerClient(client, options, debounce_key="key")
@@ -230,12 +266,15 @@ def test_debouncer_client(dbos: DBOS, client: DBOSClient) -> None:
230
266
  @pytest.mark.asyncio
231
267
  async def test_debouncer_client_async(dbos: DBOS, client: DBOSClient) -> None:
232
268
 
233
- DBOS.workflow()(workflow_async)
269
+ @DBOS.workflow()
270
+ async def workflow_async(x: int) -> int:
271
+ return x
272
+
234
273
  first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
235
274
  queue = Queue("test-queue")
236
275
 
237
276
  options: EnqueueOptions = {
238
- "workflow_name": workflow_async.__name__,
277
+ "workflow_name": workflow_async.__qualname__,
239
278
  "queue_name": queue.name,
240
279
  }
241
280
  debouncer = DebouncerClient(client, options, debounce_key="key")
@@ -231,16 +231,19 @@ def test_stream_error_cases(dbos: DBOS) -> None:
231
231
  def test_stream_workflow_recovery(dbos: DBOS) -> None:
232
232
  """Test that stream operations are properly recovered during workflow replay."""
233
233
 
234
- call_count = 0
234
+ workflow_call_count = 0
235
+ step_call_count = 0
235
236
 
236
237
  @DBOS.step()
237
238
  def counting_step() -> int:
238
- nonlocal call_count
239
- call_count += 1
240
- return call_count
239
+ nonlocal step_call_count
240
+ step_call_count += 1
241
+ return step_call_count
241
242
 
242
243
  @DBOS.workflow()
243
244
  def recovery_test_workflow() -> None:
245
+ nonlocal workflow_call_count
246
+ workflow_call_count += 1
244
247
  count1 = counting_step()
245
248
  DBOS.write_stream("recovery_stream", f"step_{count1}")
246
249
 
@@ -254,13 +257,18 @@ def test_stream_workflow_recovery(dbos: DBOS) -> None:
254
257
  with SetWorkflowID(wfid):
255
258
  recovery_test_workflow()
256
259
 
260
+ # Validate stream contents
261
+ values = list(DBOS.read_stream(wfid, "recovery_stream"))
262
+ assert values == ["step_1", "step_2"]
263
+
257
264
  # Reset call count and run the same workflow ID again (should replay)
258
- call_count = 0
265
+ dbos._sys_db.update_workflow_outcome(wfid, "PENDING")
259
266
  with SetWorkflowID(wfid):
260
267
  recovery_test_workflow()
261
268
 
262
- # The counting step should not have been called again (replayed from recorded results)
263
- assert call_count == 0
269
+ # The workflow should have been called again
270
+ assert workflow_call_count == 2
271
+ assert step_call_count == 2
264
272
 
265
273
  # Stream should still be readable and contain the same values
266
274
  values = list(DBOS.read_stream(wfid, "recovery_stream"))
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
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