pytest-neon 2.2.2__tar.gz → 2.3.1__tar.gz
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-2.3.1/CLAUDE.md +120 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/PKG-INFO +150 -76
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/README.md +149 -75
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/pyproject.toml +1 -1
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/src/pytest_neon/__init__.py +1 -1
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/src/pytest_neon/plugin.py +922 -292
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/conftest.py +27 -2
- pytest_neon-2.3.1/tests/test_dirty_isolated_fixtures.py +400 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_readwrite_readonly_fixtures.py +129 -2
- pytest_neon-2.3.1/tests/test_service_classes.py +119 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/uv.lock +1 -1
- pytest_neon-2.2.2/CLAUDE.md +0 -89
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.config/wt.toml +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.env.example +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.github/workflows/release.yml +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.github/workflows/tests.yml +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.gitignore +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/.neon +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/LICENSE +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/src/pytest_neon/py.typed +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_branch_lifecycle.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_branch_name_prefix.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_cli_options.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_default_branch_safety.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_env_var.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_fixture_errors.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_integration.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_migrations.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_reset_behavior.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_skip_behavior.py +0 -0
- {pytest_neon-2.2.2 → pytest_neon-2.3.1}/tests/test_xdist_worker_support.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Claude Code Instructions for pytest-neon
|
|
2
|
+
|
|
3
|
+
## Understanding the Plugin
|
|
4
|
+
|
|
5
|
+
Read `README.md` for complete documentation on how to use this plugin, including fixtures, configuration options, and migration support.
|
|
6
|
+
|
|
7
|
+
## Project Overview
|
|
8
|
+
|
|
9
|
+
This is a pytest plugin that provides isolated Neon database branches for integration testing. Each test gets isolated database state via branch reset after each test.
|
|
10
|
+
|
|
11
|
+
## Key Architecture
|
|
12
|
+
|
|
13
|
+
- **Entry point**: `src/pytest_neon/plugin.py` - Contains all fixtures and pytest hooks
|
|
14
|
+
- **Migration fixture**: `_neon_migration_branch` - Session-scoped, parent for all test branches
|
|
15
|
+
- **User migration hook**: `neon_apply_migrations` - Session-scoped no-op, users override to run migrations
|
|
16
|
+
- **Core fixtures**:
|
|
17
|
+
- `neon_branch_readonly` - Session-scoped, uses true read_only endpoint (enforced read-only)
|
|
18
|
+
- `neon_branch_dirty` - Session-scoped, shared across ALL xdist workers (fast, shared state)
|
|
19
|
+
- `neon_branch_isolated` - Function-scoped, per-worker branch with reset after each test (recommended for writes)
|
|
20
|
+
- `neon_branch_readwrite` - Deprecated alias for `neon_branch_isolated`
|
|
21
|
+
- `neon_branch` - Deprecated alias for `neon_branch_isolated`
|
|
22
|
+
- **Shared fixture**: `neon_branch_shared` - Module-scoped, no reset between tests
|
|
23
|
+
- **Convenience fixtures**: `neon_connection`, `neon_connection_psycopg`, `neon_engine` - Optional, require extras
|
|
24
|
+
|
|
25
|
+
## Branch Hierarchy
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Parent Branch (configured or project default)
|
|
29
|
+
└── Migration Branch (session-scoped, read_write endpoint)
|
|
30
|
+
│ ↑ migrations run here ONCE
|
|
31
|
+
│
|
|
32
|
+
├── Read-only Endpoint (read_only endpoint ON migration branch)
|
|
33
|
+
│ ↑ neon_branch_readonly uses this (enforced read-only)
|
|
34
|
+
│
|
|
35
|
+
├── Dirty Branch (session-scoped child, shared across ALL workers)
|
|
36
|
+
│ ↑ neon_branch_dirty uses this
|
|
37
|
+
│
|
|
38
|
+
└── Isolated Branch (one per xdist worker, lazily created)
|
|
39
|
+
↑ neon_branch_isolated uses this, reset after each test
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Dependencies
|
|
43
|
+
|
|
44
|
+
- Core: `pytest`, `neon-api`, `requests`, `filelock`
|
|
45
|
+
- Optional extras: `psycopg2`, `psycopg`, `sqlalchemy` - for convenience fixtures
|
|
46
|
+
|
|
47
|
+
## Important Patterns
|
|
48
|
+
|
|
49
|
+
### Modular Architecture
|
|
50
|
+
|
|
51
|
+
The plugin uses a service-oriented architecture for testability:
|
|
52
|
+
|
|
53
|
+
- **NeonConfig**: Dataclass for configuration extraction from pytest config
|
|
54
|
+
- **NeonBranchManager**: Manages all Neon API operations (branch create/delete, endpoint create, password reset)
|
|
55
|
+
- **XdistCoordinator**: Handles worker synchronization with file locks and JSON caching
|
|
56
|
+
- **EnvironmentManager**: Manages DATABASE_URL environment variable lifecycle
|
|
57
|
+
|
|
58
|
+
### Fixture Scopes
|
|
59
|
+
- `_neon_config`: `scope="session"` - Configuration extracted from pytest config
|
|
60
|
+
- `_neon_branch_manager`: `scope="session"` - Branch lifecycle manager
|
|
61
|
+
- `_neon_xdist_coordinator`: `scope="session"` - Worker synchronization
|
|
62
|
+
- `_neon_migration_branch`: `scope="session"` - Parent for all test branches, migrations run here
|
|
63
|
+
- `neon_apply_migrations`: `scope="session"` - User overrides to run migrations
|
|
64
|
+
- `_neon_migrations_synchronized`: `scope="session"` - Signals migration completion across workers
|
|
65
|
+
- `_neon_dirty_branch`: `scope="session"` - Internal, shared across ALL workers
|
|
66
|
+
- `_neon_readonly_endpoint`: `scope="session"` - Internal, read_only endpoint on migration branch
|
|
67
|
+
- `_neon_isolated_branch`: `scope="session"` - Internal, one per xdist worker
|
|
68
|
+
- `neon_branch_readonly`: `scope="session"` - User-facing, true read-only access
|
|
69
|
+
- `neon_branch_dirty`: `scope="session"` - User-facing, shared state across workers
|
|
70
|
+
- `neon_branch_isolated`: `scope="function"` - User-facing, reset after each test
|
|
71
|
+
- `neon_branch_readwrite`: `scope="function"` - Deprecated alias for isolated
|
|
72
|
+
- `neon_branch`: `scope="function"` - Deprecated alias for isolated
|
|
73
|
+
- `neon_branch_shared`: `scope="module"` - One branch per test file, no reset
|
|
74
|
+
- Connection fixtures: `scope="function"` (default) - Fresh connection per test
|
|
75
|
+
|
|
76
|
+
### Environment Variable Handling
|
|
77
|
+
The `EnvironmentManager` class handles `DATABASE_URL` lifecycle:
|
|
78
|
+
- Sets environment variable when fixture starts
|
|
79
|
+
- Saves original value for restoration
|
|
80
|
+
- Restores original value (or removes) when fixture ends
|
|
81
|
+
|
|
82
|
+
### xdist Worker Synchronization
|
|
83
|
+
The `XdistCoordinator` handles sharing resources across workers:
|
|
84
|
+
- Uses file locks (`filelock`) for coordination
|
|
85
|
+
- Stores shared resource data in JSON files
|
|
86
|
+
- `coordinate_resource()` ensures only one worker creates shared resources
|
|
87
|
+
- `wait_for_signal()` / `send_signal()` for migration synchronization
|
|
88
|
+
|
|
89
|
+
### Error Messages
|
|
90
|
+
Convenience fixtures use `pytest.fail()` with detailed, formatted error messages when dependencies are missing. Keep this pattern - users need clear guidance on how to fix import errors.
|
|
91
|
+
|
|
92
|
+
## Documentation
|
|
93
|
+
|
|
94
|
+
Important help text should be documented in BOTH:
|
|
95
|
+
1. **README.md** - Full user-facing documentation
|
|
96
|
+
2. **Module/fixture docstrings** - So `help(pytest_neon)` shows useful info
|
|
97
|
+
|
|
98
|
+
The module docstring in `plugin.py` should include key usage notes (like the SQLAlchemy `pool_pre_ping=True` requirement). Keep docstrings and README in sync.
|
|
99
|
+
|
|
100
|
+
## Commit Messages
|
|
101
|
+
- Do NOT add Claude attribution or Co-Authored-By lines
|
|
102
|
+
- Keep commits clean and descriptive
|
|
103
|
+
|
|
104
|
+
## Testing
|
|
105
|
+
|
|
106
|
+
Run tests with:
|
|
107
|
+
```bash
|
|
108
|
+
uv run pytest tests/ -v
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Tests in `tests/` use `pytester` for testing pytest plugins. The plugin itself can be tested without a real Neon connection by mocking `NeonAPI`.
|
|
112
|
+
|
|
113
|
+
## Publishing
|
|
114
|
+
|
|
115
|
+
**Always use the GitHub Actions release workflow** - do not manually bump versions:
|
|
116
|
+
1. Go to Actions → Release → Run workflow
|
|
117
|
+
2. Choose patch/minor/major
|
|
118
|
+
3. Workflow bumps version, commits, tags, and publishes to PyPI
|
|
119
|
+
|
|
120
|
+
Package name on PyPI: `pytest-neon`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-neon
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -98,7 +98,7 @@ export NEON_PROJECT_ID="your-project-id"
|
|
|
98
98
|
2. Write tests:
|
|
99
99
|
|
|
100
100
|
```python
|
|
101
|
-
def test_user_creation(
|
|
101
|
+
def test_user_creation(neon_branch_isolated):
|
|
102
102
|
# DATABASE_URL is automatically set to the test branch
|
|
103
103
|
import psycopg # Your own install
|
|
104
104
|
|
|
@@ -119,12 +119,40 @@ pytest
|
|
|
119
119
|
|
|
120
120
|
**Which fixture should I use?**
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
| Fixture | Scope | Reset | Best For | Overhead |
|
|
123
|
+
|---------|-------|-------|----------|----------|
|
|
124
|
+
| `neon_branch_readonly` | session | None | Read-only tests (SELECT) - enforced | ~0s/test |
|
|
125
|
+
| `neon_branch_dirty` | session | None | Fast writes, shared state OK | ~0s/test |
|
|
126
|
+
| `neon_branch_isolated` | function | After each test | Write tests needing isolation | ~0.5s/test |
|
|
127
|
+
| `neon_branch_shared` | module | None | Module-level shared state | ~0s/test |
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
**Quick guide:**
|
|
130
|
+
- **Use `neon_branch_readonly`** if your test only reads data (SELECT queries). This uses a true read-only endpoint that enforces read-only access at the database level. Any write attempt will fail with a database error.
|
|
131
|
+
- **Use `neon_branch_dirty`** if your tests write data but can tolerate shared state across the session. Fast because no reset or branch creation per test.
|
|
132
|
+
- **Use `neon_branch_isolated`** if your test modifies data and needs isolation from other tests. Resets the branch after each test.
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
### Architecture
|
|
135
|
+
|
|
136
|
+
The plugin creates a branch hierarchy to efficiently support all fixture types:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Parent Branch (configured or project default)
|
|
140
|
+
└── Migration Branch (session-scoped, read_write endpoint)
|
|
141
|
+
│ ↑ migrations run here ONCE
|
|
142
|
+
│
|
|
143
|
+
├── Read-only Endpoint (read_only endpoint ON migration branch)
|
|
144
|
+
│ ↑ neon_branch_readonly uses this (enforced read-only)
|
|
145
|
+
│
|
|
146
|
+
├── Dirty Branch (session-scoped child, shared across ALL workers)
|
|
147
|
+
│ ↑ neon_branch_dirty uses this
|
|
148
|
+
│
|
|
149
|
+
└── Isolated Branch (one per xdist worker, lazily created)
|
|
150
|
+
↑ neon_branch_isolated uses this, reset after each test
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `neon_branch_readonly` (session-scoped, enforced read-only)
|
|
154
|
+
|
|
155
|
+
**Use this fixture** for tests that only read data. It uses a true `read_only` endpoint on the migration branch, which enforces read-only access at the database level. Any attempt to INSERT, UPDATE, or DELETE will result in a database error.
|
|
128
156
|
|
|
129
157
|
```python
|
|
130
158
|
def test_query_users(neon_branch_readonly):
|
|
@@ -133,57 +161,115 @@ def test_query_users(neon_branch_readonly):
|
|
|
133
161
|
with psycopg.connect(neon_branch_readonly.connection_string) as conn:
|
|
134
162
|
result = conn.execute("SELECT * FROM users").fetchall()
|
|
135
163
|
assert len(result) >= 0
|
|
136
|
-
|
|
164
|
+
|
|
165
|
+
# This would fail with a database error:
|
|
166
|
+
# conn.execute("INSERT INTO users (name) VALUES ('test')")
|
|
137
167
|
```
|
|
138
168
|
|
|
139
169
|
**Use this when**:
|
|
140
170
|
- Tests only perform SELECT queries
|
|
141
171
|
- Tests don't modify database state
|
|
142
|
-
- You want maximum performance
|
|
172
|
+
- You want maximum performance with true enforcement
|
|
143
173
|
|
|
144
|
-
**
|
|
174
|
+
**Session-scoped**: The endpoint is created once and shared across all tests and workers.
|
|
145
175
|
|
|
146
|
-
**Performance**: ~1.5s initial setup per session, **no per-test overhead**. For 10 read-only tests, expect only ~1.5s total overhead
|
|
176
|
+
**Performance**: ~1.5s initial setup per session, **no per-test overhead**. For 10 read-only tests, expect only ~1.5s total overhead.
|
|
147
177
|
|
|
148
|
-
### `
|
|
178
|
+
### `neon_branch_dirty` (session-scoped, shared state)
|
|
149
179
|
|
|
150
|
-
Use this fixture when your tests
|
|
180
|
+
Use this fixture when your tests write data but can tolerate shared state across the session. All tests share the same branch and writes persist (no cleanup between tests). This is faster than `neon_branch_isolated` because there's no reset overhead.
|
|
151
181
|
|
|
152
182
|
```python
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def test_insert_user(neon_branch_readwrite):
|
|
183
|
+
def test_insert_user(neon_branch_dirty):
|
|
156
184
|
# DATABASE_URL is set automatically
|
|
157
|
-
|
|
185
|
+
import psycopg
|
|
186
|
+
with psycopg.connect(neon_branch_dirty.connection_string) as conn:
|
|
187
|
+
conn.execute("INSERT INTO users (name) VALUES ('test')")
|
|
188
|
+
conn.commit()
|
|
189
|
+
# Data persists - subsequent tests will see this user
|
|
190
|
+
|
|
191
|
+
def test_count_users(neon_branch_dirty):
|
|
192
|
+
# This test sees data from previous tests
|
|
193
|
+
import psycopg
|
|
194
|
+
with psycopg.connect(neon_branch_dirty.connection_string) as conn:
|
|
195
|
+
result = conn.execute("SELECT COUNT(*) FROM users").fetchone()
|
|
196
|
+
# Count includes users from previous tests
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Use this when**:
|
|
200
|
+
- Most tests can share database state without interference
|
|
201
|
+
- You want maximum performance with minimal API calls
|
|
202
|
+
- You manually manage test data cleanup if needed
|
|
203
|
+
- You're combining it with `neon_branch_isolated` for specific tests that need clean state
|
|
204
|
+
|
|
205
|
+
**Warning**: Data written by one test WILL be visible to subsequent tests AND to other xdist workers. This is truly shared - use `neon_branch_isolated` for tests that require guaranteed clean state.
|
|
206
|
+
|
|
207
|
+
**pytest-xdist note**: ALL workers share the same dirty branch. Concurrent writes from different workers may conflict. This is "dirty" by design - for isolation, use `neon_branch_isolated`.
|
|
208
|
+
|
|
209
|
+
**Performance**: ~1.5s initial setup per session, **no per-test overhead**. For 10 write tests, expect only ~1.5s total overhead.
|
|
158
210
|
|
|
159
|
-
|
|
211
|
+
### `neon_branch_isolated` (function-scoped, full isolation)
|
|
212
|
+
|
|
213
|
+
Use this fixture when your test modifies data and needs isolation from other tests. Each xdist worker has its own branch, and the branch is reset to the migration state after each test.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
def test_insert_user(neon_branch_isolated):
|
|
217
|
+
# DATABASE_URL is set automatically
|
|
160
218
|
import psycopg
|
|
161
|
-
with psycopg.connect(
|
|
219
|
+
with psycopg.connect(neon_branch_isolated.connection_string) as conn:
|
|
220
|
+
# Guaranteed clean state - no data from other tests
|
|
221
|
+
# (only migration/seed data if you defined neon_apply_migrations)
|
|
222
|
+
result = conn.execute("SELECT COUNT(*) FROM users").fetchone()
|
|
223
|
+
initial_count = result[0]
|
|
224
|
+
|
|
162
225
|
conn.execute("INSERT INTO users (name) VALUES ('test')")
|
|
163
226
|
conn.commit()
|
|
164
|
-
|
|
227
|
+
|
|
228
|
+
# Verify our insert worked
|
|
229
|
+
result = conn.execute("SELECT COUNT(*) FROM users").fetchone()
|
|
230
|
+
assert result[0] == initial_count + 1
|
|
231
|
+
# Branch resets after this test - next test sees clean state
|
|
165
232
|
```
|
|
166
233
|
|
|
167
|
-
**
|
|
234
|
+
**Use this when**:
|
|
235
|
+
- A test modifies database state (INSERT, UPDATE, DELETE)
|
|
236
|
+
- Test isolation is important
|
|
237
|
+
- You're combining it with `neon_branch_dirty` for most tests but need isolation for specific ones
|
|
238
|
+
|
|
239
|
+
**SQLAlchemy Users**: If you create your own engine (not using the `neon_engine` fixture), you MUST use `pool_pre_ping=True`:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Branch resets terminate server-side connections. Without `pool_pre_ping`, SQLAlchemy may reuse dead pooled connections, causing SSL errors.
|
|
246
|
+
|
|
247
|
+
**pytest-xdist note**: Each worker has its own isolated branch. Resets only affect that worker's branch, so workers don't interfere with each other.
|
|
248
|
+
|
|
249
|
+
**Performance**: ~1.5s initial setup per session per worker + ~0.5s reset per test. For 10 write tests, expect ~6.5s total overhead.
|
|
168
250
|
|
|
169
251
|
### `NeonBranch` dataclass
|
|
170
252
|
|
|
171
|
-
|
|
253
|
+
All fixtures return a `NeonBranch` dataclass with:
|
|
172
254
|
|
|
173
255
|
- `branch_id`: The Neon branch ID
|
|
174
256
|
- `project_id`: The Neon project ID
|
|
175
257
|
- `connection_string`: Full PostgreSQL connection URI
|
|
176
258
|
- `host`: The database host
|
|
177
259
|
- `parent_id`: The parent branch ID (used for resets)
|
|
260
|
+
- `endpoint_id`: The endpoint ID (for cleanup)
|
|
178
261
|
|
|
179
|
-
### `
|
|
262
|
+
### `neon_branch_readwrite` (deprecated)
|
|
180
263
|
|
|
181
|
-
> **Deprecated**: Use `
|
|
264
|
+
> **Deprecated**: Use `neon_branch_isolated` instead.
|
|
265
|
+
|
|
266
|
+
This fixture is an alias for `neon_branch_isolated` and will emit a deprecation warning.
|
|
267
|
+
|
|
268
|
+
### `neon_branch` (deprecated)
|
|
182
269
|
|
|
183
|
-
|
|
270
|
+
> **Deprecated**: Use `neon_branch_isolated`, `neon_branch_dirty`, or `neon_branch_readonly` instead.
|
|
184
271
|
|
|
185
|
-
|
|
186
|
-
- `neon_branch_readonly`: For tests that only read data (SELECT)
|
|
272
|
+
This fixture is an alias for `neon_branch_isolated` and will emit a deprecation warning.
|
|
187
273
|
|
|
188
274
|
### `neon_branch_shared` (module-scoped, no isolation)
|
|
189
275
|
|
|
@@ -251,21 +337,21 @@ def test_query(neon_engine):
|
|
|
251
337
|
|
|
252
338
|
### Using Your Own SQLAlchemy Engine
|
|
253
339
|
|
|
254
|
-
If you have a module-level SQLAlchemy engine (common pattern) and use `
|
|
340
|
+
If you have a module-level SQLAlchemy engine (common pattern) and use `neon_branch_isolated`, you **must** use `pool_pre_ping=True`:
|
|
255
341
|
|
|
256
342
|
```python
|
|
257
343
|
# database.py
|
|
258
344
|
from sqlalchemy import create_engine
|
|
259
345
|
from config import DATABASE_URL
|
|
260
346
|
|
|
261
|
-
# pool_pre_ping=True is REQUIRED when using
|
|
347
|
+
# pool_pre_ping=True is REQUIRED when using neon_branch_isolated
|
|
262
348
|
# It verifies connections are alive before using them
|
|
263
349
|
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
|
264
350
|
```
|
|
265
351
|
|
|
266
|
-
**Why?** After each test, `
|
|
352
|
+
**Why?** After each test, `neon_branch_isolated` 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.
|
|
267
353
|
|
|
268
|
-
**Note**: If you only use `neon_branch_readonly`, `pool_pre_ping` is not required since no resets occur.
|
|
354
|
+
**Note**: If you only use `neon_branch_readonly` or `neon_branch_dirty`, `pool_pre_ping` is not required since no resets occur.
|
|
269
355
|
|
|
270
356
|
This is also a best practice for any cloud database (Neon, RDS, etc.) where connections can be terminated externally.
|
|
271
357
|
|
|
@@ -273,41 +359,18 @@ This is also a best practice for any cloud database (Neon, RDS, etc.) where conn
|
|
|
273
359
|
|
|
274
360
|
pytest-neon supports running migrations once before tests, with all test resets preserving the migrated state.
|
|
275
361
|
|
|
276
|
-
### Smart Migration Detection
|
|
277
|
-
|
|
278
|
-
The plugin automatically detects whether migrations actually modified the database schema. This optimization:
|
|
279
|
-
|
|
280
|
-
- **Saves Neon costs**: No extra branch created when migrations don't change anything
|
|
281
|
-
- **Saves branch slots**: Neon projects have branch limits; this avoids wasting them
|
|
282
|
-
- **Zero configuration**: Works automatically with any migration tool
|
|
283
|
-
|
|
284
|
-
**When a second branch is created:**
|
|
285
|
-
- Only when `neon_apply_migrations` is overridden AND the schema actually changes
|
|
286
|
-
|
|
287
|
-
**When only one branch is used:**
|
|
288
|
-
- If you don't override `neon_apply_migrations` (no migrations defined)
|
|
289
|
-
- If your migrations are already applied (schema unchanged)
|
|
290
|
-
|
|
291
|
-
The detection works by comparing a fingerprint of `information_schema.columns` before and after migrations run.
|
|
292
|
-
|
|
293
362
|
### How It Works
|
|
294
363
|
|
|
295
|
-
|
|
364
|
+
The plugin always creates a migration branch from your configured parent:
|
|
296
365
|
|
|
297
366
|
```
|
|
298
367
|
Parent Branch (your configured parent)
|
|
299
368
|
└── Migration Branch (session-scoped)
|
|
300
369
|
│ ↑ migrations run here ONCE
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
When no schema changes occur, the plugin uses a single-branch architecture:
|
|
306
|
-
|
|
307
|
-
```
|
|
308
|
-
Parent Branch (your configured parent)
|
|
309
|
-
└── Migration/Test Branch (session-scoped)
|
|
310
|
-
↑ resets to parent after each test
|
|
370
|
+
│
|
|
371
|
+
├── Read-only Endpoint (for neon_branch_readonly)
|
|
372
|
+
├── Dirty Branch (for neon_branch_dirty)
|
|
373
|
+
└── Isolated Branches (for neon_branch_isolated)
|
|
311
374
|
```
|
|
312
375
|
|
|
313
376
|
This means:
|
|
@@ -470,18 +533,19 @@ jobs:
|
|
|
470
533
|
|
|
471
534
|
## How It Works
|
|
472
535
|
|
|
473
|
-
1. At the start of the test session, the plugin creates a
|
|
474
|
-
2.
|
|
475
|
-
3.
|
|
476
|
-
4.
|
|
477
|
-
5.
|
|
478
|
-
6.
|
|
536
|
+
1. At the start of the test session, the plugin creates a migration branch from your parent branch
|
|
537
|
+
2. If defined, migrations run once on the migration branch
|
|
538
|
+
3. Test branches (dirty, isolated) are created as children of the migration branch
|
|
539
|
+
4. `DATABASE_URL` is set to point to the appropriate branch for each fixture
|
|
540
|
+
5. For `neon_branch_isolated`, the branch is reset after each test (~0.5s)
|
|
541
|
+
6. After all tests complete, branches are deleted
|
|
542
|
+
7. As a safety net, branches auto-expire after 10 minutes even if cleanup fails
|
|
479
543
|
|
|
480
544
|
Branches use copy-on-write storage, so you only pay for data that differs from the parent branch.
|
|
481
545
|
|
|
482
546
|
### What Reset Does
|
|
483
547
|
|
|
484
|
-
The `
|
|
548
|
+
The `neon_branch_isolated` fixture uses Neon's branch restore API to reset database state after each test:
|
|
485
549
|
|
|
486
550
|
- **Data changes are reverted**: All INSERT, UPDATE, DELETE operations are undone
|
|
487
551
|
- **Schema changes are reverted**: CREATE TABLE, ALTER TABLE, DROP TABLE, etc. are undone
|
|
@@ -499,15 +563,16 @@ pytest-[git-branch]-[random]-[suffix]
|
|
|
499
563
|
```
|
|
500
564
|
|
|
501
565
|
**Examples:**
|
|
502
|
-
- `pytest-main-a1b2-
|
|
503
|
-
- `pytest-feature-auth-c3d4-
|
|
504
|
-
- `pytest-a1b2-
|
|
566
|
+
- `pytest-main-a1b2-migration` - Migration branch from `main`
|
|
567
|
+
- `pytest-feature-auth-c3d4-dirty` - Dirty branch from `feature/auth`
|
|
568
|
+
- `pytest-main-a1b2-isolated-gw0` - Isolated branch for xdist worker 0
|
|
569
|
+
- `pytest-a1b2-migration` - When not in a git repo
|
|
505
570
|
|
|
506
571
|
The git branch name is sanitized (only `a-z`, `0-9`, `-`, `_` allowed) and truncated to 15 characters. This makes it easy to identify orphaned branches in the Neon console.
|
|
507
572
|
|
|
508
573
|
## Parallel Test Execution (pytest-xdist)
|
|
509
574
|
|
|
510
|
-
This plugin supports parallel test execution with [pytest-xdist](https://pytest-xdist.readthedocs.io/).
|
|
575
|
+
This plugin supports parallel test execution with [pytest-xdist](https://pytest-xdist.readthedocs.io/).
|
|
511
576
|
|
|
512
577
|
```bash
|
|
513
578
|
# Run tests in parallel with 4 workers
|
|
@@ -516,15 +581,24 @@ pytest -n 4
|
|
|
516
581
|
```
|
|
517
582
|
|
|
518
583
|
**How it works:**
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
584
|
+
|
|
585
|
+
| Fixture | xdist Behavior |
|
|
586
|
+
|---------|----------------|
|
|
587
|
+
| `neon_branch_readonly` | Shared across ALL workers (read-only endpoint) |
|
|
588
|
+
| `neon_branch_dirty` | Shared across ALL workers (concurrent writes possible) |
|
|
589
|
+
| `neon_branch_isolated` | One branch per worker (e.g., `-isolated-gw0`, `-isolated-gw1`) |
|
|
590
|
+
|
|
591
|
+
**Key points:**
|
|
592
|
+
- The migration branch is created once and shared across all workers
|
|
593
|
+
- `neon_branch_readonly` and `neon_branch_dirty` share resources across workers
|
|
594
|
+
- `neon_branch_isolated` creates one branch per worker for isolation
|
|
595
|
+
- Workers run tests in parallel without database state interference (when using isolated)
|
|
522
596
|
- All branches are cleaned up after the test session
|
|
523
597
|
|
|
524
598
|
**Cost implications:**
|
|
525
|
-
- Running with `-n 4` creates 4 branches (
|
|
599
|
+
- Running with `-n 4` creates: 1 migration branch + 1 dirty branch + 4 isolated branches (if all workers use isolated)
|
|
526
600
|
- Choose your parallelism level based on your Neon plan's branch limits
|
|
527
|
-
- Each worker's branch is reset after each test using the fast reset operation (~0.5s)
|
|
601
|
+
- Each worker's isolated branch is reset after each test using the fast reset operation (~0.5s)
|
|
528
602
|
|
|
529
603
|
## Troubleshooting
|
|
530
604
|
|
|
@@ -546,9 +620,9 @@ pip install pytest-neon[sqlalchemy]
|
|
|
546
620
|
Or use the core fixtures with your own driver:
|
|
547
621
|
|
|
548
622
|
```python
|
|
549
|
-
def test_example(
|
|
623
|
+
def test_example(neon_branch_isolated):
|
|
550
624
|
import my_preferred_driver
|
|
551
|
-
conn = my_preferred_driver.connect(
|
|
625
|
+
conn = my_preferred_driver.connect(neon_branch_isolated.connection_string)
|
|
552
626
|
```
|
|
553
627
|
|
|
554
628
|
### "Neon API key not configured"
|