sqlphilosophy 0.2.1__tar.gz → 0.3.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 (34) hide show
  1. {sqlphilosophy-0.2.1/src/sqlphilosophy.egg-info → sqlphilosophy-0.3.0}/PKG-INFO +4 -1
  2. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/README.md +3 -0
  3. sqlphilosophy-0.3.0/src/sqlphilosophy/VERSION +1 -0
  4. sqlphilosophy-0.3.0/src/sqlphilosophy/strict.py +142 -0
  5. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0/src/sqlphilosophy.egg-info}/PKG-INFO +4 -1
  6. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/SOURCES.txt +1 -0
  7. sqlphilosophy-0.2.1/src/sqlphilosophy/VERSION +0 -1
  8. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/LICENSE +0 -0
  9. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/MANIFEST.in +0 -0
  10. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/pyproject.toml +0 -0
  11. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/setup.cfg +0 -0
  12. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/__init__.py +0 -0
  13. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/_repository_shared.py +0 -0
  14. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/__init__.py +0 -0
  15. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/protocols.py +0 -0
  16. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/query.py +0 -0
  17. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/repository.py +0 -0
  18. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/__init__.py +0 -0
  19. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/context.py +0 -0
  20. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/fields.py +0 -0
  21. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/listener.py +0 -0
  22. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/model.py +0 -0
  23. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/py.typed +0 -0
  24. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sorting.py +0 -0
  25. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sql.py +0 -0
  26. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/__init__.py +0 -0
  27. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/protocols.py +0 -0
  28. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/query.py +0 -0
  29. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/repository.py +0 -0
  30. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/trusted_sql.py +0 -0
  31. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/types.py +0 -0
  32. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/dependency_links.txt +0 -0
  33. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/requires.txt +0 -0
  34. {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlphilosophy
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Portable SQLAlchemy repository kit: sync and async CRUD, statement builders, sort/pagination, and SQL helpers.
5
5
  Author-email: Josh Martin <denverprogrammer@gmail.com>
6
6
  License-Expression: MIT
@@ -68,6 +68,8 @@ Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builde
68
68
  - **Repository pattern** for a single mapped model (`BaseRepository`, `AsyncBaseRepository`).
69
69
  - **Fluent query builders** with pagination/sort (`StatementQueryBuilder`, `ListQuery`, `SortConfig`).
70
70
  - **SQL helpers** for row mapping, partial updates, and developer-defined fragments via **`sqlphilosophy.trusted_sql`**.
71
+ - **Opt-in strict row decoders** via `sqlphilosophy.strict` for non-coercing
72
+ scalars, timestamps, enums, collections, UUIDs, and recursive JSON.
71
73
  - **Optional audit listeners** and timestamp mixins.
72
74
 
73
75
  ## What this package does not do
@@ -132,6 +134,7 @@ with SessionLocal() as session:
132
134
  | `sqlphilosophy` | `__version__` only |
133
135
  | `sqlphilosophy.types` | Portable typing aliases |
134
136
  | `sqlphilosophy.sql` | Row mapping, partial updates, Core helpers (re-exports `trusted_sql`) |
137
+ | `sqlphilosophy.strict` | Opt-in non-coercing row decoders and strict JSON validation |
135
138
  | `sqlphilosophy.trusted_sql` | Developer-trusted SQL fragments — see [SECURITY.md](./SECURITY.md) |
136
139
  | `sqlphilosophy.sorting` | `ListQuery`, `SortConfig`, `SortSpec` |
137
140
  | `sqlphilosophy.sync` / `sqlphilosophy.aio` | Repositories, query builders, factory protocols |
@@ -28,6 +28,8 @@ Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builde
28
28
  - **Repository pattern** for a single mapped model (`BaseRepository`, `AsyncBaseRepository`).
29
29
  - **Fluent query builders** with pagination/sort (`StatementQueryBuilder`, `ListQuery`, `SortConfig`).
30
30
  - **SQL helpers** for row mapping, partial updates, and developer-defined fragments via **`sqlphilosophy.trusted_sql`**.
31
+ - **Opt-in strict row decoders** via `sqlphilosophy.strict` for non-coercing
32
+ scalars, timestamps, enums, collections, UUIDs, and recursive JSON.
31
33
  - **Optional audit listeners** and timestamp mixins.
32
34
 
33
35
  ## What this package does not do
@@ -92,6 +94,7 @@ with SessionLocal() as session:
92
94
  | `sqlphilosophy` | `__version__` only |
93
95
  | `sqlphilosophy.types` | Portable typing aliases |
94
96
  | `sqlphilosophy.sql` | Row mapping, partial updates, Core helpers (re-exports `trusted_sql`) |
97
+ | `sqlphilosophy.strict` | Opt-in non-coercing row decoders and strict JSON validation |
95
98
  | `sqlphilosophy.trusted_sql` | Developer-trusted SQL fragments — see [SECURITY.md](./SECURITY.md) |
96
99
  | `sqlphilosophy.sorting` | `ListQuery`, `SortConfig`, `SortSpec` |
97
100
  | `sqlphilosophy.sync` / `sqlphilosophy.aio` | Repositories, query builders, factory protocols |
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,142 @@
1
+ """Opt-in, non-coercing row decoders for repository boundaries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from datetime import date, datetime
7
+ from enum import Enum
8
+ from math import isfinite
9
+ from typing import TypeGuard
10
+ from uuid import UUID
11
+
12
+ from sqlphilosophy.types import JSONValue
13
+
14
+ __all__ = [
15
+ "is_strict_json_value",
16
+ "strict_row_bool",
17
+ "strict_row_date",
18
+ "strict_row_datetime",
19
+ "strict_row_enum",
20
+ "strict_row_float",
21
+ "strict_row_int",
22
+ "strict_row_json",
23
+ "strict_row_json_object",
24
+ "strict_row_optional",
25
+ "strict_row_str",
26
+ "strict_row_str_list",
27
+ "strict_row_uuid",
28
+ ]
29
+
30
+ Row = Mapping[str, object]
31
+
32
+
33
+ def _required(row: Row, key: str) -> object:
34
+ return row[key]
35
+
36
+
37
+ def _type_error(key: str, expected: str, value: object) -> TypeError:
38
+ return TypeError(f"expected strict {expected} for {key!r}, got {type(value).__name__}")
39
+
40
+
41
+ def strict_row_int(row: Row, key: str) -> int:
42
+ value = _required(row, key)
43
+ if type(value) is not int:
44
+ raise _type_error(key, "int", value)
45
+ return value
46
+
47
+
48
+ def strict_row_str(row: Row, key: str) -> str:
49
+ value = _required(row, key)
50
+ if type(value) is not str:
51
+ raise _type_error(key, "str", value)
52
+ return value
53
+
54
+
55
+ def strict_row_bool(row: Row, key: str) -> bool:
56
+ value = _required(row, key)
57
+ if type(value) is not bool:
58
+ raise _type_error(key, "bool", value)
59
+ return value
60
+
61
+
62
+ def strict_row_float(row: Row, key: str) -> float:
63
+ value = _required(row, key)
64
+ if type(value) is not float or not isfinite(value):
65
+ raise _type_error(key, "finite float", value)
66
+ return value
67
+
68
+
69
+ def strict_row_datetime(row: Row, key: str) -> datetime:
70
+ value = _required(row, key)
71
+ if type(value) is not datetime:
72
+ raise _type_error(key, "datetime", value)
73
+ return value
74
+
75
+
76
+ def strict_row_date(row: Row, key: str) -> date:
77
+ value = _required(row, key)
78
+ if type(value) is not date:
79
+ raise _type_error(key, "date", value)
80
+ return value
81
+
82
+
83
+ def strict_row_uuid(row: Row, key: str) -> UUID:
84
+ value = _required(row, key)
85
+ if not isinstance(value, UUID):
86
+ raise _type_error(key, "UUID", value)
87
+ return value
88
+
89
+
90
+ def strict_row_enum[EnumT: Enum](row: Row, key: str, enum_type: type[EnumT]) -> EnumT:
91
+ value = _required(row, key)
92
+ if isinstance(value, enum_type):
93
+ return value
94
+ if type(value) is str:
95
+ try:
96
+ return enum_type(value)
97
+ except ValueError as error:
98
+ raise _type_error(key, enum_type.__name__, value) from error
99
+ raise _type_error(key, enum_type.__name__, value)
100
+
101
+
102
+ def strict_row_str_list(row: Row, key: str) -> list[str]:
103
+ value = _required(row, key)
104
+ if type(value) is not list or not all(type(item) is str for item in value):
105
+ raise _type_error(key, "list[str]", value)
106
+ return value
107
+
108
+
109
+ def is_strict_json_value(value: object) -> TypeGuard[JSONValue]:
110
+ if value is None or type(value) in (bool, int, str):
111
+ return True
112
+ if type(value) is float:
113
+ return isfinite(value)
114
+ if type(value) is list:
115
+ return all(is_strict_json_value(item) for item in value)
116
+ if type(value) is dict:
117
+ return all(type(key) is str and is_strict_json_value(item) for key, item in value.items())
118
+ return False
119
+
120
+
121
+ def strict_row_json(row: Row, key: str) -> JSONValue:
122
+ value = _required(row, key)
123
+ if not is_strict_json_value(value):
124
+ raise _type_error(key, "JSON value", value)
125
+ return value
126
+
127
+
128
+ def strict_row_json_object(row: Row, key: str) -> dict[str, JSONValue]:
129
+ value = strict_row_json(row, key)
130
+ if type(value) is not dict:
131
+ raise _type_error(key, "JSON object", value)
132
+ return value
133
+
134
+
135
+ def strict_row_optional[ValueT](
136
+ row: Row,
137
+ key: str,
138
+ decoder: Callable[[Row, str], ValueT],
139
+ ) -> ValueT | None:
140
+ if key not in row or row[key] is None:
141
+ return None
142
+ return decoder(row, key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlphilosophy
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Portable SQLAlchemy repository kit: sync and async CRUD, statement builders, sort/pagination, and SQL helpers.
5
5
  Author-email: Josh Martin <denverprogrammer@gmail.com>
6
6
  License-Expression: MIT
@@ -68,6 +68,8 @@ Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builde
68
68
  - **Repository pattern** for a single mapped model (`BaseRepository`, `AsyncBaseRepository`).
69
69
  - **Fluent query builders** with pagination/sort (`StatementQueryBuilder`, `ListQuery`, `SortConfig`).
70
70
  - **SQL helpers** for row mapping, partial updates, and developer-defined fragments via **`sqlphilosophy.trusted_sql`**.
71
+ - **Opt-in strict row decoders** via `sqlphilosophy.strict` for non-coercing
72
+ scalars, timestamps, enums, collections, UUIDs, and recursive JSON.
71
73
  - **Optional audit listeners** and timestamp mixins.
72
74
 
73
75
  ## What this package does not do
@@ -132,6 +134,7 @@ with SessionLocal() as session:
132
134
  | `sqlphilosophy` | `__version__` only |
133
135
  | `sqlphilosophy.types` | Portable typing aliases |
134
136
  | `sqlphilosophy.sql` | Row mapping, partial updates, Core helpers (re-exports `trusted_sql`) |
137
+ | `sqlphilosophy.strict` | Opt-in non-coercing row decoders and strict JSON validation |
135
138
  | `sqlphilosophy.trusted_sql` | Developer-trusted SQL fragments — see [SECURITY.md](./SECURITY.md) |
136
139
  | `sqlphilosophy.sorting` | `ListQuery`, `SortConfig`, `SortSpec` |
137
140
  | `sqlphilosophy.sync` / `sqlphilosophy.aio` | Repositories, query builders, factory protocols |
@@ -8,6 +8,7 @@ src/sqlphilosophy/_repository_shared.py
8
8
  src/sqlphilosophy/py.typed
9
9
  src/sqlphilosophy/sorting.py
10
10
  src/sqlphilosophy/sql.py
11
+ src/sqlphilosophy/strict.py
11
12
  src/sqlphilosophy/trusted_sql.py
12
13
  src/sqlphilosophy/types.py
13
14
  src/sqlphilosophy.egg-info/PKG-INFO
@@ -1 +0,0 @@
1
- 0.2.1
File without changes
File without changes
File without changes