dbos 1.14.0a2__tar.gz → 1.14.0a4__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.0a2 → dbos-1.14.0a4}/PKG-INFO +1 -1
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_core.py +25 -5
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_outcome.py +67 -13
- {dbos-1.14.0a2 → dbos-1.14.0a4}/pyproject.toml +1 -1
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_async.py +90 -4
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_async_workflow_management.py +1 -1
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_dbos.py +35 -3
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_workflow_introspection.py +4 -4
- {dbos-1.14.0a2 → dbos-1.14.0a4}/LICENSE +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/README.md +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/__init__.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/__main__.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_admin_server.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/env.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/script.py.mako +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/01ce9f07bd10_streaming.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_app_db.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_classproperty.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_client.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_context.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_croniter.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_dbos.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_dbos_config.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_debug.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_error.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_event_loop.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_fastapi.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_flask.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_kafka.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_kafka_message.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_logger.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_migration.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_queue.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_recovery.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_registrations.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_roles.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_scheduler.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_serialization.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_sys_db.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_sys_db_postgres.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_sys_db_sqlite.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_tracer.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_utils.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_workflow_commands.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/cli/_github_init.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/cli/_template_init.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/cli/cli.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/cli/migration.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/py.typed +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/__init__.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/atexit_no_launch.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/classdefs.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/client_collateral.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/client_worker.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/conftest.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/more_classdefs.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/queuedworkflow.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_admin_server.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_classdecorators.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_cli.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_client.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_concurrency.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_config.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_croniter.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_debug.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_docker_secrets.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_failures.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_fastapi.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_flask.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_kafka.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_outcome.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_package.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_queue.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_scheduler.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_schema_migration.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_singleton.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_spans.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_streaming.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/tests/test_workflow_management.py +0 -0
- {dbos-1.14.0a2 → dbos-1.14.0a4}/version/__init__.py +0 -0
@@ -19,8 +19,6 @@ from typing import (
|
|
19
19
|
cast,
|
20
20
|
)
|
21
21
|
|
22
|
-
import psycopg
|
23
|
-
|
24
22
|
from dbos._outcome import Immediate, NoResult, Outcome, Pending
|
25
23
|
from dbos._utils import GlobalParams, retriable_postgres_exception
|
26
24
|
|
@@ -58,6 +56,7 @@ from ._error import (
|
|
58
56
|
DBOSWorkflowConflictIDError,
|
59
57
|
DBOSWorkflowFunctionNotFoundError,
|
60
58
|
)
|
59
|
+
from ._logger import dbos_logger
|
61
60
|
from ._registrations import (
|
62
61
|
DEFAULT_MAX_RECOVERY_ATTEMPTS,
|
63
62
|
get_config_name,
|
@@ -98,6 +97,14 @@ F = TypeVar("F", bound=Callable[..., Any])
|
|
98
97
|
TEMP_SEND_WF_NAME = "<temp>.temp_send_workflow"
|
99
98
|
|
100
99
|
|
100
|
+
def check_is_in_coroutine() -> bool:
|
101
|
+
try:
|
102
|
+
asyncio.get_running_loop()
|
103
|
+
return True
|
104
|
+
except RuntimeError:
|
105
|
+
return False
|
106
|
+
|
107
|
+
|
101
108
|
class WorkflowHandleFuture(Generic[R]):
|
102
109
|
|
103
110
|
def __init__(self, workflow_id: str, future: Future[R], dbos: "DBOS"):
|
@@ -830,11 +837,16 @@ def workflow_wrapper(
|
|
830
837
|
dbos._sys_db.record_get_result(workflow_id, serialized_r, None)
|
831
838
|
return r
|
832
839
|
|
840
|
+
if check_is_in_coroutine() and not inspect.iscoroutinefunction(func):
|
841
|
+
dbos_logger.warning(
|
842
|
+
f"Sync workflow ({get_dbos_func_name(func)}) shouldn't be invoked from within another async function. Define it as async or use asyncio.to_thread instead."
|
843
|
+
)
|
844
|
+
|
833
845
|
outcome = (
|
834
|
-
wfOutcome.wrap(init_wf)
|
846
|
+
wfOutcome.wrap(init_wf, dbos=dbos)
|
835
847
|
.also(DBOSAssumeRole(rr))
|
836
848
|
.also(enterWorkflowCtxMgr(attributes))
|
837
|
-
.then(record_get_result)
|
849
|
+
.then(record_get_result, dbos=dbos)
|
838
850
|
)
|
839
851
|
return outcome() # type: ignore
|
840
852
|
|
@@ -1011,6 +1023,10 @@ def decorate_transaction(
|
|
1011
1023
|
assert (
|
1012
1024
|
ctx.is_workflow()
|
1013
1025
|
), "Transactions must be called from within workflows"
|
1026
|
+
if check_is_in_coroutine():
|
1027
|
+
dbos_logger.warning(
|
1028
|
+
f"Transaction function ({get_dbos_func_name(func)}) shouldn't be invoked from within another async function. Use asyncio.to_thread instead."
|
1029
|
+
)
|
1014
1030
|
with DBOSAssumeRole(rr):
|
1015
1031
|
return invoke_tx(*args, **kwargs)
|
1016
1032
|
else:
|
@@ -1146,7 +1162,7 @@ def decorate_step(
|
|
1146
1162
|
|
1147
1163
|
outcome = (
|
1148
1164
|
stepOutcome.then(record_step_result)
|
1149
|
-
.intercept(check_existing_result)
|
1165
|
+
.intercept(check_existing_result, dbos=dbos)
|
1150
1166
|
.also(EnterDBOSStep(attributes))
|
1151
1167
|
)
|
1152
1168
|
return outcome()
|
@@ -1155,6 +1171,10 @@ def decorate_step(
|
|
1155
1171
|
|
1156
1172
|
@wraps(func)
|
1157
1173
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
1174
|
+
if check_is_in_coroutine() and not inspect.iscoroutinefunction(func):
|
1175
|
+
dbos_logger.warning(
|
1176
|
+
f"Sync step ({get_dbos_func_name(func)}) shouldn't be invoked from within another async function. Define it as async or use asyncio.to_thread instead."
|
1177
|
+
)
|
1158
1178
|
# If the step is called from a workflow, run it as a step.
|
1159
1179
|
# Otherwise, run it as a normal function.
|
1160
1180
|
ctx = get_local_dbos_context()
|
@@ -2,9 +2,24 @@ import asyncio
|
|
2
2
|
import contextlib
|
3
3
|
import inspect
|
4
4
|
import time
|
5
|
-
from typing import
|
5
|
+
from typing import (
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Any,
|
8
|
+
Callable,
|
9
|
+
Coroutine,
|
10
|
+
Optional,
|
11
|
+
Protocol,
|
12
|
+
TypeVar,
|
13
|
+
Union,
|
14
|
+
cast,
|
15
|
+
)
|
6
16
|
|
7
17
|
from dbos._context import EnterDBOSStepRetry
|
18
|
+
from dbos._error import DBOSException
|
19
|
+
from dbos._registrations import get_dbos_func_name
|
20
|
+
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
from ._dbos import DBOS
|
8
23
|
|
9
24
|
T = TypeVar("T")
|
10
25
|
R = TypeVar("R")
|
@@ -24,10 +39,15 @@ class NoResult:
|
|
24
39
|
class Outcome(Protocol[T]):
|
25
40
|
|
26
41
|
def wrap(
|
27
|
-
self,
|
42
|
+
self,
|
43
|
+
before: Callable[[], Callable[[Callable[[], T]], R]],
|
44
|
+
*,
|
45
|
+
dbos: Optional["DBOS"] = None,
|
28
46
|
) -> "Outcome[R]": ...
|
29
47
|
|
30
|
-
def then(
|
48
|
+
def then(
|
49
|
+
self, next: Callable[[Callable[[], T]], R], *, dbos: Optional["DBOS"] = None
|
50
|
+
) -> "Outcome[R]": ...
|
31
51
|
|
32
52
|
def also(
|
33
53
|
self, cm: contextlib.AbstractContextManager[Any, bool]
|
@@ -41,7 +61,10 @@ class Outcome(Protocol[T]):
|
|
41
61
|
) -> "Outcome[T]": ...
|
42
62
|
|
43
63
|
def intercept(
|
44
|
-
self,
|
64
|
+
self,
|
65
|
+
interceptor: Callable[[], Union[NoResult, T]],
|
66
|
+
*,
|
67
|
+
dbos: Optional["DBOS"] = None,
|
45
68
|
) -> "Outcome[T]": ...
|
46
69
|
|
47
70
|
def __call__(self) -> Union[T, Coroutine[Any, Any, T]]: ...
|
@@ -63,11 +86,17 @@ class Immediate(Outcome[T]):
|
|
63
86
|
def __init__(self, func: Callable[[], T]):
|
64
87
|
self._func = func
|
65
88
|
|
66
|
-
def then(
|
89
|
+
def then(
|
90
|
+
self,
|
91
|
+
next: Callable[[Callable[[], T]], R],
|
92
|
+
dbos: Optional["DBOS"] = None,
|
93
|
+
) -> "Immediate[R]":
|
67
94
|
return Immediate(lambda: next(self._func))
|
68
95
|
|
69
96
|
def wrap(
|
70
|
-
self,
|
97
|
+
self,
|
98
|
+
before: Callable[[], Callable[[Callable[[], T]], R]],
|
99
|
+
dbos: Optional["DBOS"] = None,
|
71
100
|
) -> "Immediate[R]":
|
72
101
|
return Immediate(lambda: before()(self._func))
|
73
102
|
|
@@ -79,7 +108,10 @@ class Immediate(Outcome[T]):
|
|
79
108
|
return intercepted if not isinstance(intercepted, NoResult) else func()
|
80
109
|
|
81
110
|
def intercept(
|
82
|
-
self,
|
111
|
+
self,
|
112
|
+
interceptor: Callable[[], Union[NoResult, T]],
|
113
|
+
*,
|
114
|
+
dbos: Optional["DBOS"] = None,
|
83
115
|
) -> "Immediate[T]":
|
84
116
|
return Immediate[T](lambda: Immediate._intercept(self._func, interceptor))
|
85
117
|
|
@@ -142,7 +174,12 @@ class Pending(Outcome[T]):
|
|
142
174
|
async def _wrap(
|
143
175
|
func: Callable[[], Coroutine[Any, Any, T]],
|
144
176
|
before: Callable[[], Callable[[Callable[[], T]], R]],
|
177
|
+
*,
|
178
|
+
dbos: Optional["DBOS"] = None,
|
145
179
|
) -> R:
|
180
|
+
# Make sure the executor pool is configured correctly
|
181
|
+
if dbos is not None:
|
182
|
+
await dbos._configure_asyncio_thread_pool()
|
146
183
|
after = await asyncio.to_thread(before)
|
147
184
|
try:
|
148
185
|
value = await func()
|
@@ -151,12 +188,17 @@ class Pending(Outcome[T]):
|
|
151
188
|
return await asyncio.to_thread(after, lambda: Pending._raise(exp))
|
152
189
|
|
153
190
|
def wrap(
|
154
|
-
self,
|
191
|
+
self,
|
192
|
+
before: Callable[[], Callable[[Callable[[], T]], R]],
|
193
|
+
*,
|
194
|
+
dbos: Optional["DBOS"] = None,
|
155
195
|
) -> "Pending[R]":
|
156
|
-
return Pending[R](lambda: Pending._wrap(self._func, before))
|
196
|
+
return Pending[R](lambda: Pending._wrap(self._func, before, dbos=dbos))
|
157
197
|
|
158
|
-
def then(
|
159
|
-
|
198
|
+
def then(
|
199
|
+
self, next: Callable[[Callable[[], T]], R], *, dbos: Optional["DBOS"] = None
|
200
|
+
) -> "Pending[R]":
|
201
|
+
return Pending[R](lambda: Pending._wrap(self._func, lambda: next, dbos=dbos))
|
160
202
|
|
161
203
|
@staticmethod
|
162
204
|
async def _also( # type: ignore
|
@@ -173,12 +215,24 @@ class Pending(Outcome[T]):
|
|
173
215
|
async def _intercept(
|
174
216
|
func: Callable[[], Coroutine[Any, Any, T]],
|
175
217
|
interceptor: Callable[[], Union[NoResult, T]],
|
218
|
+
*,
|
219
|
+
dbos: Optional["DBOS"] = None,
|
176
220
|
) -> T:
|
221
|
+
# Make sure the executor pool is configured correctly
|
222
|
+
if dbos is not None:
|
223
|
+
await dbos._configure_asyncio_thread_pool()
|
177
224
|
intercepted = await asyncio.to_thread(interceptor)
|
178
225
|
return intercepted if not isinstance(intercepted, NoResult) else await func()
|
179
226
|
|
180
|
-
def intercept(
|
181
|
-
|
227
|
+
def intercept(
|
228
|
+
self,
|
229
|
+
interceptor: Callable[[], Union[NoResult, T]],
|
230
|
+
*,
|
231
|
+
dbos: Optional["DBOS"] = None,
|
232
|
+
) -> "Pending[T]":
|
233
|
+
return Pending[T](
|
234
|
+
lambda: Pending._intercept(self._func, interceptor, dbos=dbos)
|
235
|
+
)
|
182
236
|
|
183
237
|
@staticmethod
|
184
238
|
async def _retry(
|
@@ -5,6 +5,9 @@ from typing import List, Optional
|
|
5
5
|
|
6
6
|
import pytest
|
7
7
|
import sqlalchemy as sa
|
8
|
+
from opentelemetry._logs import set_logger_provider
|
9
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
10
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, InMemoryLogExporter
|
8
11
|
|
9
12
|
# Public API
|
10
13
|
from dbos import (
|
@@ -19,6 +22,8 @@ from dbos._context import assert_current_dbos_context
|
|
19
22
|
from dbos._dbos import WorkflowHandle
|
20
23
|
from dbos._dbos_config import ConfigFile
|
21
24
|
from dbos._error import DBOSAwaitedWorkflowCancelledError, DBOSException
|
25
|
+
from dbos._logger import dbos_logger
|
26
|
+
from dbos._registrations import get_dbos_func_name
|
22
27
|
|
23
28
|
|
24
29
|
@pytest.mark.asyncio
|
@@ -31,7 +36,7 @@ async def test_async_workflow(dbos: DBOS) -> None:
|
|
31
36
|
async def test_workflow(var1: str, var2: str) -> str:
|
32
37
|
nonlocal wf_counter
|
33
38
|
wf_counter += 1
|
34
|
-
res1 = test_transaction
|
39
|
+
res1 = await asyncio.to_thread(test_transaction, var1)
|
35
40
|
res2 = await test_step(var2)
|
36
41
|
DBOS.logger.info("I'm test_workflow")
|
37
42
|
return res1 + res2
|
@@ -88,7 +93,7 @@ async def test_async_step(dbos: DBOS) -> None:
|
|
88
93
|
async def test_workflow(var1: str, var2: str) -> str:
|
89
94
|
nonlocal wf_counter
|
90
95
|
wf_counter += 1
|
91
|
-
res1 = test_transaction
|
96
|
+
res1 = await asyncio.to_thread(test_transaction, var1)
|
92
97
|
res2 = await test_step(var2)
|
93
98
|
DBOS.logger.info("I'm test_workflow")
|
94
99
|
return res1 + res2
|
@@ -325,6 +330,7 @@ def test_async_tx_raises(config: ConfigFile) -> None:
|
|
325
330
|
async def test_async_tx() -> None:
|
326
331
|
pass
|
327
332
|
|
333
|
+
assert "is a coroutine function" in str(exc_info.value)
|
328
334
|
# destroy call needed to avoid "functions were registered but DBOS() was not called" warning
|
329
335
|
DBOS.destroy(destroy_registry=True)
|
330
336
|
|
@@ -343,12 +349,12 @@ async def test_start_workflow_async(dbos: DBOS) -> None:
|
|
343
349
|
wf_el_id = id(asyncio.get_running_loop())
|
344
350
|
nonlocal wf_counter
|
345
351
|
wf_counter += 1
|
346
|
-
res2 = test_step(var2)
|
352
|
+
res2 = await test_step(var2)
|
347
353
|
DBOS.logger.info("I'm test_workflow")
|
348
354
|
return var1 + res2
|
349
355
|
|
350
356
|
@DBOS.step()
|
351
|
-
def test_step(var: str) -> str:
|
357
|
+
async def test_step(var: str) -> str:
|
352
358
|
nonlocal step_el_id
|
353
359
|
step_el_id = id(asyncio.get_running_loop())
|
354
360
|
nonlocal step_counter
|
@@ -605,3 +611,83 @@ async def test_workflow_with_task_cancellation(dbos: DBOS) -> None:
|
|
605
611
|
# Verify the workflow completes despite the task cancellation
|
606
612
|
handle: WorkflowHandleAsync[str] = await DBOS.retrieve_workflow_async(wfid)
|
607
613
|
assert await handle.get_result() == "completed"
|
614
|
+
|
615
|
+
|
616
|
+
@pytest.mark.asyncio
|
617
|
+
async def test_check_async_violation(dbos: DBOS) -> None:
|
618
|
+
# Set up in-memory log exporter
|
619
|
+
log_exporter = InMemoryLogExporter() # type: ignore
|
620
|
+
log_processor = BatchLogRecordProcessor(log_exporter)
|
621
|
+
log_provider = LoggerProvider()
|
622
|
+
log_provider.add_log_record_processor(log_processor)
|
623
|
+
set_logger_provider(log_provider)
|
624
|
+
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
625
|
+
|
626
|
+
@DBOS.workflow()
|
627
|
+
def sync_workflow() -> str:
|
628
|
+
return "sync"
|
629
|
+
|
630
|
+
@DBOS.step()
|
631
|
+
def sync_step() -> str:
|
632
|
+
return "step"
|
633
|
+
|
634
|
+
@DBOS.workflow()
|
635
|
+
async def async_workflow_sync_step() -> str:
|
636
|
+
return sync_step()
|
637
|
+
|
638
|
+
@DBOS.transaction()
|
639
|
+
def sync_transaction() -> str:
|
640
|
+
return "txn"
|
641
|
+
|
642
|
+
@DBOS.workflow()
|
643
|
+
async def async_workflow_sync_txn() -> str:
|
644
|
+
return sync_transaction()
|
645
|
+
|
646
|
+
# Call a sync workflow should log a warning
|
647
|
+
sync_workflow()
|
648
|
+
|
649
|
+
log_processor.force_flush(timeout_millis=5000)
|
650
|
+
logs = log_exporter.get_finished_logs()
|
651
|
+
assert len(logs) == 1
|
652
|
+
assert (
|
653
|
+
logs[0].log_record.body is not None
|
654
|
+
and f"Sync workflow ({get_dbos_func_name(sync_workflow)}) shouldn't be invoked from within another async function."
|
655
|
+
in logs[0].log_record.body
|
656
|
+
)
|
657
|
+
log_exporter.clear()
|
658
|
+
|
659
|
+
# Call a sync step from within an async workflow should log a warning
|
660
|
+
await async_workflow_sync_step()
|
661
|
+
log_processor.force_flush(timeout_millis=5000)
|
662
|
+
logs = log_exporter.get_finished_logs()
|
663
|
+
assert len(logs) == 1
|
664
|
+
assert (
|
665
|
+
logs[0].log_record.body is not None
|
666
|
+
and f"Sync step ({get_dbos_func_name(sync_step)}) shouldn't be invoked from within another async function."
|
667
|
+
in logs[0].log_record.body
|
668
|
+
)
|
669
|
+
log_exporter.clear()
|
670
|
+
|
671
|
+
# Directly call a sync step should log a warning
|
672
|
+
sync_step()
|
673
|
+
log_processor.force_flush(timeout_millis=5000)
|
674
|
+
logs = log_exporter.get_finished_logs()
|
675
|
+
assert len(logs) == 1
|
676
|
+
assert (
|
677
|
+
logs[0].log_record.body is not None
|
678
|
+
and f"Sync step ({get_dbos_func_name(sync_step)}) shouldn't be invoked from within another async function."
|
679
|
+
in logs[0].log_record.body
|
680
|
+
)
|
681
|
+
log_exporter.clear()
|
682
|
+
|
683
|
+
# Call a sync transaction from within an async workflow should log a warning
|
684
|
+
await async_workflow_sync_txn()
|
685
|
+
log_processor.force_flush(timeout_millis=5000)
|
686
|
+
logs = log_exporter.get_finished_logs()
|
687
|
+
assert len(logs) == 1
|
688
|
+
assert (
|
689
|
+
logs[0].log_record.body is not None
|
690
|
+
and f"Transaction function ({get_dbos_func_name(sync_transaction)}) shouldn't be invoked from within another async function."
|
691
|
+
in logs[0].log_record.body
|
692
|
+
)
|
693
|
+
log_exporter.clear()
|
@@ -148,7 +148,7 @@ async def test_fork_workflow_async(dbos: DBOS) -> None:
|
|
148
148
|
|
149
149
|
wfid = str(uuid.uuid4())
|
150
150
|
with SetWorkflowID(wfid):
|
151
|
-
assert simple_workflow
|
151
|
+
assert await asyncio.to_thread(simple_workflow, input_val) == output
|
152
152
|
|
153
153
|
assert step_one_count == 1
|
154
154
|
assert step_two_count == 1
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# mypy: disable-error-code="no-redef"
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import datetime
|
4
5
|
import logging
|
5
6
|
import os
|
@@ -1250,12 +1251,43 @@ def test_destroy_semantics(dbos: DBOS, config: DBOSConfig) -> None:
|
|
1250
1251
|
var = "test"
|
1251
1252
|
assert test_workflow(var) == var
|
1252
1253
|
|
1254
|
+
# Start the workflow asynchornously
|
1255
|
+
wf = dbos.start_workflow(test_workflow, var)
|
1256
|
+
assert wf.get_result() == var
|
1257
|
+
|
1253
1258
|
DBOS.destroy()
|
1254
1259
|
DBOS(config=config)
|
1255
1260
|
DBOS.launch()
|
1256
1261
|
|
1257
1262
|
assert test_workflow(var) == var
|
1258
1263
|
|
1264
|
+
wf = dbos.start_workflow(test_workflow, var)
|
1265
|
+
assert wf.get_result() == var
|
1266
|
+
|
1267
|
+
|
1268
|
+
@pytest.mark.asyncio
|
1269
|
+
async def test_destroy_semantics_async(dbos: DBOS, config: DBOSConfig) -> None:
|
1270
|
+
|
1271
|
+
@DBOS.workflow()
|
1272
|
+
async def test_workflow(var: str) -> str:
|
1273
|
+
return var
|
1274
|
+
|
1275
|
+
var = "test"
|
1276
|
+
assert await test_workflow(var) == var
|
1277
|
+
|
1278
|
+
# Start the workflow asynchornously
|
1279
|
+
wf = await dbos.start_workflow_async(test_workflow, var)
|
1280
|
+
assert await wf.get_result() == var
|
1281
|
+
|
1282
|
+
DBOS.destroy()
|
1283
|
+
DBOS(config=config)
|
1284
|
+
DBOS.launch()
|
1285
|
+
|
1286
|
+
assert await test_workflow(var) == var
|
1287
|
+
|
1288
|
+
wf = await dbos.start_workflow_async(test_workflow, var)
|
1289
|
+
assert await wf.get_result() == var
|
1290
|
+
|
1259
1291
|
|
1260
1292
|
def test_double_decoration(dbos: DBOS) -> None:
|
1261
1293
|
with pytest.raises(
|
@@ -1637,17 +1669,17 @@ async def test_step_without_dbos(dbos: DBOS, config: DBOSConfig) -> None:
|
|
1637
1669
|
assert DBOS.workflow_id is None
|
1638
1670
|
return x
|
1639
1671
|
|
1640
|
-
assert step
|
1672
|
+
assert await asyncio.to_thread(step, 5) == 5
|
1641
1673
|
assert await async_step(5) == 5
|
1642
1674
|
|
1643
1675
|
DBOS(config=config)
|
1644
1676
|
|
1645
|
-
assert step
|
1677
|
+
assert await asyncio.to_thread(step, 5) == 5
|
1646
1678
|
assert await async_step(5) == 5
|
1647
1679
|
|
1648
1680
|
DBOS.launch()
|
1649
1681
|
|
1650
|
-
assert step
|
1682
|
+
assert await asyncio.to_thread(step, 5) == 5
|
1651
1683
|
assert await async_step(5) == 5
|
1652
1684
|
|
1653
1685
|
assert len(DBOS.list_workflows()) == 0
|
@@ -828,18 +828,18 @@ async def test_callchild_first_asyncio(dbos: DBOS) -> None:
|
|
828
828
|
async def parentWorkflow() -> str:
|
829
829
|
handle = await dbos.start_workflow_async(child_workflow)
|
830
830
|
child_id = await handle.get_result()
|
831
|
-
stepOne()
|
832
|
-
stepTwo()
|
831
|
+
await stepOne()
|
832
|
+
await stepTwo()
|
833
833
|
return child_id
|
834
834
|
|
835
835
|
@DBOS.step()
|
836
|
-
def stepOne() -> str:
|
836
|
+
async def stepOne() -> str:
|
837
837
|
workflow_id = DBOS.workflow_id
|
838
838
|
assert workflow_id is not None
|
839
839
|
return workflow_id
|
840
840
|
|
841
841
|
@DBOS.step()
|
842
|
-
def stepTwo() -> None:
|
842
|
+
async def stepTwo() -> None:
|
843
843
|
return
|
844
844
|
|
845
845
|
@DBOS.workflow()
|
File without changes
|
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.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py
RENAMED
File without changes
|
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py
RENAMED
File without changes
|
{dbos-1.14.0a2 → dbos-1.14.0a4}/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
|