pytest-neon 2.3.1__py3-none-any.whl → 3.0.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 +3 -3
- pytest_neon/plugin.py +140 -1084
- pytest_neon-3.0.0.dist-info/METADATA +348 -0
- pytest_neon-3.0.0.dist-info/RECORD +8 -0
- pytest_neon-2.3.1.dist-info/METADATA +0 -650
- pytest_neon-2.3.1.dist-info/RECORD +0 -8
- {pytest_neon-2.3.1.dist-info → pytest_neon-3.0.0.dist-info}/WHEEL +0 -0
- {pytest_neon-2.3.1.dist-info → pytest_neon-3.0.0.dist-info}/entry_points.txt +0 -0
- {pytest_neon-2.3.1.dist-info → pytest_neon-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-neon
|
|
3
|
+
Version: 3.0.0
|
|
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
|
+
[](https://github.com/ZainRizvi/pytest-neon/actions/workflows/tests.yml)
|
|
50
|
+
|
|
51
|
+
A pytest plugin that provides Neon database branches for integration testing.
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Automatic branch management**: Creates a test branch at session start, deletes at end
|
|
56
|
+
- **Branch expiry**: Auto-cleanup via 10-minute expiry (crash-safe)
|
|
57
|
+
- **Migration support**: Run migrations once, all tests share the migrated schema
|
|
58
|
+
- **pytest-xdist support**: All workers share a single branch
|
|
59
|
+
- **Minimal API calls**: Single branch creation reduces rate limiting issues
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install pytest-neon
|
|
65
|
+
|
|
66
|
+
# With optional database drivers
|
|
67
|
+
pip install pytest-neon[psycopg] # psycopg v3 support
|
|
68
|
+
pip install pytest-neon[psycopg2] # psycopg2 support
|
|
69
|
+
pip install pytest-neon[sqlalchemy] # SQLAlchemy engine support
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
1. Set environment variables:
|
|
75
|
+
```bash
|
|
76
|
+
export NEON_API_KEY="your-api-key"
|
|
77
|
+
export NEON_PROJECT_ID="your-project-id"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. Use the `neon_branch` fixture in your tests:
|
|
81
|
+
```python
|
|
82
|
+
def test_query_users(neon_branch):
|
|
83
|
+
import psycopg
|
|
84
|
+
with psycopg.connect(neon_branch.connection_string) as conn:
|
|
85
|
+
result = conn.execute("SELECT * FROM users").fetchall()
|
|
86
|
+
assert len(result) >= 0
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The `DATABASE_URL` environment variable is automatically set when the fixture is active.
|
|
90
|
+
|
|
91
|
+
## Fixtures
|
|
92
|
+
|
|
93
|
+
### `neon_branch` (session-scoped)
|
|
94
|
+
|
|
95
|
+
The main fixture providing a shared Neon branch for all tests.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
def test_example(neon_branch):
|
|
99
|
+
# neon_branch.branch_id - Neon branch ID
|
|
100
|
+
# neon_branch.project_id - Neon project ID
|
|
101
|
+
# neon_branch.connection_string - PostgreSQL connection string
|
|
102
|
+
# neon_branch.host - Database host
|
|
103
|
+
pass
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Important**: All tests share the same branch. Data written by one test is visible to subsequent tests. See [Test Isolation](#test-isolation) for patterns to handle this.
|
|
107
|
+
|
|
108
|
+
### `neon_apply_migrations` (session-scoped)
|
|
109
|
+
|
|
110
|
+
Override this fixture to run migrations before tests:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# conftest.py
|
|
114
|
+
@pytest.fixture(scope="session")
|
|
115
|
+
def neon_apply_migrations(_neon_test_branch):
|
|
116
|
+
"""Run database migrations."""
|
|
117
|
+
import subprocess
|
|
118
|
+
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or with Django:
|
|
122
|
+
```python
|
|
123
|
+
@pytest.fixture(scope="session")
|
|
124
|
+
def neon_apply_migrations(_neon_test_branch):
|
|
125
|
+
from django.core.management import call_command
|
|
126
|
+
call_command("migrate", "--noinput")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or with raw SQL:
|
|
130
|
+
```python
|
|
131
|
+
@pytest.fixture(scope="session")
|
|
132
|
+
def neon_apply_migrations(_neon_test_branch):
|
|
133
|
+
import psycopg
|
|
134
|
+
branch, is_creator = _neon_test_branch
|
|
135
|
+
with psycopg.connect(branch.connection_string) as conn:
|
|
136
|
+
with open("schema.sql") as f:
|
|
137
|
+
conn.execute(f.read())
|
|
138
|
+
conn.commit()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Connection Fixtures (Optional)
|
|
142
|
+
|
|
143
|
+
These require extra dependencies:
|
|
144
|
+
|
|
145
|
+
**`neon_connection`** - psycopg2 connection (requires `pytest-neon[psycopg2]`)
|
|
146
|
+
```python
|
|
147
|
+
def test_insert(neon_connection):
|
|
148
|
+
cur = neon_connection.cursor()
|
|
149
|
+
cur.execute("INSERT INTO users (name) VALUES (%s)", ("test",))
|
|
150
|
+
neon_connection.commit()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**`neon_connection_psycopg`** - psycopg v3 connection (requires `pytest-neon[psycopg]`)
|
|
154
|
+
```python
|
|
155
|
+
def test_insert(neon_connection_psycopg):
|
|
156
|
+
with neon_connection_psycopg.cursor() as cur:
|
|
157
|
+
cur.execute("INSERT INTO users (name) VALUES ('test')")
|
|
158
|
+
neon_connection_psycopg.commit()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**`neon_engine`** - SQLAlchemy engine (requires `pytest-neon[sqlalchemy]`)
|
|
162
|
+
```python
|
|
163
|
+
def test_query(neon_engine):
|
|
164
|
+
from sqlalchemy import text
|
|
165
|
+
with neon_engine.connect() as conn:
|
|
166
|
+
result = conn.execute(text("SELECT 1"))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Test Isolation
|
|
170
|
+
|
|
171
|
+
Since all tests share a single branch, you may need to handle test isolation yourself. Here are recommended patterns:
|
|
172
|
+
|
|
173
|
+
### Transaction Rollback (Recommended)
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
@pytest.fixture
|
|
177
|
+
def db_transaction(neon_branch):
|
|
178
|
+
"""Provide a database transaction that rolls back after each test."""
|
|
179
|
+
import psycopg
|
|
180
|
+
conn = psycopg.connect(neon_branch.connection_string)
|
|
181
|
+
conn.execute("BEGIN")
|
|
182
|
+
yield conn
|
|
183
|
+
conn.execute("ROLLBACK")
|
|
184
|
+
conn.close()
|
|
185
|
+
|
|
186
|
+
def test_insert(db_transaction):
|
|
187
|
+
db_transaction.execute("INSERT INTO users (name) VALUES ('test')")
|
|
188
|
+
# Automatically rolled back - next test won't see this
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Table Truncation
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
@pytest.fixture(autouse=True)
|
|
195
|
+
def clean_tables(neon_branch):
|
|
196
|
+
"""Clean up test data after each test."""
|
|
197
|
+
yield
|
|
198
|
+
import psycopg
|
|
199
|
+
with psycopg.connect(neon_branch.connection_string) as conn:
|
|
200
|
+
conn.execute("TRUNCATE users, orders CASCADE")
|
|
201
|
+
conn.commit()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Unique Identifiers
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
import uuid
|
|
208
|
+
|
|
209
|
+
def test_create_user(neon_branch):
|
|
210
|
+
unique_id = uuid.uuid4().hex[:8]
|
|
211
|
+
email = f"test_{unique_id}@example.com"
|
|
212
|
+
# Create user with unique email - no conflicts with other tests
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Configuration
|
|
216
|
+
|
|
217
|
+
### Environment Variables
|
|
218
|
+
|
|
219
|
+
| Variable | Description |
|
|
220
|
+
|----------|-------------|
|
|
221
|
+
| `NEON_API_KEY` | Neon API key (required) |
|
|
222
|
+
| `NEON_PROJECT_ID` | Neon project ID (required) |
|
|
223
|
+
| `NEON_PARENT_BRANCH_ID` | Parent branch to create test branches from |
|
|
224
|
+
| `NEON_DATABASE` | Database name (default: `neondb`) |
|
|
225
|
+
| `NEON_ROLE` | Database role (default: `neondb_owner`) |
|
|
226
|
+
|
|
227
|
+
### Command Line Options
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pytest --neon-api-key=KEY --neon-project-id=ID
|
|
231
|
+
pytest --neon-parent-branch=BRANCH_ID
|
|
232
|
+
pytest --neon-database=mydb --neon-role=myrole
|
|
233
|
+
pytest --neon-keep-branches # Don't delete branches (for debugging)
|
|
234
|
+
pytest --neon-branch-expiry=600 # Branch expiry in seconds (default: 600)
|
|
235
|
+
pytest --neon-env-var=CUSTOM_URL # Use custom env var instead of DATABASE_URL
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### pytest.ini / pyproject.toml
|
|
239
|
+
|
|
240
|
+
```ini
|
|
241
|
+
[pytest]
|
|
242
|
+
neon_api_key = your-api-key
|
|
243
|
+
neon_project_id = your-project-id
|
|
244
|
+
neon_parent_branch = br-parent-123
|
|
245
|
+
neon_database = mydb
|
|
246
|
+
neon_role = myrole
|
|
247
|
+
neon_keep_branches = false
|
|
248
|
+
neon_branch_expiry = 600
|
|
249
|
+
neon_env_var = DATABASE_URL
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Architecture
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
Parent Branch (configured or project default)
|
|
256
|
+
└── Test Branch (session-scoped, 10-min expiry)
|
|
257
|
+
↑ migrations run here ONCE, all tests share this
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The plugin creates exactly **one branch per test session**:
|
|
261
|
+
1. First test triggers branch creation with auto-expiry
|
|
262
|
+
2. Migrations run once (if `neon_apply_migrations` is overridden)
|
|
263
|
+
3. All tests share the same branch
|
|
264
|
+
4. Branch deleted at session end (plus auto-expiry as safety net)
|
|
265
|
+
|
|
266
|
+
### pytest-xdist Support
|
|
267
|
+
|
|
268
|
+
When running with pytest-xdist, all workers share the same branch:
|
|
269
|
+
- First worker creates the branch and runs migrations
|
|
270
|
+
- Other workers wait for migrations to complete
|
|
271
|
+
- All workers see the same database state
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pytest -n 4 # 4 workers, all sharing one branch
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Branch Naming
|
|
278
|
+
|
|
279
|
+
Branches are automatically named to help identify their source:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
pytest-[git-branch]-[random]-test
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Examples:**
|
|
286
|
+
- `pytest-main-a1b2-test` - Test branch from `main`
|
|
287
|
+
- `pytest-feature-auth-c3d4-test` - Test branch from `feature/auth`
|
|
288
|
+
- `pytest-a1b2-test` - When not in a git repo
|
|
289
|
+
|
|
290
|
+
The git branch name is sanitized (only `a-z`, `0-9`, `-`, `_` allowed) and truncated to 15 characters.
|
|
291
|
+
|
|
292
|
+
## Upgrading from v2.x
|
|
293
|
+
|
|
294
|
+
Version 3.0 simplifies the plugin significantly. If you're upgrading from v2.x:
|
|
295
|
+
|
|
296
|
+
### Removed Fixtures
|
|
297
|
+
|
|
298
|
+
These fixtures have been removed:
|
|
299
|
+
- `neon_branch_readonly` → use `neon_branch`
|
|
300
|
+
- `neon_branch_readwrite` → use `neon_branch`
|
|
301
|
+
- `neon_branch_isolated` → use `neon_branch` + transaction rollback
|
|
302
|
+
- `neon_branch_dirty` → use `neon_branch`
|
|
303
|
+
- `neon_branch_shared` → use `neon_branch`
|
|
304
|
+
|
|
305
|
+
### Migration Hook Change
|
|
306
|
+
|
|
307
|
+
The migration hook now uses `_neon_test_branch` instead of `_neon_migration_branch`:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# Before (v2.x)
|
|
311
|
+
@pytest.fixture(scope="session")
|
|
312
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
313
|
+
...
|
|
314
|
+
|
|
315
|
+
# After (v3.x)
|
|
316
|
+
@pytest.fixture(scope="session")
|
|
317
|
+
def neon_apply_migrations(_neon_test_branch):
|
|
318
|
+
...
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### No Per-Test Reset
|
|
322
|
+
|
|
323
|
+
The v2.x `neon_branch_isolated` fixture reset the branch after each test. In v3.x, there's no automatic reset. Use transaction rollback or cleanup fixtures for test isolation.
|
|
324
|
+
|
|
325
|
+
## Troubleshooting
|
|
326
|
+
|
|
327
|
+
### Rate Limiting
|
|
328
|
+
|
|
329
|
+
The plugin includes automatic retry with exponential backoff for Neon API rate limits. If you're hitting rate limits:
|
|
330
|
+
- The plugin creates only 1-2 API calls per session (create + delete)
|
|
331
|
+
- Consider increasing `--neon-branch-expiry` to reduce cleanup calls
|
|
332
|
+
|
|
333
|
+
### Stale Connections (SQLAlchemy)
|
|
334
|
+
|
|
335
|
+
If using SQLAlchemy with connection pooling, use `pool_pre_ping=True`:
|
|
336
|
+
```python
|
|
337
|
+
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This is a best practice for any cloud database where connections can be terminated externally.
|
|
341
|
+
|
|
342
|
+
### Branch Not Deleted
|
|
343
|
+
|
|
344
|
+
If a test run crashes, the branch auto-expires after 10 minutes (configurable). You can also use `--neon-keep-branches` to prevent deletion for debugging.
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pytest_neon/__init__.py,sha256=W6OIAvx3sB2jfKCZaUUK1XcQPBxtWCnaYQCRn5NQhuA,404
|
|
2
|
+
pytest_neon/plugin.py,sha256=ADH8on2MMI7jIKdk9mQDKbk59ypMwGNuh4_NsYNTphQ,40114
|
|
3
|
+
pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pytest_neon-3.0.0.dist-info/METADATA,sha256=F68tFRyHHx6C5eKTXjq_bUMRtmR_Q0LcVrlKm8CwLHs,10706
|
|
5
|
+
pytest_neon-3.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
pytest_neon-3.0.0.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
|
|
7
|
+
pytest_neon-3.0.0.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
|
|
8
|
+
pytest_neon-3.0.0.dist-info/RECORD,,
|