dbos 0.20.0a6__tar.gz → 0.20.0a8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

Files changed (89) hide show
  1. {dbos-0.20.0a6 → dbos-0.20.0a8}/PKG-INFO +1 -1
  2. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_dbos.py +17 -1
  3. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_sys_db.py +43 -0
  4. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/cli/cli.py +7 -50
  5. {dbos-0.20.0a6 → dbos-0.20.0a8}/pyproject.toml +1 -1
  6. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_package.py +58 -1
  7. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_schema_migration.py +40 -0
  8. {dbos-0.20.0a6 → dbos-0.20.0a8}/LICENSE +0 -0
  9. {dbos-0.20.0a6 → dbos-0.20.0a8}/README.md +0 -0
  10. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/__init__.py +0 -0
  11. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_admin_server.py +0 -0
  12. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_app_db.py +0 -0
  13. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_classproperty.py +0 -0
  14. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_cloudutils/authentication.py +0 -0
  15. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_cloudutils/cloudutils.py +0 -0
  16. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_cloudutils/databases.py +0 -0
  17. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_context.py +0 -0
  18. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_core.py +0 -0
  19. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_croniter.py +0 -0
  20. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_db_wizard.py +0 -0
  21. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_dbos_config.py +0 -0
  22. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_error.py +0 -0
  23. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_fastapi.py +0 -0
  24. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_flask.py +0 -0
  25. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_kafka.py +0 -0
  26. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_kafka_message.py +0 -0
  27. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_logger.py +0 -0
  28. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/env.py +0 -0
  29. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/script.py.mako +0 -0
  30. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  31. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  32. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  33. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  34. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  35. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  36. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  37. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_outcome.py +0 -0
  38. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_queue.py +0 -0
  39. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_recovery.py +0 -0
  40. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_registrations.py +0 -0
  41. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_request.py +0 -0
  42. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_roles.py +0 -0
  43. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_scheduler.py +0 -0
  44. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_schemas/__init__.py +0 -0
  45. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_schemas/application_database.py +0 -0
  46. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_schemas/system_database.py +0 -0
  47. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_serialization.py +0 -0
  48. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
  49. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  50. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  51. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  52. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  53. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  54. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  55. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  56. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  57. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  58. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_tracer.py +0 -0
  59. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/_workflow_commands.py +0 -0
  60. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/cli/_github_init.py +0 -0
  61. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/cli/_template_init.py +0 -0
  62. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/dbos-config.schema.json +0 -0
  63. {dbos-0.20.0a6 → dbos-0.20.0a8}/dbos/py.typed +0 -0
  64. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/__init__.py +0 -0
  65. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/atexit_no_ctor.py +0 -0
  66. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/atexit_no_launch.py +0 -0
  67. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/classdefs.py +0 -0
  68. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/conftest.py +0 -0
  69. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/more_classdefs.py +0 -0
  70. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/queuedworkflow.py +0 -0
  71. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_admin_server.py +0 -0
  72. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_async.py +0 -0
  73. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_classdecorators.py +0 -0
  74. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_concurrency.py +0 -0
  75. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_config.py +0 -0
  76. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_croniter.py +0 -0
  77. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_dbos.py +0 -0
  78. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_failures.py +0 -0
  79. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_fastapi.py +0 -0
  80. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_fastapi_roles.py +0 -0
  81. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_flask.py +0 -0
  82. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_kafka.py +0 -0
  83. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_outcome.py +0 -0
  84. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_queue.py +0 -0
  85. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_scheduler.py +0 -0
  86. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_singleton.py +0 -0
  87. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_spans.py +0 -0
  88. {dbos-0.20.0a6 → dbos-0.20.0a8}/tests/test_workflow_cmds.py +0 -0
  89. {dbos-0.20.0a6 → dbos-0.20.0a8}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.20.0a6
3
+ Version: 0.20.0a8
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -56,7 +56,7 @@ from ._registrations import (
56
56
  )
57
57
  from ._roles import default_required_roles, required_roles
58
58
  from ._scheduler import ScheduledWorkflow, scheduled
59
- from ._sys_db import WorkflowStatusString
59
+ from ._sys_db import WorkflowStatusString, reset_system_database
60
60
  from ._tracer import dbos_tracer
61
61
 
62
62
  if TYPE_CHECKING:
@@ -409,6 +409,22 @@ class DBOS:
409
409
  dbos_logger.error(f"DBOS failed to launch: {traceback.format_exc()}")
410
410
  raise
411
411
 
412
+ @classmethod
413
+ def reset_system_database(cls) -> None:
414
+ """
415
+ Destroy the DBOS system database. Useful for resetting the state of DBOS between tests.
416
+ This is a destructive operation and should only be used in a test environment.
417
+ More information on testing DBOS apps: https://docs.dbos.dev/python/tutorials/testing
418
+ """
419
+ if _dbos_global_instance is not None:
420
+ _dbos_global_instance._reset_system_database()
421
+
422
+ def _reset_system_database(self) -> None:
423
+ assert (
424
+ not self._launched
425
+ ), "The system database cannot be reset after DBOS is launched. Resetting the system database is a destructive operation that should only be used in a test environment."
426
+ reset_system_database(self.config)
427
+
412
428
  def _destroy(self) -> None:
413
429
  self._initialized = False
414
430
  for event in self.stop_events:
@@ -1265,3 +1265,46 @@ class SystemDatabase:
1265
1265
  .where(SystemSchema.workflow_queue.c.workflow_uuid == workflow_id)
1266
1266
  .values(completed_at_epoch_ms=int(time.time() * 1000))
1267
1267
  )
1268
+
1269
+
1270
+ def reset_system_database(config: ConfigFile) -> None:
1271
+ sysdb_name = (
1272
+ config["database"]["sys_db_name"]
1273
+ if "sys_db_name" in config["database"] and config["database"]["sys_db_name"]
1274
+ else config["database"]["app_db_name"] + SystemSchema.sysdb_suffix
1275
+ )
1276
+ postgres_db_url = sa.URL.create(
1277
+ "postgresql+psycopg",
1278
+ username=config["database"]["username"],
1279
+ password=config["database"]["password"],
1280
+ host=config["database"]["hostname"],
1281
+ port=config["database"]["port"],
1282
+ database="postgres",
1283
+ )
1284
+ try:
1285
+ # Connect to postgres default database
1286
+ engine = sa.create_engine(postgres_db_url)
1287
+
1288
+ with engine.connect() as conn:
1289
+ # Set autocommit required for database dropping
1290
+ conn.execution_options(isolation_level="AUTOCOMMIT")
1291
+
1292
+ # Terminate existing connections
1293
+ conn.execute(
1294
+ sa.text(
1295
+ """
1296
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
1297
+ FROM pg_stat_activity
1298
+ WHERE pg_stat_activity.datname = :db_name
1299
+ AND pid <> pg_backend_pid()
1300
+ """
1301
+ ),
1302
+ {"db_name": sysdb_name},
1303
+ )
1304
+
1305
+ # Drop the database
1306
+ conn.execute(sa.text(f"DROP DATABASE IF EXISTS {sysdb_name}"))
1307
+
1308
+ except sa.exc.SQLAlchemyError as e:
1309
+ dbos_logger.error(f"Error resetting system database: {str(e)}")
1310
+ raise e
@@ -18,8 +18,7 @@ from typing_extensions import Annotated
18
18
  from .. import load_config
19
19
  from .._app_db import ApplicationDatabase
20
20
  from .._dbos_config import _is_valid_app_name
21
- from .._schemas.system_database import SystemSchema
22
- from .._sys_db import SystemDatabase
21
+ from .._sys_db import SystemDatabase, reset_system_database
23
22
  from .._workflow_commands import _cancel_workflow, _get_workflow, _list_workflows
24
23
  from ..cli._github_init import create_template_from_github
25
24
  from ._template_init import copy_template, get_project_name, get_templates_directory
@@ -100,13 +99,15 @@ def init(
100
99
  ] = False,
101
100
  ) -> None:
102
101
  try:
102
+
103
103
  git_templates = ["dbos-app-starter", "dbos-cron-starter"]
104
104
  templates_dir = get_templates_directory()
105
105
  templates = git_templates + [
106
106
  x.name for x in os.scandir(templates_dir) if x.is_dir()
107
107
  ]
108
- if len(templates) == 0:
109
- raise Exception(f"no DBOS templates found in {templates_dir} ")
108
+
109
+ if config and template is None:
110
+ template = templates[-1]
110
111
 
111
112
  if template:
112
113
  if template not in templates:
@@ -222,56 +223,12 @@ def reset(
222
223
  typer.echo("Operation cancelled.")
223
224
  raise typer.Exit()
224
225
  config = load_config()
225
- sysdb_name = (
226
- config["database"]["sys_db_name"]
227
- if "sys_db_name" in config["database"] and config["database"]["sys_db_name"]
228
- else config["database"]["app_db_name"] + SystemSchema.sysdb_suffix
229
- )
230
- postgres_db_url = sa.URL.create(
231
- "postgresql+psycopg",
232
- username=config["database"]["username"],
233
- password=config["database"]["password"],
234
- host=config["database"]["hostname"],
235
- port=config["database"]["port"],
236
- database="postgres",
237
- )
238
226
  try:
239
- # Connect to postgres default database
240
- engine = sa.create_engine(postgres_db_url)
241
-
242
- with engine.connect() as conn:
243
- # Set autocommit required for database dropping
244
- conn.execution_options(isolation_level="AUTOCOMMIT")
245
-
246
- # Terminate existing connections
247
- conn.execute(
248
- sa.text(
249
- """
250
- SELECT pg_terminate_backend(pg_stat_activity.pid)
251
- FROM pg_stat_activity
252
- WHERE pg_stat_activity.datname = :db_name
253
- AND pid <> pg_backend_pid()
254
- """
255
- ),
256
- {"db_name": sysdb_name},
257
- )
258
-
259
- # Drop the database
260
- conn.execute(sa.text(f"DROP DATABASE IF EXISTS {sysdb_name}"))
261
-
227
+ reset_system_database(config)
262
228
  except sa.exc.SQLAlchemyError as e:
263
- typer.echo(f"Error dropping database: {str(e)}")
229
+ typer.echo(f"Error resetting system database: {str(e)}")
264
230
  return
265
231
 
266
- sys_db = None
267
- try:
268
- sys_db = SystemDatabase(config)
269
- except Exception as e:
270
- typer.echo(f"DBOS system schema migration failed: {e}")
271
- finally:
272
- if sys_db:
273
- sys_db.destroy()
274
-
275
232
 
276
233
  @workflow.command(help="List workflows for your application")
277
234
  def list(
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "0.20.0a6"
30
+ version = "0.20.0a8"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -9,6 +9,7 @@ import urllib.error
9
9
  import urllib.request
10
10
 
11
11
  import sqlalchemy as sa
12
+ import yaml
12
13
 
13
14
 
14
15
  def test_package(build_wheel: str, postgres_db_engine: sa.Engine) -> None:
@@ -49,7 +50,7 @@ def test_package(build_wheel: str, postgres_db_engine: sa.Engine) -> None:
49
50
 
50
51
  # initalize the app with dbos scaffolding
51
52
  subprocess.check_call(
52
- ["dbos", "init", template_name, "--template", "dbos-db-starter"],
53
+ ["dbos", "init", template_name, "--template", template_name],
53
54
  cwd=temp_path,
54
55
  env=venv,
55
56
  )
@@ -90,3 +91,59 @@ def test_package(build_wheel: str, postgres_db_engine: sa.Engine) -> None:
90
91
  finally:
91
92
  os.kill(process.pid, signal.SIGINT)
92
93
  process.wait()
94
+
95
+
96
+ def test_init_config() -> None:
97
+ app_name = "example-name"
98
+ expected_yaml = {
99
+ "name": app_name,
100
+ "language": "python",
101
+ "runtimeConfig": {"start": ["fastapi run ./main.py"]},
102
+ "database": {"migrate": ["echo 'No migrations specified'"]},
103
+ "telemetry": {"logs": {"logLevel": "INFO"}},
104
+ }
105
+ with tempfile.TemporaryDirectory() as temp_path:
106
+ subprocess.check_call(
107
+ ["dbos", "init", app_name, "--config"],
108
+ cwd=temp_path,
109
+ )
110
+
111
+ config_path = os.path.join(temp_path, "dbos-config.yaml")
112
+ assert os.path.exists(config_path)
113
+
114
+ with open(config_path) as f:
115
+ actual_yaml = yaml.safe_load(f)
116
+
117
+ assert actual_yaml == expected_yaml
118
+
119
+
120
+ def test_reset(postgres_db_engine: sa.Engine) -> None:
121
+ app_name = "reset-app"
122
+ sysdb_name = "reset_app_dbos_sys"
123
+ with tempfile.TemporaryDirectory() as temp_path:
124
+ subprocess.check_call(
125
+ ["dbos", "init", app_name, "--template", "dbos-db-starter"],
126
+ cwd=temp_path,
127
+ )
128
+
129
+ # Create a system database and verify it exists
130
+ subprocess.check_call(["dbos", "migrate"], cwd=temp_path)
131
+ with postgres_db_engine.connect() as c:
132
+ c.execution_options(isolation_level="AUTOCOMMIT")
133
+ result = c.execute(
134
+ sa.text(
135
+ f"SELECT COUNT(*) FROM pg_database WHERE datname = '{sysdb_name}'"
136
+ )
137
+ ).scalar()
138
+ assert result == 1
139
+
140
+ # Call reset and verify it's destroyed
141
+ subprocess.check_call(["dbos", "reset", "-y"], cwd=temp_path)
142
+ with postgres_db_engine.connect() as c:
143
+ c.execution_options(isolation_level="AUTOCOMMIT")
144
+ result = c.execute(
145
+ sa.text(
146
+ f"SELECT COUNT(*) FROM pg_database WHERE datname = '{sysdb_name}'"
147
+ )
148
+ ).scalar()
149
+ assert result == 0
@@ -102,3 +102,43 @@ def rollback_system_db(sysdb_url: str) -> None:
102
102
  )
103
103
  alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
104
104
  command.downgrade(alembic_cfg, "base") # Rollback all migrations
105
+
106
+
107
+ def test_reset(config: ConfigFile, postgres_db_engine: sa.Engine) -> None:
108
+ DBOS.destroy()
109
+ dbos = DBOS(config=config)
110
+ DBOS.launch()
111
+
112
+ # Make sure the system database exists
113
+ with dbos._sys_db.engine.connect() as c:
114
+ sql = SystemSchema.workflow_status.select()
115
+ result = c.execute(sql)
116
+ assert result.fetchall() == []
117
+
118
+ DBOS.destroy()
119
+ dbos = DBOS(config=config)
120
+ DBOS.reset_system_database()
121
+
122
+ sysdb_name = (
123
+ config["database"]["sys_db_name"]
124
+ if "sys_db_name" in config["database"] and config["database"]["sys_db_name"]
125
+ else config["database"]["app_db_name"] + SystemSchema.sysdb_suffix
126
+ )
127
+ with postgres_db_engine.connect() as c:
128
+ c.execution_options(isolation_level="AUTOCOMMIT")
129
+ count: int = c.execute(
130
+ sa.text(f"SELECT COUNT(*) FROM pg_database WHERE datname = '{sysdb_name}'")
131
+ ).scalar_one()
132
+ assert count == 0
133
+
134
+ DBOS.launch()
135
+
136
+ # Make sure the system database is recreated
137
+ with dbos._sys_db.engine.connect() as c:
138
+ sql = SystemSchema.workflow_status.select()
139
+ result = c.execute(sql)
140
+ assert result.fetchall() == []
141
+
142
+ # Verify that resetting after launch throws
143
+ with pytest.raises(AssertionError):
144
+ DBOS.reset_system_database()
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