ddsql 0.2.0__tar.gz → 0.3.1__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.
- {ddsql-0.2.0 → ddsql-0.3.1}/PKG-INFO +12 -7
- {ddsql-0.2.0 → ddsql-0.3.1}/README.md +10 -5
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/adapter.py +4 -4
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/connections.py +6 -6
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/serializers/base.py +8 -1
- {ddsql-0.2.0 → ddsql-0.3.1}/pyproject.toml +2 -2
- {ddsql-0.2.0 → ddsql-0.3.1}/pyproject.toml.orig +2 -2
- {ddsql-0.2.0 → ddsql-0.3.1}/LICENSE +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/__init__.py +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/py.typed +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/query.py +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/serializers/__init__.py +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/serializers/clickhouse.py +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/serializers/postgres.py +0 -0
- {ddsql-0.2.0 → ddsql-0.3.1}/ddsql/sqlbase.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ddsql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: SQL Query Library
|
|
5
5
|
Keywords: python,ddsql
|
|
6
6
|
Author: davyddd
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
14
|
Classifier: Typing :: Typed
|
|
15
|
-
Requires-Dist: ddutils>=0.
|
|
15
|
+
Requires-Dist: ddutils>=0.3.0,<0.4.0
|
|
16
16
|
Requires-Dist: jinja2>=3.0.0,<4.0.0
|
|
17
17
|
Requires-Python: >=3.11, <3.15
|
|
18
18
|
Project-URL: Repository, https://github.com/davyddd/ddsql
|
|
@@ -71,6 +71,11 @@ or create your own by inheriting from `BaseSerializer`.
|
|
|
71
71
|
| `date` | `'2025-01-01'` | `'2025-01-01'::date` | `toDate('2025-01-01')` |
|
|
72
72
|
| `list`/`tuple`/`set` | `(item1, item2, ...)` | `(item1, item2, ...)` | `(item1, item2, ...)` |
|
|
73
73
|
|
|
74
|
+
An `Enum` member is serialized by its value: `Kind.ADMIN` of a `class Kind(str, Enum)` renders as `'admin'`,
|
|
75
|
+
a plain `Enum` holding a `datetime` as that `datetime`. A subclass is matched as its base type
|
|
76
|
+
(a `ProfileId(UUID)` as a `UUID`, a `pendulum.DateTime` as a `datetime`). `bytes` and `bytearray` are not
|
|
77
|
+
serialized: like any other type missing from the table they go to `serialize_other_object`.
|
|
78
|
+
|
|
74
79
|
String values are escaped according to the dialect rules, so quotes, backslashes
|
|
75
80
|
and control characters cannot break the query:
|
|
76
81
|
|
|
@@ -141,7 +146,7 @@ per task. The connections module adds two things on top:
|
|
|
141
146
|
|
|
142
147
|
- `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
|
|
143
148
|
connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
|
|
144
|
-
chosen registry; `registry(
|
|
149
|
+
chosen registry; `registry(alias)` returns the registry itself.
|
|
145
150
|
- `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
|
|
146
151
|
the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
|
|
147
152
|
pools themselves, such as ClickHouse.
|
|
@@ -177,7 +182,7 @@ async with clickhouse() as client:
|
|
|
177
182
|
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
178
183
|
|
|
179
184
|
|
|
180
|
-
async with atomic(
|
|
185
|
+
async with atomic(alias=PostgresDB.REPLICA) as session:
|
|
181
186
|
...
|
|
182
187
|
```
|
|
183
188
|
|
|
@@ -202,7 +207,7 @@ class PostgresAdapter(Adapter):
|
|
|
202
207
|
serializer = PostgresSerializer()
|
|
203
208
|
|
|
204
209
|
async def _execute(self) -> Sequence[dict[str, Any]]:
|
|
205
|
-
async with atomic(
|
|
210
|
+
async with atomic(alias=self.alias) as session:
|
|
206
211
|
query = await self.get_query() # the rendered SQL query
|
|
207
212
|
result = await session.execute(text(query))
|
|
208
213
|
return [dict(zip(result.keys(), row)) for row in result.fetchall()]
|
|
@@ -213,8 +218,8 @@ adapters are validated.
|
|
|
213
218
|
|
|
214
219
|
### Choosing the database
|
|
215
220
|
|
|
216
|
-
`Adapter.using(
|
|
217
|
-
`self.
|
|
221
|
+
`Adapter.using(alias)` picks the connection alias right before executing, like Django's `using`. `_execute`
|
|
222
|
+
reads `self.alias` (`None` means the default) and passes it to whatever opens the connection, as in the example above:
|
|
218
223
|
|
|
219
224
|
```python
|
|
220
225
|
await SQL(query).postgres.execute() # default connection
|
|
@@ -51,6 +51,11 @@ or create your own by inheriting from `BaseSerializer`.
|
|
|
51
51
|
| `date` | `'2025-01-01'` | `'2025-01-01'::date` | `toDate('2025-01-01')` |
|
|
52
52
|
| `list`/`tuple`/`set` | `(item1, item2, ...)` | `(item1, item2, ...)` | `(item1, item2, ...)` |
|
|
53
53
|
|
|
54
|
+
An `Enum` member is serialized by its value: `Kind.ADMIN` of a `class Kind(str, Enum)` renders as `'admin'`,
|
|
55
|
+
a plain `Enum` holding a `datetime` as that `datetime`. A subclass is matched as its base type
|
|
56
|
+
(a `ProfileId(UUID)` as a `UUID`, a `pendulum.DateTime` as a `datetime`). `bytes` and `bytearray` are not
|
|
57
|
+
serialized: like any other type missing from the table they go to `serialize_other_object`.
|
|
58
|
+
|
|
54
59
|
String values are escaped according to the dialect rules, so quotes, backslashes
|
|
55
60
|
and control characters cannot break the query:
|
|
56
61
|
|
|
@@ -121,7 +126,7 @@ per task. The connections module adds two things on top:
|
|
|
121
126
|
|
|
122
127
|
- `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
|
|
123
128
|
connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
|
|
124
|
-
chosen registry; `registry(
|
|
129
|
+
chosen registry; `registry(alias)` returns the registry itself.
|
|
125
130
|
- `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
|
|
126
131
|
the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
|
|
127
132
|
pools themselves, such as ClickHouse.
|
|
@@ -157,7 +162,7 @@ async with clickhouse() as client:
|
|
|
157
162
|
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
158
163
|
|
|
159
164
|
|
|
160
|
-
async with atomic(
|
|
165
|
+
async with atomic(alias=PostgresDB.REPLICA) as session:
|
|
161
166
|
...
|
|
162
167
|
```
|
|
163
168
|
|
|
@@ -182,7 +187,7 @@ class PostgresAdapter(Adapter):
|
|
|
182
187
|
serializer = PostgresSerializer()
|
|
183
188
|
|
|
184
189
|
async def _execute(self) -> Sequence[dict[str, Any]]:
|
|
185
|
-
async with atomic(
|
|
190
|
+
async with atomic(alias=self.alias) as session:
|
|
186
191
|
query = await self.get_query() # the rendered SQL query
|
|
187
192
|
result = await session.execute(text(query))
|
|
188
193
|
return [dict(zip(result.keys(), row)) for row in result.fetchall()]
|
|
@@ -193,8 +198,8 @@ adapters are validated.
|
|
|
193
198
|
|
|
194
199
|
### Choosing the database
|
|
195
200
|
|
|
196
|
-
`Adapter.using(
|
|
197
|
-
`self.
|
|
201
|
+
`Adapter.using(alias)` picks the connection alias right before executing, like Django's `using`. `_execute`
|
|
202
|
+
reads `self.alias` (`None` means the default) and passes it to whatever opens the connection, as in the example above:
|
|
198
203
|
|
|
199
204
|
```python
|
|
200
205
|
await SQL(query).postgres.execute() # default connection
|
|
@@ -31,17 +31,17 @@ class Adapter(ABC):
|
|
|
31
31
|
|
|
32
32
|
def __init__(self, sql: SQLBase) -> None:
|
|
33
33
|
self.sql = sql
|
|
34
|
-
self.
|
|
34
|
+
self.alias: Hashable | None = None
|
|
35
35
|
|
|
36
|
-
def using(self,
|
|
36
|
+
def using(self, alias: Hashable) -> Self:
|
|
37
37
|
"""
|
|
38
|
-
Picks the
|
|
38
|
+
Picks the connection alias for this execution, like Django's `using`; `_execute` reads `self.alias`
|
|
39
39
|
(`None` = default) and passes it to whatever opens the connection:
|
|
40
40
|
|
|
41
41
|
await SQL(query).postgres.execute() # default connection
|
|
42
42
|
await SQL(query).postgres.using(PostgresDB.REPLICA).execute()
|
|
43
43
|
"""
|
|
44
|
-
self.
|
|
44
|
+
self.alias = alias
|
|
45
45
|
return self
|
|
46
46
|
|
|
47
47
|
async def get_query(self) -> str:
|
|
@@ -41,7 +41,7 @@ class ConnectionManagerFactory(Generic[ConnectionAliasT, ConnectionT, Connection
|
|
|
41
41
|
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
42
42
|
|
|
43
43
|
async with clickhouse() as client: ...
|
|
44
|
-
async with atomic(
|
|
44
|
+
async with atomic(alias=PostgresDB.REPLICA) as session: ...
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
47
|
def __init__(
|
|
@@ -56,15 +56,15 @@ class ConnectionManagerFactory(Generic[ConnectionAliasT, ConnectionT, Connection
|
|
|
56
56
|
self.default = default
|
|
57
57
|
self.connection_manager_class = connection_manager_class
|
|
58
58
|
|
|
59
|
-
def registry(self,
|
|
60
|
-
"""The registry of `
|
|
61
|
-
alias = self.default if
|
|
59
|
+
def registry(self, alias: Hashable | None = None) -> ScopedRegistry[ConnectionT]:
|
|
60
|
+
"""The registry of `alias`, or of the default connection; `Hashable` because `Adapter.alias` is untyped."""
|
|
61
|
+
alias = self.default if alias is None else alias
|
|
62
62
|
if alias not in self.registries:
|
|
63
63
|
raise KeyError(f'Unknown connection {alias!r}; expected one of {list(self.registries)!r}')
|
|
64
64
|
return self.registries[alias] # type: ignore[index]
|
|
65
65
|
|
|
66
|
-
def __call__(self,
|
|
67
|
-
return self.connection_manager_class(self.registry(
|
|
66
|
+
def __call__(self, alias: Hashable | None = None) -> ConnectionManagerT:
|
|
67
|
+
return self.connection_manager_class(self.registry(alias))
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
__all__ = ('ConnectionManager', 'ConnectionManagerFactory')
|
|
@@ -4,6 +4,9 @@ from decimal import Decimal
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
+
from ddutils.annotation_helpers import NON_COMPLEX_SEQUENCE_TYPES
|
|
8
|
+
from ddutils.value_helpers import unwrap_enum
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
class BaseSerializer:
|
|
9
12
|
@property
|
|
@@ -11,6 +14,9 @@ class BaseSerializer:
|
|
|
11
14
|
return {'serialize_value': self.serialize_value}
|
|
12
15
|
|
|
13
16
|
def serialize_value(self, value: Any) -> str:
|
|
17
|
+
# an `Enum` member is serialized by its value: `f'{Kind.A}'` of a `class Kind(int, Enum)` is 'Kind.A', not '1'
|
|
18
|
+
value = unwrap_enum(value)
|
|
19
|
+
|
|
14
20
|
if value is None:
|
|
15
21
|
return self.serialize_none(value)
|
|
16
22
|
elif isinstance(value, bool):
|
|
@@ -27,7 +33,8 @@ class BaseSerializer:
|
|
|
27
33
|
# the check for date must come after datetime,
|
|
28
34
|
# because a datetime instance can also be identified as a date
|
|
29
35
|
return self.serialize_date(value)
|
|
30
|
-
elif isinstance(value, Collection) and not isinstance(value, Mapping):
|
|
36
|
+
elif isinstance(value, Collection) and not isinstance(value, (Mapping, *NON_COMPLEX_SEQUENCE_TYPES)):
|
|
37
|
+
# `bytes` is a collection of ints, but has no SQL representation here
|
|
31
38
|
return self.serialize_collection(value)
|
|
32
39
|
else:
|
|
33
40
|
return self.serialize_other_object(value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddsql"
|
|
3
3
|
description = "SQL Query Library"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.3.1"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
7
7
|
readme = "README.md"
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Typing :: Typed",
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"ddutils>=0.
|
|
22
|
+
"ddutils>=0.3.0,<0.4.0",
|
|
23
23
|
"jinja2>=3.0.0,<4.0.0",
|
|
24
24
|
]
|
|
25
25
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddsql"
|
|
3
3
|
description = "SQL Query Library"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.3.1"
|
|
5
5
|
authors = [{ name = "davyddd" }]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
"Typing :: Typed",
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"ddutils>=0.
|
|
21
|
+
"ddutils>=0.3.0,<0.4.0",
|
|
22
22
|
"jinja2>=3.0.0,<4.0.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|