ddsql 0.1.0__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.
- ddsql-0.1.0/README.md → ddsql-0.3.0/PKG-INFO +94 -9
- ddsql-0.1.0/PKG-INFO → ddsql-0.3.0/README.md +74 -23
- ddsql-0.3.0/ddsql/__init__.py +3 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/adapter.py +18 -2
- ddsql-0.3.0/ddsql/connections.py +70 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/sqlbase.py +1 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/pyproject.toml +9 -1
- {ddsql-0.1.0 → ddsql-0.3.0}/pyproject.toml.orig +10 -1
- ddsql-0.1.0/ddsql/__init__.py +0 -3
- {ddsql-0.1.0 → ddsql-0.3.0}/LICENSE +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/py.typed +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/query.py +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/serializers/__init__.py +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/serializers/base.py +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/serializers/clickhouse.py +0 -0
- {ddsql-0.1.0 → ddsql-0.3.0}/ddsql/serializers/postgres.py +0 -0
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddsql
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: SQL Query Library
|
|
5
|
+
Keywords: python,ddsql
|
|
6
|
+
Author: davyddd
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Dist: ddutils>=0.2.0,<0.3.0
|
|
16
|
+
Requires-Dist: jinja2>=3.0.0,<4.0.0
|
|
17
|
+
Requires-Python: >=3.11, <3.15
|
|
18
|
+
Project-URL: Repository, https://github.com/davyddd/ddsql
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
1
21
|
# DDSQL
|
|
2
22
|
|
|
3
23
|
[](https://pypi.python.org/pypi/ddsql)
|
|
@@ -113,14 +133,67 @@ class CustomSerializer(BaseSerializer):
|
|
|
113
133
|
}
|
|
114
134
|
```
|
|
115
135
|
|
|
136
|
+
## Connections
|
|
137
|
+
|
|
138
|
+
`ddsql.connections` sits between a `ScopedRegistry` (from [ddutils](https://github.com/davyddd/ddutils)) and
|
|
139
|
+
the code that runs queries. A registry holds one connection per scope: a pooled client per process, a session
|
|
140
|
+
per task. The connections module adds two things on top:
|
|
141
|
+
|
|
142
|
+
- `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
|
|
143
|
+
connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
|
|
144
|
+
chosen registry; `registry(alias)` returns the registry itself.
|
|
145
|
+
- `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
|
|
146
|
+
the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
|
|
147
|
+
pools themselves, such as ClickHouse.
|
|
148
|
+
|
|
149
|
+
Databases with transactions pass their own `connection_manager_class`: a unit of work that begins, commits
|
|
150
|
+
or rolls back and closes the session on exit. It depends on the driver, so it lives in the project.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import os
|
|
154
|
+
from enum import Enum
|
|
155
|
+
|
|
156
|
+
from ddutils.scoped_registry import ScopedRegistry
|
|
157
|
+
|
|
158
|
+
from ddsql.connections import ConnectionManagerFactory
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ClickhouseDB(str, Enum):
|
|
162
|
+
PRIMARY = 'primary'
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
client_registries = {
|
|
166
|
+
db: ScopedRegistry(create_func=make_client_for(db), scope_func=os.getpid, destructor_method_name='close')
|
|
167
|
+
for db in ClickhouseDB
|
|
168
|
+
}
|
|
169
|
+
clickhouse = ConnectionManagerFactory(client_registries, default=ClickhouseDB.PRIMARY)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async with clickhouse() as client:
|
|
173
|
+
result = await client.query(sql)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# a transactional database: `Atomic` is your unit of work over `registry.scope()`
|
|
177
|
+
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async with atomic(alias=PostgresDB.REPLICA) as session:
|
|
181
|
+
...
|
|
182
|
+
```
|
|
183
|
+
|
|
116
184
|
## Adapter
|
|
117
185
|
|
|
118
|
-
`Adapter` encapsulates database interactions. To create an adapter, inherit from the `Adapter` base class
|
|
186
|
+
`Adapter` encapsulates database interactions. To create an adapter, inherit from the `Adapter` base class
|
|
119
187
|
and define two required elements:
|
|
120
188
|
- **serializer** – an instance of a serializer for converting Python types to SQL representations;
|
|
121
189
|
- **_execute** method – the database-specific query execution logic.
|
|
122
190
|
|
|
123
191
|
```python
|
|
192
|
+
from collections.abc import Sequence
|
|
193
|
+
from typing import Any
|
|
194
|
+
|
|
195
|
+
from sqlalchemy import text
|
|
196
|
+
|
|
124
197
|
from ddsql.adapter import Adapter
|
|
125
198
|
from ddsql.serializers import PostgresSerializer
|
|
126
199
|
|
|
@@ -128,13 +201,26 @@ from ddsql.serializers import PostgresSerializer
|
|
|
128
201
|
class PostgresAdapter(Adapter):
|
|
129
202
|
serializer = PostgresSerializer()
|
|
130
203
|
|
|
131
|
-
async def _execute(self) -> Sequence[
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
result = await
|
|
204
|
+
async def _execute(self) -> Sequence[dict[str, Any]]:
|
|
205
|
+
async with atomic(alias=self.alias) as session:
|
|
206
|
+
query = await self.get_query() # the rendered SQL query
|
|
207
|
+
result = await session.execute(text(query))
|
|
135
208
|
return [dict(zip(result.keys(), row)) for row in result.fetchall()]
|
|
136
209
|
```
|
|
137
210
|
|
|
211
|
+
Intermediate adapter classes without `_execute` are abstract and need no `serializer`; only concrete
|
|
212
|
+
adapters are validated.
|
|
213
|
+
|
|
214
|
+
### Choosing the database
|
|
215
|
+
|
|
216
|
+
`Adapter.using(alias)` picks the connection alias right before executing, like Django's `using`. `_execute`
|
|
217
|
+
reads `self.alias` (`None` means the default) and passes it to whatever opens the connection, as in the example above:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
await SQL(query).postgres.execute() # default connection
|
|
221
|
+
await SQL(query).postgres.using(PostgresDB.REPLICA).execute()
|
|
222
|
+
```
|
|
223
|
+
|
|
138
224
|
## SQLBase
|
|
139
225
|
|
|
140
226
|
`SQLBase` is configured once per project and defines which adapters are available for query execution.
|
|
@@ -216,12 +302,11 @@ The result of query execution is a `Result` object that wraps the data into the
|
|
|
216
302
|
```python
|
|
217
303
|
from dataclasses import dataclass
|
|
218
304
|
from datetime import datetime
|
|
219
|
-
from typing import Optional
|
|
220
305
|
|
|
221
|
-
from ddsql.query import Query
|
|
222
|
-
from ddsql.sqlbase import SQLBase
|
|
223
306
|
from ddsql.adapter import Adapter, AdapterDescriptor
|
|
307
|
+
from ddsql.query import Query
|
|
224
308
|
from ddsql.serializers import PostgresSerializer
|
|
309
|
+
from ddsql.sqlbase import SQLBase
|
|
225
310
|
|
|
226
311
|
|
|
227
312
|
class PostgresAdapter(Adapter):
|
|
@@ -239,7 +324,7 @@ class SQL(SQLBase):
|
|
|
239
324
|
class User:
|
|
240
325
|
user_id: int
|
|
241
326
|
name: str
|
|
242
|
-
email:
|
|
327
|
+
email: str | None
|
|
243
328
|
created_at: datetime
|
|
244
329
|
is_deleted: bool
|
|
245
330
|
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ddsql
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: SQL Query Library
|
|
5
|
-
Keywords: python,ddsql
|
|
6
|
-
Author: davyddd
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: ddutils>=0.2.0,<0.3.0
|
|
10
|
-
Requires-Dist: jinja2>=3.0.0,<4.0.0
|
|
11
|
-
Requires-Python: >=3.11, <3.15
|
|
12
|
-
Project-URL: Repository, https://github.com/davyddd/ddsql
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
1
|
# DDSQL
|
|
16
2
|
|
|
17
3
|
[](https://pypi.python.org/pypi/ddsql)
|
|
@@ -127,14 +113,67 @@ class CustomSerializer(BaseSerializer):
|
|
|
127
113
|
}
|
|
128
114
|
```
|
|
129
115
|
|
|
116
|
+
## Connections
|
|
117
|
+
|
|
118
|
+
`ddsql.connections` sits between a `ScopedRegistry` (from [ddutils](https://github.com/davyddd/ddutils)) and
|
|
119
|
+
the code that runs queries. A registry holds one connection per scope: a pooled client per process, a session
|
|
120
|
+
per task. The connections module adds two things on top:
|
|
121
|
+
|
|
122
|
+
- `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
|
|
123
|
+
connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
|
|
124
|
+
chosen registry; `registry(alias)` returns the registry itself.
|
|
125
|
+
- `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
|
|
126
|
+
the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
|
|
127
|
+
pools themselves, such as ClickHouse.
|
|
128
|
+
|
|
129
|
+
Databases with transactions pass their own `connection_manager_class`: a unit of work that begins, commits
|
|
130
|
+
or rolls back and closes the session on exit. It depends on the driver, so it lives in the project.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import os
|
|
134
|
+
from enum import Enum
|
|
135
|
+
|
|
136
|
+
from ddutils.scoped_registry import ScopedRegistry
|
|
137
|
+
|
|
138
|
+
from ddsql.connections import ConnectionManagerFactory
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ClickhouseDB(str, Enum):
|
|
142
|
+
PRIMARY = 'primary'
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
client_registries = {
|
|
146
|
+
db: ScopedRegistry(create_func=make_client_for(db), scope_func=os.getpid, destructor_method_name='close')
|
|
147
|
+
for db in ClickhouseDB
|
|
148
|
+
}
|
|
149
|
+
clickhouse = ConnectionManagerFactory(client_registries, default=ClickhouseDB.PRIMARY)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async with clickhouse() as client:
|
|
153
|
+
result = await client.query(sql)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# a transactional database: `Atomic` is your unit of work over `registry.scope()`
|
|
157
|
+
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async with atomic(alias=PostgresDB.REPLICA) as session:
|
|
161
|
+
...
|
|
162
|
+
```
|
|
163
|
+
|
|
130
164
|
## Adapter
|
|
131
165
|
|
|
132
|
-
`Adapter` encapsulates database interactions. To create an adapter, inherit from the `Adapter` base class
|
|
166
|
+
`Adapter` encapsulates database interactions. To create an adapter, inherit from the `Adapter` base class
|
|
133
167
|
and define two required elements:
|
|
134
168
|
- **serializer** – an instance of a serializer for converting Python types to SQL representations;
|
|
135
169
|
- **_execute** method – the database-specific query execution logic.
|
|
136
170
|
|
|
137
171
|
```python
|
|
172
|
+
from collections.abc import Sequence
|
|
173
|
+
from typing import Any
|
|
174
|
+
|
|
175
|
+
from sqlalchemy import text
|
|
176
|
+
|
|
138
177
|
from ddsql.adapter import Adapter
|
|
139
178
|
from ddsql.serializers import PostgresSerializer
|
|
140
179
|
|
|
@@ -142,13 +181,26 @@ from ddsql.serializers import PostgresSerializer
|
|
|
142
181
|
class PostgresAdapter(Adapter):
|
|
143
182
|
serializer = PostgresSerializer()
|
|
144
183
|
|
|
145
|
-
async def _execute(self) -> Sequence[
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
result = await
|
|
184
|
+
async def _execute(self) -> Sequence[dict[str, Any]]:
|
|
185
|
+
async with atomic(alias=self.alias) as session:
|
|
186
|
+
query = await self.get_query() # the rendered SQL query
|
|
187
|
+
result = await session.execute(text(query))
|
|
149
188
|
return [dict(zip(result.keys(), row)) for row in result.fetchall()]
|
|
150
189
|
```
|
|
151
190
|
|
|
191
|
+
Intermediate adapter classes without `_execute` are abstract and need no `serializer`; only concrete
|
|
192
|
+
adapters are validated.
|
|
193
|
+
|
|
194
|
+
### Choosing the database
|
|
195
|
+
|
|
196
|
+
`Adapter.using(alias)` picks the connection alias right before executing, like Django's `using`. `_execute`
|
|
197
|
+
reads `self.alias` (`None` means the default) and passes it to whatever opens the connection, as in the example above:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
await SQL(query).postgres.execute() # default connection
|
|
201
|
+
await SQL(query).postgres.using(PostgresDB.REPLICA).execute()
|
|
202
|
+
```
|
|
203
|
+
|
|
152
204
|
## SQLBase
|
|
153
205
|
|
|
154
206
|
`SQLBase` is configured once per project and defines which adapters are available for query execution.
|
|
@@ -230,12 +282,11 @@ The result of query execution is a `Result` object that wraps the data into the
|
|
|
230
282
|
```python
|
|
231
283
|
from dataclasses import dataclass
|
|
232
284
|
from datetime import datetime
|
|
233
|
-
from typing import Optional
|
|
234
285
|
|
|
235
|
-
from ddsql.query import Query
|
|
236
|
-
from ddsql.sqlbase import SQLBase
|
|
237
286
|
from ddsql.adapter import Adapter, AdapterDescriptor
|
|
287
|
+
from ddsql.query import Query
|
|
238
288
|
from ddsql.serializers import PostgresSerializer
|
|
289
|
+
from ddsql.sqlbase import SQLBase
|
|
239
290
|
|
|
240
291
|
|
|
241
292
|
class PostgresAdapter(Adapter):
|
|
@@ -253,7 +304,7 @@ class SQL(SQLBase):
|
|
|
253
304
|
class User:
|
|
254
305
|
user_id: int
|
|
255
306
|
name: str
|
|
256
|
-
email:
|
|
307
|
+
email: str | None
|
|
257
308
|
created_at: datetime
|
|
258
309
|
is_deleted: bool
|
|
259
310
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, Self, TypeVar, overload
|
|
5
6
|
|
|
6
7
|
from ddutils.annotation_helpers import is_subclass
|
|
7
8
|
|
|
8
9
|
from ddsql.serializers import BaseSerializer
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
|
-
from collections.abc import Sequence
|
|
12
|
+
from collections.abc import Hashable, Sequence
|
|
12
13
|
|
|
13
14
|
from ddsql.query import Result
|
|
14
15
|
from ddsql.sqlbase import SQLBase
|
|
@@ -18,6 +19,9 @@ class Adapter(ABC):
|
|
|
18
19
|
serializer: BaseSerializer
|
|
19
20
|
|
|
20
21
|
def __init_subclass__(cls, **kwargs):
|
|
22
|
+
super().__init_subclass__(**kwargs) # keeps Generic / mixins in the MRO working
|
|
23
|
+
if inspect.isabstract(cls): # intermediate base classes declare no serializer
|
|
24
|
+
return
|
|
21
25
|
serializer = getattr(cls, 'serializer', None)
|
|
22
26
|
serializer_class = getattr(serializer, '__class__', None)
|
|
23
27
|
if not is_subclass(serializer_class, BaseSerializer):
|
|
@@ -27,6 +31,18 @@ class Adapter(ABC):
|
|
|
27
31
|
|
|
28
32
|
def __init__(self, sql: SQLBase) -> None:
|
|
29
33
|
self.sql = sql
|
|
34
|
+
self.alias: Hashable | None = None
|
|
35
|
+
|
|
36
|
+
def using(self, alias: Hashable) -> Self:
|
|
37
|
+
"""
|
|
38
|
+
Picks the connection alias for this execution, like Django's `using`; `_execute` reads `self.alias`
|
|
39
|
+
(`None` = default) and passes it to whatever opens the connection:
|
|
40
|
+
|
|
41
|
+
await SQL(query).postgres.execute() # default connection
|
|
42
|
+
await SQL(query).postgres.using(PostgresDB.REPLICA).execute()
|
|
43
|
+
"""
|
|
44
|
+
self.alias = alias
|
|
45
|
+
return self
|
|
30
46
|
|
|
31
47
|
async def get_query(self) -> str:
|
|
32
48
|
return await self.sql.query.render_template(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from collections.abc import Callable, Hashable, Mapping
|
|
2
|
+
from typing import Any, Generic, TypeVar
|
|
3
|
+
|
|
4
|
+
from ddutils.scoped_registry import ScopedRegistry
|
|
5
|
+
|
|
6
|
+
# The name a connection is registered under and selected by, like Django's `using` alias
|
|
7
|
+
ConnectionAliasT = TypeVar('ConnectionAliasT', bound=Hashable)
|
|
8
|
+
# The object a registry holds for a connection: a pooled client, a session
|
|
9
|
+
ConnectionT = TypeVar('ConnectionT')
|
|
10
|
+
# The context manager the factory opens on the chosen registry
|
|
11
|
+
ConnectionManagerT = TypeVar('ConnectionManagerT')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnectionManager(Generic[ConnectionT]):
|
|
15
|
+
"""
|
|
16
|
+
Default connection manager: hands out the connection of the registry's current scope and releases nothing on exit.
|
|
17
|
+
|
|
18
|
+
The connection is created on first use via `registry.set()` and kept for the life of its scope,
|
|
19
|
+
typically a process, since a client is itself a connection pool. Databases with transactions need their
|
|
20
|
+
own connection manager on top of the registry, e.g. a unit of work that begins, commits and closes a session on exit.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, registry: ScopedRegistry[ConnectionT]) -> None:
|
|
24
|
+
self._registry = registry
|
|
25
|
+
|
|
26
|
+
async def __aenter__(self) -> ConnectionT:
|
|
27
|
+
return await self._registry.set()
|
|
28
|
+
|
|
29
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: Any) -> None:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConnectionManagerFactory(Generic[ConnectionAliasT, ConnectionT, ConnectionManagerT]):
|
|
34
|
+
"""
|
|
35
|
+
Resolves a connection alias to its registry and opens a connection manager on it, like Django's `using`.
|
|
36
|
+
|
|
37
|
+
`connection_manager_class` is the context manager opened on the chosen registry: `ConnectionManager` by
|
|
38
|
+
default, a project class for anything that needs setup and teardown around the connection (transactions):
|
|
39
|
+
|
|
40
|
+
clickhouse = ConnectionManagerFactory(client_registries, default=ClickhouseDB.PRIMARY)
|
|
41
|
+
atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
|
|
42
|
+
|
|
43
|
+
async with clickhouse() as client: ...
|
|
44
|
+
async with atomic(alias=PostgresDB.REPLICA) as session: ...
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
registries: Mapping[ConnectionAliasT, ScopedRegistry[ConnectionT]],
|
|
50
|
+
default: ConnectionAliasT,
|
|
51
|
+
connection_manager_class: Callable[[ScopedRegistry[ConnectionT]], ConnectionManagerT] = ConnectionManager, # type: ignore[assignment]
|
|
52
|
+
) -> None:
|
|
53
|
+
if default not in registries:
|
|
54
|
+
raise ValueError(f'Default connection {default!r} is not among the registries')
|
|
55
|
+
self.registries = registries
|
|
56
|
+
self.default = default
|
|
57
|
+
self.connection_manager_class = connection_manager_class
|
|
58
|
+
|
|
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
|
+
if alias not in self.registries:
|
|
63
|
+
raise KeyError(f'Unknown connection {alias!r}; expected one of {list(self.registries)!r}')
|
|
64
|
+
return self.registries[alias] # type: ignore[index]
|
|
65
|
+
|
|
66
|
+
def __call__(self, alias: Hashable | None = None) -> ConnectionManagerT:
|
|
67
|
+
return self.connection_manager_class(self.registry(alias))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = ('ConnectionManager', 'ConnectionManagerFactory')
|
|
@@ -31,6 +31,7 @@ class SQLBase(ABC):
|
|
|
31
31
|
params: dict[str, Any]
|
|
32
32
|
|
|
33
33
|
def __init_subclass__(cls, **kwargs):
|
|
34
|
+
super().__init_subclass__(**kwargs) # keeps Generic / mixins in the MRO working
|
|
34
35
|
if not cls.has_adapters:
|
|
35
36
|
raise NotImplementedError('Subclasses must define at least one adapter')
|
|
36
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddsql"
|
|
3
3
|
description = "SQL Query Library"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.3.0"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
7
7
|
readme = "README.md"
|
|
@@ -10,6 +10,14 @@ keywords = [
|
|
|
10
10
|
"python",
|
|
11
11
|
"ddsql",
|
|
12
12
|
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
13
21
|
dependencies = [
|
|
14
22
|
"ddutils>=0.2.0,<0.3.0",
|
|
15
23
|
"jinja2>=3.0.0,<4.0.0",
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ddsql"
|
|
3
3
|
description = "SQL Query Library"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.3.0"
|
|
5
5
|
authors = [{ name = "davyddd" }]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.11,<3.15"
|
|
10
10
|
keywords = ["python", "ddsql"]
|
|
11
|
+
# uv_build does not derive these from requires-python (poetry did); PyPI and the pyversions badge read them
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Programming Language :: Python :: 3.14",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
11
20
|
dependencies = [
|
|
12
21
|
"ddutils>=0.2.0,<0.3.0",
|
|
13
22
|
"jinja2>=3.0.0,<4.0.0",
|
ddsql-0.1.0/ddsql/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|