dbos 0.22.0a7__py3-none-any.whl → 0.22.0a9__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/_context.py CHANGED
@@ -49,7 +49,6 @@ class TracedAttributes(TypedDict, total=False):
49
49
  class DBOSContext:
50
50
  def __init__(self) -> None:
51
51
  self.executor_id = os.environ.get("DBOS__VMID", "local")
52
- self.app_version = os.environ.get("DBOS__APPVERSION", "")
53
52
  self.app_id = os.environ.get("DBOS__APPID", "")
54
53
 
55
54
  self.logger = dbos_logger
dbos/_core.py CHANGED
@@ -163,7 +163,7 @@ def _init_workflow(
163
163
  "output": None,
164
164
  "error": None,
165
165
  "app_id": ctx.app_id,
166
- "app_version": ctx.app_version,
166
+ "app_version": dbos.app_version,
167
167
  "executor_id": ctx.executor_id,
168
168
  "request": (
169
169
  _serialization.serialize(ctx.request) if ctx.request is not None else None
dbos/_db_wizard.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  import time
4
- from typing import TYPE_CHECKING, Optional, TypedDict
4
+ from typing import TYPE_CHECKING, Optional, TypedDict, cast
5
5
 
6
6
  import docker # type: ignore
7
7
  import typer
@@ -50,10 +50,14 @@ def db_wizard(config: "ConfigFile", config_file_path: str) -> "ConfigFile":
50
50
  with open(config_file_path, "r") as file:
51
51
  content = file.read()
52
52
  local_config = yaml.safe_load(content)
53
+ if "database" not in local_config:
54
+ local_config["database"] = {}
55
+ local_config = cast("ConfigFile", local_config)
56
+
53
57
  if (
54
- local_config["database"]["hostname"]
55
- or local_config["database"]["port"]
56
- or local_config["database"]["username"]
58
+ local_config["database"].get("hostname")
59
+ or local_config["database"].get("port")
60
+ or local_config["database"].get("username")
57
61
  or db_config["hostname"] != "localhost"
58
62
  or db_config["port"] != 5432
59
63
  or db_config["username"] != "postgres"
dbos/_dbos.py CHANGED
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import atexit
5
+ import hashlib
6
+ import inspect
5
7
  import json
6
8
  import os
7
9
  import sys
@@ -186,6 +188,22 @@ class DBOSRegistry:
186
188
  else:
187
189
  self.instance_info_map[fn] = inst
188
190
 
191
+ def compute_app_version(self) -> str:
192
+ """
193
+ An application's version is computed from a hash of the source of its workflows.
194
+ This is guaranteed to be stable given identical source code because it uses an MD5 hash
195
+ and because it iterates through the workflows in sorted order.
196
+ This way, if the app's workflows are updated (which would break recovery), its version changes.
197
+ App version can be manually set through the DBOS__APPVERSION environment variable.
198
+ """
199
+ hasher = hashlib.md5()
200
+ sources = sorted(
201
+ [inspect.getsource(wf) for wf in self.workflow_info_map.values()]
202
+ )
203
+ for source in sources:
204
+ hasher.update(source.encode("utf-8"))
205
+ return hasher.hexdigest()
206
+
189
207
 
190
208
  class DBOS:
191
209
  """
@@ -283,6 +301,7 @@ class DBOS:
283
301
  self._executor_field: Optional[ThreadPoolExecutor] = None
284
302
  self._background_threads: List[threading.Thread] = []
285
303
  self._executor_id: str = os.environ.get("DBOS__VMID", "local")
304
+ self.app_version: str = os.environ.get("DBOS__APPVERSION", "")
286
305
 
287
306
  # If using FastAPI, set up middleware and lifecycle events
288
307
  if self.fastapi is not None:
@@ -351,6 +370,10 @@ class DBOS:
351
370
  dbos_logger.warning(f"DBOS was already launched")
352
371
  return
353
372
  self._launched = True
373
+ if self.app_version == "":
374
+ self.app_version = self._registry.compute_app_version()
375
+ dbos_logger.info(f"Application version: {self.app_version}")
376
+ dbos_tracer.app_version = self.app_version
354
377
  self._executor_field = ThreadPoolExecutor(max_workers=64)
355
378
  self._sys_db_field = SystemDatabase(self.config)
356
379
  self._app_db_field = ApplicationDatabase(self.config)
@@ -359,9 +382,19 @@ class DBOS:
359
382
  admin_port = 3001
360
383
  self._admin_server_field = AdminServer(dbos=self, port=admin_port)
361
384
 
362
- if not os.environ.get("DBOS__VMID"):
363
- workflow_ids = self._sys_db.get_pending_workflows("local")
364
- self._executor.submit(startup_recovery_thread, self, workflow_ids)
385
+ workflow_ids = self._sys_db.get_pending_workflows(
386
+ self._executor_id, self.app_version
387
+ )
388
+ if (len(workflow_ids)) > 0:
389
+ self.logger.info(
390
+ f"Recovering {len(workflow_ids)} workflows from application version {self.app_version}"
391
+ )
392
+ else:
393
+ self.logger.info(
394
+ f"No workflows to recover from application version {self.app_version}"
395
+ )
396
+
397
+ self._executor.submit(startup_recovery_thread, self, workflow_ids)
365
398
 
366
399
  # Listen to notifications
367
400
  notification_listener_thread = threading.Thread(
@@ -398,13 +431,13 @@ class DBOS:
398
431
  self._background_threads.append(poller_thread)
399
432
  self._registry.pollers = []
400
433
 
401
- dbos_logger.info("DBOS launched")
434
+ dbos_logger.info("DBOS launched!")
402
435
 
403
436
  # Flush handlers and add OTLP to all loggers if enabled
404
437
  # to enable their export in DBOS Cloud
405
438
  for handler in dbos_logger.handlers:
406
439
  handler.flush()
407
- add_otlp_to_all_loggers()
440
+ add_otlp_to_all_loggers(self.app_version)
408
441
  except Exception:
409
442
  dbos_logger.error(f"DBOS failed to launch: {traceback.format_exc()}")
410
443
  raise
dbos/_logger.py CHANGED
@@ -86,8 +86,9 @@ def config_logger(config: "ConfigFile") -> None:
86
86
  dbos_logger.addFilter(_otlp_transformer)
87
87
 
88
88
 
89
- def add_otlp_to_all_loggers() -> None:
89
+ def add_otlp_to_all_loggers(app_version: str) -> None:
90
90
  if _otlp_handler is not None and _otlp_transformer is not None:
91
+ _otlp_transformer.app_version = app_version
91
92
  root = logging.root
92
93
 
93
94
  root.addHandler(_otlp_handler)
dbos/_recovery.py CHANGED
@@ -43,12 +43,10 @@ def recover_pending_workflows(
43
43
  ) -> List["WorkflowHandle[Any]"]:
44
44
  workflow_handles: List["WorkflowHandle[Any]"] = []
45
45
  for executor_id in executor_ids:
46
- if executor_id == "local" and os.environ.get("DBOS__VMID"):
47
- dbos.logger.debug(
48
- f"Skip local recovery because it's running in a VM: {os.environ.get('DBOS__VMID')}"
49
- )
50
46
  dbos.logger.debug(f"Recovering pending workflows for executor: {executor_id}")
51
- pending_workflows = dbos._sys_db.get_pending_workflows(executor_id)
47
+ pending_workflows = dbos._sys_db.get_pending_workflows(
48
+ executor_id, dbos.app_version
49
+ )
52
50
  for pending_workflow in pending_workflows:
53
51
  if (
54
52
  pending_workflow.queue_name
@@ -65,6 +63,7 @@ def recover_pending_workflows(
65
63
  workflow_handles.append(
66
64
  execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
67
65
  )
68
-
69
- dbos.logger.info("Recovered pending workflows")
66
+ dbos.logger.info(
67
+ f"Recovering {len(pending_workflows)} workflows from version {dbos.app_version}"
68
+ )
70
69
  return workflow_handles
dbos/_sys_db.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import logging
2
3
  import os
3
4
  import re
4
5
  import threading
@@ -13,6 +14,7 @@ from typing import (
13
14
  Optional,
14
15
  Sequence,
15
16
  Set,
17
+ Tuple,
16
18
  TypedDict,
17
19
  cast,
18
20
  )
@@ -191,7 +193,7 @@ class SystemDatabase:
191
193
  database="postgres",
192
194
  # fills the "application_name" column in pg_stat_activity
193
195
  query={
194
- "application_name": f"dbos_transact_{os.environ.get('DBOS__VMID', 'local')}_{os.environ.get('DBOS__APPVERSION', '')}"
196
+ "application_name": f"dbos_transact_{os.environ.get('DBOS__VMID', 'local')}"
195
197
  },
196
198
  )
197
199
  engine = sa.create_engine(postgres_db_url)
@@ -213,7 +215,7 @@ class SystemDatabase:
213
215
  database=sysdb_name,
214
216
  # fills the "application_name" column in pg_stat_activity
215
217
  query={
216
- "application_name": f"dbos_transact_{os.environ.get('DBOS__VMID', 'local')}_{os.environ.get('DBOS__APPVERSION', '')}"
218
+ "application_name": f"dbos_transact_{os.environ.get('DBOS__VMID', 'local')}"
217
219
  },
218
220
  )
219
221
 
@@ -228,6 +230,7 @@ class SystemDatabase:
228
230
  )
229
231
  alembic_cfg = Config()
230
232
  alembic_cfg.set_main_option("script_location", migration_dir)
233
+ logging.getLogger("alembic").setLevel(logging.WARNING)
231
234
  # Alembic requires the % in URL-escaped parameters to itself be escaped to %%.
232
235
  escaped_conn_string = re.sub(
233
236
  r"%(?=[0-9A-Fa-f]{2})",
@@ -769,7 +772,7 @@ class SystemDatabase:
769
772
  return GetWorkflowsOutput(workflow_uuids)
770
773
 
771
774
  def get_pending_workflows(
772
- self, executor_id: str
775
+ self, executor_id: str, app_version: str
773
776
  ) -> list[GetPendingWorkflowsOutput]:
774
777
  with self.engine.begin() as c:
775
778
  rows = c.execute(
@@ -780,8 +783,10 @@ class SystemDatabase:
780
783
  SystemSchema.workflow_status.c.status
781
784
  == WorkflowStatusString.PENDING.value,
782
785
  SystemSchema.workflow_status.c.executor_id == executor_id,
786
+ SystemSchema.workflow_status.c.application_version == app_version,
783
787
  )
784
788
  ).fetchall()
789
+
785
790
  return [
786
791
  GetPendingWorkflowsOutput(
787
792
  workflow_uuid=row.workflow_uuid,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.22.0a7
3
+ Version: 0.22.0a9
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- dbos-0.22.0a7.dist-info/METADATA,sha256=OZrIIqhA5tVwlWd5tAFmhPBc4aIQ-8GCOd-3bNuuBNI,5309
2
- dbos-0.22.0a7.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- dbos-0.22.0a7.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-0.22.0a7.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-0.22.0a9.dist-info/METADATA,sha256=F2gVeTkPRbVFI8zT2Cx_cF0YMbOtOT3rhLJHLpx-lZI,5309
2
+ dbos-0.22.0a9.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ dbos-0.22.0a9.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-0.22.0a9.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
5
  dbos/__init__.py,sha256=CxRHBHEthPL4PZoLbZhp3rdm44-KkRTT2-7DkK9d4QQ,724
6
6
  dbos/_admin_server.py,sha256=YiVn5lywz2Vg8_juyNHOYl0HVEy48--7b4phwK7r92o,5732
7
7
  dbos/_app_db.py,sha256=_tv2vmPjjiaikwgxH3mqxgJ4nUUcG2-0uMXKWCqVu1c,5509
@@ -9,18 +9,18 @@ dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
9
9
  dbos/_cloudutils/authentication.py,sha256=V0fCWQN9stCkhbuuxgPTGpvuQcDqfU3KAxPAh01vKW4,5007
10
10
  dbos/_cloudutils/cloudutils.py,sha256=5e3CW1deSW-dI5G3QN0XbiVsBhyqT8wu7fuV2f8wtGU,7688
11
11
  dbos/_cloudutils/databases.py,sha256=x4187Djsyoa-QaG3Kog8JT2_GERsnqa93LIVanmVUmg,8393
12
- dbos/_context.py,sha256=FHB_fpE4fQt4fIJvAmMMsbY4xHwH77gsW01cFsRZjsE,17779
13
- dbos/_core.py,sha256=PvII-lcUgii0WqaDb9-56gfQ_69VoWthKAkKexRebuk,35461
12
+ dbos/_context.py,sha256=gikN5lUVqnvR-ISoOElXYeYsR_BO2whebB3YP2DJBM4,17713
13
+ dbos/_core.py,sha256=y6FV2l7EDBXya2EJFJMCWM4Acc-4T8J1xAY91O9pImY,35462
14
14
  dbos/_croniter.py,sha256=hbhgfsHBqclUS8VeLnJ9PSE9Z54z6mi4nnrr1aUXn0k,47561
15
- dbos/_db_wizard.py,sha256=0kF_WO3oXJ7Bf2CtsfmF_RS3rmaex2SmnDqkpOPv0wo,7907
16
- dbos/_dbos.py,sha256=klpuF1Wj05_qsWiKnLSWfei-16-K-g4BQ5R0MMfTuNE,36620
15
+ dbos/_db_wizard.py,sha256=6tfJaCRa1NtkUdNW75a2yvi_mEgnPJ9C1HP2zPG1hCU,8067
16
+ dbos/_dbos.py,sha256=FE1OPzLM55F3Y6EUg4tyuSOtuggOGzrJNrGOZ3mqCpU,38118
17
17
  dbos/_dbos_config.py,sha256=DfiqVVxNqnafkocSzLqBp1Ig5vCviDTDK_GO3zTtQqI,8298
18
18
  dbos/_error.py,sha256=vtaSsG0QW6cRlwfZ4zzZWy_IHCZlomwSlrDyGWuyn8c,4337
19
19
  dbos/_fastapi.py,sha256=ke03vqsSYDnO6XeOtOVFXj0-f-v1MGsOxa9McaROvNc,3616
20
20
  dbos/_flask.py,sha256=DZKUZR5-xOzPI7tYZ53r2PvvHVoAb8SYwLzMVFsVfjI,2608
21
21
  dbos/_kafka.py,sha256=o6DbwnsYRDtvVTZVsN7BAK8cdP79AfoWX3Q7CGY2Yuo,4199
22
22
  dbos/_kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
23
- dbos/_logger.py,sha256=iYwbA7DLyXalWa2Yu07HO6Xm301nRuenMU64GgwUMkU,3576
23
+ dbos/_logger.py,sha256=hNEeOgR9yOwdgcOuvnW_wN9rbfpTk5OowPNhEJmjoQE,3644
24
24
  dbos/_migrations/env.py,sha256=38SIGVbmn_VV2x2u1aHLcPOoWgZ84eCymf3g_NljmbU,1626
25
25
  dbos/_migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
26
26
  dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py,sha256=ICLPl8CN9tQXMsLDsAj8z1TsL831-Z3F8jSBvrR-wyw,736
@@ -32,7 +32,7 @@ dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4
32
32
  dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
33
33
  dbos/_outcome.py,sha256=FDMgWVjZ06vm9xO-38H17mTqBImUYQxgKs_bDCSIAhE,6648
34
34
  dbos/_queue.py,sha256=eZiapBcyn70-viW0y9fo7u09V6_VF5ACNGJxD-U_dNM,2844
35
- dbos/_recovery.py,sha256=rek9rm2CaENbbl_vu3To-BdXop7tMEyGvtoNiJLVxjQ,2772
35
+ dbos/_recovery.py,sha256=GtNMvPFM9qetob-gCU9FPI2fo_BGZYRws4EFSpOuFa4,2675
36
36
  dbos/_registrations.py,sha256=_zy6k944Ll8QwqU12Kr3OP23ukVtm8axPNN1TS_kJRc,6717
37
37
  dbos/_request.py,sha256=cX1B3Atlh160phgS35gF1VEEV4pD126c9F3BDgBmxZU,929
38
38
  dbos/_roles.py,sha256=iOsgmIAf1XVzxs3gYWdGRe1B880YfOw5fpU7Jwx8_A8,2271
@@ -41,7 +41,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  dbos/_schemas/application_database.py,sha256=KeyoPrF7hy_ODXV7QNike_VFSD74QBRfQ76D7QyE9HI,966
42
42
  dbos/_schemas/system_database.py,sha256=rwp4EvCSaXcUoMaRczZCvETCxGp72k3-hvLyGUDkih0,5163
43
43
  dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
44
- dbos/_sys_db.py,sha256=muP2iEL0MeUBliJ7aBtCGARhZT1fsvZpfGjB0gghHII,62572
44
+ dbos/_sys_db.py,sha256=aGa3KDKUVeK9d7r_yK21uoKV3LJGzRzsg8S3Bea5j_U,62685
45
45
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
46
46
  dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  dbos/_templates/dbos-db-starter/__package/main.py,sha256=eI0SS9Nwj-fldtiuSzIlIG6dC91GXXwdRsoHxv6S_WI,2719
@@ -60,4 +60,4 @@ dbos/cli/cli.py,sha256=_tXw2IQrWW7fV_h51f_R99vEBSi6aMLz-vCOxKaENiQ,14155
60
60
  dbos/dbos-config.schema.json,sha256=X5TpXNcARGceX0zQs0fVgtZW_Xj9uBbY5afPt9Rz9yk,5741
61
61
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
62
62
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
63
- dbos-0.22.0a7.dist-info/RECORD,,
63
+ dbos-0.22.0a9.dist-info/RECORD,,