pytest-neon 2.3.1__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/py.typed ADDED
File without changes
@@ -0,0 +1,650 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-neon
3
+ Version: 2.3.1
4
+ Summary: Pytest plugin for Neon database branch isolation in tests
5
+ Project-URL: Homepage, https://github.com/ZainRizvi/pytest-neon
6
+ Project-URL: Repository, https://github.com/ZainRizvi/pytest-neon
7
+ Project-URL: Issues, https://github.com/ZainRizvi/pytest-neon/issues
8
+ Author: Zain Rizvi
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: branching,database,neon,postgres,pytest,testing
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Database
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: filelock>=3.0
28
+ Requires-Dist: neon-api>=0.1.0
29
+ Requires-Dist: pytest>=7.0
30
+ Requires-Dist: requests>=2.20
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy>=1.0; extra == 'dev'
33
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'dev'
34
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
36
+ Requires-Dist: pytest-mock>=3.0; extra == 'dev'
37
+ Requires-Dist: ruff>=0.8; extra == 'dev'
38
+ Requires-Dist: sqlalchemy>=2.0; extra == 'dev'
39
+ Provides-Extra: psycopg
40
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'psycopg'
41
+ Provides-Extra: psycopg2
42
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'psycopg2'
43
+ Provides-Extra: sqlalchemy
44
+ Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # pytest-neon
48
+
49
+ [![Tests](https://github.com/ZainRizvi/pytest-neon/actions/workflows/tests.yml/badge.svg)](https://github.com/ZainRizvi/pytest-neon/actions/workflows/tests.yml)
50
+
51
+ Pytest plugin for [Neon](https://neon.tech) database branch isolation in tests.
52
+
53
+ Each test gets its own isolated database state via Neon's instant branching and reset features. Branches are automatically cleaned up after tests complete.
54
+
55
+ ## Features
56
+
57
+ - **Isolated test environments**: Each test runs against a clean database state
58
+ - **Fast resets**: ~0.5s per test to reset the branch (not create a new one)
59
+ - **Automatic cleanup**: Branches are deleted after tests, with auto-expiry fallback
60
+ - **Zero infrastructure**: No Docker, no local Postgres, no manual setup
61
+ - **Real database testing**: Test against actual Postgres with your production schema
62
+ - **Automatic `DATABASE_URL`**: Connection string is set in environment automatically
63
+ - **Driver agnostic**: Bring your own driver, or use the optional convenience fixtures
64
+
65
+ ## Installation
66
+
67
+ Core package (bring your own database driver):
68
+
69
+ ```bash
70
+ pip install pytest-neon
71
+ ```
72
+
73
+ With optional convenience fixtures:
74
+
75
+ ```bash
76
+ # For psycopg v3 (recommended)
77
+ pip install pytest-neon[psycopg]
78
+
79
+ # For psycopg2 (legacy)
80
+ pip install pytest-neon[psycopg2]
81
+
82
+ # For SQLAlchemy
83
+ pip install pytest-neon[sqlalchemy]
84
+
85
+ # Multiple extras
86
+ pip install pytest-neon[psycopg,sqlalchemy]
87
+ ```
88
+
89
+ ## Quick Start
90
+
91
+ 1. Set environment variables:
92
+
93
+ ```bash
94
+ export NEON_API_KEY="your-api-key"
95
+ export NEON_PROJECT_ID="your-project-id"
96
+ ```
97
+
98
+ 2. Write tests:
99
+
100
+ ```python
101
+ def test_user_creation(neon_branch_isolated):
102
+ # DATABASE_URL is automatically set to the test branch
103
+ import psycopg # Your own install
104
+
105
+ with psycopg.connect() as conn: # Uses DATABASE_URL by default
106
+ with conn.cursor() as cur:
107
+ cur.execute("INSERT INTO users (email) VALUES ('test@example.com')")
108
+ conn.commit()
109
+ # Branch automatically resets after test - next test sees clean state
110
+ ```
111
+
112
+ 3. Run tests:
113
+
114
+ ```bash
115
+ pytest
116
+ ```
117
+
118
+ ## Fixtures
119
+
120
+ **Which fixture should I use?**
121
+
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 |
128
+
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.
133
+
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.
156
+
157
+ ```python
158
+ def test_query_users(neon_branch_readonly):
159
+ # DATABASE_URL is set automatically
160
+ import psycopg
161
+ with psycopg.connect(neon_branch_readonly.connection_string) as conn:
162
+ result = conn.execute("SELECT * FROM users").fetchall()
163
+ assert len(result) >= 0
164
+
165
+ # This would fail with a database error:
166
+ # conn.execute("INSERT INTO users (name) VALUES ('test')")
167
+ ```
168
+
169
+ **Use this when**:
170
+ - Tests only perform SELECT queries
171
+ - Tests don't modify database state
172
+ - You want maximum performance with true enforcement
173
+
174
+ **Session-scoped**: The endpoint is created once and shared across all tests and workers.
175
+
176
+ **Performance**: ~1.5s initial setup per session, **no per-test overhead**. For 10 read-only tests, expect only ~1.5s total overhead.
177
+
178
+ ### `neon_branch_dirty` (session-scoped, shared state)
179
+
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.
181
+
182
+ ```python
183
+ def test_insert_user(neon_branch_dirty):
184
+ # DATABASE_URL is set automatically
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.
210
+
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
218
+ import psycopg
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
+
225
+ conn.execute("INSERT INTO users (name) VALUES ('test')")
226
+ conn.commit()
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
232
+ ```
233
+
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.
250
+
251
+ ### `NeonBranch` dataclass
252
+
253
+ All fixtures return a `NeonBranch` dataclass with:
254
+
255
+ - `branch_id`: The Neon branch ID
256
+ - `project_id`: The Neon project ID
257
+ - `connection_string`: Full PostgreSQL connection URI
258
+ - `host`: The database host
259
+ - `parent_id`: The parent branch ID (used for resets)
260
+ - `endpoint_id`: The endpoint ID (for cleanup)
261
+
262
+ ### `neon_branch_readwrite` (deprecated)
263
+
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)
269
+
270
+ > **Deprecated**: Use `neon_branch_isolated`, `neon_branch_dirty`, or `neon_branch_readonly` instead.
271
+
272
+ This fixture is an alias for `neon_branch_isolated` and will emit a deprecation warning.
273
+
274
+ ### `neon_branch_shared` (module-scoped, no isolation)
275
+
276
+ 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.
277
+
278
+ ```python
279
+ def test_read_only_query(neon_branch_shared):
280
+ # Fast: no reset between tests
281
+ # Warning: data from other tests in this module may be visible
282
+ conn = psycopg.connect(neon_branch_shared.connection_string)
283
+ ```
284
+
285
+ **Use this when**:
286
+ - Tests are read-only
287
+ - Tests don't interfere with each other
288
+ - You manually clean up test data
289
+ - Maximum speed is more important than isolation
290
+
291
+ **Performance**: ~1.5s initial setup per module, no per-test overhead.
292
+
293
+ ### `neon_connection_psycopg` (psycopg v3)
294
+
295
+ Convenience fixture providing a [psycopg v3](https://www.psycopg.org/psycopg3/) connection with automatic rollback and cleanup.
296
+
297
+ **Requires:** `pip install pytest-neon[psycopg]`
298
+
299
+ ```python
300
+ def test_insert(neon_connection_psycopg):
301
+ with neon_connection_psycopg.cursor() as cur:
302
+ cur.execute("INSERT INTO users (name) VALUES (%s)", ("test",))
303
+ neon_connection_psycopg.commit()
304
+
305
+ with neon_connection_psycopg.cursor() as cur:
306
+ cur.execute("SELECT name FROM users")
307
+ assert cur.fetchone()[0] == "test"
308
+ ```
309
+
310
+ ### `neon_connection` (psycopg2)
311
+
312
+ Convenience fixture providing a [psycopg2](https://www.psycopg.org/docs/) connection with automatic rollback and cleanup.
313
+
314
+ **Requires:** `pip install pytest-neon[psycopg2]`
315
+
316
+ ```python
317
+ def test_insert(neon_connection):
318
+ cur = neon_connection.cursor()
319
+ cur.execute("INSERT INTO users (name) VALUES (%s)", ("test",))
320
+ neon_connection.commit()
321
+ ```
322
+
323
+ ### `neon_engine` (SQLAlchemy)
324
+
325
+ Convenience fixture providing a [SQLAlchemy](https://www.sqlalchemy.org/) engine with automatic disposal.
326
+
327
+ **Requires:** `pip install pytest-neon[sqlalchemy]`
328
+
329
+ ```python
330
+ from sqlalchemy import text
331
+
332
+ def test_query(neon_engine):
333
+ with neon_engine.connect() as conn:
334
+ result = conn.execute(text("SELECT 1"))
335
+ assert result.scalar() == 1
336
+ ```
337
+
338
+ ### Using Your Own SQLAlchemy Engine
339
+
340
+ If you have a module-level SQLAlchemy engine (common pattern) and use `neon_branch_isolated`, you **must** use `pool_pre_ping=True`:
341
+
342
+ ```python
343
+ # database.py
344
+ from sqlalchemy import create_engine
345
+ from config import DATABASE_URL
346
+
347
+ # pool_pre_ping=True is REQUIRED when using neon_branch_isolated
348
+ # It verifies connections are alive before using them
349
+ engine = create_engine(DATABASE_URL, pool_pre_ping=True)
350
+ ```
351
+
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.
353
+
354
+ **Note**: If you only use `neon_branch_readonly` or `neon_branch_dirty`, `pool_pre_ping` is not required since no resets occur.
355
+
356
+ This is also a best practice for any cloud database (Neon, RDS, etc.) where connections can be terminated externally.
357
+
358
+ ## Migrations
359
+
360
+ pytest-neon supports running migrations once before tests, with all test resets preserving the migrated state.
361
+
362
+ ### How It Works
363
+
364
+ The plugin always creates a migration branch from your configured parent:
365
+
366
+ ```
367
+ Parent Branch (your configured parent)
368
+ └── Migration Branch (session-scoped)
369
+ │ ↑ migrations run here ONCE
370
+
371
+ ├── Read-only Endpoint (for neon_branch_readonly)
372
+ ├── Dirty Branch (for neon_branch_dirty)
373
+ └── Isolated Branches (for neon_branch_isolated)
374
+ ```
375
+
376
+ This means:
377
+ - Migrations run **once per test session** (not per test or per module)
378
+ - Each test reset restores to the **post-migration state**
379
+ - Tests always see your migrated schema
380
+
381
+ ### Setup
382
+
383
+ Override the `neon_apply_migrations` fixture in your `conftest.py`:
384
+
385
+ **Alembic:**
386
+ ```python
387
+ # conftest.py
388
+ import subprocess
389
+ import pytest
390
+
391
+ @pytest.fixture(scope="session")
392
+ def neon_apply_migrations(_neon_migration_branch):
393
+ """Run Alembic migrations before tests."""
394
+ # DATABASE_URL is already set to the migration branch
395
+ subprocess.run(["alembic", "upgrade", "head"], check=True)
396
+ ```
397
+
398
+ **Django:**
399
+ ```python
400
+ # conftest.py
401
+ import pytest
402
+
403
+ @pytest.fixture(scope="session")
404
+ def neon_apply_migrations(_neon_migration_branch):
405
+ """Run Django migrations before tests."""
406
+ from django.core.management import call_command
407
+ call_command("migrate", "--noinput")
408
+ ```
409
+
410
+ **Raw SQL:**
411
+ ```python
412
+ # conftest.py
413
+ import pytest
414
+
415
+ @pytest.fixture(scope="session")
416
+ def neon_apply_migrations(_neon_migration_branch):
417
+ """Apply schema from SQL file."""
418
+ import psycopg
419
+ with psycopg.connect(_neon_migration_branch.connection_string) as conn:
420
+ with open("schema.sql") as f:
421
+ conn.execute(f.read())
422
+ conn.commit()
423
+ ```
424
+
425
+ **Custom migration tool:**
426
+ ```python
427
+ # conftest.py
428
+ import pytest
429
+
430
+ @pytest.fixture(scope="session")
431
+ def neon_apply_migrations(_neon_migration_branch):
432
+ """Run custom migrations."""
433
+ from myapp.migrations import run_migrations
434
+ run_migrations(_neon_migration_branch.connection_string)
435
+ ```
436
+
437
+ ### Important Notes
438
+
439
+ - The `_neon_migration_branch` parameter gives you access to the `NeonBranch` object with `connection_string`, `branch_id`, etc.
440
+ - `DATABASE_URL` (or your configured env var) is automatically set when the fixture runs
441
+ - If you don't override `neon_apply_migrations`, no migrations run (the fixture is a no-op by default)
442
+ - Migrations run before any test branches are created, so all tests see the same migrated schema
443
+
444
+ ## Configuration
445
+
446
+ ### Environment Variables
447
+
448
+ | Variable | Description | Required |
449
+ |----------|-------------|----------|
450
+ | `NEON_API_KEY` | Your Neon API key | Yes |
451
+ | `NEON_PROJECT_ID` | Your Neon project ID | Yes |
452
+ | `NEON_PARENT_BRANCH_ID` | Parent branch to create test branches from | No |
453
+ | `NEON_DATABASE` | Database name (default: `neondb`) | No |
454
+ | `NEON_ROLE` | Database role (default: `neondb_owner`) | No |
455
+
456
+ ### Command Line Options
457
+
458
+ | Option | Description | Default |
459
+ |--------|-------------|---------|
460
+ | `--neon-api-key` | Neon API key | `NEON_API_KEY` env |
461
+ | `--neon-project-id` | Neon project ID | `NEON_PROJECT_ID` env |
462
+ | `--neon-parent-branch` | Parent branch ID | Project default |
463
+ | `--neon-database` | Database name | `neondb` |
464
+ | `--neon-role` | Database role | `neondb_owner` |
465
+ | `--neon-keep-branches` | Don't delete branches after tests | `false` |
466
+ | `--neon-branch-expiry` | Branch auto-expiry in seconds | `600` (10 min) |
467
+ | `--neon-env-var` | Environment variable for connection string | `DATABASE_URL` |
468
+
469
+ Examples:
470
+
471
+ ```bash
472
+ # Keep branches for debugging
473
+ pytest --neon-keep-branches
474
+
475
+ # Disable auto-expiry
476
+ pytest --neon-branch-expiry=0
477
+
478
+ # Use a different env var
479
+ pytest --neon-env-var=TEST_DATABASE_URL
480
+ ```
481
+
482
+ ### pyproject.toml / pytest.ini
483
+
484
+ You can also configure options in your `pyproject.toml`:
485
+
486
+ ```toml
487
+ [tool.pytest.ini_options]
488
+ neon_database = "mydb"
489
+ neon_role = "myrole"
490
+ neon_keep_branches = true
491
+ neon_branch_expiry = "300"
492
+ ```
493
+
494
+ Or in `pytest.ini`:
495
+
496
+ ```ini
497
+ [pytest]
498
+ neon_database = mydb
499
+ neon_role = myrole
500
+ neon_keep_branches = true
501
+ neon_branch_expiry = 300
502
+ ```
503
+
504
+ **Priority order**: CLI options > environment variables > ini settings > defaults
505
+
506
+ ## CI/CD Integration
507
+
508
+ ### GitHub Actions
509
+
510
+ ```yaml
511
+ name: Tests
512
+
513
+ on: [push, pull_request]
514
+
515
+ jobs:
516
+ test:
517
+ runs-on: ubuntu-latest
518
+ steps:
519
+ - uses: actions/checkout@v4
520
+ - uses: actions/setup-python@v5
521
+ with:
522
+ python-version: '3.12'
523
+
524
+ - name: Install dependencies
525
+ run: pip install -e .[psycopg,dev]
526
+
527
+ - name: Run tests
528
+ env:
529
+ NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
530
+ NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }}
531
+ run: pytest
532
+ ```
533
+
534
+ ## How It Works
535
+
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
543
+
544
+ Branches use copy-on-write storage, so you only pay for data that differs from the parent branch.
545
+
546
+ ### What Reset Does
547
+
548
+ The `neon_branch_isolated` fixture uses Neon's branch restore API to reset database state after each test:
549
+
550
+ - **Data changes are reverted**: All INSERT, UPDATE, DELETE operations are undone
551
+ - **Schema changes are reverted**: CREATE TABLE, ALTER TABLE, DROP TABLE, etc. are undone
552
+ - **Sequences are reset**: Auto-increment counters return to parent state
553
+ - **Complete rollback**: The branch is restored to the exact state of the parent at the time the child branch was created
554
+
555
+ This is similar to database transactions but at the branch level.
556
+
557
+ ## Branch Naming
558
+
559
+ Branches are automatically named to help identify their source:
560
+
561
+ ```
562
+ pytest-[git-branch]-[random]-[suffix]
563
+ ```
564
+
565
+ **Examples:**
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
570
+
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.
572
+
573
+ ## Parallel Test Execution (pytest-xdist)
574
+
575
+ This plugin supports parallel test execution with [pytest-xdist](https://pytest-xdist.readthedocs.io/).
576
+
577
+ ```bash
578
+ # Run tests in parallel with 4 workers
579
+ pip install pytest-xdist
580
+ pytest -n 4
581
+ ```
582
+
583
+ **How it works:**
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)
596
+ - All branches are cleaned up after the test session
597
+
598
+ **Cost implications:**
599
+ - Running with `-n 4` creates: 1 migration branch + 1 dirty branch + 4 isolated branches (if all workers use isolated)
600
+ - Choose your parallelism level based on your Neon plan's branch limits
601
+ - Each worker's isolated branch is reset after each test using the fast reset operation (~0.5s)
602
+
603
+ ## Troubleshooting
604
+
605
+ ### "psycopg not installed" or "psycopg2 not installed"
606
+
607
+ The convenience fixtures require their respective drivers. Install the appropriate extra:
608
+
609
+ ```bash
610
+ # For neon_connection_psycopg fixture
611
+ pip install pytest-neon[psycopg]
612
+
613
+ # For neon_connection fixture
614
+ pip install pytest-neon[psycopg2]
615
+
616
+ # For neon_engine fixture
617
+ pip install pytest-neon[sqlalchemy]
618
+ ```
619
+
620
+ Or use the core fixtures with your own driver:
621
+
622
+ ```python
623
+ def test_example(neon_branch_isolated):
624
+ import my_preferred_driver
625
+ conn = my_preferred_driver.connect(neon_branch_isolated.connection_string)
626
+ ```
627
+
628
+ ### "Neon API key not configured"
629
+
630
+ Set the `NEON_API_KEY` environment variable or use the `--neon-api-key` CLI option.
631
+
632
+ ### "Neon project ID not configured"
633
+
634
+ Set the `NEON_PROJECT_ID` environment variable or use the `--neon-project-id` CLI option.
635
+
636
+ ### "SSL connection has been closed unexpectedly" (SQLAlchemy)
637
+
638
+ This happens when SQLAlchemy tries to reuse a pooled connection after a branch reset. The reset terminates server-side connections, but SQLAlchemy's pool doesn't know.
639
+
640
+ **Fix:** Add `pool_pre_ping=True` to your engine:
641
+
642
+ ```python
643
+ engine = create_engine(DATABASE_URL, pool_pre_ping=True)
644
+ ```
645
+
646
+ This makes SQLAlchemy verify connections before using them, automatically discarding stale ones.
647
+
648
+ ## License
649
+
650
+ MIT
@@ -0,0 +1,8 @@
1
+ pytest_neon/__init__.py,sha256=jUIgvcpeZvbOiwyAZEkOnI5KkA1Pb4BYefeUq7xkrqM,398
2
+ pytest_neon/plugin.py,sha256=Lim03yyXviIpxQbHGZK8KUvG0AlwXZg7TtVDUGwHKDo,76166
3
+ pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ pytest_neon-2.3.1.dist-info/METADATA,sha256=tNmTJvn0Rll969BcHvrPaacw9V1pARmFwzFIiSzxYAg,23149
5
+ pytest_neon-2.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ pytest_neon-2.3.1.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
7
+ pytest_neon-2.3.1.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
8
+ pytest_neon-2.3.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ neon = pytest_neon.plugin
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Zain Rizvi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.