pytest-neon 0.2.0__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.
@@ -0,0 +1,4 @@
1
+ # Copy this file to .env and fill in your Neon API key
2
+ # Get your API key from: https://console.neon.tech/app/settings/api-keys
3
+
4
+ NEON_API_KEY=your-api-key-here
@@ -0,0 +1,76 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e ".[dev,psycopg]"
29
+
30
+ - name: Run unit tests
31
+ run: pytest tests/ --ignore=tests/test_integration.py -v
32
+
33
+ integration:
34
+ runs-on: ubuntu-latest
35
+ # Only run integration tests on main branch pushes to avoid using API quota on PRs
36
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
37
+
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - name: Set up Python
42
+ uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.14"
45
+
46
+ - name: Install dependencies
47
+ run: |
48
+ python -m pip install --upgrade pip
49
+ pip install -e ".[dev,psycopg]"
50
+
51
+ - name: Run integration tests
52
+ env:
53
+ NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
54
+ NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }}
55
+ run: pytest tests/test_integration.py -v
56
+
57
+ lint:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+
62
+ - name: Set up Python
63
+ uses: actions/setup-python@v5
64
+ with:
65
+ python-version: "3.14"
66
+
67
+ - name: Install dependencies
68
+ run: |
69
+ python -m pip install --upgrade pip
70
+ pip install -e ".[dev]"
71
+
72
+ - name: Run ruff check
73
+ run: ruff check .
74
+
75
+ - name: Run ruff format check
76
+ run: ruff format --check .
@@ -0,0 +1,39 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # IDE
18
+ .idea/
19
+ .vscode/
20
+ *.swp
21
+ *.swo
22
+
23
+ # Testing
24
+ .pytest_cache/
25
+ .coverage
26
+ htmlcov/
27
+ .tox/
28
+ .nox/
29
+
30
+ # mypy
31
+ .mypy_cache/
32
+
33
+ # Environment
34
+ .env
35
+ .env.local
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
@@ -0,0 +1,3 @@
1
+ {
2
+ "projectId": "frosty-violet-45543831"
3
+ }
@@ -0,0 +1,45 @@
1
+ # Claude Code Instructions for pytest-neon
2
+
3
+ ## Project Overview
4
+
5
+ This is a pytest plugin that provides isolated Neon database branches for integration testing. Each test module gets its own branch, with automatic cleanup.
6
+
7
+ ## Key Architecture
8
+
9
+ - **Entry point**: `src/pytest_neon/plugin.py` - Contains all fixtures and pytest hooks
10
+ - **Core fixture**: `neon_branch` - Creates branch, sets `DATABASE_URL`, yields `NeonBranch` dataclass
11
+ - **Convenience fixtures**: `neon_connection`, `neon_connection_psycopg`, `neon_engine` - Optional, require extras
12
+
13
+ ## Dependencies
14
+
15
+ - Core: `pytest`, `neon-api` only
16
+ - Optional extras: `psycopg2`, `psycopg`, `sqlalchemy` - for convenience fixtures
17
+
18
+ ## Important Patterns
19
+
20
+ ### Fixture Scopes
21
+ - `neon_branch`: `scope="module"` - one branch per test file
22
+ - Connection fixtures: `scope="function"` (default) - fresh connection per test
23
+
24
+ ### Environment Variable Handling
25
+ The `_temporary_env` context manager sets `DATABASE_URL` during test execution and restores the original value after. This is critical for not polluting other tests.
26
+
27
+ ### Error Messages
28
+ 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.
29
+
30
+ ## Commit Messages
31
+ - Do NOT add Claude attribution or Co-Authored-By lines
32
+ - Keep commits clean and descriptive
33
+
34
+ ## Testing
35
+
36
+ Tests in `tests/` use `pytester` for testing pytest plugins. The plugin itself can be tested without a real Neon connection by mocking `NeonAPI`.
37
+
38
+ ## Publishing
39
+
40
+ ```bash
41
+ python -m build
42
+ python -m twine upload dist/*
43
+ ```
44
+
45
+ Package name on PyPI: `pytest-neon`
@@ -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.
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-neon
3
+ Version: 0.2.0
4
+ Summary: Pytest plugin for Neon database branch isolation in tests
5
+ Project-URL: Homepage, https://github.com/zain/pytest-neon
6
+ Project-URL: Repository, https://github.com/zain/pytest-neon
7
+ Project-URL: Issues, https://github.com/zain/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: neon-api>=0.1.0
28
+ Requires-Dist: pytest>=7.0
29
+ Requires-Dist: requests>=2.20
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.0; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
33
+ Requires-Dist: pytest-mock>=3.0; extra == 'dev'
34
+ Requires-Dist: ruff>=0.8; extra == 'dev'
35
+ Provides-Extra: psycopg
36
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'psycopg'
37
+ Provides-Extra: psycopg2
38
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'psycopg2'
39
+ Provides-Extra: sqlalchemy
40
+ Requires-Dist: sqlalchemy>=2.0; extra == 'sqlalchemy'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # pytest-neon
44
+
45
+ Pytest plugin for [Neon](https://neon.tech) database branch isolation in tests.
46
+
47
+ Each test gets its own isolated database state via Neon's instant branching and reset features. Branches are automatically cleaned up after tests complete.
48
+
49
+ ## Features
50
+
51
+ - **Isolated test environments**: Each test runs against a clean database state
52
+ - **Fast resets**: ~0.5s per test to reset the branch (not create a new one)
53
+ - **Automatic cleanup**: Branches are deleted after tests, with auto-expiry fallback
54
+ - **Zero infrastructure**: No Docker, no local Postgres, no manual setup
55
+ - **Real database testing**: Test against actual Postgres with your production schema
56
+ - **Automatic `DATABASE_URL`**: Connection string is set in environment automatically
57
+ - **Driver agnostic**: Bring your own driver, or use the optional convenience fixtures
58
+
59
+ ## Installation
60
+
61
+ Core package (bring your own database driver):
62
+
63
+ ```bash
64
+ pip install pytest-neon
65
+ ```
66
+
67
+ With optional convenience fixtures:
68
+
69
+ ```bash
70
+ # For psycopg v3 (recommended)
71
+ pip install pytest-neon[psycopg]
72
+
73
+ # For psycopg2 (legacy)
74
+ pip install pytest-neon[psycopg2]
75
+
76
+ # For SQLAlchemy
77
+ pip install pytest-neon[sqlalchemy]
78
+
79
+ # Multiple extras
80
+ pip install pytest-neon[psycopg,sqlalchemy]
81
+ ```
82
+
83
+ ## Quick Start
84
+
85
+ 1. Set environment variables:
86
+
87
+ ```bash
88
+ export NEON_API_KEY="your-api-key"
89
+ export NEON_PROJECT_ID="your-project-id"
90
+ ```
91
+
92
+ 2. Write tests:
93
+
94
+ ```python
95
+ def test_user_creation(neon_branch):
96
+ # DATABASE_URL is automatically set to the test branch
97
+ import psycopg # Your own install
98
+
99
+ with psycopg.connect() as conn: # Uses DATABASE_URL by default
100
+ with conn.cursor() as cur:
101
+ cur.execute("INSERT INTO users (email) VALUES ('test@example.com')")
102
+ conn.commit()
103
+ ```
104
+
105
+ 3. Run tests:
106
+
107
+ ```bash
108
+ pytest
109
+ ```
110
+
111
+ ## Fixtures
112
+
113
+ ### `neon_branch` (default, recommended)
114
+
115
+ The primary fixture for database testing. Creates one branch per test module, then resets it to the parent branch's state after each test. This provides test isolation with ~0.5s overhead per test.
116
+
117
+ Returns a `NeonBranch` dataclass with:
118
+
119
+ - `branch_id`: The Neon branch ID
120
+ - `project_id`: The Neon project ID
121
+ - `connection_string`: Full PostgreSQL connection URI
122
+ - `host`: The database host
123
+
124
+ ```python
125
+ import os
126
+
127
+ def test_branch_info(neon_branch):
128
+ # DATABASE_URL is set automatically
129
+ assert os.environ["DATABASE_URL"] == neon_branch.connection_string
130
+
131
+ # Use with any driver
132
+ import psycopg
133
+ conn = psycopg.connect(neon_branch.connection_string)
134
+ ```
135
+
136
+ **Performance**: ~1.5s initial setup per module + ~0.5s reset per test. For a module with 10 tests, expect ~6.5s total overhead.
137
+
138
+ ### `neon_branch_shared` (fastest, no isolation)
139
+
140
+ 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.
141
+
142
+ ```python
143
+ def test_read_only_query(neon_branch_shared):
144
+ # Fast: no reset between tests
145
+ # Warning: data from other tests in this module may be visible
146
+ conn = psycopg.connect(neon_branch_shared.connection_string)
147
+ ```
148
+
149
+ **Use this when**:
150
+ - Tests are read-only
151
+ - Tests don't interfere with each other
152
+ - You manually clean up test data
153
+ - Maximum speed is more important than isolation
154
+
155
+ **Performance**: ~1.5s initial setup per module, no per-test overhead.
156
+
157
+ ### `neon_connection_psycopg` (psycopg v3)
158
+
159
+ Convenience fixture providing a [psycopg v3](https://www.psycopg.org/psycopg3/) connection with automatic rollback and cleanup.
160
+
161
+ **Requires:** `pip install pytest-neon[psycopg]`
162
+
163
+ ```python
164
+ def test_insert(neon_connection_psycopg):
165
+ with neon_connection_psycopg.cursor() as cur:
166
+ cur.execute("INSERT INTO users (name) VALUES (%s)", ("test",))
167
+ neon_connection_psycopg.commit()
168
+
169
+ with neon_connection_psycopg.cursor() as cur:
170
+ cur.execute("SELECT name FROM users")
171
+ assert cur.fetchone()[0] == "test"
172
+ ```
173
+
174
+ ### `neon_connection` (psycopg2)
175
+
176
+ Convenience fixture providing a [psycopg2](https://www.psycopg.org/docs/) connection with automatic rollback and cleanup.
177
+
178
+ **Requires:** `pip install pytest-neon[psycopg2]`
179
+
180
+ ```python
181
+ def test_insert(neon_connection):
182
+ cur = neon_connection.cursor()
183
+ cur.execute("INSERT INTO users (name) VALUES (%s)", ("test",))
184
+ neon_connection.commit()
185
+ ```
186
+
187
+ ### `neon_engine` (SQLAlchemy)
188
+
189
+ Convenience fixture providing a [SQLAlchemy](https://www.sqlalchemy.org/) engine with automatic disposal.
190
+
191
+ **Requires:** `pip install pytest-neon[sqlalchemy]`
192
+
193
+ ```python
194
+ from sqlalchemy import text
195
+
196
+ def test_query(neon_engine):
197
+ with neon_engine.connect() as conn:
198
+ result = conn.execute(text("SELECT 1"))
199
+ assert result.scalar() == 1
200
+ ```
201
+
202
+ ## Configuration
203
+
204
+ ### Environment Variables
205
+
206
+ | Variable | Description | Required |
207
+ |----------|-------------|----------|
208
+ | `NEON_API_KEY` | Your Neon API key | Yes |
209
+ | `NEON_PROJECT_ID` | Your Neon project ID | Yes |
210
+ | `NEON_PARENT_BRANCH_ID` | Parent branch to create test branches from | No |
211
+ | `NEON_DATABASE` | Database name (default: `neondb`) | No |
212
+ | `NEON_ROLE` | Database role (default: `neondb_owner`) | No |
213
+
214
+ ### Command Line Options
215
+
216
+ | Option | Description | Default |
217
+ |--------|-------------|---------|
218
+ | `--neon-api-key` | Neon API key | `NEON_API_KEY` env |
219
+ | `--neon-project-id` | Neon project ID | `NEON_PROJECT_ID` env |
220
+ | `--neon-parent-branch` | Parent branch ID | Project default |
221
+ | `--neon-database` | Database name | `neondb` |
222
+ | `--neon-role` | Database role | `neondb_owner` |
223
+ | `--neon-keep-branches` | Don't delete branches after tests | `false` |
224
+ | `--neon-branch-expiry` | Branch auto-expiry in seconds | `600` (10 min) |
225
+ | `--neon-env-var` | Environment variable for connection string | `DATABASE_URL` |
226
+
227
+ Examples:
228
+
229
+ ```bash
230
+ # Keep branches for debugging
231
+ pytest --neon-keep-branches
232
+
233
+ # Disable auto-expiry
234
+ pytest --neon-branch-expiry=0
235
+
236
+ # Use a different env var
237
+ pytest --neon-env-var=TEST_DATABASE_URL
238
+ ```
239
+
240
+ ## CI/CD Integration
241
+
242
+ ### GitHub Actions
243
+
244
+ ```yaml
245
+ name: Tests
246
+
247
+ on: [push, pull_request]
248
+
249
+ jobs:
250
+ test:
251
+ runs-on: ubuntu-latest
252
+ steps:
253
+ - uses: actions/checkout@v4
254
+ - uses: actions/setup-python@v5
255
+ with:
256
+ python-version: '3.12'
257
+
258
+ - name: Install dependencies
259
+ run: pip install -e .[psycopg,dev]
260
+
261
+ - name: Run tests
262
+ env:
263
+ NEON_API_KEY: ${{ secrets.NEON_API_KEY }}
264
+ NEON_PROJECT_ID: ${{ secrets.NEON_PROJECT_ID }}
265
+ run: pytest
266
+ ```
267
+
268
+ ## How It Works
269
+
270
+ 1. Before each test module, the plugin creates a new Neon branch from your parent branch
271
+ 2. `DATABASE_URL` is set to point to the new branch
272
+ 3. Tests run against this isolated branch with full access to your schema and data
273
+ 4. After each test, the branch is reset to its parent state (~0.5s)
274
+ 5. After all tests in the module complete, the branch is deleted
275
+ 6. As a safety net, branches auto-expire after 10 minutes even if cleanup fails
276
+
277
+ Branches use copy-on-write storage, so you only pay for data that differs from the parent branch.
278
+
279
+ ## Troubleshooting
280
+
281
+ ### "psycopg not installed" or "psycopg2 not installed"
282
+
283
+ The convenience fixtures require their respective drivers. Install the appropriate extra:
284
+
285
+ ```bash
286
+ # For neon_connection_psycopg fixture
287
+ pip install pytest-neon[psycopg]
288
+
289
+ # For neon_connection fixture
290
+ pip install pytest-neon[psycopg2]
291
+
292
+ # For neon_engine fixture
293
+ pip install pytest-neon[sqlalchemy]
294
+ ```
295
+
296
+ Or use the core `neon_branch` fixture with your own driver:
297
+
298
+ ```python
299
+ def test_example(neon_branch):
300
+ import my_preferred_driver
301
+ conn = my_preferred_driver.connect(neon_branch.connection_string)
302
+ ```
303
+
304
+ ### "Neon API key not configured"
305
+
306
+ Set the `NEON_API_KEY` environment variable or use the `--neon-api-key` CLI option.
307
+
308
+ ### "Neon project ID not configured"
309
+
310
+ Set the `NEON_PROJECT_ID` environment variable or use the `--neon-project-id` CLI option.
311
+
312
+ ## License
313
+
314
+ MIT