dbos 0.5.0a11__py3-none-any.whl → 0.6.0__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,19 +1,17 @@
1
1
  from . import error as error
2
- from .context import SetWorkflowID as SetWorkflowID
3
- from .dbos import DBOS as DBOS
4
- from .dbos import DBOSConfiguredInstance as DBOSConfiguredInstance
5
- from .dbos import WorkflowHandle as WorkflowHandle
6
- from .dbos import WorkflowStatus as WorkflowStatus
7
- from .dbos_config import ConfigFile as ConfigFile
8
- from .dbos_config import get_dbos_database_url, load_config
9
- from .system_database import GetWorkflowsInput as GetWorkflowsInput
10
- from .system_database import WorkflowStatusString as WorkflowStatusString
2
+ from .context import DBOSContextEnsure, SetWorkflowID
3
+ from .dbos import DBOS, DBOSConfiguredInstance, WorkflowHandle, WorkflowStatus
4
+ from .dbos_config import ConfigFile, get_dbos_database_url, load_config
5
+ from .kafka_message import KafkaMessage
6
+ from .system_database import GetWorkflowsInput, WorkflowStatusString
11
7
 
12
8
  __all__ = [
13
9
  "ConfigFile",
14
10
  "DBOS",
15
11
  "DBOSConfiguredInstance",
12
+ "DBOSContextEnsure",
16
13
  "GetWorkflowsInput",
14
+ "KafkaMessage",
17
15
  "SetWorkflowID",
18
16
  "WorkflowHandle",
19
17
  "WorkflowStatus",
dbos/admin_sever.py CHANGED
@@ -28,11 +28,11 @@ class AdminServer:
28
28
  self.server_thread = threading.Thread(target=self.server.serve_forever)
29
29
  self.server_thread.daemon = True
30
30
 
31
- dbos_logger.info("Starting DBOS admin server on port %d", self.port)
31
+ dbos_logger.debug("Starting DBOS admin server on port %d", self.port)
32
32
  self.server_thread.start()
33
33
 
34
34
  def stop(self) -> None:
35
- dbos_logger.info("Stopping DBOS admin server")
35
+ dbos_logger.debug("Stopping DBOS admin server")
36
36
  self.server.shutdown()
37
37
  self.server.server_close()
38
38
  self.server_thread.join()
dbos/cli.py CHANGED
@@ -119,7 +119,7 @@ def copy_template_dir(src_dir: str, dst_dir: str, ctx: dict[str, str]) -> None:
119
119
  shutil.copy(src, dst)
120
120
 
121
121
 
122
- def copy_template(src_dir: str, project_name: str) -> None:
122
+ def copy_template(src_dir: str, project_name: str, config_mode: bool) -> None:
123
123
 
124
124
  dst_dir = path.abspath(".")
125
125
 
@@ -131,10 +131,17 @@ def copy_template(src_dir: str, project_name: str) -> None:
131
131
  "db_name": db_name,
132
132
  }
133
133
 
134
- copy_template_dir(src_dir, dst_dir, ctx)
135
- copy_template_dir(
136
- path.join(src_dir, "__package"), path.join(dst_dir, package_name), ctx
137
- )
134
+ if config_mode:
135
+ copy_dbos_template(
136
+ os.path.join(src_dir, "dbos-config.yaml.dbos"),
137
+ os.path.join(dst_dir, "dbos-config.yaml"),
138
+ ctx,
139
+ )
140
+ else:
141
+ copy_template_dir(src_dir, dst_dir, ctx)
142
+ copy_template_dir(
143
+ path.join(src_dir, "__package"), path.join(dst_dir, package_name), ctx
144
+ )
138
145
 
139
146
 
140
147
  def get_project_name() -> typing.Union[str, None]:
@@ -173,6 +180,10 @@ def init(
173
180
  typing.Optional[str],
174
181
  typer.Option("--template", "-t", help="Specify template to use"),
175
182
  ] = None,
183
+ config: Annotated[
184
+ bool,
185
+ typer.Option("--config", "-c", help="Only add dbos-config.yaml"),
186
+ ] = False,
176
187
  ) -> None:
177
188
  try:
178
189
  if project_name is None:
@@ -199,7 +210,9 @@ def init(
199
210
  if template not in templates:
200
211
  raise Exception(f"template {template} not found in {templates_dir}")
201
212
 
202
- copy_template(path.join(templates_dir, template), project_name)
213
+ copy_template(
214
+ path.join(templates_dir, template), project_name, config_mode=config
215
+ )
203
216
  except Exception as e:
204
217
  print(f"[red]{e}[/red]")
205
218
 
dbos/context.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import os
4
5
  import uuid
5
6
  from contextvars import ContextVar
@@ -39,6 +40,9 @@ class TracedAttributes(TypedDict, total=False):
39
40
  applicationID: Optional[str]
40
41
  applicationVersion: Optional[str]
41
42
  executorID: Optional[str]
43
+ authenticatedUser: Optional[str]
44
+ authenticatedUserRoles: Optional[str]
45
+ authenticatedUserAssumedRole: Optional[str]
42
46
 
43
47
 
44
48
  class DBOSContext:
@@ -159,6 +163,13 @@ class DBOSContext:
159
163
  attributes["operationUUID"] = (
160
164
  self.workflow_id if len(self.workflow_id) > 0 else None
161
165
  )
166
+ attributes["authenticatedUser"] = self.authenticated_user
167
+ attributes["authenticatedUserRoles"] = (
168
+ json.dumps(self.authenticated_roles)
169
+ if self.authenticated_roles is not None
170
+ else ""
171
+ )
172
+ attributes["authenticatedUserAssumedRole"] = self.assumed_role
162
173
  span = dbos_tracer.start_span(
163
174
  attributes, parent=self.spans[-1] if len(self.spans) > 0 else None
164
175
  )
@@ -178,6 +189,11 @@ class DBOSContext:
178
189
  ) -> None:
179
190
  self.authenticated_user = user
180
191
  self.authenticated_roles = roles
192
+ if user is not None and len(self.spans) > 0:
193
+ self.spans[-1].set_attribute("authenticatedUser", user)
194
+ self.spans[-1].set_attribute(
195
+ "authenticatedUserRoles", json.dumps(roles) if roles is not None else ""
196
+ )
181
197
 
182
198
 
183
199
  ##############################################################
@@ -217,13 +233,13 @@ class DBOSContextEnsure:
217
233
  def __init__(self) -> None:
218
234
  self.created_ctx = False
219
235
 
220
- def __enter__(self) -> DBOSContextEnsure:
236
+ def __enter__(self) -> DBOSContext:
221
237
  # Code to create a basic context
222
238
  ctx = get_local_dbos_context()
223
239
  if ctx is None:
224
240
  self.created_ctx = True
225
241
  set_local_dbos_context(DBOSContext())
226
- return self
242
+ return assert_current_dbos_context()
227
243
 
228
244
  def __exit__(
229
245
  self,
dbos/core.py CHANGED
@@ -1,9 +1,20 @@
1
+ import json
1
2
  import sys
2
3
  import time
3
4
  import traceback
4
5
  from concurrent.futures import Future
5
6
  from functools import wraps
6
- from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Tuple, TypeVar, cast
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
18
 
8
19
  from dbos.application_database import ApplicationDatabase, TransactionResultInternal
9
20
 
@@ -134,6 +145,11 @@ def _init_workflow(
134
145
  "executor_id": ctx.executor_id,
135
146
  "request": (utils.serialize(ctx.request) if ctx.request is not None else None),
136
147
  "recovery_attempts": None,
148
+ "authenticated_user": ctx.authenticated_user,
149
+ "authenticated_roles": (
150
+ json.dumps(ctx.authenticated_roles) if ctx.authenticated_roles else None
151
+ ),
152
+ "assumed_role": ctx.assumed_role,
137
153
  }
138
154
 
139
155
  # If we have a class name, the first arg is the instance and do not serialize
dbos/dbos.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import atexit
4
+ import json
4
5
  import os
5
6
  import sys
6
7
  import threading
@@ -19,6 +20,7 @@ from typing import (
19
20
  Tuple,
20
21
  Type,
21
22
  TypeVar,
23
+ cast,
22
24
  )
23
25
 
24
26
  from opentelemetry.trace import Span
@@ -52,10 +54,14 @@ from .tracer import dbos_tracer
52
54
 
53
55
  if TYPE_CHECKING:
54
56
  from fastapi import FastAPI
57
+ from dbos.kafka import KafkaConsumerWorkflow
55
58
  from .request import Request
59
+ from flask import Flask
56
60
 
57
61
  from sqlalchemy.orm import Session
58
62
 
63
+ from dbos.request import Request
64
+
59
65
  if sys.version_info < (3, 10):
60
66
  from typing_extensions import ParamSpec, TypeAlias
61
67
  else:
@@ -205,6 +211,7 @@ class DBOS:
205
211
  *,
206
212
  config: Optional[ConfigFile] = None,
207
213
  fastapi: Optional["FastAPI"] = None,
214
+ flask: Optional["Flask"] = None,
208
215
  ) -> DBOS:
209
216
  global _dbos_global_instance
210
217
  global _dbos_global_registry
@@ -219,7 +226,7 @@ class DBOS:
219
226
  )
220
227
  config = _dbos_global_registry.config
221
228
  _dbos_global_instance = super().__new__(cls)
222
- _dbos_global_instance.__init__(fastapi=fastapi, config=config) # type: ignore
229
+ _dbos_global_instance.__init__(fastapi=fastapi, config=config, flask=flask) # type: ignore
223
230
  else:
224
231
  if (config is not None and _dbos_global_instance.config is not config) or (
225
232
  _dbos_global_instance.fastapi is not fastapi
@@ -243,6 +250,7 @@ class DBOS:
243
250
  *,
244
251
  config: Optional[ConfigFile] = None,
245
252
  fastapi: Optional["FastAPI"] = None,
253
+ flask: Optional["Flask"] = None,
246
254
  ) -> None:
247
255
  if hasattr(self, "_initialized") and self._initialized:
248
256
  return
@@ -264,14 +272,44 @@ class DBOS:
264
272
  self._admin_server: Optional[AdminServer] = None
265
273
  self.stop_events: List[threading.Event] = []
266
274
  self.fastapi: Optional["FastAPI"] = fastapi
275
+ self.flask: Optional["Flask"] = flask
267
276
  self._executor: Optional[ThreadPoolExecutor] = None
277
+
278
+ # If using FastAPI, set up middleware and lifecycle events
268
279
  if self.fastapi is not None:
280
+ from fastapi.requests import Request as FARequest
281
+ from fastapi.responses import JSONResponse
282
+
283
+ async def dbos_error_handler(
284
+ request: FARequest, gexc: Exception
285
+ ) -> JSONResponse:
286
+ exc: DBOSException = cast(DBOSException, gexc)
287
+ status_code = 500
288
+ if exc.status_code is not None:
289
+ status_code = exc.status_code
290
+ return JSONResponse(
291
+ status_code=status_code,
292
+ content={
293
+ "message": str(exc.message),
294
+ "dbos_error_code": str(exc.dbos_error_code),
295
+ "dbos_error": str(exc.__class__.__name__),
296
+ },
297
+ )
298
+
299
+ self.fastapi.add_exception_handler(DBOSException, dbos_error_handler)
300
+
269
301
  from dbos.fastapi import setup_fastapi_middleware
270
302
 
271
303
  setup_fastapi_middleware(self.fastapi)
272
304
  self.fastapi.on_event("startup")(self._launch)
273
305
  self.fastapi.on_event("shutdown")(self._destroy)
274
306
 
307
+ # If using Flask, set up middleware
308
+ if self.flask is not None:
309
+ from dbos.flask import setup_flask_middleware
310
+
311
+ setup_flask_middleware(self.flask)
312
+
275
313
  # Register send_stub as a workflow
276
314
  def send_temp_workflow(
277
315
  destination_id: str, message: Any, topic: Optional[str]
@@ -471,6 +509,20 @@ class DBOS:
471
509
 
472
510
  return scheduled(_get_or_create_dbos_registry(), cron)
473
511
 
512
+ @classmethod
513
+ def kafka_consumer(
514
+ cls, config: dict[str, Any], topics: list[str]
515
+ ) -> Callable[[KafkaConsumerWorkflow], KafkaConsumerWorkflow]:
516
+ """Decorate a function to be used as a Kafka consumer."""
517
+ try:
518
+ from dbos.kafka import kafka_consumer
519
+
520
+ return kafka_consumer(_get_or_create_dbos_registry(), config, topics)
521
+ except ModuleNotFoundError as e:
522
+ raise DBOSException(
523
+ f"{e.name} dependency not found. Please install {e.name} via your package manager."
524
+ ) from e
525
+
474
526
  @classmethod
475
527
  def start_workflow(
476
528
  cls,
@@ -502,9 +554,13 @@ class DBOS:
502
554
  recovery_attempts=stat["recovery_attempts"],
503
555
  class_name=stat["class_name"],
504
556
  config_name=stat["config_name"],
505
- authenticated_user=None,
506
- assumed_role=None,
507
- authenticatedRoles=None,
557
+ authenticated_user=stat["authenticated_user"],
558
+ assumed_role=stat["assumed_role"],
559
+ authenticated_roles=(
560
+ json.loads(stat["authenticated_roles"])
561
+ if stat["authenticated_roles"] is not None
562
+ else None
563
+ ),
508
564
  )
509
565
 
510
566
  @classmethod
@@ -663,6 +719,33 @@ class DBOS:
663
719
  ctx = assert_current_dbos_context()
664
720
  return ctx.request
665
721
 
722
+ @classproperty
723
+ def authenticated_user(cls) -> Optional[str]:
724
+ """Return the current authenticated user, if any, associated with the current context."""
725
+ ctx = assert_current_dbos_context()
726
+ return ctx.authenticated_user
727
+
728
+ @classproperty
729
+ def authenticated_roles(cls) -> Optional[List[str]]:
730
+ """Return the roles granted to the current authenticated user, if any, associated with the current context."""
731
+ ctx = assert_current_dbos_context()
732
+ return ctx.authenticated_roles
733
+
734
+ @classproperty
735
+ def assumed_role(cls) -> Optional[str]:
736
+ """Return the role currently assumed by the authenticated user, if any, associated with the current context."""
737
+ ctx = assert_current_dbos_context()
738
+ return ctx.assumed_role
739
+
740
+ @classmethod
741
+ def set_authentication(
742
+ cls, authenticated_user: Optional[str], authenticated_roles: Optional[List[str]]
743
+ ) -> None:
744
+ """Set the current authenticated user and granted roles into the current context."""
745
+ ctx = assert_current_dbos_context()
746
+ ctx.authenticated_user = authenticated_user
747
+ ctx.authenticated_roles = authenticated_roles
748
+
666
749
 
667
750
  @dataclass
668
751
  class WorkflowStatus:
@@ -679,7 +762,7 @@ class WorkflowStatus:
679
762
  config_name(str): For instance member functions, the name of the class instance for the execution
680
763
  authenticated_user(str): The user who invoked the workflow
681
764
  assumed_role(str): The access role used by the user to allow access to the workflow function
682
- authenticatedRoles(List[str]): List of all access roles available to the authenticated user
765
+ authenticated_roles(List[str]): List of all access roles available to the authenticated user
683
766
  recovery_attempts(int): Number of times the workflow has been restarted (usually by recovery)
684
767
 
685
768
  """
@@ -691,7 +774,7 @@ class WorkflowStatus:
691
774
  config_name: Optional[str]
692
775
  authenticated_user: Optional[str]
693
776
  assumed_role: Optional[str]
694
- authenticatedRoles: Optional[List[str]]
777
+ authenticated_roles: Optional[List[str]]
695
778
  recovery_attempts: Optional[int]
696
779
 
697
780
 
@@ -740,13 +823,9 @@ class DBOSConfiguredInstance:
740
823
 
741
824
  """
742
825
 
743
- def __init__(self, config_name: str, dbos: Optional[DBOS] = None) -> None:
826
+ def __init__(self, config_name: str) -> None:
744
827
  self.config_name = config_name
745
- if dbos is not None:
746
- assert isinstance(dbos, DBOS)
747
- dbos._registry.register_instance(self)
748
- else:
749
- DBOS.register_instance(self)
828
+ DBOS.register_instance(self)
750
829
 
751
830
 
752
831
  # Apps that import DBOS probably don't exit. If they do, let's see if
dbos/dbos_config.py CHANGED
@@ -69,6 +69,7 @@ class ConfigFile(TypedDict, total=False):
69
69
  database: DatabaseConfig
70
70
  telemetry: Optional[TelemetryConfig]
71
71
  env: Dict[str, str]
72
+ application: Dict[str, Any]
72
73
 
73
74
 
74
75
  def substitute_env_vars(content: str) -> str:
dbos/error.py CHANGED
@@ -17,6 +17,7 @@ class DBOSException(Exception):
17
17
  def __init__(self, message: str, dbos_error_code: Optional[int] = None):
18
18
  self.message = message
19
19
  self.dbos_error_code = dbos_error_code
20
+ self.status_code: Optional[int] = None
20
21
  super().__init__(self.message)
21
22
 
22
23
  def __str__(self) -> str:
@@ -104,6 +105,7 @@ class DBOSNotAuthorizedError(DBOSException):
104
105
  msg,
105
106
  dbos_error_code=DBOSErrorCode.NotAuthorized.value,
106
107
  )
108
+ self.status_code = 403
107
109
 
108
110
 
109
111
  class DBOSMaxStepRetriesExceeded(DBOSException):
dbos/fastapi.py CHANGED
@@ -11,9 +11,7 @@ from .context import (
11
11
  TracedAttributes,
12
12
  assert_current_dbos_context,
13
13
  )
14
- from .request import Address, Request
15
-
16
- request_id_header = "x-request-id"
14
+ from .request import Address, Request, request_id_header
17
15
 
18
16
 
19
17
  def get_or_generate_request_id(request: FastAPIRequest) -> str:
dbos/flask.py ADDED
@@ -0,0 +1,79 @@
1
+ import uuid
2
+ from typing import Any
3
+ from urllib.parse import urlparse
4
+
5
+ from flask import Flask, request
6
+ from werkzeug.wrappers import Request as WRequest
7
+
8
+ from dbos.context import (
9
+ EnterDBOSHandler,
10
+ OperationType,
11
+ SetWorkflowID,
12
+ TracedAttributes,
13
+ assert_current_dbos_context,
14
+ )
15
+
16
+ from .request import Address, Request, request_id_header
17
+
18
+
19
+ class FlaskMiddleware:
20
+ def __init__(self, app: Any) -> None:
21
+ self.app = app
22
+
23
+ def __call__(self, environ: Any, start_response: Any) -> Any:
24
+ request = WRequest(environ)
25
+ attributes: TracedAttributes = {
26
+ "name": urlparse(request.url).path,
27
+ "requestID": get_or_generate_request_id(request),
28
+ "requestIP": (
29
+ request.remote_addr if request.remote_addr is not None else None
30
+ ),
31
+ "requestURL": request.url,
32
+ "requestMethod": request.method,
33
+ "operationType": OperationType.HANDLER.value,
34
+ }
35
+ with EnterDBOSHandler(attributes):
36
+ ctx = assert_current_dbos_context()
37
+ ctx.request = make_request(request)
38
+ workflow_id = request.headers.get("dbos-idempotency-key", "")
39
+ with SetWorkflowID(workflow_id):
40
+ response = self.app(environ, start_response)
41
+ return response
42
+
43
+
44
+ def get_or_generate_request_id(request: WRequest) -> str:
45
+ request_id = request.headers.get(request_id_header, None)
46
+ if request_id is not None:
47
+ return request_id
48
+ else:
49
+ return str(uuid.uuid4())
50
+
51
+
52
+ def make_request(request: WRequest) -> Request:
53
+ parsed_url = urlparse(request.url)
54
+ base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
55
+
56
+ client = None
57
+ if request.remote_addr:
58
+ hostname = request.remote_addr
59
+ port = request.environ.get("REMOTE_PORT")
60
+ if port:
61
+ client = Address(hostname=hostname, port=int(port))
62
+ else:
63
+ # If port is not available, use 0 as a placeholder
64
+ client = Address(hostname=hostname, port=0)
65
+
66
+ return Request(
67
+ headers=dict(request.headers),
68
+ path_params={},
69
+ query_params=dict(request.args),
70
+ url=request.url,
71
+ base_url=base_url,
72
+ client=client,
73
+ cookies=dict(request.cookies),
74
+ method=request.method,
75
+ )
76
+
77
+
78
+ def setup_flask_middleware(app: Flask) -> None:
79
+ app.wsgi_app = FlaskMiddleware(app.wsgi_app) # type: ignore
dbos/kafka.py ADDED
@@ -0,0 +1,94 @@
1
+ import threading
2
+ import traceback
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Callable, Generator, NoReturn, Optional, Union
5
+
6
+ from confluent_kafka import Consumer, KafkaError, KafkaException
7
+ from confluent_kafka import Message as CTypeMessage
8
+
9
+ if TYPE_CHECKING:
10
+ from dbos.dbos import _DBOSRegistry
11
+
12
+ from .context import SetWorkflowID
13
+ from .kafka_message import KafkaMessage
14
+ from .logger import dbos_logger
15
+
16
+ KafkaConsumerWorkflow = Callable[[KafkaMessage], None]
17
+
18
+
19
+ def _kafka_consumer_loop(
20
+ func: KafkaConsumerWorkflow,
21
+ config: dict[str, Any],
22
+ topics: list[str],
23
+ stop_event: threading.Event,
24
+ ) -> None:
25
+
26
+ def on_error(err: KafkaError) -> NoReturn:
27
+ raise KafkaException(err)
28
+
29
+ config["error_cb"] = on_error
30
+ if "auto.offset.reset" not in config:
31
+ config["auto.offset.reset"] = "earliest"
32
+
33
+ consumer = Consumer(config)
34
+ try:
35
+ consumer.subscribe(topics)
36
+ while not stop_event.is_set():
37
+ cmsg = consumer.poll(1.0)
38
+
39
+ if stop_event.is_set():
40
+ return
41
+
42
+ if cmsg is None:
43
+ continue
44
+
45
+ err = cmsg.error()
46
+ if err is not None:
47
+ dbos_logger.error(
48
+ f"Kafka error {err.code()} ({err.name()}): {err.str()}"
49
+ )
50
+ # fatal errors require an updated consumer instance
51
+ if err.code() == KafkaError._FATAL or err.fatal():
52
+ original_consumer = consumer
53
+ try:
54
+ consumer = Consumer(config)
55
+ consumer.subscribe(topics)
56
+ finally:
57
+ original_consumer.close()
58
+ else:
59
+ msg = KafkaMessage(
60
+ headers=cmsg.headers(),
61
+ key=cmsg.key(),
62
+ latency=cmsg.latency(),
63
+ leader_epoch=cmsg.leader_epoch(),
64
+ offset=cmsg.offset(),
65
+ partition=cmsg.partition(),
66
+ timestamp=cmsg.timestamp(),
67
+ topic=cmsg.topic(),
68
+ value=cmsg.value(),
69
+ )
70
+ with SetWorkflowID(
71
+ f"kafka-unique-id-{msg.topic}-{msg.partition}-{msg.offset}"
72
+ ):
73
+ try:
74
+ func(msg)
75
+ except Exception as e:
76
+ dbos_logger.error(
77
+ f"Exception encountered in Kafka consumer: {traceback.format_exc()}"
78
+ )
79
+
80
+ finally:
81
+ consumer.close()
82
+
83
+
84
+ def kafka_consumer(
85
+ dbosreg: "_DBOSRegistry", config: dict[str, Any], topics: list[str]
86
+ ) -> Callable[[KafkaConsumerWorkflow], KafkaConsumerWorkflow]:
87
+ def decorator(func: KafkaConsumerWorkflow) -> KafkaConsumerWorkflow:
88
+ stop_event = threading.Event()
89
+ dbosreg.register_poller(
90
+ stop_event, _kafka_consumer_loop, func, config, topics, stop_event
91
+ )
92
+ return func
93
+
94
+ return decorator
dbos/kafka_message.py ADDED
@@ -0,0 +1,15 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Union
3
+
4
+
5
+ @dataclass
6
+ class KafkaMessage:
7
+ headers: Optional[list[tuple[str, Union[str, bytes]]]]
8
+ key: Optional[Union[str, bytes]]
9
+ latency: Optional[float]
10
+ leader_epoch: Optional[int]
11
+ offset: Optional[int]
12
+ partition: Optional[int]
13
+ timestamp: tuple[int, int]
14
+ topic: Optional[str]
15
+ value: Optional[Union[str, bytes]]
dbos/request.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Any, Mapping, NamedTuple, Optional
3
3
 
4
+ request_id_header = "x-request-id"
5
+
4
6
 
5
7
  class Address(NamedTuple):
6
8
  hostname: str
dbos/roles.py CHANGED
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, TypeVar,
4
4
  from dbos.error import DBOSNotAuthorizedError
5
5
 
6
6
  if TYPE_CHECKING:
7
- from dbos.dbos import DBOS, _DBOSRegistry
7
+ from dbos.dbos import _DBOSRegistry
8
8
 
9
9
  from dbos.context import DBOSAssumeRole, get_local_dbos_context
10
10
  from dbos.registrations import (
dbos/system_database.py CHANGED
@@ -4,18 +4,7 @@ import select
4
4
  import threading
5
5
  import time
6
6
  from enum import Enum
7
- from typing import (
8
- TYPE_CHECKING,
9
- Any,
10
- Dict,
11
- List,
12
- Literal,
13
- Optional,
14
- Sequence,
15
- Set,
16
- TypedDict,
17
- cast,
18
- )
7
+ from typing import Any, Dict, List, Literal, Optional, Sequence, Set, TypedDict, cast
19
8
 
20
9
  import psycopg2
21
10
  import sqlalchemy as sa
@@ -69,6 +58,9 @@ class WorkflowStatusInternal(TypedDict):
69
58
  app_id: Optional[str]
70
59
  request: Optional[str] # JSON (jsonpickle)
71
60
  recovery_attempts: Optional[int]
61
+ authenticated_user: Optional[str]
62
+ assumed_role: Optional[str]
63
+ authenticated_roles: Optional[str] # JSON list of roles.
72
64
 
73
65
 
74
66
  class RecordedResult(TypedDict):
@@ -136,7 +128,7 @@ class WorkflowInformation(TypedDict, total=False):
136
128
  authenticated_user: str # The user who ran the workflow. Empty string if not set.
137
129
  assumed_role: str
138
130
  # The role used to run this workflow. Empty string if authorization is not required.
139
- authenticatedRoles: List[str]
131
+ authenticated_roles: List[str]
140
132
  # All roles the authenticated user has, if any.
141
133
  input: Optional[WorkflowInputs]
142
134
  output: Optional[str]
@@ -230,7 +222,7 @@ class SystemDatabase:
230
222
  def wait_for_buffer_flush(self) -> None:
231
223
  # Wait until the buffers are flushed.
232
224
  while self._is_flushing_status_buffer or not self._is_buffers_empty:
233
- dbos_logger.info("Waiting for system buffers to be exported")
225
+ dbos_logger.debug("Waiting for system buffers to be exported")
234
226
  time.sleep(1)
235
227
 
236
228
  def update_workflow_status(
@@ -252,6 +244,9 @@ class SystemDatabase:
252
244
  application_version=status["app_version"],
253
245
  application_id=status["app_id"],
254
246
  request=status["request"],
247
+ authenticated_user=status["authenticated_user"],
248
+ authenticated_roles=status["authenticated_roles"],
249
+ assumed_role=status["assumed_role"],
255
250
  )
256
251
  if replace:
257
252
  cmd = cmd.on_conflict_do_update(
@@ -322,6 +317,9 @@ class SystemDatabase:
322
317
  SystemSchema.workflow_status.c.recovery_attempts,
323
318
  SystemSchema.workflow_status.c.config_name,
324
319
  SystemSchema.workflow_status.c.class_name,
320
+ SystemSchema.workflow_status.c.authenticated_user,
321
+ SystemSchema.workflow_status.c.authenticated_roles,
322
+ SystemSchema.workflow_status.c.assumed_role,
325
323
  ).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
326
324
  ).fetchone()
327
325
  if row is None:
@@ -339,6 +337,9 @@ class SystemDatabase:
339
337
  "executor_id": None,
340
338
  "request": row[2],
341
339
  "recovery_attempts": row[3],
340
+ "authenticated_user": row[6],
341
+ "authenticated_roles": row[7],
342
+ "assumed_role": row[8],
342
343
  }
343
344
  return status
344
345
 
@@ -375,6 +376,9 @@ class SystemDatabase:
375
376
  SystemSchema.workflow_status.c.error,
376
377
  SystemSchema.workflow_status.c.config_name,
377
378
  SystemSchema.workflow_status.c.class_name,
379
+ SystemSchema.workflow_status.c.authenticated_user,
380
+ SystemSchema.workflow_status.c.authenticated_roles,
381
+ SystemSchema.workflow_status.c.assumed_role,
378
382
  ).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
379
383
  ).fetchone()
380
384
  if row is None:
@@ -392,6 +396,9 @@ class SystemDatabase:
392
396
  "executor_id": None,
393
397
  "request": row[2],
394
398
  "recovery_attempts": None,
399
+ "authenticated_user": row[7],
400
+ "authenticated_roles": row[8],
401
+ "assumed_role": row[9],
395
402
  }
396
403
  return status
397
404
 
@@ -2,13 +2,12 @@
2
2
 
3
3
  # This is a sample app built with DBOS and FastAPI.
4
4
  # It displays greetings to visitors and keeps track of how
5
- # many times each visitor has been greeted.
5
+ # many times visitors have been greeted.
6
6
 
7
7
  # First, let's do imports, create a FastAPI app, and initialize DBOS.
8
8
 
9
9
  from fastapi import FastAPI
10
10
  from fastapi.responses import HTMLResponse
11
- from sqlalchemy.dialects.postgresql import insert
12
11
 
13
12
  from dbos import DBOS
14
13
 
@@ -19,33 +18,26 @@ DBOS(fastapi=app)
19
18
 
20
19
  # Next, let's write a function that greets visitors.
21
20
  # To make it more interesting, we'll keep track of how
22
- # many times each visitor has been greeted and store
21
+ # many times visitors have been greeted and store
23
22
  # the count in the database.
24
23
 
25
- # We annotate this function with @DBOS.transaction to
26
- # access to an automatically-configured database client,
27
- # (DBOS.sql_sesion) then implement the database operations
28
- # using SQLAlchemy. We serve this function from a FastAPI endpoint.
24
+ # We implement the database operations using SQLAlchemy
25
+ # and serve the function from a FastAPI endpoint.
26
+ # We annotate it with @DBOS.transaction() to access
27
+ # an automatically-configured database client.
29
28
 
30
29
 
31
30
  @app.get("/greeting/{name}")
32
31
  @DBOS.transaction()
33
32
  def example_transaction(name: str) -> str:
34
- query = (
35
- insert(dbos_hello)
36
- .values(name="dbos", greet_count=1)
37
- .on_conflict_do_update(
38
- index_elements=["name"], set_={"greet_count": dbos_hello.c.greet_count + 1}
39
- )
40
- .returning(dbos_hello.c.greet_count)
41
- )
33
+ query = dbos_hello.insert().values(name=name).returning(dbos_hello.c.greet_count)
42
34
  greet_count = DBOS.sql_session.execute(query).scalar_one()
43
35
  greeting = f"Greetings, {name}! You have been greeted {greet_count} times."
44
36
  DBOS.logger.info(greeting)
45
37
  return greeting
46
38
 
47
39
 
48
- # Finally, let's use FastAPI to serve a simple HTML readme
40
+ # Finally, let's use FastAPI to serve an HTML + CSS readme
49
41
  # from the root path.
50
42
 
51
43
 
@@ -74,13 +66,14 @@ def readme() -> HTMLResponse:
74
66
  return HTMLResponse(readme)
75
67
 
76
68
 
77
- # To run this app locally:
78
- # - Make sure you have a Postgres database to connect
79
- # - "dbos migrate" to set up your database tables
80
- # - "dbos start" to start the app
81
- # - Visit localhost:8000 to see your app!
82
-
83
69
  # To deploy this app to DBOS Cloud:
84
70
  # - "npm i -g @dbos-inc/dbos-cloud@latest" to install the Cloud CLI (requires Node)
85
71
  # - "dbos-cloud app deploy" to deploy your app
86
72
  # - Deploy outputs a URL--visit it to see your app!
73
+
74
+
75
+ # To run this app locally:
76
+ # - Make sure you have a Postgres database to connect to
77
+ # - "dbos migrate" to set up your database tables
78
+ # - "dbos start" to start the app
79
+ # - Visit localhost:8000 to see your app!
@@ -5,6 +5,6 @@ metadata = MetaData()
5
5
  dbos_hello = Table(
6
6
  "dbos_hello",
7
7
  metadata,
8
- Column("name", String, primary_key=True),
9
- Column("greet_count", Integer, default=0),
8
+ Column("greet_count", Integer, primary_key=True, autoincrement=True),
9
+ Column("name", String, nullable=False),
10
10
  )
@@ -22,9 +22,9 @@ def upgrade() -> None:
22
22
  # ### commands auto generated by Alembic - please adjust! ###
23
23
  op.create_table(
24
24
  "dbos_hello",
25
+ sa.Column("greet_count", sa.Integer(), autoincrement=True, nullable=False),
25
26
  sa.Column("name", sa.String(), nullable=False),
26
- sa.Column("greet_count", sa.Integer(), nullable=True),
27
- sa.PrimaryKeyConstraint("name"),
27
+ sa.PrimaryKeyConstraint("greet_count"),
28
28
  )
29
29
  # ### end Alembic commands ###
30
30
 
dbos/tracer.py CHANGED
@@ -19,6 +19,7 @@ class DBOSTracer:
19
19
  self.app_id = os.environ.get("DBOS__APPID", None)
20
20
  self.app_version = os.environ.get("DBOS__APPVERSION", None)
21
21
  self.executor_id = os.environ.get("DBOS__VMID", "local")
22
+ self.provider: Optional[TracerProvider] = None
22
23
 
23
24
  def config(self, config: ConfigFile) -> None:
24
25
  if not isinstance(trace.get_tracer_provider(), TracerProvider):
@@ -36,10 +37,17 @@ class DBOSTracer:
36
37
  provider.add_span_processor(processor)
37
38
  trace.set_tracer_provider(provider)
38
39
 
40
+ def set_provider(self, provider: Optional[TracerProvider]) -> None:
41
+ self.provider = provider
42
+
39
43
  def start_span(
40
44
  self, attributes: "TracedAttributes", parent: Optional[Span] = None
41
45
  ) -> Span:
42
- tracer = trace.get_tracer("dbos-tracer")
46
+ tracer = (
47
+ self.provider.get_tracer("dbos-tracer")
48
+ if self.provider is not None
49
+ else trace.get_tracer("dbos-tracer")
50
+ )
43
51
  context = trace.set_span_in_context(parent) if parent else None
44
52
  span: Span = tracer.start_span(name=attributes["name"], context=context)
45
53
  attributes["applicationID"] = self.app_id
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.1
2
+ Name: dbos
3
+ Version: 0.6.0
4
+ Summary: Ultra-lightweight durable execution in Python
5
+ Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: pyyaml>=6.0.2
9
+ Requires-Dist: jsonschema>=4.23.0
10
+ Requires-Dist: alembic>=1.13.2
11
+ Requires-Dist: psycopg2-binary>=2.9.9
12
+ Requires-Dist: typing-extensions>=4.12.2; python_version < "3.10"
13
+ Requires-Dist: typer>=0.12.3
14
+ Requires-Dist: jsonpickle>=3.2.2
15
+ Requires-Dist: opentelemetry-api>=1.26.0
16
+ Requires-Dist: opentelemetry-sdk>=1.26.0
17
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.26.0
18
+ Requires-Dist: python-dateutil>=2.9.0.post0
19
+ Requires-Dist: fastapi[standard]>=0.112.1
20
+ Requires-Dist: psutil>=6.0.0
21
+ Requires-Dist: tomlkit>=0.13.2
22
+ Description-Content-Type: text/markdown
23
+
24
+
25
+ <div align="center">
26
+
27
+ # DBOS Transact: Ultra-Lightweight Durable Execution
28
+
29
+ #### [Documentation](https://docs.dbos.dev/) &nbsp;&nbsp;•&nbsp;&nbsp; [Examples](https://docs.dbos.dev/examples) &nbsp;&nbsp;•&nbsp;&nbsp; [Github](https://github.com/dbos-inc) &nbsp;&nbsp;•&nbsp;&nbsp; [Discord](https://discord.com/invite/jsmC6pXGgX)
30
+ </div>
31
+
32
+ ---
33
+
34
+ DBOS Transact is a Python library providing **ultra-lightweight durable execution**.
35
+ For example:
36
+
37
+ ```python
38
+ @DBOS.step()
39
+ def step_one():
40
+ ...
41
+
42
+ @DBOS.step()
43
+ def step_two():
44
+ ...
45
+
46
+ @DBOS.workflow()
47
+ def workflow()
48
+ step_one()
49
+ step_two()
50
+ ```
51
+
52
+ Durable execution means your program is **resilient to any failure**.
53
+ If it is ever interrupted or crashes, all your workflows will automatically resume from the last completed step.
54
+ If you want to see durable execution in action, check out [this demo app](https://demo-widget-store.cloud.dbos.dev/) (source code [here](https://github.com/dbos-inc/dbos-demo-apps/tree/main/python/widget-store)).
55
+ No matter how many times you try to crash it, it always resumes from exactly where it left off!
56
+
57
+ Under the hood, DBOS Transact works by storing your program's execution state (which workflows are currently executing and which steps they've completed) in a Postgres database.
58
+ So all you need to use it is a Postgres database to connect to&mdash;there's no need for a "workflow server."
59
+ This approach is also incredibly fast, for example [25x faster than AWS Step Functions](https://www.dbos.dev/blog/dbos-vs-aws-step-functions-benchmark).
60
+
61
+ Some more cool features include:
62
+
63
+ - Scheduled jobs&mdash;run your workflows exactly-once per time interval.
64
+ - Exactly-once event processing&mdash;use workflows to process incoming events (for example, from a Kafka topic) exactly-once.
65
+ - Observability&mdash;all workflows automatically emit [OpenTelemetry](https://opentelemetry.io/) traces.
66
+
67
+ ## Getting Started
68
+
69
+ Install and configure with:
70
+
71
+ ```shell
72
+ pip install dbos
73
+ dbos init --config
74
+ ```
75
+
76
+ Then, try it out with this simple program (requires Postgres):
77
+
78
+ ```python
79
+ from fastapi import FastAPI
80
+ from dbos import DBOS
81
+
82
+ app = FastAPI()
83
+ DBOS(fastapi=app)
84
+
85
+ @DBOS.step()
86
+ def step_one():
87
+ print("Step one completed!")
88
+
89
+ @DBOS.step()
90
+ def step_two():
91
+ print("Step two completed!")
92
+
93
+ @DBOS.workflow()
94
+ def workflow():
95
+ step_one()
96
+ for _ in range(5):
97
+ print("Press Control + \ to stop the app...")
98
+ DBOS.sleep(1)
99
+ step_two()
100
+
101
+ @app.get("/")
102
+ def endpoint():
103
+ workflow()
104
+ ```
105
+
106
+ Save the program into `main.py`, edit `dbos-config.yaml` to configure your Postgres connection settings, and start it with `fastapi run`.
107
+ Visit `localhost:8000` in your browser to start the workflow.
108
+ When prompted, press `Control + \` to force quit your application.
109
+ It should crash midway through the workflow, having completed step one but not step two.
110
+ Then, restart your app with `fastapi run`.
111
+ It should resume the workflow from where it left off, completing step two without re-executing step one.
112
+
113
+ To learn how to build more complex workflows, see our [programming guide](https://docs.dbos.dev/python/programming-guide) or [examples](https://docs.dbos.dev/examples).
114
+
115
+ ## Documentation
116
+
117
+ [https://docs.dbos.dev](https://docs.dbos.dev)
118
+
119
+ ## Examples
120
+
121
+
122
+ - [**AI-Powered Slackbot**](https://docs.dbos.dev/python/examples/rag-slackbot) &mdash; A Slackbot that answers questions about previous Slack conversations, using DBOS to durably orchestrate its RAG pipeline.
123
+ - [**Widget Store**](https://docs.dbos.dev/python/examples/widget-store) &mdash; An online storefront that uses DBOS durable workflows to be resilient to any failure.
124
+ - [**Earthquake Tracker**](https://docs.dbos.dev/python/examples/earthquake-tracker) &mdash; A real-time earthquake dashboard that uses DBOS to stream data from the USGS into Postgres, then visualizes it with Streamlit.
125
+
126
+ More examples [here](https://docs.dbos.dev/examples)!
127
+
128
+ ## Community
129
+
130
+ If you're interested in building with us, please star our repository and join our community on [Discord](https://discord.gg/fMwQjeW5zg)!
131
+ If you see a bug or have a feature request, don't hesitate to open an issue here on GitHub.
132
+ If you're interested in contributing, check out our [contributions guide](./CONTRIBUTING.md).
@@ -1,19 +1,22 @@
1
- dbos-0.5.0a11.dist-info/METADATA,sha256=zWq1cPyLlyR1-qJrUhSwbURRvDUOI9LfxIg3M0kVFC0,5421
2
- dbos-0.5.0a11.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- dbos-0.5.0a11.dist-info/entry_points.txt,sha256=3PmOPbM4FYxEmggRRdJw0oAsiBzKR8U0yx7bmwUmMOM,39
4
- dbos-0.5.0a11.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
- dbos/__init__.py,sha256=X1LdP36NomDtvPfFwoMNtgXf81TO05jj7vltsp79UUw,787
6
- dbos/admin_sever.py,sha256=KtzH6aKyskCm4h3yulpy9jb5PIqRlYI2sjctw5mvaKY,3395
1
+ dbos-0.6.0.dist-info/METADATA,sha256=KmnHNM81muzumN3rCi5ehIHATQepqFl0DyWlKrlV5xY,4998
2
+ dbos-0.6.0.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
+ dbos-0.6.0.dist-info/entry_points.txt,sha256=3PmOPbM4FYxEmggRRdJw0oAsiBzKR8U0yx7bmwUmMOM,39
4
+ dbos-0.6.0.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
+ dbos/__init__.py,sha256=heuB3bqRXlVdfea9sKHBIVKSqFP6UuwhQecQfV4hyas,642
6
+ dbos/admin_sever.py,sha256=Qg5T3YRrbPW05PR_99yAaxgo1ugQrAp_uTeTqSfjm_k,3397
7
7
  dbos/application_database.py,sha256=1K3kE96BgGi_QWOd2heXluyNTwFAwlUVuAR6JKKUqf0,5659
8
- dbos/cli.py,sha256=QnbGtZ8S963q3iyFvXNBcL4DB35r4SFMarlb5DRqN6M,7915
9
- dbos/context.py,sha256=qAVj_pAIV4YBOAbI0WCv-Roq7aNwPzAoj3CeQaVqlrU,15666
10
- dbos/core.py,sha256=HfKnPpIaQqIBAHzP2hD67aSIchTHp87NgD21CcujKkE,28300
8
+ dbos/cli.py,sha256=YARlQiWHUwFni-fEOr0k5P_-pqPS4xkywj_B0oTMXn0,8318
9
+ dbos/context.py,sha256=NVMGyvAa2RIiBVspvDz-8MBk_BQyGyYdPdorgO-GSng,16407
10
+ dbos/core.py,sha256=jeqO8DABPAUrFlJXOfRfFDSnA8BGwiPnMa1JNbGuYLs,28584
11
11
  dbos/dbos-config.schema.json,sha256=azpfmoDZg7WfSy3kvIsk9iEiKB_-VZt03VEOoXJAkqE,5331
12
- dbos/dbos.py,sha256=bDp-m25V_CkaR4hsr4VmCFCZbDQ4V99Um9bDUxahpag,26561
13
- dbos/dbos_config.py,sha256=EkO0c0xaIM7_vAAqqnvNNEAKG5fOJbmmalqnZvaKYZA,5312
12
+ dbos/dbos.py,sha256=wzL51K7bY3J8grHXi0k0F0N09vFVBYWKwI0DGxyN4MY,29730
13
+ dbos/dbos_config.py,sha256=ih_TD_1zTKhPKxk8TPdEIp3ihu82R06SGKg-s4rHxws,5344
14
14
  dbos/decorators.py,sha256=lbPefsLK6Cya4cb7TrOcLglOpGT3pc6qjZdsQKlfZLg,629
15
- dbos/error.py,sha256=nBdLC4hxGO_K9V26YbDGOo7xi1CKuN4PsE_cBv7K8Cc,3798
16
- dbos/fastapi.py,sha256=LkARLITiN_NuQh4g2QL7sfK0oG1GvJjj2tvU7WWO8f8,1898
15
+ dbos/error.py,sha256=DDhB0VHmoZE_CP51ICdFMZSL2gmVS3Dm0aPNWncci94,3876
16
+ dbos/fastapi.py,sha256=s7LnwwYVpJm_QZZwBW5um8NV2Q2Qx85uVZqGcKlSZAo,1881
17
+ dbos/flask.py,sha256=azr4geMEGuuTBCyxIZmgDmmP-6s_pTIF-lGyp9Q4IB8,2430
18
+ dbos/kafka.py,sha256=FtngQHBu2TKfyDF7GFsKJAawFQJiOFxgKEUlNNxrdrw,3055
19
+ dbos/kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
17
20
  dbos/logger.py,sha256=D-aFSZUCHBP34J1IZ5YNkTrJW-rDiH3py_v9jLU4Yrk,3565
18
21
  dbos/migrations/env.py,sha256=38SIGVbmn_VV2x2u1aHLcPOoWgZ84eCymf3g_NljmbU,1626
19
22
  dbos/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
@@ -22,25 +25,25 @@ dbos/migrations/versions/a3b18ad34abe_added_triggers.py,sha256=Rv0ZsZYZ_WdgGEULY
22
25
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
23
26
  dbos/recovery.py,sha256=xfwQFWNuD8DXg5HD5_-3tG7Neo9j-x1lrqiwtn5FSh8,2015
24
27
  dbos/registrations.py,sha256=gMI-u05tv5bpvyddQGtoUgCsqARx51aOY7p0JXPafQo,6539
25
- dbos/request.py,sha256=FTjmgzqWwKKTSf6PKPdlQ4Ssp39PATQukYmMOW_xP7k,892
26
- dbos/roles.py,sha256=9u0z4CWmXPeqIKzQWEzaOKIlzOuaagBtMiB-swqjX_U,2291
28
+ dbos/request.py,sha256=-FIwtknayvRl6OjvqO4V2GySVzSdP1Ft3cc9ZBS-PLY,928
29
+ dbos/roles.py,sha256=7Lh7uwUq1dpa6TXCOHre4mPTd5qmXzK_QPkvYR52DXg,2285
27
30
  dbos/scheduler/croniter.py,sha256=hbhgfsHBqclUS8VeLnJ9PSE9Z54z6mi4nnrr1aUXn0k,47561
28
31
  dbos/scheduler/scheduler.py,sha256=uO4_9jmWW2rLv1ODL3lc1cE_37ZaVTgnvmFx_FAlN50,1472
29
32
  dbos/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
33
  dbos/schemas/application_database.py,sha256=q_Wr2XbiZNBYFkOtu7uKavo1T_cSOBblxKGHThYGGsY,962
31
34
  dbos/schemas/system_database.py,sha256=5V3vqnEzry0Hn7ZbVS9Gs_dJKia8uX8p7mGC82Ru8rk,4303
32
- dbos/system_database.py,sha256=84c53iAel113SRb7DcgFJ8XQNWBhD4VrCRCb0s5Oe8Y,39635
35
+ dbos/system_database.py,sha256=BZ8yE79FH9NZhEt8AAYNXy0-wMcKLBAq8VPdZbfidR4,40564
33
36
  dbos/templates/hello/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
34
37
  dbos/templates/hello/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- dbos/templates/hello/__package/main.py,sha256=LFN48qBWt-xsf21Cg1llf5Grr0e7Il_7lcyi60sK4ec,2984
36
- dbos/templates/hello/__package/schema.py,sha256=XOSeq_vFG0vN1LxWPob-L9K65jq9OMCz2qOmvw5CKN8,235
38
+ dbos/templates/hello/__package/main.py,sha256=eI0SS9Nwj-fldtiuSzIlIG6dC91GXXwdRsoHxv6S_WI,2719
39
+ dbos/templates/hello/__package/schema.py,sha256=7Z27JGC8yy7Z44cbVXIREYxtUhU4JVkLCp5Q7UahVQ0,260
37
40
  dbos/templates/hello/alembic.ini,sha256=VKBn4Gy8mMuCdY7Hip1jmo3wEUJ1VG1aW7EqY0_n-as,3695
38
41
  dbos/templates/hello/dbos-config.yaml.dbos,sha256=8wxCf_MIEFNWqMXj0nAHUwg1U3YaKz4xcUN6g51WkDE,603
39
42
  dbos/templates/hello/migrations/env.py.dbos,sha256=CsiFOea3ZIsahqkfYtioha0ewmlGR78Ng0wOB2t5LQg,2208
40
43
  dbos/templates/hello/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
41
- dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py,sha256=rcubrMdw-NUKnrY0Qsftgz4is1OEqWXf6Skcv4PVdME,914
44
+ dbos/templates/hello/migrations/versions/2024_07_31_180642_init.py,sha256=U5thFWGqNN4QLrNXT7wUUqftIFDNE5eSdqD8JNW1mec,942
42
45
  dbos/templates/hello/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
43
- dbos/tracer.py,sha256=RPW9oxmX9tSc0Yq7O-FAhpQWBg1QT7Ni1Q06uwhtNDk,2237
46
+ dbos/tracer.py,sha256=GaXDhdKKF_IQp5SAMipGXiDVwteRKjNbrXyYCH1mor0,2520
44
47
  dbos/utils.py,sha256=hWj9iWDrby2cVEhb0pG-IdnrxLqP64NhkaWUXiLc8bA,402
45
48
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
46
- dbos-0.5.0a11.dist-info/RECORD,,
49
+ dbos-0.6.0.dist-info/RECORD,,
@@ -1,78 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: dbos
3
- Version: 0.5.0a11
4
- Summary: A Python framework for backends that scale
5
- Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
- License: MIT
7
- Requires-Python: >=3.9
8
- Requires-Dist: pyyaml>=6.0.2
9
- Requires-Dist: jsonschema>=4.23.0
10
- Requires-Dist: alembic>=1.13.2
11
- Requires-Dist: psycopg2-binary>=2.9.9
12
- Requires-Dist: typing-extensions>=4.12.2; python_version < "3.10"
13
- Requires-Dist: typer>=0.12.3
14
- Requires-Dist: jsonpickle>=3.2.2
15
- Requires-Dist: opentelemetry-api>=1.26.0
16
- Requires-Dist: opentelemetry-sdk>=1.26.0
17
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.26.0
18
- Requires-Dist: python-dateutil>=2.9.0.post0
19
- Requires-Dist: fastapi[standard]>=0.112.1
20
- Requires-Dist: psutil>=6.0.0
21
- Requires-Dist: tomlkit>=0.13.2
22
- Description-Content-Type: text/markdown
23
-
24
- # DBOS Transact Python
25
-
26
- **DBOS Python is under construction! 🚧🚧🚧 Check back regularly for updates, release coming in mid-September!**
27
-
28
- DBOS Transact is a **Python library** for building durable and scalable applications.
29
-
30
- You want to use DBOS Transact in your application because you need:
31
-
32
- - **Resilience to any failure**. If your app is interrupted for any reason, it automatically resumes from where it left off. Reliable message delivery is built in. Idempotency is built in.
33
- - **Reliable event processing**. Need to consume Kafka events exactly-once? Just add one line of code to your app. Need to run a task exactly once per hour, day, or month? Just one more line of code.
34
- - **Built-in observability**. Automatically emit [OpenTelemetry](https://opentelemetry.io/)-compatible logs and traces from any application. Query your app's history from the command line or with SQL.
35
- - **Blazing-fast, developer-friendly serverless**. Develop your project locally and run it anywhere. When you're ready, [deploy it for free to DBOS Cloud](https://docs.dbos.dev/getting-started/quickstart#deploying-to-dbos-cloud) and we'll host it for you, [25x faster](https://www.dbos.dev/blog/dbos-vs-aws-step-functions-benchmark) and [15x cheaper](https://www.dbos.dev/blog/dbos-vs-lambda-cost) than AWS Lambda.
36
-
37
- ## Getting Started
38
-
39
- To try out the latest pre-release version, install with:
40
-
41
- ```shell
42
- pip install --pre dbos
43
- ```
44
-
45
- ## Documentation
46
-
47
- Coming soon! 🚧
48
-
49
- But we have some cool demo apps for you to check out: [https://github.com/dbos-inc/dbos-demo-apps/tree/main/python](https://github.com/dbos-inc/dbos-demo-apps/tree/main/python)
50
-
51
- ## Main Features
52
-
53
- Here are some of the core features of DBOS Transact:
54
-
55
- | Feature | Description
56
- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
57
- | [Transactions](https://www.dbos.dev/dbos-transact-python) | Easily and safely query your application database using [SQLAlchemy](https://www.sqlalchemy.org/) or raw SQL.
58
- | [Workflows](https://www.dbos.dev/dbos-transact-python) | Reliable workflow orchestration&#8212;resume your program after any failure.
59
- | [Idempotency](https://www.dbos.dev/dbos-transact-python) | Automatically make any request idempotent, so your requests happen exactly once.
60
- | [Authentication and Authorization](https://www.dbos.dev/dbos-transact-python) | Secure your HTTP endpoints so only authorized users can access them.
61
- | [Kafka Integration](https://www.dbos.dev/dbos-transact-python) | Consume Kafka messages exactly-once with transactions or workflows.
62
- | [Scheduled Workflows](https://www.dbos.dev/dbos-transact-python) | Schedule your workflows to run exactly-once per time interval with cron-like syntax.
63
- | [Self-Hosting](https://www.dbos.dev/dbos-transact-python) | Host your applications anywhere, as long as they have a Postgres database to connect to.
64
-
65
- And DBOS Cloud:
66
-
67
- | Feature | Description
68
- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
69
- | [Serverless App Deployment](https://docs.dbos.dev/cloud-tutorials/application-management) | Deploy apps to DBOS Cloud in minutes.
70
- | [Interactive Time Travel](https://docs.dbos.dev/cloud-tutorials/interactive-timetravel) | Query your application database as of any past point in time.
71
- | [Cloud Database Management](https://docs.dbos.dev/cloud-tutorials/database-management) | Provision cloud Postgres instances for your applications. Alternatively, [bring your own database](https://docs.dbos.dev/cloud-tutorials/byod-management).
72
- | [Built-in Observability](https://docs.dbos.dev/cloud-tutorials/monitoring-dashboard) | Built-in log capture, request tracing, and dashboards.
73
-
74
- ## Community
75
-
76
- If you're interested in building with us, please star our repository and join our community on [Discord](https://discord.gg/fMwQjeW5zg)!
77
- If you see a bug or have a feature request, don't hesitate to open an issue here on GitHub.
78
- If you're interested in contributing, check out our [contributions guide](./CONTRIBUTING.md).
File without changes