dbos 1.3.0a9__tar.gz → 1.4.0__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.3.0a9 → dbos-1.4.0}/PKG-INFO +1 -1
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_event_loop.py +18 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_fastapi.py +4 -5
- {dbos-1.3.0a9 → dbos-1.4.0}/pyproject.toml +1 -1
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_async.py +26 -1
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_fastapi.py +16 -2
- {dbos-1.3.0a9 → dbos-1.4.0}/LICENSE +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/README.md +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/__init__.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/__main__.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_admin_server.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_app_db.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_classproperty.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_client.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_context.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_core.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_croniter.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_dbos.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_dbos_config.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_debug.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_error.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_flask.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_kafka.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_kafka_message.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_logger.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/env.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_outcome.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_queue.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_recovery.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_registrations.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_roles.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_scheduler.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_serialization.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_sys_db.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_tracer.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_utils.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/_workflow_commands.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/cli/_github_init.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/cli/_template_init.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/cli/cli.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/dbos/py.typed +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/__init__.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/atexit_no_launch.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/classdefs.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/client_collateral.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/client_worker.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/conftest.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/more_classdefs.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/queuedworkflow.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_admin_server.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_classdecorators.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_cli.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_client.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_concurrency.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_config.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_croniter.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_dbos.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_debug.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_docker_secrets.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_failures.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_flask.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_kafka.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_outcome.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_package.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_queue.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_scheduler.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_schema_migration.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_singleton.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_spans.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/tests/test_workflow_management.py +0 -0
- {dbos-1.3.0a9 → dbos-1.4.0}/version/__init__.py +0 -0
@@ -7,9 +7,13 @@ class BackgroundEventLoop:
|
|
7
7
|
"""
|
8
8
|
This is the event loop to which DBOS submits any coroutines that are not started from within an event loop.
|
9
9
|
In particular, coroutines submitted to queues (such as from scheduled workflows) run on this event loop.
|
10
|
+
|
11
|
+
If a main event loop is known (whether because an event loop existed in the thread that called DBOS.launch
|
12
|
+
or because a FastAPI event loop was detected) then coroutines are submitted there instead.
|
10
13
|
"""
|
11
14
|
|
12
15
|
def __init__(self) -> None:
|
16
|
+
self._main_loop: Optional[asyncio.AbstractEventLoop] = None
|
13
17
|
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
14
18
|
self._thread: Optional[threading.Thread] = None
|
15
19
|
self._running = False
|
@@ -19,6 +23,7 @@ class BackgroundEventLoop:
|
|
19
23
|
if self._running:
|
20
24
|
return
|
21
25
|
|
26
|
+
self.set_main_loop()
|
22
27
|
self._thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
23
28
|
self._thread.start()
|
24
29
|
self._ready.wait() # Wait until the loop is running
|
@@ -58,10 +63,23 @@ class BackgroundEventLoop:
|
|
58
63
|
await asyncio.gather(*tasks, return_exceptions=True)
|
59
64
|
self._loop.stop()
|
60
65
|
|
66
|
+
def set_main_loop(self) -> None:
|
67
|
+
"""
|
68
|
+
Set the main loop to the currently running event loop.
|
69
|
+
Should be called from the main thread.
|
70
|
+
"""
|
71
|
+
try:
|
72
|
+
self._main_loop = asyncio.get_running_loop()
|
73
|
+
except:
|
74
|
+
# There's no running event loop to set
|
75
|
+
pass
|
76
|
+
|
61
77
|
T = TypeVar("T")
|
62
78
|
|
63
79
|
def submit_coroutine(self, coro: Coroutine[Any, Any, T]) -> T:
|
64
80
|
"""Submit a coroutine to the background event loop"""
|
81
|
+
if self._main_loop is not None and self._main_loop.is_running():
|
82
|
+
return asyncio.run_coroutine_threadsafe(coro, self._main_loop).result()
|
65
83
|
if self._loop is None:
|
66
84
|
raise RuntimeError("Event loop not started")
|
67
85
|
return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
|
@@ -44,11 +44,10 @@ class LifespanMiddleware:
|
|
44
44
|
if scope["type"] == "lifespan":
|
45
45
|
|
46
46
|
async def wrapped_send(message: MutableMapping[str, Any]) -> None:
|
47
|
-
if
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
self.dbos._launch()
|
47
|
+
if message["type"] == "lifespan.startup.complete":
|
48
|
+
self.dbos._background_event_loop.set_main_loop()
|
49
|
+
if not self.dbos._launched:
|
50
|
+
self.dbos._launch()
|
52
51
|
elif message["type"] == "lifespan.shutdown.complete":
|
53
52
|
self.dbos._destroy()
|
54
53
|
await send(message)
|
@@ -7,7 +7,14 @@ import pytest
|
|
7
7
|
import sqlalchemy as sa
|
8
8
|
|
9
9
|
# Public API
|
10
|
-
from dbos import
|
10
|
+
from dbos import (
|
11
|
+
DBOS,
|
12
|
+
DBOSConfig,
|
13
|
+
Queue,
|
14
|
+
SetWorkflowID,
|
15
|
+
SetWorkflowTimeout,
|
16
|
+
WorkflowHandleAsync,
|
17
|
+
)
|
11
18
|
from dbos._context import assert_current_dbos_context
|
12
19
|
from dbos._dbos import WorkflowHandle
|
13
20
|
from dbos._dbos_config import ConfigFile
|
@@ -564,3 +571,21 @@ async def test_max_parallel_workflows(dbos: DBOS) -> None:
|
|
564
571
|
assert (
|
565
572
|
end_time - begin_time < 10
|
566
573
|
), "All enqueued tasks should complete in less than 10 seconds"
|
574
|
+
|
575
|
+
|
576
|
+
@pytest.mark.asyncio
|
577
|
+
async def test_main_loop(dbos: DBOS, config: DBOSConfig) -> None:
|
578
|
+
DBOS.destroy(destroy_registry=True)
|
579
|
+
dbos = DBOS(config=config)
|
580
|
+
DBOS.launch()
|
581
|
+
|
582
|
+
queue = Queue("queue")
|
583
|
+
|
584
|
+
@DBOS.workflow()
|
585
|
+
async def test_workflow() -> int:
|
586
|
+
await DBOS.sleep_async(0.1)
|
587
|
+
return id(asyncio.get_running_loop())
|
588
|
+
|
589
|
+
# Verify the enqueued task is submitted into the main event loop
|
590
|
+
handle = await queue.enqueue_async(test_workflow)
|
591
|
+
assert await handle.get_result() == id(asyncio.get_running_loop())
|
@@ -12,7 +12,7 @@ from fastapi import FastAPI
|
|
12
12
|
from fastapi.testclient import TestClient
|
13
13
|
|
14
14
|
# Public API
|
15
|
-
from dbos import DBOS, DBOSConfig
|
15
|
+
from dbos import DBOS, DBOSConfig, Queue
|
16
16
|
|
17
17
|
# Private API because this is a unit test
|
18
18
|
from dbos._context import assert_current_dbos_context
|
@@ -155,10 +155,21 @@ async def test_custom_lifespan(
|
|
155
155
|
DBOS.destroy()
|
156
156
|
DBOS(fastapi=app, config=config)
|
157
157
|
|
158
|
+
queue = Queue("queue")
|
159
|
+
|
158
160
|
@app.get("/")
|
159
161
|
@DBOS.workflow()
|
160
162
|
async def resource_workflow() -> Any:
|
161
|
-
|
163
|
+
handle = await queue.enqueue_async(queue_workflow)
|
164
|
+
return {
|
165
|
+
"resource": resource,
|
166
|
+
"loop": id(asyncio.get_event_loop()),
|
167
|
+
"queue_loop": await handle.get_result(),
|
168
|
+
}
|
169
|
+
|
170
|
+
@DBOS.workflow()
|
171
|
+
async def queue_workflow() -> int:
|
172
|
+
return id(asyncio.get_event_loop())
|
162
173
|
|
163
174
|
uvicorn_config = uvicorn.Config(
|
164
175
|
app=app, host="127.0.0.1", port=port, log_level="error"
|
@@ -172,6 +183,9 @@ async def test_custom_lifespan(
|
|
172
183
|
async with httpx.AsyncClient() as client:
|
173
184
|
r = await client.get(f"http://127.0.0.1:{port}")
|
174
185
|
assert r.json()["resource"] == 1
|
186
|
+
# Verify that both the FastAPI and enqueued workflows run in the main event loop
|
187
|
+
assert r.json()["loop"] == id(asyncio.get_event_loop())
|
188
|
+
assert r.json()["queue_loop"] == id(asyncio.get_event_loop())
|
175
189
|
|
176
190
|
server.should_exit = True
|
177
191
|
await server_task
|
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
|
{dbos-1.3.0a9 → dbos-1.4.0}/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
|