dbos 0.7.0a9__py3-none-any.whl → 0.8.0a3__py3-none-any.whl

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.

dbos/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from . import error as error
2
- from .context import DBOSContextEnsure, SetWorkflowID
2
+ from .context import DBOSContextEnsure, DBOSContextSetAuth, SetWorkflowID
3
3
  from .dbos import DBOS, DBOSConfiguredInstance, WorkflowHandle, WorkflowStatus
4
4
  from .dbos_config import ConfigFile, get_dbos_database_url, load_config
5
5
  from .kafka_message import KafkaMessage
@@ -11,6 +11,7 @@ __all__ = [
11
11
  "DBOS",
12
12
  "DBOSConfiguredInstance",
13
13
  "DBOSContextEnsure",
14
+ "DBOSContextSetAuth",
14
15
  "GetWorkflowsInput",
15
16
  "KafkaMessage",
16
17
  "SetWorkflowID",
dbos/context.py CHANGED
@@ -492,6 +492,39 @@ class EnterDBOSHandler:
492
492
  return False # Did not handle
493
493
 
494
494
 
495
+ class DBOSContextSetAuth(DBOSContextEnsure):
496
+ def __init__(self, user: Optional[str], roles: Optional[List[str]]) -> None:
497
+ self.created_ctx = False
498
+ self.user = user
499
+ self.roles = roles
500
+ self.prev_user: Optional[str] = None
501
+ self.prev_roles: Optional[List[str]] = None
502
+
503
+ def __enter__(self) -> DBOSContext:
504
+ ctx = get_local_dbos_context()
505
+ if ctx is None:
506
+ self.created_ctx = True
507
+ set_local_dbos_context(DBOSContext())
508
+ ctx = assert_current_dbos_context()
509
+ self.prev_user = ctx.authenticated_user
510
+ self.prev_roles = ctx.authenticated_roles
511
+ ctx.set_authentication(self.user, self.roles)
512
+ return ctx
513
+
514
+ def __exit__(
515
+ self,
516
+ exc_type: Optional[Type[BaseException]],
517
+ exc_value: Optional[BaseException],
518
+ traceback: Optional[TracebackType],
519
+ ) -> Literal[False]:
520
+ ctx = assert_current_dbos_context()
521
+ ctx.set_authentication(self.prev_user, self.prev_roles)
522
+ # Clean up the basic context if we created it
523
+ if self.created_ctx:
524
+ clear_local_dbos_context()
525
+ return False # Did not handle
526
+
527
+
495
528
  class DBOSAssumeRole:
496
529
  def __init__(self, assume_role: Optional[str]) -> None:
497
530
  self.prior_role: Optional[str] = None
dbos/core.py CHANGED
@@ -4,17 +4,7 @@ import time
4
4
  import traceback
5
5
  from concurrent.futures import Future
6
6
  from functools import wraps
7
- from typing import (
8
- TYPE_CHECKING,
9
- Any,
10
- Callable,
11
- Generic,
12
- List,
13
- Optional,
14
- Tuple,
15
- TypeVar,
16
- cast,
17
- )
7
+ from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Tuple, TypeVar, cast
18
8
 
19
9
  from dbos.application_database import ApplicationDatabase, TransactionResultInternal
20
10
 
@@ -61,10 +51,10 @@ from dbos.roles import check_required_roles
61
51
  from dbos.system_database import (
62
52
  GetEventWorkflowContext,
63
53
  OperationResultInternal,
64
- WorkflowInputs,
65
54
  WorkflowStatusInternal,
66
55
  WorkflowStatusString,
67
56
  )
57
+ from dbos.utils import WorkflowInputs
68
58
 
69
59
  if TYPE_CHECKING:
70
60
  from dbos.dbos import DBOS, Workflow, WorkflowHandle, WorkflowStatus, _DBOSRegistry
@@ -168,10 +158,10 @@ def _init_workflow(
168
158
  # We also have to do this for single-step workflows because of the foreign key constraint on the operation outputs table
169
159
  # TODO: Make this transactional (and with the queue step below)
170
160
  dbos._sys_db.update_workflow_status(status, False, ctx.in_recovery)
171
- dbos._sys_db.update_workflow_inputs(wfid, utils.serialize(inputs))
161
+ dbos._sys_db.update_workflow_inputs(wfid, utils.serialize_args(inputs))
172
162
  else:
173
163
  # Buffer the inputs for single-transaction workflows, but don't buffer the status
174
- dbos._sys_db.buffer_workflow_inputs(wfid, utils.serialize(inputs))
164
+ dbos._sys_db.buffer_workflow_inputs(wfid, utils.serialize_args(inputs))
175
165
 
176
166
  if queue is not None:
177
167
  dbos._sys_db.enqueue(wfid, queue)
@@ -203,7 +193,7 @@ def _execute_workflow(
203
193
  return output
204
194
  except Exception as error:
205
195
  status["status"] = "ERROR"
206
- status["error"] = utils.serialize(error)
196
+ status["error"] = utils.serialize_exception(error)
207
197
  if status["queue_name"] is not None:
208
198
  dbos._sys_db.remove_from_queue(status["workflow_uuid"])
209
199
  dbos._sys_db.update_workflow_status(status)
@@ -488,8 +478,10 @@ def _transaction(
488
478
  )
489
479
  if recorded_output:
490
480
  if recorded_output["error"]:
491
- deserialized_error = utils.deserialize(
492
- recorded_output["error"]
481
+ deserialized_error = (
482
+ utils.deserialize_exception(
483
+ recorded_output["error"]
484
+ )
493
485
  )
494
486
  has_recorded_error = True
495
487
  raise deserialized_error
@@ -527,7 +519,7 @@ def _transaction(
527
519
  except Exception as error:
528
520
  # Don't record the error if it was already recorded
529
521
  if not has_recorded_error:
530
- txn_output["error"] = utils.serialize(error)
522
+ txn_output["error"] = utils.serialize_exception(error)
531
523
  dbos._app_db.record_transaction_error(txn_output)
532
524
  raise
533
525
  return output
@@ -599,7 +591,9 @@ def _step(
599
591
  )
600
592
  if recorded_output:
601
593
  if recorded_output["error"] is not None:
602
- deserialized_error = utils.deserialize(recorded_output["error"])
594
+ deserialized_error = utils.deserialize_exception(
595
+ recorded_output["error"]
596
+ )
603
597
  raise deserialized_error
604
598
  elif recorded_output["output"] is not None:
605
599
  return utils.deserialize(recorded_output["output"])
@@ -639,7 +633,7 @@ def _step(
639
633
  )
640
634
 
641
635
  step_output["error"] = (
642
- utils.serialize(error) if error is not None else None
636
+ utils.serialize_exception(error) if error is not None else None
643
637
  )
644
638
  dbos._sys_db.record_operation_result(step_output)
645
639
 
@@ -0,0 +1,34 @@
1
+ """fix_job_queue
2
+
3
+ Revision ID: 50f3227f0b4b
4
+ Revises: eab0cc1d9a14
5
+ Create Date: 2024-09-25 14:03:53.308068
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "50f3227f0b4b"
15
+ down_revision: Union[str, None] = "eab0cc1d9a14"
16
+ branch_labels: Union[str, Sequence[str], None] = None
17
+ depends_on: Union[str, Sequence[str], None] = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.drop_constraint("job_queue_pkey", "job_queue", schema="dbos", type_="primary")
22
+
23
+ op.create_primary_key(
24
+ "job_queue_pkey", "job_queue", ["workflow_uuid"], schema="dbos"
25
+ )
26
+
27
+
28
+ def downgrade() -> None:
29
+ # Reverting the changes
30
+ op.drop_constraint("job_queue_pkey", "job_queue", schema="dbos", type_="primary")
31
+
32
+ op.create_primary_key(
33
+ "job_queue_pkey", "job_queue", ["created_at_epoch_ms"], schema="dbos"
34
+ )
dbos/queue.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import threading
2
2
  import time
3
+ import traceback
3
4
  from typing import TYPE_CHECKING, Optional
4
5
 
5
6
  from dbos.core import P, R, _execute_workflow_id, _start_workflow
6
- from dbos.error import DBOSInitializationError
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from dbos.dbos import DBOS, Workflow, WorkflowHandle
@@ -31,6 +31,13 @@ def queue_thread(stop_event: threading.Event, dbos: "DBOS") -> None:
31
31
  while not stop_event.is_set():
32
32
  time.sleep(1)
33
33
  for queue_name, queue in dbos._registry.queue_info_map.items():
34
- wf_ids = dbos._sys_db.start_queued_workflows(queue_name, queue.concurrency)
35
- for id in wf_ids:
36
- _execute_workflow_id(dbos, id)
34
+ try:
35
+ wf_ids = dbos._sys_db.start_queued_workflows(
36
+ queue_name, queue.concurrency
37
+ )
38
+ for id in wf_ids:
39
+ _execute_workflow_id(dbos, id)
40
+ except Exception:
41
+ dbos.logger.warning(
42
+ f"Exception encountered in queue thread: {traceback.format_exc()}"
43
+ )
dbos/system_database.py CHANGED
@@ -3,7 +3,18 @@ import os
3
3
  import threading
4
4
  import time
5
5
  from enum import Enum
6
- from typing import Any, Dict, List, Literal, Optional, Sequence, Set, TypedDict, cast
6
+ from typing import (
7
+ Any,
8
+ Dict,
9
+ List,
10
+ Literal,
11
+ Optional,
12
+ Sequence,
13
+ Set,
14
+ Tuple,
15
+ TypedDict,
16
+ cast,
17
+ )
7
18
 
8
19
  import psycopg
9
20
  import sqlalchemy as sa
@@ -36,11 +47,6 @@ WorkflowStatuses = Literal[
36
47
  ]
37
48
 
38
49
 
39
- class WorkflowInputs(TypedDict):
40
- args: Any
41
- kwargs: Any
42
-
43
-
44
50
  class WorkflowStatusInternal(TypedDict):
45
51
  workflow_uuid: str
46
52
  status: WorkflowStatuses
@@ -127,7 +133,7 @@ class WorkflowInformation(TypedDict, total=False):
127
133
  # The role used to run this workflow. Empty string if authorization is not required.
128
134
  authenticated_roles: List[str]
129
135
  # All roles the authenticated user has, if any.
130
- input: Optional[WorkflowInputs]
136
+ input: Optional[utils.WorkflowInputs]
131
137
  output: Optional[str]
132
138
  error: Optional[str]
133
139
  request: Optional[str]
@@ -447,7 +453,7 @@ class SystemDatabase:
447
453
  if status == str(WorkflowStatusString.SUCCESS.value):
448
454
  return utils.deserialize(stat["output"])
449
455
  elif status == str(WorkflowStatusString.ERROR.value):
450
- raise utils.deserialize(stat["error"])
456
+ raise utils.deserialize_exception(stat["error"])
451
457
  return None
452
458
 
453
459
  def get_workflow_info(
@@ -487,7 +493,7 @@ class SystemDatabase:
487
493
  self._exported_temp_txn_wf_status.discard(workflow_uuid)
488
494
  self._temp_txn_wf_ids.discard(workflow_uuid)
489
495
 
490
- def get_workflow_inputs(self, workflow_uuid: str) -> Optional[WorkflowInputs]:
496
+ def get_workflow_inputs(self, workflow_uuid: str) -> Optional[utils.WorkflowInputs]:
491
497
  with self.engine.begin() as c:
492
498
  row = c.execute(
493
499
  sa.select(SystemSchema.workflow_inputs.c.inputs).where(
@@ -496,7 +502,7 @@ class SystemDatabase:
496
502
  ).fetchone()
497
503
  if row is None:
498
504
  return None
499
- inputs: WorkflowInputs = utils.deserialize(row[0])
505
+ inputs: utils.WorkflowInputs = utils.deserialize_args(row[0])
500
506
  return inputs
501
507
 
502
508
  def get_workflows(self, input: GetWorkflowsInput) -> GetWorkflowsOutput:
dbos/utils.py CHANGED
@@ -1,14 +1,55 @@
1
- from typing import Any
1
+ import types
2
+ from typing import Any, Dict, Tuple, TypedDict
2
3
 
3
4
  import jsonpickle # type: ignore
4
5
 
5
6
 
7
+ class WorkflowInputs(TypedDict):
8
+ args: Tuple[Any, ...]
9
+ kwargs: Dict[str, Any]
10
+
11
+
12
+ def validate_item(data: Any) -> None:
13
+ if isinstance(data, (types.FunctionType, types.MethodType)):
14
+ raise TypeError("Serialized data item should not be a function")
15
+
16
+
6
17
  def serialize(data: Any) -> str:
7
18
  """Serialize an object to a JSON string using jsonpickle."""
8
- encoded_data: str = jsonpickle.encode(data)
19
+ validate_item(data)
20
+ encoded_data: str = jsonpickle.encode(data, unpicklable=True)
21
+ return encoded_data
22
+
23
+
24
+ def serialize_args(data: WorkflowInputs) -> str:
25
+ """Serialize args to a JSON string using jsonpickle."""
26
+ arg: Any
27
+ for arg in data["args"]:
28
+ validate_item(arg)
29
+ for arg in data["kwargs"].values():
30
+ validate_item(arg)
31
+ encoded_data: str = jsonpickle.encode(data, unpicklable=True)
32
+ return encoded_data
33
+
34
+
35
+ def serialize_exception(data: Exception) -> str:
36
+ """Serialize an Exception object to a JSON string using jsonpickle."""
37
+ encoded_data: str = jsonpickle.encode(data, unpicklable=True)
9
38
  return encoded_data
10
39
 
11
40
 
12
41
  def deserialize(serialized_data: str) -> Any:
13
42
  """Deserialize a JSON string back to a Python object using jsonpickle."""
14
43
  return jsonpickle.decode(serialized_data)
44
+
45
+
46
+ def deserialize_args(serialized_data: str) -> WorkflowInputs:
47
+ """Deserialize a JSON string back to a Python object list using jsonpickle."""
48
+ args: WorkflowInputs = jsonpickle.decode(serialized_data)
49
+ return args
50
+
51
+
52
+ def deserialize_exception(serialized_data: str) -> Exception:
53
+ """Deserialize JSON string back to a Python Exception using jsonpickle."""
54
+ upo: Exception = jsonpickle.decode(serialized_data)
55
+ return upo
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.7.0a9
3
+ Version: 0.8.0a3
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,13 +1,13 @@
1
- dbos-0.7.0a9.dist-info/METADATA,sha256=-j59txtUuEZaesLoUp2rVA_29aKu8w-lkWcbM4KI1oY,5010
2
- dbos-0.7.0a9.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- dbos-0.7.0a9.dist-info/entry_points.txt,sha256=3PmOPbM4FYxEmggRRdJw0oAsiBzKR8U0yx7bmwUmMOM,39
4
- dbos-0.7.0a9.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
- dbos/__init__.py,sha256=jjlBFzSAzO2e-LD5IKJw7bRqZjrxpF5Sn-_JUJJptHU,680
1
+ dbos-0.8.0a3.dist-info/METADATA,sha256=u3PDfNC0j5qeu05Uq1834FHAScD6ECq64z9_XOQqdiw,5010
2
+ dbos-0.8.0a3.dist-info/WHEEL,sha256=Vza3XR51HW1KmFP0iIMUVYIvz0uQuKJpIXKYOBGQyFQ,90
3
+ dbos-0.8.0a3.dist-info/entry_points.txt,sha256=z6GcVANQV7Uw_82H9Ob2axJX6V3imftyZsljdh-M1HU,54
4
+ dbos-0.8.0a3.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
+ dbos/__init__.py,sha256=-h1QgWNL11CiLlHEKa2ycAJVJw5SXYZ4BGNNWBAiE9k,726
6
6
  dbos/admin_sever.py,sha256=Qg5T3YRrbPW05PR_99yAaxgo1ugQrAp_uTeTqSfjm_k,3397
7
7
  dbos/application_database.py,sha256=knFK8We8y6WrIpnFCKvFq5hvSuFQqUuJqOqDpSVMCPI,5521
8
8
  dbos/cli.py,sha256=z5dXbbnGWzSC3E1rfS8Lp1_OIImzcDKM7jP-iu_Q4aI,8602
9
- dbos/context.py,sha256=1Xp0i-ZP72Vj6eMdHuyfXi9RHnoT2w4MZ2Kx1CrKnQ8,16559
10
- dbos/core.py,sha256=LnQRHLjfpdlzlRkElduay8nObNMyRNC8SJSZEzqei7o,29161
9
+ dbos/context.py,sha256=4MsxZdoh1WIsgoUsaxo0B6caGN6xq2WC60MzbBppzGk,17738
10
+ dbos/core.py,sha256=nV0w0wCkKm0VRVbUO0DgRnVqX5ue1Bn37UyAsdSXl48,29342
11
11
  dbos/dbos-config.schema.json,sha256=azpfmoDZg7WfSy3kvIsk9iEiKB_-VZt03VEOoXJAkqE,5331
12
12
  dbos/dbos.py,sha256=RtDcvKe4sm1TlnCGU4cyex-UI7hMMlhgzmOl1NuRLo4,29294
13
13
  dbos/dbos_config.py,sha256=NJVze2GkKgYUmcPP31Unb-QpsA0TzImEeQGJgVq6W6k,5352
@@ -20,11 +20,12 @@ dbos/kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
20
20
  dbos/logger.py,sha256=D-aFSZUCHBP34J1IZ5YNkTrJW-rDiH3py_v9jLU4Yrk,3565
21
21
  dbos/migrations/env.py,sha256=38SIGVbmn_VV2x2u1aHLcPOoWgZ84eCymf3g_NljmbU,1626
22
22
  dbos/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
23
+ dbos/migrations/versions/50f3227f0b4b_fix_job_queue.py,sha256=ZtnsZFMuon-D0n8V5BR10jQEqJPUsYsOwt29FAoKG8g,868
23
24
  dbos/migrations/versions/5c361fc04708_added_system_tables.py,sha256=QMgFMb0aLgC25YicsvPSr6AHRCA6Zd66hyaRUhwKzrQ,6404
24
25
  dbos/migrations/versions/a3b18ad34abe_added_triggers.py,sha256=Rv0ZsZYZ_WdgGEULYsPfnp4YzaO5L198gDTgYY39AVA,2022
25
26
  dbos/migrations/versions/eab0cc1d9a14_job_queue.py,sha256=_9-FCW-zOpCQfblTS_yRLtFiUaWlC1tM4BoKBTDeH9k,1395
26
27
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
27
- dbos/queue.py,sha256=5unMPX1ThoVWfXOR2IUSeIjgATgcX_YmZS3Fz1Uoc7o,1228
28
+ dbos/queue.py,sha256=ngY1MN3xD7jAvEXlrl_D16FUPpP_vpRgbyERLfPyU9Y,1437
28
29
  dbos/recovery.py,sha256=zqtO_ExGoIErLMVnbneU3VeHLVWvhV4jnfqssAVlQQk,2016
29
30
  dbos/registrations.py,sha256=gMI-u05tv5bpvyddQGtoUgCsqARx51aOY7p0JXPafQo,6539
30
31
  dbos/request.py,sha256=-FIwtknayvRl6OjvqO4V2GySVzSdP1Ft3cc9ZBS-PLY,928
@@ -34,7 +35,7 @@ dbos/scheduler/scheduler.py,sha256=Sz4EIpAtur7so2YajTic64GrTpa4qPw8QxXn0M34v80,1
34
35
  dbos/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
36
  dbos/schemas/application_database.py,sha256=q_Wr2XbiZNBYFkOtu7uKavo1T_cSOBblxKGHThYGGsY,962
36
37
  dbos/schemas/system_database.py,sha256=ed4c1UntsD-cqXD0ekM4jvcYYEViavDh_G6c0pVDe7k,4938
37
- dbos/system_database.py,sha256=VSGFSPubbMokGYsZfRb6cQPltLfjoWM-Re_2Gj9qkRc,41844
38
+ dbos/system_database.py,sha256=Os1-qqnrirKm_K37zuMgk14mm4ziXNYDjZnM1o8A450,41864
38
39
  dbos/templates/hello/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
39
40
  dbos/templates/hello/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  dbos/templates/hello/__package/main.py,sha256=eI0SS9Nwj-fldtiuSzIlIG6dC91GXXwdRsoHxv6S_WI,2719
@@ -46,6 +47,6 @@ dbos/templates/hello/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy
46
47
  dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py,sha256=U5thFWGqNN4QLrNXT7wUUqftIFDNE5eSdqD8JNW1mec,942
47
48
  dbos/templates/hello/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
48
49
  dbos/tracer.py,sha256=GaXDhdKKF_IQp5SAMipGXiDVwteRKjNbrXyYCH1mor0,2520
49
- dbos/utils.py,sha256=hWj9iWDrby2cVEhb0pG-IdnrxLqP64NhkaWUXiLc8bA,402
50
+ dbos/utils.py,sha256=lwRymY-y7GprAS8pKmbICQvOJd5eGxKGTxCMFn0OwaQ,1739
50
51
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
51
- dbos-0.7.0a9.dist-info/RECORD,,
52
+ dbos-0.8.0a3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.3.3)
2
+ Generator: pdm-backend (2.4.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,3 +1,5 @@
1
1
  [console_scripts]
2
2
  dbos = dbos.cli:app
3
3
 
4
+ [gui_scripts]
5
+