sqlstratum 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {sqlstratum-0.1.0/sqlstratum.egg-info → sqlstratum-0.2.0}/PKG-INFO +70 -6
  2. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/README.md +66 -5
  3. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/pyproject.toml +12 -2
  4. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/ast.py +1 -1
  5. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/dsl.py +3 -3
  6. sqlstratum-0.1.0/sqlstratum/hydrate.py → sqlstratum-0.2.0/sqlstratum/hydrate/__init__.py +3 -3
  7. sqlstratum-0.2.0/sqlstratum/hydrate/pydantic.py +52 -0
  8. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/runner.py +2 -2
  9. {sqlstratum-0.1.0 → sqlstratum-0.2.0/sqlstratum.egg-info}/PKG-INFO +70 -6
  10. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum.egg-info/SOURCES.txt +3 -1
  11. sqlstratum-0.2.0/sqlstratum.egg-info/requires.txt +8 -0
  12. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_hydration.py +3 -3
  13. sqlstratum-0.2.0/tests/test_pydantic_hydration.py +79 -0
  14. sqlstratum-0.1.0/sqlstratum.egg-info/requires.txt +0 -4
  15. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/LICENSE +0 -0
  16. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/setup.cfg +0 -0
  17. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/__init__.py +0 -0
  18. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/compile.py +0 -0
  19. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/expr.py +0 -0
  20. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/meta.py +0 -0
  21. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum/types.py +0 -0
  22. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum.egg-info/dependency_links.txt +0 -0
  23. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/sqlstratum.egg-info/top_level.txt +0 -0
  24. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_compile_aggregate.py +0 -0
  25. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_compile_dml.py +0 -0
  26. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_compile_join.py +0 -0
  27. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_compile_select.py +0 -0
  28. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_runner_debug_logging.py +0 -0
  29. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_sqlite_integration.py +0 -0
  30. {sqlstratum-0.1.0 → sqlstratum-0.2.0}/tests/test_transactions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlstratum
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Lightweight, source-first SQL AST + compiler + runner.
5
5
  Author-email: Antonio Ognio <aognio@gmail.com>
6
6
  License: MIT License
@@ -47,13 +47,16 @@ Description-Content-Type: text/markdown
47
47
  License-File: LICENSE
48
48
  Provides-Extra: dev
49
49
  Requires-Dist: build>=1.2.0; extra == "dev"
50
+ Requires-Dist: poethepoet>=0.30.0; extra == "dev"
50
51
  Requires-Dist: twine>=5.0.0; extra == "dev"
52
+ Provides-Extra: pydantic
53
+ Requires-Dist: pydantic>=2; extra == "pydantic"
51
54
  Dynamic: license-file
52
55
 
53
56
  # SQLStratum
54
57
 
55
58
  <p align="center">
56
- <img src="assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
59
+ <img src="https://raw.githubusercontent.com/aognio/sqlstratum/main/assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
57
60
  </p>
58
61
 
59
62
  SQLStratum is a modern, typed, deterministic SQL query builder and compiler for Python with a
@@ -101,7 +104,7 @@ q = (
101
104
  SELECT(users.c.id, users.c.email)
102
105
  .FROM(users)
103
106
  .WHERE(users.c.active.is_true())
104
- .HYDRATE(dict)
107
+ .hydrate(dict)
105
108
  )
106
109
 
107
110
  rows = runner.fetch_all(q)
@@ -110,7 +113,7 @@ print(rows)
110
113
 
111
114
  ## Why `Table` objects?
112
115
  SQLStratum’s `Table` objects are the schema anchor for the typed, deterministic query builder. They
113
- provides column metadata and a stable namespace for column access, which enables predictable SQL
116
+ provide column metadata and a stable namespace for column access, which enables predictable SQL
114
117
  generation and safe parameter binding. They also support explicit aliasing to avoid ambiguous column
115
118
  names in joins.
116
119
 
@@ -118,7 +121,7 @@ names in joins.
118
121
  - AST: immutable query nodes in `sqlstratum/ast.py`
119
122
  - Compiler: SQL + params generation in `sqlstratum/compile.py`
120
123
  - Runner: SQLite execution and transactions in `sqlstratum/runner.py`
121
- - Hydration: projection rules and targets in `sqlstratum/hydrate.py`
124
+ - Hydration: projection rules and targets in `sqlstratum/hydrate/`
122
125
 
123
126
  ## SQL Debugging
124
127
  SQLStratum can log executed SQL statements (compiled SQL + parameters + duration), but logging is
@@ -154,6 +157,33 @@ SQL: <compiled sql> | params={<sorted params>} | duration_ms=<...>
154
157
  Architectural intent: logging happens at the Runner boundary (after execution). AST building and
155
158
  compilation remain deterministic and side-effect free, preserving separation of concerns.
156
159
 
160
+ ## Pydantic Hydration (Optional)
161
+ SQLStratum does not depend on Pydantic, but it provides an optional hydration adapter for Pydantic
162
+ v2 models.
163
+
164
+ Install:
165
+ ```
166
+ pip install sqlstratum[pydantic]
167
+ ```
168
+
169
+ Example:
170
+ ```python
171
+ from pydantic import BaseModel
172
+ from sqlstratum.hydrate.pydantic import hydrate_model, using_pydantic
173
+
174
+ class User(BaseModel):
175
+ id: int
176
+ email: str
177
+
178
+ row = {"id": "1", "email": "a@b.com"}
179
+ user = hydrate_model(User, row)
180
+
181
+ q = using_pydantic(
182
+ SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1)
183
+ ).hydrate(User)
184
+ user_row = runner.fetch_one(q)
185
+ ```
186
+
157
187
  ## Logo Inspiration
158
188
 
159
189
  Vinicunca (Rainbow Mountain) in Peru’s Cusco Region — a high-altitude day hike from
@@ -161,7 +191,7 @@ Cusco at roughly 5,036 m (16,500 ft). See [Vinicunca](https://en.wikipedia.org/w
161
191
  background.
162
192
 
163
193
  ## Versioning / Roadmap
164
- Current version: `0.1.0`.
194
+ Current version: `0.2.0`.
165
195
  Design notes and current limitations are tracked in `NOTES.md`. Roadmap planning is intentionally
166
196
  minimal at this stage and will evolve with real usage.
167
197
 
@@ -177,3 +207,37 @@ MIT License.
177
207
 
178
208
  ## Contributing
179
209
  PRs are welcome. Please read `CONTRIBUTING.md` for the workflow and expectations.
210
+
211
+ ## Documentation
212
+ Install docs dependencies:
213
+ ```bash
214
+ python -m pip install -r docs/requirements.txt
215
+ ```
216
+
217
+ Run the local docs server:
218
+ ```bash
219
+ mkdocs serve
220
+ ```
221
+
222
+ Build the static site:
223
+ ```bash
224
+ mkdocs build --clean
225
+ ```
226
+
227
+ Read the Docs will build documentation automatically once the repository is imported.
228
+
229
+ ## Release Automation
230
+ Install dev dependencies:
231
+ ```bash
232
+ python -m pip install -e ".[dev]"
233
+ ```
234
+
235
+ Run the full release pipeline:
236
+ ```bash
237
+ poe release
238
+ ```
239
+
240
+ This runs, in order:
241
+ - `python -m build --no-isolation`
242
+ - `python -m twine check dist/*`
243
+ - `python -m twine upload dist/*`
@@ -1,7 +1,7 @@
1
1
  # SQLStratum
2
2
 
3
3
  <p align="center">
4
- <img src="assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
4
+ <img src="https://raw.githubusercontent.com/aognio/sqlstratum/main/assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
5
5
  </p>
6
6
 
7
7
  SQLStratum is a modern, typed, deterministic SQL query builder and compiler for Python with a
@@ -49,7 +49,7 @@ q = (
49
49
  SELECT(users.c.id, users.c.email)
50
50
  .FROM(users)
51
51
  .WHERE(users.c.active.is_true())
52
- .HYDRATE(dict)
52
+ .hydrate(dict)
53
53
  )
54
54
 
55
55
  rows = runner.fetch_all(q)
@@ -58,7 +58,7 @@ print(rows)
58
58
 
59
59
  ## Why `Table` objects?
60
60
  SQLStratum’s `Table` objects are the schema anchor for the typed, deterministic query builder. They
61
- provides column metadata and a stable namespace for column access, which enables predictable SQL
61
+ provide column metadata and a stable namespace for column access, which enables predictable SQL
62
62
  generation and safe parameter binding. They also support explicit aliasing to avoid ambiguous column
63
63
  names in joins.
64
64
 
@@ -66,7 +66,7 @@ names in joins.
66
66
  - AST: immutable query nodes in `sqlstratum/ast.py`
67
67
  - Compiler: SQL + params generation in `sqlstratum/compile.py`
68
68
  - Runner: SQLite execution and transactions in `sqlstratum/runner.py`
69
- - Hydration: projection rules and targets in `sqlstratum/hydrate.py`
69
+ - Hydration: projection rules and targets in `sqlstratum/hydrate/`
70
70
 
71
71
  ## SQL Debugging
72
72
  SQLStratum can log executed SQL statements (compiled SQL + parameters + duration), but logging is
@@ -102,6 +102,33 @@ SQL: <compiled sql> | params={<sorted params>} | duration_ms=<...>
102
102
  Architectural intent: logging happens at the Runner boundary (after execution). AST building and
103
103
  compilation remain deterministic and side-effect free, preserving separation of concerns.
104
104
 
105
+ ## Pydantic Hydration (Optional)
106
+ SQLStratum does not depend on Pydantic, but it provides an optional hydration adapter for Pydantic
107
+ v2 models.
108
+
109
+ Install:
110
+ ```
111
+ pip install sqlstratum[pydantic]
112
+ ```
113
+
114
+ Example:
115
+ ```python
116
+ from pydantic import BaseModel
117
+ from sqlstratum.hydrate.pydantic import hydrate_model, using_pydantic
118
+
119
+ class User(BaseModel):
120
+ id: int
121
+ email: str
122
+
123
+ row = {"id": "1", "email": "a@b.com"}
124
+ user = hydrate_model(User, row)
125
+
126
+ q = using_pydantic(
127
+ SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1)
128
+ ).hydrate(User)
129
+ user_row = runner.fetch_one(q)
130
+ ```
131
+
105
132
  ## Logo Inspiration
106
133
 
107
134
  Vinicunca (Rainbow Mountain) in Peru’s Cusco Region — a high-altitude day hike from
@@ -109,7 +136,7 @@ Cusco at roughly 5,036 m (16,500 ft). See [Vinicunca](https://en.wikipedia.org/w
109
136
  background.
110
137
 
111
138
  ## Versioning / Roadmap
112
- Current version: `0.1.0`.
139
+ Current version: `0.2.0`.
113
140
  Design notes and current limitations are tracked in `NOTES.md`. Roadmap planning is intentionally
114
141
  minimal at this stage and will evolve with real usage.
115
142
 
@@ -125,3 +152,37 @@ MIT License.
125
152
 
126
153
  ## Contributing
127
154
  PRs are welcome. Please read `CONTRIBUTING.md` for the workflow and expectations.
155
+
156
+ ## Documentation
157
+ Install docs dependencies:
158
+ ```bash
159
+ python -m pip install -r docs/requirements.txt
160
+ ```
161
+
162
+ Run the local docs server:
163
+ ```bash
164
+ mkdocs serve
165
+ ```
166
+
167
+ Build the static site:
168
+ ```bash
169
+ mkdocs build --clean
170
+ ```
171
+
172
+ Read the Docs will build documentation automatically once the repository is imported.
173
+
174
+ ## Release Automation
175
+ Install dev dependencies:
176
+ ```bash
177
+ python -m pip install -e ".[dev]"
178
+ ```
179
+
180
+ Run the full release pipeline:
181
+ ```bash
182
+ poe release
183
+ ```
184
+
185
+ This runs, in order:
186
+ - `python -m build --no-isolation`
187
+ - `python -m twine check dist/*`
188
+ - `python -m twine upload dist/*`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sqlstratum"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Lightweight, source-first SQL AST + compiler + runner."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -37,8 +37,18 @@ Issues = "https://github.com/aognio/sqlstratum/issues"
37
37
  [project.optional-dependencies]
38
38
  dev = [
39
39
  "build>=1.2.0",
40
+ "poethepoet>=0.30.0",
40
41
  "twine>=5.0.0",
41
42
  ]
43
+ pydantic = [
44
+ "pydantic>=2",
45
+ ]
42
46
 
43
47
  [tool.setuptools]
44
- packages = ["sqlstratum"]
48
+ packages = ["sqlstratum", "sqlstratum.hydrate"]
49
+
50
+ [tool.poe.tasks]
51
+ release-build.cmd = "python -m build --no-isolation"
52
+ release-check.cmd = "python -m twine check dist/*"
53
+ release-upload.cmd = "python -m twine upload dist/*"
54
+ release.sequence = ["release-build", "release-check", "release-upload"]
@@ -27,7 +27,7 @@ class SelectQuery:
27
27
  limit: Optional[int]
28
28
  offset: Optional[int]
29
29
  distinct: bool
30
- hydrate: HydrationTarget
30
+ hydration: HydrationTarget
31
31
 
32
32
 
33
33
  @dataclass(frozen=True)
@@ -22,7 +22,7 @@ def SELECT(*projections: Expression) -> SelectQuery:
22
22
  limit=None,
23
23
  offset=None,
24
24
  distinct=False,
25
- hydrate=None,
25
+ hydration=None,
26
26
  )
27
27
 
28
28
 
@@ -100,7 +100,7 @@ def _as(self: SelectQuery, alias: str) -> Subquery:
100
100
 
101
101
 
102
102
  def _hydrate(self: SelectQuery, target: HydrationTarget) -> SelectQuery:
103
- return replace(self, hydrate=target)
103
+ return replace(self, hydration=target)
104
104
 
105
105
 
106
106
  SelectQuery.FROM = _from # type: ignore[attr-defined]
@@ -114,7 +114,7 @@ SelectQuery.LIMIT = _limit # type: ignore[attr-defined]
114
114
  SelectQuery.OFFSET = _offset # type: ignore[attr-defined]
115
115
  SelectQuery.DISTINCT = _distinct # type: ignore[attr-defined]
116
116
  SelectQuery.AS = _as # type: ignore[attr-defined]
117
- SelectQuery.HYDRATE = _hydrate # type: ignore[attr-defined]
117
+ SelectQuery.hydrate = _hydrate # type: ignore[attr-defined]
118
118
 
119
119
 
120
120
  class InsertBuilder:
@@ -4,9 +4,9 @@ from __future__ import annotations
4
4
  from dataclasses import is_dataclass
5
5
  from typing import Any, Dict, Iterable, List, Mapping, Sequence
6
6
 
7
- from .expr import AliasExpr, Function
8
- from .meta import Column
9
- from .types import HydrationTarget
7
+ from ..expr import AliasExpr, Function
8
+ from ..meta import Column
9
+ from ..types import HydrationTarget
10
10
 
11
11
 
12
12
  class HydrationError(ValueError):
@@ -0,0 +1,52 @@
1
+ """Optional Pydantic v2 hydration adapters."""
2
+ from __future__ import annotations
3
+
4
+ import importlib
5
+ from typing import Any, Mapping, Protocol, TypeVar
6
+
7
+ _INSTALL_MESSAGE = "Install with: pip install sqlstratum[pydantic]"
8
+
9
+
10
+ class _PydanticModel(Protocol):
11
+ @classmethod
12
+ def model_validate(cls, obj: Any) -> Any: # pragma: no cover - protocol signature
13
+ ...
14
+
15
+
16
+ TModel = TypeVar("TModel", bound=_PydanticModel)
17
+
18
+
19
+ def _import_pydantic():
20
+ return importlib.import_module("pydantic")
21
+
22
+
23
+ def is_pydantic_available() -> bool:
24
+ try:
25
+ _import_pydantic()
26
+ return True
27
+ except Exception:
28
+ return False
29
+
30
+
31
+ def hydrate_model(model_cls: type[TModel], data: Mapping[str, Any]) -> TModel:
32
+ try:
33
+ _import_pydantic()
34
+ except Exception as exc:
35
+ raise RuntimeError(_INSTALL_MESSAGE) from exc
36
+ return model_cls.model_validate(dict(data))
37
+
38
+
39
+ def hydrate_models(model_cls: type[TModel], rows: list[Mapping[str, Any]]) -> list[TModel]:
40
+ return [hydrate_model(model_cls, row) for row in rows]
41
+
42
+
43
+ class _PydanticHydrateWrapper:
44
+ def __init__(self, query: Any) -> None:
45
+ self._query = query
46
+
47
+ def hydrate(self, model_cls: type[TModel]) -> Any:
48
+ return self._query.hydrate(lambda m: hydrate_model(model_cls, m))
49
+
50
+
51
+ def using_pydantic(query: Any) -> _PydanticHydrateWrapper:
52
+ return _PydanticHydrateWrapper(query)
@@ -87,7 +87,7 @@ class Runner:
87
87
  rows = cur.fetchall()
88
88
  if log_enabled:
89
89
  _debug_log(compiled, (time.perf_counter() - start) * 1000)
90
- return hydrate_rows(rows, query.projections, query.hydrate or dict)
90
+ return hydrate_rows(rows, query.projections, query.hydration or dict)
91
91
 
92
92
  def fetch_one(self, query: ast.SelectQuery) -> Optional[Any]:
93
93
  compiled = compile(query)
@@ -100,7 +100,7 @@ class Runner:
100
100
  _debug_log(compiled, (time.perf_counter() - start) * 1000)
101
101
  if row is None:
102
102
  return None
103
- return hydrate_rows([row], query.projections, query.hydrate or dict)[0]
103
+ return hydrate_rows([row], query.projections, query.hydration or dict)[0]
104
104
 
105
105
  def scalar(self, query: ast.SelectQuery) -> Optional[Any]:
106
106
  compiled = compile(query)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlstratum
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Lightweight, source-first SQL AST + compiler + runner.
5
5
  Author-email: Antonio Ognio <aognio@gmail.com>
6
6
  License: MIT License
@@ -47,13 +47,16 @@ Description-Content-Type: text/markdown
47
47
  License-File: LICENSE
48
48
  Provides-Extra: dev
49
49
  Requires-Dist: build>=1.2.0; extra == "dev"
50
+ Requires-Dist: poethepoet>=0.30.0; extra == "dev"
50
51
  Requires-Dist: twine>=5.0.0; extra == "dev"
52
+ Provides-Extra: pydantic
53
+ Requires-Dist: pydantic>=2; extra == "pydantic"
51
54
  Dynamic: license-file
52
55
 
53
56
  # SQLStratum
54
57
 
55
58
  <p align="center">
56
- <img src="assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
59
+ <img src="https://raw.githubusercontent.com/aognio/sqlstratum/main/assets/images/SQLStratum-Logo-500x500-transparent.png" alt="SQLStratum logo" />
57
60
  </p>
58
61
 
59
62
  SQLStratum is a modern, typed, deterministic SQL query builder and compiler for Python with a
@@ -101,7 +104,7 @@ q = (
101
104
  SELECT(users.c.id, users.c.email)
102
105
  .FROM(users)
103
106
  .WHERE(users.c.active.is_true())
104
- .HYDRATE(dict)
107
+ .hydrate(dict)
105
108
  )
106
109
 
107
110
  rows = runner.fetch_all(q)
@@ -110,7 +113,7 @@ print(rows)
110
113
 
111
114
  ## Why `Table` objects?
112
115
  SQLStratum’s `Table` objects are the schema anchor for the typed, deterministic query builder. They
113
- provides column metadata and a stable namespace for column access, which enables predictable SQL
116
+ provide column metadata and a stable namespace for column access, which enables predictable SQL
114
117
  generation and safe parameter binding. They also support explicit aliasing to avoid ambiguous column
115
118
  names in joins.
116
119
 
@@ -118,7 +121,7 @@ names in joins.
118
121
  - AST: immutable query nodes in `sqlstratum/ast.py`
119
122
  - Compiler: SQL + params generation in `sqlstratum/compile.py`
120
123
  - Runner: SQLite execution and transactions in `sqlstratum/runner.py`
121
- - Hydration: projection rules and targets in `sqlstratum/hydrate.py`
124
+ - Hydration: projection rules and targets in `sqlstratum/hydrate/`
122
125
 
123
126
  ## SQL Debugging
124
127
  SQLStratum can log executed SQL statements (compiled SQL + parameters + duration), but logging is
@@ -154,6 +157,33 @@ SQL: <compiled sql> | params={<sorted params>} | duration_ms=<...>
154
157
  Architectural intent: logging happens at the Runner boundary (after execution). AST building and
155
158
  compilation remain deterministic and side-effect free, preserving separation of concerns.
156
159
 
160
+ ## Pydantic Hydration (Optional)
161
+ SQLStratum does not depend on Pydantic, but it provides an optional hydration adapter for Pydantic
162
+ v2 models.
163
+
164
+ Install:
165
+ ```
166
+ pip install sqlstratum[pydantic]
167
+ ```
168
+
169
+ Example:
170
+ ```python
171
+ from pydantic import BaseModel
172
+ from sqlstratum.hydrate.pydantic import hydrate_model, using_pydantic
173
+
174
+ class User(BaseModel):
175
+ id: int
176
+ email: str
177
+
178
+ row = {"id": "1", "email": "a@b.com"}
179
+ user = hydrate_model(User, row)
180
+
181
+ q = using_pydantic(
182
+ SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1)
183
+ ).hydrate(User)
184
+ user_row = runner.fetch_one(q)
185
+ ```
186
+
157
187
  ## Logo Inspiration
158
188
 
159
189
  Vinicunca (Rainbow Mountain) in Peru’s Cusco Region — a high-altitude day hike from
@@ -161,7 +191,7 @@ Cusco at roughly 5,036 m (16,500 ft). See [Vinicunca](https://en.wikipedia.org/w
161
191
  background.
162
192
 
163
193
  ## Versioning / Roadmap
164
- Current version: `0.1.0`.
194
+ Current version: `0.2.0`.
165
195
  Design notes and current limitations are tracked in `NOTES.md`. Roadmap planning is intentionally
166
196
  minimal at this stage and will evolve with real usage.
167
197
 
@@ -177,3 +207,37 @@ MIT License.
177
207
 
178
208
  ## Contributing
179
209
  PRs are welcome. Please read `CONTRIBUTING.md` for the workflow and expectations.
210
+
211
+ ## Documentation
212
+ Install docs dependencies:
213
+ ```bash
214
+ python -m pip install -r docs/requirements.txt
215
+ ```
216
+
217
+ Run the local docs server:
218
+ ```bash
219
+ mkdocs serve
220
+ ```
221
+
222
+ Build the static site:
223
+ ```bash
224
+ mkdocs build --clean
225
+ ```
226
+
227
+ Read the Docs will build documentation automatically once the repository is imported.
228
+
229
+ ## Release Automation
230
+ Install dev dependencies:
231
+ ```bash
232
+ python -m pip install -e ".[dev]"
233
+ ```
234
+
235
+ Run the full release pipeline:
236
+ ```bash
237
+ poe release
238
+ ```
239
+
240
+ This runs, in order:
241
+ - `python -m build --no-isolation`
242
+ - `python -m twine check dist/*`
243
+ - `python -m twine upload dist/*`
@@ -6,7 +6,6 @@ sqlstratum/ast.py
6
6
  sqlstratum/compile.py
7
7
  sqlstratum/dsl.py
8
8
  sqlstratum/expr.py
9
- sqlstratum/hydrate.py
10
9
  sqlstratum/meta.py
11
10
  sqlstratum/runner.py
12
11
  sqlstratum/types.py
@@ -15,11 +14,14 @@ sqlstratum.egg-info/SOURCES.txt
15
14
  sqlstratum.egg-info/dependency_links.txt
16
15
  sqlstratum.egg-info/requires.txt
17
16
  sqlstratum.egg-info/top_level.txt
17
+ sqlstratum/hydrate/__init__.py
18
+ sqlstratum/hydrate/pydantic.py
18
19
  tests/test_compile_aggregate.py
19
20
  tests/test_compile_dml.py
20
21
  tests/test_compile_join.py
21
22
  tests/test_compile_select.py
22
23
  tests/test_hydration.py
24
+ tests/test_pydantic_hydration.py
23
25
  tests/test_runner_debug_logging.py
24
26
  tests/test_sqlite_integration.py
25
27
  tests/test_transactions.py
@@ -0,0 +1,8 @@
1
+
2
+ [dev]
3
+ build>=1.2.0
4
+ poethepoet>=0.30.0
5
+ twine>=5.0.0
6
+
7
+ [pydantic]
8
+ pydantic>=2
@@ -30,7 +30,7 @@ class TestHydration(unittest.TestCase):
30
30
  self.conn.close()
31
31
 
32
32
  def test_dict_and_json(self):
33
- q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).HYDRATE(dict)
33
+ q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).hydrate(dict)
34
34
  rows = self.runner.fetch_all(q)
35
35
  self.assertEqual(rows, [{"id": 1, "email": "a@b.com"}])
36
36
  json.dumps(rows)
@@ -41,12 +41,12 @@ class TestHydration(unittest.TestCase):
41
41
  id: int
42
42
  email: str
43
43
 
44
- q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).HYDRATE(User)
44
+ q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).hydrate(User)
45
45
  row = self.runner.fetch_one(q)
46
46
  self.assertEqual(row, User(id=1, email="a@b.com"))
47
47
 
48
48
  def test_callable(self):
49
- q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).HYDRATE(
49
+ q = SELECT(users.c.id, users.c.email).FROM(users).WHERE(users.c.id == 1).hydrate(
50
50
  lambda m: f"{m['id']}:{m['email']}"
51
51
  )
52
52
  row = self.runner.fetch_one(q)
@@ -0,0 +1,79 @@
1
+ import importlib
2
+ import unittest
3
+ from unittest import mock
4
+
5
+ from sqlstratum.hydrate import pydantic as pydantic_hydrate
6
+
7
+
8
+ class TestPydanticHydration(unittest.TestCase):
9
+ def test_missing_pydantic_raises(self):
10
+ class DummyModel:
11
+ @classmethod
12
+ def model_validate(cls, obj):
13
+ return obj
14
+
15
+ with mock.patch(
16
+ "sqlstratum.hydrate.pydantic._import_pydantic",
17
+ side_effect=ImportError("no pydantic"),
18
+ ):
19
+ with self.assertRaises(RuntimeError) as cm:
20
+ pydantic_hydrate.hydrate_model(DummyModel, {"id": 1})
21
+ self.assertIn("pip install sqlstratum[pydantic]", str(cm.exception))
22
+
23
+ def test_is_pydantic_available_false_when_missing(self):
24
+ with mock.patch(
25
+ "sqlstratum.hydrate.pydantic._import_pydantic",
26
+ side_effect=ImportError("no pydantic"),
27
+ ):
28
+ self.assertFalse(pydantic_hydrate.is_pydantic_available())
29
+
30
+ def test_hydrate_model_and_models(self):
31
+ try:
32
+ pydantic = importlib.import_module("pydantic")
33
+ except Exception:
34
+ self.skipTest("Pydantic not installed")
35
+
36
+ class User(pydantic.BaseModel):
37
+ id: int
38
+ email: str
39
+
40
+ row = {"id": "1", "email": "a@b.com"}
41
+ user = pydantic_hydrate.hydrate_model(User, row)
42
+ self.assertIsInstance(user, User)
43
+ self.assertEqual(user.id, 1)
44
+ self.assertEqual(user.email, "a@b.com")
45
+
46
+ users = pydantic_hydrate.hydrate_models(User, [row])
47
+ self.assertEqual(users, [user])
48
+
49
+ def test_using_pydantic_wrapper(self):
50
+ try:
51
+ pydantic = importlib.import_module("pydantic")
52
+ except Exception:
53
+ self.skipTest("Pydantic not installed")
54
+
55
+ class User(pydantic.BaseModel):
56
+ id: int
57
+ email: str
58
+
59
+ class DummyQuery:
60
+ def __init__(self):
61
+ self.target = None
62
+
63
+ def hydrate(self, target):
64
+ self.target = target
65
+ return self
66
+
67
+ query = DummyQuery()
68
+ wrapped = pydantic_hydrate.using_pydantic(query)
69
+ result = wrapped.hydrate(User)
70
+ self.assertIs(result, query)
71
+ self.assertIsNotNone(query.target)
72
+ hydrated = query.target({"id": "2", "email": "b@c.com"})
73
+ self.assertIsInstance(hydrated, User)
74
+ self.assertEqual(hydrated.id, 2)
75
+ self.assertEqual(hydrated.email, "b@c.com")
76
+
77
+
78
+ if __name__ == "__main__":
79
+ unittest.main()
@@ -1,4 +0,0 @@
1
-
2
- [dev]
3
- build>=1.2.0
4
- twine>=5.0.0
File without changes
File without changes