dbos 0.17.0a2__tar.gz → 0.17.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.

Potentially problematic release.


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

Files changed (79) hide show
  1. {dbos-0.17.0a2 → dbos-0.17.0a4}/PKG-INFO +1 -1
  2. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_core.py +6 -4
  3. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_outcome.py +21 -15
  4. {dbos-0.17.0a2 → dbos-0.17.0a4}/pyproject.toml +1 -1
  5. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_dbos.py +70 -1
  6. {dbos-0.17.0a2 → dbos-0.17.0a4}/LICENSE +0 -0
  7. {dbos-0.17.0a2 → dbos-0.17.0a4}/README.md +0 -0
  8. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/__init__.py +0 -0
  9. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_admin_server.py +0 -0
  10. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_app_db.py +0 -0
  11. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_classproperty.py +0 -0
  12. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_context.py +0 -0
  13. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_croniter.py +0 -0
  14. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_dbos.py +0 -0
  15. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_dbos_config.py +0 -0
  16. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_error.py +0 -0
  17. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_fastapi.py +0 -0
  18. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_flask.py +0 -0
  19. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_kafka.py +0 -0
  20. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_kafka_message.py +0 -0
  21. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_logger.py +0 -0
  22. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/env.py +0 -0
  23. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/script.py.mako +0 -0
  24. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  25. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  26. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  27. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  28. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  29. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  30. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_queue.py +0 -0
  31. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_recovery.py +0 -0
  32. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_registrations.py +0 -0
  33. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_request.py +0 -0
  34. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_roles.py +0 -0
  35. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_scheduler.py +0 -0
  36. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_schemas/__init__.py +0 -0
  37. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_schemas/application_database.py +0 -0
  38. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_schemas/system_database.py +0 -0
  39. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_serialization.py +0 -0
  40. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_sys_db.py +0 -0
  41. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/README.md +0 -0
  42. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/__package/__init__.py +0 -0
  43. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/__package/main.py +0 -0
  44. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/__package/schema.py +0 -0
  45. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/alembic.ini +0 -0
  46. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/dbos-config.yaml.dbos +0 -0
  47. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/migrations/env.py.dbos +0 -0
  48. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/migrations/script.py.mako +0 -0
  49. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/migrations/versions/2024_07_31_180642_init.py +0 -0
  50. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_templates/hello/start_postgres_docker.py +0 -0
  51. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/_tracer.py +0 -0
  52. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/cli.py +0 -0
  53. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/dbos-config.schema.json +0 -0
  54. {dbos-0.17.0a2 → dbos-0.17.0a4}/dbos/py.typed +0 -0
  55. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/__init__.py +0 -0
  56. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/atexit_no_ctor.py +0 -0
  57. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/atexit_no_launch.py +0 -0
  58. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/classdefs.py +0 -0
  59. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/conftest.py +0 -0
  60. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/more_classdefs.py +0 -0
  61. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_admin_server.py +0 -0
  62. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_async.py +0 -0
  63. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_classdecorators.py +0 -0
  64. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_concurrency.py +0 -0
  65. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_config.py +0 -0
  66. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_croniter.py +0 -0
  67. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_failures.py +0 -0
  68. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_fastapi.py +0 -0
  69. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_fastapi_roles.py +0 -0
  70. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_flask.py +0 -0
  71. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_kafka.py +0 -0
  72. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_outcome.py +0 -0
  73. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_package.py +0 -0
  74. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_queue.py +0 -0
  75. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_scheduler.py +0 -0
  76. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_schema_migration.py +0 -0
  77. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_singleton.py +0 -0
  78. {dbos-0.17.0a2 → dbos-0.17.0a4}/tests/test_spans.py +0 -0
  79. {dbos-0.17.0a2 → dbos-0.17.0a4}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.17.0a2
3
+ Version: 0.17.0a4
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -21,7 +21,7 @@ from typing import (
21
21
  overload,
22
22
  )
23
23
 
24
- from dbos._outcome import Immediate, Outcome, Pending
24
+ from dbos._outcome import Immediate, NoResult, Outcome, Pending
25
25
 
26
26
  from ._app_db import ApplicationDatabase, TransactionResultInternal
27
27
 
@@ -719,7 +719,7 @@ def decorate_step(
719
719
  finally:
720
720
  dbos._sys_db.record_operation_result(step_output)
721
721
 
722
- def check_existing_result() -> Optional[str]:
722
+ def check_existing_result() -> Union[NoResult, R]:
723
723
  ctx = assert_current_dbos_context()
724
724
  recorded_output = dbos._sys_db.check_operation_execution(
725
725
  ctx.workflow_id, ctx.function_id
@@ -734,14 +734,16 @@ def decorate_step(
734
734
  )
735
735
  raise deserialized_error
736
736
  elif recorded_output["output"] is not None:
737
- return recorded_output["output"]
737
+ return cast(
738
+ R, _serialization.deserialize(recorded_output["output"])
739
+ )
738
740
  else:
739
741
  raise Exception("Output and error are both None")
740
742
  else:
741
743
  dbos.logger.debug(
742
744
  f"Running step, id: {ctx.function_id}, name: {attributes['name']}"
743
745
  )
744
- return None
746
+ return NoResult()
745
747
 
746
748
  stepOutcome = Outcome[R].make(functools.partial(func, *args, **kwargs))
747
749
  if retries_allowed:
@@ -4,12 +4,20 @@ import inspect
4
4
  import time
5
5
  from typing import Any, Callable, Coroutine, Optional, Protocol, TypeVar, Union, cast
6
6
 
7
- from . import _serialization
8
-
9
7
  T = TypeVar("T")
10
8
  R = TypeVar("R")
11
9
 
12
10
 
11
+ class NoResult:
12
+ _instance: Optional["NoResult"] = None
13
+ __slots__ = ()
14
+
15
+ def __new__(cls, *args: Any, **kwargs: Any) -> "NoResult":
16
+ if not cls._instance:
17
+ cls._instance = super(NoResult, cls).__new__(cls, *args, **kwargs)
18
+ return cls._instance
19
+
20
+
13
21
  # define Outcome protocol w/ common composition methods
14
22
  class Outcome(Protocol[T]):
15
23
 
@@ -30,7 +38,9 @@ class Outcome(Protocol[T]):
30
38
  exceeded_retries: Callable[[int], BaseException],
31
39
  ) -> "Outcome[T]": ...
32
40
 
33
- def intercept(self, interceptor: Callable[[], Optional[str]]) -> "Outcome[T]": ...
41
+ def intercept(
42
+ self, interceptor: Callable[[], Union[NoResult, T]]
43
+ ) -> "Outcome[T]": ...
34
44
 
35
45
  def __call__(self) -> Union[T, Coroutine[Any, Any, T]]: ...
36
46
 
@@ -61,14 +71,14 @@ class Immediate(Outcome[T]):
61
71
 
62
72
  @staticmethod
63
73
  def _intercept(
64
- func: Callable[[], T], interceptor: Callable[[], Optional[str]]
74
+ func: Callable[[], T], interceptor: Callable[[], Union[NoResult, T]]
65
75
  ) -> T:
66
76
  intercepted = interceptor()
67
- return (
68
- cast(T, _serialization.deserialize(intercepted)) if intercepted else func()
69
- )
77
+ return intercepted if not isinstance(intercepted, NoResult) else func()
70
78
 
71
- def intercept(self, interceptor: Callable[[], Optional[str]]) -> "Immediate[T]":
79
+ def intercept(
80
+ self, interceptor: Callable[[], Union[NoResult, T]]
81
+ ) -> "Immediate[T]":
72
82
  return Immediate[T](lambda: Immediate._intercept(self._func, interceptor))
73
83
 
74
84
  @staticmethod
@@ -157,16 +167,12 @@ class Pending(Outcome[T]):
157
167
  @staticmethod
158
168
  async def _intercept(
159
169
  func: Callable[[], Coroutine[Any, Any, T]],
160
- interceptor: Callable[[], Optional[str]],
170
+ interceptor: Callable[[], Union[NoResult, T]],
161
171
  ) -> T:
162
172
  intercepted = await asyncio.to_thread(interceptor)
163
- return (
164
- cast(T, _serialization.deserialize(intercepted))
165
- if intercepted
166
- else await func()
167
- )
173
+ return intercepted if not isinstance(intercepted, NoResult) else await func()
168
174
 
169
- def intercept(self, interceptor: Callable[[], Optional[str]]) -> "Pending[T]":
175
+ def intercept(self, interceptor: Callable[[], Union[NoResult, T]]) -> "Pending[T]":
170
176
  return Pending[T](lambda: Pending._intercept(self._func, interceptor))
171
177
 
172
178
  @staticmethod
@@ -23,7 +23,7 @@ dependencies = [
23
23
  ]
24
24
  requires-python = ">=3.9"
25
25
  readme = "README.md"
26
- version = "0.17.0a2"
26
+ version = "0.17.0a4"
27
27
 
28
28
  [project.license]
29
29
  text = "MIT"
@@ -318,6 +318,7 @@ def test_temp_workflow_errors(dbos: DBOS) -> None:
318
318
 
319
319
  def test_recovery_workflow(dbos: DBOS) -> None:
320
320
  txn_counter: int = 0
321
+ txn_return_none_counter: int = 0
321
322
  wf_counter: int = 0
322
323
 
323
324
  @DBOS.workflow()
@@ -325,6 +326,8 @@ def test_recovery_workflow(dbos: DBOS) -> None:
325
326
  nonlocal wf_counter
326
327
  wf_counter += 1
327
328
  res = test_transaction(var2)
329
+ should_be_none = test_transaction_return_none()
330
+ assert should_be_none is None
328
331
  return res + var
329
332
 
330
333
  @DBOS.transaction()
@@ -334,6 +337,13 @@ def test_recovery_workflow(dbos: DBOS) -> None:
334
337
  txn_counter += 1
335
338
  return var2 + str(rows[0][0])
336
339
 
340
+ @DBOS.transaction()
341
+ def test_transaction_return_none() -> None:
342
+ nonlocal txn_return_none_counter
343
+ DBOS.sql_session.execute(sa.text("SELECT 1")).fetchall()
344
+ txn_return_none_counter += 1
345
+ return
346
+
337
347
  wfuuid = str(uuid.uuid4())
338
348
  with SetWorkflowID(wfuuid):
339
349
  assert test_workflow("bob", "bob") == "bob1bob"
@@ -367,6 +377,7 @@ def test_recovery_workflow(dbos: DBOS) -> None:
367
377
  assert workflow_handles[0].get_result() == "bob1bob"
368
378
  assert wf_counter == 2
369
379
  assert txn_counter == 1
380
+ assert txn_return_none_counter == 1
370
381
 
371
382
  # Test that there was a recovery attempt of this
372
383
  stat = workflow_handles[0].get_status()
@@ -382,7 +393,8 @@ def test_recovery_workflow_step(dbos: DBOS) -> None:
382
393
  def test_workflow(var: str, var2: str) -> str:
383
394
  nonlocal wf_counter
384
395
  wf_counter += 1
385
- test_step(var2)
396
+ should_be_none = test_step(var2)
397
+ assert should_be_none is None
386
398
  return var
387
399
 
388
400
  @DBOS.step()
@@ -432,6 +444,63 @@ def test_recovery_workflow_step(dbos: DBOS) -> None:
432
444
  assert stat.recovery_attempts == 1
433
445
 
434
446
 
447
+ def test_workflow_returns_none(dbos: DBOS) -> None:
448
+ wf_counter: int = 0
449
+
450
+ @DBOS.workflow()
451
+ def test_workflow(var: str, var2: str) -> None:
452
+ nonlocal wf_counter
453
+ wf_counter += 1
454
+ assert var == var2 == "bob"
455
+ return
456
+
457
+ wfuuid = str(uuid.uuid4())
458
+ with SetWorkflowID(wfuuid):
459
+ assert test_workflow("bob", "bob") is None
460
+ assert wf_counter == 1
461
+
462
+ dbos._sys_db.wait_for_buffer_flush()
463
+ with SetWorkflowID(wfuuid):
464
+ assert test_workflow("bob", "bob") is None
465
+ assert wf_counter == 2
466
+
467
+ handle: WorkflowHandle[None] = DBOS.retrieve_workflow(wfuuid)
468
+ assert handle.get_result() == None
469
+ assert wf_counter == 2
470
+
471
+ # Change the workflow status to pending
472
+ dbos._sys_db.update_workflow_status(
473
+ {
474
+ "workflow_uuid": wfuuid,
475
+ "status": "PENDING",
476
+ "name": test_workflow.__qualname__,
477
+ "class_name": None,
478
+ "config_name": None,
479
+ "output": None,
480
+ "error": None,
481
+ "executor_id": None,
482
+ "app_id": None,
483
+ "app_version": None,
484
+ "request": None,
485
+ "recovery_attempts": None,
486
+ "authenticated_user": None,
487
+ "authenticated_roles": None,
488
+ "assumed_role": None,
489
+ "queue_name": None,
490
+ }
491
+ )
492
+
493
+ workflow_handles = DBOS.recover_pending_workflows()
494
+ assert len(workflow_handles) == 1
495
+ assert workflow_handles[0].get_result() is None
496
+ assert wf_counter == 3
497
+
498
+ # Test that there was a recovery attempt of this
499
+ stat = workflow_handles[0].get_status()
500
+ assert stat
501
+ assert stat.recovery_attempts == 1
502
+
503
+
435
504
  def test_recovery_temp_workflow(dbos: DBOS) -> None:
436
505
  txn_counter: int = 0
437
506
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes