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.
@@ -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
+ [![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
+ 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,,