dbos 0.5.0a7__tar.gz → 0.5.0a11__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 (63) hide show
  1. {dbos-0.5.0a7 → dbos-0.5.0a11}/PKG-INFO +1 -1
  2. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/context.py +1 -4
  3. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/dbos.py +14 -6
  4. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/fastapi.py +13 -26
  5. dbos-0.5.0a11/dbos/request.py +32 -0
  6. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/__package/main.py +1 -1
  7. {dbos-0.5.0a7 → dbos-0.5.0a11}/pyproject.toml +1 -1
  8. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/conftest.py +12 -3
  9. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_concurrency.py +14 -24
  10. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_dbos.py +2 -2
  11. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_schema_migration.py +1 -1
  12. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_singleton.py +5 -5
  13. {dbos-0.5.0a7 → dbos-0.5.0a11}/LICENSE +0 -0
  14. {dbos-0.5.0a7 → dbos-0.5.0a11}/README.md +0 -0
  15. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/__init__.py +0 -0
  16. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/admin_sever.py +0 -0
  17. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/application_database.py +0 -0
  18. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/cli.py +0 -0
  19. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/core.py +0 -0
  20. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/dbos-config.schema.json +0 -0
  21. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/dbos_config.py +0 -0
  22. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/decorators.py +0 -0
  23. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/error.py +0 -0
  24. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/logger.py +0 -0
  25. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/migrations/env.py +0 -0
  26. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/migrations/script.py.mako +0 -0
  27. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  28. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  29. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/py.typed +0 -0
  30. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/recovery.py +0 -0
  31. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/registrations.py +0 -0
  32. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/roles.py +0 -0
  33. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/scheduler/croniter.py +0 -0
  34. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/scheduler/scheduler.py +0 -0
  35. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/schemas/__init__.py +0 -0
  36. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/schemas/application_database.py +0 -0
  37. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/schemas/system_database.py +0 -0
  38. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/system_database.py +0 -0
  39. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/README.md +0 -0
  40. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/__package/__init__.py +0 -0
  41. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/__package/schema.py +0 -0
  42. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/alembic.ini +0 -0
  43. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/dbos-config.yaml.dbos +0 -0
  44. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/migrations/env.py.dbos +0 -0
  45. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/migrations/script.py.mako +0 -0
  46. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py +0 -0
  47. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/templates/hello/start_postgres_docker.py +0 -0
  48. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/tracer.py +0 -0
  49. {dbos-0.5.0a7 → dbos-0.5.0a11}/dbos/utils.py +0 -0
  50. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/__init__.py +0 -0
  51. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/atexit_no_ctor.py +0 -0
  52. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/atexit_no_launch.py +0 -0
  53. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/classdefs.py +0 -0
  54. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/more_classdefs.py +0 -0
  55. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/scheduler/test_croniter.py +0 -0
  56. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/scheduler/test_scheduler.py +0 -0
  57. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_admin_server.py +0 -0
  58. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_classdecorators.py +0 -0
  59. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_config.py +0 -0
  60. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_failures.py +0 -0
  61. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_fastapi.py +0 -0
  62. {dbos-0.5.0a7 → dbos-0.5.0a11}/tests/test_package.py +0 -0
  63. {dbos-0.5.0a7 → dbos-0.5.0a11}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.5.0a7
3
+ Version: 0.5.0a11
4
4
  Summary: A Python framework for backends that scale
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -8,13 +8,10 @@ from types import TracebackType
8
8
  from typing import TYPE_CHECKING, List, Literal, Optional, Type, TypedDict
9
9
 
10
10
  from opentelemetry.trace import Span, Status, StatusCode
11
-
12
- if TYPE_CHECKING:
13
- from .fastapi import Request
14
-
15
11
  from sqlalchemy.orm import Session
16
12
 
17
13
  from .logger import dbos_logger
14
+ from .request import Request
18
15
  from .tracer import dbos_tracer
19
16
 
20
17
 
@@ -52,7 +52,7 @@ from .tracer import dbos_tracer
52
52
 
53
53
  if TYPE_CHECKING:
54
54
  from fastapi import FastAPI
55
- from .fastapi import Request
55
+ from .request import Request
56
56
 
57
57
  from sqlalchemy.orm import Session
58
58
 
@@ -202,8 +202,9 @@ class DBOS:
202
202
 
203
203
  def __new__(
204
204
  cls: Type[DBOS],
205
- fastapi: Optional["FastAPI"] = None,
205
+ *,
206
206
  config: Optional[ConfigFile] = None,
207
+ fastapi: Optional["FastAPI"] = None,
207
208
  ) -> DBOS:
208
209
  global _dbos_global_instance
209
210
  global _dbos_global_registry
@@ -239,8 +240,9 @@ class DBOS:
239
240
 
240
241
  def __init__(
241
242
  self,
242
- fastapi: Optional["FastAPI"] = None,
243
+ *,
243
244
  config: Optional[ConfigFile] = None,
245
+ fastapi: Optional["FastAPI"] = None,
244
246
  ) -> None:
245
247
  if hasattr(self, "_initialized") and self._initialized:
246
248
  return
@@ -267,7 +269,8 @@ class DBOS:
267
269
  from dbos.fastapi import setup_fastapi_middleware
268
270
 
269
271
  setup_fastapi_middleware(self.fastapi)
270
- self.fastapi.on_event("startup")(self.launch)
272
+ self.fastapi.on_event("startup")(self._launch)
273
+ self.fastapi.on_event("shutdown")(self._destroy)
271
274
 
272
275
  # Register send_stub as a workflow
273
276
  def send_temp_workflow(
@@ -313,7 +316,12 @@ class DBOS:
313
316
  rv: AdminServer = self._admin_server
314
317
  return rv
315
318
 
316
- def launch(self) -> None:
319
+ @classmethod
320
+ def launch(cls) -> None:
321
+ if _dbos_global_instance is not None:
322
+ _dbos_global_instance._launch()
323
+
324
+ def _launch(self) -> None:
317
325
  if self._launched:
318
326
  dbos_logger.warning(f"DBOS was already launched")
319
327
  return
@@ -651,7 +659,7 @@ class DBOS:
651
659
 
652
660
  @classproperty
653
661
  def request(cls) -> Optional["Request"]:
654
- """Return the FastAPI `Request`, if any, associated with the current context."""
662
+ """Return the HTTP `Request`, if any, associated with the current context."""
655
663
  ctx = assert_current_dbos_context()
656
664
  return ctx.request
657
665
 
@@ -11,6 +11,7 @@ from .context import (
11
11
  TracedAttributes,
12
12
  assert_current_dbos_context,
13
13
  )
14
+ from .request import Address, Request
14
15
 
15
16
  request_id_header = "x-request-id"
16
17
 
@@ -23,31 +24,17 @@ def get_or_generate_request_id(request: FastAPIRequest) -> str:
23
24
  return str(uuid.uuid4())
24
25
 
25
26
 
26
- class Request:
27
- """
28
- Serializable subset of the FastAPI Request object.
29
-
30
- Attributes:
31
- base_url(URL): Base of URL requested, as in application code
32
- client(Address): HTTP Client
33
- cookies(Dict[str, str]): HTTP Cookies
34
- headers(Headers): HTTP headers
35
- method(str): HTTP verb
36
- path_params(Dict[str,Any]): Parameters extracted from URL path sections
37
- query_params(QueryParams): URL query string parameters
38
- url(URL): Full URL accessed
39
-
40
- """
41
-
42
- def __init__(self, req: FastAPIRequest):
43
- self.headers = req.headers
44
- self.path_params = req.path_params
45
- self.query_params = req.query_params
46
- self.url = req.url
47
- self.base_url = req.base_url
48
- self.client = req.client
49
- self.cookies = req.cookies
50
- self.method = req.method
27
+ def make_request(request: FastAPIRequest) -> Request:
28
+ return Request(
29
+ headers=request.headers,
30
+ path_params=request.path_params,
31
+ query_params=request.query_params,
32
+ url=str(request.url),
33
+ base_url=str(request.base_url),
34
+ client=Address(*request.client) if request.client is not None else None,
35
+ cookies=request.cookies,
36
+ method=request.method,
37
+ )
51
38
 
52
39
 
53
40
  def setup_fastapi_middleware(app: FastAPI) -> None:
@@ -65,7 +52,7 @@ def setup_fastapi_middleware(app: FastAPI) -> None:
65
52
  }
66
53
  with EnterDBOSHandler(attributes):
67
54
  ctx = assert_current_dbos_context()
68
- ctx.request = Request(request)
55
+ ctx.request = make_request(request)
69
56
  workflow_id = request.headers.get("dbos-idempotency-key", "")
70
57
  with SetWorkflowID(workflow_id):
71
58
  response = await call_next(request)
@@ -0,0 +1,32 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Mapping, NamedTuple, Optional
3
+
4
+
5
+ class Address(NamedTuple):
6
+ hostname: str
7
+ port: int
8
+
9
+
10
+ @dataclass
11
+ class Request:
12
+ """
13
+ Serializable HTTP Request object.
14
+ Attributes:
15
+ base_url(str): Base of URL requested, as in application code
16
+ client(Optional[Address]): HTTP Client
17
+ cookies(Mapping[str, str]): HTTP Cookies
18
+ headers(Mapping[str, str]): HTTP headers
19
+ method(str): HTTP verb
20
+ path_params(Mapping[str, Any]): Parameters extracted from URL path sections
21
+ query_params(Mapping[str, str]): URL query string parameters
22
+ url(str): Full URL accessed
23
+ """
24
+
25
+ headers: Mapping[str, str]
26
+ path_params: Mapping[str, Any]
27
+ query_params: Mapping[str, str]
28
+ url: str
29
+ base_url: str
30
+ client: Optional[Address]
31
+ cookies: Mapping[str, str]
32
+ method: str
@@ -15,7 +15,7 @@ from dbos import DBOS
15
15
  from .schema import dbos_hello
16
16
 
17
17
  app = FastAPI()
18
- DBOS(app)
18
+ DBOS(fastapi=app)
19
19
 
20
20
  # Next, let's write a function that greets visitors.
21
21
  # To make it more interesting, we'll keep track of how
@@ -23,7 +23,7 @@ dependencies = [
23
23
  ]
24
24
  requires-python = ">=3.9"
25
25
  readme = "README.md"
26
- version = "0.5.0a7"
26
+ version = "0.5.0a11"
27
27
 
28
28
  [project.license]
29
29
  text = "MIT"
@@ -1,6 +1,7 @@
1
1
  import glob
2
2
  import os
3
3
  import subprocess
4
+ import warnings
4
5
  from typing import Any, Generator, Tuple
5
6
 
6
7
  import pytest
@@ -104,7 +105,7 @@ def dbos(
104
105
  # If your test is tricky and has a problem with this, use a different
105
106
  # fixture that does not launch.
106
107
  dbos = DBOS(config=config)
107
- dbos.launch()
108
+ DBOS.launch()
108
109
 
109
110
  yield dbos
110
111
  DBOS.destroy()
@@ -116,11 +117,19 @@ def dbos_fastapi(
116
117
  ) -> Generator[Tuple[DBOS, FastAPI], Any, None]:
117
118
  DBOS.destroy()
118
119
  app = FastAPI()
119
- dbos = DBOS(fastapi=app, config=config)
120
+
121
+ # ignore the on_event deprecation warnings
122
+ with warnings.catch_warnings():
123
+ warnings.filterwarnings(
124
+ "ignore",
125
+ category=DeprecationWarning,
126
+ message="\s*on_event is deprecated, use lifespan event handlers instead\.",
127
+ )
128
+ dbos = DBOS(fastapi=app, config=config)
120
129
 
121
130
  # This is for test convenience.
122
131
  # Usually fastapi itself does launch, but we are not completing the fastapi lifecycle
123
- dbos.launch()
132
+ DBOS.launch()
124
133
 
125
134
  yield dbos, app
126
135
  DBOS.destroy()
@@ -34,24 +34,20 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
34
34
  condition = threading.Condition()
35
35
  step_count = 0
36
36
  txn_count = 0
37
- notified = False
38
37
 
39
38
  @DBOS.step()
40
39
  def test_step() -> str:
41
- nonlocal step_count, notified
40
+ nonlocal step_count
41
+ condition.acquire()
42
42
  step_count += 1
43
- if step_count == 1:
43
+ if step_count % 2 == 1:
44
44
  # Wait for the other one to notify
45
- condition.acquire()
46
45
  condition.wait()
47
- notified = True
48
- condition.release()
49
46
  else:
50
- while not notified:
51
- condition.acquire()
52
- condition.notify()
53
- condition.release()
54
- time.sleep(0.1)
47
+ # Notify the other one
48
+ condition.notify()
49
+ condition.release()
50
+
55
51
  return DBOS.workflow_id
56
52
 
57
53
  @DBOS.workflow()
@@ -67,20 +63,17 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
67
63
  @DBOS.transaction(isolation_level="REPEATABLE READ")
68
64
  def test_transaction() -> str:
69
65
  DBOS.sql_session.execute(text("SELECT 1")).fetchall()
70
- nonlocal txn_count, notified
66
+ nonlocal txn_count
67
+ condition.acquire()
71
68
  txn_count += 1
72
- if txn_count == 1:
69
+ if txn_count % 2 == 1:
73
70
  # Wait for the other one to notify
74
- condition.acquire()
75
71
  condition.wait()
76
- notified = True
77
- condition.release()
78
72
  else:
79
- while not notified:
80
- condition.acquire()
81
- condition.notify()
82
- condition.release()
83
- time.sleep(0.1)
73
+ # Notify the other one
74
+ condition.notify()
75
+ condition.release()
76
+
84
77
  return DBOS.workflow_id
85
78
 
86
79
  def test_txn_thread(id: str) -> str:
@@ -99,8 +92,6 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
99
92
  assert wf_handle2.get_result() == wfuuid
100
93
 
101
94
  # Make sure temp workflows can handle conflicts as well.
102
- step_count = 0
103
- notified = False
104
95
  wfuuid = str(uuid.uuid4())
105
96
  with ThreadPoolExecutor(max_workers=2) as executor:
106
97
  future1 = executor.submit(test_comm_thread, wfuuid)
@@ -111,7 +102,6 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
111
102
 
112
103
  # Make sure temp transactions can handle conflicts as well.
113
104
  wfuuid = str(uuid.uuid4())
114
- notified = False
115
105
  with ThreadPoolExecutor(max_workers=2) as executor:
116
106
  future1 = executor.submit(test_txn_thread, wfuuid)
117
107
  future2 = executor.submit(test_txn_thread, wfuuid)
@@ -481,7 +481,7 @@ def test_recovery_thread(config: ConfigFile, dbos: DBOS) -> None:
481
481
  wf_counter += 1
482
482
  return var
483
483
 
484
- dbos.launch() # Usually the framework does this but we destroyed it above
484
+ DBOS.launch() # Usually the framework does this but we destroyed it above
485
485
 
486
486
  # Upon re-initialization, the background thread should recover the workflow safely.
487
487
  max_retries = 10
@@ -685,7 +685,7 @@ def test_without_fastapi() -> None:
685
685
  sys.meta_path.remove(blocker)
686
686
 
687
687
  dbos = DBOS(config=config)
688
- dbos.launch()
688
+ DBOS.launch()
689
689
 
690
690
  try:
691
691
 
@@ -65,7 +65,7 @@ def test_custom_sysdb_name_migration(
65
65
  # Test migrating up
66
66
  DBOS.destroy() # In case of other tests leaving it
67
67
  dbos = DBOS(config=config)
68
- dbos.launch()
68
+ DBOS.launch()
69
69
 
70
70
  # Make sure all tables exist
71
71
  with dbos.sys_db.engine.connect() as connection:
@@ -22,11 +22,11 @@ def test_dbos_singleton(cleanup_test_databases: None) -> None:
22
22
  # then imports more
23
23
  from tests.classdefs import DBOSSendRecv, DBOSTestClass, DBOSTestRoles
24
24
 
25
- dbos: DBOS = DBOS(None, default_config())
25
+ dbos: DBOS = DBOS(config=default_config())
26
26
 
27
27
  from tests.more_classdefs import DBOSWFEvents, wfFunc
28
28
 
29
- dbos.launch() # Usually framework (fastapi) does this via lifecycle event
29
+ DBOS.launch() # Usually framework (fastapi) does this via lifecycle event
30
30
 
31
31
  # Basics
32
32
  with SetWorkflowID("wfid"):
@@ -120,11 +120,11 @@ def test_dbos_singleton_negative(cleanup_test_databases: None) -> None:
120
120
  # then imports more
121
121
  from tests.classdefs import DBOSTestClass
122
122
 
123
- dbos: DBOS = DBOS(None, default_config())
123
+ dbos: DBOS = DBOS(config=default_config())
124
124
 
125
125
  # Don't initialize DBOS twice
126
126
  with pytest.raises(Exception) as exc_info:
127
- DBOS(None, default_config())
127
+ DBOS(config=default_config())
128
128
  assert "conflicting configuration" in str(exc_info.value)
129
129
 
130
130
  # Something should have launched
@@ -188,7 +188,7 @@ def test_config_before_singleton_negative(cleanup_test_databases: None) -> None:
188
188
 
189
189
  # Not OK, config already loaded in the default way
190
190
  with pytest.raises(Exception) as exc_info:
191
- DBOS(None, config=default_config())
191
+ DBOS(config=default_config())
192
192
  assert "configured multiple" in str(exc_info.value)
193
193
  finally:
194
194
  DBOS.destroy()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes