dbos 0.19.0a4__py3-none-any.whl → 0.20.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.
- dbos/_admin_server.py +45 -2
- dbos/_context.py +11 -2
- dbos/_core.py +66 -6
- dbos/_dbos.py +36 -1
- dbos/_error.py +11 -0
- dbos/_fastapi.py +16 -11
- dbos/_flask.py +6 -2
- dbos/_kafka.py +17 -1
- dbos/_queue.py +1 -0
- dbos/_sys_db.py +113 -38
- dbos/_workflow_commands.py +171 -0
- dbos/cli/_github_init.py +107 -0
- dbos/cli/_template_init.py +98 -0
- dbos/cli/cli.py +367 -0
- {dbos-0.19.0a4.dist-info → dbos-0.20.0.dist-info}/METADATA +21 -16
- {dbos-0.19.0a4.dist-info → dbos-0.20.0.dist-info}/RECORD +29 -26
- {dbos-0.19.0a4.dist-info → dbos-0.20.0.dist-info}/entry_points.txt +1 -1
- dbos/cli.py +0 -337
- /dbos/_templates/{hello → dbos-db-starter}/README.md +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/__package/__init__.py +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/__package/main.py +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/__package/schema.py +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/alembic.ini +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/dbos-config.yaml.dbos +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/migrations/env.py.dbos +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/migrations/script.py.mako +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/migrations/versions/2024_07_31_180642_init.py +0 -0
- /dbos/_templates/{hello → dbos-db-starter}/start_postgres_docker.py +0 -0
- {dbos-0.19.0a4.dist-info → dbos-0.20.0.dist-info}/WHEEL +0 -0
- {dbos-0.19.0a4.dist-info → dbos-0.20.0.dist-info}/licenses/LICENSE +0 -0
dbos/cli.py
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import platform
|
|
3
|
-
import shutil
|
|
4
|
-
import signal
|
|
5
|
-
import subprocess
|
|
6
|
-
import time
|
|
7
|
-
import typing
|
|
8
|
-
from os import path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
import sqlalchemy as sa
|
|
12
|
-
import tomlkit
|
|
13
|
-
import typer
|
|
14
|
-
from rich import print
|
|
15
|
-
from rich.prompt import Prompt
|
|
16
|
-
from typing_extensions import Annotated
|
|
17
|
-
|
|
18
|
-
from dbos._schemas.system_database import SystemSchema
|
|
19
|
-
|
|
20
|
-
from . import load_config
|
|
21
|
-
from ._app_db import ApplicationDatabase
|
|
22
|
-
from ._dbos_config import _is_valid_app_name
|
|
23
|
-
from ._sys_db import SystemDatabase
|
|
24
|
-
|
|
25
|
-
app = typer.Typer()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _on_windows() -> bool:
|
|
29
|
-
return platform.system() == "Windows"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@app.command(
|
|
33
|
-
help="Start your DBOS application using the start commands in 'dbos-config.yaml'"
|
|
34
|
-
)
|
|
35
|
-
def start() -> None:
|
|
36
|
-
config = load_config()
|
|
37
|
-
start_commands = config["runtimeConfig"]["start"]
|
|
38
|
-
typer.echo("Executing start commands from 'dbos-config.yaml'")
|
|
39
|
-
for command in start_commands:
|
|
40
|
-
typer.echo(f"Executing: {command}")
|
|
41
|
-
|
|
42
|
-
# Run the command in the child process.
|
|
43
|
-
# On Unix-like systems, set its process group
|
|
44
|
-
process = subprocess.Popen(
|
|
45
|
-
command,
|
|
46
|
-
shell=True,
|
|
47
|
-
text=True,
|
|
48
|
-
preexec_fn=os.setsid if not _on_windows() else None,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
def signal_handler(signum: int, frame: Any) -> None:
|
|
52
|
-
"""
|
|
53
|
-
Forward kill signals to children.
|
|
54
|
-
|
|
55
|
-
When we receive a signal, send it to the entire process group of the child.
|
|
56
|
-
If that doesn't work, SIGKILL them then exit.
|
|
57
|
-
"""
|
|
58
|
-
# Send the signal to the child's entire process group
|
|
59
|
-
if process.poll() is None:
|
|
60
|
-
os.killpg(os.getpgid(process.pid), signum)
|
|
61
|
-
|
|
62
|
-
# Give some time for the child to terminate
|
|
63
|
-
for _ in range(10): # Wait up to 1 second
|
|
64
|
-
if process.poll() is not None:
|
|
65
|
-
break
|
|
66
|
-
time.sleep(0.1)
|
|
67
|
-
|
|
68
|
-
# If the child is still running, force kill it
|
|
69
|
-
if process.poll() is None:
|
|
70
|
-
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
|
71
|
-
|
|
72
|
-
# Exit immediately
|
|
73
|
-
os._exit(process.returncode if process.returncode is not None else 1)
|
|
74
|
-
|
|
75
|
-
# Configure the single handler only on Unix-like systems.
|
|
76
|
-
# TODO: Also kill the children on Windows.
|
|
77
|
-
if not _on_windows():
|
|
78
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
79
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
|
80
|
-
process.wait()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _get_templates_directory() -> str:
|
|
84
|
-
import dbos
|
|
85
|
-
|
|
86
|
-
package_dir = path.abspath(path.dirname(dbos.__file__))
|
|
87
|
-
return path.join(package_dir, "_templates")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _copy_dbos_template(src: str, dst: str, ctx: dict[str, str]) -> None:
|
|
91
|
-
with open(src, "r") as f:
|
|
92
|
-
content = f.read()
|
|
93
|
-
|
|
94
|
-
for key, value in ctx.items():
|
|
95
|
-
content = content.replace(f"${{{key}}}", value)
|
|
96
|
-
|
|
97
|
-
with open(dst, "w") as f:
|
|
98
|
-
f.write(content)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _copy_template_dir(src_dir: str, dst_dir: str, ctx: dict[str, str]) -> None:
|
|
102
|
-
|
|
103
|
-
for root, dirs, files in os.walk(src_dir, topdown=True):
|
|
104
|
-
dirs[:] = [d for d in dirs if d != "__package"]
|
|
105
|
-
|
|
106
|
-
dst_root = path.join(dst_dir, path.relpath(root, src_dir))
|
|
107
|
-
if len(dirs) == 0:
|
|
108
|
-
os.makedirs(dst_root, exist_ok=True)
|
|
109
|
-
else:
|
|
110
|
-
for dir in dirs:
|
|
111
|
-
os.makedirs(path.join(dst_root, dir), exist_ok=True)
|
|
112
|
-
|
|
113
|
-
for file in files:
|
|
114
|
-
src = path.join(root, file)
|
|
115
|
-
base, ext = path.splitext(file)
|
|
116
|
-
|
|
117
|
-
dst = path.join(dst_root, base if ext == ".dbos" else file)
|
|
118
|
-
if path.exists(dst):
|
|
119
|
-
print(f"[yellow]File {dst} already exists, skipping[/yellow]")
|
|
120
|
-
continue
|
|
121
|
-
|
|
122
|
-
if ext == ".dbos":
|
|
123
|
-
_copy_dbos_template(src, dst, ctx)
|
|
124
|
-
else:
|
|
125
|
-
shutil.copy(src, dst)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _copy_template(src_dir: str, project_name: str, config_mode: bool) -> None:
|
|
129
|
-
|
|
130
|
-
dst_dir = path.abspath(".")
|
|
131
|
-
|
|
132
|
-
package_name = project_name.replace("-", "_")
|
|
133
|
-
ctx = {
|
|
134
|
-
"project_name": project_name,
|
|
135
|
-
"package_name": package_name,
|
|
136
|
-
"migration_command": "alembic upgrade head",
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if config_mode:
|
|
140
|
-
ctx["package_name"] = "."
|
|
141
|
-
ctx["migration_command"] = "echo 'No migrations specified'"
|
|
142
|
-
_copy_dbos_template(
|
|
143
|
-
os.path.join(src_dir, "dbos-config.yaml.dbos"),
|
|
144
|
-
os.path.join(dst_dir, "dbos-config.yaml"),
|
|
145
|
-
ctx,
|
|
146
|
-
)
|
|
147
|
-
else:
|
|
148
|
-
_copy_template_dir(src_dir, dst_dir, ctx)
|
|
149
|
-
_copy_template_dir(
|
|
150
|
-
path.join(src_dir, "__package"), path.join(dst_dir, package_name), ctx
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _get_project_name() -> typing.Union[str, None]:
|
|
155
|
-
name = None
|
|
156
|
-
try:
|
|
157
|
-
with open("pyproject.toml", "rb") as file:
|
|
158
|
-
pyproj = typing.cast(dict[str, Any], tomlkit.load(file))
|
|
159
|
-
name = typing.cast(str, pyproj["project"]["name"])
|
|
160
|
-
except:
|
|
161
|
-
pass
|
|
162
|
-
|
|
163
|
-
if name == None:
|
|
164
|
-
try:
|
|
165
|
-
_, parent = path.split(path.abspath("."))
|
|
166
|
-
name = parent
|
|
167
|
-
except:
|
|
168
|
-
pass
|
|
169
|
-
|
|
170
|
-
return name
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@app.command(help="Initialize a new DBOS application from a template")
|
|
174
|
-
def init(
|
|
175
|
-
project_name: Annotated[
|
|
176
|
-
typing.Optional[str], typer.Argument(help="Specify application name")
|
|
177
|
-
] = None,
|
|
178
|
-
template: Annotated[
|
|
179
|
-
typing.Optional[str],
|
|
180
|
-
typer.Option("--template", "-t", help="Specify template to use"),
|
|
181
|
-
] = None,
|
|
182
|
-
config: Annotated[
|
|
183
|
-
bool,
|
|
184
|
-
typer.Option("--config", "-c", help="Only add dbos-config.yaml"),
|
|
185
|
-
] = False,
|
|
186
|
-
) -> None:
|
|
187
|
-
try:
|
|
188
|
-
if project_name is None:
|
|
189
|
-
project_name = typing.cast(
|
|
190
|
-
str, typer.prompt("What is your project's name?", _get_project_name())
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
if not _is_valid_app_name(project_name):
|
|
194
|
-
raise Exception(
|
|
195
|
-
f"{project_name} is an invalid DBOS app name. App names must be between 3 and 30 characters long and contain only lowercase letters, numbers, dashes, and underscores."
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
templates_dir = _get_templates_directory()
|
|
199
|
-
templates = [x.name for x in os.scandir(templates_dir) if x.is_dir()]
|
|
200
|
-
if len(templates) == 0:
|
|
201
|
-
raise Exception(f"no DBOS templates found in {templates_dir} ")
|
|
202
|
-
|
|
203
|
-
if template == None:
|
|
204
|
-
if len(templates) == 1:
|
|
205
|
-
template = templates[0]
|
|
206
|
-
else:
|
|
207
|
-
template = Prompt.ask(
|
|
208
|
-
"Which project template do you want to use?", choices=templates
|
|
209
|
-
)
|
|
210
|
-
else:
|
|
211
|
-
if template not in templates:
|
|
212
|
-
raise Exception(f"template {template} not found in {templates_dir}")
|
|
213
|
-
|
|
214
|
-
_copy_template(
|
|
215
|
-
path.join(templates_dir, template), project_name, config_mode=config
|
|
216
|
-
)
|
|
217
|
-
except Exception as e:
|
|
218
|
-
print(f"[red]{e}[/red]")
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
@app.command(
|
|
222
|
-
help="Run your database schema migrations using the migration commands in 'dbos-config.yaml'"
|
|
223
|
-
)
|
|
224
|
-
def migrate() -> None:
|
|
225
|
-
config = load_config()
|
|
226
|
-
if not config["database"]["password"]:
|
|
227
|
-
typer.echo(
|
|
228
|
-
"DBOS configuration does not contain database password, please check your config file and retry!"
|
|
229
|
-
)
|
|
230
|
-
raise typer.Exit(code=1)
|
|
231
|
-
app_db_name = config["database"]["app_db_name"]
|
|
232
|
-
|
|
233
|
-
typer.echo(f"Starting schema migration for database {app_db_name}")
|
|
234
|
-
|
|
235
|
-
# First, run DBOS migrations on the system database and the application database
|
|
236
|
-
app_db = None
|
|
237
|
-
sys_db = None
|
|
238
|
-
try:
|
|
239
|
-
sys_db = SystemDatabase(config)
|
|
240
|
-
app_db = ApplicationDatabase(config)
|
|
241
|
-
except Exception as e:
|
|
242
|
-
typer.echo(f"DBOS system schema migration failed: {e}")
|
|
243
|
-
finally:
|
|
244
|
-
if sys_db:
|
|
245
|
-
sys_db.destroy()
|
|
246
|
-
if app_db:
|
|
247
|
-
app_db.destroy()
|
|
248
|
-
|
|
249
|
-
# Next, run any custom migration commands specified in the configuration
|
|
250
|
-
typer.echo("Executing migration commands from 'dbos-config.yaml'")
|
|
251
|
-
try:
|
|
252
|
-
migrate_commands = (
|
|
253
|
-
config["database"]["migrate"]
|
|
254
|
-
if "migrate" in config["database"] and config["database"]["migrate"]
|
|
255
|
-
else []
|
|
256
|
-
)
|
|
257
|
-
for command in migrate_commands:
|
|
258
|
-
typer.echo(f"Executing migration command: {command}")
|
|
259
|
-
result = subprocess.run(command, shell=True, text=True)
|
|
260
|
-
if result.returncode != 0:
|
|
261
|
-
typer.echo(f"Migration command failed: {command}")
|
|
262
|
-
typer.echo(result.stderr)
|
|
263
|
-
raise typer.Exit(1)
|
|
264
|
-
if result.stdout:
|
|
265
|
-
typer.echo(result.stdout.rstrip())
|
|
266
|
-
except Exception as e:
|
|
267
|
-
typer.echo(f"An error occurred during schema migration: {e}")
|
|
268
|
-
raise typer.Exit(code=1)
|
|
269
|
-
|
|
270
|
-
typer.echo(f"Completed schema migration for database {app_db_name}")
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
@app.command(help="Reset the DBOS system database")
|
|
274
|
-
def reset(
|
|
275
|
-
yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt")
|
|
276
|
-
) -> None:
|
|
277
|
-
if not yes:
|
|
278
|
-
confirm = typer.confirm(
|
|
279
|
-
"This command resets your DBOS system database, deleting metadata about past workflows and steps. Are you sure you want to proceed?"
|
|
280
|
-
)
|
|
281
|
-
if not confirm:
|
|
282
|
-
typer.echo("Operation cancelled.")
|
|
283
|
-
raise typer.Exit()
|
|
284
|
-
config = load_config()
|
|
285
|
-
sysdb_name = (
|
|
286
|
-
config["database"]["sys_db_name"]
|
|
287
|
-
if "sys_db_name" in config["database"] and config["database"]["sys_db_name"]
|
|
288
|
-
else config["database"]["app_db_name"] + SystemSchema.sysdb_suffix
|
|
289
|
-
)
|
|
290
|
-
postgres_db_url = sa.URL.create(
|
|
291
|
-
"postgresql+psycopg",
|
|
292
|
-
username=config["database"]["username"],
|
|
293
|
-
password=config["database"]["password"],
|
|
294
|
-
host=config["database"]["hostname"],
|
|
295
|
-
port=config["database"]["port"],
|
|
296
|
-
database="postgres",
|
|
297
|
-
)
|
|
298
|
-
try:
|
|
299
|
-
# Connect to postgres default database
|
|
300
|
-
engine = sa.create_engine(postgres_db_url)
|
|
301
|
-
|
|
302
|
-
with engine.connect() as conn:
|
|
303
|
-
# Set autocommit required for database dropping
|
|
304
|
-
conn.execution_options(isolation_level="AUTOCOMMIT")
|
|
305
|
-
|
|
306
|
-
# Terminate existing connections
|
|
307
|
-
conn.execute(
|
|
308
|
-
sa.text(
|
|
309
|
-
"""
|
|
310
|
-
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
311
|
-
FROM pg_stat_activity
|
|
312
|
-
WHERE pg_stat_activity.datname = :db_name
|
|
313
|
-
AND pid <> pg_backend_pid()
|
|
314
|
-
"""
|
|
315
|
-
),
|
|
316
|
-
{"db_name": sysdb_name},
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
# Drop the database
|
|
320
|
-
conn.execute(sa.text(f"DROP DATABASE IF EXISTS {sysdb_name}"))
|
|
321
|
-
|
|
322
|
-
except sa.exc.SQLAlchemyError as e:
|
|
323
|
-
typer.echo(f"Error dropping database: {str(e)}")
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
sys_db = None
|
|
327
|
-
try:
|
|
328
|
-
sys_db = SystemDatabase(config)
|
|
329
|
-
except Exception as e:
|
|
330
|
-
typer.echo(f"DBOS system schema migration failed: {e}")
|
|
331
|
-
finally:
|
|
332
|
-
if sys_db:
|
|
333
|
-
sys_db.destroy()
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if __name__ == "__main__":
|
|
337
|
-
app()
|
|
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
|