Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,44 @@ jobs:
- name: Check formatting
run: |
uv run --python ${{ matrix.python-version }} black --check .
uv run --python ${{ matrix.python-version }} isort --check-only .
uv run --python ${{ matrix.python-version }} isort --check-only .

test-osx:
name: macOS Python 3.12
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6

- name: Install Python 3.12
run: uv python install 3.12

- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Run tests
run: uv run --python 3.12 pytest

test-windows:
name: Windows Python 3.12
runs-on: windows-latest

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up uv
uses: astral-sh/setup-uv@v6

- name: Install Python 3.12
run: uv python install 3.12

- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Run tests
run: uv run --python 3.12 pytest
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ build:
python: "3.12"
commands:
- pip install .[docs]
- python -m pdoc --docformat google --output-directory $READTHEDOCS_OUTPUT/html tinypg
- python -m pdoc --docformat google --output-directory $READTHEDOCS_OUTPUT/html --logo https://iili.io/Klv1Zcx.md.png tinypg
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

[![Klv1Zcx.md.png](https://iili.io/Klv1Zcx.md.png)](https://freeimage.host/i/Klv1Zcx)
![logo](https://iili.io/Klv1Zcx.md.png)

# TinyPG

Expand All @@ -9,7 +9,7 @@ A Python package for creating ephemeral PostgreSQL databases, inspired by [ephem

TinyPG provides a clean Python API for creating temporary PostgreSQL databases for development and testing. It's designed to be self-contained and work without requiring system-wide PostgreSQL installation.

**Currently only tested on linux, but should work on OSX and Windows hopefully**
**Works on Linux, OSX and Windows**

## Features

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand Down
1 change: 0 additions & 1 deletion src/tinypg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""
[![Klv1Zcx.md.png](https://iili.io/Klv1Zcx.md.png)](https://freeimage.host/i/Klv1Zcx)

TinyPG: Ephemeral PostgreSQL databases for Python development and testing.

Expand Down
29 changes: 22 additions & 7 deletions src/tinypg/binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
class PostgreSQLBinaries:
"""Manages PostgreSQL binary installation and versioning."""

PLATFORM = platform.machine().lower()
SYSTEM = platform.system().lower()

# PostgreSQL versions supported (from pg-embed)
SUPPORTED_VERSIONS = {
"17": "17.2.0",
Expand Down Expand Up @@ -50,7 +53,7 @@ def __init__(self, cache_dir: Optional[Path] = None):

def _detect_os(self) -> str:
"""Detect the operating system."""
system = platform.system().lower()
system = self.SYSTEM
if system == "darwin":
return "darwin"
elif system == "windows":
Expand All @@ -63,7 +66,7 @@ def _detect_os(self) -> str:

def _detect_arch(self) -> str:
"""Detect the CPU architecture."""
machine = platform.machine().lower()
machine = self.PLATFORM
if machine in ["x86_64", "amd64"]:
return "amd64"
elif machine in ["i386", "i686"]:
Expand All @@ -81,6 +84,17 @@ def _get_platform_string(self) -> str:
"""Get the platform string for binary downloads."""
return f"{self.os_name}-{self.arch}"

@staticmethod
def _get_binary_path(
manager: "PostgreSQLBinaries", version: str, binary_name: str
) -> str:
install_dir = manager._get_install_dir(version)
binary_path = install_dir / "bin" / binary_name

if manager.SYSTEM == "windows":
binary_path = binary_path.with_suffix(".exe")
return binary_path

@classmethod
def ensure_version(cls, version: str) -> Path:
"""
Expand Down Expand Up @@ -142,9 +156,7 @@ def get_binary_path(cls, binary_name: str, version: str = None) -> Path:
return Path(system_binary)

# Check our installation
install_dir = manager._get_install_dir(version)
binary_path = install_dir / "bin" / binary_name

binary_path = cls._get_binary_path(manager, version, binary_name)
if binary_path.exists() and os.access(binary_path, os.X_OK):
return binary_path

Expand Down Expand Up @@ -311,8 +323,8 @@ def _is_version_installed(self, version: str) -> bool:
install_dir = self._get_install_dir(version)

# Check if all required binaries exist
for binary in self.REQUIRED_BINARIES:
binary_path = install_dir / "bin" / binary
for binary_name in self.REQUIRED_BINARIES:
binary_path = self._get_binary_path(self, version, binary_name)
if not (binary_path.exists() and os.access(binary_path, os.X_OK)):
return False

Expand Down Expand Up @@ -366,6 +378,9 @@ def _verify_installation(self, install_dir: Path) -> None:

for binary in self.REQUIRED_BINARIES:
binary_path = bin_dir / binary
if self.SYSTEM == "windows":
binary_path = binary_path.with_suffix(".exe")

if not (binary_path.exists() and os.access(binary_path, os.X_OK)):
raise BinaryNotFoundError(f"Binary {binary} not found in installation")

Expand Down
12 changes: 8 additions & 4 deletions src/tinypg/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from .config import TinyPGConfig
from .exceptions import (
DatabaseStartError,
DatabaseTimeoutError,
InitDBError,
ProcessError,
)
Expand Down Expand Up @@ -210,8 +209,13 @@ def load_sql_file(self, file_path: Path) -> None:
def _initialize_database(self) -> Path:
"""Initialize a new PostgreSQL database cluster."""
# Create temporary directory
self._temp_dir = tempfile.mkdtemp(prefix="tinypg.")
temp_path = Path(self._temp_dir)
is_windows = PostgreSQLBinaries.SYSTEM == "windows"
sep = "-" if is_windows else "."
if is_windows:
temp_path = Path(tempfile.gettempdir()) / sep / tempfile.gettempprefix()
else:
self._temp_dir = tempfile.mkdtemp(prefix=f"tinypg{sep}")
temp_path = Path(self._temp_dir)

# Data directory for this PostgreSQL version
data_dir = temp_path / self.version
Expand All @@ -233,7 +237,7 @@ def _initialize_database(self) -> Path:
],
check=True,
capture_output=True,
cwd=temp_path,
cwd=None if is_windows else temp_path,
)

# Configure PostgreSQL for ephemeral use
Expand Down
Loading