improve error handling + add logs in python sdk · vectorize-io/pg0@b6cb333 · GitHub
Skip to content

Commit b6cb333

Browse files
committed
improve error handling + add logs in python sdk
1 parent 7094673 commit b6cb333

5 files changed

Lines changed: 157 additions & 88 deletions

File tree

‎README.md‎

Lines changed: 14 additions & 3 deletions

‎sdk/python/pg0/__init__.py‎

Lines changed: 56 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@
2929

3030
__version__ = "0.1.0"
3131

32-
# GitHub repo for pg0 releases
33-
PG0_REPO = "vectorize-io/pg0"
34-
INSTALL_SCRIPT_URL = f"https://raw.githubusercontent.com/{PG0_REPO}/main/install.sh"
35-
3632

3733
class Pg0Error(Exception):
3834
"""Base exception for pg0 errors."""
@@ -102,72 +98,8 @@ def _get_install_dir() -> Path:
10298
return Path.home() / ".local" / "bin"
10399

104100

105-
def install(force: bool = False) -> Path:
106-
"""
107-
Install the pg0 binary using the official install script.
108-
109-
Note: If the package was installed from a platform-specific wheel,
110-
the binary is already bundled and this function returns immediately.
111-
112-
Args:
113-
force: Force reinstall even if already installed
114-
115-
Returns:
116-
Path to the installed binary
117-
"""
118-
# Check for bundled binary first (from platform-specific wheel)
119-
bundled = _get_bundled_binary()
120-
if bundled and not force:
121-
return bundled
122-
123-
install_dir = _get_install_dir()
124-
binary_name = "pg0.exe" if sys.platform == "win32" else "pg0"
125-
binary_path = install_dir / binary_name
126-
127-
# Check if already installed externally
128-
if binary_path.exists() and not force:
129-
return binary_path
130-
131-
# Use the official install script which handles:
132-
# - Platform detection (including old glibc fallback to musl)
133-
# - Intel Mac Rosetta handling
134-
# - Proper binary naming
135-
if sys.platform == "win32":
136-
# Windows: download directly since bash isn't available
137-
raise Pg0NotFoundError(
138-
"Auto-install not supported on Windows. "
139-
"Please download pg0 manually from https://github.com/vectorize-io/pg0/releases"
140-
)
141-
142-
print("Installing pg0 using official install script...")
143-
try:
144-
result = subprocess.run(
145-
["bash", "-c", f"curl -fsSL {INSTALL_SCRIPT_URL} | bash"],
146-
capture_output=True,
147-
text=True,
148-
timeout=120,
149-
)
150-
if result.returncode != 0:
151-
raise Pg0NotFoundError(f"Install script failed: {result.stderr}")
152-
153-
# Verify installation
154-
if binary_path.exists():
155-
return binary_path
156-
157-
# Check if installed to a different location
158-
path = shutil.which("pg0")
159-
if path:
160-
return Path(path)
161-
162-
raise Pg0NotFoundError("Install script succeeded but pg0 binary not found")
163-
except subprocess.TimeoutExpired:
164-
raise Pg0NotFoundError("Install script timed out")
165-
except FileNotFoundError:
166-
raise Pg0NotFoundError("bash not found - please install pg0 manually")
167-
168-
169101
def _find_pg0() -> str:
170-
"""Find the pg0 binary, installing if necessary."""
102+
"""Find the pg0 binary or raise an error if not found."""
171103
# Check for bundled binary first (from platform-specific wheel)
172104
bundled = _get_bundled_binary()
173105
if bundled:
@@ -178,17 +110,20 @@ def _find_pg0() -> str:
178110
if path:
179111
return path
180112

181-
# Check our install location
113+
# Check common install location
182114
install_dir = _get_install_dir()
183115
binary_name = "pg0.exe" if sys.platform == "win32" else "pg0"
184116
binary_path = install_dir / binary_name
185117

186118
if binary_path.exists():
187119
return str(binary_path)
188120

189-
# Auto-install as fallback
190-
installed_path = install()
191-
return str(installed_path)
121+
# No binary found
122+
raise Pg0NotFoundError(
123+
"pg0 binary not found. Install it with:\n"
124+
" curl -fsSL https://raw.githubusercontent.com/vectorize-io/pg0/main/install.sh | bash\n"
125+
"Or download from: https://github.com/vectorize-io/pg0/releases"
126+
)
192127

193128

194129
def _run_pg0(*args: str, check: bool = True) -> subprocess.CompletedProcess:
@@ -219,21 +154,21 @@ class Pg0:
219154
220155
Args:
221156
name: Instance name (allows multiple instances)
222-
port: Port to listen on
157+
port: Port to listen on (None = auto-select available port)
223158
username: Database username
224159
password: Database password
225160
database: Database name
226161
data_dir: Custom data directory
227162
config: Dict of PostgreSQL configuration options
228163
229164
Example:
230-
# Simple usage
165+
# Simple usage (auto-selects available port)
231166
pg = Pg0()
232167
pg.start()
233168
print(pg.uri)
234169
pg.stop()
235170
236-
# Context manager
171+
# Context manager with specific port
237172
with Pg0(port=5433, database="myapp") as pg:
238173
print(pg.uri)
239174
@@ -244,7 +179,7 @@ class Pg0:
244179
def __init__(
245180
self,
246181
name: str = "default",
247-
port: int = 5432,
182+
port: Optional[int] = None,
248183
username: str = "postgres",
249184
password: str = "postgres",
250185
database: str = "postgres",
@@ -273,12 +208,14 @@ def start(self) -> InstanceInfo:
273208
args = [
274209
"start",
275210
"--name", self.name,
276-
"--port", str(self.port),
277211
"--username", self.username,
278212
"--password", self.password,
279213
"--database", self.database,
280214
]
281215

216+
if self.port is not None:
217+
args.extend(["--port", str(self.port)])
218+
282219
if self.data_dir:
283220
args.extend(["--data-dir", self.data_dir])
284221

@@ -364,6 +301,25 @@ def execute(self, sql: str) -> str:
364301
result = self.psql("-c", sql)
365302
return result.stdout
366303

304+
def logs(self, lines: Optional[int] = None) -> str:
305+
"""
306+
Get PostgreSQL logs for this instance.
307+
308+
Args:
309+
lines: Number of lines to return (None = all logs)
310+
311+
Returns:
312+
Log content as string
313+
314+
Example:
315+
print(pg.logs(50)) # Last 50 lines
316+
"""
317+
args = ["logs", "--name", self.name]
318+
if lines is not None:
319+
args.extend(["-n", str(lines)])
320+
result = _run_pg0(*args, check=False)
321+
return result.stdout
322+
367323
def __enter__(self) -> "Pg0":
368324
"""Context manager entry - starts PostgreSQL."""
369325
self.start()
@@ -407,7 +363,7 @@ def list_extensions() -> list[str]:
407363

408364
def start(
409365
name: str = "default",
410-
port: int = 5432,
366+
port: Optional[int] = None,
411367
username: str = "postgres",
412368
password: str = "postgres",
413369
database: str = "postgres",
@@ -418,7 +374,7 @@ def start(
418374
419375
Args:
420376
name: Instance name
421-
port: Port to listen on
377+
port: Port to listen on (None = auto-select available port)
422378
username: Database username
423379
password: Database password
424380
database: Database name
@@ -428,7 +384,7 @@ def start(
428384
InstanceInfo with connection details
429385
430386
Example:
431-
info = pg0.start(port=5433, shared_buffers="512MB")
387+
info = pg0.start(shared_buffers="512MB") # auto-selects port
432388
print(info.uri)
433389
"""
434390
pg = Pg0(
@@ -486,6 +442,24 @@ def info(name: str = "default") -> InstanceInfo:
486442
return InstanceInfo.from_dict(data)
487443

488444

445+
def logs(name: str = "default", lines: Optional[int] = None) -> str:
446+
"""
447+
Get PostgreSQL logs for an instance (convenience function).
448+
449+
Args:
450+
name: Instance name
451+
lines: Number of lines to return (None = all logs)
452+
453+
Returns:
454+
Log content as string
455+
"""
456+
args = ["logs", "--name", name]
457+
if lines is not None:
458+
args.extend(["-n", str(lines)])
459+
result = _run_pg0(*args, check=False)
460+
return result.stdout
461+
462+
489463
# Keep PostgreSQL as alias for backwards compatibility
490464
PostgreSQL = Pg0
491465

@@ -498,12 +472,12 @@ def info(name: str = "default") -> InstanceInfo:
498472
"Pg0NotFoundError",
499473
"Pg0NotRunningError",
500474
"Pg0AlreadyRunningError",
501-
"install",
502475
"list_instances",
503476
"list_extensions",
504477
"start",
505478
"stop",
506479
"drop",
507480
"info",
481+
"logs",
508482
"_get_bundled_binary", # for testing
509483
]

‎sdk/python/tests/test_pg0.py‎

Lines changed: 44 additions & 1 deletion

‎sdk/python/uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)