dbos 0.26.0a8__py3-none-any.whl → 0.26.0a10__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/_db_wizard.py DELETED
@@ -1,220 +0,0 @@
1
- import json
2
- import os
3
- import time
4
- from typing import TYPE_CHECKING, Optional, TypedDict, cast
5
-
6
- import docker # type: ignore
7
- import typer
8
- import yaml
9
- from rich import print
10
- from sqlalchemy import URL, create_engine, text
11
-
12
- if TYPE_CHECKING:
13
- from ._dbos_config import ConfigFile
14
-
15
- from ._cloudutils.cloudutils import get_cloud_credentials
16
- from ._cloudutils.databases import choose_database, get_user_db_credentials
17
- from ._error import DBOSInitializationError
18
- from ._logger import dbos_logger
19
-
20
- DB_CONNECTION_PATH = os.path.join(".dbos", "db_connection")
21
-
22
-
23
- class DatabaseConnection(TypedDict):
24
- hostname: Optional[str]
25
- port: Optional[int]
26
- username: Optional[str]
27
- password: Optional[str]
28
- local_suffix: Optional[bool]
29
-
30
-
31
- def db_wizard(config: "ConfigFile") -> "ConfigFile":
32
- """Checks database connectivity and helps the user start a database if needed
33
-
34
- First, check connectivity to the database configured in the provided `config` object.
35
- If it fails:
36
- - Return an error if the connection failed due to incorrect credentials.
37
- - Return an error if it detects a non-default configuration.
38
- - Otherwise assume the configured database is not running and guide the user through setting it up.
39
-
40
- The wizard will first attempt to start a local Postgres instance using Docker.
41
- If Docker is not available, it will prompt the user to connect to a DBOS Cloud database.
42
-
43
- Finally, if a database was configured, its connection details will be saved in the local `.dbos/db_connection` file.
44
- """
45
- # 1. Check the connectivity to the database. Return if successful. If cannot connect, continue to the following steps.
46
- db_connection_error = _check_db_connectivity(config)
47
- if db_connection_error is None:
48
- return config
49
-
50
- # 2. If the error is due to password authentication or the configuration is non-default, surface the error and exit.
51
- error_str = str(db_connection_error)
52
- dbos_logger.debug(f"Error connecting to Postgres: {error_str}")
53
- if (
54
- "password authentication failed" in error_str
55
- or "28P01" in error_str
56
- or "no password supplied" in error_str
57
- ):
58
- raise DBOSInitializationError(
59
- f"Could not connect to Postgres: password authentication failed: {db_connection_error}"
60
- )
61
-
62
- # If the database config is not the default one, surface the error and exit.
63
- db_config = config["database"] # FIXME: what if database is not in config?
64
- if (
65
- db_config["hostname"] != "localhost"
66
- or db_config["port"] != 5432
67
- or db_config["username"] != "postgres"
68
- ):
69
- raise DBOSInitializationError(
70
- f"Could not connect to the database. Exception: {db_connection_error}"
71
- )
72
-
73
- print("[yellow]Postgres not detected locally[/yellow]")
74
-
75
- # 3. If the database config is the default one, check if the user has Docker properly installed.
76
- print("Attempting to start Postgres via Docker")
77
- has_docker = _check_docker_installed()
78
-
79
- # 4. If Docker is installed, prompt the user to start a local Docker based Postgres, and then set the PGPASSWORD to 'dbos' and try to connect to the database.
80
- docker_started = False
81
- if has_docker:
82
- docker_started = _start_docker_postgres(config)
83
- else:
84
- print("[yellow]Docker not detected locally[/yellow]")
85
-
86
- # 5. If no Docker, then prompt the user to log in to DBOS Cloud and provision a DB there. Wait for the remote DB to be ready, and then create a copy of the original config file, and then load the remote connection string to the local config file.
87
- if not docker_started:
88
- print("Attempting to connect to Postgres via DBOS Cloud")
89
- cred = get_cloud_credentials()
90
- db = choose_database(cred)
91
- if db is None:
92
- raise DBOSInitializationError("Error connecting to cloud database")
93
- config["database"]["hostname"] = db.HostName
94
- config["database"]["port"] = db.Port
95
- if db.SupabaseReference is not None:
96
- config["database"]["username"] = f"postgres.{db.SupabaseReference}"
97
- supabase_password = typer.prompt(
98
- "Enter your Supabase database password", hide_input=True
99
- )
100
- config["database"]["password"] = supabase_password
101
- else:
102
- config["database"]["username"] = db.DatabaseUsername
103
- db_credentials = get_user_db_credentials(cred, db.PostgresInstanceName)
104
- config["database"]["password"] = db_credentials.Password
105
- config["database"]["local_suffix"] = True
106
-
107
- # Verify these new credentials work
108
- db_connection_error = _check_db_connectivity(config)
109
- if db_connection_error is not None:
110
- raise DBOSInitializationError(
111
- f"Could not connect to the database. Exception: {db_connection_error}"
112
- )
113
-
114
- # 6. Save the config to the database connection file
115
- updated_connection = DatabaseConnection(
116
- hostname=config["database"]["hostname"],
117
- port=config["database"]["port"],
118
- username=config["database"]["username"],
119
- password=config["database"]["password"],
120
- local_suffix=config["database"]["local_suffix"],
121
- )
122
- save_db_connection(updated_connection)
123
- return config
124
-
125
-
126
- def _start_docker_postgres(config: "ConfigFile") -> bool:
127
- print("Starting a Postgres Docker container...")
128
- client = docker.from_env()
129
- pg_data = "/var/lib/postgresql/data"
130
- container_name = "dbos-db"
131
- client.containers.run(
132
- image="pgvector/pgvector:pg16",
133
- detach=True,
134
- environment={
135
- "POSTGRES_PASSWORD": config["database"]["password"],
136
- "PGDATA": pg_data,
137
- },
138
- volumes={pg_data: {"bind": pg_data, "mode": "rw"}},
139
- ports={"5432/tcp": config["database"]["port"]},
140
- name=container_name,
141
- remove=True,
142
- )
143
-
144
- container = client.containers.get(container_name)
145
- attempts = 30
146
- while attempts > 0:
147
- if attempts % 5 == 0:
148
- print("Waiting for Postgres Docker container to start...")
149
- try:
150
- res = container.exec_run("psql -U postgres -c 'SELECT 1;'")
151
- if res.exit_code != 0:
152
- attempts -= 1
153
- time.sleep(1)
154
- continue
155
- print("[green]Postgres Docker container started successfully![/green]")
156
- break
157
- except:
158
- attempts -= 1
159
- time.sleep(1)
160
-
161
- if attempts == 0:
162
- print("[yellow]Failed to start Postgres Docker container.[/yellow]")
163
- return False
164
-
165
- return True
166
-
167
-
168
- def _check_docker_installed() -> bool:
169
- # Check if Docker is installed
170
- try:
171
- client = docker.from_env()
172
- client.ping()
173
- except Exception:
174
- return False
175
- return True
176
-
177
-
178
- def _check_db_connectivity(config: "ConfigFile") -> Optional[Exception]:
179
- postgres_db_url = URL.create(
180
- "postgresql+psycopg",
181
- username=config["database"]["username"],
182
- password=config["database"]["password"],
183
- host=config["database"]["hostname"],
184
- port=config["database"]["port"],
185
- database="postgres",
186
- query={"connect_timeout": "2"},
187
- )
188
- postgres_db_engine = create_engine(postgres_db_url)
189
- try:
190
- with postgres_db_engine.connect() as conn:
191
- conn.execute(text("SELECT 1")).scalar()
192
- except Exception as e:
193
- return e
194
- finally:
195
- postgres_db_engine.dispose()
196
-
197
- return None
198
-
199
-
200
- def load_db_connection() -> DatabaseConnection:
201
- try:
202
- with open(DB_CONNECTION_PATH, "r") as f:
203
- data = json.load(f)
204
- return DatabaseConnection(
205
- hostname=data.get("hostname", None),
206
- port=data.get("port", None),
207
- username=data.get("username", None),
208
- password=data.get("password", None),
209
- local_suffix=data.get("local_suffix", None),
210
- )
211
- except:
212
- return DatabaseConnection(
213
- hostname=None, port=None, username=None, password=None, local_suffix=None
214
- )
215
-
216
-
217
- def save_db_connection(connection: DatabaseConnection) -> None:
218
- os.makedirs(".dbos", exist_ok=True)
219
- with open(DB_CONNECTION_PATH, "w") as f:
220
- json.dump(connection, f)