{"id":43536908,"url":"https://github.com/ZainRizvi/pytest-neon","last_synced_at":"2026-02-14T22:02:00.145Z","repository":{"id":333820659,"uuid":"1138822219","full_name":"ZainRizvi/pytest-neon","owner":"ZainRizvi","description":"Pytest plugin for Neon database branch isolation in tests","archived":false,"fork":false,"pushed_at":"2026-02-05T02:16:04.000Z","size":888,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-05T13:35:25.618Z","etag":null,"topics":["database","integration-testing","neon","neon-database","postgres","postgresql","pytest","pytest-plugin","serverless-postgres","testing"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/pytest-neon/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZainRizvi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-21T06:50:59.000Z","updated_at":"2026-02-05T02:16:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ZainRizvi/pytest-neon","commit_stats":null,"previous_names":["zainrizvi/pytest-neon"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/ZainRizvi/pytest-neon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZainRizvi%2Fpytest-neon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZainRizvi%2Fpytest-neon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZainRizvi%2Fpytest-neon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZainRizvi%2Fpytest-neon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZainRizvi","download_url":"https://codeload.github.com/ZainRizvi/pytest-neon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZainRizvi%2Fpytest-neon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29457786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T21:29:27.764Z","status":"ssl_error","status_checked_at":"2026-02-14T21:28:11.111Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","integration-testing","neon","neon-database","postgres","postgresql","pytest","pytest-plugin","serverless-postgres","testing"],"created_at":"2026-02-03T17:00:22.652Z","updated_at":"2026-02-14T22:02:00.139Z","avatar_url":"https://github.com/ZainRizvi.png","language":"Python","funding_links":[],"categories":["Plugins"],"sub_categories":[],"readme":"# pytest-neon\n\n[![Tests](https://github.com/ZainRizvi/pytest-neon/actions/workflows/tests.yml/badge.svg)](https://github.com/ZainRizvi/pytest-neon/actions/workflows/tests.yml)\n\nA pytest plugin that provides Neon database branches for integration testing.\n\n## Features\n\n- **Automatic branch management**: Creates a test branch at session start, deletes at end\n- **Branch expiry**: Auto-cleanup via 10-minute expiry (crash-safe)\n- **Migration support**: Run migrations once, all tests share the migrated schema\n- **pytest-xdist support**: All workers share a single branch\n- **Minimal API calls**: Single branch creation reduces rate limiting issues\n\n## Installation\n\n```bash\npip install pytest-neon\n\n# With optional database drivers\npip install pytest-neon[psycopg]     # psycopg v3 support\npip install pytest-neon[psycopg2]    # psycopg2 support\npip install pytest-neon[sqlalchemy]  # SQLAlchemy engine support\n```\n\n## Quick Start\n\n1. Set environment variables:\n```bash\nexport NEON_API_KEY=\"your-api-key\"\nexport NEON_PROJECT_ID=\"your-project-id\"\n```\n\n2. Use the `neon_branch` fixture in your tests:\n```python\ndef test_query_users(neon_branch):\n    import psycopg\n    with psycopg.connect(neon_branch.connection_string) as conn:\n        result = conn.execute(\"SELECT * FROM users\").fetchall()\n        assert len(result) \u003e= 0\n```\n\nThe `DATABASE_URL` environment variable is automatically set when the fixture is active.\n\n## Fixtures\n\n### `neon_branch` (session-scoped)\n\nThe main fixture providing a shared Neon branch for all tests.\n\n```python\ndef test_example(neon_branch):\n    # neon_branch.branch_id - Neon branch ID\n    # neon_branch.project_id - Neon project ID\n    # neon_branch.connection_string - PostgreSQL connection string\n    # neon_branch.host - Database host\n    pass\n```\n\n**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.\n\n### `neon_apply_migrations` (session-scoped)\n\nOverride this fixture to run migrations before tests:\n\n```python\n# conftest.py\n@pytest.fixture(scope=\"session\")\ndef neon_apply_migrations(_neon_test_branch):\n    \"\"\"Run database migrations.\"\"\"\n    import subprocess\n    subprocess.run([\"alembic\", \"upgrade\", \"head\"], check=True)\n```\n\nOr with Django:\n```python\n@pytest.fixture(scope=\"session\")\ndef neon_apply_migrations(_neon_test_branch):\n    from django.core.management import call_command\n    call_command(\"migrate\", \"--noinput\")\n```\n\nOr with raw SQL:\n```python\n@pytest.fixture(scope=\"session\")\ndef neon_apply_migrations(_neon_test_branch):\n    import psycopg\n    branch, is_creator = _neon_test_branch\n    with psycopg.connect(branch.connection_string) as conn:\n        with open(\"schema.sql\") as f:\n            conn.execute(f.read())\n        conn.commit()\n```\n\n### Connection Fixtures (Optional)\n\nThese require extra dependencies:\n\n**`neon_connection`** - psycopg2 connection (requires `pytest-neon[psycopg2]`)\n```python\ndef test_insert(neon_connection):\n    cur = neon_connection.cursor()\n    cur.execute(\"INSERT INTO users (name) VALUES (%s)\", (\"test\",))\n    neon_connection.commit()\n```\n\n**`neon_connection_psycopg`** - psycopg v3 connection (requires `pytest-neon[psycopg]`)\n```python\ndef test_insert(neon_connection_psycopg):\n    with neon_connection_psycopg.cursor() as cur:\n        cur.execute(\"INSERT INTO users (name) VALUES ('test')\")\n    neon_connection_psycopg.commit()\n```\n\n**`neon_engine`** - SQLAlchemy engine (requires `pytest-neon[sqlalchemy]`)\n```python\ndef test_query(neon_engine):\n    from sqlalchemy import text\n    with neon_engine.connect() as conn:\n        result = conn.execute(text(\"SELECT 1\"))\n```\n\n## Test Isolation\n\nSince all tests share a single branch, you may need to handle test isolation yourself. Here are recommended patterns:\n\n### Transaction Rollback (Recommended)\n\n```python\n@pytest.fixture\ndef db_transaction(neon_branch):\n    \"\"\"Provide a database transaction that rolls back after each test.\"\"\"\n    import psycopg\n    conn = psycopg.connect(neon_branch.connection_string)\n    conn.execute(\"BEGIN\")\n    yield conn\n    conn.execute(\"ROLLBACK\")\n    conn.close()\n\ndef test_insert(db_transaction):\n    db_transaction.execute(\"INSERT INTO users (name) VALUES ('test')\")\n    # Automatically rolled back - next test won't see this\n```\n\n### Table Truncation\n\n```python\n@pytest.fixture(autouse=True)\ndef clean_tables(neon_branch):\n    \"\"\"Clean up test data after each test.\"\"\"\n    yield\n    import psycopg\n    with psycopg.connect(neon_branch.connection_string) as conn:\n        conn.execute(\"TRUNCATE users, orders CASCADE\")\n        conn.commit()\n```\n\n### Unique Identifiers\n\n```python\nimport uuid\n\ndef test_create_user(neon_branch):\n    unique_id = uuid.uuid4().hex[:8]\n    email = f\"test_{unique_id}@example.com\"\n    # Create user with unique email - no conflicts with other tests\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `NEON_API_KEY` | Neon API key (required) |\n| `NEON_PROJECT_ID` | Neon project ID (required) |\n| `NEON_PARENT_BRANCH_ID` | Parent branch to create test branches from |\n| `NEON_DATABASE` | Database name (default: `neondb`) |\n| `NEON_ROLE` | Database role (default: `neondb_owner`) |\n\n### Command Line Options\n\n```bash\npytest --neon-api-key=KEY --neon-project-id=ID\npytest --neon-parent-branch=BRANCH_ID\npytest --neon-database=mydb --neon-role=myrole\npytest --neon-keep-branches  # Don't delete branches (for debugging)\npytest --neon-branch-expiry=600  # Branch expiry in seconds (default: 600)\npytest --neon-env-var=CUSTOM_URL  # Use custom env var instead of DATABASE_URL\n```\n\n### pytest.ini / pyproject.toml\n\n```ini\n[pytest]\nneon_api_key = your-api-key\nneon_project_id = your-project-id\nneon_parent_branch = br-parent-123\nneon_database = mydb\nneon_role = myrole\nneon_keep_branches = false\nneon_branch_expiry = 600\nneon_env_var = DATABASE_URL\n```\n\n## Architecture\n\n```\nParent Branch (configured or project default)\n    └── Test Branch (session-scoped, 10-min expiry)\n            ↑ migrations run here ONCE, all tests share this\n```\n\nThe plugin creates exactly **one branch per test session**:\n1. First test triggers branch creation with auto-expiry\n2. Migrations run once (if `neon_apply_migrations` is overridden)\n3. All tests share the same branch\n4. Branch deleted at session end (plus auto-expiry as safety net)\n\n### pytest-xdist Support\n\nWhen running with pytest-xdist, all workers share the same branch:\n- First worker creates the branch and runs migrations\n- Other workers wait for migrations to complete\n- All workers see the same database state\n\n```bash\npytest -n 4  # 4 workers, all sharing one branch\n```\n\n## Branch Naming\n\nBranches are automatically named to help identify their source:\n\n```\npytest-[git-branch]-[random]-test\n```\n\n**Examples:**\n- `pytest-main-a1b2-test` - Test branch from `main`\n- `pytest-feature-auth-c3d4-test` - Test branch from `feature/auth`\n- `pytest-a1b2-test` - When not in a git repo\n\nThe git branch name is sanitized (only `a-z`, `0-9`, `-`, `_` allowed) and truncated to 15 characters.\n\n## Upgrading from v2.x\n\nVersion 3.0 simplifies the plugin significantly. If you're upgrading from v2.x:\n\n### Removed Fixtures\n\nThese fixtures have been removed:\n- `neon_branch_readonly` → use `neon_branch`\n- `neon_branch_readwrite` → use `neon_branch`\n- `neon_branch_isolated` → use `neon_branch` + transaction rollback\n- `neon_branch_dirty` → use `neon_branch`\n- `neon_branch_shared` → use `neon_branch`\n\n### Migration Hook Change\n\nThe migration hook now uses `_neon_test_branch` instead of `_neon_migration_branch`:\n\n```python\n# Before (v2.x)\n@pytest.fixture(scope=\"session\")\ndef neon_apply_migrations(_neon_migration_branch):\n    ...\n\n# After (v3.x)\n@pytest.fixture(scope=\"session\")\ndef neon_apply_migrations(_neon_test_branch):\n    ...\n```\n\n### No Per-Test Reset\n\nThe 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.\n\n## Troubleshooting\n\n### Rate Limiting\n\nThe plugin includes automatic retry with exponential backoff for Neon API rate limits. If you're hitting rate limits:\n- The plugin creates only 1-2 API calls per session (create + delete)\n- Consider increasing `--neon-branch-expiry` to reduce cleanup calls\n\n### Stale Connections (SQLAlchemy)\n\nIf using SQLAlchemy with connection pooling, use `pool_pre_ping=True`:\n```python\nengine = create_engine(DATABASE_URL, pool_pre_ping=True)\n```\n\nThis is a best practice for any cloud database where connections can be terminated externally.\n\n### Branch Not Deleted\n\nIf a test run crashes, the branch auto-expires after 10 minutes (configurable). You can also use `--neon-keep-branches` to prevent deletion for debugging.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZainRizvi%2Fpytest-neon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FZainRizvi%2Fpytest-neon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZainRizvi%2Fpytest-neon/lists"}