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.
- {sqlphilosophy-0.2.1/src/sqlphilosophy.egg-info → sqlphilosophy-0.3.0}/PKG-INFO +4 -1
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/README.md +3 -0
- sqlphilosophy-0.3.0/src/sqlphilosophy/VERSION +1 -0
- sqlphilosophy-0.3.0/src/sqlphilosophy/strict.py +142 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0/src/sqlphilosophy.egg-info}/PKG-INFO +4 -1
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/SOURCES.txt +1 -0
- sqlphilosophy-0.2.1/src/sqlphilosophy/VERSION +0 -1
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/LICENSE +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/MANIFEST.in +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/pyproject.toml +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/setup.cfg +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/__init__.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/_repository_shared.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/__init__.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/protocols.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/query.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/aio/repository.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/__init__.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/context.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/fields.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/listener.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/audit/model.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/py.typed +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sorting.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sql.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/__init__.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/protocols.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/query.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/sync/repository.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/trusted_sql.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy/types.py +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/dependency_links.txt +0 -0
- {sqlphilosophy-0.2.1 → sqlphilosophy-0.3.0}/src/sqlphilosophy.egg-info/requires.txt +0 -0
- {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.
|
|
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.
|
|
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 |
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.2.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|