Skip to content
Merged
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
17 changes: 13 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ Perfect SQLAlchemy integration with automatic cleanup.
### **🌟 Django** - Auto-configured testing

```bash
# Basic Django test, without pytest-django
pytest testing-patterns/django/ -v
```

Django models + pytest-django compatibility, zero setup.
# Django test with pytest-django (requires pytest-django)
pip install pytest-django
pytest testing-patterns/django/test_pytest_django.py -v
```

### **🎪 Comprehensive** - All fixtures

Expand Down Expand Up @@ -116,10 +119,16 @@ def test_users(pglite_session):
pglite_session.commit()
assert user.id == 1 # Real PostgreSQL!

# Django tests
def test_models(db):
# Django tests without pytest-django
def test_models(pglite_django_db):
Post.objects.create(title="Hello World")
assert Post.objects.count() == 1 # Zero config!

# Django tests with pytest-django
@pytest.mark.django_db
def test_with_pytest_django(pglite_django_db):
Post.objects.create(title="Hello World")
assert Post.objects.count() == 1 # 更多测试功能!
```

### **🚀 Production Examples**
Expand Down
26 changes: 16 additions & 10 deletions examples/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@
@pytest.fixture(scope="module")
def pglite_manager() -> Generator[PGliteManager, None, None]:
"""Isolated PGlite manager for examples - one per test module.

This overrides the session-scoped fixture from the main package
to provide better isolation when running all tests together.
"""
# Create unique configuration to prevent socket conflicts
config = PGliteConfig()

# Create a unique socket directory for this example module
# PGlite expects socket_path to be the full path including .s.PGSQL.5432
socket_dir = Path(tempfile.gettempdir()) / f"py-pglite-example-{uuid.uuid4().hex[:8]}"
socket_dir = (
Path(tempfile.gettempdir()) / f"py-pglite-example-{uuid.uuid4().hex[:8]}"
)
socket_dir.mkdir(mode=0o700, exist_ok=True) # Restrict to user only
config.socket_path = str(socket_dir / ".s.PGSQL.5432")

manager = PGliteManager(config)
manager.start()
manager.wait_for_ready()
Expand Down Expand Up @@ -94,28 +96,32 @@ def pglite_session(pglite_engine: Engine) -> Generator[Any, None, None]:
if table_names:
# Disable foreign key checks for faster cleanup
conn.execute(text("SET session_replication_role = replica;"))

# Truncate all tables
for table_name in table_names:
logger.info(f"Truncating table: {table_name}")
conn.execute(
text(f'TRUNCATE TABLE "{table_name}" RESTART IDENTITY CASCADE;')
text(
f'TRUNCATE TABLE "{table_name}" RESTART IDENTITY CASCADE;'
)
)

# Re-enable foreign key checks
conn.execute(text("SET session_replication_role = DEFAULT;"))

# Commit the cleanup
conn.commit()
logger.info("Database cleanup completed successfully")
else:
logger.info("No tables found to clean")
break # Success, exit retry loop

except Exception as e:
logger.info(f"Database cleanup attempt {attempt + 1} failed: {e}")
if attempt == retry_count - 1:
logger.warning("Database cleanup failed after all retries, continuing anyway")
logger.warning(
"Database cleanup failed after all retries, continuing anyway"
)
else:
time.sleep(0.5) # Brief pause before retry

Expand Down Expand Up @@ -144,4 +150,4 @@ def pglite_session(pglite_engine: Engine) -> Generator[Any, None, None]:
try:
session.close()
except Exception as e:
logger.warning(f"Error closing session: {e}")
logger.warning(f"Error closing session: {e}")
13 changes: 4 additions & 9 deletions examples/quickstart/simple_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ def measure_feature_power():
f" → {result[1]} events/minute, avg score: {result[2]:.1f}",
)

total_feature_time = (
json_time + array_time + window_time + time_series_time
)
total_feature_time = json_time + array_time + window_time + time_series_time

print(f"\n 🚀 Total advanced features: {total_feature_time:.4f}s")
print(" 💥 SQLite equivalent: IMPOSSIBLE ❌")
Expand Down Expand Up @@ -263,12 +261,10 @@ def measure_raw_performance():
sqlite_insert = time.time() - start

print(
f" py-pglite: {pglite_insert:.3f}s "
f"({1000 / pglite_insert:,.0f} rec/sec)"
f" py-pglite: {pglite_insert:.3f}s ({1000 / pglite_insert:,.0f} rec/sec)"
)
print(
f" SQLite: {sqlite_insert:.3f}s "
f"({1000 / sqlite_insert:,.0f} rec/sec)"
f" SQLite: {sqlite_insert:.3f}s ({1000 / sqlite_insert:,.0f} rec/sec)"
)

if sqlite_insert < pglite_insert:
Expand Down Expand Up @@ -322,8 +318,7 @@ def generate_final_report(boot_times, feature_time, perf_times):
print("\n" + "🎯 THE HONEST TRUTH" + "\n" + "=" * 60)

print("📊 PERFORMANCE COMPARISON:")
print(f" Boot Time: SQLite {sqlite_boot:.3f}s vs "
f"py-pglite {pglite_boot:.2f}s")
print(f" Boot Time: SQLite {sqlite_boot:.3f}s vs py-pglite {pglite_boot:.2f}s")
print(
f" Insert Speed: SQLite {1000 / sqlite_insert:,.0f}/s vs "
f"py-pglite {1000 / pglite_insert:,.0f}/s"
Expand Down
6 changes: 4 additions & 2 deletions examples/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# Example model
class BasicUser(SQLModel, table=True):
__table_args__ = {'extend_existing': True}
__table_args__ = {"extend_existing": True}
id: int | None = Field(default=None, primary_key=True)
name: str
email: str
Expand Down Expand Up @@ -60,7 +60,9 @@ def test_user_update(pglite_session: Session):
pglite_session.commit()

# Verify the update
updated_user = pglite_session.exec(select(BasicUser).where(BasicUser.name == "David")).first()
updated_user = pglite_session.exec(
select(BasicUser).where(BasicUser.name == "David")
).first()
assert updated_user is not None
assert updated_user.email == "david@new.com"

Expand Down
26 changes: 16 additions & 10 deletions examples/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_database_cleanup_utils(pglite_engine):
title="Python Testing Guide",
author_id=author.id,
isbn="978-0123456789",
published_year=2024
published_year=2024,
)
session.add(book)
session.commit()
Expand Down Expand Up @@ -138,7 +138,7 @@ def test_partial_cleanup(pglite_engine):
title="Temporary Book",
author_id=author.id,
isbn="978-0987654321",
published_year=2024
published_year=2024,
)
session.add(book)
session.commit()
Expand All @@ -153,7 +153,9 @@ def test_partial_cleanup(pglite_engine):

# Verify with exclude list in verification
assert utils.verify_database_empty(pglite_engine, exclude_tables=["author"])
assert not utils.verify_database_empty(pglite_engine) # Should be False due to author
assert not utils.verify_database_empty(
pglite_engine
) # Should be False due to author


def test_schema_operations(pglite_engine):
Expand All @@ -167,8 +169,10 @@ def test_schema_operations(pglite_engine):
with Session(pglite_engine) as session:
with session.connection() as conn:
result = conn.execute(
text("SELECT schema_name FROM information_schema.schemata WHERE schema_name = :name"),
{"name": test_schema}
text(
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = :name"
),
{"name": test_schema},
)
schemas = result.fetchall()
assert len(schemas) == 1
Expand All @@ -180,8 +184,10 @@ def test_schema_operations(pglite_engine):
with Session(pglite_engine) as session:
with session.connection() as conn:
result = conn.execute(
text("SELECT schema_name FROM information_schema.schemata WHERE schema_name = :name"),
{"name": test_schema}
text(
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = :name"
),
{"name": test_schema},
)
schemas = result.fetchall()
assert len(schemas) == 0
Expand All @@ -198,8 +204,8 @@ def test_combined_cleanup_fixture(pglite_session: Session):
# Clean all tables manually to ensure fresh state
conn.execute(text('DELETE FROM "book"'))
conn.execute(text('DELETE FROM "author"'))
conn.execute(text('ALTER SEQUENCE author_id_seq RESTART WITH 1'))
conn.execute(text('ALTER SEQUENCE book_id_seq RESTART WITH 1'))
conn.execute(text("ALTER SEQUENCE author_id_seq RESTART WITH 1"))
conn.execute(text("ALTER SEQUENCE book_id_seq RESTART WITH 1"))
pglite_session.commit()

# Add test data directly using the existing session
Expand All @@ -213,7 +219,7 @@ def test_combined_cleanup_fixture(pglite_session: Session):
title="Test Book",
author_id=author.id,
isbn="978-0111111111",
published_year=2024
published_year=2024,
)
pglite_session.add(book)
pglite_session.commit()
Expand Down
18 changes: 11 additions & 7 deletions examples/testing-patterns/django/test_pytest_django.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""
🌟 pytest-django + py-pglite Example
===================================
🌟 pytest-django + py-pglite Example (Optional Integration)
=========================================================

Demonstrates pytest-django specific features with py-pglite.
Demonstrates optional pytest-django integration with py-pglite.

This shows pytest-django integration:
• Using @pytest.mark.django_db decorator
• pytest-django fixtures and utilities
This shows how to use pytest-django features with py-pglite:
• Using @pytest.mark.django_db decorator (optional)
• pytest-django fixtures and utilities (optional)
• Django test utilities with py-pglite

This is DIFFERENT from plain Django ORM usage!
Note: This is an OPTIONAL integration. You can use py-pglite with Django
without pytest-django. This example is for users who specifically want
to use pytest-django features.

For basic Django testing without pytest-django, see test_django_quickstart.py
"""

import pytest
Expand Down
2 changes: 1 addition & 1 deletion py_pglite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
and Python test suites with support for SQLAlchemy, SQLModel, and Django.
"""

__version__ = "0.2.0"
__version__ = "0.2.1"

Check warning on line 7 in py_pglite/__init__.py

View check run for this annotation

Codecov / codecov/patch

py_pglite/__init__.py#L7

Added line #L7 was not covered by tests

# Core exports (always available)
from .config import PGliteConfig
Expand Down
3 changes: 2 additions & 1 deletion py_pglite/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
"🚫 Django not available. Install with: pip install py-pglite[django]"
)

if item.get_closest_marker("pglite_pytest_django") and not HAS_PYTEST_DJANGO:
# Only check for pytest-django if the test is explicitly marked to use it
if item.get_closest_marker("pytest_django") and not HAS_PYTEST_DJANGO:
pytest.skip(
"🚫 pytest-django not available. Install with: pip install pytest-django"
)
Expand Down