dbos 0.28.0a12__py3-none-any.whl → 0.28.0a15__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/_app_db.py +14 -49
- dbos/_client.py +17 -6
- dbos/_conductor/conductor.py +32 -1
- dbos/_conductor/protocol.py +19 -2
- dbos/_dbos.py +18 -21
- dbos/_dbos_config.py +142 -114
- dbos/_sys_db.py +23 -50
- dbos/cli/cli.py +56 -14
- dbos/dbos-config.schema.json +0 -10
- {dbos-0.28.0a12.dist-info → dbos-0.28.0a15.dist-info}/METADATA +1 -1
- {dbos-0.28.0a12.dist-info → dbos-0.28.0a15.dist-info}/RECORD +14 -14
- {dbos-0.28.0a12.dist-info → dbos-0.28.0a15.dist-info}/WHEEL +0 -0
- {dbos-0.28.0a12.dist-info → dbos-0.28.0a15.dist-info}/entry_points.txt +0 -0
- {dbos-0.28.0a12.dist-info → dbos-0.28.0a15.dist-info}/licenses/LICENSE +0 -0
dbos/_app_db.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List, Optional, TypedDict
|
1
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
2
2
|
|
3
3
|
import sqlalchemy as sa
|
4
4
|
import sqlalchemy.dialects.postgresql as pg
|
@@ -7,7 +7,6 @@ from sqlalchemy.exc import DBAPIError
|
|
7
7
|
from sqlalchemy.orm import Session, sessionmaker
|
8
8
|
|
9
9
|
from . import _serialization
|
10
|
-
from ._dbos_config import DatabaseConfig
|
11
10
|
from ._error import DBOSUnexpectedStepError, DBOSWorkflowConflictIDError
|
12
11
|
from ._schemas.application_database import ApplicationSchema
|
13
12
|
from ._sys_db import StepInfo
|
@@ -31,67 +30,33 @@ class RecordedResult(TypedDict):
|
|
31
30
|
|
32
31
|
class ApplicationDatabase:
|
33
32
|
|
34
|
-
def __init__(
|
35
|
-
|
36
|
-
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
*,
|
36
|
+
database_url: str,
|
37
|
+
engine_kwargs: Dict[str, Any],
|
38
|
+
debug_mode: bool = False,
|
39
|
+
):
|
40
|
+
app_db_url = sa.make_url(database_url).set(drivername="postgresql+psycopg")
|
37
41
|
|
38
42
|
# If the application database does not already exist, create it
|
39
43
|
if not debug_mode:
|
40
|
-
|
41
|
-
"
|
42
|
-
|
43
|
-
password=database["password"],
|
44
|
-
host=database["hostname"],
|
45
|
-
port=database["port"],
|
46
|
-
database="postgres",
|
44
|
+
postgres_db_engine = sa.create_engine(
|
45
|
+
app_db_url.set(database="postgres"),
|
46
|
+
**engine_kwargs,
|
47
47
|
)
|
48
|
-
postgres_db_engine = sa.create_engine(postgres_db_url)
|
49
48
|
with postgres_db_engine.connect() as conn:
|
50
49
|
conn.execution_options(isolation_level="AUTOCOMMIT")
|
51
50
|
if not conn.execute(
|
52
51
|
sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
|
53
|
-
parameters={"db_name":
|
52
|
+
parameters={"db_name": app_db_url.database},
|
54
53
|
).scalar():
|
55
|
-
conn.execute(sa.text(f"CREATE DATABASE {
|
54
|
+
conn.execute(sa.text(f"CREATE DATABASE {app_db_url.database}"))
|
56
55
|
postgres_db_engine.dispose()
|
57
56
|
|
58
|
-
# Create a connection pool for the application database
|
59
|
-
app_db_url = sa.URL.create(
|
60
|
-
"postgresql+psycopg",
|
61
|
-
username=database["username"],
|
62
|
-
password=database["password"],
|
63
|
-
host=database["hostname"],
|
64
|
-
port=database["port"],
|
65
|
-
database=app_db_name,
|
66
|
-
)
|
67
|
-
|
68
|
-
connect_args = {}
|
69
|
-
if (
|
70
|
-
"connectionTimeoutMillis" in database
|
71
|
-
and database["connectionTimeoutMillis"]
|
72
|
-
):
|
73
|
-
connect_args["connect_timeout"] = int(
|
74
|
-
database["connectionTimeoutMillis"] / 1000
|
75
|
-
)
|
76
|
-
|
77
|
-
pool_size = database.get("app_db_pool_size")
|
78
|
-
if pool_size is None:
|
79
|
-
pool_size = 20
|
80
|
-
|
81
|
-
engine_kwargs = database.get("db_engine_kwargs")
|
82
57
|
if engine_kwargs is None:
|
83
58
|
engine_kwargs = {}
|
84
59
|
|
85
|
-
# Respect user-provided values. Otherwise, set defaults.
|
86
|
-
if "pool_size" not in engine_kwargs:
|
87
|
-
engine_kwargs["pool_size"] = pool_size
|
88
|
-
if "max_overflow" not in engine_kwargs:
|
89
|
-
engine_kwargs["max_overflow"] = 0
|
90
|
-
if "pool_timeout" not in engine_kwargs:
|
91
|
-
engine_kwargs["pool_timeout"] = 30
|
92
|
-
if "connect_args" not in engine_kwargs:
|
93
|
-
engine_kwargs["connect_args"] = connect_args
|
94
|
-
|
95
60
|
self.engine = sa.create_engine(
|
96
61
|
app_db_url,
|
97
62
|
**engine_kwargs,
|
dbos/_client.py
CHANGED
@@ -15,7 +15,6 @@ else:
|
|
15
15
|
|
16
16
|
from dbos import _serialization
|
17
17
|
from dbos._dbos import WorkflowHandle, WorkflowHandleAsync
|
18
|
-
from dbos._dbos_config import parse_database_url_to_dbconfig
|
19
18
|
from dbos._error import DBOSException, DBOSNonExistentWorkflowError
|
20
19
|
from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
|
21
20
|
from dbos._serialization import WorkflowInputs
|
@@ -100,11 +99,23 @@ class WorkflowHandleClientAsyncPolling(Generic[R]):
|
|
100
99
|
|
101
100
|
class DBOSClient:
|
102
101
|
def __init__(self, database_url: str, *, system_database: Optional[str] = None):
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
self._sys_db = SystemDatabase(
|
103
|
+
database_url=database_url,
|
104
|
+
engine_kwargs={
|
105
|
+
"pool_timeout": 30,
|
106
|
+
"max_overflow": 0,
|
107
|
+
"pool_size": 2,
|
108
|
+
},
|
109
|
+
sys_db_name=system_database,
|
110
|
+
)
|
111
|
+
self._app_db = ApplicationDatabase(
|
112
|
+
database_url=database_url,
|
113
|
+
engine_kwargs={
|
114
|
+
"pool_timeout": 30,
|
115
|
+
"max_overflow": 0,
|
116
|
+
"pool_size": 2,
|
117
|
+
},
|
118
|
+
)
|
108
119
|
self._db_url = database_url
|
109
120
|
|
110
121
|
def destroy(self) -> None:
|
dbos/_conductor/conductor.py
CHANGED
@@ -2,6 +2,7 @@ import socket
|
|
2
2
|
import threading
|
3
3
|
import time
|
4
4
|
import traceback
|
5
|
+
import uuid
|
5
6
|
from importlib.metadata import version
|
6
7
|
from typing import TYPE_CHECKING, Optional
|
7
8
|
|
@@ -9,6 +10,7 @@ from websockets import ConnectionClosed, ConnectionClosedOK, InvalidStatus
|
|
9
10
|
from websockets.sync.client import connect
|
10
11
|
from websockets.sync.connection import Connection
|
11
12
|
|
13
|
+
from dbos._context import SetWorkflowID
|
12
14
|
from dbos._utils import GlobalParams
|
13
15
|
from dbos._workflow_commands import (
|
14
16
|
get_workflow,
|
@@ -168,10 +170,11 @@ class ConductorWebsocket(threading.Thread):
|
|
168
170
|
)
|
169
171
|
websocket.send(resume_response.to_json())
|
170
172
|
elif msg_type == p.MessageType.RESTART:
|
173
|
+
# TODO: deprecate this message type in favor of Fork
|
171
174
|
restart_message = p.RestartRequest.from_json(message)
|
172
175
|
success = True
|
173
176
|
try:
|
174
|
-
self.dbos.
|
177
|
+
self.dbos.fork_workflow(restart_message.workflow_id, 1)
|
175
178
|
except Exception as e:
|
176
179
|
error_message = f"Exception encountered when restarting workflow {restart_message.workflow_id}: {traceback.format_exc()}"
|
177
180
|
self.dbos.logger.error(error_message)
|
@@ -183,6 +186,34 @@ class ConductorWebsocket(threading.Thread):
|
|
183
186
|
error_message=error_message,
|
184
187
|
)
|
185
188
|
websocket.send(restart_response.to_json())
|
189
|
+
elif msg_type == p.MessageType.FORK_WORKFLOW:
|
190
|
+
fork_message = p.ForkWorkflowRequest.from_json(message)
|
191
|
+
new_workflow_id = fork_message.body["new_workflow_id"]
|
192
|
+
if new_workflow_id is None:
|
193
|
+
new_workflow_id = str(uuid.uuid4())
|
194
|
+
workflow_id = fork_message.body["workflow_id"]
|
195
|
+
start_step = fork_message.body["start_step"]
|
196
|
+
app_version = fork_message.body["application_version"]
|
197
|
+
try:
|
198
|
+
with SetWorkflowID(new_workflow_id):
|
199
|
+
new_handle = self.dbos.fork_workflow(
|
200
|
+
workflow_id,
|
201
|
+
start_step,
|
202
|
+
application_version=app_version,
|
203
|
+
)
|
204
|
+
new_workflow_id = new_handle.workflow_id
|
205
|
+
except Exception as e:
|
206
|
+
error_message = f"Exception encountered when forking workflow {workflow_id} to new workflow {new_workflow_id} on step {start_step}, app version {app_version}: {traceback.format_exc()}"
|
207
|
+
self.dbos.logger.error(error_message)
|
208
|
+
new_workflow_id = None
|
209
|
+
|
210
|
+
fork_response = p.ForkWorkflowResponse(
|
211
|
+
type=p.MessageType.FORK_WORKFLOW,
|
212
|
+
request_id=base_message.request_id,
|
213
|
+
new_workflow_id=new_workflow_id,
|
214
|
+
error_message=error_message,
|
215
|
+
)
|
216
|
+
websocket.send(fork_response.to_json())
|
186
217
|
elif msg_type == p.MessageType.LIST_WORKFLOWS:
|
187
218
|
list_workflows_message = p.ListWorkflowsRequest.from_json(
|
188
219
|
message
|
dbos/_conductor/protocol.py
CHANGED
@@ -17,6 +17,7 @@ class MessageType(str, Enum):
|
|
17
17
|
GET_WORKFLOW = "get_workflow"
|
18
18
|
EXIST_PENDING_WORKFLOWS = "exist_pending_workflows"
|
19
19
|
LIST_STEPS = "list_steps"
|
20
|
+
FORK_WORKFLOW = "fork_workflow"
|
20
21
|
|
21
22
|
|
22
23
|
T = TypeVar("T", bound="BaseMessage")
|
@@ -133,7 +134,6 @@ class WorkflowsOutput:
|
|
133
134
|
AuthenticatedRoles: Optional[str]
|
134
135
|
Input: Optional[str]
|
135
136
|
Output: Optional[str]
|
136
|
-
Request: Optional[str]
|
137
137
|
Error: Optional[str]
|
138
138
|
CreatedAt: Optional[str]
|
139
139
|
UpdatedAt: Optional[str]
|
@@ -167,7 +167,6 @@ class WorkflowsOutput:
|
|
167
167
|
AuthenticatedRoles=roles_str,
|
168
168
|
Input=inputs_str,
|
169
169
|
Output=outputs_str,
|
170
|
-
Request=request_str,
|
171
170
|
Error=error_str,
|
172
171
|
CreatedAt=created_at_str,
|
173
172
|
UpdatedAt=updated_at_str,
|
@@ -263,3 +262,21 @@ class ListStepsRequest(BaseMessage):
|
|
263
262
|
class ListStepsResponse(BaseMessage):
|
264
263
|
output: Optional[List[WorkflowSteps]]
|
265
264
|
error_message: Optional[str] = None
|
265
|
+
|
266
|
+
|
267
|
+
class ForkWorkflowBody(TypedDict):
|
268
|
+
workflow_id: str
|
269
|
+
start_step: int
|
270
|
+
application_version: Optional[str]
|
271
|
+
new_workflow_id: Optional[str]
|
272
|
+
|
273
|
+
|
274
|
+
@dataclass
|
275
|
+
class ForkWorkflowRequest(BaseMessage):
|
276
|
+
body: ForkWorkflowBody
|
277
|
+
|
278
|
+
|
279
|
+
@dataclass
|
280
|
+
class ForkWorkflowResponse(BaseMessage):
|
281
|
+
new_workflow_id: Optional[str]
|
282
|
+
error_message: Optional[str] = None
|
dbos/_dbos.py
CHANGED
@@ -24,8 +24,6 @@ from typing import (
|
|
24
24
|
Tuple,
|
25
25
|
Type,
|
26
26
|
TypeVar,
|
27
|
-
Union,
|
28
|
-
cast,
|
29
27
|
)
|
30
28
|
|
31
29
|
from opentelemetry.trace import Span
|
@@ -64,7 +62,6 @@ from ._registrations import (
|
|
64
62
|
)
|
65
63
|
from ._roles import default_required_roles, required_roles
|
66
64
|
from ._scheduler import ScheduledWorkflow, scheduled
|
67
|
-
from ._schemas.system_database import SystemSchema
|
68
65
|
from ._sys_db import StepInfo, SystemDatabase, WorkflowStatus, reset_system_database
|
69
66
|
from ._tracer import DBOSTracer, dbos_tracer
|
70
67
|
|
@@ -73,7 +70,7 @@ if TYPE_CHECKING:
|
|
73
70
|
from ._kafka import _KafkaConsumerWorkflow
|
74
71
|
from flask import Flask
|
75
72
|
|
76
|
-
from sqlalchemy import
|
73
|
+
from sqlalchemy import make_url
|
77
74
|
from sqlalchemy.orm import Session
|
78
75
|
|
79
76
|
if sys.version_info < (3, 10):
|
@@ -418,11 +415,19 @@ class DBOS:
|
|
418
415
|
dbos_logger.info(f"Application version: {GlobalParams.app_version}")
|
419
416
|
self._executor_field = ThreadPoolExecutor(max_workers=64)
|
420
417
|
self._background_event_loop.start()
|
418
|
+
assert self._config["database_url"] is not None
|
419
|
+
assert self._config["database"]["sys_db_engine_kwargs"] is not None
|
421
420
|
self._sys_db_field = SystemDatabase(
|
422
|
-
self._config["
|
421
|
+
database_url=self._config["database_url"],
|
422
|
+
engine_kwargs=self._config["database"]["sys_db_engine_kwargs"],
|
423
|
+
sys_db_name=self._config["database"]["sys_db_name"],
|
424
|
+
debug_mode=debug_mode,
|
423
425
|
)
|
426
|
+
assert self._config["database"]["db_engine_kwargs"] is not None
|
424
427
|
self._app_db_field = ApplicationDatabase(
|
425
|
-
self._config["
|
428
|
+
database_url=self._config["database_url"],
|
429
|
+
engine_kwargs=self._config["database"]["db_engine_kwargs"],
|
430
|
+
debug_mode=debug_mode,
|
426
431
|
)
|
427
432
|
|
428
433
|
if debug_mode:
|
@@ -530,21 +535,13 @@ class DBOS:
|
|
530
535
|
not self._launched
|
531
536
|
), "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."
|
532
537
|
|
533
|
-
sysdb_name =
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
"postgresql+psycopg",
|
541
|
-
username=self._config["database"]["username"],
|
542
|
-
password=self._config["database"]["password"],
|
543
|
-
host=self._config["database"]["hostname"],
|
544
|
-
port=self._config["database"]["port"],
|
545
|
-
database="postgres",
|
546
|
-
)
|
547
|
-
reset_system_database(postgres_db_url, sysdb_name)
|
538
|
+
sysdb_name = self._config["database"]["sys_db_name"]
|
539
|
+
assert sysdb_name is not None
|
540
|
+
|
541
|
+
assert self._config["database_url"] is not None
|
542
|
+
pg_db_url = make_url(self._config["database_url"]).set(database="postgres")
|
543
|
+
|
544
|
+
reset_system_database(pg_db_url, sysdb_name)
|
548
545
|
|
549
546
|
def _destroy(self) -> None:
|
550
547
|
self._initialized = False
|
dbos/_dbos_config.py
CHANGED
@@ -11,6 +11,7 @@ from sqlalchemy import make_url
|
|
11
11
|
|
12
12
|
from ._error import DBOSInitializationError
|
13
13
|
from ._logger import dbos_logger
|
14
|
+
from ._schemas.system_database import SystemSchema
|
14
15
|
|
15
16
|
DBOS_CONFIG_PATH = "dbos-config.yaml"
|
16
17
|
|
@@ -22,7 +23,6 @@ class DBOSConfig(TypedDict, total=False):
|
|
22
23
|
Attributes:
|
23
24
|
name (str): Application name
|
24
25
|
database_url (str): Database connection string
|
25
|
-
app_db_pool_size (int): Application database pool size
|
26
26
|
sys_db_name (str): System database name
|
27
27
|
sys_db_pool_size (int): System database pool size
|
28
28
|
db_engine_kwargs (Dict[str, Any]): SQLAlchemy engine kwargs (See https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine)
|
@@ -35,7 +35,6 @@ class DBOSConfig(TypedDict, total=False):
|
|
35
35
|
|
36
36
|
name: str
|
37
37
|
database_url: Optional[str]
|
38
|
-
app_db_pool_size: Optional[int]
|
39
38
|
sys_db_name: Optional[str]
|
40
39
|
sys_db_pool_size: Optional[int]
|
41
40
|
db_engine_kwargs: Optional[Dict[str, Any]]
|
@@ -57,49 +56,22 @@ class DatabaseConfig(TypedDict, total=False):
|
|
57
56
|
"""
|
58
57
|
Internal data structure containing the DBOS database configuration.
|
59
58
|
Attributes:
|
60
|
-
app_db_pool_size (int): Application database pool size
|
61
59
|
sys_db_name (str): System database name
|
62
60
|
sys_db_pool_size (int): System database pool size
|
63
61
|
db_engine_kwargs (Dict[str, Any]): SQLAlchemy engine kwargs
|
64
62
|
migrate (List[str]): Migration commands to run on startup
|
65
63
|
"""
|
66
64
|
|
67
|
-
hostname: str # Will be removed in a future version
|
68
|
-
port: int # Will be removed in a future version
|
69
|
-
username: str # Will be removed in a future version
|
70
|
-
password: str # Will be removed in a future version
|
71
|
-
connectionTimeoutMillis: Optional[int] # Will be removed in a future version
|
72
|
-
app_db_name: str # Will be removed in a future version
|
73
|
-
app_db_pool_size: Optional[int]
|
74
65
|
sys_db_name: Optional[str]
|
75
|
-
sys_db_pool_size: Optional[
|
66
|
+
sys_db_pool_size: Optional[
|
67
|
+
int
|
68
|
+
] # For internal use, will be removed in a future version
|
76
69
|
db_engine_kwargs: Optional[Dict[str, Any]]
|
77
|
-
|
78
|
-
ssl_ca: Optional[str] # Will be removed in a future version
|
70
|
+
sys_db_engine_kwargs: Optional[Dict[str, Any]]
|
79
71
|
migrate: Optional[List[str]]
|
80
72
|
rollback: Optional[List[str]] # Will be removed in a future version
|
81
73
|
|
82
74
|
|
83
|
-
def parse_database_url_to_dbconfig(database_url: str) -> DatabaseConfig:
|
84
|
-
db_url = make_url(database_url)
|
85
|
-
db_config = {
|
86
|
-
"hostname": db_url.host,
|
87
|
-
"port": db_url.port or 5432,
|
88
|
-
"username": db_url.username,
|
89
|
-
"password": db_url.password,
|
90
|
-
"app_db_name": db_url.database,
|
91
|
-
}
|
92
|
-
for key, value in db_url.query.items():
|
93
|
-
str_value = value[0] if isinstance(value, tuple) else value
|
94
|
-
if key == "connect_timeout":
|
95
|
-
db_config["connectionTimeoutMillis"] = int(str_value) * 1000
|
96
|
-
elif key == "sslmode":
|
97
|
-
db_config["ssl"] = str_value == "require"
|
98
|
-
elif key == "sslrootcert":
|
99
|
-
db_config["ssl_ca"] = str_value
|
100
|
-
return cast(DatabaseConfig, db_config)
|
101
|
-
|
102
|
-
|
103
75
|
class OTLPExporterConfig(TypedDict, total=False):
|
104
76
|
logsEndpoint: Optional[List[str]]
|
105
77
|
tracesEndpoint: Optional[List[str]]
|
@@ -150,13 +122,8 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
|
|
150
122
|
|
151
123
|
# Database config
|
152
124
|
db_config: DatabaseConfig = {}
|
153
|
-
database_url = config.get("database_url")
|
154
|
-
if database_url:
|
155
|
-
db_config = parse_database_url_to_dbconfig(database_url)
|
156
125
|
if "sys_db_name" in config:
|
157
126
|
db_config["sys_db_name"] = config.get("sys_db_name")
|
158
|
-
if "app_db_pool_size" in config:
|
159
|
-
db_config["app_db_pool_size"] = config.get("app_db_pool_size")
|
160
127
|
if "sys_db_pool_size" in config:
|
161
128
|
db_config["sys_db_pool_size"] = config.get("sys_db_pool_size")
|
162
129
|
if "db_engine_kwargs" in config:
|
@@ -164,6 +131,9 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
|
|
164
131
|
if db_config:
|
165
132
|
translated_config["database"] = db_config
|
166
133
|
|
134
|
+
if "database_url" in config:
|
135
|
+
translated_config["database_url"] = config.get("database_url")
|
136
|
+
|
167
137
|
# Runtime config
|
168
138
|
translated_config["runtimeConfig"] = {"run_admin_server": True}
|
169
139
|
if "admin_port" in config:
|
@@ -306,6 +276,24 @@ def process_config(
|
|
306
276
|
data: ConfigFile,
|
307
277
|
silent: bool = False,
|
308
278
|
) -> ConfigFile:
|
279
|
+
"""
|
280
|
+
If a database_url is provided, pass it as is in the config.
|
281
|
+
|
282
|
+
Else, build a database_url from defaults.
|
283
|
+
|
284
|
+
Also build SQL Alchemy "kwargs" base on user input + defaults.
|
285
|
+
Specifically, db_engine_kwargs takes precedence over app_db_pool_size
|
286
|
+
|
287
|
+
In debug mode, apply overrides from DBOS_DBHOST, DBOS_DBPORT, DBOS_DBUSER, and DBOS_DBPASSWORD.
|
288
|
+
|
289
|
+
Default configuration:
|
290
|
+
- Hostname: localhost
|
291
|
+
- Port: 5432
|
292
|
+
- Username: postgres
|
293
|
+
- Password: $PGPASSWORD
|
294
|
+
- Database name: transformed application name.
|
295
|
+
"""
|
296
|
+
|
309
297
|
if "name" not in data:
|
310
298
|
raise DBOSInitializationError(f"Configuration must specify an application name")
|
311
299
|
|
@@ -323,55 +311,7 @@ def process_config(
|
|
323
311
|
if logs.get("logLevel") is None:
|
324
312
|
logs["logLevel"] = "INFO"
|
325
313
|
|
326
|
-
|
327
|
-
data["database"] = {}
|
328
|
-
|
329
|
-
# database_url takes precedence over database config, but we need to preserve rollback and migrate if they exist
|
330
|
-
migrate = data["database"].get("migrate", False)
|
331
|
-
rollback = data["database"].get("rollback", False)
|
332
|
-
if data.get("database_url"):
|
333
|
-
dbconfig = parse_database_url_to_dbconfig(cast(str, data["database_url"]))
|
334
|
-
if migrate:
|
335
|
-
dbconfig["migrate"] = cast(List[str], migrate)
|
336
|
-
if rollback:
|
337
|
-
dbconfig["rollback"] = cast(List[str], rollback)
|
338
|
-
data["database"] = dbconfig
|
339
|
-
|
340
|
-
if "app_db_name" not in data["database"] or not (data["database"]["app_db_name"]):
|
341
|
-
data["database"]["app_db_name"] = _app_name_to_db_name(data["name"])
|
342
|
-
|
343
|
-
connection_passed_in = data["database"].get("hostname", None) is not None
|
344
|
-
|
345
|
-
dbos_dbport: Optional[int] = None
|
346
|
-
dbport_env = os.getenv("DBOS_DBPORT")
|
347
|
-
if dbport_env:
|
348
|
-
try:
|
349
|
-
dbos_dbport = int(dbport_env)
|
350
|
-
except ValueError:
|
351
|
-
pass
|
352
|
-
|
353
|
-
data["database"]["hostname"] = (
|
354
|
-
os.getenv("DBOS_DBHOST") or data["database"].get("hostname") or "localhost"
|
355
|
-
)
|
356
|
-
|
357
|
-
data["database"]["port"] = dbos_dbport or data["database"].get("port") or 5432
|
358
|
-
data["database"]["username"] = (
|
359
|
-
os.getenv("DBOS_DBUSER") or data["database"].get("username") or "postgres"
|
360
|
-
)
|
361
|
-
data["database"]["password"] = (
|
362
|
-
os.getenv("DBOS_DBPASSWORD")
|
363
|
-
or data["database"].get("password")
|
364
|
-
or os.environ.get("PGPASSWORD")
|
365
|
-
or "dbos"
|
366
|
-
)
|
367
|
-
|
368
|
-
if not data["database"].get("app_db_pool_size"):
|
369
|
-
data["database"]["app_db_pool_size"] = 20
|
370
|
-
if not data["database"].get("sys_db_pool_size"):
|
371
|
-
data["database"]["sys_db_pool_size"] = 20
|
372
|
-
if not data["database"].get("connectionTimeoutMillis"):
|
373
|
-
data["database"]["connectionTimeoutMillis"] = 10000
|
374
|
-
|
314
|
+
# Handle admin server config
|
375
315
|
if not data.get("runtimeConfig"):
|
376
316
|
data["runtimeConfig"] = {
|
377
317
|
"run_admin_server": True,
|
@@ -379,27 +319,118 @@ def process_config(
|
|
379
319
|
elif "run_admin_server" not in data["runtimeConfig"]:
|
380
320
|
data["runtimeConfig"]["run_admin_server"] = True
|
381
321
|
|
322
|
+
isDebugMode = os.getenv("DBOS_DBHOST") is not None
|
323
|
+
|
324
|
+
# Ensure database dict exists
|
325
|
+
data.setdefault("database", {})
|
326
|
+
|
327
|
+
# Database URL resolution
|
328
|
+
connect_timeout = None
|
329
|
+
if data.get("database_url") is not None and data["database_url"] != "":
|
330
|
+
# Parse the db string and check required fields
|
331
|
+
assert data["database_url"] is not None
|
332
|
+
url = make_url(data["database_url"])
|
333
|
+
required_fields = [
|
334
|
+
("username", "Username must be specified in the connection URL"),
|
335
|
+
("password", "Password must be specified in the connection URL"),
|
336
|
+
("host", "Host must be specified in the connection URL"),
|
337
|
+
("database", "Database name must be specified in the connection URL"),
|
338
|
+
]
|
339
|
+
for field_name, error_message in required_fields:
|
340
|
+
field_value = getattr(url, field_name, None)
|
341
|
+
if not field_value:
|
342
|
+
raise DBOSInitializationError(error_message)
|
343
|
+
|
344
|
+
if not data["database"].get("sys_db_name"):
|
345
|
+
assert url.database is not None
|
346
|
+
data["database"]["sys_db_name"] = url.database + SystemSchema.sysdb_suffix
|
347
|
+
|
348
|
+
# Gather connect_timeout from the URL if provided. It should be used in engine kwargs if not provided there (instead of our default)
|
349
|
+
connect_timeout_str = url.query.get("connect_timeout")
|
350
|
+
if connect_timeout_str is not None:
|
351
|
+
assert isinstance(
|
352
|
+
connect_timeout_str, str
|
353
|
+
), "connect_timeout must be a string and defined once in the URL"
|
354
|
+
if connect_timeout_str.isdigit():
|
355
|
+
connect_timeout = int(connect_timeout_str)
|
356
|
+
|
357
|
+
# In debug mode perform env vars overrides
|
358
|
+
if isDebugMode:
|
359
|
+
# Override the username, password, host, and port
|
360
|
+
port_str = os.getenv("DBOS_DBPORT")
|
361
|
+
port = (
|
362
|
+
int(port_str)
|
363
|
+
if port_str is not None and port_str.isdigit()
|
364
|
+
else url.port
|
365
|
+
)
|
366
|
+
data["database_url"] = url.set(
|
367
|
+
username=os.getenv("DBOS_DBUSER", url.username),
|
368
|
+
password=os.getenv("DBOS_DBPASSWORD", url.password),
|
369
|
+
host=os.getenv("DBOS_DBHOST", url.host),
|
370
|
+
port=port,
|
371
|
+
).render_as_string(hide_password=False)
|
372
|
+
else:
|
373
|
+
_app_db_name = _app_name_to_db_name(data["name"])
|
374
|
+
_password = os.environ.get("PGPASSWORD", "dbos")
|
375
|
+
data["database_url"] = (
|
376
|
+
f"postgres://postgres:{_password}@localhost:5432/{_app_db_name}?connect_timeout=10&sslmode=prefer"
|
377
|
+
)
|
378
|
+
if not data["database"].get("sys_db_name"):
|
379
|
+
data["database"]["sys_db_name"] = _app_db_name + SystemSchema.sysdb_suffix
|
380
|
+
assert data["database_url"] is not None
|
381
|
+
|
382
|
+
configure_db_engine_parameters(data["database"], connect_timeout=connect_timeout)
|
383
|
+
|
382
384
|
# Pretty-print where we've loaded database connection information from, respecting the log level
|
383
385
|
if not silent and logs["logLevel"] == "INFO" or logs["logLevel"] == "DEBUG":
|
384
|
-
|
385
|
-
|
386
|
-
if os.getenv("DBOS_DBHOST"):
|
387
|
-
print(
|
388
|
-
f"[bold blue]Loading database connection string from debug environment variables: {conn_string}[/bold blue]"
|
389
|
-
)
|
390
|
-
elif connection_passed_in:
|
391
|
-
print(
|
392
|
-
f"[bold blue]Using database connection string: {conn_string}[/bold blue]"
|
393
|
-
)
|
394
|
-
else:
|
395
|
-
print(
|
396
|
-
f"[bold blue]Using default database connection string: {conn_string}[/bold blue]"
|
397
|
-
)
|
386
|
+
log_url = make_url(data["database_url"]).render_as_string(hide_password=True)
|
387
|
+
print(f"[bold blue]Using database connection string: {log_url}[/bold blue]")
|
398
388
|
|
399
389
|
# Return data as ConfigFile type
|
400
390
|
return data
|
401
391
|
|
402
392
|
|
393
|
+
def configure_db_engine_parameters(
|
394
|
+
data: DatabaseConfig, connect_timeout: Optional[int] = None
|
395
|
+
) -> None:
|
396
|
+
"""
|
397
|
+
Configure SQLAlchemy engine parameters for both user and system databases.
|
398
|
+
|
399
|
+
If provided, sys_db_pool_size will take precedence over user_kwargs for the system db engine.
|
400
|
+
|
401
|
+
Args:
|
402
|
+
data: Configuration dictionary containing database settings
|
403
|
+
"""
|
404
|
+
|
405
|
+
# Configure user database engine parameters
|
406
|
+
app_engine_kwargs: dict[str, Any] = {
|
407
|
+
"pool_timeout": 30,
|
408
|
+
"max_overflow": 0,
|
409
|
+
"pool_size": 20,
|
410
|
+
}
|
411
|
+
# If user-provided kwargs are present, use them instead
|
412
|
+
user_kwargs = data.get("db_engine_kwargs")
|
413
|
+
if user_kwargs is not None:
|
414
|
+
app_engine_kwargs.update(user_kwargs)
|
415
|
+
|
416
|
+
# If user-provided kwargs do not contain connect_timeout, check if their URL did (this function connect_timeout parameter).
|
417
|
+
# Else default to 10
|
418
|
+
if "connect_args" not in app_engine_kwargs:
|
419
|
+
app_engine_kwargs["connect_args"] = {}
|
420
|
+
if "connect_timeout" not in app_engine_kwargs["connect_args"]:
|
421
|
+
app_engine_kwargs["connect_args"]["connect_timeout"] = (
|
422
|
+
connect_timeout if connect_timeout else 10
|
423
|
+
)
|
424
|
+
|
425
|
+
# Configure system database engine parameters. User-provided sys_db_pool_size takes precedence
|
426
|
+
system_engine_kwargs = app_engine_kwargs.copy()
|
427
|
+
if data.get("sys_db_pool_size") is not None:
|
428
|
+
system_engine_kwargs["pool_size"] = data["sys_db_pool_size"]
|
429
|
+
|
430
|
+
data["db_engine_kwargs"] = app_engine_kwargs
|
431
|
+
data["sys_db_engine_kwargs"] = system_engine_kwargs
|
432
|
+
|
433
|
+
|
403
434
|
def _is_valid_app_name(name: str) -> bool:
|
404
435
|
name_len = len(name)
|
405
436
|
if name_len < 3 or name_len > 30:
|
@@ -409,7 +440,7 @@ def _is_valid_app_name(name: str) -> bool:
|
|
409
440
|
|
410
441
|
|
411
442
|
def _app_name_to_db_name(app_name: str) -> str:
|
412
|
-
name = app_name.replace("-", "_")
|
443
|
+
name = app_name.replace("-", "_").replace(" ", "_").lower()
|
413
444
|
return name if not name[0].isdigit() else f"_{name}"
|
414
445
|
|
415
446
|
|
@@ -421,7 +452,7 @@ def set_env_vars(config: ConfigFile) -> None:
|
|
421
452
|
|
422
453
|
def overwrite_config(provided_config: ConfigFile) -> ConfigFile:
|
423
454
|
# Load the DBOS configuration file and force the use of:
|
424
|
-
# 1. The database
|
455
|
+
# 1. The database url provided by DBOS_DATABASE_URL
|
425
456
|
# 2. OTLP traces endpoints (add the config data to the provided config)
|
426
457
|
# 3. Use the application name from the file. This is a defensive measure to ensure the application name is whatever it was registered with in the cloud
|
427
458
|
# 4. Remove admin_port is provided in code
|
@@ -436,22 +467,19 @@ def overwrite_config(provided_config: ConfigFile) -> ConfigFile:
|
|
436
467
|
# Name
|
437
468
|
provided_config["name"] = config_from_file["name"]
|
438
469
|
|
439
|
-
# Database config.
|
470
|
+
# Database config. Expects DBOS_DATABASE_URL to be set
|
440
471
|
if "database" not in provided_config:
|
441
472
|
provided_config["database"] = {}
|
442
|
-
provided_config["database"]["hostname"] = config_from_file["database"]["hostname"]
|
443
|
-
provided_config["database"]["port"] = config_from_file["database"]["port"]
|
444
|
-
provided_config["database"]["username"] = config_from_file["database"]["username"]
|
445
|
-
provided_config["database"]["password"] = config_from_file["database"]["password"]
|
446
|
-
provided_config["database"]["app_db_name"] = config_from_file["database"][
|
447
|
-
"app_db_name"
|
448
|
-
]
|
449
473
|
provided_config["database"]["sys_db_name"] = config_from_file["database"][
|
450
474
|
"sys_db_name"
|
451
475
|
]
|
452
|
-
|
453
|
-
|
454
|
-
|
476
|
+
|
477
|
+
db_url = os.environ.get("DBOS_DATABASE_URL")
|
478
|
+
if db_url is None:
|
479
|
+
raise DBOSInitializationError(
|
480
|
+
"DBOS_DATABASE_URL environment variable is not set. This is required to connect to the database."
|
481
|
+
)
|
482
|
+
provided_config["database_url"] = db_url
|
455
483
|
|
456
484
|
# Telemetry config
|
457
485
|
if "telemetry" not in provided_config or provided_config["telemetry"] is None:
|
dbos/_sys_db.py
CHANGED
@@ -5,7 +5,6 @@ import os
|
|
5
5
|
import re
|
6
6
|
import threading
|
7
7
|
import time
|
8
|
-
import uuid
|
9
8
|
from enum import Enum
|
10
9
|
from typing import (
|
11
10
|
TYPE_CHECKING,
|
@@ -28,11 +27,10 @@ from alembic.config import Config
|
|
28
27
|
from sqlalchemy.exc import DBAPIError
|
29
28
|
from sqlalchemy.sql import func
|
30
29
|
|
31
|
-
from dbos._utils import INTERNAL_QUEUE_NAME
|
30
|
+
from dbos._utils import INTERNAL_QUEUE_NAME
|
32
31
|
|
33
32
|
from . import _serialization
|
34
33
|
from ._context import get_local_dbos_context
|
35
|
-
from ._dbos_config import ConfigFile, DatabaseConfig
|
36
34
|
from ._error import (
|
37
35
|
DBOSConflictingWorkflowError,
|
38
36
|
DBOSDeadLetterQueueError,
|
@@ -227,26 +225,28 @@ _dbos_null_topic = "__null__topic__"
|
|
227
225
|
|
228
226
|
class SystemDatabase:
|
229
227
|
|
230
|
-
def __init__(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
228
|
+
def __init__(
|
229
|
+
self,
|
230
|
+
*,
|
231
|
+
database_url: str,
|
232
|
+
engine_kwargs: Dict[str, Any],
|
233
|
+
sys_db_name: Optional[str] = None,
|
234
|
+
debug_mode: bool = False,
|
235
|
+
):
|
236
|
+
# Set driver
|
237
|
+
system_db_url = sa.make_url(database_url).set(drivername="postgresql+psycopg")
|
238
|
+
# Resolve system database name
|
239
|
+
sysdb_name = sys_db_name
|
240
|
+
if not sysdb_name:
|
241
|
+
assert system_db_url.database is not None
|
242
|
+
sysdb_name = system_db_url.database + SystemSchema.sysdb_suffix
|
243
|
+
system_db_url = system_db_url.set(database=sysdb_name)
|
236
244
|
|
237
245
|
if not debug_mode:
|
238
246
|
# If the system database does not already exist, create it
|
239
|
-
|
240
|
-
"
|
241
|
-
username=database["username"],
|
242
|
-
password=database["password"],
|
243
|
-
host=database["hostname"],
|
244
|
-
port=database["port"],
|
245
|
-
database="postgres",
|
246
|
-
# fills the "application_name" column in pg_stat_activity
|
247
|
-
query={"application_name": f"dbos_transact_{GlobalParams.executor_id}"},
|
247
|
+
engine = sa.create_engine(
|
248
|
+
system_db_url.set(database="postgres"), **engine_kwargs
|
248
249
|
)
|
249
|
-
engine = sa.create_engine(postgres_db_url)
|
250
250
|
with engine.connect() as conn:
|
251
251
|
conn.execution_options(isolation_level="AUTOCOMMIT")
|
252
252
|
if not conn.execute(
|
@@ -257,36 +257,6 @@ class SystemDatabase:
|
|
257
257
|
conn.execute(sa.text(f"CREATE DATABASE {sysdb_name}"))
|
258
258
|
engine.dispose()
|
259
259
|
|
260
|
-
system_db_url = sa.URL.create(
|
261
|
-
"postgresql+psycopg",
|
262
|
-
username=database["username"],
|
263
|
-
password=database["password"],
|
264
|
-
host=database["hostname"],
|
265
|
-
port=database["port"],
|
266
|
-
database=sysdb_name,
|
267
|
-
# fills the "application_name" column in pg_stat_activity
|
268
|
-
query={"application_name": f"dbos_transact_{GlobalParams.executor_id}"},
|
269
|
-
)
|
270
|
-
|
271
|
-
# Create a connection pool for the system database
|
272
|
-
pool_size = database.get("sys_db_pool_size")
|
273
|
-
if pool_size is None:
|
274
|
-
pool_size = 20
|
275
|
-
|
276
|
-
engine_kwargs = database.get("db_engine_kwargs")
|
277
|
-
if engine_kwargs is None:
|
278
|
-
engine_kwargs = {}
|
279
|
-
|
280
|
-
# Respect user-provided values. Otherwise, set defaults.
|
281
|
-
if "pool_size" not in engine_kwargs:
|
282
|
-
engine_kwargs["pool_size"] = pool_size
|
283
|
-
if "max_overflow" not in engine_kwargs:
|
284
|
-
engine_kwargs["max_overflow"] = 0
|
285
|
-
if "pool_timeout" not in engine_kwargs:
|
286
|
-
engine_kwargs["pool_timeout"] = 30
|
287
|
-
if "connect_args" not in engine_kwargs:
|
288
|
-
engine_kwargs["connect_args"] = {"connect_timeout": 10}
|
289
|
-
|
290
260
|
self.engine = sa.create_engine(
|
291
261
|
system_db_url,
|
292
262
|
**engine_kwargs,
|
@@ -1958,7 +1928,10 @@ class SystemDatabase:
|
|
1958
1928
|
def reset_system_database(postgres_db_url: sa.URL, sysdb_name: str) -> None:
|
1959
1929
|
try:
|
1960
1930
|
# Connect to postgres default database
|
1961
|
-
engine = sa.create_engine(
|
1931
|
+
engine = sa.create_engine(
|
1932
|
+
postgres_db_url.set(drivername="postgresql+psycopg"),
|
1933
|
+
connect_args={"connect_timeout": 10},
|
1934
|
+
)
|
1962
1935
|
|
1963
1936
|
with engine.connect() as conn:
|
1964
1937
|
# Set autocommit required for database dropping
|
dbos/cli/cli.py
CHANGED
@@ -27,7 +27,7 @@ from ..cli._github_init import create_template_from_github
|
|
27
27
|
from ._template_init import copy_template, get_project_name, get_templates_directory
|
28
28
|
|
29
29
|
|
30
|
-
def
|
30
|
+
def _get_db_url(db_url: Optional[str]) -> str:
|
31
31
|
database_url = db_url
|
32
32
|
if database_url is None:
|
33
33
|
database_url = os.getenv("DBOS_DATABASE_URL")
|
@@ -35,7 +35,11 @@ def start_client(db_url: Optional[str] = None) -> DBOSClient:
|
|
35
35
|
raise ValueError(
|
36
36
|
"Missing database URL: please set it using the --db-url flag or the DBOS_DATABASE_URL environment variable."
|
37
37
|
)
|
38
|
+
return database_url
|
39
|
+
|
38
40
|
|
41
|
+
def start_client(db_url: Optional[str] = None) -> DBOSClient:
|
42
|
+
database_url = _get_db_url(db_url)
|
39
43
|
return DBOSClient(database_url=database_url)
|
40
44
|
|
41
45
|
|
@@ -206,14 +210,30 @@ def init(
|
|
206
210
|
@app.command(
|
207
211
|
help="Run your database schema migrations using the migration commands in 'dbos-config.yaml'"
|
208
212
|
)
|
209
|
-
def migrate(
|
210
|
-
|
211
|
-
|
212
|
-
typer.
|
213
|
-
"
|
214
|
-
|
215
|
-
|
216
|
-
|
213
|
+
def migrate(
|
214
|
+
db_url: Annotated[
|
215
|
+
typing.Optional[str],
|
216
|
+
typer.Option(
|
217
|
+
"--db-url",
|
218
|
+
"-D",
|
219
|
+
help="Your DBOS application database URL",
|
220
|
+
),
|
221
|
+
] = None,
|
222
|
+
sys_db_name: Annotated[
|
223
|
+
typing.Optional[str],
|
224
|
+
typer.Option(
|
225
|
+
"--sys-db-name",
|
226
|
+
"-s",
|
227
|
+
help="Specify the name of the system database to reset",
|
228
|
+
),
|
229
|
+
] = None,
|
230
|
+
) -> None:
|
231
|
+
config = load_config(run_process_config=False, silent=True)
|
232
|
+
connection_string = _get_db_url(db_url)
|
233
|
+
app_db_name = sa.make_url(connection_string).database
|
234
|
+
assert app_db_name is not None, "Database name is required in URL"
|
235
|
+
if sys_db_name is None:
|
236
|
+
sys_db_name = app_db_name + SystemSchema.sysdb_suffix
|
217
237
|
|
218
238
|
typer.echo(f"Starting schema migration for database {app_db_name}")
|
219
239
|
|
@@ -221,8 +241,23 @@ def migrate() -> None:
|
|
221
241
|
app_db = None
|
222
242
|
sys_db = None
|
223
243
|
try:
|
224
|
-
sys_db = SystemDatabase(
|
225
|
-
|
244
|
+
sys_db = SystemDatabase(
|
245
|
+
database_url=connection_string,
|
246
|
+
engine_kwargs={
|
247
|
+
"pool_timeout": 30,
|
248
|
+
"max_overflow": 0,
|
249
|
+
"pool_size": 2,
|
250
|
+
},
|
251
|
+
sys_db_name=sys_db_name,
|
252
|
+
)
|
253
|
+
app_db = ApplicationDatabase(
|
254
|
+
database_url=connection_string,
|
255
|
+
engine_kwargs={
|
256
|
+
"pool_timeout": 30,
|
257
|
+
"max_overflow": 0,
|
258
|
+
"pool_size": 2,
|
259
|
+
},
|
260
|
+
)
|
226
261
|
except Exception as e:
|
227
262
|
typer.echo(f"DBOS system schema migration failed: {e}")
|
228
263
|
finally:
|
@@ -234,6 +269,9 @@ def migrate() -> None:
|
|
234
269
|
# Next, run any custom migration commands specified in the configuration
|
235
270
|
typer.echo("Executing migration commands from 'dbos-config.yaml'")
|
236
271
|
try:
|
272
|
+
# handle the case where the user has not specified migrations commands
|
273
|
+
if "database" not in config:
|
274
|
+
config["database"] = {}
|
237
275
|
migrate_commands = (
|
238
276
|
config["database"]["migrate"]
|
239
277
|
if "migrate" in config["database"] and config["database"]["migrate"]
|
@@ -283,17 +321,21 @@ def reset(
|
|
283
321
|
typer.echo("Operation cancelled.")
|
284
322
|
raise typer.Exit()
|
285
323
|
try:
|
286
|
-
|
287
|
-
|
324
|
+
# Make a SA url out of the user-provided URL and verify a database name is present
|
325
|
+
database_url = _get_db_url(db_url)
|
326
|
+
pg_db_url = sa.make_url(database_url)
|
288
327
|
assert (
|
289
328
|
pg_db_url.database is not None
|
290
329
|
), f"Database name is required in URL: {pg_db_url.render_as_string(hide_password=True)}"
|
330
|
+
# Resolve system database name
|
291
331
|
sysdb_name = (
|
292
332
|
sys_db_name
|
293
333
|
if sys_db_name
|
294
334
|
else (pg_db_url.database + SystemSchema.sysdb_suffix)
|
295
335
|
)
|
296
|
-
reset_system_database(
|
336
|
+
reset_system_database(
|
337
|
+
postgres_db_url=pg_db_url.set(database="postgres"), sysdb_name=sysdb_name
|
338
|
+
)
|
297
339
|
except sa.exc.SQLAlchemyError as e:
|
298
340
|
typer.echo(f"Error resetting system database: {str(e)}")
|
299
341
|
return
|
dbos/dbos-config.schema.json
CHANGED
@@ -70,16 +70,6 @@
|
|
70
70
|
"description": "If using SSL/TLS to securely connect to a database, path to an SSL root certificate file. DEPRECATED: Use database_url instead",
|
71
71
|
"deprecated": true
|
72
72
|
},
|
73
|
-
"app_db_client": {
|
74
|
-
"type": "string",
|
75
|
-
"description": "Specify the database client to use to connect to the application database",
|
76
|
-
"enum": [
|
77
|
-
"pg-node",
|
78
|
-
"prisma",
|
79
|
-
"typeorm",
|
80
|
-
"knex"
|
81
|
-
]
|
82
|
-
},
|
83
73
|
"migrate": {
|
84
74
|
"type": "array",
|
85
75
|
"description": "Specify a list of user DB migration commands to run"
|
@@ -1,20 +1,20 @@
|
|
1
|
-
dbos-0.28.
|
2
|
-
dbos-0.28.
|
3
|
-
dbos-0.28.
|
4
|
-
dbos-0.28.
|
1
|
+
dbos-0.28.0a15.dist-info/METADATA,sha256=zpjEzcFWmk2kxievyVaZBkvakcjpprw6H8842nJMuPE,13269
|
2
|
+
dbos-0.28.0a15.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
dbos-0.28.0a15.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
4
|
+
dbos-0.28.0a15.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
5
5
|
dbos/__init__.py,sha256=NssPCubaBxdiKarOWa-wViz1hdJSkmBGcpLX_gQ4NeA,891
|
6
6
|
dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
|
7
7
|
dbos/_admin_server.py,sha256=A_28_nJ1nBBYDmCxtklJR9O2v14JRMtD1rAo_D4y8Kc,9764
|
8
|
-
dbos/_app_db.py,sha256=
|
8
|
+
dbos/_app_db.py,sha256=56jqU0oxcIkMscOg6DxC8dZ0uEJwG7iMu3aXYs2u66k,10428
|
9
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
10
|
-
dbos/_client.py,sha256=
|
11
|
-
dbos/_conductor/conductor.py,sha256=
|
12
|
-
dbos/_conductor/protocol.py,sha256=
|
10
|
+
dbos/_client.py,sha256=aPOAVWsH7uovSIsgJRamksra9y9-BVW1Jw4Gg5WjuZA,14114
|
11
|
+
dbos/_conductor/conductor.py,sha256=o0IaZjwnZ2TOyHeP2H4iSX6UnXLXQ4uODvWAKD9hHMs,21703
|
12
|
+
dbos/_conductor/protocol.py,sha256=wgOFZxmS81bv0WCB9dAyg0s6QzldpzVKQDoSPeaX0Ws,6967
|
13
13
|
dbos/_context.py,sha256=Ly1CXF1nWxICQgIpDZSaONGlz1yERBs63gqmR-yqCzM,24476
|
14
14
|
dbos/_core.py,sha256=UDpSgRA9m_YuViNXR9tVgNFLC-zxKZPxjlkj2a-Kj00,48317
|
15
15
|
dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
|
16
|
-
dbos/_dbos.py,sha256=
|
17
|
-
dbos/_dbos_config.py,sha256=
|
16
|
+
dbos/_dbos.py,sha256=7fQPKfaePD3HwxSjBhziJcVd2heLKefe92skgmuHr34,46275
|
17
|
+
dbos/_dbos_config.py,sha256=IufNrIC-M2xSNTXyT_KXlEdfB3j03pPLv_nE0fEq4_U,20955
|
18
18
|
dbos/_debug.py,sha256=MNlQVZ6TscGCRQeEEL0VE8Uignvr6dPeDDDefS3xgIE,1823
|
19
19
|
dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
|
20
20
|
dbos/_error.py,sha256=EN4eVBjMT3k7O7hfqJl6mIf4sxWPsiAOM086yhcGH_g,8012
|
@@ -47,7 +47,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
|
48
48
|
dbos/_schemas/system_database.py,sha256=3Z0L72bOgHnusK1hBaETWU9RfiLBP0QnS-fdu41i0yY,5835
|
49
49
|
dbos/_serialization.py,sha256=bWuwhXSQcGmiazvhJHA5gwhrRWxtmFmcCFQSDJnqqkU,3666
|
50
|
-
dbos/_sys_db.py,sha256=
|
50
|
+
dbos/_sys_db.py,sha256=VJ-wLjukSVMXx2EGyc3ZTL9GFDpHHdFU4iXRSFobrRU,81229
|
51
51
|
dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
|
52
52
|
dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
53
|
dbos/_templates/dbos-db-starter/__package/main.py.dbos,sha256=aQnBPSSQpkB8ERfhf7gB7P9tsU6OPKhZscfeh0yiaD8,2702
|
@@ -63,8 +63,8 @@ dbos/_utils.py,sha256=UbpMYRBSyvJqdXeWAnfSw8xXM1R1mfnyl1oTunhEjJM,513
|
|
63
63
|
dbos/_workflow_commands.py,sha256=2E8FRUv_nLYkpBTwfhh_ELhySYpMrm8qGB9J44g6DSE,3872
|
64
64
|
dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
|
65
65
|
dbos/cli/_template_init.py,sha256=7JBcpMqP1r2mfCnvWatu33z8ctEGHJarlZYKgB83cXE,2972
|
66
|
-
dbos/cli/cli.py,sha256=
|
67
|
-
dbos/dbos-config.schema.json,sha256=
|
66
|
+
dbos/cli/cli.py,sha256=YPXZyAD3GIh1cw_kBTAcJxUGO6OgBHWhjQLVe66AY8k,20143
|
67
|
+
dbos/dbos-config.schema.json,sha256=CjaspeYmOkx6Ip_pcxtmfXJTn_YGdSx_0pcPBF7KZmo,6060
|
68
68
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
69
69
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
70
|
-
dbos-0.28.
|
70
|
+
dbos-0.28.0a15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|