pytest-neon 1.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_neon/__init__.py +1 -1
- pytest_neon/plugin.py +160 -21
- {pytest_neon-1.0.0.dist-info → pytest_neon-2.1.0.dist-info}/METADATA +84 -29
- pytest_neon-2.1.0.dist-info/RECORD +8 -0
- pytest_neon-1.0.0.dist-info/RECORD +0 -8
- {pytest_neon-1.0.0.dist-info → pytest_neon-2.1.0.dist-info}/WHEEL +0 -0
- {pytest_neon-1.0.0.dist-info → pytest_neon-2.1.0.dist-info}/entry_points.txt +0 -0
- {pytest_neon-1.0.0.dist-info → pytest_neon-2.1.0.dist-info}/licenses/LICENSE +0 -0
pytest_neon/__init__.py
CHANGED
pytest_neon/plugin.py
CHANGED
|
@@ -5,15 +5,17 @@ instant branching feature. Each test gets a clean database state via
|
|
|
5
5
|
branch reset after each test.
|
|
6
6
|
|
|
7
7
|
Main fixtures:
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
neon_branch_readwrite: Read-write access with reset after each test (recommended)
|
|
9
|
+
neon_branch_readonly: Read-only access, no reset (fastest for read-only tests)
|
|
10
|
+
neon_branch: Deprecated alias for neon_branch_readwrite
|
|
11
|
+
neon_branch_shared: Shared branch without reset (module-scoped)
|
|
10
12
|
neon_connection: psycopg2 connection (requires psycopg2 extra)
|
|
11
13
|
neon_connection_psycopg: psycopg v3 connection (requires psycopg extra)
|
|
12
14
|
neon_engine: SQLAlchemy engine (requires sqlalchemy extra)
|
|
13
15
|
|
|
14
16
|
SQLAlchemy Users:
|
|
15
17
|
If you create your own SQLAlchemy engine (not using neon_engine fixture),
|
|
16
|
-
you MUST use pool_pre_ping=True:
|
|
18
|
+
you MUST use pool_pre_ping=True when using neon_branch_readwrite:
|
|
17
19
|
|
|
18
20
|
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
|
19
21
|
|
|
@@ -21,6 +23,9 @@ SQLAlchemy Users:
|
|
|
21
23
|
Without pool_pre_ping, SQLAlchemy may try to reuse dead pooled connections,
|
|
22
24
|
causing "SSL connection has been closed unexpectedly" errors.
|
|
23
25
|
|
|
26
|
+
Note: pool_pre_ping is not required for neon_branch_readonly since no
|
|
27
|
+
resets occur.
|
|
28
|
+
|
|
24
29
|
Configuration:
|
|
25
30
|
Set NEON_API_KEY and NEON_PROJECT_ID environment variables, or use
|
|
26
31
|
--neon-api-key and --neon-project-id CLI options.
|
|
@@ -33,6 +38,7 @@ from __future__ import annotations
|
|
|
33
38
|
import contextlib
|
|
34
39
|
import os
|
|
35
40
|
import time
|
|
41
|
+
import warnings
|
|
36
42
|
from collections.abc import Generator
|
|
37
43
|
from dataclasses import dataclass
|
|
38
44
|
from datetime import datetime, timedelta, timezone
|
|
@@ -50,6 +56,17 @@ DEFAULT_BRANCH_EXPIRY_SECONDS = 600
|
|
|
50
56
|
_MIGRATIONS_NOT_DEFINED = object()
|
|
51
57
|
|
|
52
58
|
|
|
59
|
+
def _get_xdist_worker_id() -> str:
|
|
60
|
+
"""
|
|
61
|
+
Get the pytest-xdist worker ID, or "main" if not running under xdist.
|
|
62
|
+
|
|
63
|
+
When running tests in parallel with pytest-xdist, each worker process
|
|
64
|
+
gets a unique ID (gw0, gw1, gw2, etc.). This is used to create separate
|
|
65
|
+
branches per worker to avoid database state pollution between parallel tests.
|
|
66
|
+
"""
|
|
67
|
+
return os.environ.get("PYTEST_XDIST_WORKER", "main")
|
|
68
|
+
|
|
69
|
+
|
|
53
70
|
def _get_schema_fingerprint(connection_string: str) -> tuple[tuple[Any, ...], ...]:
|
|
54
71
|
"""
|
|
55
72
|
Get a fingerprint of the database schema for change detection.
|
|
@@ -373,8 +390,19 @@ def _create_neon_branch(
|
|
|
373
390
|
)
|
|
374
391
|
|
|
375
392
|
|
|
376
|
-
def _reset_branch_to_parent(
|
|
377
|
-
|
|
393
|
+
def _reset_branch_to_parent(
|
|
394
|
+
branch: NeonBranch, api_key: str, max_retries: int = 3
|
|
395
|
+
) -> None:
|
|
396
|
+
"""Reset a branch to its parent's state using the Neon API.
|
|
397
|
+
|
|
398
|
+
Uses exponential backoff retry logic to handle transient API errors
|
|
399
|
+
that can occur during parallel test execution.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
branch: The branch to reset
|
|
403
|
+
api_key: Neon API key
|
|
404
|
+
max_retries: Maximum number of retry attempts (default: 3)
|
|
405
|
+
"""
|
|
378
406
|
if not branch.parent_id:
|
|
379
407
|
raise RuntimeError(f"Branch {branch.branch_id} has no parent - cannot reset")
|
|
380
408
|
|
|
@@ -383,10 +411,27 @@ def _reset_branch_to_parent(branch: NeonBranch, api_key: str) -> None:
|
|
|
383
411
|
"Authorization": f"Bearer {api_key}",
|
|
384
412
|
"Content-Type": "application/json",
|
|
385
413
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
)
|
|
389
|
-
|
|
414
|
+
|
|
415
|
+
last_error: Exception | None = None
|
|
416
|
+
for attempt in range(max_retries + 1):
|
|
417
|
+
try:
|
|
418
|
+
response = requests.post(
|
|
419
|
+
url,
|
|
420
|
+
headers=headers,
|
|
421
|
+
json={"source_branch_id": branch.parent_id},
|
|
422
|
+
timeout=30,
|
|
423
|
+
)
|
|
424
|
+
response.raise_for_status()
|
|
425
|
+
return # Success
|
|
426
|
+
except requests.RequestException as e:
|
|
427
|
+
last_error = e
|
|
428
|
+
if attempt < max_retries:
|
|
429
|
+
# Exponential backoff: 1s, 2s, 4s
|
|
430
|
+
wait_time = 2**attempt
|
|
431
|
+
time.sleep(wait_time)
|
|
432
|
+
|
|
433
|
+
# All retries exhausted
|
|
434
|
+
raise last_error # type: ignore[misc]
|
|
390
435
|
|
|
391
436
|
|
|
392
437
|
@pytest.fixture(scope="session")
|
|
@@ -493,6 +538,12 @@ def _neon_branch_for_reset(
|
|
|
493
538
|
session, avoiding issues with Python's module caching (e.g., SQLAlchemy
|
|
494
539
|
engines created at import time would otherwise point to stale branches).
|
|
495
540
|
|
|
541
|
+
Parallel Test Support (pytest-xdist):
|
|
542
|
+
When running tests in parallel with pytest-xdist, each worker gets its
|
|
543
|
+
own branch. This prevents database state pollution between tests running
|
|
544
|
+
concurrently on different workers. The worker ID is included in the
|
|
545
|
+
branch name suffix (e.g., "-test-gw0", "-test-gw1").
|
|
546
|
+
|
|
496
547
|
Smart Migration Detection:
|
|
497
548
|
This fixture implements a cost-optimization strategy:
|
|
498
549
|
|
|
@@ -524,30 +575,41 @@ def _neon_branch_for_reset(
|
|
|
524
575
|
# Assume migrations changed something to be safe
|
|
525
576
|
schema_changed = True
|
|
526
577
|
|
|
578
|
+
# Get worker ID for parallel test support
|
|
579
|
+
# Each xdist worker gets its own branch to avoid state pollution
|
|
580
|
+
worker_id = _get_xdist_worker_id()
|
|
581
|
+
branch_suffix = f"-test-{worker_id}"
|
|
582
|
+
|
|
527
583
|
# Only create a child branch if migrations actually modified the schema
|
|
528
|
-
if
|
|
584
|
+
# OR if we're running under xdist (each worker needs its own branch)
|
|
585
|
+
if schema_changed or worker_id != "main":
|
|
529
586
|
yield from _create_neon_branch(
|
|
530
587
|
request,
|
|
531
588
|
parent_branch_id_override=_neon_migration_branch.branch_id,
|
|
532
|
-
branch_name_suffix=
|
|
589
|
+
branch_name_suffix=branch_suffix,
|
|
533
590
|
)
|
|
534
591
|
else:
|
|
535
|
-
# No schema changes - reuse the migration branch directly
|
|
592
|
+
# No schema changes and not parallel - reuse the migration branch directly
|
|
536
593
|
# This saves creating an unnecessary branch
|
|
537
594
|
yield _neon_migration_branch
|
|
538
595
|
|
|
539
596
|
|
|
540
597
|
@pytest.fixture(scope="function")
|
|
541
|
-
def
|
|
598
|
+
def neon_branch_readwrite(
|
|
542
599
|
request: pytest.FixtureRequest,
|
|
543
600
|
_neon_branch_for_reset: NeonBranch,
|
|
544
601
|
) -> Generator[NeonBranch, None, None]:
|
|
545
602
|
"""
|
|
546
|
-
Provide
|
|
603
|
+
Provide a read-write Neon database branch with reset after each test.
|
|
604
|
+
|
|
605
|
+
This is the recommended fixture for tests that modify database state.
|
|
606
|
+
It creates one branch per test session, then resets it to the parent
|
|
607
|
+
branch's state after each test. This provides test isolation with
|
|
608
|
+
~0.5s overhead per test.
|
|
547
609
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
610
|
+
Use this fixture when your tests INSERT, UPDATE, or DELETE data.
|
|
611
|
+
For read-only tests, use ``neon_branch_readonly`` instead for better
|
|
612
|
+
performance (no reset overhead).
|
|
551
613
|
|
|
552
614
|
The branch is automatically deleted after all tests complete, unless
|
|
553
615
|
--neon-keep-branches is specified. Branches also auto-expire after
|
|
@@ -575,11 +637,16 @@ def neon_branch(
|
|
|
575
637
|
|
|
576
638
|
Example::
|
|
577
639
|
|
|
578
|
-
def
|
|
640
|
+
def test_insert_user(neon_branch_readwrite):
|
|
579
641
|
# DATABASE_URL is automatically set
|
|
580
642
|
conn_string = os.environ["DATABASE_URL"]
|
|
581
643
|
# or use directly
|
|
582
|
-
conn_string =
|
|
644
|
+
conn_string = neon_branch_readwrite.connection_string
|
|
645
|
+
|
|
646
|
+
# Insert data - branch will reset after this test
|
|
647
|
+
with psycopg.connect(conn_string) as conn:
|
|
648
|
+
conn.execute("INSERT INTO users (name) VALUES ('test')")
|
|
649
|
+
conn.commit()
|
|
583
650
|
"""
|
|
584
651
|
config = request.config
|
|
585
652
|
api_key = _get_config_value(config, "neon_api_key", "NEON_API_KEY", "neon_api_key")
|
|
@@ -588,8 +655,9 @@ def neon_branch(
|
|
|
588
655
|
if not _neon_branch_for_reset.parent_id:
|
|
589
656
|
pytest.fail(
|
|
590
657
|
f"\n\nBranch {_neon_branch_for_reset.branch_id} has no parent. "
|
|
591
|
-
f"The
|
|
592
|
-
f"
|
|
658
|
+
f"The neon_branch_readwrite fixture requires a parent branch for "
|
|
659
|
+
f"reset.\n\n"
|
|
660
|
+
f"Use neon_branch_readonly if you don't need reset, or specify "
|
|
593
661
|
f"a parent branch with --neon-parent-branch or NEON_PARENT_BRANCH_ID."
|
|
594
662
|
)
|
|
595
663
|
|
|
@@ -608,6 +676,77 @@ def neon_branch(
|
|
|
608
676
|
)
|
|
609
677
|
|
|
610
678
|
|
|
679
|
+
@pytest.fixture(scope="function")
|
|
680
|
+
def neon_branch_readonly(
|
|
681
|
+
_neon_branch_for_reset: NeonBranch,
|
|
682
|
+
) -> NeonBranch:
|
|
683
|
+
"""
|
|
684
|
+
Provide a read-only Neon database branch without reset.
|
|
685
|
+
|
|
686
|
+
This is the recommended fixture for tests that only read data (SELECT queries).
|
|
687
|
+
No branch reset occurs after each test, making it faster than
|
|
688
|
+
``neon_branch_readwrite`` (~0.5s saved per test).
|
|
689
|
+
|
|
690
|
+
Use this fixture when your tests only perform SELECT queries and don't
|
|
691
|
+
modify database state. For tests that INSERT, UPDATE, or DELETE data,
|
|
692
|
+
use ``neon_branch_readwrite`` instead to ensure test isolation.
|
|
693
|
+
|
|
694
|
+
Warning:
|
|
695
|
+
If you accidentally write data using this fixture, subsequent tests
|
|
696
|
+
will see those modifications. The fixture does not enforce read-only
|
|
697
|
+
access at the database level - it simply skips the reset step.
|
|
698
|
+
|
|
699
|
+
The connection string is automatically set in the DATABASE_URL environment
|
|
700
|
+
variable (configurable via --neon-env-var).
|
|
701
|
+
|
|
702
|
+
Requires either:
|
|
703
|
+
- NEON_API_KEY and NEON_PROJECT_ID environment variables, or
|
|
704
|
+
- --neon-api-key and --neon-project-id command line options
|
|
705
|
+
|
|
706
|
+
Yields:
|
|
707
|
+
NeonBranch: Object with branch_id, project_id, connection_string, and host.
|
|
708
|
+
|
|
709
|
+
Example::
|
|
710
|
+
|
|
711
|
+
def test_query_users(neon_branch_readonly):
|
|
712
|
+
# DATABASE_URL is automatically set
|
|
713
|
+
conn_string = os.environ["DATABASE_URL"]
|
|
714
|
+
|
|
715
|
+
# Read-only query - no reset needed after this test
|
|
716
|
+
with psycopg.connect(conn_string) as conn:
|
|
717
|
+
result = conn.execute("SELECT * FROM users").fetchall()
|
|
718
|
+
assert len(result) > 0
|
|
719
|
+
"""
|
|
720
|
+
return _neon_branch_for_reset
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
@pytest.fixture(scope="function")
|
|
724
|
+
def neon_branch(
|
|
725
|
+
request: pytest.FixtureRequest,
|
|
726
|
+
neon_branch_readwrite: NeonBranch,
|
|
727
|
+
) -> Generator[NeonBranch, None, None]:
|
|
728
|
+
"""
|
|
729
|
+
Deprecated: Use ``neon_branch_readwrite`` or ``neon_branch_readonly`` instead.
|
|
730
|
+
|
|
731
|
+
This fixture is an alias for ``neon_branch_readwrite`` and will be removed
|
|
732
|
+
in a future version. Please migrate to the explicit fixture names:
|
|
733
|
+
|
|
734
|
+
- ``neon_branch_readwrite``: For tests that modify data (INSERT/UPDATE/DELETE)
|
|
735
|
+
- ``neon_branch_readonly``: For tests that only read data (SELECT)
|
|
736
|
+
|
|
737
|
+
.. deprecated:: 1.1.0
|
|
738
|
+
Use ``neon_branch_readwrite`` for read-write access with reset,
|
|
739
|
+
or ``neon_branch_readonly`` for read-only access without reset.
|
|
740
|
+
"""
|
|
741
|
+
warnings.warn(
|
|
742
|
+
"neon_branch is deprecated. Use neon_branch_readwrite (for tests that "
|
|
743
|
+
"modify data) or neon_branch_readonly (for read-only tests) instead.",
|
|
744
|
+
DeprecationWarning,
|
|
745
|
+
stacklevel=2,
|
|
746
|
+
)
|
|
747
|
+
yield neon_branch_readwrite
|
|
748
|
+
|
|
749
|
+
|
|
611
750
|
@pytest.fixture(scope="module")
|
|
612
751
|
def neon_branch_shared(
|
|
613
752
|
request: pytest.FixtureRequest,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-neon
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Pytest plugin for Neon database branch isolation in tests
|
|
5
5
|
Project-URL: Homepage, https://github.com/ZainRizvi/pytest-neon
|
|
6
6
|
Project-URL: Repository, https://github.com/ZainRizvi/pytest-neon
|
|
@@ -97,7 +97,7 @@ export NEON_PROJECT_ID="your-project-id"
|
|
|
97
97
|
2. Write tests:
|
|
98
98
|
|
|
99
99
|
```python
|
|
100
|
-
def test_user_creation(
|
|
100
|
+
def test_user_creation(neon_branch_readwrite):
|
|
101
101
|
# DATABASE_URL is automatically set to the test branch
|
|
102
102
|
import psycopg # Your own install
|
|
103
103
|
|
|
@@ -105,6 +105,7 @@ def test_user_creation(neon_branch):
|
|
|
105
105
|
with conn.cursor() as cur:
|
|
106
106
|
cur.execute("INSERT INTO users (email) VALUES ('test@example.com')")
|
|
107
107
|
conn.commit()
|
|
108
|
+
# Branch automatically resets after test - next test sees clean state
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
3. Run tests:
|
|
@@ -115,33 +116,75 @@ pytest
|
|
|
115
116
|
|
|
116
117
|
## Fixtures
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
**Which fixture should I use?**
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
- **Use `neon_branch_readonly`** if your test only reads data (SELECT queries). This is the fastest option with no per-test overhead.
|
|
122
|
+
- **Use `neon_branch_readwrite`** if your test modifies data (INSERT, UPDATE, DELETE). This resets the branch after each test for isolation.
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
### `neon_branch_readonly` (recommended, fastest)
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
**Use this fixture by default** if your tests don't need to write data. It provides the best performance by skipping the branch reset step (~0.5s saved per test), which also reduces API calls and avoids rate limiting issues.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
def test_query_users(neon_branch_readonly):
|
|
130
|
+
# DATABASE_URL is set automatically
|
|
131
|
+
import psycopg
|
|
132
|
+
with psycopg.connect(neon_branch_readonly.connection_string) as conn:
|
|
133
|
+
result = conn.execute("SELECT * FROM users").fetchall()
|
|
134
|
+
assert len(result) >= 0
|
|
135
|
+
# No reset after this test - fast!
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Use this when**:
|
|
139
|
+
- Tests only perform SELECT queries
|
|
140
|
+
- Tests don't modify database state
|
|
141
|
+
- You want maximum performance
|
|
142
|
+
|
|
143
|
+
**Warning**: If you accidentally write data using this fixture, subsequent tests will see those modifications. The fixture does not enforce read-only access at the database level.
|
|
144
|
+
|
|
145
|
+
**Performance**: ~1.5s initial setup per session, **no per-test overhead**. For 10 read-only tests, expect only ~1.5s total overhead (vs ~6.5s with readwrite).
|
|
146
|
+
|
|
147
|
+
### `neon_branch_readwrite` (for write tests)
|
|
148
|
+
|
|
149
|
+
Use this fixture when your tests need to INSERT, UPDATE, or DELETE data. Creates one branch per test session, then resets it to the parent branch's state after each test. This provides test isolation with ~0.5s overhead per test.
|
|
129
150
|
|
|
130
151
|
```python
|
|
131
152
|
import os
|
|
132
153
|
|
|
133
|
-
def
|
|
154
|
+
def test_insert_user(neon_branch_readwrite):
|
|
134
155
|
# DATABASE_URL is set automatically
|
|
135
|
-
assert os.environ["DATABASE_URL"] ==
|
|
156
|
+
assert os.environ["DATABASE_URL"] == neon_branch_readwrite.connection_string
|
|
136
157
|
|
|
137
158
|
# Use with any driver
|
|
138
159
|
import psycopg
|
|
139
|
-
|
|
160
|
+
with psycopg.connect(neon_branch_readwrite.connection_string) as conn:
|
|
161
|
+
conn.execute("INSERT INTO users (name) VALUES ('test')")
|
|
162
|
+
conn.commit()
|
|
163
|
+
# Branch resets after this test - changes won't affect other tests
|
|
140
164
|
```
|
|
141
165
|
|
|
142
|
-
**Performance**: ~1.5s initial setup per session + ~0.5s reset per test. For 10 tests, expect ~6.5s total overhead.
|
|
166
|
+
**Performance**: ~1.5s initial setup per session + ~0.5s reset per test. For 10 write tests, expect ~6.5s total overhead.
|
|
143
167
|
|
|
144
|
-
### `
|
|
168
|
+
### `NeonBranch` dataclass
|
|
169
|
+
|
|
170
|
+
Both fixtures return a `NeonBranch` dataclass with:
|
|
171
|
+
|
|
172
|
+
- `branch_id`: The Neon branch ID
|
|
173
|
+
- `project_id`: The Neon project ID
|
|
174
|
+
- `connection_string`: Full PostgreSQL connection URI
|
|
175
|
+
- `host`: The database host
|
|
176
|
+
- `parent_id`: The parent branch ID (used for resets)
|
|
177
|
+
|
|
178
|
+
### `neon_branch` (deprecated)
|
|
179
|
+
|
|
180
|
+
> **Deprecated**: Use `neon_branch_readwrite` or `neon_branch_readonly` instead.
|
|
181
|
+
|
|
182
|
+
This fixture is an alias for `neon_branch_readwrite` and will emit a deprecation warning. Migrate to the explicit fixture names for clarity:
|
|
183
|
+
|
|
184
|
+
- `neon_branch_readwrite`: For tests that modify data (INSERT/UPDATE/DELETE)
|
|
185
|
+
- `neon_branch_readonly`: For tests that only read data (SELECT)
|
|
186
|
+
|
|
187
|
+
### `neon_branch_shared` (module-scoped, no isolation)
|
|
145
188
|
|
|
146
189
|
Creates one branch per test module and shares it across all tests without resetting. This is the fastest option but tests can see each other's data modifications.
|
|
147
190
|
|
|
@@ -207,19 +250,21 @@ def test_query(neon_engine):
|
|
|
207
250
|
|
|
208
251
|
### Using Your Own SQLAlchemy Engine
|
|
209
252
|
|
|
210
|
-
If you have a module-level SQLAlchemy engine (common pattern)
|
|
253
|
+
If you have a module-level SQLAlchemy engine (common pattern) and use `neon_branch_readwrite`, you **must** use `pool_pre_ping=True`:
|
|
211
254
|
|
|
212
255
|
```python
|
|
213
256
|
# database.py
|
|
214
257
|
from sqlalchemy import create_engine
|
|
215
258
|
from config import DATABASE_URL
|
|
216
259
|
|
|
217
|
-
# pool_pre_ping=True is REQUIRED
|
|
260
|
+
# pool_pre_ping=True is REQUIRED when using neon_branch_readwrite
|
|
218
261
|
# It verifies connections are alive before using them
|
|
219
262
|
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
|
220
263
|
```
|
|
221
264
|
|
|
222
|
-
**Why?** After each test,
|
|
265
|
+
**Why?** After each test, `neon_branch_readwrite` resets the branch which terminates server-side connections. Without `pool_pre_ping`, SQLAlchemy may try to reuse a dead pooled connection, causing `SSL connection has been closed unexpectedly` errors.
|
|
266
|
+
|
|
267
|
+
**Note**: If you only use `neon_branch_readonly`, `pool_pre_ping` is not required since no resets occur.
|
|
223
268
|
|
|
224
269
|
This is also a best practice for any cloud database (Neon, RDS, etc.) where connections can be terminated externally.
|
|
225
270
|
|
|
@@ -435,7 +480,7 @@ Branches use copy-on-write storage, so you only pay for data that differs from t
|
|
|
435
480
|
|
|
436
481
|
### What Reset Does
|
|
437
482
|
|
|
438
|
-
The `
|
|
483
|
+
The `neon_branch_readwrite` fixture uses Neon's branch restore API to reset database state after each test:
|
|
439
484
|
|
|
440
485
|
- **Data changes are reverted**: All INSERT, UPDATE, DELETE operations are undone
|
|
441
486
|
- **Schema changes are reverted**: CREATE TABLE, ALTER TABLE, DROP TABLE, etc. are undone
|
|
@@ -444,16 +489,26 @@ The `neon_branch` fixture uses Neon's branch restore API to reset database state
|
|
|
444
489
|
|
|
445
490
|
This is similar to database transactions but at the branch level.
|
|
446
491
|
|
|
447
|
-
##
|
|
492
|
+
## Parallel Test Execution (pytest-xdist)
|
|
493
|
+
|
|
494
|
+
This plugin supports parallel test execution with [pytest-xdist](https://pytest-xdist.readthedocs.io/). Each xdist worker automatically gets its own isolated branch.
|
|
448
495
|
|
|
449
|
-
|
|
496
|
+
```bash
|
|
497
|
+
# Run tests in parallel with 4 workers
|
|
498
|
+
pip install pytest-xdist
|
|
499
|
+
pytest -n 4
|
|
500
|
+
```
|
|
450
501
|
|
|
451
|
-
|
|
502
|
+
**How it works:**
|
|
503
|
+
- Each xdist worker (gw0, gw1, gw2, etc.) creates its own branch
|
|
504
|
+
- Branches are named with the worker ID suffix (e.g., `-test-gw0`, `-test-gw1`)
|
|
505
|
+
- Workers run tests in parallel without database state interference
|
|
506
|
+
- All branches are cleaned up after the test session
|
|
452
507
|
|
|
453
|
-
|
|
454
|
-
-
|
|
455
|
-
-
|
|
456
|
-
-
|
|
508
|
+
**Cost implications:**
|
|
509
|
+
- Running with `-n 4` creates 4 branches (one per worker) plus the migration branch
|
|
510
|
+
- Choose your parallelism level based on your Neon plan's branch limits
|
|
511
|
+
- Each worker's branch is reset after each test using the fast reset operation (~0.5s)
|
|
457
512
|
|
|
458
513
|
## Troubleshooting
|
|
459
514
|
|
|
@@ -472,12 +527,12 @@ pip install pytest-neon[psycopg2]
|
|
|
472
527
|
pip install pytest-neon[sqlalchemy]
|
|
473
528
|
```
|
|
474
529
|
|
|
475
|
-
Or use the core
|
|
530
|
+
Or use the core fixtures with your own driver:
|
|
476
531
|
|
|
477
532
|
```python
|
|
478
|
-
def test_example(
|
|
533
|
+
def test_example(neon_branch_readwrite):
|
|
479
534
|
import my_preferred_driver
|
|
480
|
-
conn = my_preferred_driver.connect(
|
|
535
|
+
conn = my_preferred_driver.connect(neon_branch_readwrite.connection_string)
|
|
481
536
|
```
|
|
482
537
|
|
|
483
538
|
### "Neon API key not configured"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pytest_neon/__init__.py,sha256=69SoGL2ciE38uvrXpEQPXiBFDkbWct7hl_k9MmswcSU,398
|
|
2
|
+
pytest_neon/plugin.py,sha256=rb9CgOqle-sQHHoyFO5xDZ5wzrHx_4BLygiVywEYGbM,34949
|
|
3
|
+
pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pytest_neon-2.1.0.dist-info/METADATA,sha256=ebdenaxT8v4GQeepZCglp04mb92iwJxp1PMnnS-9Nqs,18734
|
|
5
|
+
pytest_neon-2.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
pytest_neon-2.1.0.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
|
|
7
|
+
pytest_neon-2.1.0.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
|
|
8
|
+
pytest_neon-2.1.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pytest_neon/__init__.py,sha256=6-QHwHgghlOfVqYOMZqlqYYCPPyzaqSlWF2fwgzS7Yo,398
|
|
2
|
-
pytest_neon/plugin.py,sha256=6wcZe9E90y2kya4wM0ur9EtUhqWFUll_ebN4xgsogPk,29601
|
|
3
|
-
pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pytest_neon-1.0.0.dist-info/METADATA,sha256=C0ZIKFJNBNr_n9yyhUvGgM8uLhKp5uZbYPb7wzKCF6M,16057
|
|
5
|
-
pytest_neon-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
-
pytest_neon-1.0.0.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
|
|
7
|
-
pytest_neon-1.0.0.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
|
|
8
|
-
pytest_neon-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|