ddsql 0.3.1__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddsql
3
- Version: 0.3.1
3
+ Version: 0.4.0
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.3.0,<0.4.0
15
+ Requires-Dist: ddutils>=0.3.1,<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
@@ -140,51 +140,10 @@ class CustomSerializer(BaseSerializer):
140
140
 
141
141
  ## Connections
142
142
 
143
- `ddsql.connections` sits between a `ScopedRegistry` (from [ddutils](https://github.com/davyddd/ddutils)) and
144
- the code that runs queries. A registry holds one connection per scope: a pooled client per process, a session
145
- per task. The connections module adds two things on top:
146
-
147
- - `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
148
- connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
149
- chosen registry; `registry(alias)` returns the registry itself.
150
- - `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
151
- the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
152
- pools themselves, such as ClickHouse.
153
-
154
- Databases with transactions pass their own `connection_manager_class`: a unit of work that begins, commits
155
- or rolls back and closes the session on exit. It depends on the driver, so it lives in the project.
156
-
157
- ```python
158
- import os
159
- from enum import Enum
160
-
161
- from ddutils.scoped_registry import ScopedRegistry
162
-
163
- from ddsql.connections import ConnectionManagerFactory
164
-
165
-
166
- class ClickhouseDB(str, Enum):
167
- PRIMARY = 'primary'
168
-
169
-
170
- client_registries = {
171
- db: ScopedRegistry(create_func=make_client_for(db), scope_func=os.getpid, destructor_method_name='close')
172
- for db in ClickhouseDB
173
- }
174
- clickhouse = ConnectionManagerFactory(client_registries, default=ClickhouseDB.PRIMARY)
175
-
176
-
177
- async with clickhouse() as client:
178
- result = await client.query(sql)
179
-
180
-
181
- # a transactional database: `Atomic` is your unit of work over `registry.scope()`
182
- atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
183
-
184
-
185
- async with atomic(alias=PostgresDB.REPLICA) as session:
186
- ...
187
- ```
143
+ Connection routing (`ConnectionManagerFactory`, `ConnectionManager`) lives in
144
+ [ddutils](https://github.com/davyddd/ddutils#connections) since it is not specific to SQL: a `ScopedRegistry` per
145
+ alias, a factory that opens a connection manager on the chosen registry, like Django's `using`. An adapter runs its
146
+ query inside `factory(alias=self.alias)`; see "Choosing the database" below.
188
147
 
189
148
  ## Adapter
190
149
 
@@ -120,51 +120,10 @@ class CustomSerializer(BaseSerializer):
120
120
 
121
121
  ## Connections
122
122
 
123
- `ddsql.connections` sits between a `ScopedRegistry` (from [ddutils](https://github.com/davyddd/ddutils)) and
124
- the code that runs queries. A registry holds one connection per scope: a pooled client per process, a session
125
- per task. The connections module adds two things on top:
126
-
127
- - `ConnectionManagerFactory(registries, default, connection_manager_class=ConnectionManager)` resolves a
128
- connection alias to its registry, like Django's `using`. Calling it opens a connection manager on the
129
- chosen registry; `registry(alias)` returns the registry itself.
130
- - `ConnectionManager(registry)` is the default connection manager: an async context manager that hands out
131
- the connection of the registry's current scope and releases nothing on exit. Enough for clients that are
132
- pools themselves, such as ClickHouse.
133
-
134
- Databases with transactions pass their own `connection_manager_class`: a unit of work that begins, commits
135
- or rolls back and closes the session on exit. It depends on the driver, so it lives in the project.
136
-
137
- ```python
138
- import os
139
- from enum import Enum
140
-
141
- from ddutils.scoped_registry import ScopedRegistry
142
-
143
- from ddsql.connections import ConnectionManagerFactory
144
-
145
-
146
- class ClickhouseDB(str, Enum):
147
- PRIMARY = 'primary'
148
-
149
-
150
- client_registries = {
151
- db: ScopedRegistry(create_func=make_client_for(db), scope_func=os.getpid, destructor_method_name='close')
152
- for db in ClickhouseDB
153
- }
154
- clickhouse = ConnectionManagerFactory(client_registries, default=ClickhouseDB.PRIMARY)
155
-
156
-
157
- async with clickhouse() as client:
158
- result = await client.query(sql)
159
-
160
-
161
- # a transactional database: `Atomic` is your unit of work over `registry.scope()`
162
- atomic = ConnectionManagerFactory(session_registries, default=PostgresDB.PRIMARY, connection_manager_class=Atomic)
163
-
164
-
165
- async with atomic(alias=PostgresDB.REPLICA) as session:
166
- ...
167
- ```
123
+ Connection routing (`ConnectionManagerFactory`, `ConnectionManager`) lives in
124
+ [ddutils](https://github.com/davyddd/ddutils#connections) since it is not specific to SQL: a `ScopedRegistry` per
125
+ alias, a factory that opens a connection manager on the chosen registry, like Django's `using`. An adapter runs its
126
+ query inside `factory(alias=self.alias)`; see "Choosing the database" below.
168
127
 
169
128
  ## Adapter
170
129
 
@@ -0,0 +1,3 @@
1
+ from . import adapter, query, serializers, sqlbase
2
+
3
+ __all__ = ('adapter', 'query', 'sqlbase', 'serializers')
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "ddsql"
3
3
  description = "SQL Query Library"
4
- version = "0.3.1"
4
+ version = "0.4.0"
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.3.0,<0.4.0",
22
+ "ddutils>=0.3.1,<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.3.1"
4
+ version = "0.4.0"
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.3.0,<0.4.0",
21
+ "ddutils>=0.3.1,<0.4.0",
22
22
  "jinja2>=3.0.0,<4.0.0",
23
23
  ]
24
24
 
@@ -1,3 +0,0 @@
1
- from . import adapter, connections, query, serializers, sqlbase
2
-
3
- __all__ = ('adapter', 'connections', 'query', 'sqlbase', 'serializers')
@@ -1,70 +0,0 @@
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')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes