sqlrules 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.
- sqlrules-0.1.0/.gitignore +19 -0
- sqlrules-0.1.0/CHANGELOG.md +51 -0
- sqlrules-0.1.0/CONTRIBUTING.md +53 -0
- sqlrules-0.1.0/LICENSE +21 -0
- sqlrules-0.1.0/PKG-INFO +127 -0
- sqlrules-0.1.0/README.md +89 -0
- sqlrules-0.1.0/docs/API.md +75 -0
- sqlrules-0.1.0/docs/ARCHITECTURE.md +25 -0
- sqlrules-0.1.0/docs/COMPILER.md +168 -0
- sqlrules-0.1.0/docs/CONSTRAINTS.md +20 -0
- sqlrules-0.1.0/docs/CONTRIBUTING.md +10 -0
- sqlrules-0.1.0/docs/DESIGN_DECISIONS.md +472 -0
- sqlrules-0.1.0/docs/DIALECT_SUPPORT.md +220 -0
- sqlrules-0.1.0/docs/ERRORS.md +184 -0
- sqlrules-0.1.0/docs/INTERNAL_API.md +249 -0
- sqlrules-0.1.0/docs/MILESTONES.md +276 -0
- sqlrules-0.1.0/docs/NON_GOALS.md +15 -0
- sqlrules-0.1.0/docs/PERFORMANCE.md +235 -0
- sqlrules-0.1.0/docs/PHILOSOPHY.md +13 -0
- sqlrules-0.1.0/docs/PLUGIN_SYSTEM.md +251 -0
- sqlrules-0.1.0/docs/ROADMAP.md +33 -0
- sqlrules-0.1.0/docs/SPEC.md +66 -0
- sqlrules-0.1.0/docs/TESTING.md +28 -0
- sqlrules-0.1.0/docs/TRANSLATORS.md +225 -0
- sqlrules-0.1.0/docs/TYPE_SUPPORT.md +245 -0
- sqlrules-0.1.0/docs/VISION.md +19 -0
- sqlrules-0.1.0/docs/index.md +43 -0
- sqlrules-0.1.0/pyproject.toml +102 -0
- sqlrules-0.1.0/src/sqlrules/__init__.py +37 -0
- sqlrules-0.1.0/src/sqlrules/columns.py +76 -0
- sqlrules-0.1.0/src/sqlrules/compiler.py +82 -0
- sqlrules-0.1.0/src/sqlrules/constraints.py +256 -0
- sqlrules-0.1.0/src/sqlrules/errors.py +97 -0
- sqlrules-0.1.0/src/sqlrules/inspectors.py +54 -0
- sqlrules-0.1.0/src/sqlrules/ir.py +28 -0
- sqlrules-0.1.0/src/sqlrules/py.typed +0 -0
- sqlrules-0.1.0/src/sqlrules/translators.py +150 -0
- sqlrules-0.1.0/tests/conftest.py +17 -0
- sqlrules-0.1.0/tests/test_correctness.py +283 -0
- sqlrules-0.1.0/tests/test_coverage_edges.py +85 -0
- sqlrules-0.1.0/tests/test_errors.py +87 -0
- sqlrules-0.1.0/tests/test_helpers.py +17 -0
- sqlrules-0.1.0/tests/test_literal_enum.py +32 -0
- sqlrules-0.1.0/tests/test_numeric_constraints.py +23 -0
- sqlrules-0.1.0/tests/test_string_constraints.py +17 -0
- sqlrules-0.1.0/tests/test_v01_features.py +180 -0
|
@@ -0,0 +1,51 @@
|
|
|
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/).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-07-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Public API: `compile()`, `where()`, `flatten()`, and `Compiler`
|
|
13
|
+
- Constraint support: `gt`, `ge`, `lt`, `le`, `multiple_of`, `min_length`,
|
|
14
|
+
`max_length`, `Literal`, and `Enum`
|
|
15
|
+
- Equivalent Pydantic forms: `Interval`, `conint`/`constr`, `Len`, and
|
|
16
|
+
`StringConstraints` (supported attributes only)
|
|
17
|
+
- Fail-fast handling for unsupported metadata (`pattern`, validators,
|
|
18
|
+
decimal precision) and unsupported types (containers, UUID, time,
|
|
19
|
+
timedelta)
|
|
20
|
+
- Field alias resolution when binding SQLAlchemy columns (`alias`,
|
|
21
|
+
`validation_alias`, and `serialization_alias` string forms)
|
|
22
|
+
- Unsupported-constraint policies: `raise` (default), `warn`, and `ignore`
|
|
23
|
+
for unknown **operators** (unsupported types always raise)
|
|
24
|
+
- Exception hierarchy rooted at `SQLRulesError`
|
|
25
|
+
- Column resolution for SQLAlchemy `Table` objects, ORM attributes, and
|
|
26
|
+
explicit `column_map`
|
|
27
|
+
- Documentation for the compiler, translators, errors, types, and roadmap
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Column resolution no longer treats non-column `Table` attributes (such as
|
|
32
|
+
`name` or `is_selectable`) as columns, which previously produced invalid
|
|
33
|
+
SQL or Python `bool` values in the rules dict
|
|
34
|
+
- `Annotated[T, ...] | None` / `Optional[Annotated[T, ...]]` now unwraps
|
|
35
|
+
nested metadata correctly
|
|
36
|
+
- String `validation_alias` / `serialization_alias` participate in column
|
|
37
|
+
binding
|
|
38
|
+
- `multiple_of` values `<= 0` are rejected
|
|
39
|
+
- Length constraints on non-`str` fields and numeric constraints on `str` /
|
|
40
|
+
`bool` fields are rejected
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Alias candidates are preferred over the Python field name when binding
|
|
45
|
+
columns
|
|
46
|
+
- Unconstrained fields are skipped and no longer require a matching column
|
|
47
|
+
- Packaging URLs point at `eddiethedean/sqlrules`
|
|
48
|
+
- Docs clarify that `on_unsupported` does not soften unsupported types;
|
|
49
|
+
plugin / two-phase compile designs are marked as future
|
|
50
|
+
|
|
51
|
+
[0.1.0]: https://github.com/eddiethedean/sqlrules/releases/tag/v0.1.0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping improve SQLRules.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
- Keep the public API minimal.
|
|
8
|
+
- Every feature must map directly to SQLAlchemy expressions.
|
|
9
|
+
- Unsupported Pydantic features should not be approximated.
|
|
10
|
+
- Prefer fail-fast behavior over silent semantic changes.
|
|
11
|
+
- Update relevant docs before or alongside implementation.
|
|
12
|
+
|
|
13
|
+
## Development setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m venv .venv
|
|
17
|
+
source .venv/bin/activate
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
pre-commit install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Checks
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
ruff check .
|
|
26
|
+
ruff format .
|
|
27
|
+
mypy src/sqlrules
|
|
28
|
+
pytest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Pull requests
|
|
32
|
+
|
|
33
|
+
- Keep changes focused.
|
|
34
|
+
- Add or update tests for every constraint or error path you touch.
|
|
35
|
+
- Match existing code style and documentation tone.
|
|
36
|
+
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
0.1.x releases are published to PyPI by pushing a version tag. Before tagging:
|
|
40
|
+
|
|
41
|
+
1. Confirm `pyproject.toml` and `sqlrules.__version__` match the changelog.
|
|
42
|
+
2. Run the full check suite (`ruff`, `mypy`, `pytest`).
|
|
43
|
+
3. Ensure CI is green on `main`.
|
|
44
|
+
|
|
45
|
+
Then create and push the tag (example for 0.1.0):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git tag -a v0.1.0 -m "sqlrules 0.1.0"
|
|
49
|
+
git push origin v0.1.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The [release workflow](.github/workflows/release.yml) runs CI, builds the
|
|
53
|
+
sdist/wheel, and publishes with `PYPI_API_TOKEN`.
|
sqlrules-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SQLRules Contributors
|
|
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.
|
sqlrules-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlrules
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compile constrained Pydantic models into SQLAlchemy WHERE-rule dictionaries.
|
|
5
|
+
Project-URL: Homepage, https://github.com/eddiethedean/sqlrules
|
|
6
|
+
Project-URL: Documentation, https://github.com/eddiethedean/sqlrules#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/eddiethedean/sqlrules
|
|
8
|
+
Project-URL: Changelog, https://github.com/eddiethedean/sqlrules/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/eddiethedean/sqlrules/issues
|
|
10
|
+
Author: SQLRules Contributors
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: compiler,constraints,filters,pydantic,sqlalchemy,where
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: annotated-types>=0.6
|
|
29
|
+
Requires-Dist: pydantic>=2.0
|
|
30
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
33
|
+
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# SQLRules
|
|
40
|
+
|
|
41
|
+
**Compile constrained Pydantic models into SQLAlchemy WHERE-rule dictionaries.**
|
|
42
|
+
|
|
43
|
+
One package. One job. Deterministic output. Zero database dependency.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from typing import Annotated
|
|
47
|
+
from pydantic import BaseModel, Field
|
|
48
|
+
from sqlalchemy import Table, Column, Integer, String, MetaData
|
|
49
|
+
|
|
50
|
+
import sqlrules
|
|
51
|
+
|
|
52
|
+
metadata = MetaData()
|
|
53
|
+
|
|
54
|
+
users = Table(
|
|
55
|
+
"users",
|
|
56
|
+
metadata,
|
|
57
|
+
Column("age", Integer),
|
|
58
|
+
Column("name", String),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
class UserFilter(BaseModel):
|
|
62
|
+
age: Annotated[int, Field(ge=18, le=65)]
|
|
63
|
+
name: Annotated[str, Field(min_length=2)]
|
|
64
|
+
|
|
65
|
+
rules = sqlrules.compile(UserFilter, users)
|
|
66
|
+
# {
|
|
67
|
+
# "age": [users.c.age >= 18, users.c.age <= 65],
|
|
68
|
+
# "name": [func.length(users.c.name) >= 2],
|
|
69
|
+
# }
|
|
70
|
+
|
|
71
|
+
stmt = users.select().where(*sqlrules.where(rules))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install sqlrules
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
81
|
+
|
|
82
|
+
## Supported constraints (0.1)
|
|
83
|
+
|
|
84
|
+
| Constraint | SQLAlchemy expression |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `gt` / `ge` / `lt` / `le` | `column > / >= / < / <= value` |
|
|
87
|
+
| `multiple_of` | `column % value == 0` |
|
|
88
|
+
| `min_length` / `max_length` | `func.length(column) >= / <= value` |
|
|
89
|
+
| `Literal[...]` | `column.in_(...)` |
|
|
90
|
+
| `Enum` | `column.in_(...)` |
|
|
91
|
+
|
|
92
|
+
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
93
|
+
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
94
|
+
constraint operators (unsupported types always raise).
|
|
95
|
+
|
|
96
|
+
## Public API
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise")
|
|
100
|
+
sqlrules.where(rules) # flatten all expressions
|
|
101
|
+
sqlrules.flatten(rules) # alias of where()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Non-goals
|
|
105
|
+
|
|
106
|
+
SQLRules is not an ORM, validator, query builder, SQL string generator,
|
|
107
|
+
migration tool, or database client. It only compiles supported Pydantic
|
|
108
|
+
constraints into SQLAlchemy expressions.
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
113
|
+
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
114
|
+
[architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install -e ".[dev]"
|
|
120
|
+
pytest
|
|
121
|
+
ruff check .
|
|
122
|
+
mypy src/sqlrules
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
sqlrules-0.1.0/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# SQLRules
|
|
2
|
+
|
|
3
|
+
**Compile constrained Pydantic models into SQLAlchemy WHERE-rule dictionaries.**
|
|
4
|
+
|
|
5
|
+
One package. One job. Deterministic output. Zero database dependency.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from sqlalchemy import Table, Column, Integer, String, MetaData
|
|
11
|
+
|
|
12
|
+
import sqlrules
|
|
13
|
+
|
|
14
|
+
metadata = MetaData()
|
|
15
|
+
|
|
16
|
+
users = Table(
|
|
17
|
+
"users",
|
|
18
|
+
metadata,
|
|
19
|
+
Column("age", Integer),
|
|
20
|
+
Column("name", String),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
class UserFilter(BaseModel):
|
|
24
|
+
age: Annotated[int, Field(ge=18, le=65)]
|
|
25
|
+
name: Annotated[str, Field(min_length=2)]
|
|
26
|
+
|
|
27
|
+
rules = sqlrules.compile(UserFilter, users)
|
|
28
|
+
# {
|
|
29
|
+
# "age": [users.c.age >= 18, users.c.age <= 65],
|
|
30
|
+
# "name": [func.length(users.c.name) >= 2],
|
|
31
|
+
# }
|
|
32
|
+
|
|
33
|
+
stmt = users.select().where(*sqlrules.where(rules))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install sqlrules
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
|
|
43
|
+
|
|
44
|
+
## Supported constraints (0.1)
|
|
45
|
+
|
|
46
|
+
| Constraint | SQLAlchemy expression |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `gt` / `ge` / `lt` / `le` | `column > / >= / < / <= value` |
|
|
49
|
+
| `multiple_of` | `column % value == 0` |
|
|
50
|
+
| `min_length` / `max_length` | `func.length(column) >= / <= value` |
|
|
51
|
+
| `Literal[...]` | `column.in_(...)` |
|
|
52
|
+
| `Enum` | `column.in_(...)` |
|
|
53
|
+
|
|
54
|
+
Unsupported constraints raise `UnsupportedConstraintError` by default.
|
|
55
|
+
Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
|
|
56
|
+
constraint operators (unsupported types always raise).
|
|
57
|
+
|
|
58
|
+
## Public API
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise")
|
|
62
|
+
sqlrules.where(rules) # flatten all expressions
|
|
63
|
+
sqlrules.flatten(rules) # alias of where()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Non-goals
|
|
67
|
+
|
|
68
|
+
SQLRules is not an ORM, validator, query builder, SQL string generator,
|
|
69
|
+
migration tool, or database client. It only compiles supported Pydantic
|
|
70
|
+
constraints into SQLAlchemy expressions.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
See [`docs/index.md`](docs/index.md) for the full documentation set,
|
|
75
|
+
including the [spec](docs/SPEC.md), [API](docs/API.md),
|
|
76
|
+
[architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install -e ".[dev]"
|
|
82
|
+
pytest
|
|
83
|
+
ruff check .
|
|
84
|
+
mypy src/sqlrules
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Public API
|
|
2
|
+
|
|
3
|
+
SQLRules exposes a deliberately small public surface.
|
|
4
|
+
|
|
5
|
+
## `compile`
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
sqlrules.compile(
|
|
9
|
+
model,
|
|
10
|
+
table,
|
|
11
|
+
*,
|
|
12
|
+
column_map=None,
|
|
13
|
+
on_unsupported="raise",
|
|
14
|
+
) -> dict[str, list[ColumnElement[bool]]]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Compile a constrained Pydantic model into a field-keyed rule dictionary.
|
|
18
|
+
|
|
19
|
+
| Parameter | Description |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `model` | Pydantic `BaseModel` subclass |
|
|
22
|
+
| `table` | SQLAlchemy `Table`, alias, ORM class, or object with `.c` |
|
|
23
|
+
| `column_map` | Optional explicit `field_name` or alias → column mapping |
|
|
24
|
+
| `on_unsupported` | `"raise"` (default), `"warn"`, or `"ignore"` for unknown **operators** only |
|
|
25
|
+
|
|
26
|
+
Rule dictionary keys are always the Python field names. String field aliases
|
|
27
|
+
(`alias`, `validation_alias`, `serialization_alias`) are used only for column
|
|
28
|
+
binding. Unconstrained fields are omitted and do not require a column.
|
|
29
|
+
|
|
30
|
+
Unsupported types always raise, regardless of `on_unsupported`.
|
|
31
|
+
|
|
32
|
+
## `where` / `flatten`
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
sqlrules.where(rules) -> list[ColumnElement[bool]]
|
|
36
|
+
sqlrules.flatten(rules) -> list[ColumnElement[bool]]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Flatten a rule dictionary into a single list of expressions suitable for
|
|
40
|
+
`Query.where(*expressions)` or `select(...).where(*expressions)`.
|
|
41
|
+
|
|
42
|
+
`where` and `flatten` are identical aliases.
|
|
43
|
+
|
|
44
|
+
## `Compiler`
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
compiler = sqlrules.Compiler(on_unsupported="raise", registry=None)
|
|
48
|
+
rules = compiler.compile(model, table, column_map=None)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Reusable compiler instance with a fixed unsupported-constraint policy and
|
|
52
|
+
optional custom translator registry.
|
|
53
|
+
|
|
54
|
+
## Other exports
|
|
55
|
+
|
|
56
|
+
Also exported for advanced use and typing:
|
|
57
|
+
|
|
58
|
+
- `__version__`
|
|
59
|
+
- `SQLRulesWarning`
|
|
60
|
+
- `CompilationContext`, `Constraint`, `FieldDescriptor` (IR helpers)
|
|
61
|
+
|
|
62
|
+
## Exceptions
|
|
63
|
+
|
|
64
|
+
All public exceptions inherit from `SQLRulesError`:
|
|
65
|
+
|
|
66
|
+
- `InvalidModelError`
|
|
67
|
+
- `MissingColumnError`
|
|
68
|
+
- `UnsupportedConstraintError`
|
|
69
|
+
- `TranslatorError`
|
|
70
|
+
- `InvalidTranslatorError` (reserved; not raised in 0.1)
|
|
71
|
+
- `RegistryError`
|
|
72
|
+
- `ConfigurationError`
|
|
73
|
+
- `InternalCompilerError` (reserved; not raised in 0.1)
|
|
74
|
+
|
|
75
|
+
See [ERRORS.md](ERRORS.md) for details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
```text
|
|
4
|
+
Pydantic Model
|
|
5
|
+
↓
|
|
6
|
+
Metadata Reader
|
|
7
|
+
↓
|
|
8
|
+
Constraint IR
|
|
9
|
+
↓
|
|
10
|
+
SQLAlchemy Compiler
|
|
11
|
+
↓
|
|
12
|
+
Rule Dictionary
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
SQLRules is a pure compiler pipeline:
|
|
16
|
+
|
|
17
|
+
1. **Inspect** the Pydantic model and preserve field declaration order.
|
|
18
|
+
2. **Resolve** each field to a SQLAlchemy column.
|
|
19
|
+
3. **Extract** supported constraints into a dialect-neutral IR.
|
|
20
|
+
4. **Translate** each IR constraint into one SQLAlchemy expression.
|
|
21
|
+
5. **Assemble** expressions into `dict[str, list[ColumnElement[bool]]]`.
|
|
22
|
+
|
|
23
|
+
The compiler never connects to a database, executes SQL, or renders SQL
|
|
24
|
+
strings. See [COMPILER.md](COMPILER.md) and [INTERNAL_API.md](INTERNAL_API.md)
|
|
25
|
+
for stage-level detail.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# SQLRules Compiler Architecture
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The SQLRules compiler transforms a constrained Pydantic model into a
|
|
6
|
+
dictionary of SQLAlchemy WHERE expressions.
|
|
7
|
+
|
|
8
|
+
The compiler never connects to a database, executes SQL, or generates
|
|
9
|
+
SQL strings. It only produces SQLAlchemy expression objects.
|
|
10
|
+
|
|
11
|
+
## Pipeline
|
|
12
|
+
|
|
13
|
+
``` text
|
|
14
|
+
Pydantic Model
|
|
15
|
+
│
|
|
16
|
+
▼
|
|
17
|
+
Model Introspection
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
Field Extraction
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
Constraint Extraction
|
|
24
|
+
│
|
|
25
|
+
▼
|
|
26
|
+
Intermediate Representation (IR)
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
Constraint Translators
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
SQLAlchemy Expressions
|
|
33
|
+
│
|
|
34
|
+
▼
|
|
35
|
+
Rule Dictionary
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Stage 1 -- Model Introspection
|
|
39
|
+
|
|
40
|
+
Inputs: - Pydantic BaseModel subclass - SQLAlchemy Table, Alias, ORM
|
|
41
|
+
class, or column mapping
|
|
42
|
+
|
|
43
|
+
Responsibilities: - Validate the input model. - Enumerate model
|
|
44
|
+
fields. - Preserve declaration order.
|
|
45
|
+
|
|
46
|
+
Output: - Iterable of field definitions.
|
|
47
|
+
|
|
48
|
+
## Stage 2 -- Field Extraction
|
|
49
|
+
|
|
50
|
+
For each field:
|
|
51
|
+
|
|
52
|
+
- Python type
|
|
53
|
+
- Optionality
|
|
54
|
+
- Metadata
|
|
55
|
+
- Field name
|
|
56
|
+
- Alias (optional)
|
|
57
|
+
|
|
58
|
+
Unsupported field definitions fail fast.
|
|
59
|
+
|
|
60
|
+
## Stage 3 -- Constraint Extraction
|
|
61
|
+
|
|
62
|
+
Read only constraints that have deterministic SQL equivalents.
|
|
63
|
+
|
|
64
|
+
Examples: - gt - ge - lt - le - multiple_of - min_length - max_length -
|
|
65
|
+
Literal - Enum
|
|
66
|
+
|
|
67
|
+
Constraints without SQL equivalents are delegated to the
|
|
68
|
+
unsupported-constraint policy.
|
|
69
|
+
|
|
70
|
+
## Stage 4 -- Intermediate Representation
|
|
71
|
+
|
|
72
|
+
Each constraint becomes a normalized object.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
|
|
76
|
+
``` text
|
|
77
|
+
Constraint(
|
|
78
|
+
field="age",
|
|
79
|
+
operator="ge",
|
|
80
|
+
value=18,
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The IR isolates compiler logic from both Pydantic and SQLAlchemy APIs.
|
|
85
|
+
|
|
86
|
+
## Stage 5 -- Translators
|
|
87
|
+
|
|
88
|
+
Each constraint is translated independently.
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
|
|
92
|
+
- ge → column \>= value
|
|
93
|
+
- gt → column \> value
|
|
94
|
+
- min_length → func.length(column) \>= value
|
|
95
|
+
- Literal → column.in\_(...)
|
|
96
|
+
|
|
97
|
+
Every translator returns one SQLAlchemy expression.
|
|
98
|
+
|
|
99
|
+
## Stage 6 -- Rule Assembly
|
|
100
|
+
|
|
101
|
+
Expressions are grouped by field.
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
|
|
105
|
+
``` python
|
|
106
|
+
{
|
|
107
|
+
"age": [
|
|
108
|
+
users.c.age >= 18,
|
|
109
|
+
users.c.age <= 65,
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Ordering is deterministic and follows the source model.
|
|
115
|
+
|
|
116
|
+
## Error Handling
|
|
117
|
+
|
|
118
|
+
Modes:
|
|
119
|
+
|
|
120
|
+
- raise (default)
|
|
121
|
+
- warn
|
|
122
|
+
- ignore
|
|
123
|
+
|
|
124
|
+
Errors include:
|
|
125
|
+
|
|
126
|
+
- UnsupportedConstraintError
|
|
127
|
+
- MissingColumnError
|
|
128
|
+
- InvalidModelError
|
|
129
|
+
- TranslatorError
|
|
130
|
+
|
|
131
|
+
## Caching
|
|
132
|
+
|
|
133
|
+
Future versions may cache compiled metadata using the model type as the
|
|
134
|
+
cache key. Translation remains deterministic.
|
|
135
|
+
|
|
136
|
+
## Extension Points
|
|
137
|
+
|
|
138
|
+
Future plugin hooks:
|
|
139
|
+
|
|
140
|
+
- Register custom translators.
|
|
141
|
+
- Register dialect-specific translators.
|
|
142
|
+
- Override built-in mappings.
|
|
143
|
+
- Add new IR transforms.
|
|
144
|
+
|
|
145
|
+
## Design Principles
|
|
146
|
+
|
|
147
|
+
- Pure function compiler
|
|
148
|
+
- No database dependency
|
|
149
|
+
- Deterministic output
|
|
150
|
+
- SQLAlchemy-first
|
|
151
|
+
- Easy to test
|
|
152
|
+
- Easy to extend
|
|
153
|
+
- Fail fast on unsupported semantics
|
|
154
|
+
|
|
155
|
+
## Public Contract
|
|
156
|
+
|
|
157
|
+
``` python
|
|
158
|
+
rules = sqlrules.compile(UserFilter, users)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
|
|
163
|
+
``` python
|
|
164
|
+
dict[str, list]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
where each value is a list of SQLAlchemy boolean expressions suitable
|
|
168
|
+
for passing directly to `.where(*expressions)`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Supported Constraint Mapping
|
|
2
|
+
|
|
3
|
+
| Constraint | SQLAlchemy expression |
|
|
4
|
+
|---|---|
|
|
5
|
+
| `gt` | `column > value` |
|
|
6
|
+
| `ge` | `column >= value` |
|
|
7
|
+
| `lt` | `column < value` |
|
|
8
|
+
| `le` | `column <= value` |
|
|
9
|
+
| `min_length` | `func.length(column) >= value` |
|
|
10
|
+
| `max_length` | `func.length(column) <= value` |
|
|
11
|
+
| `multiple_of` | `column % value == 0` |
|
|
12
|
+
| `Literal[...]` | `column.in_(...)` |
|
|
13
|
+
| `Enum` | `column.in_(member values)` |
|
|
14
|
+
|
|
15
|
+
Accepted input forms for the same semantics include `Field(...)`,
|
|
16
|
+
`annotated_types` primitives, `Interval`, `Len`, `conint`/`constr`, and
|
|
17
|
+
`StringConstraints` (length attributes only).
|
|
18
|
+
|
|
19
|
+
Constraints without a deterministic SQL equivalent are rejected by default.
|
|
20
|
+
See [TYPE_SUPPORT.md](TYPE_SUPPORT.md) and [ERRORS.md](ERRORS.md).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
See the root [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup,
|
|
4
|
+
checks, and pull-request guidelines.
|
|
5
|
+
|
|
6
|
+
Key rules:
|
|
7
|
+
|
|
8
|
+
- Keep the public API minimal.
|
|
9
|
+
- Every feature must map directly to SQLAlchemy expressions.
|
|
10
|
+
- Unsupported Pydantic features should not be approximated.
|