sqlproof 0.1.0a1__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.
Files changed (89) hide show
  1. sqlproof-0.1.0a1/.github/workflows/ci.yml +38 -0
  2. sqlproof-0.1.0a1/.github/workflows/deploy-website.yml +41 -0
  3. sqlproof-0.1.0a1/.github/workflows/nightly.yml +19 -0
  4. sqlproof-0.1.0a1/.github/workflows/release.yml +36 -0
  5. sqlproof-0.1.0a1/.gitignore +13 -0
  6. sqlproof-0.1.0a1/CHANGELOG.md +58 -0
  7. sqlproof-0.1.0a1/PKG-INFO +248 -0
  8. sqlproof-0.1.0a1/README.md +199 -0
  9. sqlproof-0.1.0a1/SPEC.md +1188 -0
  10. sqlproof-0.1.0a1/examples/ecommerce/schema.sql +11 -0
  11. sqlproof-0.1.0a1/examples/ecommerce/test_orders.py +20 -0
  12. sqlproof-0.1.0a1/examples/ripenn_scoring/schema.sql +4 -0
  13. sqlproof-0.1.0a1/examples/ripenn_scoring/test_scoring.py +13 -0
  14. sqlproof-0.1.0a1/pyproject.toml +119 -0
  15. sqlproof-0.1.0a1/src/sqlproof/__init__.py +32 -0
  16. sqlproof-0.1.0a1/src/sqlproof/_version.py +1 -0
  17. sqlproof-0.1.0a1/src/sqlproof/cli.py +151 -0
  18. sqlproof-0.1.0a1/src/sqlproof/client.py +159 -0
  19. sqlproof-0.1.0a1/src/sqlproof/config.py +42 -0
  20. sqlproof-0.1.0a1/src/sqlproof/contrib/__init__.py +3 -0
  21. sqlproof-0.1.0a1/src/sqlproof/contrib/supabase.py +136 -0
  22. sqlproof-0.1.0a1/src/sqlproof/core.py +344 -0
  23. sqlproof-0.1.0a1/src/sqlproof/coverage/__init__.py +6 -0
  24. sqlproof-0.1.0a1/src/sqlproof/coverage/diversity.py +11 -0
  25. sqlproof-0.1.0a1/src/sqlproof/coverage/plpgsql.py +5 -0
  26. sqlproof-0.1.0a1/src/sqlproof/coverage/schema_shape.py +7 -0
  27. sqlproof-0.1.0a1/src/sqlproof/exceptions.py +47 -0
  28. sqlproof-0.1.0a1/src/sqlproof/generators/__init__.py +21 -0
  29. sqlproof-0.1.0a1/src/sqlproof/generators/columns.py +93 -0
  30. sqlproof-0.1.0a1/src/sqlproof/generators/constraints.py +181 -0
  31. sqlproof-0.1.0a1/src/sqlproof/generators/functions.py +9 -0
  32. sqlproof-0.1.0a1/src/sqlproof/generators/graph.py +51 -0
  33. sqlproof-0.1.0a1/src/sqlproof/generators/rows.py +153 -0
  34. sqlproof-0.1.0a1/src/sqlproof/generators/sampling.py +15 -0
  35. sqlproof-0.1.0a1/src/sqlproof/generators/well_known.py +59 -0
  36. sqlproof-0.1.0a1/src/sqlproof/pytest_plugin.py +24 -0
  37. sqlproof-0.1.0a1/src/sqlproof/reporter/__init__.py +5 -0
  38. sqlproof-0.1.0a1/src/sqlproof/reporter/console.py +20 -0
  39. sqlproof-0.1.0a1/src/sqlproof/reporter/json_io.py +26 -0
  40. sqlproof-0.1.0a1/src/sqlproof/runners/__init__.py +14 -0
  41. sqlproof-0.1.0a1/src/sqlproof/runners/db.py +48 -0
  42. sqlproof-0.1.0a1/src/sqlproof/runners/migration.py +51 -0
  43. sqlproof-0.1.0a1/src/sqlproof/runners/overload.py +41 -0
  44. sqlproof-0.1.0a1/src/sqlproof/runners/property.py +119 -0
  45. sqlproof-0.1.0a1/src/sqlproof/runners/rls.py +40 -0
  46. sqlproof-0.1.0a1/src/sqlproof/runners/stateful.py +36 -0
  47. sqlproof-0.1.0a1/src/sqlproof/schema/__init__.py +27 -0
  48. sqlproof-0.1.0a1/src/sqlproof/schema/dependency_graph.py +38 -0
  49. sqlproof-0.1.0a1/src/sqlproof/schema/fingerprint.py +34 -0
  50. sqlproof-0.1.0a1/src/sqlproof/schema/introspect.py +229 -0
  51. sqlproof-0.1.0a1/src/sqlproof/schema/model.py +98 -0
  52. sqlproof-0.1.0a1/src/sqlproof/schema/parse_sql.py +206 -0
  53. sqlproof-0.1.0a1/src/sqlproof/testing.py +101 -0
  54. sqlproof-0.1.0a1/src/sqlproof/types.py +34 -0
  55. sqlproof-0.1.0a1/tests/benchmarks/test_generation_benchmark.py +25 -0
  56. sqlproof-0.1.0a1/tests/conftest.py +1 -0
  57. sqlproof-0.1.0a1/tests/integration/test_postgres_execution.py +47 -0
  58. sqlproof-0.1.0a1/tests/meta/test_meta_properties.py +99 -0
  59. sqlproof-0.1.0a1/tests/unit/test_cli_smoke.py +65 -0
  60. sqlproof-0.1.0a1/tests/unit/test_contrib_supabase.py +296 -0
  61. sqlproof-0.1.0a1/tests/unit/test_db_manager.py +122 -0
  62. sqlproof-0.1.0a1/tests/unit/test_generators_core.py +511 -0
  63. sqlproof-0.1.0a1/tests/unit/test_package_scaffold.py +58 -0
  64. sqlproof-0.1.0a1/tests/unit/test_runner_slice.py +353 -0
  65. sqlproof-0.1.0a1/tests/unit/test_schema_core.py +268 -0
  66. sqlproof-0.1.0a1/tests/unit/test_state_machine.py +156 -0
  67. sqlproof-0.1.0a1/tests/unit/test_v01_surfaces.py +119 -0
  68. sqlproof-0.1.0a1/uv.lock +1459 -0
  69. sqlproof-0.1.0a1/website/astro.config.mjs +42 -0
  70. sqlproof-0.1.0a1/website/package-lock.json +7429 -0
  71. sqlproof-0.1.0a1/website/package.json +16 -0
  72. sqlproof-0.1.0a1/website/public/CNAME +1 -0
  73. sqlproof-0.1.0a1/website/public/favicon.svg +4 -0
  74. sqlproof-0.1.0a1/website/src/content/config.ts +6 -0
  75. sqlproof-0.1.0a1/website/src/content/docs/api/check-options.md +63 -0
  76. sqlproof-0.1.0a1/website/src/content/docs/api/sqlproof-class.md +138 -0
  77. sqlproof-0.1.0a1/website/src/content/docs/api/state-machine.md +153 -0
  78. sqlproof-0.1.0a1/website/src/content/docs/api/table-customization.md +39 -0
  79. sqlproof-0.1.0a1/website/src/content/docs/examples/orders.md +59 -0
  80. sqlproof-0.1.0a1/website/src/content/docs/getting-started.md +73 -0
  81. sqlproof-0.1.0a1/website/src/content/docs/guides/ci-cd.md +67 -0
  82. sqlproof-0.1.0a1/website/src/content/docs/guides/custom-generators.md +52 -0
  83. sqlproof-0.1.0a1/website/src/content/docs/guides/fk-distributions.md +53 -0
  84. sqlproof-0.1.0a1/website/src/content/docs/guides/local-dev.md +54 -0
  85. sqlproof-0.1.0a1/website/src/content/docs/guides/security.md +43 -0
  86. sqlproof-0.1.0a1/website/src/content/docs/guides/supabase.md +134 -0
  87. sqlproof-0.1.0a1/website/src/pages/index.astro +165 -0
  88. sqlproof-0.1.0a1/website/src/styles/custom.css +304 -0
  89. sqlproof-0.1.0a1/website/tsconfig.json +5 -0
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ python:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - uses: astral-sh/setup-uv@v5
20
+ - run: uv sync --extra dev
21
+ - run: uv run ruff check src/ tests/
22
+ - run: uv run pyright
23
+ - run: uv run mypy src/sqlproof/
24
+ - run: uv run pytest --cov=sqlproof --cov-fail-under=95
25
+
26
+ website:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-node@v4
31
+ with:
32
+ node-version: 20
33
+ cache: npm
34
+ cache-dependency-path: website/package-lock.json
35
+ - run: npm ci
36
+ working-directory: website
37
+ - run: npm run build
38
+ working-directory: website
@@ -0,0 +1,41 @@
1
+ name: Deploy website to GitHub Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - 'website/**'
8
+ - '.github/workflows/deploy-website.yml'
9
+
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ deploy:
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Node
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: 20
26
+ cache: npm
27
+ cache-dependency-path: website/package-lock.json
28
+
29
+ - name: Install dependencies
30
+ run: npm ci
31
+ working-directory: website
32
+
33
+ - name: Build
34
+ run: npm run build
35
+ working-directory: website
36
+
37
+ - name: Deploy to GitHub Pages
38
+ uses: peaceiris/actions-gh-pages@v4
39
+ with:
40
+ github_token: ${{ secrets.GITHUB_TOKEN }}
41
+ publish_dir: website/dist
@@ -0,0 +1,19 @@
1
+ name: Nightly
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 2 * * *"
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ extended:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv sync --extra dev
18
+ - run: uv run pytest tests/meta tests/unit
19
+ - run: uv run pytest tests/benchmarks || true
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv sync --extra dev
18
+ - run: uv run pytest -m "not nocover"
19
+ - run: uv build
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: dist
23
+ path: dist/
24
+
25
+ publish:
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write
31
+ steps:
32
+ - uses: actions/download-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,13 @@
1
+ node_modules/
2
+ dist/
3
+ *.js.map
4
+ .env
5
+ .superpowers/
6
+ .worktrees/
7
+ website/.astro/
8
+ __pycache__/
9
+ *.py[cod]
10
+ .pytest_cache/
11
+ .hypothesis/
12
+ .coverage
13
+ .venv/
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to SqlProof will be documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While SqlProof
6
+ remains in `0.x`, minor versions may include breaking changes.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0a1] - 2026-05-04
11
+
12
+ First public release. Early-stage alpha — APIs are unstable.
13
+
14
+ ### Added
15
+
16
+ - **Stateful testing.** New `sqlproof.testing.SqlProofStateMachine` base class
17
+ that wires Hypothesis's `RuleBasedStateMachine` into SqlProof: each example
18
+ leases an isolated `SqlProofClient` via `proof.client_for_dataset(...)`, and
19
+ writes are rolled back between examples. Subclasses define `@rule`s and
20
+ `@invariant`s as usual; `on_setup()` runs once per example with `self.db`
21
+ ready. `self.enter(cm)` adopts a context manager for the example's lifetime
22
+ (useful for JWT claims, savepoints, mocked clocks). Run via
23
+ `proof.run_state_machine(MyMachine, settings=...)`.
24
+ - **Supabase contrib helpers** in `sqlproof.contrib.supabase`:
25
+ - `as_supabase_user(db, user_id, role=...)` — context manager that sets
26
+ `request.jwt.claims` for the duration of the block, so PostgREST/Supabase
27
+ helpers (`auth.uid()`, `auth.jwt()`) resolve to the given user. Restores
28
+ prior claim on exit; nested invocations stack and unwind correctly.
29
+ - `seed_supabase_test_users(db, count)` — calls Supabase's auth admin API to
30
+ (re)create N deterministic test users with the
31
+ `sqlproof_<n>@test.invalid` email pattern. Idempotent.
32
+ - `seed_test_users_directly(db, count)` — alternative SQL-only path that
33
+ inserts into `auth.users` via the existing connection. Lets tests run
34
+ when the admin API key is unavailable but the test connection has write
35
+ access to the auth schema (e.g. local Supabase).
36
+ - **Dataset generation flexibility:**
37
+ - Shrinkable per-table size strategies (pass a Hypothesis integer strategy
38
+ where `SizeSpec` is accepted; size shrinks alongside data).
39
+ - Column-level overrides keyed by `"<table>.<column>"`: fixed values,
40
+ Hypothesis strategies, or callable derivers.
41
+ - External-table FK sampling: register an `ExternalTableSpec` to draw FK
42
+ values from a live external parent table (e.g. `auth.users`).
43
+ - **Project specification document** (`SPEC.md`) capturing mission,
44
+ architecture, API surface, and design constraints.
45
+
46
+ ### Known limitations
47
+
48
+ - Schema introspection covers tables, columns, FKs, CHECK constraints, UNIQUE
49
+ constraints, and enums. Exclusion constraints, partial unique indexes, and
50
+ generated columns are not yet honored.
51
+ - Some Postgres types (range types, composite types, custom domains) are not
52
+ yet supported.
53
+ - The pytest plugin entry point exists but the CLI flags and reporter wiring
54
+ are still stabilizing.
55
+ - No deprecation policy yet — breaking changes ship freely in `0.x`.
56
+
57
+ [Unreleased]: https://github.com/alialavia/sqlproof/compare/v0.1.0a1...HEAD
58
+ [0.1.0a1]: https://github.com/alialavia/sqlproof/releases/tag/v0.1.0a1
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlproof
3
+ Version: 0.1.0a1
4
+ Summary: Property-based testing for PostgreSQL schemas and SQL behavior. Early-stage alpha — APIs may change.
5
+ Project-URL: Homepage, https://sqlproof.com
6
+ Project-URL: Documentation, https://sqlproof.com
7
+ Project-URL: Repository, https://github.com/alialavia/sqlproof
8
+ Project-URL: Issues, https://github.com/alialavia/sqlproof/issues
9
+ Project-URL: Changelog, https://github.com/alialavia/sqlproof/blob/main/CHANGELOG.md
10
+ Author: SqlProof contributors
11
+ License: MIT
12
+ Keywords: hypothesis,postgres,postgresql,property-based-testing,rls,sql,testing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Framework :: Hypothesis
15
+ Classifier: Framework :: Pytest
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Database :: Database Engines/Servers
25
+ Classifier: Topic :: Software Development :: Quality Assurance
26
+ Classifier: Topic :: Software Development :: Testing
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Requires-Dist: hypothesis>=6.100
30
+ Requires-Dist: pglast>=6.0
31
+ Requires-Dist: psycopg[binary]>=3.1
32
+ Requires-Dist: rich>=13.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: mutmut; extra == 'dev'
35
+ Requires-Dist: mypy; extra == 'dev'
36
+ Requires-Dist: pyright; extra == 'dev'
37
+ Requires-Dist: pytest-benchmark; extra == 'dev'
38
+ Requires-Dist: pytest-cov; extra == 'dev'
39
+ Requires-Dist: pytest-xdist; extra == 'dev'
40
+ Requires-Dist: pytest>=8.0; extra == 'dev'
41
+ Requires-Dist: ruff; extra == 'dev'
42
+ Requires-Dist: syrupy; extra == 'dev'
43
+ Requires-Dist: uv; extra == 'dev'
44
+ Provides-Extra: pydantic
45
+ Requires-Dist: pydantic>=2.0; extra == 'pydantic'
46
+ Provides-Extra: testcontainers
47
+ Requires-Dist: testcontainers[postgres]>=4.0; extra == 'testcontainers'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # SqlProof
51
+
52
+ [![CI](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml/badge.svg)](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml)
53
+ [![codecov](https://codecov.io/gh/alialavia/sqlproof/branch/main/graph/badge.svg)](https://codecov.io/gh/alialavia/sqlproof)
54
+ [![PyPI](https://img.shields.io/pypi/v/sqlproof?include_prereleases)](https://pypi.org/project/sqlproof/)
55
+
56
+ **→ Full docs: [sqlproof.com](https://sqlproof.com)**
57
+
58
+ > ⚠️ **Early-stage alpha (`0.1.0a1`).** APIs are unstable and may change without
59
+ > deprecation warnings until 0.x stabilizes. Postgres edge cases and Hypothesis
60
+ > shrink behavior are still being discovered, and coverage of the schema surface
61
+ > area is incomplete. **Do not rely on this for production test suites yet.**
62
+ > Bug reports and reproductions welcome —
63
+ > [open an issue](https://github.com/alialavia/sqlproof/issues).
64
+
65
+ Property-based testing for PostgreSQL schemas and SQL behavior. Define properties about
66
+ your database code; SqlProof generates valid datasets with Hypothesis, executes your
67
+ queries through `psycopg`, and saves the smallest counterexample it finds.
68
+
69
+ ## Install
70
+
71
+ Alpha releases are gated behind a pre-release flag so you don't get one by accident:
72
+
73
+ ```bash
74
+ pip install --pre sqlproof
75
+ # or:
76
+ uv add --prerelease=allow sqlproof
77
+ ```
78
+
79
+ Requires Python 3.11+ and PostgreSQL 13+.
80
+
81
+ ## Quick Start
82
+
83
+ Given a schema file:
84
+
85
+ ```sql
86
+ -- schema.sql
87
+ CREATE TABLE orders (
88
+ id SERIAL PRIMARY KEY,
89
+ customer_id INTEGER NOT NULL,
90
+ total NUMERIC(10,2) NOT NULL CHECK (total >= 0)
91
+ );
92
+
93
+ CREATE TABLE line_items (
94
+ id SERIAL PRIMARY KEY,
95
+ order_id INTEGER NOT NULL REFERENCES orders(id),
96
+ quantity INTEGER NOT NULL CHECK (quantity > 0),
97
+ price NUMERIC(10,2) NOT NULL CHECK (price > 0)
98
+ );
99
+ ```
100
+
101
+ Write property tests with pytest:
102
+
103
+ ```python
104
+ from sqlproof import SqlProof, sqlproof
105
+
106
+ proof = SqlProof.from_schema_file("./schema.sql")
107
+
108
+
109
+ @sqlproof(proof, sizes={"orders": 20, "line_items": 50}, runs=50)
110
+ def test_no_orphan_line_items(db):
111
+ rows = db.query("""
112
+ SELECT li.id
113
+ FROM line_items li
114
+ LEFT JOIN orders o ON li.order_id = o.id
115
+ WHERE o.id IS NULL
116
+ """)
117
+ assert rows == []
118
+ ```
119
+
120
+ SqlProof will:
121
+
122
+ 1. Parse your schema (tables, columns, FKs, CHECK constraints, enums)
123
+ 2. Topologically order tables by foreign-key dependencies
124
+ 3. Generate datasets that respect common type, FK, CHECK, UNIQUE, and NOT NULL constraints
125
+ 4. Run your property with Hypothesis-managed execution and shrinking
126
+ 5. Save the shrunk counterexample as JSON when a property fails
127
+
128
+ ## API
129
+
130
+ See the full API reference at [sqlproof.com](https://sqlproof.com).
131
+
132
+ ### Quick reference
133
+
134
+ ```python
135
+ proof = SqlProof.from_schema_file("./schema.sql")
136
+ proof = SqlProof.from_connection_string("postgresql://localhost/postgres")
137
+
138
+ proof.check("name", sizes={"orders": 10}, property=lambda db: ...)
139
+ proof.invariant(
140
+ "no bad rows",
141
+ sizes={"orders": 10},
142
+ query="SELECT id FROM orders WHERE total < 0",
143
+ expect_empty=True,
144
+ )
145
+
146
+ proof.disconnect()
147
+ ```
148
+
149
+ ### Schema Sources
150
+
151
+ **SQL file** — SqlProof parses `CREATE TABLE`, `CREATE TYPE ... AS ENUM`, foreign keys, CHECK constraints, UNIQUE constraints, and column types directly from `.sql` files.
152
+
153
+ **Connection string** — Pass a `postgresql://` URL and SqlProof introspects the live database via `information_schema` and `pg_catalog`.
154
+
155
+ ```python
156
+ proof = SqlProof.from_connection_string("postgresql://localhost:5432/mydb")
157
+ ```
158
+
159
+ ### Custom Column Generators
160
+
161
+ SqlProof maps PostgreSQL types to Hypothesis strategies and refines simple range,
162
+ `IN (...)`, length, FK, and unique constraints. The public customization API is present
163
+ for v0.1 and will grow with richer per-column strategy overrides.
164
+
165
+ ### The `db` Client
166
+
167
+ The property function receives a `SqlProofClient`:
168
+
169
+ ```python
170
+ rows = db.query("SELECT id, total FROM orders WHERE total >= %s", 0)
171
+ total = db.scalar("SELECT count(*) FROM orders")
172
+ typed = db.query_typed("SELECT id, total FROM orders", OrderRow)
173
+ data = db.get_generated_data()
174
+ ```
175
+
176
+ - `query()` returns a list of dictionaries.
177
+ - `query_typed()` maps rows into `TypedDict`, dataclass, or Pydantic models.
178
+ - `get_generated_data()` returns the dataset for the current run.
179
+
180
+ ## Failure Output
181
+
182
+ When a property fails, SqlProof throws with a formatted counterexample:
183
+
184
+ ```text
185
+ Property failed: order totals match sum of line items
186
+ Failure: AssertionError: expected totals to match
187
+ Row context: {'order_id': 1}
188
+ Dataset shape: {'orders': {'rows': 1}, 'line_items': {'rows': 2}}
189
+ ```
190
+
191
+ Counterexamples are written under `.sqlproof/failures/` and can be inspected with:
192
+
193
+ ```bash
194
+ sqlproof report .sqlproof/failures/test_name.json
195
+ sqlproof report .sqlproof/failures/test_name.json --format json
196
+ sqlproof replay .sqlproof/failures/test_name.json
197
+ ```
198
+
199
+ ## How It Works
200
+
201
+ 1. **Schema parsing** — Reads your SQL file (or introspects a live DB) to extract tables, columns, types, foreign keys, CHECK/UNIQUE constraints, and enums
202
+
203
+ 2. **Dependency ordering** — Topologically sorts tables by foreign key references so parent rows are always inserted first
204
+
205
+ 3. **Data generation** — Maps PostgreSQL types to Hypothesis strategies and applies constraint-aware generation for CHECK, UNIQUE, NOT NULL, and FK constraints
206
+
207
+ 4. **Isolated execution** — Schema-file proofs run against an in-memory client for fast local feedback. DSN-backed proofs introspect PostgreSQL, insert generated data inside savepoints, run the property, then roll back the run.
208
+
209
+ 5. **Shrinking** — When a property fails, Hypothesis shrinks the dataset to find the simplest counterexample
210
+
211
+ ## Supported PostgreSQL Types
212
+
213
+ `integer`, `smallint`, `bigint`, `serial`, `bigserial`, `numeric(p,s)`, `real`, `double precision`, `boolean`, `text`, `varchar(n)`, `char(n)`, `uuid`, `timestamp`, `timestamptz`, `date`, `time`, `json`, `jsonb`, `bytea`, and custom `ENUM` types.
214
+
215
+ ## Development
216
+
217
+ ```bash
218
+ git clone https://github.com/your-org/sqlproof.git
219
+ cd sqlproof
220
+ uv sync --extra dev
221
+
222
+ uv run pytest
223
+ uv run ruff check src tests
224
+ uv run pyright src/sqlproof
225
+ uv run mypy src/sqlproof
226
+ ```
227
+
228
+ ### Postgres-backed tests
229
+
230
+ Integration tests are optional and read `SQLPROOF_TEST_DATABASE_URL`:
231
+
232
+ ```bash
233
+ SQLPROOF_TEST_DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:5432/postgres' uv run pytest tests/integration
234
+ uv run pytest tests/benchmarks
235
+ ```
236
+
237
+ The integration tests create a temporary schema named `sqlproof_it_*` and drop it at the end.
238
+
239
+ ### Why SqlProof tests itself with properties
240
+
241
+ SqlProof uses Hypothesis internally, and its own tests use properties for schema
242
+ fingerprinting, dependency ordering, FK validity, constraint generation, shrinking,
243
+ parser idempotence, and counterexample replay. This keeps the library honest about
244
+ the same invariants it asks users to write.
245
+
246
+ ## License
247
+
248
+ MIT
@@ -0,0 +1,199 @@
1
+ # SqlProof
2
+
3
+ [![CI](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml/badge.svg)](https://github.com/alialavia/sqlproof/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/alialavia/sqlproof/branch/main/graph/badge.svg)](https://codecov.io/gh/alialavia/sqlproof)
5
+ [![PyPI](https://img.shields.io/pypi/v/sqlproof?include_prereleases)](https://pypi.org/project/sqlproof/)
6
+
7
+ **→ Full docs: [sqlproof.com](https://sqlproof.com)**
8
+
9
+ > ⚠️ **Early-stage alpha (`0.1.0a1`).** APIs are unstable and may change without
10
+ > deprecation warnings until 0.x stabilizes. Postgres edge cases and Hypothesis
11
+ > shrink behavior are still being discovered, and coverage of the schema surface
12
+ > area is incomplete. **Do not rely on this for production test suites yet.**
13
+ > Bug reports and reproductions welcome —
14
+ > [open an issue](https://github.com/alialavia/sqlproof/issues).
15
+
16
+ Property-based testing for PostgreSQL schemas and SQL behavior. Define properties about
17
+ your database code; SqlProof generates valid datasets with Hypothesis, executes your
18
+ queries through `psycopg`, and saves the smallest counterexample it finds.
19
+
20
+ ## Install
21
+
22
+ Alpha releases are gated behind a pre-release flag so you don't get one by accident:
23
+
24
+ ```bash
25
+ pip install --pre sqlproof
26
+ # or:
27
+ uv add --prerelease=allow sqlproof
28
+ ```
29
+
30
+ Requires Python 3.11+ and PostgreSQL 13+.
31
+
32
+ ## Quick Start
33
+
34
+ Given a schema file:
35
+
36
+ ```sql
37
+ -- schema.sql
38
+ CREATE TABLE orders (
39
+ id SERIAL PRIMARY KEY,
40
+ customer_id INTEGER NOT NULL,
41
+ total NUMERIC(10,2) NOT NULL CHECK (total >= 0)
42
+ );
43
+
44
+ CREATE TABLE line_items (
45
+ id SERIAL PRIMARY KEY,
46
+ order_id INTEGER NOT NULL REFERENCES orders(id),
47
+ quantity INTEGER NOT NULL CHECK (quantity > 0),
48
+ price NUMERIC(10,2) NOT NULL CHECK (price > 0)
49
+ );
50
+ ```
51
+
52
+ Write property tests with pytest:
53
+
54
+ ```python
55
+ from sqlproof import SqlProof, sqlproof
56
+
57
+ proof = SqlProof.from_schema_file("./schema.sql")
58
+
59
+
60
+ @sqlproof(proof, sizes={"orders": 20, "line_items": 50}, runs=50)
61
+ def test_no_orphan_line_items(db):
62
+ rows = db.query("""
63
+ SELECT li.id
64
+ FROM line_items li
65
+ LEFT JOIN orders o ON li.order_id = o.id
66
+ WHERE o.id IS NULL
67
+ """)
68
+ assert rows == []
69
+ ```
70
+
71
+ SqlProof will:
72
+
73
+ 1. Parse your schema (tables, columns, FKs, CHECK constraints, enums)
74
+ 2. Topologically order tables by foreign-key dependencies
75
+ 3. Generate datasets that respect common type, FK, CHECK, UNIQUE, and NOT NULL constraints
76
+ 4. Run your property with Hypothesis-managed execution and shrinking
77
+ 5. Save the shrunk counterexample as JSON when a property fails
78
+
79
+ ## API
80
+
81
+ See the full API reference at [sqlproof.com](https://sqlproof.com).
82
+
83
+ ### Quick reference
84
+
85
+ ```python
86
+ proof = SqlProof.from_schema_file("./schema.sql")
87
+ proof = SqlProof.from_connection_string("postgresql://localhost/postgres")
88
+
89
+ proof.check("name", sizes={"orders": 10}, property=lambda db: ...)
90
+ proof.invariant(
91
+ "no bad rows",
92
+ sizes={"orders": 10},
93
+ query="SELECT id FROM orders WHERE total < 0",
94
+ expect_empty=True,
95
+ )
96
+
97
+ proof.disconnect()
98
+ ```
99
+
100
+ ### Schema Sources
101
+
102
+ **SQL file** — SqlProof parses `CREATE TABLE`, `CREATE TYPE ... AS ENUM`, foreign keys, CHECK constraints, UNIQUE constraints, and column types directly from `.sql` files.
103
+
104
+ **Connection string** — Pass a `postgresql://` URL and SqlProof introspects the live database via `information_schema` and `pg_catalog`.
105
+
106
+ ```python
107
+ proof = SqlProof.from_connection_string("postgresql://localhost:5432/mydb")
108
+ ```
109
+
110
+ ### Custom Column Generators
111
+
112
+ SqlProof maps PostgreSQL types to Hypothesis strategies and refines simple range,
113
+ `IN (...)`, length, FK, and unique constraints. The public customization API is present
114
+ for v0.1 and will grow with richer per-column strategy overrides.
115
+
116
+ ### The `db` Client
117
+
118
+ The property function receives a `SqlProofClient`:
119
+
120
+ ```python
121
+ rows = db.query("SELECT id, total FROM orders WHERE total >= %s", 0)
122
+ total = db.scalar("SELECT count(*) FROM orders")
123
+ typed = db.query_typed("SELECT id, total FROM orders", OrderRow)
124
+ data = db.get_generated_data()
125
+ ```
126
+
127
+ - `query()` returns a list of dictionaries.
128
+ - `query_typed()` maps rows into `TypedDict`, dataclass, or Pydantic models.
129
+ - `get_generated_data()` returns the dataset for the current run.
130
+
131
+ ## Failure Output
132
+
133
+ When a property fails, SqlProof throws with a formatted counterexample:
134
+
135
+ ```text
136
+ Property failed: order totals match sum of line items
137
+ Failure: AssertionError: expected totals to match
138
+ Row context: {'order_id': 1}
139
+ Dataset shape: {'orders': {'rows': 1}, 'line_items': {'rows': 2}}
140
+ ```
141
+
142
+ Counterexamples are written under `.sqlproof/failures/` and can be inspected with:
143
+
144
+ ```bash
145
+ sqlproof report .sqlproof/failures/test_name.json
146
+ sqlproof report .sqlproof/failures/test_name.json --format json
147
+ sqlproof replay .sqlproof/failures/test_name.json
148
+ ```
149
+
150
+ ## How It Works
151
+
152
+ 1. **Schema parsing** — Reads your SQL file (or introspects a live DB) to extract tables, columns, types, foreign keys, CHECK/UNIQUE constraints, and enums
153
+
154
+ 2. **Dependency ordering** — Topologically sorts tables by foreign key references so parent rows are always inserted first
155
+
156
+ 3. **Data generation** — Maps PostgreSQL types to Hypothesis strategies and applies constraint-aware generation for CHECK, UNIQUE, NOT NULL, and FK constraints
157
+
158
+ 4. **Isolated execution** — Schema-file proofs run against an in-memory client for fast local feedback. DSN-backed proofs introspect PostgreSQL, insert generated data inside savepoints, run the property, then roll back the run.
159
+
160
+ 5. **Shrinking** — When a property fails, Hypothesis shrinks the dataset to find the simplest counterexample
161
+
162
+ ## Supported PostgreSQL Types
163
+
164
+ `integer`, `smallint`, `bigint`, `serial`, `bigserial`, `numeric(p,s)`, `real`, `double precision`, `boolean`, `text`, `varchar(n)`, `char(n)`, `uuid`, `timestamp`, `timestamptz`, `date`, `time`, `json`, `jsonb`, `bytea`, and custom `ENUM` types.
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ git clone https://github.com/your-org/sqlproof.git
170
+ cd sqlproof
171
+ uv sync --extra dev
172
+
173
+ uv run pytest
174
+ uv run ruff check src tests
175
+ uv run pyright src/sqlproof
176
+ uv run mypy src/sqlproof
177
+ ```
178
+
179
+ ### Postgres-backed tests
180
+
181
+ Integration tests are optional and read `SQLPROOF_TEST_DATABASE_URL`:
182
+
183
+ ```bash
184
+ SQLPROOF_TEST_DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:5432/postgres' uv run pytest tests/integration
185
+ uv run pytest tests/benchmarks
186
+ ```
187
+
188
+ The integration tests create a temporary schema named `sqlproof_it_*` and drop it at the end.
189
+
190
+ ### Why SqlProof tests itself with properties
191
+
192
+ SqlProof uses Hypothesis internally, and its own tests use properties for schema
193
+ fingerprinting, dependency ordering, FK validity, constraint generation, shrinking,
194
+ parser idempotence, and counterexample replay. This keeps the library honest about
195
+ the same invariants it asks users to write.
196
+
197
+ ## License
198
+
199
+ MIT