dbos 0.20.0a3__tar.gz → 0.20.0a6__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.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

Files changed (89) hide show
  1. {dbos-0.20.0a3 → dbos-0.20.0a6}/PKG-INFO +1 -1
  2. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_core.py +21 -1
  3. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_dbos.py +1 -1
  4. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_fastapi.py +10 -9
  5. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_sys_db.py +1 -1
  6. {dbos-0.20.0a3 → dbos-0.20.0a6}/pyproject.toml +1 -1
  7. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/conftest.py +7 -15
  8. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_async.py +1 -1
  9. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_dbos.py +16 -0
  10. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_fastapi.py +76 -2
  11. {dbos-0.20.0a3 → dbos-0.20.0a6}/LICENSE +0 -0
  12. {dbos-0.20.0a3 → dbos-0.20.0a6}/README.md +0 -0
  13. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/__init__.py +0 -0
  14. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_admin_server.py +0 -0
  15. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_app_db.py +0 -0
  16. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_classproperty.py +0 -0
  17. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_cloudutils/authentication.py +0 -0
  18. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_cloudutils/cloudutils.py +0 -0
  19. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_cloudutils/databases.py +0 -0
  20. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_context.py +0 -0
  21. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_croniter.py +0 -0
  22. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_db_wizard.py +0 -0
  23. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_dbos_config.py +0 -0
  24. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_error.py +0 -0
  25. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_flask.py +0 -0
  26. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_kafka.py +0 -0
  27. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_kafka_message.py +0 -0
  28. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_logger.py +0 -0
  29. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/env.py +0 -0
  30. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/script.py.mako +0 -0
  31. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  32. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  33. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  34. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  35. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  36. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  37. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  38. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_outcome.py +0 -0
  39. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_queue.py +0 -0
  40. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_recovery.py +0 -0
  41. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_registrations.py +0 -0
  42. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_request.py +0 -0
  43. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_roles.py +0 -0
  44. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_scheduler.py +0 -0
  45. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_schemas/__init__.py +0 -0
  46. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_schemas/application_database.py +0 -0
  47. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_schemas/system_database.py +0 -0
  48. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_serialization.py +0 -0
  49. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/README.md +0 -0
  50. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  51. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  52. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  53. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  54. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  55. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  56. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  57. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  58. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  59. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_tracer.py +0 -0
  60. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/_workflow_commands.py +0 -0
  61. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/cli/_github_init.py +0 -0
  62. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/cli/_template_init.py +0 -0
  63. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/cli/cli.py +0 -0
  64. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/dbos-config.schema.json +0 -0
  65. {dbos-0.20.0a3 → dbos-0.20.0a6}/dbos/py.typed +0 -0
  66. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/__init__.py +0 -0
  67. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/atexit_no_ctor.py +0 -0
  68. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/atexit_no_launch.py +0 -0
  69. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/classdefs.py +0 -0
  70. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/more_classdefs.py +0 -0
  71. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/queuedworkflow.py +0 -0
  72. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_admin_server.py +0 -0
  73. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_classdecorators.py +0 -0
  74. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_concurrency.py +0 -0
  75. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_config.py +0 -0
  76. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_croniter.py +0 -0
  77. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_failures.py +0 -0
  78. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_fastapi_roles.py +0 -0
  79. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_flask.py +0 -0
  80. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_kafka.py +0 -0
  81. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_outcome.py +0 -0
  82. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_package.py +0 -0
  83. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_queue.py +0 -0
  84. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_scheduler.py +0 -0
  85. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_schema_migration.py +0 -0
  86. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_singleton.py +0 -0
  87. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_spans.py +0 -0
  88. {dbos-0.20.0a3 → dbos-0.20.0a6}/tests/test_workflow_cmds.py +0 -0
  89. {dbos-0.20.0a3 → dbos-0.20.0a6}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.20.0a3
3
+ Version: 0.20.0a6
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -488,6 +488,22 @@ def start_workflow(
488
488
  return WorkflowHandleFuture(new_wf_id, future, dbos)
489
489
 
490
490
 
491
+ if sys.version_info < (3, 12):
492
+
493
+ def _mark_coroutine(func: Callable[P, R]) -> Callable[P, R]:
494
+ @wraps(func)
495
+ async def async_wrapper(*args: Any, **kwargs: Any) -> R:
496
+ return await func(*args, **kwargs) # type: ignore
497
+
498
+ return async_wrapper # type: ignore
499
+
500
+ else:
501
+
502
+ def _mark_coroutine(func: Callable[P, R]) -> Callable[P, R]:
503
+ inspect.markcoroutinefunction(func)
504
+ return func
505
+
506
+
491
507
  def workflow_wrapper(
492
508
  dbosreg: "DBOSRegistry",
493
509
  func: Callable[P, R],
@@ -548,7 +564,7 @@ def workflow_wrapper(
548
564
  )
549
565
  return outcome() # type: ignore
550
566
 
551
- return wrapper
567
+ return _mark_coroutine(wrapper) if inspect.iscoroutinefunction(func) else wrapper
552
568
 
553
569
 
554
570
  def decorate_workflow(
@@ -838,6 +854,10 @@ def decorate_step(
838
854
  assert tempwf
839
855
  return tempwf(*args, **kwargs)
840
856
 
857
+ wrapper = (
858
+ _mark_coroutine(wrapper) if inspect.iscoroutinefunction(func) else wrapper # type: ignore
859
+ )
860
+
841
861
  def temp_wf_sync(*args: Any, **kwargs: Any) -> Any:
842
862
  return wrapper(*args, **kwargs)
843
863
 
@@ -245,7 +245,7 @@ class DBOS:
245
245
  return _dbos_global_instance
246
246
 
247
247
  @classmethod
248
- def destroy(cls, *, destroy_registry: bool = True) -> None:
248
+ def destroy(cls, *, destroy_registry: bool = False) -> None:
249
249
  global _dbos_global_instance
250
250
  if _dbos_global_instance is not None:
251
251
  _dbos_global_instance._destroy()
@@ -1,10 +1,10 @@
1
1
  import uuid
2
- from typing import Any, Callable, cast
2
+ from typing import Any, Callable, MutableMapping, cast
3
3
 
4
4
  from fastapi import FastAPI
5
5
  from fastapi import Request as FastAPIRequest
6
6
  from fastapi.responses import JSONResponse
7
- from starlette.types import ASGIApp, Message, Receive, Scope, Send
7
+ from starlette.types import ASGIApp, Receive, Scope, Send
8
8
 
9
9
  from . import DBOS
10
10
  from ._context import (
@@ -61,15 +61,16 @@ class LifespanMiddleware:
61
61
 
62
62
  async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
63
63
  if scope["type"] == "lifespan":
64
- while True:
65
- message = await receive()
66
- if message["type"] == "lifespan.startup":
64
+
65
+ async def wrapped_send(message: MutableMapping[str, Any]) -> None:
66
+ if message["type"] == "lifespan.startup.complete":
67
67
  self.dbos._launch()
68
- await send({"type": "lifespan.startup.complete"})
69
- elif message["type"] == "lifespan.shutdown":
68
+ elif message["type"] == "lifespan.shutdown.complete":
70
69
  self.dbos._destroy()
71
- await send({"type": "lifespan.shutdown.complete"})
72
- break
70
+ await send(message)
71
+
72
+ # Call the original app with our wrapped functions
73
+ await self.app(scope, receive, wrapped_send)
73
74
  else:
74
75
  await self.app(scope, receive, send)
75
76
 
@@ -367,7 +367,7 @@ class SystemDatabase:
367
367
  with self.engine.begin() as c:
368
368
  stmt = (
369
369
  sa.update(SystemSchema.workflow_status)
370
- .where(SystemSchema.workflow_inputs.c.workflow_uuid == workflow_uuid)
370
+ .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
371
371
  .values(
372
372
  status=status,
373
373
  )
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "0.20.0a3"
30
+ version = "0.20.0a6"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -98,7 +98,7 @@ def cleanup_test_databases(config: ConfigFile, postgres_db_engine: sa.Engine) ->
98
98
  def dbos(
99
99
  config: ConfigFile, cleanup_test_databases: None
100
100
  ) -> Generator[DBOS, Any, None]:
101
- DBOS.destroy()
101
+ DBOS.destroy(destroy_registry=True)
102
102
 
103
103
  # This launches for test convenience.
104
104
  # Tests add to running DBOS and then call stuff without adding
@@ -109,38 +109,30 @@ def dbos(
109
109
  DBOS.launch()
110
110
 
111
111
  yield dbos
112
- DBOS.destroy()
112
+ DBOS.destroy(destroy_registry=True)
113
113
 
114
114
 
115
115
  @pytest.fixture()
116
116
  def dbos_fastapi(
117
117
  config: ConfigFile, cleanup_test_databases: None
118
118
  ) -> Generator[Tuple[DBOS, FastAPI], Any, None]:
119
- DBOS.destroy()
119
+ DBOS.destroy(destroy_registry=True)
120
120
  app = FastAPI()
121
-
122
- # ignore the on_event deprecation warnings
123
- with warnings.catch_warnings():
124
- warnings.filterwarnings(
125
- "ignore",
126
- category=DeprecationWarning,
127
- message=r"\s*on_event is deprecated, use lifespan event handlers instead\.",
128
- )
129
- dbos = DBOS(fastapi=app, config=config)
121
+ dbos = DBOS(fastapi=app, config=config)
130
122
 
131
123
  # This is for test convenience.
132
124
  # Usually fastapi itself does launch, but we are not completing the fastapi lifecycle
133
125
  DBOS.launch()
134
126
 
135
127
  yield dbos, app
136
- DBOS.destroy()
128
+ DBOS.destroy(destroy_registry=True)
137
129
 
138
130
 
139
131
  @pytest.fixture()
140
132
  def dbos_flask(
141
133
  config: ConfigFile, cleanup_test_databases: None
142
134
  ) -> Generator[Tuple[DBOS, Flask], Any, None]:
143
- DBOS.destroy()
135
+ DBOS.destroy(destroy_registry=True)
144
136
  app = Flask(__name__)
145
137
 
146
138
  dbos = DBOS(flask=app, config=config)
@@ -150,7 +142,7 @@ def dbos_flask(
150
142
  DBOS.launch()
151
143
 
152
144
  yield dbos, app
153
- DBOS.destroy()
145
+ DBOS.destroy(destroy_registry=True)
154
146
 
155
147
 
156
148
  # Pretty-print test names
@@ -303,7 +303,7 @@ def test_async_tx_raises(config: ConfigFile) -> None:
303
303
  pass
304
304
 
305
305
  # destroy call needed to avoid "functions were registered but DBOS() was not called" warning
306
- DBOS.destroy()
306
+ DBOS.destroy(destroy_registry=True)
307
307
 
308
308
 
309
309
  @pytest.mark.asyncio
@@ -1212,3 +1212,19 @@ def test_debug_logging(dbos: DBOS, caplog: pytest.LogCaptureFixture) -> None:
1212
1212
 
1213
1213
  # Reset logging
1214
1214
  logging.getLogger("dbos").propagate = original_propagate
1215
+
1216
+
1217
+ def test_destroy_semantics(dbos: DBOS, config: ConfigFile) -> None:
1218
+
1219
+ @DBOS.workflow()
1220
+ def test_workflow(var: str) -> str:
1221
+ return var
1222
+
1223
+ var = "test"
1224
+ assert test_workflow(var) == var
1225
+
1226
+ DBOS.destroy()
1227
+ DBOS(config=config)
1228
+ DBOS.launch()
1229
+
1230
+ assert test_workflow(var) == var
@@ -1,14 +1,18 @@
1
+ import asyncio
1
2
  import logging
2
3
  import uuid
3
- from typing import Tuple
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any, Tuple
4
6
 
7
+ import httpx
5
8
  import pytest
6
9
  import sqlalchemy as sa
10
+ import uvicorn
7
11
  from fastapi import FastAPI
8
12
  from fastapi.testclient import TestClient
9
13
 
10
14
  # Public API
11
- from dbos import DBOS
15
+ from dbos import DBOS, ConfigFile
12
16
 
13
17
  # Private API because this is a unit test
14
18
  from dbos._context import assert_current_dbos_context
@@ -157,3 +161,73 @@ def test_endpoint_recovery(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
157
161
  workflow_handles = DBOS.recover_pending_workflows()
158
162
  assert len(workflow_handles) == 1
159
163
  assert workflow_handles[0].get_result() == ("a", wfuuid)
164
+
165
+
166
+ @pytest.mark.asyncio
167
+ async def test_custom_lifespan(
168
+ config: ConfigFile, cleanup_test_databases: None
169
+ ) -> None:
170
+ resource = None
171
+ port = 8000
172
+
173
+ @asynccontextmanager
174
+ async def lifespan(app: FastAPI) -> Any:
175
+ nonlocal resource
176
+ resource = 1
177
+ yield
178
+ resource = None
179
+
180
+ app = FastAPI(lifespan=lifespan)
181
+
182
+ DBOS.destroy()
183
+ DBOS(fastapi=app, config=config)
184
+
185
+ @app.get("/")
186
+ @DBOS.workflow()
187
+ async def resource_workflow() -> Any:
188
+ return {"resource": resource}
189
+
190
+ uvicorn_config = uvicorn.Config(
191
+ app=app, host="127.0.0.1", port=port, log_level="error"
192
+ )
193
+ server = uvicorn.Server(config=uvicorn_config)
194
+
195
+ # Run server in background task
196
+ server_task = asyncio.create_task(server.serve())
197
+ await asyncio.sleep(0.2) # Give server time to start
198
+
199
+ async with httpx.AsyncClient() as client:
200
+ r = await client.get(f"http://127.0.0.1:{port}")
201
+ assert r.json()["resource"] == 1
202
+
203
+ server.should_exit = True
204
+ await server_task
205
+ assert resource is None
206
+
207
+
208
+ def test_stacked_decorators_wf(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
209
+ dbos, app = dbos_fastapi
210
+ client = TestClient(app)
211
+
212
+ @app.get("/endpoint/{var1}/{var2}")
213
+ @DBOS.workflow()
214
+ async def test_endpoint(var1: str, var2: str) -> str:
215
+ return f"{var1}, {var2}!"
216
+
217
+ response = client.get("/endpoint/plums/deify")
218
+ assert response.status_code == 200
219
+ assert response.text == '"plums, deify!"'
220
+
221
+
222
+ def test_stacked_decorators_step(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
223
+ dbos, app = dbos_fastapi
224
+ client = TestClient(app)
225
+
226
+ @app.get("/endpoint/{var1}/{var2}")
227
+ @DBOS.step()
228
+ async def test_endpoint(var1: str, var2: str) -> str:
229
+ return f"{var1}, {var2}!"
230
+
231
+ response = client.get("/endpoint/plums/deify")
232
+ assert response.status_code == 200
233
+ assert response.text == '"plums, deify!"'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes