pytest-neon 0.4.0__py3-none-any.whl → 0.5.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 +105 -8
- {pytest_neon-0.4.0.dist-info → pytest_neon-0.5.0.dist-info}/METADATA +85 -1
- pytest_neon-0.5.0.dist-info/RECORD +8 -0
- pytest_neon-0.4.0.dist-info/RECORD +0 -8
- {pytest_neon-0.4.0.dist-info → pytest_neon-0.5.0.dist-info}/WHEEL +0 -0
- {pytest_neon-0.4.0.dist-info → pytest_neon-0.5.0.dist-info}/entry_points.txt +0 -0
- {pytest_neon-0.4.0.dist-info → pytest_neon-0.5.0.dist-info}/licenses/LICENSE +0 -0
pytest_neon/__init__.py
CHANGED
pytest_neon/plugin.py
CHANGED
|
@@ -137,11 +137,20 @@ def _get_config_value(
|
|
|
137
137
|
|
|
138
138
|
def _create_neon_branch(
|
|
139
139
|
request: pytest.FixtureRequest,
|
|
140
|
+
parent_branch_id_override: str | None = None,
|
|
141
|
+
branch_expiry_override: int | None = None,
|
|
142
|
+
branch_name_suffix: str = "",
|
|
140
143
|
) -> Generator[NeonBranch, None, None]:
|
|
141
144
|
"""
|
|
142
145
|
Internal helper that creates and manages a Neon branch lifecycle.
|
|
143
146
|
|
|
144
147
|
This is the core implementation used by branch fixtures.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
request: Pytest fixture request
|
|
151
|
+
parent_branch_id_override: If provided, use this as parent instead of config
|
|
152
|
+
branch_expiry_override: If provided, use this expiry instead of config
|
|
153
|
+
branch_name_suffix: Optional suffix for branch name (e.g., "-migrated", "-test")
|
|
145
154
|
"""
|
|
146
155
|
config = request.config
|
|
147
156
|
|
|
@@ -149,7 +158,8 @@ def _create_neon_branch(
|
|
|
149
158
|
project_id = _get_config_value(
|
|
150
159
|
config, "neon_project_id", "NEON_PROJECT_ID", "neon_project_id"
|
|
151
160
|
)
|
|
152
|
-
|
|
161
|
+
# Use override if provided, otherwise read from config
|
|
162
|
+
parent_branch_id = parent_branch_id_override or _get_config_value(
|
|
153
163
|
config, "neon_parent_branch", "NEON_PARENT_BRANCH_ID", "neon_parent_branch"
|
|
154
164
|
)
|
|
155
165
|
database_name = _get_config_value(
|
|
@@ -164,9 +174,13 @@ def _create_neon_branch(
|
|
|
164
174
|
if keep_branches is None:
|
|
165
175
|
keep_branches = config.getini("neon_keep_branches")
|
|
166
176
|
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
branch_expiry =
|
|
177
|
+
# Use override if provided, otherwise read from config
|
|
178
|
+
if branch_expiry_override is not None:
|
|
179
|
+
branch_expiry = branch_expiry_override
|
|
180
|
+
else:
|
|
181
|
+
branch_expiry = config.getoption("neon_branch_expiry", default=None)
|
|
182
|
+
if branch_expiry is None:
|
|
183
|
+
branch_expiry = int(config.getini("neon_branch_expiry"))
|
|
170
184
|
|
|
171
185
|
env_var_name = _get_config_value(
|
|
172
186
|
config, "neon_env_var", "", "neon_env_var", "DATABASE_URL"
|
|
@@ -185,7 +199,7 @@ def _create_neon_branch(
|
|
|
185
199
|
neon = NeonAPI(api_key=api_key)
|
|
186
200
|
|
|
187
201
|
# Generate unique branch name
|
|
188
|
-
branch_name = f"pytest-{os.urandom(4).hex()}"
|
|
202
|
+
branch_name = f"pytest-{os.urandom(4).hex()}{branch_name_suffix}"
|
|
189
203
|
|
|
190
204
|
# Build branch creation payload
|
|
191
205
|
branch_config: dict[str, Any] = {"name": branch_name}
|
|
@@ -311,12 +325,86 @@ def _reset_branch_to_parent(branch: NeonBranch, api_key: str) -> None:
|
|
|
311
325
|
response.raise_for_status()
|
|
312
326
|
|
|
313
327
|
|
|
328
|
+
@pytest.fixture(scope="session")
|
|
329
|
+
def _neon_migration_branch(
|
|
330
|
+
request: pytest.FixtureRequest,
|
|
331
|
+
) -> Generator[NeonBranch, None, None]:
|
|
332
|
+
"""
|
|
333
|
+
Session-scoped branch where migrations are applied.
|
|
334
|
+
|
|
335
|
+
This branch is created from the configured parent and serves as
|
|
336
|
+
the parent for all test branches. Migrations run once per session
|
|
337
|
+
on this branch.
|
|
338
|
+
|
|
339
|
+
Note: The migration branch cannot have an expiry because Neon doesn't
|
|
340
|
+
allow creating child branches from branches with expiration dates.
|
|
341
|
+
Cleanup relies on the fixture teardown at session end.
|
|
342
|
+
"""
|
|
343
|
+
# No expiry - Neon doesn't allow children from branches with expiry
|
|
344
|
+
yield from _create_neon_branch(
|
|
345
|
+
request,
|
|
346
|
+
branch_expiry_override=0,
|
|
347
|
+
branch_name_suffix="-migrated",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@pytest.fixture(scope="session")
|
|
352
|
+
def neon_apply_migrations(_neon_migration_branch: NeonBranch) -> None:
|
|
353
|
+
"""
|
|
354
|
+
Override this fixture to run migrations on the test database.
|
|
355
|
+
|
|
356
|
+
The migration branch is already created and DATABASE_URL is set.
|
|
357
|
+
Migrations run once per test session, before any tests execute.
|
|
358
|
+
|
|
359
|
+
Example in conftest.py:
|
|
360
|
+
|
|
361
|
+
@pytest.fixture(scope="session")
|
|
362
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
363
|
+
import subprocess
|
|
364
|
+
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
|
365
|
+
|
|
366
|
+
Or with Django:
|
|
367
|
+
|
|
368
|
+
@pytest.fixture(scope="session")
|
|
369
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
370
|
+
from django.core.management import call_command
|
|
371
|
+
call_command("migrate", "--noinput")
|
|
372
|
+
|
|
373
|
+
Or with raw SQL:
|
|
374
|
+
|
|
375
|
+
@pytest.fixture(scope="session")
|
|
376
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
377
|
+
import psycopg
|
|
378
|
+
with psycopg.connect(_neon_migration_branch.connection_string) as conn:
|
|
379
|
+
with open("schema.sql") as f:
|
|
380
|
+
conn.execute(f.read())
|
|
381
|
+
conn.commit()
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
_neon_migration_branch: The migration branch with connection details.
|
|
385
|
+
Use _neon_migration_branch.connection_string to connect directly,
|
|
386
|
+
or rely on DATABASE_URL which is already set.
|
|
387
|
+
"""
|
|
388
|
+
pass # No-op by default - users override this fixture to run migrations
|
|
389
|
+
|
|
390
|
+
|
|
314
391
|
@pytest.fixture(scope="module")
|
|
315
392
|
def _neon_branch_for_reset(
|
|
316
393
|
request: pytest.FixtureRequest,
|
|
394
|
+
_neon_migration_branch: NeonBranch,
|
|
395
|
+
neon_apply_migrations: None, # Ensures migrations run first
|
|
317
396
|
) -> Generator[NeonBranch, None, None]:
|
|
318
|
-
"""
|
|
319
|
-
|
|
397
|
+
"""
|
|
398
|
+
Internal fixture that creates a test branch from the migration branch.
|
|
399
|
+
|
|
400
|
+
The test branch is created as a child of the migration branch, so resets
|
|
401
|
+
restore to post-migration state rather than the original parent state.
|
|
402
|
+
"""
|
|
403
|
+
yield from _create_neon_branch(
|
|
404
|
+
request,
|
|
405
|
+
parent_branch_id_override=_neon_migration_branch.branch_id,
|
|
406
|
+
branch_name_suffix="-test",
|
|
407
|
+
)
|
|
320
408
|
|
|
321
409
|
|
|
322
410
|
@pytest.fixture(scope="function")
|
|
@@ -383,6 +471,8 @@ def neon_branch(
|
|
|
383
471
|
@pytest.fixture(scope="module")
|
|
384
472
|
def neon_branch_shared(
|
|
385
473
|
request: pytest.FixtureRequest,
|
|
474
|
+
_neon_migration_branch: NeonBranch,
|
|
475
|
+
neon_apply_migrations: None, # Ensures migrations run first
|
|
386
476
|
) -> Generator[NeonBranch, None, None]:
|
|
387
477
|
"""
|
|
388
478
|
Provide a shared Neon database branch for all tests in a module.
|
|
@@ -391,6 +481,9 @@ def neon_branch_shared(
|
|
|
391
481
|
tests without resetting. This is the fastest option but tests can see
|
|
392
482
|
each other's data modifications.
|
|
393
483
|
|
|
484
|
+
If you override the `neon_apply_migrations` fixture, migrations will run
|
|
485
|
+
once before the first test, and this branch will include the migrated schema.
|
|
486
|
+
|
|
394
487
|
Use this when:
|
|
395
488
|
- Tests are read-only or don't interfere with each other
|
|
396
489
|
- You manually clean up test data within each test
|
|
@@ -408,7 +501,11 @@ def neon_branch_shared(
|
|
|
408
501
|
# Fast: no reset between tests, but be careful about data leakage
|
|
409
502
|
conn_string = neon_branch_shared.connection_string
|
|
410
503
|
"""
|
|
411
|
-
yield from _create_neon_branch(
|
|
504
|
+
yield from _create_neon_branch(
|
|
505
|
+
request,
|
|
506
|
+
parent_branch_id_override=_neon_migration_branch.branch_id,
|
|
507
|
+
branch_name_suffix="-shared",
|
|
508
|
+
)
|
|
412
509
|
|
|
413
510
|
|
|
414
511
|
@pytest.fixture
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-neon
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Pytest plugin for Neon database branch isolation in tests
|
|
5
5
|
Project-URL: Homepage, https://github.com/zain/pytest-neon
|
|
6
6
|
Project-URL: Repository, https://github.com/zain/pytest-neon
|
|
@@ -200,6 +200,90 @@ def test_query(neon_engine):
|
|
|
200
200
|
assert result.scalar() == 1
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
+
## Migrations
|
|
204
|
+
|
|
205
|
+
pytest-neon supports running migrations once before tests, with all test resets preserving the migrated state.
|
|
206
|
+
|
|
207
|
+
### How It Works
|
|
208
|
+
|
|
209
|
+
When you override the `neon_apply_migrations` fixture, the plugin uses a two-branch architecture:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
Parent Branch (your configured parent)
|
|
213
|
+
└── Migration Branch (session-scoped)
|
|
214
|
+
│ ↑ migrations run here ONCE
|
|
215
|
+
└── Test Branch (module-scoped)
|
|
216
|
+
↑ resets to migration branch after each test
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This means:
|
|
220
|
+
- Migrations run **once per test session** (not per test or per module)
|
|
221
|
+
- Each test reset restores to the **post-migration state**
|
|
222
|
+
- Tests always see your migrated schema
|
|
223
|
+
|
|
224
|
+
### Setup
|
|
225
|
+
|
|
226
|
+
Override the `neon_apply_migrations` fixture in your `conftest.py`:
|
|
227
|
+
|
|
228
|
+
**Alembic:**
|
|
229
|
+
```python
|
|
230
|
+
# conftest.py
|
|
231
|
+
import subprocess
|
|
232
|
+
import pytest
|
|
233
|
+
|
|
234
|
+
@pytest.fixture(scope="session")
|
|
235
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
236
|
+
"""Run Alembic migrations before tests."""
|
|
237
|
+
# DATABASE_URL is already set to the migration branch
|
|
238
|
+
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Django:**
|
|
242
|
+
```python
|
|
243
|
+
# conftest.py
|
|
244
|
+
import pytest
|
|
245
|
+
|
|
246
|
+
@pytest.fixture(scope="session")
|
|
247
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
248
|
+
"""Run Django migrations before tests."""
|
|
249
|
+
from django.core.management import call_command
|
|
250
|
+
call_command("migrate", "--noinput")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Raw SQL:**
|
|
254
|
+
```python
|
|
255
|
+
# conftest.py
|
|
256
|
+
import pytest
|
|
257
|
+
|
|
258
|
+
@pytest.fixture(scope="session")
|
|
259
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
260
|
+
"""Apply schema from SQL file."""
|
|
261
|
+
import psycopg
|
|
262
|
+
with psycopg.connect(_neon_migration_branch.connection_string) as conn:
|
|
263
|
+
with open("schema.sql") as f:
|
|
264
|
+
conn.execute(f.read())
|
|
265
|
+
conn.commit()
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Custom migration tool:**
|
|
269
|
+
```python
|
|
270
|
+
# conftest.py
|
|
271
|
+
import pytest
|
|
272
|
+
|
|
273
|
+
@pytest.fixture(scope="session")
|
|
274
|
+
def neon_apply_migrations(_neon_migration_branch):
|
|
275
|
+
"""Run custom migrations."""
|
|
276
|
+
from myapp.migrations import run_migrations
|
|
277
|
+
run_migrations(_neon_migration_branch.connection_string)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Important Notes
|
|
281
|
+
|
|
282
|
+
- The `_neon_migration_branch` parameter gives you access to the `NeonBranch` object with `connection_string`, `branch_id`, etc.
|
|
283
|
+
- `DATABASE_URL` (or your configured env var) is automatically set when the fixture runs
|
|
284
|
+
- If you don't override `neon_apply_migrations`, no migrations run (the fixture is a no-op by default)
|
|
285
|
+
- Migrations run before any test branches are created, so all tests see the same migrated schema
|
|
286
|
+
|
|
203
287
|
## Configuration
|
|
204
288
|
|
|
205
289
|
### Environment Variables
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pytest_neon/__init__.py,sha256=GmCMNwXFausX8ZucJLRZmywVEc2IamMolGhQ3JIvqpk,398
|
|
2
|
+
pytest_neon/plugin.py,sha256=1TukuxsBQ88NlgQS6qosUarIrCc94apMrwvVrYt9IOg,23241
|
|
3
|
+
pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pytest_neon-0.5.0.dist-info/METADATA,sha256=UsUvPOuacr1cs2sAjWDhsQJfoGnKFswo3Td3m5unuNw,13532
|
|
5
|
+
pytest_neon-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
pytest_neon-0.5.0.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
|
|
7
|
+
pytest_neon-0.5.0.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
|
|
8
|
+
pytest_neon-0.5.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pytest_neon/__init__.py,sha256=C_3fxrYTEBtLi_n9gJNJ9KyDk3yPFYcAmVDm87Sj-JQ,398
|
|
2
|
-
pytest_neon/plugin.py,sha256=dYNcaC1kiBWsoi-_jgANTQsCRRyi3ynONqBG0ZXX4i8,19526
|
|
3
|
-
pytest_neon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pytest_neon-0.4.0.dist-info/METADATA,sha256=XIRZcTXGiYefebFw1Vm6Uc2cL9ZvHTxzukWtUmtN40Q,11052
|
|
5
|
-
pytest_neon-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
-
pytest_neon-0.4.0.dist-info/entry_points.txt,sha256=5U88Idj_G8-PSDb9VF3OwYFbGLHnGOo_GxgYvi0dtXw,37
|
|
7
|
-
pytest_neon-0.4.0.dist-info/licenses/LICENSE,sha256=aKKp_Ex4WBHTByY4BhXJ181dzB_qYhi2pCUmZ7Spn_0,1067
|
|
8
|
-
pytest_neon-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|