WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit ad32c19

Browse files
authored
Merge pull request #16 from wey-gu/pg_lite_extensions_pg_vector
feat: pglite extensions && pgvector
2 parents 8d762c5 + 0c16eac commit ad32c19

File tree

19 files changed

+689
-368
lines changed

19 files changed

+689
-368
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ jobs:
3232
- name: Install Python dependencies
3333
run: |
3434
python -m pip install --upgrade pip
35+
pip install -e ".[all]"
3536
pip install -e ".[dev]"
3637
pip install types-psutil
3738
38-
- name: Run stable tests and examples
39+
- name: Run stable tests and examples
40+
# As the coverage trigger already runs the tests, we don't need to run them again.
41+
if: matrix.python-version != '3.11'
3942
run: |
4043
python scripts/dev.py
4144
env:
@@ -45,13 +48,7 @@ jobs:
4548
if: matrix.python-version == '3.11'
4649
run: |
4750
pip install coverage[toml] pytest-cov
48-
pytest tests/ examples/ --cov=py_pglite --cov-report=xml --cov-report=term-missing --ignore=examples/testing-patterns/test_performance_benchmarks.py -k "not test_custom_configuration_patterns"
49-
50-
- name: Run stress tests (optional)
51-
continue-on-error: true
52-
if: matrix.python-version == '3.11'
53-
run: |
54-
python scripts/dev.py --stress
51+
pytest tests/ examples/ --cov=py_pglite --cov-report=xml --cov-report=term-missing
5552
5653
- name: Upload coverage to Codecov
5754
if: matrix.python-version == '3.11'

CONTRIBUTING.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ py-pglite/
128128
│ │ ├── backend.py # Custom database backend
129129
│ │ ├── fixtures.py # Django fixtures
130130
│ │ └── utils.py # Django utilities
131-
│ └── pytest_plugin.py # Auto-discovery pytest plugin
131+
│ ├── pytest_plugin.py # Auto-discovery pytest plugin
132+
│ └── extensions.py # 🆕 Extension registry (e.g., pgvector)
132133
133134
├── tests/ # 🧪 Core tests (88 tests)
134135
│ ├── test_core_manager.py # Manager lifecycle & process management
@@ -138,13 +139,16 @@ py-pglite/
138139
│ ├── test_reliability.py # 🆕 Error recovery & resilience
139140
│ ├── test_django_backend.py # 🆕 Django backend & decoupling
140141
│ ├── test_fastapi_integration.py # FastAPI patterns
141-
│ └── test_framework_isolation.py # Framework isolation validation
142+
│ ├── test_framework_isolation.py # Framework isolation validation
143+
│ └── test_extensions.py # 🆕 Extension tests (e.g., pgvector)
142144
143145
├── examples/ # 📚 Examples & demos (51 tests)
144146
│ ├── quickstart/ # ⚡ Instant demos
145147
│ │ ├── demo_instant.py # 5-line PostgreSQL demo
146148
│ │ ├── simple_fastapi.py # FastAPI integration
147149
│ │ └── simple_performance.py # Performance comparison
150+
│ ├── features/ # 🆕 Advanced feature examples
151+
│ │ └── test_pgvector_rag.py # pgvector RAG example
148152
│ └── testing-patterns/ # 🧪 Production examples
149153
│ ├── sqlalchemy/ # SQLAlchemy patterns (2 tests)
150154
│ ├── django/ # Django patterns (10 tests)
@@ -288,6 +292,46 @@ pytest examples/testing-patterns/new_example.py -v
288292
python examples/quickstart/demo_instant.py
289293
```
290294

295+
### 4. PostgreSQL Extensions
296+
297+
`py-pglite` supports a growing number of PostgreSQL extensions.
298+
299+
**1. Register the Extension:**
300+
Add the extension's details to `py_pglite/extensions.py`.
301+
302+
```python
303+
# py_pglite/extensions.py
304+
SUPPORTED_EXTENSIONS = {
305+
"pgvector": {"module": "@electric-sql/pglite/vector", "name": "vector"},
306+
"new_extension": {"module": "npm-package-name", "name": "js_export_name"},
307+
}
308+
```
309+
310+
**2. Add Optional Dependencies:**
311+
Add any necessary Python client libraries to `pyproject.toml` under the `[project.optional-dependencies]` section.
312+
313+
```toml
314+
# pyproject.toml
315+
[project.optional-dependencies]
316+
extensions = [
317+
"pgvector>=0.4.1",
318+
"numpy>=1.0.0",
319+
"new-python-dependency>=1.0.0",
320+
]
321+
```
322+
323+
**3. Add a Test:**
324+
Create a new test file in `tests/` to validate the extension's functionality. Use the `@pytest.mark.extensions` marker.
325+
326+
```python
327+
# tests/test_new_extension.py
328+
import pytest
329+
330+
@pytest.mark.extensions
331+
def test_new_extension_feature():
332+
# ...
333+
```
334+
291335
---
292336

293337
## 📝 **Documentation**

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
<img src="https://github.com/user-attachments/assets/3c6ef886-5075-4d82-a180-a6b1dafe792b" alt="py-pglite Logo" width="60" align="left" style="margin-right: 16px;"/>
44

5-
**Instant PostgreSQL for Python testing**
5+
**A Pythonic interface for PGlite - the instant, zero-config PostgreSQL.** ⚡️
6+
7+
`py-pglite` brings the magic of [PGlite](https://github.com/electric-sql/pglite) to Python with a high-level, developer-friendly API. Real PostgreSQL, instant testing.
68

79
`pip install py-pglite`
810

@@ -62,6 +64,9 @@ pip install py-pglite[sqlalchemy] # SQLAlchemy + SQLModel
6264
pip install py-pglite[django] # Django + pytest-django
6365
pip install py-pglite[asyncpg] # Pure async client
6466
pip install py-pglite[all] # Everything
67+
68+
# Extra Features
69+
pip install py-pglite[extensions] # pglite extensions, like pgvector, fuzzystrmatch etc.
6570
```
6671

6772
---
@@ -146,6 +151,48 @@ def test_postgresql_power(pglite_session):
146151
assert result.clicks == '100'
147152
```
148153

154+
### **PostgreSQL Extensions**
155+
156+
`py-pglite` supports PostgreSQL extensions, allowing you to test advanced features like vector similarity search for AI/RAG applications.
157+
158+
### **🚀 `pgvector` for RAG Applications**
159+
160+
Enable `pgvector` to test vector embeddings and similarity search directly in your test suite.
161+
162+
**1. Install with the `[extensions]` extra:**
163+
164+
```bash
165+
pip install 'py-pglite[extensions]'
166+
```
167+
168+
**2. Enable `pgvector` in the configuration:**
169+
170+
```python
171+
from py_pglite import PGliteConfig, PGliteManager
172+
from pgvector.psycopg import register_vector
173+
import psycopg
174+
import numpy as np
175+
176+
# Enable the extension
177+
config = PGliteConfig(extensions=["pgvector"])
178+
179+
with PGliteManager(config=config) as db:
180+
with psycopg.connect(db.get_dsn(), autocommit=True) as conn:
181+
# Create the extension and register the type
182+
conn.execute("CREATE EXTENSION IF NOT EXISTS vector")
183+
register_vector(conn)
184+
185+
# Create a table and insert a vector
186+
conn.execute("CREATE TABLE items (embedding vector(3))")
187+
conn.execute("INSERT INTO items (embedding) VALUES (%s)", (np.array([1, 2, 3]),))
188+
189+
# Perform a similarity search
190+
result = conn.execute("SELECT * FROM items ORDER BY embedding <-> %s LIMIT 1", (np.array([1, 1, 1]),)).fetchone()
191+
assert np.array_equal(result[0], np.array([1, 2, 3]))
192+
```
193+
194+
`py-pglite` can support many other extensions available in the underlying [PGlite extensions](https://pglite.dev/extensions/) ♥️.
195+
149196
---
150197

151198
## **Advanced**
@@ -225,3 +272,5 @@ pytest tests/sqlalchemy/ # Directory isolation
225272
---
226273

227274
*py-pglite: Because testing should be simple.*
275+
276+
Powered by the 🚀 amazing and ♥️ beloved [PGlite](https://github.com/electric-sql/pglite).

examples/README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ python quickstart/simple_performance.py
4242
4343
---
4444
45+
## **✨ Feature Examples**
46+
47+
### **🤖 `pgvector` for AI/RAG**
48+
49+
Test vector similarity search for Retrieval-Augmented Generation (RAG) applications.
50+
51+
```bash
52+
# Requires 'py-pglite[extensions]' to be installed
53+
pytest examples/features/test_pgvector_rag.py -v
54+
```
55+
56+
---
57+
4558
## 🧪 **Testing Patterns** (Production examples)
4659
4760
### **📊 SQLAlchemy** - Zero config testing
@@ -82,6 +95,9 @@ examples/
8295
│ ├── simple_fastapi.py # 🌐 FastAPI + PostgreSQL API
8396
│ └── simple_performance.py # 🏃 The honest performance sweet spot
8497
98+
├── features/ # ✨ Feature examples
99+
│ └── test_pgvector_rag.py # 🤖 pgvector for AI/RAG
100+
85101
├── testing-patterns/ # 🧪 Production examples
86102
│ ├── sqlalchemy/ # 📊 SQLAlchemy patterns
87103
│ │ ├── test_sqlalchemy_quickstart.py
@@ -205,9 +221,5 @@ def test_my_feature(pglite_session):
205221
1. **⚡ See the magic** - `python quickstart/demo_instant.py`
206222
2. **🌐 Try FastAPI** - `python quickstart/simple_fastapi.py`
207223
3. **🏃 See the value** - `python quickstart/simple_performance.py`
208-
4. **🧪 Run tests** - `pytest testing-patterns/ -v`
209-
5. **🎪 Explore advanced** - `pytest testing-patterns/test_fixtures_showcase.py -v`
210-
211-
---
212-
213-
**py-pglite: Because PostgreSQL testing should be instant.** ⚡
224+
4. **🤖 Try pgvector** - `pytest examples/features/test_pgvector_rag.py -v`
225+
5. **🎪 Explore advanced** - `
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""
2+
Example of using pgvector with py-pglite for a simple RAG application.
3+
4+
This test demonstrates how to:
5+
1. Enable the `pgvector` extension.
6+
2. Create a table for storing text chunks and their embeddings.
7+
3. Insert documents and their vector embeddings.
8+
4. Perform a similarity search to find the most relevant document chunk.
9+
5. Use the retrieved chunk to answer a question.
10+
"""
11+
12+
from typing import TYPE_CHECKING
13+
14+
import psycopg
15+
import pytest
16+
17+
from py_pglite import PGliteManager
18+
from py_pglite.config import PGliteConfig
19+
20+
if TYPE_CHECKING:
21+
import numpy as np
22+
from numpy.typing import NDArray
23+
from pgvector.psycopg import register_vector
24+
25+
# Try to import optional dependencies, or skip tests
26+
try:
27+
import numpy as np
28+
from pgvector.psycopg import register_vector
29+
except ImportError:
30+
np = None
31+
register_vector = None
32+
33+
34+
@pytest.mark.skipif(
35+
not np or not register_vector, reason="numpy or pgvector not available"
36+
)
37+
def test_pgvector_rag_example():
38+
"""Demonstrates a simple RAG workflow using pgvector."""
39+
# --- 1. Setup: Documents and a mock embedding function ---
40+
41+
documents = {
42+
"doc1": "The sky is blue.",
43+
"doc2": "The sun is bright.",
44+
"doc3": "The cat walks on the street.",
45+
}
46+
47+
# A mock function to simulate generating embeddings (e.g., from an API)
48+
def get_embedding(text: str) -> "NDArray":
49+
assert np is not None
50+
# In a real app, this would be a call to an embedding model
51+
if "sky" in text:
52+
return np.array([0.1, 0.9, 0.1])
53+
if "sun" in text:
54+
return np.array([0.8, 0.2, 0.1])
55+
if "cat" in text:
56+
return np.array([0.1, 0.1, 0.8])
57+
return np.array([0.0, 0.0, 0.0])
58+
59+
# --- 2. Database Setup: Enable pgvector and create schema ---
60+
61+
config = PGliteConfig(extensions=["pgvector"])
62+
with PGliteManager(config=config) as db:
63+
with psycopg.connect(db.get_dsn(), autocommit=True) as conn:
64+
conn.execute("CREATE EXTENSION IF NOT EXISTS vector")
65+
assert register_vector is not None
66+
register_vector(conn)
67+
68+
conn.execute(
69+
"""
70+
CREATE TABLE documents (
71+
id SERIAL PRIMARY KEY,
72+
content TEXT,
73+
embedding vector(3)
74+
)
75+
"""
76+
)
77+
78+
# --- 3. Ingestion: Store documents and embeddings ---
79+
80+
for content in documents.values():
81+
embedding = get_embedding(content)
82+
conn.execute(
83+
"INSERT INTO documents (content, embedding) VALUES (%s, %s)",
84+
(content, embedding),
85+
)
86+
87+
# --- 4. RAG Workflow: Ask a question and retrieve context ---
88+
89+
question = "What color is the sky?"
90+
question_embedding = get_embedding(question)
91+
92+
# Find the most similar document chunk
93+
result = conn.execute(
94+
"SELECT content FROM documents ORDER BY embedding <-> %s LIMIT 1",
95+
(question_embedding,),
96+
).fetchone()
97+
98+
assert result is not None
99+
retrieved_context = result[0]
100+
101+
# --- 5. Generation: Use the context to answer the question ---
102+
103+
# A mock generation step
104+
def generate_answer(context: str, question: str) -> str:
105+
if "sky" in question and "blue" in context:
106+
return "Based on the context, the sky is blue."
107+
return "I cannot answer the question based on the provided context."
108+
109+
answer = generate_answer(retrieved_context, question)
110+
111+
# --- 6. Verification ---
112+
113+
print(f"\nQuestion: {question}")
114+
print(f"Retrieved Context: '{retrieved_context}'")
115+
print(f"Answer: {answer}")
116+
117+
assert "The sky is blue" in retrieved_context
118+
assert "the sky is blue" in answer.lower()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Pytest configuration for advanced testing patterns."""
2+
3+
import pytest
4+
from sqlmodel import SQLModel
5+
6+
from py_pglite import PGliteConfig
7+
from py_pglite.sqlalchemy import SQLAlchemyPGliteManager
8+
9+
10+
@pytest.fixture(scope="function")
11+
def benchmark_engine():
12+
"""High-performance engine configuration for benchmarking."""
13+
config = PGliteConfig(
14+
timeout=120,
15+
log_level="WARNING",
16+
cleanup_on_exit=True,
17+
node_options="--max-old-space-size=8192",
18+
)
19+
20+
with SQLAlchemyPGliteManager(config) as manager:
21+
manager.wait_for_ready(max_retries=20, delay=1.0)
22+
23+
engine = manager.get_engine(
24+
pool_pre_ping=False,
25+
echo=False,
26+
pool_recycle=3600,
27+
)
28+
29+
SQLModel.metadata.create_all(engine)
30+
yield engine

0 commit comments

Comments
 (0)