rowguard 0.1.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.
Files changed (100) hide show
  1. rowguard-0.1.0/.github/workflows/benchmarks.yml +23 -0
  2. rowguard-0.1.0/.github/workflows/ci.yml +53 -0
  3. rowguard-0.1.0/.github/workflows/release.yml +62 -0
  4. rowguard-0.1.0/.gitignore +38 -0
  5. rowguard-0.1.0/.pre-commit-config.yaml +18 -0
  6. rowguard-0.1.0/API.md +177 -0
  7. rowguard-0.1.0/ARCHITECTURE.md +265 -0
  8. rowguard-0.1.0/CHANGELOG.md +41 -0
  9. rowguard-0.1.0/CURSOR_PROMPT.md +54 -0
  10. rowguard-0.1.0/LICENSE +5 -0
  11. rowguard-0.1.0/MANIFEST.md +28 -0
  12. rowguard-0.1.0/Makefile +21 -0
  13. rowguard-0.1.0/PKG-INFO +174 -0
  14. rowguard-0.1.0/README.md +129 -0
  15. rowguard-0.1.0/ROADMAP.md +193 -0
  16. rowguard-0.1.0/SPEC.md +325 -0
  17. rowguard-0.1.0/benchmarks/conftest.py +0 -0
  18. rowguard-0.1.0/benchmarks/test_validation_benchmark.py +17 -0
  19. rowguard-0.1.0/docs/architecture/ASYNC.md +210 -0
  20. rowguard-0.1.0/docs/architecture/CACHE.md +1429 -0
  21. rowguard-0.1.0/docs/architecture/DESIGN_DECISIONS.md +1241 -0
  22. rowguard-0.1.0/docs/architecture/DIALECT_SUPPORT.md +1387 -0
  23. rowguard-0.1.0/docs/architecture/EXECUTION_PIPELINE.md +249 -0
  24. rowguard-0.1.0/docs/architecture/FILTER_PUSHDOWN.md +918 -0
  25. rowguard-0.1.0/docs/architecture/INTERNAL_API.md +1388 -0
  26. rowguard-0.1.0/docs/architecture/PERFORMANCE.md +1345 -0
  27. rowguard-0.1.0/docs/architecture/PLUGIN_SYSTEM.md +1772 -0
  28. rowguard-0.1.0/docs/architecture/QUERY_COMPILATION.md +273 -0
  29. rowguard-0.1.0/docs/architecture/QUERY_ENGINE.md +247 -0
  30. rowguard-0.1.0/docs/architecture/RESULT_OBJECT.md +701 -0
  31. rowguard-0.1.0/docs/architecture/ROW_ADAPTER.md +726 -0
  32. rowguard-0.1.0/docs/architecture/SQLRULES_INTEGRATION.md +345 -0
  33. rowguard-0.1.0/docs/architecture/STREAMING.md +270 -0
  34. rowguard-0.1.0/docs/architecture/VALIDATION_ENGINE.md +296 -0
  35. rowguard-0.1.0/docs/developer/BENCHMARKS.md +822 -0
  36. rowguard-0.1.0/docs/developer/CONTRIBUTING.md +760 -0
  37. rowguard-0.1.0/docs/developer/MILESTONES.md +567 -0
  38. rowguard-0.1.0/docs/developer/TESTING.md +913 -0
  39. rowguard-0.1.0/docs/integrations/CORE.md +1169 -0
  40. rowguard-0.1.0/docs/integrations/ORM.md +1218 -0
  41. rowguard-0.1.0/docs/integrations/RAW_SQL.md +223 -0
  42. rowguard-0.1.0/docs/integrations/REFLECTION.md +263 -0
  43. rowguard-0.1.0/docs/integrations/SQLMODEL.md +1316 -0
  44. rowguard-0.1.0/docs/integrations/WHY_NOT_SQLMODEL.md +268 -0
  45. rowguard-0.1.0/docs/rejection/CALLBACKS.md +1160 -0
  46. rowguard-0.1.0/docs/rejection/DIAGNOSTICS.md +263 -0
  47. rowguard-0.1.0/docs/rejection/QUARANTINE.md +1395 -0
  48. rowguard-0.1.0/docs/rejection/REJECTION_HANDLING.md +436 -0
  49. rowguard-0.1.0/docs/rejection/REJECTION_POLICIES.md +277 -0
  50. rowguard-0.1.0/docs/validation/PARTIAL_VALIDATION.md +260 -0
  51. rowguard-0.1.0/docs/validation/PYDANTIC.md +1538 -0
  52. rowguard-0.1.0/docs/validation/TYPE_SUPPORT.md +1655 -0
  53. rowguard-0.1.0/docs/validation/VALIDATION_ERRORS.md +1193 -0
  54. rowguard-0.1.0/examples/basic.py +48 -0
  55. rowguard-0.1.0/pyproject.toml +105 -0
  56. rowguard-0.1.0/src/rowguard/__init__.py +31 -0
  57. rowguard-0.1.0/src/rowguard/adapters/__init__.py +6 -0
  58. rowguard-0.1.0/src/rowguard/adapters/base.py +15 -0
  59. rowguard-0.1.0/src/rowguard/adapters/sqlalchemy_row.py +46 -0
  60. rowguard-0.1.0/src/rowguard/api.py +130 -0
  61. rowguard-0.1.0/src/rowguard/cache.py +26 -0
  62. rowguard-0.1.0/src/rowguard/diagnostics.py +15 -0
  63. rowguard-0.1.0/src/rowguard/errors.py +59 -0
  64. rowguard-0.1.0/src/rowguard/execution/__init__.py +6 -0
  65. rowguard-0.1.0/src/rowguard/execution/processor.py +112 -0
  66. rowguard-0.1.0/src/rowguard/execution/streaming.py +14 -0
  67. rowguard-0.1.0/src/rowguard/execution/sync.py +152 -0
  68. rowguard-0.1.0/src/rowguard/integrations/__init__.py +11 -0
  69. rowguard-0.1.0/src/rowguard/integrations/sqlalchemy_core.py +28 -0
  70. rowguard-0.1.0/src/rowguard/integrations/sqlalchemy_orm.py +5 -0
  71. rowguard-0.1.0/src/rowguard/integrations/sqlmodel.py +5 -0
  72. rowguard-0.1.0/src/rowguard/integrations/sqlrules.py +68 -0
  73. rowguard-0.1.0/src/rowguard/planning/__init__.py +7 -0
  74. rowguard-0.1.0/src/rowguard/planning/compiler.py +116 -0
  75. rowguard-0.1.0/src/rowguard/planning/execution_plan.py +30 -0
  76. rowguard-0.1.0/src/rowguard/planning/request.py +24 -0
  77. rowguard-0.1.0/src/rowguard/plugins/__init__.py +0 -0
  78. rowguard-0.1.0/src/rowguard/plugins/registry.py +13 -0
  79. rowguard-0.1.0/src/rowguard/py.typed +0 -0
  80. rowguard-0.1.0/src/rowguard/rejection/__init__.py +12 -0
  81. rowguard-0.1.0/src/rowguard/rejection/base.py +14 -0
  82. rowguard-0.1.0/src/rowguard/rejection/policies.py +40 -0
  83. rowguard-0.1.0/src/rowguard/results/__init__.py +6 -0
  84. rowguard-0.1.0/src/rowguard/results/query_result.py +45 -0
  85. rowguard-0.1.0/src/rowguard/results/rejected_row.py +16 -0
  86. rowguard-0.1.0/src/rowguard/statistics.py +19 -0
  87. rowguard-0.1.0/src/rowguard/validation/__init__.py +6 -0
  88. rowguard-0.1.0/src/rowguard/validation/base.py +20 -0
  89. rowguard-0.1.0/src/rowguard/validation/pydantic.py +20 -0
  90. rowguard-0.1.0/tests/conftest.py +48 -0
  91. rowguard-0.1.0/tests/integration/test_core.py +177 -0
  92. rowguard-0.1.0/tests/typing/query_result_typing.py +12 -0
  93. rowguard-0.1.0/tests/unit/test_bugfix_regressions.py +211 -0
  94. rowguard-0.1.0/tests/unit/test_coverage_edges.py +155 -0
  95. rowguard-0.1.0/tests/unit/test_planner.py +85 -0
  96. rowguard-0.1.0/tests/unit/test_processor.py +59 -0
  97. rowguard-0.1.0/tests/unit/test_pydantic_validator.py +21 -0
  98. rowguard-0.1.0/tests/unit/test_query_result.py +58 -0
  99. rowguard-0.1.0/tests/unit/test_rejection_policies.py +70 -0
  100. rowguard-0.1.0/tests/unit/test_row_adapter.py +50 -0
@@ -0,0 +1,23 @@
1
+ name: Benchmarks
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ schedule:
6
+ - cron: "0 6 * * 1"
7
+
8
+ jobs:
9
+ benchmark:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - run: |
17
+ python -m pip install --upgrade pip
18
+ pip install -e ".[dev]"
19
+ pytest benchmarks --benchmark-only --benchmark-json=benchmark.json
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: benchmark-results
23
+ path: benchmark.json
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_call:
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install package
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install -e ".[dev,async]"
30
+
31
+ - name: Ruff
32
+ run: ruff check .
33
+
34
+ - name: Mypy
35
+ run: mypy src/rowguard
36
+
37
+ - name: Pytest
38
+ run: pytest --cov=rowguard --cov-report=term-missing
39
+
40
+ - name: Example smoke
41
+ run: python examples/basic.py
42
+
43
+ benchmark-smoke:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: "3.12"
50
+ - run: |
51
+ python -m pip install --upgrade pip
52
+ pip install -e ".[dev]"
53
+ pytest benchmarks --benchmark-only
@@ -0,0 +1,62 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ ci:
13
+ name: Commit checks
14
+ uses: ./.github/workflows/ci.yml
15
+
16
+ publish:
17
+ name: Publish to PyPI
18
+ needs: ci
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.12"
27
+
28
+ - name: Install build tools
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ python -m pip install build twine
32
+
33
+ - name: Verify tag matches package version
34
+ run: |
35
+ python - <<'PY'
36
+ import os
37
+ import tomllib
38
+ from pathlib import Path
39
+
40
+ tag = os.environ["GITHUB_REF_NAME"]
41
+ assert tag.startswith("v"), tag
42
+ expected = tag[1:]
43
+ version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
44
+ init = Path("src/rowguard/__init__.py").read_text()
45
+ marker = '__version__ = "'
46
+ start = init.index(marker) + len(marker)
47
+ end = init.index('"', start)
48
+ init_version = init[start:end]
49
+ assert version == expected == init_version, (version, expected, init_version)
50
+ print(f"tag/version ok: {tag} -> {version}")
51
+ PY
52
+
53
+ - name: Build distributions
54
+ run: python -m build
55
+
56
+ - name: Twine check
57
+ run: twine check dist/*
58
+
59
+ - name: Publish to PyPI
60
+ uses: pypa/gh-action-pypi-publish@release/v1
61
+ with:
62
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,38 @@
1
+ # Bytecode / caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ wheels/
10
+ *.egg-info/
11
+ .eggs/
12
+ *.egg
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ ENV/
18
+
19
+ # Test / type / lint caches
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+ .coverage
24
+ .coverage.*
25
+ htmlcov/
26
+ .hypothesis/
27
+ .benchmarks/
28
+
29
+ # Tooling
30
+ .pre-commit/
31
+ *.log
32
+ .DS_Store
33
+
34
+ # Editors
35
+ .idea/
36
+ .vscode/
37
+ *.swp
38
+ *~
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.9.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/mirrors-mypy
10
+ rev: v1.15.0
11
+ hooks:
12
+ - id: mypy
13
+ additional_dependencies:
14
+ - pydantic>=2.7
15
+ - SQLAlchemy>=2.0
16
+ - sqlrules>=0.2.0
17
+ args: [--config-file=pyproject.toml]
18
+ pass_filenames: false
rowguard-0.1.0/API.md ADDED
@@ -0,0 +1,177 @@
1
+ # API.md
2
+
3
+ # RowGuard Public API
4
+
5
+ ## Philosophy
6
+
7
+ The public API is intentionally small. RowGuard should expose a few
8
+ powerful, composable entry points while keeping execution details
9
+ internal.
10
+
11
+ ## Core Functions
12
+
13
+ ### select()
14
+
15
+ Execute a SQL query, validate every returned row against a Pydantic
16
+ model, and return a `QueryResult`.
17
+
18
+ ``` python
19
+ result = rowguard.select(
20
+ session=session,
21
+ table=users,
22
+ model=UserRead,
23
+ where=(),
24
+ field_map=None,
25
+ column_map=None,
26
+ parameters=None,
27
+ on_reject="raise",
28
+ use_sqlrules=True,
29
+ )
30
+ ```
31
+
32
+ Parameters:
33
+
34
+ - `session` or `connection`: exactly one SQLAlchemy execution context
35
+ - `table`: SQLAlchemy Core `Table` or selectable
36
+ - `model`: Pydantic `BaseModel` subclass
37
+ - `where`: optional additional SQLAlchemy expressions (default `()`)
38
+ - `field_map`: optional model-field → result-key mapping
39
+ - `column_map`: optional model-field → SQLAlchemy column mapping for SQLRules
40
+ - `parameters`: optional bound parameters forwarded to SQLAlchemy
41
+ - `on_reject`: `raise`, `collect`, or `skip`
42
+ (`callback` / `quarantine` / `log` planned for later releases)
43
+ - `use_sqlrules`: enable SQLRules constraint pushdown (default `True`)
44
+
45
+ Returns:
46
+
47
+ ``` python
48
+ QueryResult[UserRead]
49
+ ```
50
+
51
+ ------------------------------------------------------------------------
52
+
53
+ ### stream()
54
+
55
+ Validate rows incrementally while streaming large result sets.
56
+
57
+ > **Status (0.1.0):** Not implemented. Raises `NotImplementedError`.
58
+ > Planned for 0.3.0.
59
+
60
+ ``` python
61
+ for model in rowguard.stream(
62
+ session=session,
63
+ statement=stmt,
64
+ model=UserRead,
65
+ ):
66
+ ...
67
+ ```
68
+
69
+ ------------------------------------------------------------------------
70
+
71
+ ### execute()
72
+
73
+ Execute an existing SQLAlchemy statement instead of constructing one.
74
+
75
+ ``` python
76
+ result = rowguard.execute(
77
+ session=session,
78
+ statement=stmt,
79
+ model=UserRead,
80
+ source=None,
81
+ on_reject="raise",
82
+ use_sqlrules=True,
83
+ )
84
+ ```
85
+
86
+ Optional `source=` supplies the selectable used for SQLRules pushdown when
87
+ `use_sqlrules=True`. When both `statement` and `source` are provided, RowGuard
88
+ emits a `sqlrules.pushdown_source_explicit` diagnostic.
89
+
90
+ ------------------------------------------------------------------------
91
+
92
+ ### validate_rows()
93
+
94
+ Validate an iterable of row mappings without executing SQL.
95
+
96
+ ``` python
97
+ result = rowguard.validate_rows(
98
+ rows=rows,
99
+ model=UserRead,
100
+ field_map=None,
101
+ on_reject="raise",
102
+ )
103
+ ```
104
+
105
+ Useful for CSV readers, ETL pipelines, or custom data sources.
106
+
107
+ ## QueryResult
108
+
109
+ ``` python
110
+ result.models
111
+ result.rejected
112
+ result.statistics
113
+ result.statement
114
+ result.diagnostics
115
+ result.execution_time
116
+ ```
117
+
118
+ Convenience properties:
119
+
120
+ ``` python
121
+ result.valid_count
122
+ result.rejected_count # retained rejections only (0 under skip)
123
+ result.has_rejections # True if any row was rejected (including skip)
124
+ result.is_clean
125
+ ```
126
+
127
+ `has_rejections` is based on `statistics.rows_rejected`, not on whether
128
+ rejected rows were retained. Under `skip`, `has_rejections` may be `True`
129
+ while `rejected` is empty.
130
+
131
+ `execution_time` is end-to-end wall time in seconds (statement fetch plus
132
+ validation for SQL paths; full processing for `validate_rows`).
133
+
134
+ ## RejectedRow
135
+
136
+ Each rejected row exposes:
137
+
138
+ ``` python
139
+ rejected.index
140
+ rejected.model
141
+ rejected.mapping
142
+ rejected.validation_error
143
+ rejected.adaptation_error
144
+ rejected.raw_row
145
+ ```
146
+
147
+ ## Statistics
148
+
149
+ `QueryStatistics` fields:
150
+
151
+ - `rows_read`
152
+ - `rows_validated` (rows that reached Pydantic validation)
153
+ - `rows_accepted`
154
+ - `rows_rejected`
155
+ - `execution_time_ns`
156
+ - `adaptation_time_ns`
157
+ - `validation_time_ns`
158
+ - `rejection_time_ns`
159
+
160
+ ## Async API (Planned — 0.4.0)
161
+
162
+ > Not available in 0.1.0.
163
+
164
+ ``` python
165
+ await rowguard.aselect(...)
166
+ await rowguard.aexecute(...)
167
+ async for model in rowguard.astream(...):
168
+ ...
169
+ ```
170
+
171
+ ## Design Guidelines
172
+
173
+ - Return immutable result objects where practical.
174
+ - Never silently discard invalid rows without counting them.
175
+ - Keep function names short and predictable.
176
+ - Prefer explicit configuration over implicit behavior.
177
+ - Build on SQLAlchemy and SQLRules rather than replacing them.
@@ -0,0 +1,265 @@
1
+ # ARCHITECTURE.md
2
+
3
+ # RowGuard Architecture
4
+
5
+ ## Overview
6
+
7
+ RowGuard is a layered validation-first query engine built on top of
8
+ SQLAlchemy, Pydantic, and SQLRules.
9
+
10
+ Its architecture intentionally separates SQL compilation, query
11
+ execution, row adaptation, validation, rejection handling, and result
12
+ construction.
13
+
14
+ ``` text
15
+ Application
16
+
17
+
18
+ RowGuard Public API
19
+
20
+ ┌───────────────┼────────────────┐
21
+ ▼ ▼ ▼
22
+ Query Builder Validation Engine Streaming API
23
+ │ │ │
24
+ └───────────────┼────────────────┘
25
+
26
+ Execution Engine
27
+
28
+
29
+ SQLAlchemy Engine
30
+
31
+
32
+ Database
33
+ ```
34
+
35
+ ------------------------------------------------------------------------
36
+
37
+ # Architectural Principles
38
+
39
+ - Single responsibility per component
40
+ - SQLAlchemy-native
41
+ - Pydantic-native
42
+ - SQLRules for SQL pushdown only
43
+ - Immutable data flow where practical
44
+ - Explicit rejection handling
45
+ - Observable execution
46
+
47
+ ------------------------------------------------------------------------
48
+
49
+ # Layered Design
50
+
51
+ ## Layer 1 -- Public API
52
+
53
+ Responsibilities:
54
+
55
+ - Accept user requests
56
+ - Configure execution
57
+ - Return QueryResult
58
+
59
+ Representative API:
60
+
61
+ ``` python
62
+ rowguard.select(...)
63
+ rowguard.stream(...)
64
+ rowguard.execute(...)
65
+ rowguard.validate_rows(...)
66
+ ```
67
+
68
+ ------------------------------------------------------------------------
69
+
70
+ ## Layer 2 -- Query Builder
71
+
72
+ Responsibilities:
73
+
74
+ - Accept SQLAlchemy objects
75
+ - Invoke SQLRules
76
+ - Merge WHERE expressions
77
+ - Produce executable SQLAlchemy statements
78
+
79
+ Output:
80
+
81
+ ``` text
82
+ SQLAlchemy Select
83
+ ```
84
+
85
+ ------------------------------------------------------------------------
86
+
87
+ ## Layer 3 -- Execution Engine
88
+
89
+ Responsibilities:
90
+
91
+ - Execute statements
92
+ - Iterate database rows
93
+ - Support sync and async execution
94
+ - Support streaming
95
+
96
+ The execution engine never performs validation itself.
97
+
98
+ ------------------------------------------------------------------------
99
+
100
+ ## Layer 4 -- Row Adapter
101
+
102
+ Responsibilities:
103
+
104
+ - Convert SQLAlchemy rows into mappings
105
+ - Preserve column names
106
+ - Apply aliases when configured
107
+
108
+ Output:
109
+
110
+ ``` python
111
+ dict[str, object]
112
+ ```
113
+
114
+ ------------------------------------------------------------------------
115
+
116
+ ## Layer 5 -- Validation Engine
117
+
118
+ Responsibilities:
119
+
120
+ - Call `model_validate()`
121
+ - Produce typed Pydantic models
122
+ - Capture ValidationError objects
123
+ - Forward failures to rejection handling
124
+
125
+ Validation remains entirely delegated to Pydantic.
126
+
127
+ ------------------------------------------------------------------------
128
+
129
+ ## Layer 6 -- Rejection Handling
130
+
131
+ Responsibilities:
132
+
133
+ - Raise
134
+ - Collect
135
+ - Skip
136
+ - Log
137
+ - Callback
138
+ - Quarantine
139
+
140
+ Every rejected row follows the configured policy.
141
+
142
+ ------------------------------------------------------------------------
143
+
144
+ ## Layer 7 -- Result Assembly
145
+
146
+ Produces a QueryResult containing:
147
+
148
+ - validated models
149
+ - rejected rows
150
+ - execution statistics
151
+ - diagnostics
152
+ - executed statement
153
+
154
+ ------------------------------------------------------------------------
155
+
156
+ # SQLRules Integration
157
+
158
+ ``` text
159
+ Pydantic Model
160
+
161
+
162
+ SQLRules
163
+
164
+
165
+ WHERE Expressions
166
+
167
+
168
+ Query Builder
169
+ ```
170
+
171
+ SQLRules is responsible only for SQL-safe constraint compilation.
172
+
173
+ RowGuard owns execution and validation.
174
+
175
+ ------------------------------------------------------------------------
176
+
177
+ # Internal Components
178
+
179
+ ``` text
180
+ Public API
181
+
182
+ Compiler Bridge
183
+
184
+ Query Builder
185
+
186
+ Execution Engine
187
+
188
+ Row Adapter
189
+
190
+ Validation Engine
191
+
192
+ Reject Handler
193
+
194
+ Result Assembler
195
+ ```
196
+
197
+ Each component should be independently testable.
198
+
199
+ ------------------------------------------------------------------------
200
+
201
+ # Query Lifecycle
202
+
203
+ ``` text
204
+ Receive Request
205
+
206
+ Build Statement
207
+
208
+ Compile SQLRules Filters
209
+
210
+ Execute SQL
211
+
212
+ Adapt Row
213
+
214
+ Validate
215
+
216
+ Accept or Reject
217
+
218
+ Return QueryResult
219
+ ```
220
+
221
+ ------------------------------------------------------------------------
222
+
223
+ # Thread Safety
224
+
225
+ Compiler configuration and registries should be immutable after
226
+ initialization.
227
+
228
+ Execution state is scoped to each query.
229
+
230
+ ------------------------------------------------------------------------
231
+
232
+ # Extension Points
233
+
234
+ Future plugins may provide:
235
+
236
+ - row adapters
237
+ - reject handlers
238
+ - diagnostics
239
+ - dialect helpers
240
+ - result exporters
241
+
242
+ ------------------------------------------------------------------------
243
+
244
+ # Non-Goals
245
+
246
+ Architecture intentionally excludes:
247
+
248
+ - ORM mapping
249
+ - migrations
250
+ - SQL parsing
251
+ - schema reflection logic
252
+ - database drivers
253
+
254
+ ------------------------------------------------------------------------
255
+
256
+ # Design Goals
257
+
258
+ The architecture should make it easy to:
259
+
260
+ - support legacy databases
261
+ - integrate SQLRules
262
+ - validate every row
263
+ - stream millions of rows
264
+ - add async support
265
+ - extend behavior without changing the core
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] — 2026-07-10
9
+
10
+ ### Added
11
+
12
+ - Public sync API: `select()`, `execute()`, `validate_rows()`
13
+ - SQLAlchemy Core support for `Table` and existing `Select`
14
+ - Sync `Session` and `Connection` execution
15
+ - SQLRules pushdown integration (`use_sqlrules`, optional `column_map`)
16
+ - Pydantic v2 row validation via mapping adaptation
17
+ - Rejection policies: `raise`, `collect`, `skip`
18
+ - `QueryResult[T]`, `RejectedRow`, `QueryStatistics`, and diagnostics
19
+ - Optional `field_map` for result key remapping
20
+ - SQLite integration tests, unit tests, CI, and example
21
+
22
+ ### Fixed
23
+
24
+ - `field_map` no longer silently binds wrong keys or accepts defaults when mapped
25
+ source columns are missing (raises `RowAdaptationError`)
26
+ - SQLAlchemy results are closed after buffered execution
27
+ - Rows are processed incrementally so `raise` can abort without full prefetch
28
+ - Adaptation failures record timing and enrich `RowAdaptationError` with
29
+ `row_index` / `model` under the raise policy
30
+ - `execution_time` is end-to-end for SQL and `validate_rows` paths
31
+ - `rows_validated` counts only rows that reached Pydantic validation
32
+ - Explicit `statement` + `source` pushdown emits
33
+ `sqlrules.pushdown_source_explicit` diagnostic
34
+ - Require `sqlrules>=0.2.0` (needs `Compiler.diagnostics` for pushdown reporting)
35
+
36
+ ### Deferred
37
+
38
+ - `stream()` (planned for 0.3.0)
39
+ - Async APIs (planned for 0.4.0)
40
+ - ORM / SQLModel integrations
41
+ - Callback and quarantine rejection policies