Skip to content

Commit 279447c

Browse files
authored
feat: valkey support & startup_connection fixture (#42)
1 parent c7121ae commit 279447c

25 files changed

+806
-28
lines changed

Makefile

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,20 @@ help: ## Display this help
2020
# =============================================================================
2121
# Developer Utils
2222
# =============================================================================
23-
install-pipx: ## Install pipx
24-
@python3 -m pip install --upgrade --user pipx
25-
26-
install-hatch: ## Install Hatch, UV, and Ruff
27-
@pipx install hatch --force
28-
@pipx inject hatch ruff uv hatch-pip-compile hatch-vcs hatch-mypyc mypy --include-deps --include-apps --force
23+
install-hatch: ## Install Hatch
24+
@sh ./scripts/install-hatch.sh
2925

3026
configure-hatch: ## Configure Hatch defaults
3127
@hatch config set dirs.env.virtual .direnv
3228
@hatch config set dirs.env.pip-compile .direnv
3329

3430
upgrade-hatch: ## Update Hatch, UV, and Ruff
35-
@pipx upgrade hatch --include-injected
31+
@hatch self update
3632

3733
install: ## Install the project and all dependencies
3834
@if [ "$(VENV_EXISTS)" ]; then echo "=> Removing existing virtual environment"; $(MAKE) destroy-venv; fi
3935
@$(MAKE) clean
40-
@if ! pipx --version > /dev/null; then echo '=> Installing `pipx`'; $(MAKE) install-pipx ; fi
41-
@if ! hatch --version > /dev/null; then echo '=> Installing `hatch` with `pipx`'; $(MAKE) install-hatch ; fi
36+
@if ! hatch --version > /dev/null; then echo '=> Installing `hatch` with standalone installation'; $(MAKE) install-hatch ; fi
4237
@if ! hatch-pip-compile --version > /dev/null; then echo '=> Updating `hatch` and installing plugins'; $(MAKE) upgrade-hatch ; fi
4338
@echo "=> Creating Python environments..."
4439
@$(MAKE) configure-hatch

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license = "MIT"
1212
name = "pytest-databases"
1313
readme = "README.md"
1414
requires-python = ">=3.8"
15-
version = "0.6.0"
15+
version = "0.7.0"
1616
#
1717
authors = [{ name = "Cody Fincher", email = "cody.fincher@gmail.com" }]
1818
keywords = [

scripts/install-hatch.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/bin/bash
2+
3+
# --- Constants ---
4+
BASE_URL="https://github.com/pypa/hatch/releases/latest/download"
5+
EXTRACT_CMD="tar -xzf"
6+
7+
# --- Handle Optional Installation Directory ---
8+
INSTALL_DIR="$1" # Default: current directory
9+
if [[ -n "$INSTALL_DIR" ]]; then
10+
if [[ ! -d "$INSTALL_DIR" ]]; then # Check if directory exists
11+
INSTALL_DIR="$HOME/.local/bin"
12+
echo "Error: Invalid install directory '$INSTALL_DIR'"
13+
exit 1
14+
fi
15+
INSTALL_DIR=$(realpath "$INSTALL_DIR") # Get absolute path
16+
fi
17+
18+
# --- Determine Platform ---
19+
PLATFORM=$(uname -s)
20+
MACHINE=$(uname -m)
21+
FILE_EXT="tar.gz"
22+
23+
if [[ $PLATFORM == "Darwin" ]]; then
24+
PLATFORM_NAME="apple-darwin"
25+
elif [[ $PLATFORM == "Linux" ]]; then
26+
PLATFORM_NAME="unknown-linux-gnu"
27+
if [[ $MACHINE == "aarch64" ]]; then
28+
MACHINE="aarch64"
29+
fi
30+
elif [[ $PLATFORM == "Windows" ]]; then
31+
PLATFORM_NAME="pc-windows-msvc"
32+
FILE_EXT="zip"
33+
EXTRACT_CMD="unzip"
34+
else
35+
echo "Unsupported platform: $PLATFORM"
36+
exit 1
37+
fi
38+
39+
# --- Construct File Name and URL ---
40+
FILENAME="hatch-$MACHINE-$PLATFORM_NAME.$FILE_EXT"
41+
URL="$BASE_URL/$FILENAME"
42+
43+
# --- Download and Extract ---
44+
echo "Downloading Hatch binary: $FILENAME"
45+
curl -L -o "$FILENAME" "$URL"
46+
47+
echo "Extracting to '$INSTALL_DIR'..."
48+
$EXTRACT_CMD "$FILENAME" -C "$INSTALL_DIR" # Extract to install directory
49+
rm "$FILENAME" # Remove archive
50+
51+
HATCH_BINARY="$INSTALL_DIR/hatch" # Path to the extracted binary
52+
if [[ -x "$HATCH_BINARY" ]]; then
53+
echo "Hatch binary successfully installed at '$HATCH_BINARY'"
54+
else
55+
echo "Error: Hatch binary not found or not executable."
56+
fi

src/pytest_databases/docker/alloydb_omni.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,25 @@ async def alloydb_omni_service(
118118
password=postgres_password,
119119
)
120120
yield
121+
122+
123+
@pytest.fixture(autouse=False, scope="session")
124+
async def alloydb_omni_startup_connection(
125+
alloydb_omni_service: DockerServiceRegistry,
126+
alloydb_docker_ip: str,
127+
alloydb_omni_port: int,
128+
postgres_database: str,
129+
postgres_user: str,
130+
postgres_password: str,
131+
) -> AsyncGenerator[asyncpg.Connection[asyncpg.Record], None]:
132+
conn = await asyncpg.connect(
133+
host=alloydb_docker_ip,
134+
port=alloydb_omni_port,
135+
user=postgres_user,
136+
database=postgres_database,
137+
password=postgres_password,
138+
)
139+
try:
140+
yield conn
141+
finally:
142+
await conn.close()

src/pytest_databases/docker/bigquery.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,15 @@ async def bigquery_service(
140140
bigquery_client_options=bigquery_client_options,
141141
)
142142
yield
143+
144+
145+
@pytest.fixture(autouse=False, scope="session")
146+
async def bigquery_startup_connection(
147+
bigquery_service: DockerServiceRegistry,
148+
bigquery_project: str,
149+
bigquery_credentials: Credentials,
150+
bigquery_client_options: ClientOptions,
151+
) -> AsyncGenerator[bigquery.Client, None]:
152+
yield bigquery.Client(
153+
project=bigquery_project, client_options=bigquery_client_options, credentials=bigquery_credentials
154+
)

src/pytest_databases/docker/cockroachdb.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pathlib import Path
66
from typing import TYPE_CHECKING, AsyncGenerator
77

8-
import psycopg
8+
import asyncpg
99
import pytest
1010

1111
from pytest_databases.docker import DockerServiceRegistry
@@ -21,13 +21,16 @@
2121
async def cockroachdb_responsive(host: str, port: int, database: str, driver_opts: dict[str, str]) -> bool:
2222
opts = "&".join(f"{k}={v}" for k, v in driver_opts.items()) if driver_opts else ""
2323
try:
24-
with psycopg.connect(f"postgresql://root@{host}:{port}/{database}?{opts}") as conn, conn.cursor() as cursor:
25-
cursor.execute("select 1 as is_available")
26-
resp = cursor.fetchone()
27-
return resp[0] if resp is not None else 0 == 1 # noqa: PLR0133
24+
conn = await asyncpg.connect(f"postgresql://root@{host}:{port}/{database}?{opts}")
2825
except Exception: # noqa: BLE001
2926
return False
3027

28+
try:
29+
db_open = await conn.fetchrow("SELECT 1")
30+
return bool(db_open is not None and db_open[0] == 1)
31+
finally:
32+
await conn.close()
33+
3134

3235
@pytest.fixture(scope="session")
3336
def cockroachdb_compose_project_name() -> str:
@@ -100,3 +103,21 @@ async def cockroachdb_service(
100103
driver_opts=cockroachdb_driver_opts,
101104
)
102105
yield
106+
107+
108+
@pytest.fixture(autouse=False, scope="session")
109+
async def cockroachdb_startup_connection(
110+
cockroachdb_service: DockerServiceRegistry,
111+
cockroachdb_docker_ip: str,
112+
cockroachdb_port: int,
113+
cockroachdb_database: str,
114+
cockroachdb_driver_opts: dict[str, str],
115+
) -> AsyncGenerator[asyncpg.Connection[asyncpg.Record], None]:
116+
opts = "&".join(f"{k}={v}" for k, v in cockroachdb_driver_opts.items()) if cockroachdb_driver_opts else ""
117+
conn = await asyncpg.connect(
118+
f"postgresql://root@{cockroachdb_docker_ip}:{cockroachdb_port}/{cockroachdb_database}?{opts}"
119+
)
120+
try:
121+
yield conn
122+
finally:
123+
await conn.close()
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
services:
22
cockroachdb:
3-
image: cockroachdb/cockroach:latest-v23.1
4-
command: start-single-node --insecure
3+
image: cockroachdb/cockroach:latest
4+
command: start-single-node --insecure --http-addr=cockroachdb:8080
55
restart: "no"
66
expose:
77
- "8080"
88
- "${COCKROACHDB_PORT:-26257}"
99
ports:
1010
- "${COCKROACHDB_PORT:-26257}:26257"
11-
- "8880:8080"
11+
- "${COCKROACHDB_WEB_PORT:-8880}:8080"
12+
volumes:
13+
- cockroach-data:/cockroach/cockroach-data/
1214
healthcheck:
1315
test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]
1416
interval: 3s
@@ -17,3 +19,5 @@ services:
1719
networks:
1820
default:
1921
driver: bridge
22+
volumes:
23+
cockroach-data:
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
valkey:
3+
image: valkey/valkey
4+
ports:
5+
- "${VALKEY_PORT:-6308}:6379"
6+
networks:
7+
default:
8+
driver: bridge

src/pytest_databases/docker/mariadb.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import sys
66
from pathlib import Path
7-
from typing import TYPE_CHECKING, AsyncGenerator
7+
from typing import TYPE_CHECKING, Any, AsyncGenerator
88

99
import asyncmy
1010
import pytest
@@ -162,3 +162,41 @@ async def mariadb_service(
162162
password=mariadb_password,
163163
)
164164
yield
165+
166+
167+
@pytest.fixture(autouse=False, scope="session")
168+
async def mariadb_startup_connection(
169+
mariadb_service: DockerServiceRegistry,
170+
mariadb_docker_ip: str,
171+
mariadb_port: int,
172+
mariadb_database: str,
173+
mariadb_user: str,
174+
mariadb_password: str,
175+
) -> AsyncGenerator[Any, None]:
176+
conn = await asyncmy.connect(
177+
host=mariadb_docker_ip,
178+
port=mariadb_port,
179+
user=mariadb_user,
180+
database=mariadb_database,
181+
password=mariadb_password,
182+
)
183+
yield conn
184+
185+
186+
@pytest.fixture(autouse=False, scope="session")
187+
async def mariadb113_startup_connection(
188+
mariadb113_service: DockerServiceRegistry,
189+
mariadb_docker_ip: str,
190+
mariadb113_port: int,
191+
mariadb_database: str,
192+
mariadb_user: str,
193+
mariadb_password: str,
194+
) -> AsyncGenerator[Any, None]:
195+
conn = await asyncmy.connect(
196+
host=mariadb_docker_ip,
197+
port=mariadb113_port,
198+
user=mariadb_user,
199+
database=mariadb_database,
200+
password=mariadb_password,
201+
)
202+
yield conn

src/pytest_databases/docker/mssql.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ def mssql_docker_ip(mssql_docker_services: DockerServiceRegistry) -> str:
9393
return mssql_docker_services.docker_ip
9494

9595

96+
@pytest.fixture(scope="session")
97+
def mssql_connection_string(
98+
mssql_docker_ip: str, mssql_port: int, mssql_database: str, mssql_user: str, mssql_password: str
99+
) -> str:
100+
return f"encrypt=no; TrustServerCertificate=yes; driver={{ODBC Driver 18 for SQL Server}}; server={mssql_docker_ip},{mssql_port}; database={mssql_database}; UID={mssql_user}; PWD={mssql_password}"
101+
102+
103+
@pytest.fixture(scope="session")
104+
def mssql2022_connection_string(
105+
mssql_docker_ip: str, mssql2022_port: int, mssql_database: str, mssql_user: str, mssql_password: str
106+
) -> str:
107+
return f"encrypt=no; TrustServerCertificate=yes; driver={{ODBC Driver 18 for SQL Server}}; server={mssql_docker_ip},{mssql2022_port}; database={mssql_database}; UID={mssql_user}; PWD={mssql_password}"
108+
109+
96110
@pytest.fixture(autouse=False, scope="session")
97111
async def mssql2022_service(
98112
mssql_docker_services: DockerServiceRegistry,
@@ -102,19 +116,19 @@ async def mssql2022_service(
102116
mssql_database: str,
103117
mssql_user: str,
104118
mssql_password: str,
119+
mssql2022_connection_string: str,
105120
) -> AsyncGenerator[None, None]:
106121
os.environ["MSSQL_PASSWORD"] = mssql_password
107122
os.environ["MSSQL_USER"] = mssql_user
108123
os.environ["MSSQL_DATABASE"] = mssql_database
109124
os.environ["MSSQL2022_PORT"] = str(mssql2022_port)
110-
connstring = f"encrypt=no; TrustServerCertificate=yes; driver={{ODBC Driver 18 for SQL Server}}; server={mssql_docker_ip},{mssql2022_port}; database={mssql_database}; UID={mssql_user}; PWD={mssql_password}"
111125
await mssql_docker_services.start(
112126
"mssql2022",
113127
docker_compose_files=mssql_docker_compose_files,
114128
timeout=120,
115129
pause=1,
116130
check=mssql_responsive,
117-
connstring=connstring,
131+
connstring=mssql2022_connection_string,
118132
)
119133
yield
120134

@@ -129,8 +143,8 @@ async def mssql_service(
129143
mssql_database: str,
130144
mssql_user: str,
131145
mssql_password: str,
146+
mssql_connection_string: str,
132147
) -> AsyncGenerator[None, None]:
133-
connstring = f"encrypt=no; TrustServerCertificate=yes; driver={{ODBC Driver 18 for SQL Server}}; server={mssql_docker_ip},{mssql_port}; database={mssql_database}; UID={mssql_user}; PWD={mssql_password}"
134148
os.environ["MSSQL_PASSWORD"] = mssql_password
135149
os.environ["MSSQL_USER"] = mssql_user
136150
os.environ["MSSQL_DATABASE"] = mssql_database
@@ -141,6 +155,28 @@ async def mssql_service(
141155
timeout=120,
142156
pause=1,
143157
check=mssql_responsive,
144-
connstring=connstring,
158+
connstring=mssql_connection_string,
145159
)
146160
yield
161+
162+
163+
@pytest.fixture(autouse=False, scope="session")
164+
async def mssql_startup_connection(
165+
mssql_service: DockerServiceRegistry, mssql_connection_string: str
166+
) -> AsyncGenerator[aioodbc.Connection, None]:
167+
async with await aioodbc.connect(
168+
dsn=mssql_connection_string,
169+
timeout=2,
170+
) as db_connection:
171+
yield db_connection
172+
173+
174+
@pytest.fixture(autouse=False, scope="session")
175+
async def mssql2022_startup_connection(
176+
mssql2022_service: DockerServiceRegistry, mssql2022_connection_string: str
177+
) -> AsyncGenerator[aioodbc.Connection, None]:
178+
async with await aioodbc.connect(
179+
dsn=mssql2022_connection_string,
180+
timeout=2,
181+
) as db_connection:
182+
yield db_connection

0 commit comments

Comments
 (0)