dbos 2.1.0a2__tar.gz → 2.1.0a3__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.
Files changed (99) hide show
  1. {dbos-2.1.0a2 → dbos-2.1.0a3}/PKG-INFO +1 -1
  2. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_scheduler.py +24 -14
  3. {dbos-2.1.0a2 → dbos-2.1.0a3}/pyproject.toml +1 -1
  4. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_scheduler.py +7 -7
  5. {dbos-2.1.0a2 → dbos-2.1.0a3}/LICENSE +0 -0
  6. {dbos-2.1.0a2 → dbos-2.1.0a3}/README.md +0 -0
  7. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/__init__.py +0 -0
  8. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/__main__.py +0 -0
  9. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_admin_server.py +0 -0
  10. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_app_db.py +0 -0
  11. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_classproperty.py +0 -0
  12. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_client.py +0 -0
  13. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_conductor/conductor.py +0 -0
  14. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_conductor/protocol.py +0 -0
  15. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_context.py +0 -0
  16. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_core.py +0 -0
  17. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_croniter.py +0 -0
  18. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_dbos.py +0 -0
  19. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_dbos_config.py +0 -0
  20. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_debouncer.py +0 -0
  21. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_debug.py +0 -0
  22. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_docker_pg_helper.py +0 -0
  23. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_error.py +0 -0
  24. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_event_loop.py +0 -0
  25. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_fastapi.py +0 -0
  26. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_flask.py +0 -0
  27. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_kafka.py +0 -0
  28. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_kafka_message.py +0 -0
  29. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_logger.py +0 -0
  30. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_migration.py +0 -0
  31. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_outcome.py +0 -0
  32. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_queue.py +0 -0
  33. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_recovery.py +0 -0
  34. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_registrations.py +0 -0
  35. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_roles.py +0 -0
  36. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_schemas/__init__.py +0 -0
  37. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_schemas/application_database.py +0 -0
  38. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_schemas/system_database.py +0 -0
  39. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_serialization.py +0 -0
  40. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_sys_db.py +0 -0
  41. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_sys_db_postgres.py +0 -0
  42. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_sys_db_sqlite.py +0 -0
  43. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/README.md +0 -0
  44. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  45. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
  46. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  47. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  48. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/migrations/create_table.py.dbos +0 -0
  49. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  50. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_tracer.py +0 -0
  51. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_utils.py +0 -0
  52. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/_workflow_commands.py +0 -0
  53. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/cli/_github_init.py +0 -0
  54. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/cli/_template_init.py +0 -0
  55. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/cli/cli.py +0 -0
  56. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/cli/migration.py +0 -0
  57. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/dbos-config.schema.json +0 -0
  58. {dbos-2.1.0a2 → dbos-2.1.0a3}/dbos/py.typed +0 -0
  59. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/__init__.py +0 -0
  60. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/atexit_no_ctor.py +0 -0
  61. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/atexit_no_launch.py +0 -0
  62. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/classdefs.py +0 -0
  63. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/client_collateral.py +0 -0
  64. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/client_worker.py +0 -0
  65. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/conftest.py +0 -0
  66. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/dupname_classdefs1.py +0 -0
  67. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/dupname_classdefsa.py +0 -0
  68. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/more_classdefs.py +0 -0
  69. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/queuedworkflow.py +0 -0
  70. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/script_without_fastapi.py +0 -0
  71. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_admin_server.py +0 -0
  72. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_async.py +0 -0
  73. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_async_workflow_management.py +0 -0
  74. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_classdecorators.py +0 -0
  75. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_cli.py +0 -0
  76. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_client.py +0 -0
  77. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_concurrency.py +0 -0
  78. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_config.py +0 -0
  79. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_croniter.py +0 -0
  80. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_dbos.py +0 -0
  81. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_debouncer.py +0 -0
  82. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_debug.py +0 -0
  83. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_docker_secrets.py +0 -0
  84. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_failures.py +0 -0
  85. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_fastapi.py +0 -0
  86. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_fastapi_roles.py +0 -0
  87. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_flask.py +0 -0
  88. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_kafka.py +0 -0
  89. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_outcome.py +0 -0
  90. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_package.py +0 -0
  91. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_queue.py +0 -0
  92. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_schema_migration.py +0 -0
  93. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_singleton.py +0 -0
  94. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_spans.py +0 -0
  95. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_sqlalchemy.py +0 -0
  96. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_streaming.py +0 -0
  97. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_workflow_introspection.py +0 -0
  98. {dbos-2.1.0a2 → dbos-2.1.0a3}/tests/test_workflow_management.py +0 -0
  99. {dbos-2.1.0a2 → dbos-2.1.0a3}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 2.1.0a2
3
+ Version: 2.1.0a3
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,3 +1,4 @@
1
+ import random
1
2
  import threading
2
3
  import traceback
3
4
  from datetime import datetime, timezone
@@ -15,28 +16,40 @@ from ._registrations import get_dbos_func_name
15
16
 
16
17
  ScheduledWorkflow = Callable[[datetime, datetime], None]
17
18
 
18
- scheduler_queue: Queue
19
-
20
19
 
21
20
  def scheduler_loop(
22
21
  func: ScheduledWorkflow, cron: str, stop_event: threading.Event
23
22
  ) -> None:
23
+ from dbos._dbos import _get_dbos_instance
24
+
25
+ dbos = _get_dbos_instance()
26
+ scheduler_queue = dbos._registry.get_internal_queue()
24
27
  try:
25
28
  iter = croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
26
- except Exception as e:
29
+ except Exception:
27
30
  dbos_logger.error(
28
31
  f'Cannot run scheduled function {get_dbos_func_name(func)}. Invalid crontab "{cron}"'
29
32
  )
33
+ raise
30
34
  while not stop_event.is_set():
31
- nextExecTime = iter.get_next(datetime)
32
- sleepTime = nextExecTime - datetime.now(timezone.utc)
33
- if stop_event.wait(timeout=sleepTime.total_seconds()):
35
+ next_exec_time = iter.get_next(datetime)
36
+ sleep_time = (next_exec_time - datetime.now(timezone.utc)).total_seconds()
37
+ sleep_time = max(0, sleep_time)
38
+ # To prevent a "thundering herd" problem in a distributed setting,
39
+ # apply jitter of up to 10% the sleep time, capped at 10 seconds
40
+ max_jitter = min(sleep_time / 10, 10)
41
+ jitter = random.uniform(0, max_jitter)
42
+ if stop_event.wait(timeout=sleep_time + jitter):
34
43
  return
35
44
  try:
36
- with SetWorkflowID(
37
- f"sched-{get_dbos_func_name(func)}-{nextExecTime.isoformat()}"
38
- ):
39
- scheduler_queue.enqueue(func, nextExecTime, datetime.now(timezone.utc))
45
+ workflowID = (
46
+ f"sched-{get_dbos_func_name(func)}-{next_exec_time.isoformat()}"
47
+ )
48
+ if not dbos._sys_db.get_workflow_status(workflowID):
49
+ with SetWorkflowID(workflowID):
50
+ scheduler_queue.enqueue(
51
+ func, next_exec_time, datetime.now(timezone.utc)
52
+ )
40
53
  except Exception:
41
54
  dbos_logger.warning(
42
55
  f"Exception encountered in scheduler thread: {traceback.format_exc()})"
@@ -49,13 +62,10 @@ def scheduled(
49
62
  def decorator(func: ScheduledWorkflow) -> ScheduledWorkflow:
50
63
  try:
51
64
  croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
52
- except Exception as e:
65
+ except Exception:
53
66
  raise ValueError(
54
67
  f'Invalid crontab "{cron}" for scheduled function function {get_dbos_func_name(func)}.'
55
68
  )
56
-
57
- global scheduler_queue
58
- scheduler_queue = dbosreg.get_internal_queue()
59
69
  stop_event = threading.Event()
60
70
  dbosreg.register_poller(stop_event, scheduler_loop, func, cron, stop_event)
61
71
  return func
@@ -34,7 +34,7 @@ classifiers = [
34
34
  "Topic :: Software Development :: Libraries :: Python Modules",
35
35
  "Framework :: AsyncIO",
36
36
  ]
37
- version = "2.1.0a2"
37
+ version = "2.1.0a3"
38
38
 
39
39
  [project.license]
40
40
  text = "MIT"
@@ -102,7 +102,7 @@ def test_scheduled_workflow(dbos: DBOS) -> None:
102
102
  wf_counter += 1
103
103
 
104
104
  time.sleep(5)
105
- assert wf_counter > 2 and wf_counter <= 5
105
+ assert wf_counter > 1 and wf_counter <= 5
106
106
 
107
107
 
108
108
  def test_appdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
@@ -123,7 +123,7 @@ def test_appdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
123
123
  time.sleep(2)
124
124
  assert dbos._app_db
125
125
  simulate_db_restart(dbos._app_db.engine, 2)
126
- time.sleep(2)
126
+ time.sleep(3)
127
127
  assert wf_counter > 2
128
128
 
129
129
 
@@ -138,7 +138,7 @@ def test_sysdb_downtime(dbos: DBOS, skip_with_sqlite: None) -> None:
138
138
 
139
139
  time.sleep(2)
140
140
  simulate_db_restart(dbos._sys_db.engine, 2)
141
- time.sleep(2)
141
+ time.sleep(3)
142
142
  # We know there should be at least 2 occurrences from the 4 seconds when the DB was up.
143
143
  # There could be more than 4, depending on the pace the machine...
144
144
  assert wf_counter >= 2
@@ -154,7 +154,7 @@ def test_scheduled_transaction(dbos: DBOS) -> None:
154
154
  txn_counter += 1
155
155
 
156
156
  time.sleep(5)
157
- assert txn_counter > 2 and txn_counter <= 5
157
+ assert txn_counter > 1 and txn_counter <= 5
158
158
 
159
159
 
160
160
  def test_scheduled_step(dbos: DBOS) -> None:
@@ -205,8 +205,8 @@ def test_scheduler_oaoo(dbos: DBOS) -> None:
205
205
  nonlocal txn_counter
206
206
  txn_counter += 1
207
207
 
208
- time.sleep(3)
209
- assert wf_counter >= 1 and wf_counter <= 3
208
+ time.sleep(4)
209
+ assert wf_counter >= 1 and wf_counter <= 4
210
210
  max_tries = 10
211
211
  for i in range(max_tries):
212
212
  try:
@@ -223,7 +223,7 @@ def test_scheduler_oaoo(dbos: DBOS) -> None:
223
223
  evt.set()
224
224
 
225
225
  # Wait for workflows to finish
226
- time.sleep(2)
226
+ time.sleep(3)
227
227
 
228
228
  dbos._sys_db.update_workflow_outcome(workflow_id, "PENDING")
229
229
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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