dbos 1.6.0a4__tar.gz → 1.7.0a2__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.6.0a4 → dbos-1.7.0a2}/PKG-INFO +1 -1
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_admin_server.py +17 -8
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_context.py +5 -11
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_core.py +0 -5
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_dbos.py +11 -1
- {dbos-1.6.0a4 → dbos-1.7.0a2}/pyproject.toml +1 -1
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_admin_server.py +217 -15
- {dbos-1.6.0a4 → dbos-1.7.0a2}/LICENSE +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/README.md +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/__init__.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/__main__.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_app_db.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_classproperty.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_client.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_croniter.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_dbos_config.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_debug.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_error.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_event_loop.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_fastapi.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_flask.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_kafka.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_kafka_message.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_logger.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/env.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_outcome.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_queue.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_recovery.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_registrations.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_roles.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_scheduler.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_serialization.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_sys_db.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_tracer.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_utils.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_workflow_commands.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/cli/_github_init.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/cli/_template_init.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/cli/cli.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/py.typed +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/__init__.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/atexit_no_launch.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/classdefs.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/client_collateral.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/client_worker.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/conftest.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/more_classdefs.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/queuedworkflow.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_async.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_classdecorators.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_cli.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_client.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_concurrency.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_config.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_croniter.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_dbos.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_debug.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_docker_secrets.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_failures.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_fastapi.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_flask.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_kafka.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_outcome.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_package.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_queue.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_scheduler.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_schema_migration.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_singleton.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_spans.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/tests/test_workflow_management.py +0 -0
- {dbos-1.6.0a4 → dbos-1.7.0a2}/version/__init__.py +0 -0
@@ -3,12 +3,14 @@ from __future__ import annotations
|
|
3
3
|
import json
|
4
4
|
import re
|
5
5
|
import threading
|
6
|
+
from dataclasses import asdict
|
6
7
|
from functools import partial
|
7
8
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
8
9
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypedDict
|
9
10
|
|
10
11
|
from dbos._workflow_commands import garbage_collect, global_timeout
|
11
12
|
|
13
|
+
from ._conductor import protocol as conductor_protocol
|
12
14
|
from ._context import SetWorkflowID
|
13
15
|
from ._error import DBOSException
|
14
16
|
from ._logger import dbos_logger
|
@@ -326,20 +328,24 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
|
|
326
328
|
|
327
329
|
def _handle_workflows(self, filters: Dict[str, Any]) -> None:
|
328
330
|
workflows = self.dbos.list_workflows(
|
329
|
-
workflow_ids=filters.get("
|
330
|
-
|
331
|
+
workflow_ids=filters.get("workflow_uuids"),
|
332
|
+
user=filters.get("authenticated_user"),
|
331
333
|
start_time=filters.get("start_time"),
|
332
334
|
end_time=filters.get("end_time"),
|
333
335
|
status=filters.get("status"),
|
334
336
|
app_version=filters.get("application_version"),
|
337
|
+
name=filters.get("workflow_name"),
|
335
338
|
limit=filters.get("limit"),
|
336
339
|
offset=filters.get("offset"),
|
337
340
|
sort_desc=filters.get("sort_desc", False),
|
338
341
|
workflow_id_prefix=filters.get("workflow_id_prefix"),
|
339
342
|
)
|
340
|
-
|
343
|
+
workflows_output = [
|
344
|
+
conductor_protocol.WorkflowsOutput.from_workflow_information(i)
|
345
|
+
for i in workflows
|
346
|
+
]
|
341
347
|
response_body = json.dumps(
|
342
|
-
[workflow.__dict__ for workflow in
|
348
|
+
[workflow.__dict__ for workflow in workflows_output]
|
343
349
|
).encode("utf-8")
|
344
350
|
self.send_response(200)
|
345
351
|
self.send_header("Content-Type", "application/json")
|
@@ -349,18 +355,21 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
|
|
349
355
|
|
350
356
|
def _handle_queued_workflows(self, filters: Dict[str, Any]) -> None:
|
351
357
|
workflows = self.dbos.list_queued_workflows(
|
352
|
-
queue_name=filters.get("queue_name"),
|
353
|
-
name=filters.get("name"),
|
354
358
|
start_time=filters.get("start_time"),
|
355
359
|
end_time=filters.get("end_time"),
|
356
360
|
status=filters.get("status"),
|
361
|
+
name=filters.get("workflow_name"),
|
357
362
|
limit=filters.get("limit"),
|
358
363
|
offset=filters.get("offset"),
|
364
|
+
queue_name=filters.get("queue_name"),
|
359
365
|
sort_desc=filters.get("sort_desc", False),
|
360
366
|
)
|
361
|
-
|
367
|
+
workflows_output = [
|
368
|
+
conductor_protocol.WorkflowsOutput.from_workflow_information(i)
|
369
|
+
for i in workflows
|
370
|
+
]
|
362
371
|
response_body = json.dumps(
|
363
|
-
[workflow.__dict__ for workflow in
|
372
|
+
[workflow.__dict__ for workflow in workflows_output]
|
364
373
|
).encode("utf-8")
|
365
374
|
self.send_response(200)
|
366
375
|
self.send_header("Content-Type", "application/json")
|
@@ -140,23 +140,18 @@ class DBOSContext:
|
|
140
140
|
self,
|
141
141
|
wfid: Optional[str],
|
142
142
|
attributes: TracedAttributes,
|
143
|
-
is_temp_workflow: bool = False,
|
144
143
|
) -> None:
|
145
144
|
if wfid is None or len(wfid) == 0:
|
146
145
|
wfid = self.assign_workflow_id()
|
147
146
|
self.id_assigned_for_next_workflow = ""
|
148
147
|
self.workflow_id = wfid
|
149
148
|
self.function_id = 0
|
150
|
-
|
151
|
-
self._start_span(attributes)
|
149
|
+
self._start_span(attributes)
|
152
150
|
|
153
|
-
def end_workflow(
|
154
|
-
self, exc_value: Optional[BaseException], is_temp_workflow: bool = False
|
155
|
-
) -> None:
|
151
|
+
def end_workflow(self, exc_value: Optional[BaseException]) -> None:
|
156
152
|
self.workflow_id = ""
|
157
153
|
self.function_id = -1
|
158
|
-
|
159
|
-
self._end_span(exc_value)
|
154
|
+
self._end_span(exc_value)
|
160
155
|
|
161
156
|
def is_within_workflow(self) -> bool:
|
162
157
|
return len(self.workflow_id) > 0
|
@@ -490,7 +485,6 @@ class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]):
|
|
490
485
|
def __init__(self, attributes: TracedAttributes) -> None:
|
491
486
|
self.created_ctx = False
|
492
487
|
self.attributes = attributes
|
493
|
-
self.is_temp_workflow = attributes["name"] == "temp_wf"
|
494
488
|
self.saved_workflow_timeout: Optional[int] = None
|
495
489
|
self.saved_deduplication_id: Optional[str] = None
|
496
490
|
self.saved_priority: Optional[int] = None
|
@@ -514,7 +508,7 @@ class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]):
|
|
514
508
|
self.saved_priority = ctx.priority
|
515
509
|
ctx.priority = None
|
516
510
|
ctx.start_workflow(
|
517
|
-
None, self.attributes
|
511
|
+
None, self.attributes
|
518
512
|
) # Will get from the context's next workflow ID
|
519
513
|
return ctx
|
520
514
|
|
@@ -526,7 +520,7 @@ class EnterDBOSWorkflow(AbstractContextManager[DBOSContext, Literal[False]]):
|
|
526
520
|
) -> Literal[False]:
|
527
521
|
ctx = assert_current_dbos_context()
|
528
522
|
assert ctx.is_within_workflow()
|
529
|
-
ctx.end_workflow(exc_value
|
523
|
+
ctx.end_workflow(exc_value)
|
530
524
|
# Restore the saved workflow timeout
|
531
525
|
ctx.workflow_timeout_ms = self.saved_workflow_timeout
|
532
526
|
# Clear any propagating timeout
|
@@ -1187,11 +1187,6 @@ def decorate_step(
|
|
1187
1187
|
async def temp_wf_async(*args: Any, **kwargs: Any) -> Any:
|
1188
1188
|
return await wrapper(*args, **kwargs)
|
1189
1189
|
|
1190
|
-
# Other code in transact-py depends on the name of temporary workflow functions to be "temp_wf"
|
1191
|
-
# so set the name of both sync and async temporary workflow functions explicitly
|
1192
|
-
temp_wf_sync.__name__ = "temp_wf"
|
1193
|
-
temp_wf_async.__name__ = "temp_wf"
|
1194
|
-
|
1195
1190
|
temp_wf = temp_wf_async if inspect.iscoroutinefunction(func) else temp_wf_sync
|
1196
1191
|
wrapped_wf = workflow_wrapper(dbosreg, temp_wf)
|
1197
1192
|
set_dbos_func_name(temp_wf, "<temp>." + step_name)
|
@@ -7,7 +7,6 @@ import inspect
|
|
7
7
|
import os
|
8
8
|
import sys
|
9
9
|
import threading
|
10
|
-
import traceback
|
11
10
|
import uuid
|
12
11
|
from concurrent.futures import ThreadPoolExecutor
|
13
12
|
from logging import Logger
|
@@ -28,6 +27,7 @@ from typing import (
|
|
28
27
|
)
|
29
28
|
|
30
29
|
from opentelemetry.trace import Span
|
30
|
+
from rich import print
|
31
31
|
|
32
32
|
from dbos._conductor.conductor import ConductorWebsocket
|
33
33
|
from dbos._sys_db import WorkflowStatus
|
@@ -517,6 +517,16 @@ class DBOS:
|
|
517
517
|
|
518
518
|
dbos_logger.info("DBOS launched!")
|
519
519
|
|
520
|
+
if self.conductor_key is None and os.environ.get("DBOS__CLOUD") != "true":
|
521
|
+
# Hint the user to open the URL to register and set up Conductor
|
522
|
+
app_name = self._config["name"]
|
523
|
+
conductor_registration_url = (
|
524
|
+
f"https://console.dbos.dev/self-host?appname={app_name}"
|
525
|
+
)
|
526
|
+
print(
|
527
|
+
f"[bold]To view and manage workflows, connect to DBOS Conductor at:[/bold] [bold blue]{conductor_registration_url}[/bold blue]"
|
528
|
+
)
|
529
|
+
|
520
530
|
# Flush handlers and add OTLP to all loggers if enabled
|
521
531
|
# to enable their export in DBOS Cloud
|
522
532
|
for handler in dbos_logger.handlers:
|
@@ -3,7 +3,8 @@ import socket
|
|
3
3
|
import threading
|
4
4
|
import time
|
5
5
|
import uuid
|
6
|
-
from datetime import datetime, timezone
|
6
|
+
from datetime import datetime, timedelta, timezone
|
7
|
+
from typing import Any, Dict
|
7
8
|
|
8
9
|
import pytest
|
9
10
|
import requests
|
@@ -462,13 +463,13 @@ def test_list_workflows(dbos: DBOS) -> None:
|
|
462
463
|
pass
|
463
464
|
|
464
465
|
@DBOS.workflow()
|
465
|
-
def test_workflow_2() ->
|
466
|
-
|
466
|
+
def test_workflow_2(my_time: datetime) -> str:
|
467
|
+
return DBOS.workflow_id + " completed at " + my_time.isoformat()
|
467
468
|
|
468
469
|
# Start workflows
|
469
470
|
handle_1 = DBOS.start_workflow(test_workflow_1)
|
470
471
|
time.sleep(2) # Sleep for 2 seconds between workflows
|
471
|
-
handle_2 = DBOS.start_workflow(test_workflow_2)
|
472
|
+
handle_2 = DBOS.start_workflow(test_workflow_2, datetime.now())
|
472
473
|
|
473
474
|
# Wait for workflows to complete
|
474
475
|
handle_1.get_result()
|
@@ -492,8 +493,8 @@ def test_list_workflows(dbos: DBOS) -> None:
|
|
492
493
|
).isoformat()
|
493
494
|
|
494
495
|
# Test POST /workflows with filters
|
495
|
-
filters = {
|
496
|
-
"
|
496
|
+
filters: Dict[str, Any] = {
|
497
|
+
"workflow_uuids": workflow_ids,
|
497
498
|
"start_time": start_time_filter,
|
498
499
|
}
|
499
500
|
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
@@ -501,7 +502,24 @@ def test_list_workflows(dbos: DBOS) -> None:
|
|
501
502
|
|
502
503
|
workflows = response.json()
|
503
504
|
assert len(workflows) == 1, f"Expected 1 workflows, but got {len(workflows)}"
|
504
|
-
|
505
|
+
|
506
|
+
# Make sure it contains all the expected fields
|
507
|
+
assert workflows[0]["WorkflowUUID"] == handle_2.workflow_id, "Workflow ID mismatch"
|
508
|
+
assert workflows[0]["WorkflowName"] == test_workflow_2.__qualname__
|
509
|
+
assert workflows[0]["Status"] == "SUCCESS"
|
510
|
+
assert workflows[0]["WorkflowClassName"] is None
|
511
|
+
assert workflows[0]["WorkflowConfigName"] is None
|
512
|
+
assert workflows[0]["AuthenticatedUser"] is None
|
513
|
+
assert workflows[0]["AssumedRole"] is None
|
514
|
+
assert workflows[0]["AuthenticatedRoles"] is None
|
515
|
+
assert workflows[0]["Input"] is not None and len(workflows[0]["Input"]) > 0
|
516
|
+
assert workflows[0]["Output"] is not None and len(workflows[0]["Output"]) > 0
|
517
|
+
assert workflows[0]["Error"] is None
|
518
|
+
assert workflows[0]["CreatedAt"] is not None and len(workflows[0]["CreatedAt"]) > 0
|
519
|
+
assert workflows[0]["UpdatedAt"] is not None and len(workflows[0]["UpdatedAt"]) > 0
|
520
|
+
assert workflows[0]["QueueName"] is None
|
521
|
+
assert workflows[0]["ApplicationVersion"] == GlobalParams.app_version
|
522
|
+
assert workflows[0]["ExecutorID"] == GlobalParams.executor_id
|
505
523
|
|
506
524
|
# Test POST /workflows without filters
|
507
525
|
response = requests.post("http://localhost:3001/workflows", json={}, timeout=5)
|
@@ -512,7 +530,106 @@ def test_list_workflows(dbos: DBOS) -> None:
|
|
512
530
|
workflows_list
|
513
531
|
), f"Expected {len(workflows_list)} workflows, but got {len(workflows)}"
|
514
532
|
for workflow in workflows:
|
515
|
-
assert workflow["
|
533
|
+
assert workflow["WorkflowUUID"] in workflow_ids, "Workflow ID mismatch"
|
534
|
+
|
535
|
+
# Verify sort_desc inverts the order
|
536
|
+
filters = {
|
537
|
+
"sort_desc": True,
|
538
|
+
}
|
539
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
540
|
+
assert response.status_code == 200
|
541
|
+
workflows = response.json()
|
542
|
+
assert len(workflows) == len(workflows_list)
|
543
|
+
assert (
|
544
|
+
workflows[0]["WorkflowUUID"] == handle_2.workflow_id
|
545
|
+
), "First workflow should be the last one started"
|
546
|
+
|
547
|
+
# Test all filters
|
548
|
+
filters = {
|
549
|
+
"workflow_uuids": ["not-a-valid-uuid"],
|
550
|
+
}
|
551
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
552
|
+
assert response.status_code == 200
|
553
|
+
workflows = response.json()
|
554
|
+
assert len(workflows) == 0, "Expected no workflows for invalid UUID"
|
555
|
+
|
556
|
+
filters = {
|
557
|
+
"workflow_uuids": [handle_1.workflow_id, handle_2.workflow_id],
|
558
|
+
}
|
559
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
560
|
+
assert response.status_code == 200
|
561
|
+
workflows = response.json()
|
562
|
+
assert len(workflows) == 2
|
563
|
+
|
564
|
+
filters = {
|
565
|
+
"authenticated_user": "no-user",
|
566
|
+
}
|
567
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
568
|
+
assert response.status_code == 200
|
569
|
+
workflows = response.json()
|
570
|
+
assert len(workflows) == 0
|
571
|
+
|
572
|
+
filters = {
|
573
|
+
"workflow_name": test_workflow_1.__qualname__,
|
574
|
+
}
|
575
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
576
|
+
assert response.status_code == 200
|
577
|
+
workflows = response.json()
|
578
|
+
assert len(workflows) == 1
|
579
|
+
assert workflows[0]["WorkflowUUID"] == handle_1.workflow_id
|
580
|
+
|
581
|
+
filters = {
|
582
|
+
"end_time": (datetime.now(timezone.utc) - timedelta(minutes=10)).isoformat()
|
583
|
+
}
|
584
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
585
|
+
assert response.status_code == 200
|
586
|
+
workflows = response.json()
|
587
|
+
assert len(workflows) == 0
|
588
|
+
|
589
|
+
filters = {
|
590
|
+
"start_time": (datetime.now(timezone.utc) + timedelta(minutes=10)).isoformat(),
|
591
|
+
}
|
592
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
593
|
+
assert response.status_code == 200
|
594
|
+
workflows = response.json()
|
595
|
+
assert len(workflows) == 0
|
596
|
+
|
597
|
+
filters = {
|
598
|
+
"status": ["SUCCESS", "CANCELLED"],
|
599
|
+
}
|
600
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
601
|
+
assert response.status_code == 200
|
602
|
+
workflows = response.json()
|
603
|
+
assert len(workflows) == 2
|
604
|
+
|
605
|
+
filters = {
|
606
|
+
"application_version": GlobalParams.app_version,
|
607
|
+
}
|
608
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
609
|
+
assert response.status_code == 200
|
610
|
+
workflows = response.json()
|
611
|
+
assert len(workflows) == 2
|
612
|
+
|
613
|
+
filters = {
|
614
|
+
"limit": 1,
|
615
|
+
"offset": 1,
|
616
|
+
}
|
617
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
618
|
+
assert response.status_code == 200
|
619
|
+
workflows = response.json()
|
620
|
+
assert len(workflows) == 1
|
621
|
+
assert workflows[0]["WorkflowUUID"] == handle_2.workflow_id
|
622
|
+
|
623
|
+
filters = {
|
624
|
+
"workflow_id_prefix": handle_1.workflow_id[
|
625
|
+
:10
|
626
|
+
], # First 10 characters of the workflow name
|
627
|
+
}
|
628
|
+
response = requests.post("http://localhost:3001/workflows", json=filters, timeout=5)
|
629
|
+
assert response.status_code == 200
|
630
|
+
workflows = response.json()
|
631
|
+
assert len(workflows) == 1
|
632
|
+
assert workflows[0]["WorkflowUUID"] == handle_1.workflow_id
|
516
633
|
|
517
634
|
|
518
635
|
def test_get_workflow_by_id(dbos: DBOS) -> None:
|
@@ -606,15 +723,15 @@ def test_queued_workflows_endpoint(dbos: DBOS) -> None:
|
|
606
723
|
test_queue2 = Queue("test-queue-2", concurrency=1)
|
607
724
|
|
608
725
|
@DBOS.workflow()
|
609
|
-
def blocking_workflow() -> str:
|
726
|
+
def blocking_workflow(i: int) -> str:
|
610
727
|
while True:
|
611
728
|
time.sleep(0.1)
|
612
729
|
|
613
730
|
# Enqueue some workflows to create queued entries
|
614
|
-
handles = []
|
615
|
-
handles.append(test_queue1.enqueue(blocking_workflow))
|
616
|
-
handles.append(test_queue1.enqueue(blocking_workflow))
|
617
|
-
handles.append(test_queue2.enqueue(blocking_workflow))
|
731
|
+
handles: list[WorkflowHandle[str]] = []
|
732
|
+
handles.append(test_queue1.enqueue(blocking_workflow, 1))
|
733
|
+
handles.append(test_queue1.enqueue(blocking_workflow, 2))
|
734
|
+
handles.append(test_queue2.enqueue(blocking_workflow, 3))
|
618
735
|
|
619
736
|
# Test basic queued workflows endpoint
|
620
737
|
response = requests.post("http://localhost:3001/queues", json={}, timeout=5)
|
@@ -628,16 +745,101 @@ def test_queued_workflows_endpoint(dbos: DBOS) -> None:
|
|
628
745
|
len(queued_workflows) == 3
|
629
746
|
), f"Expected 3 queued workflows, got {len(queued_workflows)}"
|
630
747
|
|
631
|
-
#
|
632
|
-
|
748
|
+
# Make sure it contains all the expected fields
|
749
|
+
assert queued_workflows[0]["WorkflowName"] == blocking_workflow.__qualname__
|
750
|
+
assert (
|
751
|
+
queued_workflows[0]["WorkflowUUID"] == handles[0].workflow_id
|
752
|
+
), "Workflow ID mismatch"
|
753
|
+
assert (
|
754
|
+
queued_workflows[0]["Status"] == "ENQUEUED"
|
755
|
+
or queued_workflows[0]["Status"] == "PENDING"
|
756
|
+
)
|
757
|
+
assert queued_workflows[0]["WorkflowClassName"] is None
|
758
|
+
assert queued_workflows[0]["WorkflowConfigName"] is None
|
759
|
+
assert queued_workflows[0]["AuthenticatedUser"] is None
|
760
|
+
assert queued_workflows[0]["AssumedRole"] is None
|
761
|
+
assert queued_workflows[0]["AuthenticatedRoles"] is None
|
762
|
+
assert (
|
763
|
+
queued_workflows[0]["Input"] is not None
|
764
|
+
and len(queued_workflows[0]["Input"]) > 0
|
765
|
+
)
|
766
|
+
assert "1" in queued_workflows[0]["Input"]
|
767
|
+
assert queued_workflows[0]["Output"] is None
|
768
|
+
assert queued_workflows[0]["Error"] is None
|
769
|
+
assert (
|
770
|
+
queued_workflows[0]["CreatedAt"] is not None
|
771
|
+
and len(queued_workflows[0]["CreatedAt"]) > 0
|
772
|
+
)
|
773
|
+
assert (
|
774
|
+
queued_workflows[0]["UpdatedAt"] is not None
|
775
|
+
and len(queued_workflows[0]["UpdatedAt"]) > 0
|
776
|
+
)
|
777
|
+
assert queued_workflows[0]["QueueName"] == test_queue1.name
|
778
|
+
assert queued_workflows[0]["ApplicationVersion"] == GlobalParams.app_version
|
779
|
+
assert queued_workflows[0]["ExecutorID"] == GlobalParams.executor_id
|
780
|
+
|
781
|
+
# Verify sort_desc inverts the order
|
782
|
+
filters: Dict[str, Any] = {
|
783
|
+
"sort_desc": True,
|
784
|
+
}
|
785
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
786
|
+
assert response.status_code == 200
|
787
|
+
filtered_workflows = response.json()
|
788
|
+
assert len(filtered_workflows) == len(handles)
|
789
|
+
assert (
|
790
|
+
filtered_workflows[0]["WorkflowUUID"] == handles[2].workflow_id
|
791
|
+
), "First workflow should be the last one enqueued"
|
792
|
+
|
793
|
+
# Test all filters
|
794
|
+
filters = {
|
795
|
+
"workflow_name": blocking_workflow.__qualname__,
|
796
|
+
}
|
797
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
798
|
+
assert response.status_code == 200
|
799
|
+
filtered_workflows = response.json()
|
800
|
+
assert len(filtered_workflows) == len(handles)
|
801
|
+
|
802
|
+
filters = {
|
803
|
+
"end_time": (datetime.now(timezone.utc) - timedelta(minutes=10)).isoformat(),
|
804
|
+
}
|
633
805
|
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
634
806
|
assert response.status_code == 200
|
807
|
+
filtered_workflows = response.json()
|
808
|
+
assert len(filtered_workflows) == 0
|
809
|
+
|
810
|
+
filters = {
|
811
|
+
"start_time": (datetime.now(timezone.utc) + timedelta(minutes=10)).isoformat(),
|
812
|
+
}
|
813
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
814
|
+
assert response.status_code == 200
|
815
|
+
filtered_workflows = response.json()
|
816
|
+
assert len(filtered_workflows) == 0
|
635
817
|
|
818
|
+
filters = {
|
819
|
+
"status": ["PENDING", "ENQUEUED"],
|
820
|
+
}
|
821
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
822
|
+
assert response.status_code == 200
|
823
|
+
filtered_workflows = response.json()
|
824
|
+
assert len(filtered_workflows) == len(handles)
|
825
|
+
|
826
|
+
filters = {
|
827
|
+
"queue_name": test_queue1.name,
|
828
|
+
}
|
829
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
830
|
+
assert response.status_code == 200
|
831
|
+
filtered_workflows = response.json()
|
832
|
+
assert len(filtered_workflows) == 2
|
833
|
+
|
834
|
+
filters = {"queue_name": test_queue1.name, "limit": 1, "offset": 1}
|
835
|
+
response = requests.post("http://localhost:3001/queues", json=filters, timeout=5)
|
836
|
+
assert response.status_code == 200
|
636
837
|
filtered_workflows = response.json()
|
637
838
|
assert isinstance(filtered_workflows, list), "Response should be a list"
|
638
839
|
assert (
|
639
840
|
len(filtered_workflows) == 1
|
640
841
|
), f"Expected 1 workflow, got {len(filtered_workflows)}"
|
842
|
+
assert filtered_workflows[0]["WorkflowUUID"] == handles[1].workflow_id
|
641
843
|
|
642
844
|
# Test with non-existent queue name
|
643
845
|
filters = {"queue_name": "non-existent-queue"}
|
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
|
{dbos-1.6.0a4 → dbos-1.7.0a2}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.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
|