dbos 0.22.0a10__tar.gz → 0.23.0a1__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 (91) hide show
  1. {dbos-0.22.0a10 → dbos-0.23.0a1}/PKG-INFO +1 -1
  2. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_cloudutils/cloudutils.py +4 -2
  3. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_cloudutils/databases.py +4 -0
  4. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_core.py +24 -0
  5. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_dbos.py +12 -0
  6. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_error.py +11 -0
  7. {dbos-0.22.0a10 → dbos-0.23.0a1}/pyproject.toml +1 -1
  8. dbos-0.23.0a1/tests/test_workflow_cancel.py +145 -0
  9. {dbos-0.22.0a10 → dbos-0.23.0a1}/LICENSE +0 -0
  10. {dbos-0.22.0a10 → dbos-0.23.0a1}/README.md +0 -0
  11. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/__init__.py +0 -0
  12. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_admin_server.py +0 -0
  13. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_app_db.py +0 -0
  14. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_classproperty.py +0 -0
  15. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_cloudutils/authentication.py +0 -0
  16. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_context.py +0 -0
  17. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_croniter.py +0 -0
  18. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_db_wizard.py +0 -0
  19. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_dbos_config.py +0 -0
  20. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_fastapi.py +0 -0
  21. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_flask.py +0 -0
  22. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_kafka.py +0 -0
  23. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_kafka_message.py +0 -0
  24. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_logger.py +0 -0
  25. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/env.py +0 -0
  26. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/script.py.mako +0 -0
  27. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  28. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  29. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  30. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  31. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  32. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  33. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  34. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_outcome.py +0 -0
  35. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_queue.py +0 -0
  36. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_recovery.py +0 -0
  37. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_registrations.py +0 -0
  38. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_request.py +0 -0
  39. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_roles.py +0 -0
  40. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_scheduler.py +0 -0
  41. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_schemas/__init__.py +0 -0
  42. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_schemas/application_database.py +0 -0
  43. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_schemas/system_database.py +0 -0
  44. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_serialization.py +0 -0
  45. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_sys_db.py +0 -0
  46. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/README.md +0 -0
  47. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  48. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  49. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  50. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  51. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  52. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  53. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  54. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  55. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  56. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_tracer.py +0 -0
  57. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/_workflow_commands.py +0 -0
  58. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/cli/_github_init.py +0 -0
  59. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/cli/_template_init.py +0 -0
  60. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/cli/cli.py +0 -0
  61. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/dbos-config.schema.json +0 -0
  62. {dbos-0.22.0a10 → dbos-0.23.0a1}/dbos/py.typed +0 -0
  63. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/__init__.py +0 -0
  64. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/atexit_no_ctor.py +0 -0
  65. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/atexit_no_launch.py +0 -0
  66. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/classdefs.py +0 -0
  67. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/conftest.py +0 -0
  68. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/more_classdefs.py +0 -0
  69. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/queuedworkflow.py +0 -0
  70. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_admin_server.py +0 -0
  71. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_async.py +0 -0
  72. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_classdecorators.py +0 -0
  73. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_concurrency.py +0 -0
  74. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_config.py +0 -0
  75. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_croniter.py +0 -0
  76. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_dbos.py +0 -0
  77. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_failures.py +0 -0
  78. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_fastapi.py +0 -0
  79. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_fastapi_roles.py +0 -0
  80. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_flask.py +0 -0
  81. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_kafka.py +0 -0
  82. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_outcome.py +0 -0
  83. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_package.py +0 -0
  84. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_queue.py +0 -0
  85. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_scheduler.py +0 -0
  86. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_schema_migration.py +0 -0
  87. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_singleton.py +0 -0
  88. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_spans.py +0 -0
  89. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_sqlalchemy.py +0 -0
  90. {dbos-0.22.0a10 → dbos-0.23.0a1}/tests/test_workflow_cmds.py +0 -0
  91. {dbos-0.22.0a10 → dbos-0.23.0a1}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.22.0a10
3
+ Version: 0.23.0a1
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -29,9 +29,11 @@ class DBOSCloudCredentials:
29
29
  @dataclass
30
30
  class UserProfile:
31
31
  Name: str
32
- Email: str
33
32
  Organization: str
34
- SubscriptionPlan: str
33
+
34
+ def __init__(self, **kwargs: Any) -> None:
35
+ self.Name = kwargs.get("Name", "")
36
+ self.Organization = kwargs.get("Organization", "")
35
37
 
36
38
 
37
39
  class AppLanguages(Enum):
@@ -23,6 +23,10 @@ class UserDBCredentials:
23
23
  RoleName: str
24
24
  Password: str
25
25
 
26
+ def __init__(self, **kwargs: Any) -> None:
27
+ self.RoleName = kwargs.get("RoleName", "")
28
+ self.Password = kwargs.get("Password", "")
29
+
26
30
 
27
31
  @dataclass
28
32
  class UserDBInstance:
@@ -51,6 +51,7 @@ from ._error import (
51
51
  DBOSMaxStepRetriesExceeded,
52
52
  DBOSNonExistentWorkflowError,
53
53
  DBOSRecoveryError,
54
+ DBOSWorkflowCancelledError,
54
55
  DBOSWorkflowConflictIDError,
55
56
  DBOSWorkflowFunctionNotFoundError,
56
57
  )
@@ -224,6 +225,8 @@ def _get_wf_invoke_func(
224
225
  )
225
226
  output = wf_handle.get_result()
226
227
  return output
228
+ except DBOSWorkflowCancelledError as error:
229
+ raise
227
230
  except Exception as error:
228
231
  status["status"] = "ERROR"
229
232
  status["error"] = _serialization.serialize_exception(error)
@@ -539,6 +542,13 @@ def decorate_transaction(
539
542
  raise DBOSException(
540
543
  f"Function {func.__name__} invoked before DBOS initialized"
541
544
  )
545
+
546
+ ctx = assert_current_dbos_context()
547
+ if dbosreg.is_workflow_cancelled(ctx.workflow_id):
548
+ raise DBOSWorkflowCancelledError(
549
+ f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {func.__name__}."
550
+ )
551
+
542
552
  dbos = dbosreg.dbos
543
553
  with dbos._app_db.sessionmaker() as session:
544
554
  attributes: TracedAttributes = {
@@ -560,6 +570,12 @@ def decorate_transaction(
560
570
  backoff_factor = 1.5
561
571
  max_retry_wait_seconds = 2.0
562
572
  while True:
573
+
574
+ if dbosreg.is_workflow_cancelled(ctx.workflow_id):
575
+ raise DBOSWorkflowCancelledError(
576
+ f"Workflow {ctx.workflow_id} is cancelled. Aborting transaction {func.__name__}."
577
+ )
578
+
563
579
  has_recorded_error = False
564
580
  txn_error: Optional[Exception] = None
565
581
  try:
@@ -710,6 +726,13 @@ def decorate_step(
710
726
  "operationType": OperationType.STEP.value,
711
727
  }
712
728
 
729
+ # Check if the workflow is cancelled
730
+ ctx = assert_current_dbos_context()
731
+ if dbosreg.is_workflow_cancelled(ctx.workflow_id):
732
+ raise DBOSWorkflowCancelledError(
733
+ f"Workflow {ctx.workflow_id} is cancelled. Aborting step {func.__name__}."
734
+ )
735
+
713
736
  attempts = max_attempts if retries_allowed else 1
714
737
  max_retry_interval_seconds: float = 3600 # 1 Hour
715
738
 
@@ -800,6 +823,7 @@ def decorate_step(
800
823
  ctx = get_local_dbos_context()
801
824
  if ctx and ctx.is_step():
802
825
  # Call the original function directly
826
+
803
827
  return func(*args, **kwargs)
804
828
  if ctx and ctx.is_within_workflow():
805
829
  assert ctx.is_workflow(), "Steps must be called from within workflows"
@@ -155,6 +155,7 @@ class DBOSRegistry:
155
155
  self.pollers: list[RegisteredJob] = []
156
156
  self.dbos: Optional[DBOS] = None
157
157
  self.config: Optional[ConfigFile] = None
158
+ self.workflow_cancelled_map: dict[str, bool] = {}
158
159
 
159
160
  def register_wf_function(self, name: str, wrapped_func: F, functype: str) -> None:
160
161
  if name in self.function_type_map:
@@ -197,6 +198,15 @@ class DBOSRegistry:
197
198
  else:
198
199
  self.instance_info_map[fn] = inst
199
200
 
201
+ def cancel_workflow(self, workflow_id: str) -> None:
202
+ self.workflow_cancelled_map[workflow_id] = True
203
+
204
+ def is_workflow_cancelled(self, workflow_id: str) -> bool:
205
+ return self.workflow_cancelled_map.get(workflow_id, False)
206
+
207
+ def clear_workflow_cancelled(self, workflow_id: str) -> None:
208
+ self.workflow_cancelled_map.pop(workflow_id, None)
209
+
200
210
  def compute_app_version(self) -> str:
201
211
  """
202
212
  An application's version is computed from a hash of the source of its workflows.
@@ -844,11 +854,13 @@ class DBOS:
844
854
  def cancel_workflow(cls, workflow_id: str) -> None:
845
855
  """Cancel a workflow by ID."""
846
856
  _get_dbos_instance()._sys_db.cancel_workflow(workflow_id)
857
+ _get_or_create_dbos_registry().cancel_workflow(workflow_id)
847
858
 
848
859
  @classmethod
849
860
  def resume_workflow(cls, workflow_id: str) -> WorkflowHandle[Any]:
850
861
  """Resume a workflow by ID."""
851
862
  _get_dbos_instance()._sys_db.resume_workflow(workflow_id)
863
+ _get_or_create_dbos_registry().clear_workflow_cancelled(workflow_id)
852
864
  return execute_workflow_by_id(_get_dbos_instance(), workflow_id, False)
853
865
 
854
866
  @classproperty
@@ -36,6 +36,7 @@ class DBOSErrorCode(Enum):
36
36
  MaxStepRetriesExceeded = 7
37
37
  NotAuthorized = 8
38
38
  ConflictingWorkflowError = 9
39
+ WorkflowCancelled = 10
39
40
  ConflictingRegistrationError = 25
40
41
 
41
42
 
@@ -130,6 +131,16 @@ class DBOSMaxStepRetriesExceeded(DBOSException):
130
131
  )
131
132
 
132
133
 
134
+ class DBOSWorkflowCancelledError(DBOSException):
135
+ """Exception raised when the workflow has already been cancelled."""
136
+
137
+ def __init__(self, msg: str) -> None:
138
+ super().__init__(
139
+ msg,
140
+ dbos_error_code=DBOSErrorCode.WorkflowCancelled.value,
141
+ )
142
+
143
+
133
144
  class DBOSConflictingRegistrationError(DBOSException):
134
145
  """Exception raised when conflicting decorators are applied to the same function."""
135
146
 
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "0.22.0a10"
30
+ version = "0.23.0a1"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -0,0 +1,145 @@
1
+ import threading
2
+ import time
3
+ import uuid
4
+ from datetime import datetime, timedelta, timezone
5
+
6
+ # Public API
7
+ from dbos import (
8
+ DBOS,
9
+ ConfigFile,
10
+ Queue,
11
+ SetWorkflowID,
12
+ WorkflowStatusString,
13
+ _workflow_commands,
14
+ )
15
+
16
+
17
+ def test_basic(dbos: DBOS, config: ConfigFile) -> None:
18
+
19
+ steps_completed = 0
20
+
21
+ @DBOS.step()
22
+ def step_one() -> None:
23
+ nonlocal steps_completed
24
+ steps_completed += 1
25
+ print("Step one completed!")
26
+
27
+ @DBOS.step()
28
+ def step_two() -> None:
29
+ nonlocal steps_completed
30
+ steps_completed += 1
31
+ print("Step two completed!")
32
+
33
+ @DBOS.workflow()
34
+ def simple_workflow() -> None:
35
+ step_one()
36
+ dbos.sleep(1)
37
+ step_two()
38
+ print("Executed Simple workflow")
39
+ return
40
+
41
+ # run the workflow
42
+ simple_workflow()
43
+ time.sleep(1) # wait for the workflow to complete
44
+ assert (
45
+ steps_completed == 2
46
+ ), f"Expected steps_completed to be 2, but got {steps_completed}"
47
+
48
+
49
+ def test_two_steps_cancel(dbos: DBOS, config: ConfigFile) -> None:
50
+
51
+ steps_completed = 0
52
+
53
+ @DBOS.step()
54
+ def step_one() -> None:
55
+ nonlocal steps_completed
56
+ steps_completed += 1
57
+ print("Step one completed!")
58
+
59
+ @DBOS.step()
60
+ def step_two() -> None:
61
+ nonlocal steps_completed
62
+ steps_completed += 1
63
+ print("Step two completed!")
64
+
65
+ @DBOS.workflow()
66
+ def simple_workflow() -> None:
67
+ step_one()
68
+ dbos.sleep(2)
69
+ step_two()
70
+ print("Executed Simple workflow")
71
+ return
72
+
73
+ # run the workflow
74
+ try:
75
+ wfuuid = str(uuid.uuid4())
76
+ with SetWorkflowID(wfuuid):
77
+ simple_workflow()
78
+
79
+ dbos.cancel_workflow(wfuuid)
80
+ except Exception as e:
81
+ # time.sleep(1) # wait for the workflow to complete
82
+ assert (
83
+ steps_completed == 1
84
+ ), f"Expected steps_completed to be 1, but got {steps_completed}"
85
+
86
+ dbos.resume_workflow(wfuuid)
87
+ time.sleep(1)
88
+
89
+ assert (
90
+ steps_completed == 2
91
+ ), f"Expected steps_completed to be 2, but got {steps_completed}"
92
+
93
+
94
+ def test_two_transactions_cancel(dbos: DBOS, config: ConfigFile) -> None:
95
+
96
+ tr_completed = 0
97
+
98
+ @DBOS.transaction()
99
+ def transaction_one() -> None:
100
+ nonlocal tr_completed
101
+ tr_completed += 1
102
+ print("Transaction one completed!")
103
+
104
+ @DBOS.transaction()
105
+ def transaction_two() -> None:
106
+ nonlocal tr_completed
107
+ tr_completed += 1
108
+ print("Step two completed!")
109
+
110
+ @DBOS.workflow()
111
+ def simple_workflow() -> None:
112
+ transaction_one()
113
+ dbos.sleep(2)
114
+ transaction_two()
115
+ print("Executed Simple workflow")
116
+ return
117
+
118
+ # run the workflow
119
+ try:
120
+ wfuuid = str(uuid.uuid4())
121
+ with SetWorkflowID(wfuuid):
122
+ simple_workflow()
123
+
124
+ dbos.cancel_workflow(wfuuid)
125
+ except Exception as e:
126
+ # time.sleep(1) # wait for the workflow to complete
127
+ assert (
128
+ tr_completed == 1
129
+ ), f"Expected tr_completed to be 1, but got {tr_completed}"
130
+
131
+ dbos.resume_workflow(wfuuid)
132
+ time.sleep(1)
133
+
134
+ assert (
135
+ tr_completed == 2
136
+ ), f"Expected steps_completed to be 2, but got {tr_completed}"
137
+
138
+ # resume it a 2nd time
139
+
140
+ dbos.resume_workflow(wfuuid)
141
+ time.sleep(1)
142
+
143
+ assert (
144
+ tr_completed == 2
145
+ ), f"Expected steps_completed to be 2, but got {tr_completed}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes