dbos 1.14.0a6__tar.gz → 1.14.0a9__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.
- {dbos-1.14.0a6 → dbos-1.14.0a9}/PKG-INFO +1 -1
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_debouncer.py +18 -17
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_sys_db.py +6 -1
- {dbos-1.14.0a6 → dbos-1.14.0a9}/pyproject.toml +1 -1
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_debouncer.py +101 -54
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_streaming.py +15 -7
- {dbos-1.14.0a6 → dbos-1.14.0a9}/LICENSE +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/README.md +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/__init__.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/__main__.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_admin_server.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/env.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/script.py.mako +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/01ce9f07bd10_streaming.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_app_db.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_classproperty.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_client.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_context.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_core.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_croniter.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_dbos.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_dbos_config.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_debug.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_error.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_event_loop.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_fastapi.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_flask.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_kafka.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_kafka_message.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_logger.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_migration.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_outcome.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_queue.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_recovery.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_registrations.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_roles.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_scheduler.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_serialization.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_sys_db_postgres.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_sys_db_sqlite.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_tracer.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_utils.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_workflow_commands.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/cli/_github_init.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/cli/_template_init.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/cli/cli.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/cli/migration.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/py.typed +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/__init__.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/atexit_no_launch.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/classdefs.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/client_collateral.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/client_worker.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/conftest.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/more_classdefs.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/queuedworkflow.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_admin_server.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_async.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_async_workflow_management.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_classdecorators.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_cli.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_client.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_concurrency.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_config.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_croniter.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_dbos.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_debug.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_docker_secrets.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_failures.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_fastapi.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_flask.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_kafka.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_outcome.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_package.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_queue.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_scheduler.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_schema_migration.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_singleton.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_spans.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/tests/test_workflow_management.py +0 -0
- {dbos-1.14.0a6 → dbos-1.14.0a9}/version/__init__.py +0 -0
@@ -147,7 +147,6 @@ class Debouncer(Generic[P, R]):
|
|
147
147
|
self,
|
148
148
|
workflow_name: str,
|
149
149
|
*,
|
150
|
-
debounce_key: str,
|
151
150
|
debounce_timeout_sec: Optional[float] = None,
|
152
151
|
queue: Optional[Queue] = None,
|
153
152
|
):
|
@@ -157,13 +156,11 @@ class Debouncer(Generic[P, R]):
|
|
157
156
|
"queue_name": queue.name if queue else None,
|
158
157
|
"workflow_name": workflow_name,
|
159
158
|
}
|
160
|
-
self.debounce_key = debounce_key
|
161
159
|
|
162
160
|
@staticmethod
|
163
161
|
def create(
|
164
162
|
workflow: Callable[P, R],
|
165
163
|
*,
|
166
|
-
debounce_key: str,
|
167
164
|
debounce_timeout_sec: Optional[float] = None,
|
168
165
|
queue: Optional[Queue] = None,
|
169
166
|
) -> "Debouncer[P, R]":
|
@@ -172,7 +169,6 @@ class Debouncer(Generic[P, R]):
|
|
172
169
|
raise TypeError("Only workflow functions may be debounced, not methods")
|
173
170
|
return Debouncer[P, R](
|
174
171
|
get_dbos_func_name(workflow),
|
175
|
-
debounce_key=debounce_key,
|
176
172
|
debounce_timeout_sec=debounce_timeout_sec,
|
177
173
|
queue=queue,
|
178
174
|
)
|
@@ -181,7 +177,6 @@ class Debouncer(Generic[P, R]):
|
|
181
177
|
def create_async(
|
182
178
|
workflow: Callable[P, Coroutine[Any, Any, R]],
|
183
179
|
*,
|
184
|
-
debounce_key: str,
|
185
180
|
debounce_timeout_sec: Optional[float] = None,
|
186
181
|
queue: Optional[Queue] = None,
|
187
182
|
) -> "Debouncer[P, R]":
|
@@ -190,13 +185,16 @@ class Debouncer(Generic[P, R]):
|
|
190
185
|
raise TypeError("Only workflow functions may be debounced, not methods")
|
191
186
|
return Debouncer[P, R](
|
192
187
|
get_dbos_func_name(workflow),
|
193
|
-
debounce_key=debounce_key,
|
194
188
|
debounce_timeout_sec=debounce_timeout_sec,
|
195
189
|
queue=queue,
|
196
190
|
)
|
197
191
|
|
198
192
|
def debounce(
|
199
|
-
self,
|
193
|
+
self,
|
194
|
+
debounce_key: str,
|
195
|
+
debounce_period_sec: float,
|
196
|
+
*args: P.args,
|
197
|
+
**kwargs: P.kwargs,
|
200
198
|
) -> "WorkflowHandle[R]":
|
201
199
|
from dbos._dbos import DBOS, _get_dbos_instance
|
202
200
|
|
@@ -232,7 +230,8 @@ class Debouncer(Generic[P, R]):
|
|
232
230
|
while True:
|
233
231
|
try:
|
234
232
|
# Attempt to enqueue a debouncer for this workflow.
|
235
|
-
|
233
|
+
deduplication_id = f"{self.options['workflow_name']}-{debounce_key}"
|
234
|
+
with SetEnqueueOptions(deduplication_id=deduplication_id):
|
236
235
|
with SetWorkflowTimeout(None):
|
237
236
|
internal_queue.enqueue(
|
238
237
|
debouncer_workflow,
|
@@ -249,7 +248,7 @@ class Debouncer(Generic[P, R]):
|
|
249
248
|
def get_deduplicated_workflow() -> Optional[str]:
|
250
249
|
return dbos._sys_db.get_deduplicated_workflow(
|
251
250
|
queue_name=internal_queue.name,
|
252
|
-
deduplication_id=
|
251
|
+
deduplication_id=deduplication_id,
|
253
252
|
)
|
254
253
|
|
255
254
|
dedup_wfid = dbos._sys_db.call_function_as_step(
|
@@ -281,6 +280,7 @@ class Debouncer(Generic[P, R]):
|
|
281
280
|
|
282
281
|
async def debounce_async(
|
283
282
|
self,
|
283
|
+
debounce_key: str,
|
284
284
|
debounce_period_sec: float,
|
285
285
|
*args: P.args,
|
286
286
|
**kwargs: P.kwargs,
|
@@ -289,7 +289,7 @@ class Debouncer(Generic[P, R]):
|
|
289
289
|
|
290
290
|
dbos = _get_dbos_instance()
|
291
291
|
handle = await asyncio.to_thread(
|
292
|
-
self.debounce, debounce_period_sec, *args, **kwargs
|
292
|
+
self.debounce, debounce_key, debounce_period_sec, *args, **kwargs
|
293
293
|
)
|
294
294
|
return WorkflowHandleAsyncPolling(handle.workflow_id, dbos)
|
295
295
|
|
@@ -301,7 +301,6 @@ class DebouncerClient:
|
|
301
301
|
client: DBOSClient,
|
302
302
|
workflow_options: EnqueueOptions,
|
303
303
|
*,
|
304
|
-
debounce_key: str,
|
305
304
|
debounce_timeout_sec: Optional[float] = None,
|
306
305
|
queue: Optional[Queue] = None,
|
307
306
|
):
|
@@ -311,11 +310,10 @@ class DebouncerClient:
|
|
311
310
|
"queue_name": queue.name if queue else None,
|
312
311
|
"workflow_name": workflow_options["workflow_name"],
|
313
312
|
}
|
314
|
-
self.debounce_key = debounce_key
|
315
313
|
self.client = client
|
316
314
|
|
317
315
|
def debounce(
|
318
|
-
self, debounce_period_sec: float, *args: Any, **kwargs: Any
|
316
|
+
self, debounce_key: str, debounce_period_sec: float, *args: Any, **kwargs: Any
|
319
317
|
) -> "WorkflowHandle[R]":
|
320
318
|
|
321
319
|
ctxOptions: ContextOptions = {
|
@@ -333,10 +331,13 @@ class DebouncerClient:
|
|
333
331
|
while True:
|
334
332
|
try:
|
335
333
|
# Attempt to enqueue a debouncer for this workflow.
|
334
|
+
deduplication_id = (
|
335
|
+
f"{self.debouncer_options['workflow_name']}-{debounce_key}"
|
336
|
+
)
|
336
337
|
debouncer_options: EnqueueOptions = {
|
337
338
|
"workflow_name": DEBOUNCER_WORKFLOW_NAME,
|
338
339
|
"queue_name": INTERNAL_QUEUE_NAME,
|
339
|
-
"deduplication_id":
|
340
|
+
"deduplication_id": deduplication_id,
|
340
341
|
}
|
341
342
|
self.client.enqueue(
|
342
343
|
debouncer_options,
|
@@ -353,7 +354,7 @@ class DebouncerClient:
|
|
353
354
|
# If there is already a debouncer, send a message to it.
|
354
355
|
dedup_wfid = self.client._sys_db.get_deduplicated_workflow(
|
355
356
|
queue_name=INTERNAL_QUEUE_NAME,
|
356
|
-
deduplication_id=
|
357
|
+
deduplication_id=deduplication_id,
|
357
358
|
)
|
358
359
|
if dedup_wfid is None:
|
359
360
|
continue
|
@@ -384,10 +385,10 @@ class DebouncerClient:
|
|
384
385
|
)
|
385
386
|
|
386
387
|
async def debounce_async(
|
387
|
-
self, debounce_period_sec: float, *args: Any, **kwargs: Any
|
388
|
+
self, deboucne_key: str, debounce_period_sec: float, *args: Any, **kwargs: Any
|
388
389
|
) -> "WorkflowHandleAsync[R]":
|
389
390
|
handle: "WorkflowHandle[R]" = await asyncio.to_thread(
|
390
|
-
self.debounce, debounce_period_sec, *args, **kwargs
|
391
|
+
self.debounce, deboucne_key, debounce_period_sec, *args, **kwargs
|
391
392
|
)
|
392
393
|
return WorkflowHandleClientAsyncPolling[R](
|
393
394
|
handle.workflow_id, self.client._sys_db
|
@@ -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
|
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(
|
@@ -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()
|
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,18 +30,21 @@ 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
|
|
42
|
-
|
43
|
-
|
36
|
+
debouncer = Debouncer.create(workflow)
|
37
|
+
first_handle = debouncer.debounce("key", debounce_period, first_value)
|
38
|
+
debouncer = Debouncer.create(workflow)
|
39
|
+
second_handle = debouncer.debounce("key", 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
|
|
48
|
-
|
49
|
-
|
44
|
+
debouncer = Debouncer.create(workflow)
|
45
|
+
third_handle = debouncer.debounce("key", debounce_period, third_value)
|
46
|
+
debouncer = Debouncer.create(workflow)
|
47
|
+
fourth_handle = debouncer.debounce("key", 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
|
52
50
|
assert third_handle.get_result() == fourth_value
|
@@ -55,7 +53,7 @@ def test_debouncer(dbos: DBOS) -> None:
|
|
55
53
|
# Test SetWorkflowID works
|
56
54
|
wfid = generate_uuid()
|
57
55
|
with SetWorkflowID(wfid):
|
58
|
-
handle = debouncer.debounce(debounce_period, first_value)
|
56
|
+
handle = debouncer.debounce("key", debounce_period, first_value)
|
59
57
|
assert handle.workflow_id == wfid
|
60
58
|
assert handle.get_result() == first_value
|
61
59
|
|
@@ -76,25 +74,27 @@ def test_debouncer(dbos: DBOS) -> None:
|
|
76
74
|
|
77
75
|
def test_debouncer_timeout(dbos: DBOS) -> None:
|
78
76
|
|
79
|
-
DBOS.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
|
83
84
|
debouncer = Debouncer.create(
|
84
85
|
workflow,
|
85
|
-
debounce_key="key",
|
86
86
|
debounce_timeout_sec=2,
|
87
87
|
)
|
88
88
|
long_debounce_period = 10000000
|
89
89
|
|
90
|
-
first_handle = debouncer.debounce(long_debounce_period, first_value)
|
91
|
-
second_handle = debouncer.debounce(long_debounce_period, second_value)
|
90
|
+
first_handle = debouncer.debounce("key", long_debounce_period, first_value)
|
91
|
+
second_handle = debouncer.debounce("key", long_debounce_period, second_value)
|
92
92
|
assert first_handle.workflow_id == second_handle.workflow_id
|
93
93
|
assert first_handle.get_result() == second_value
|
94
94
|
assert second_handle.get_result() == second_value
|
95
95
|
|
96
|
-
third_handle = debouncer.debounce(long_debounce_period, third_value)
|
97
|
-
fourth_handle = debouncer.debounce(long_debounce_period, fourth_value)
|
96
|
+
third_handle = debouncer.debounce("key", long_debounce_period, third_value)
|
97
|
+
fourth_handle = debouncer.debounce("key", long_debounce_period, fourth_value)
|
98
98
|
assert third_handle.workflow_id != first_handle.workflow_id
|
99
99
|
assert third_handle.workflow_id == fourth_handle.workflow_id
|
100
100
|
assert third_handle.get_result() == fourth_value
|
@@ -103,29 +103,57 @@ def test_debouncer_timeout(dbos: DBOS) -> None:
|
|
103
103
|
# Submit first with a long period then with a short one, verify workflows start on time
|
104
104
|
debouncer = Debouncer.create(
|
105
105
|
workflow,
|
106
|
-
debounce_key="key",
|
107
106
|
)
|
108
107
|
short_debounce_period = 1
|
109
108
|
|
110
|
-
first_handle = debouncer.debounce(long_debounce_period, first_value)
|
111
|
-
second_handle = debouncer.debounce(short_debounce_period, second_value)
|
109
|
+
first_handle = debouncer.debounce("key", long_debounce_period, first_value)
|
110
|
+
second_handle = debouncer.debounce("key", short_debounce_period, second_value)
|
112
111
|
assert fourth_handle.workflow_id != first_handle.workflow_id
|
113
112
|
assert first_handle.workflow_id == second_handle.workflow_id
|
114
113
|
assert first_handle.get_result() == second_value
|
115
114
|
assert second_handle.get_result() == second_value
|
116
115
|
|
117
116
|
|
117
|
+
def test_multiple_debouncers(dbos: DBOS) -> None:
|
118
|
+
|
119
|
+
@DBOS.workflow()
|
120
|
+
def workflow(x: int) -> int:
|
121
|
+
return x
|
122
|
+
|
123
|
+
first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
|
124
|
+
|
125
|
+
# Set a huge period but small timeout, verify workflows start after the timeout
|
126
|
+
debouncer_one = Debouncer.create(workflow)
|
127
|
+
debouncer_two = Debouncer.create(workflow)
|
128
|
+
debounce_period = 2
|
129
|
+
|
130
|
+
first_handle = debouncer_one.debounce("key_one", debounce_period, first_value)
|
131
|
+
second_handle = debouncer_one.debounce("key_one", debounce_period, second_value)
|
132
|
+
third_handle = debouncer_two.debounce("key_two", debounce_period, third_value)
|
133
|
+
fourth_handle = debouncer_two.debounce("key_two", debounce_period, fourth_value)
|
134
|
+
assert first_handle.workflow_id == second_handle.workflow_id
|
135
|
+
assert first_handle.workflow_id != third_handle.workflow_id
|
136
|
+
assert third_handle.workflow_id == fourth_handle.workflow_id
|
137
|
+
assert first_handle.get_result() == second_value
|
138
|
+
assert second_handle.get_result() == second_value
|
139
|
+
assert third_handle.get_result() == fourth_value
|
140
|
+
assert fourth_handle.get_result() == fourth_value
|
141
|
+
|
142
|
+
|
118
143
|
def test_debouncer_queue(dbos: DBOS) -> None:
|
119
144
|
|
120
|
-
DBOS.workflow()
|
145
|
+
@DBOS.workflow()
|
146
|
+
def workflow(x: int) -> int:
|
147
|
+
return x
|
148
|
+
|
121
149
|
first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
|
122
150
|
queue = Queue("test-queue")
|
123
151
|
|
124
|
-
debouncer = Debouncer.create(workflow,
|
152
|
+
debouncer = Debouncer.create(workflow, queue=queue)
|
125
153
|
debounce_period_sec = 2
|
126
154
|
|
127
|
-
first_handle = debouncer.debounce(debounce_period_sec, first_value)
|
128
|
-
second_handle = debouncer.debounce(debounce_period_sec, second_value)
|
155
|
+
first_handle = debouncer.debounce("key", debounce_period_sec, first_value)
|
156
|
+
second_handle = debouncer.debounce("key", debounce_period_sec, second_value)
|
129
157
|
assert first_handle.workflow_id == second_handle.workflow_id
|
130
158
|
assert first_handle.get_result() == second_value
|
131
159
|
assert second_handle.get_result() == second_value
|
@@ -133,8 +161,8 @@ def test_debouncer_queue(dbos: DBOS) -> None:
|
|
133
161
|
|
134
162
|
# Test SetWorkflowTimeout works
|
135
163
|
with SetWorkflowTimeout(5.0):
|
136
|
-
third_handle = debouncer.debounce(debounce_period_sec, third_value)
|
137
|
-
fourth_handle = debouncer.debounce(debounce_period_sec, fourth_value)
|
164
|
+
third_handle = debouncer.debounce("key", debounce_period_sec, third_value)
|
165
|
+
fourth_handle = debouncer.debounce("key", debounce_period_sec, fourth_value)
|
138
166
|
assert third_handle.workflow_id != first_handle.workflow_id
|
139
167
|
assert third_handle.workflow_id == fourth_handle.workflow_id
|
140
168
|
assert third_handle.get_result() == fourth_value
|
@@ -146,7 +174,7 @@ def test_debouncer_queue(dbos: DBOS) -> None:
|
|
146
174
|
# Test SetWorkflowID works
|
147
175
|
wfid = str(uuid.uuid4())
|
148
176
|
with SetWorkflowID(wfid):
|
149
|
-
handle = debouncer.debounce(debounce_period_sec, first_value)
|
177
|
+
handle = debouncer.debounce("key", debounce_period_sec, first_value)
|
150
178
|
assert handle.workflow_id == wfid
|
151
179
|
assert handle.get_result() == first_value
|
152
180
|
assert handle.get_status().queue_name == queue.name
|
@@ -157,7 +185,7 @@ def test_debouncer_queue(dbos: DBOS) -> None:
|
|
157
185
|
with SetEnqueueOptions(
|
158
186
|
priority=1, deduplication_id="test", app_version=test_version
|
159
187
|
):
|
160
|
-
handle = debouncer.debounce(debounce_period_sec, first_value)
|
188
|
+
handle = debouncer.debounce("key", debounce_period_sec, first_value)
|
161
189
|
assert handle.get_result() == first_value
|
162
190
|
assert handle.get_status().queue_name == queue.name
|
163
191
|
assert handle.get_status().app_version == test_version
|
@@ -166,20 +194,31 @@ def test_debouncer_queue(dbos: DBOS) -> None:
|
|
166
194
|
@pytest.mark.asyncio
|
167
195
|
async def test_debouncer_async(dbos: DBOS) -> None:
|
168
196
|
|
169
|
-
DBOS.workflow()
|
197
|
+
@DBOS.workflow()
|
198
|
+
async def workflow_async(x: int) -> int:
|
199
|
+
return x
|
200
|
+
|
170
201
|
first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
|
171
202
|
|
172
|
-
debouncer = Debouncer.create_async(workflow_async
|
203
|
+
debouncer = Debouncer.create_async(workflow_async)
|
173
204
|
debounce_period_sec = 2
|
174
205
|
|
175
|
-
first_handle = await debouncer.debounce_async(
|
176
|
-
|
206
|
+
first_handle = await debouncer.debounce_async(
|
207
|
+
"key", debounce_period_sec, first_value
|
208
|
+
)
|
209
|
+
second_handle = await debouncer.debounce_async(
|
210
|
+
"key", debounce_period_sec, second_value
|
211
|
+
)
|
177
212
|
assert first_handle.workflow_id == second_handle.workflow_id
|
178
213
|
assert await first_handle.get_result() == second_value
|
179
214
|
assert await second_handle.get_result() == second_value
|
180
215
|
|
181
|
-
third_handle = await debouncer.debounce_async(
|
182
|
-
|
216
|
+
third_handle = await debouncer.debounce_async(
|
217
|
+
"key", debounce_period_sec, third_value
|
218
|
+
)
|
219
|
+
fourth_handle = await debouncer.debounce_async(
|
220
|
+
"key", debounce_period_sec, fourth_value
|
221
|
+
)
|
183
222
|
assert third_handle.workflow_id != first_handle.workflow_id
|
184
223
|
assert third_handle.workflow_id == fourth_handle.workflow_id
|
185
224
|
assert await third_handle.get_result() == fourth_value
|
@@ -188,32 +227,35 @@ async def test_debouncer_async(dbos: DBOS) -> None:
|
|
188
227
|
|
189
228
|
def test_debouncer_client(dbos: DBOS, client: DBOSClient) -> None:
|
190
229
|
|
191
|
-
DBOS.workflow()
|
230
|
+
@DBOS.workflow()
|
231
|
+
def workflow(x: int) -> int:
|
232
|
+
return x
|
233
|
+
|
192
234
|
first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
|
193
235
|
queue = Queue("test-queue")
|
194
236
|
|
195
237
|
options: EnqueueOptions = {
|
196
|
-
"workflow_name": workflow.
|
238
|
+
"workflow_name": workflow.__qualname__,
|
197
239
|
"queue_name": queue.name,
|
198
240
|
}
|
199
|
-
debouncer = DebouncerClient(client, options
|
241
|
+
debouncer = DebouncerClient(client, options)
|
200
242
|
debounce_period_sec = 2
|
201
243
|
|
202
244
|
first_handle: WorkflowHandle[int] = debouncer.debounce(
|
203
|
-
debounce_period_sec, first_value
|
245
|
+
"key", debounce_period_sec, first_value
|
204
246
|
)
|
205
247
|
second_handle: WorkflowHandle[int] = debouncer.debounce(
|
206
|
-
debounce_period_sec, second_value
|
248
|
+
"key", debounce_period_sec, second_value
|
207
249
|
)
|
208
250
|
assert first_handle.workflow_id == second_handle.workflow_id
|
209
251
|
assert first_handle.get_result() == second_value
|
210
252
|
assert second_handle.get_result() == second_value
|
211
253
|
|
212
254
|
third_handle: WorkflowHandle[int] = debouncer.debounce(
|
213
|
-
debounce_period_sec, third_value
|
255
|
+
"key", debounce_period_sec, third_value
|
214
256
|
)
|
215
257
|
fourth_handle: WorkflowHandle[int] = debouncer.debounce(
|
216
|
-
debounce_period_sec, fourth_value
|
258
|
+
"key", debounce_period_sec, fourth_value
|
217
259
|
)
|
218
260
|
assert third_handle.workflow_id != first_handle.workflow_id
|
219
261
|
assert third_handle.workflow_id == fourth_handle.workflow_id
|
@@ -222,7 +264,9 @@ def test_debouncer_client(dbos: DBOS, client: DBOSClient) -> None:
|
|
222
264
|
|
223
265
|
wfid = str(uuid.uuid4())
|
224
266
|
options["workflow_id"] = wfid
|
225
|
-
handle: WorkflowHandle[int] = debouncer.debounce(
|
267
|
+
handle: WorkflowHandle[int] = debouncer.debounce(
|
268
|
+
"key", debounce_period_sec, first_value
|
269
|
+
)
|
226
270
|
assert handle.workflow_id == wfid
|
227
271
|
assert handle.get_result() == first_value
|
228
272
|
|
@@ -230,32 +274,35 @@ def test_debouncer_client(dbos: DBOS, client: DBOSClient) -> None:
|
|
230
274
|
@pytest.mark.asyncio
|
231
275
|
async def test_debouncer_client_async(dbos: DBOS, client: DBOSClient) -> None:
|
232
276
|
|
233
|
-
DBOS.workflow()
|
277
|
+
@DBOS.workflow()
|
278
|
+
async def workflow_async(x: int) -> int:
|
279
|
+
return x
|
280
|
+
|
234
281
|
first_value, second_value, third_value, fourth_value = 0, 1, 2, 3
|
235
282
|
queue = Queue("test-queue")
|
236
283
|
|
237
284
|
options: EnqueueOptions = {
|
238
|
-
"workflow_name": workflow_async.
|
285
|
+
"workflow_name": workflow_async.__qualname__,
|
239
286
|
"queue_name": queue.name,
|
240
287
|
}
|
241
|
-
debouncer = DebouncerClient(client, options
|
288
|
+
debouncer = DebouncerClient(client, options)
|
242
289
|
debounce_period_sec = 2
|
243
290
|
|
244
291
|
first_handle: WorkflowHandleAsync[int] = await debouncer.debounce_async(
|
245
|
-
debounce_period_sec, first_value
|
292
|
+
"key", debounce_period_sec, first_value
|
246
293
|
)
|
247
294
|
second_handle: WorkflowHandleAsync[int] = await debouncer.debounce_async(
|
248
|
-
debounce_period_sec, second_value
|
295
|
+
"key", debounce_period_sec, second_value
|
249
296
|
)
|
250
297
|
assert first_handle.workflow_id == second_handle.workflow_id
|
251
298
|
assert await first_handle.get_result() == second_value
|
252
299
|
assert await second_handle.get_result() == second_value
|
253
300
|
|
254
301
|
third_handle: WorkflowHandleAsync[int] = await debouncer.debounce_async(
|
255
|
-
debounce_period_sec, third_value
|
302
|
+
"key", debounce_period_sec, third_value
|
256
303
|
)
|
257
304
|
fourth_handle: WorkflowHandleAsync[int] = await debouncer.debounce_async(
|
258
|
-
debounce_period_sec, fourth_value
|
305
|
+
"key", debounce_period_sec, fourth_value
|
259
306
|
)
|
260
307
|
assert third_handle.workflow_id != first_handle.workflow_id
|
261
308
|
assert third_handle.workflow_id == fourth_handle.workflow_id
|
@@ -265,7 +312,7 @@ async def test_debouncer_client_async(dbos: DBOS, client: DBOSClient) -> None:
|
|
265
312
|
wfid = str(uuid.uuid4())
|
266
313
|
options["workflow_id"] = wfid
|
267
314
|
handle: WorkflowHandleAsync[int] = await debouncer.debounce_async(
|
268
|
-
debounce_period_sec, first_value
|
315
|
+
"key", debounce_period_sec, first_value
|
269
316
|
)
|
270
317
|
assert handle.workflow_id == wfid
|
271
318
|
assert await handle.get_result() == first_value
|
@@ -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
|
-
|
234
|
+
workflow_call_count = 0
|
235
|
+
step_call_count = 0
|
235
236
|
|
236
237
|
@DBOS.step()
|
237
238
|
def counting_step() -> int:
|
238
|
-
nonlocal
|
239
|
-
|
240
|
-
return
|
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
|
-
|
265
|
+
dbos._sys_db.update_workflow_outcome(wfid, "PENDING")
|
259
266
|
with SetWorkflowID(wfid):
|
260
267
|
recovery_test_workflow()
|
261
268
|
|
262
|
-
# The
|
263
|
-
assert
|
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
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py
RENAMED
File without changes
|
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py
RENAMED
File without changes
|
{dbos-1.14.0a6 → dbos-1.14.0a9}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py
RENAMED
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
|
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
|