encore-dev 2.0.0a7__py3-none-any.whl
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.
- encore/__init__.py +43 -0
- encore/cron/__init__.py +50 -0
- encore/internal/__init__.py +1 -0
- encore/internal/_loader.py +79 -0
- encore/internal/_native.pyi +734 -0
- encore/internal/lazy.py +35 -0
- encore/internal/runtime.py +133 -0
- encore/internal/startup.py +24 -0
- encore/logs/__init__.py +19 -0
- encore/logs/_console.py +155 -0
- encore/logs/_runtime.py +56 -0
- encore/meta/__init__.py +228 -0
- encore/metrics/__init__.py +390 -0
- encore/pubsub/__init__.py +425 -0
- encore/py.typed +0 -0
- encore/secrets/__init__.py +139 -0
- encore/shutdown/__init__.py +164 -0
- encore/storage/__init__.py +5 -0
- encore/storage/objects/__init__.py +396 -0
- encore/storage/postgres/__init__.py +737 -0
- encore/storage/redis/__init__.py +200 -0
- encore/storage/redis/_base.py +42 -0
- encore/storage/redis/_responses.py +150 -0
- encore/storage/redis/_values.py +189 -0
- encore/storage/redis/bitmap.py +142 -0
- encore/storage/redis/generic.py +367 -0
- encore/storage/redis/geo.py +508 -0
- encore/storage/redis/hash.py +504 -0
- encore/storage/redis/json.py +614 -0
- encore/storage/redis/list.py +511 -0
- encore/storage/redis/pubsub.py +276 -0
- encore/storage/redis/set.py +570 -0
- encore/storage/redis/sorted_set.py +753 -0
- encore/storage/redis/stream.py +773 -0
- encore/storage/redis/string.py +654 -0
- encore/tracing/__init__.py +19 -0
- encore/tracing/_context.py +27 -0
- encore/tracing/_encode.py +351 -0
- encore/tracing/_provider.py +62 -0
- encore/tracing/_span.py +226 -0
- encore/tracing/_tracer.py +93 -0
- encore_dev-2.0.0a7.dist-info/METADATA +791 -0
- encore_dev-2.0.0a7.dist-info/RECORD +44 -0
- encore_dev-2.0.0a7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
"""Postgres declarations, explicit access handles, and streaming queries.
|
|
2
|
+
|
|
3
|
+
Declare databases at module scope and use their query methods to execute SQL.
|
|
4
|
+
Use begin() for transactions and acquire() for dedicated pooled connections.
|
|
5
|
+
|
|
6
|
+
Typical usage example:
|
|
7
|
+
|
|
8
|
+
from encore.storage import postgres
|
|
9
|
+
|
|
10
|
+
db = postgres.Database("orders", migrations="./migrations")
|
|
11
|
+
|
|
12
|
+
async def get_user(user_id: int):
|
|
13
|
+
return await db.query("SELECT * FROM users WHERE id = $1", user_id).one()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from collections.abc import AsyncGenerator, Callable, Generator
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from types import TracebackType
|
|
22
|
+
from typing import Any, Literal
|
|
23
|
+
|
|
24
|
+
from ...internal.lazy import Lazy
|
|
25
|
+
from ...internal.runtime import EncoreError, _native, runtime
|
|
26
|
+
from ...tracing._context import active_ctx_bytes
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Database",
|
|
30
|
+
"DatabaseRef",
|
|
31
|
+
"Queryable",
|
|
32
|
+
"Cursor",
|
|
33
|
+
"Connection",
|
|
34
|
+
"Transaction",
|
|
35
|
+
"ExecResult",
|
|
36
|
+
"Row",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
Row = dict[str, Any]
|
|
41
|
+
_Access = Literal["reader", "writer", "admin"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ExecResult:
|
|
46
|
+
"""An immutable result of a completed SQL statement.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
rows_affected: The number of rows affected, as reported by Postgres.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
rows_affected: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Cursor:
|
|
56
|
+
"""A SQL cursor consumed once by iteration, one(), or all().
|
|
57
|
+
|
|
58
|
+
Execution starts when iteration advances or a helper is awaited. Starting
|
|
59
|
+
one consumption mode prevents any other consumption of this query, including
|
|
60
|
+
after an error or partial iteration. Obtain cursors from query() on a
|
|
61
|
+
database, reference, connection, or transaction.
|
|
62
|
+
|
|
63
|
+
Exhaust a stream or call aclose() to release its connection. For early exits,
|
|
64
|
+
use the query as an async context manager. Entering the context does not
|
|
65
|
+
execute or consume the query. Leaving it closes the stream. The one() and
|
|
66
|
+
all() helpers close their cursors automatically.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, rows: AsyncGenerator[Row, None]) -> None:
|
|
70
|
+
"""Initialize a cursor with a deferred row stream.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
rows: The row stream that executes the query when advanced.
|
|
74
|
+
"""
|
|
75
|
+
self._rows = rows
|
|
76
|
+
self._consumed = False
|
|
77
|
+
|
|
78
|
+
def _consume(self) -> AsyncGenerator[Row, None]:
|
|
79
|
+
if self._consumed:
|
|
80
|
+
raise EncoreError("query has already been consumed")
|
|
81
|
+
self._consumed = True
|
|
82
|
+
return self._rows
|
|
83
|
+
|
|
84
|
+
def __aiter__(self) -> AsyncGenerator[Row, None]:
|
|
85
|
+
"""Return the row iterator and reserve this query for streaming.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
An async iterator yielding dictionaries of column names to values.
|
|
89
|
+
"""
|
|
90
|
+
return self._consume()
|
|
91
|
+
|
|
92
|
+
async def one(self) -> Row | None:
|
|
93
|
+
"""Return zero or one row and close the cursor.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A dictionary of column names to values, or None if no rows match.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
EncoreError: The query fails or returns more than one row.
|
|
100
|
+
"""
|
|
101
|
+
rows = self._consume()
|
|
102
|
+
try:
|
|
103
|
+
first = None
|
|
104
|
+
async for row in rows:
|
|
105
|
+
if first is not None:
|
|
106
|
+
raise EncoreError("query returned more than one row")
|
|
107
|
+
first = row
|
|
108
|
+
return first
|
|
109
|
+
finally:
|
|
110
|
+
await rows.aclose()
|
|
111
|
+
|
|
112
|
+
async def all(self) -> list[Row]:
|
|
113
|
+
"""Collect all rows in memory and close the cursor.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The result rows as dictionaries of column names to values.
|
|
117
|
+
An empty list if no rows match.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
EncoreError: The query fails.
|
|
121
|
+
"""
|
|
122
|
+
rows = self._consume()
|
|
123
|
+
try:
|
|
124
|
+
return [row async for row in rows]
|
|
125
|
+
finally:
|
|
126
|
+
await rows.aclose()
|
|
127
|
+
|
|
128
|
+
async def aclose(self) -> None:
|
|
129
|
+
"""Close the row stream and prevent further consumption of this query.
|
|
130
|
+
|
|
131
|
+
Closing an unstarted query does not execute SQL. Repeated closes have
|
|
132
|
+
no effect. Wait for any active read or helper to finish before closing.
|
|
133
|
+
"""
|
|
134
|
+
self._consumed = True
|
|
135
|
+
await self._rows.aclose()
|
|
136
|
+
|
|
137
|
+
async def __aenter__(self) -> Cursor:
|
|
138
|
+
"""Return this cursor without executing or consuming it."""
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
async def __aexit__(
|
|
142
|
+
self,
|
|
143
|
+
exc_type: type[BaseException] | None,
|
|
144
|
+
exc: BaseException | None,
|
|
145
|
+
tb: TracebackType | None,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Close the query without suppressing an exception from the block.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
exc_type: The exception type, or None on a clean exit.
|
|
151
|
+
exc: The exception instance, or None on a clean exit.
|
|
152
|
+
tb: The traceback, or None on a clean exit.
|
|
153
|
+
"""
|
|
154
|
+
await self.aclose()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _Queries:
|
|
158
|
+
"""A query executor shared by the public Postgres wrappers.
|
|
159
|
+
|
|
160
|
+
Attributes:
|
|
161
|
+
get_handle: A callable that resolves the native execution handle on
|
|
162
|
+
demand.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, get_handle: Callable[[], _native.PostgresHandle]) -> None:
|
|
166
|
+
"""Initialize the executor without resolving its native handle.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
get_handle: A callable that returns the native execution handle.
|
|
170
|
+
"""
|
|
171
|
+
self.get_handle = get_handle
|
|
172
|
+
|
|
173
|
+
async def query(self, sql: str, *params: Any) -> AsyncGenerator[Row, None]:
|
|
174
|
+
"""Yield rows and close the native cursor when iteration ends.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
178
|
+
*params: Values bound to the placeholders in order.
|
|
179
|
+
|
|
180
|
+
Yields:
|
|
181
|
+
Dictionaries mapping column names to decoded Python values.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
EncoreError: Postgres cannot execute the query or retrieve a row.
|
|
185
|
+
"""
|
|
186
|
+
cursor = await self.get_handle().query(
|
|
187
|
+
sql, list(params), source=active_ctx_bytes()
|
|
188
|
+
)
|
|
189
|
+
try:
|
|
190
|
+
while (row := await cursor.next()) is not None:
|
|
191
|
+
yield row
|
|
192
|
+
finally:
|
|
193
|
+
await cursor.close()
|
|
194
|
+
|
|
195
|
+
async def exec(self, sql: str, *params: Any) -> ExecResult:
|
|
196
|
+
"""Execute a statement and wait for its affected-row count.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
200
|
+
*params: Values bound to the placeholders in order.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
An ExecResult containing the affected-row count reported by
|
|
204
|
+
Postgres. Any rows returned by the statement are discarded.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
EncoreError: Postgres cannot complete the statement.
|
|
208
|
+
"""
|
|
209
|
+
count = await self.get_handle().exec(
|
|
210
|
+
sql, list(params), source=active_ctx_bytes()
|
|
211
|
+
)
|
|
212
|
+
return ExecResult(rows_affected=count)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class Queryable:
|
|
216
|
+
"""A query interface for database references, connections, and transactions.
|
|
217
|
+
|
|
218
|
+
SQL parameters use $1, $2, and subsequent positional placeholders.
|
|
219
|
+
Connections and transactions must be open and have no other active operation
|
|
220
|
+
or row stream when starting a query.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def __init__(self, queries: _Queries) -> None:
|
|
224
|
+
"""Initialize the interface with its query executor.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
queries: The executor used by this handle's query methods.
|
|
228
|
+
"""
|
|
229
|
+
self._queries = queries
|
|
230
|
+
|
|
231
|
+
def query(self, sql: str, *params: Any) -> Cursor:
|
|
232
|
+
"""Create a query for streaming or fetching rows.
|
|
233
|
+
|
|
234
|
+
Consume the query once with async iteration, one(), or all(). No SQL runs
|
|
235
|
+
until iteration advances or a helper is awaited. Exhaust the stream or
|
|
236
|
+
close it before reusing its connection or transaction. Use
|
|
237
|
+
the query as an async context manager when leaving iteration early.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
241
|
+
*params: Values bound to the placeholders in order.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
A Cursor whose rows map column names to decoded Python values.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
EncoreError: During iteration, acquiring a connection, executing the
|
|
248
|
+
query, or retrieving a row fails.
|
|
249
|
+
"""
|
|
250
|
+
return Cursor(self._queries.query(sql, *params))
|
|
251
|
+
|
|
252
|
+
async def exec(self, sql: str, *params: Any) -> ExecResult:
|
|
253
|
+
"""Execute a statement and discard any returned rows.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
257
|
+
*params: Values bound to the placeholders in order.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
An ExecResult containing the affected-row count reported by
|
|
261
|
+
Postgres.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
EncoreError: Acquiring a connection or executing the statement fails.
|
|
265
|
+
"""
|
|
266
|
+
return await self._queries.exec(sql, *params)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class DatabaseRef(Queryable):
|
|
270
|
+
"""A database handle that can be passed to other functions.
|
|
271
|
+
|
|
272
|
+
Obtain a reference from Database.read(), Database.write(), or
|
|
273
|
+
Database.admin() so Encore can infer the service's required permissions.
|
|
274
|
+
References share the database's pool and connection privileges.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
get_handle: Callable[[], _native.PostgresHandle],
|
|
280
|
+
*,
|
|
281
|
+
access: _Access,
|
|
282
|
+
primary: bool = True,
|
|
283
|
+
) -> None:
|
|
284
|
+
"""Initialize a reference without resolving its native handle.
|
|
285
|
+
|
|
286
|
+
Application code should use Database.read(), Database.write(), or
|
|
287
|
+
Database.admin() so Encore can discover the required permissions.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
get_handle: A callable that returns the database's native handle.
|
|
291
|
+
access: The selected access level: "reader", "writer", or "admin".
|
|
292
|
+
primary: The stored primary-server preference. Defaults to True.
|
|
293
|
+
Does not affect connection selection.
|
|
294
|
+
"""
|
|
295
|
+
super().__init__(_Queries(get_handle))
|
|
296
|
+
self._access = access
|
|
297
|
+
self._primary = primary
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def connection_string(self) -> str:
|
|
301
|
+
"""The proxy connection string for connecting with another client.
|
|
302
|
+
|
|
303
|
+
All references return the same URL, regardless of their
|
|
304
|
+
selected access level or primary-server preference.
|
|
305
|
+
"""
|
|
306
|
+
return self._queries.get_handle().conn_string()
|
|
307
|
+
|
|
308
|
+
def begin(self) -> _BeginTransaction:
|
|
309
|
+
"""Create an awaitable that begins a pooled transaction.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
An awaitable that produces a Transaction, also usable as an async
|
|
313
|
+
context manager. No transaction starts until the result is awaited
|
|
314
|
+
or entered. A clean context exit commits. An exceptional exit rolls
|
|
315
|
+
back. When awaiting directly, commit or roll back the transaction
|
|
316
|
+
explicitly.
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
EncoreError: When awaited or entered, the runtime cannot acquire a
|
|
320
|
+
connection or begin the transaction.
|
|
321
|
+
"""
|
|
322
|
+
return _BeginTransaction(self._queries.get_handle())
|
|
323
|
+
|
|
324
|
+
def acquire(self) -> _AcquireConnection:
|
|
325
|
+
"""Create an awaitable that acquires a pooled connection.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
An awaitable that produces a Connection, also usable as an async
|
|
329
|
+
context manager. No connection is acquired until the result is
|
|
330
|
+
awaited or entered. Leaving the context closes the connection. When
|
|
331
|
+
awaiting directly, close the connection explicitly after use.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
EncoreError: When awaited or entered, the runtime cannot acquire a
|
|
335
|
+
connection.
|
|
336
|
+
"""
|
|
337
|
+
return _AcquireConnection(self._queries.get_handle())
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class Database:
|
|
341
|
+
"""A module-level Postgres database declaration.
|
|
342
|
+
|
|
343
|
+
Encore infers read-write permissions from direct database operations.
|
|
344
|
+
Use read(), write(), or admin() to obtain a reference that can be passed to
|
|
345
|
+
other functions.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
def __init__(self, name: str, /, *, migrations: str | None = None) -> None:
|
|
349
|
+
"""Declare a new Postgres database.
|
|
350
|
+
|
|
351
|
+
Encore discovers this declaration at module scope and provisions the
|
|
352
|
+
database. Constructing it does not acquire a connection.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
name: The logical database name, supplied as a positional string
|
|
356
|
+
literal so Encore can resolve it statically.
|
|
357
|
+
migrations: A slash-separated path to a subdirectory relative to the
|
|
358
|
+
declaring file, such as "./migrations". Must be a string literal
|
|
359
|
+
or None. Defaults to None, which declares no Encore migration
|
|
360
|
+
directory.
|
|
361
|
+
"""
|
|
362
|
+
self._name = name
|
|
363
|
+
self._migrations = migrations
|
|
364
|
+
self._handle = Lazy(lambda: runtime().sql_database(name))
|
|
365
|
+
self._writer = DatabaseRef(self._handle.get, access="writer")
|
|
366
|
+
|
|
367
|
+
@classmethod
|
|
368
|
+
def named(cls, name: str, /) -> Database:
|
|
369
|
+
"""Reference an existing database without declaring it again.
|
|
370
|
+
|
|
371
|
+
Use this at module scope. The database is configured where it is
|
|
372
|
+
declared.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
name: The existing database's logical name, supplied as a positional
|
|
376
|
+
string literal so Encore can resolve it statically.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
A Database that refers to the existing resource.
|
|
380
|
+
"""
|
|
381
|
+
return cls(name)
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def name(self) -> str:
|
|
385
|
+
"""The logical name of the database."""
|
|
386
|
+
return self._name
|
|
387
|
+
|
|
388
|
+
def admin(self) -> DatabaseRef:
|
|
389
|
+
"""Create a handle that records admin access for this service.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
A reference sharing this database's pool and connection privileges.
|
|
393
|
+
"""
|
|
394
|
+
return DatabaseRef(self._handle.get, access="admin")
|
|
395
|
+
|
|
396
|
+
def write(self) -> DatabaseRef:
|
|
397
|
+
"""Return a handle that records read-write access for this service.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
A reference sharing this database's pool and connection privileges.
|
|
401
|
+
"""
|
|
402
|
+
return self._writer
|
|
403
|
+
|
|
404
|
+
def read(self, primary: bool = False) -> DatabaseRef:
|
|
405
|
+
"""Create a handle that records reader access for this service.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
primary: The stored primary-server preference. Defaults to False.
|
|
409
|
+
Does not affect connection selection.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
A reference sharing this database's pool and connection privileges.
|
|
413
|
+
"""
|
|
414
|
+
return DatabaseRef(self._handle.get, access="reader", primary=primary)
|
|
415
|
+
|
|
416
|
+
def query(self, sql: str, *params: Any) -> Cursor:
|
|
417
|
+
"""Create a query for streaming or fetching rows.
|
|
418
|
+
|
|
419
|
+
Consume the query once with async iteration, one(), or all(). No SQL runs
|
|
420
|
+
until iteration advances or a helper is awaited. See Queryable.query
|
|
421
|
+
for stream cleanup requirements.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
425
|
+
*params: Values bound to the placeholders in order.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
A Cursor whose rows map column names to decoded Python values.
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
EncoreError: During iteration, the runtime cannot acquire a
|
|
432
|
+
connection or Postgres cannot execute the query or retrieve a
|
|
433
|
+
row.
|
|
434
|
+
"""
|
|
435
|
+
return self._writer.query(sql, *params)
|
|
436
|
+
|
|
437
|
+
async def exec(self, sql: str, *params: Any) -> ExecResult:
|
|
438
|
+
"""Execute a statement and discard any returned rows.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
sql: A SQL statement with positional placeholders such as $1 and $2.
|
|
442
|
+
*params: Values bound to the placeholders in order.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
An ExecResult containing the affected-row count reported by
|
|
446
|
+
Postgres. Any rows returned by the statement are discarded.
|
|
447
|
+
|
|
448
|
+
Raises:
|
|
449
|
+
EncoreError: The runtime cannot acquire a connection or the
|
|
450
|
+
statement fails.
|
|
451
|
+
"""
|
|
452
|
+
return await self._writer.exec(sql, *params)
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def connection_string(self) -> str:
|
|
456
|
+
"""The proxy connection string for connecting with another client."""
|
|
457
|
+
return self._writer.connection_string
|
|
458
|
+
|
|
459
|
+
def begin(self) -> _BeginTransaction:
|
|
460
|
+
"""Create an awaitable that begins a pooled transaction.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
An awaitable that produces a Transaction, also usable as an async
|
|
464
|
+
context manager. No transaction starts until the result is awaited
|
|
465
|
+
or entered. A clean context exit commits. An exceptional exit rolls
|
|
466
|
+
back. When awaiting directly, commit or roll back the transaction
|
|
467
|
+
explicitly.
|
|
468
|
+
|
|
469
|
+
Raises:
|
|
470
|
+
EncoreError: When awaited or entered, the runtime cannot acquire a
|
|
471
|
+
connection or begin the transaction.
|
|
472
|
+
"""
|
|
473
|
+
return self._writer.begin()
|
|
474
|
+
|
|
475
|
+
def acquire(self) -> _AcquireConnection:
|
|
476
|
+
"""Create an awaitable that acquires a pooled connection.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
An awaitable that produces a Connection, also usable as an async
|
|
480
|
+
context manager. No connection is acquired until the result is
|
|
481
|
+
awaited or entered. Leaving the context closes the connection. When
|
|
482
|
+
awaiting directly, close the connection explicitly after use.
|
|
483
|
+
|
|
484
|
+
Raises:
|
|
485
|
+
EncoreError: When awaited or entered, the runtime cannot acquire a
|
|
486
|
+
connection.
|
|
487
|
+
"""
|
|
488
|
+
return self._writer.acquire()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class Connection(Queryable):
|
|
492
|
+
"""An acquired connection returned to its pool when closed or destroyed.
|
|
493
|
+
|
|
494
|
+
Obtain a connection with Database.acquire() or DatabaseRef.acquire(). Close
|
|
495
|
+
it explicitly or use it as an async context manager. Operations require an
|
|
496
|
+
open connection with no other active operation, transaction, or row stream.
|
|
497
|
+
Finish the transaction or close the stream before reusing the connection.
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
def __init__(self, impl: _native.PostgresHandle) -> None:
|
|
501
|
+
"""Wrap an acquired native connection.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
impl: An acquired native connection handle. Application code should
|
|
505
|
+
obtain a Connection by awaiting or entering acquire().
|
|
506
|
+
"""
|
|
507
|
+
self._impl = impl
|
|
508
|
+
super().__init__(_Queries(lambda: self._impl))
|
|
509
|
+
|
|
510
|
+
def begin(self) -> _BeginTransaction:
|
|
511
|
+
"""Create an awaitable that begins a transaction on this connection.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
An awaitable that produces a Transaction, also usable as an async
|
|
515
|
+
context manager. The transaction exclusively borrows this connection
|
|
516
|
+
until it finishes. A clean context exit commits. An exceptional exit
|
|
517
|
+
rolls back. The connection stays acquired afterwards.
|
|
518
|
+
|
|
519
|
+
Raises:
|
|
520
|
+
EncoreError: When awaited or entered, Postgres cannot begin the
|
|
521
|
+
transaction.
|
|
522
|
+
"""
|
|
523
|
+
return _BeginTransaction(self._impl)
|
|
524
|
+
|
|
525
|
+
async def close(self) -> None:
|
|
526
|
+
"""Return this connection to its pool.
|
|
527
|
+
|
|
528
|
+
Repeated calls have no effect after the connection is closed. Close any
|
|
529
|
+
active cursor and finish any transaction before closing the connection.
|
|
530
|
+
|
|
531
|
+
"""
|
|
532
|
+
await self._impl.close()
|
|
533
|
+
|
|
534
|
+
async def __aenter__(self) -> Connection:
|
|
535
|
+
"""Return this open connection for use in an async context."""
|
|
536
|
+
if self._impl.is_closed():
|
|
537
|
+
raise EncoreError("database handle is closed")
|
|
538
|
+
return self
|
|
539
|
+
|
|
540
|
+
async def __aexit__(
|
|
541
|
+
self,
|
|
542
|
+
exc_type: type[BaseException] | None,
|
|
543
|
+
exc: BaseException | None,
|
|
544
|
+
tb: TracebackType | None,
|
|
545
|
+
) -> None:
|
|
546
|
+
"""Close the connection without suppressing an exception from the block.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
exc_type: The exception type, or None on a clean exit.
|
|
550
|
+
exc: The exception instance, or None on a clean exit.
|
|
551
|
+
tb: The traceback, or None on a clean exit.
|
|
552
|
+
|
|
553
|
+
"""
|
|
554
|
+
await self.close()
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class Transaction(Queryable):
|
|
558
|
+
"""An open transaction that commits or rolls back on context exit.
|
|
559
|
+
|
|
560
|
+
Obtain a transaction by awaiting or entering begin(). A clean context exit
|
|
561
|
+
commits. An exceptional exit rolls back. Without a context manager, commit
|
|
562
|
+
or roll back explicitly. Abandoned transactions are rolled back when their
|
|
563
|
+
native handles are destroyed.
|
|
564
|
+
|
|
565
|
+
Once commit or rollback starts, it consumes the handle even if Postgres
|
|
566
|
+
reports an error. Operations require an open transaction with no other
|
|
567
|
+
active operation or row stream. Close row streams before committing or
|
|
568
|
+
rolling back.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
def __init__(self, impl: _native.PostgresHandle) -> None:
|
|
572
|
+
"""Wrap an open native transaction.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
impl: An open native transaction handle. Application code should
|
|
576
|
+
obtain a Transaction by awaiting or entering begin().
|
|
577
|
+
"""
|
|
578
|
+
self._impl = impl
|
|
579
|
+
super().__init__(_Queries(lambda: self._impl))
|
|
580
|
+
|
|
581
|
+
async def commit(self) -> None:
|
|
582
|
+
"""Commit the transaction and close its handle.
|
|
583
|
+
|
|
584
|
+
Close active row streams before committing. Once completion starts, it
|
|
585
|
+
continues if the awaiting task is cancelled, and a borrowed connection
|
|
586
|
+
remains unavailable until completion finishes. A Postgres error still
|
|
587
|
+
consumes the transaction handle.
|
|
588
|
+
|
|
589
|
+
Raises:
|
|
590
|
+
EncoreError: Postgres cannot commit the transaction.
|
|
591
|
+
"""
|
|
592
|
+
await self._impl.commit()
|
|
593
|
+
|
|
594
|
+
async def rollback(self) -> None:
|
|
595
|
+
"""Roll back the transaction and close its handle.
|
|
596
|
+
|
|
597
|
+
Close active row streams before rolling back. Once completion starts, it
|
|
598
|
+
continues if the awaiting task is cancelled, and a borrowed connection
|
|
599
|
+
remains unavailable until completion finishes. A Postgres error still
|
|
600
|
+
consumes the transaction handle.
|
|
601
|
+
|
|
602
|
+
Raises:
|
|
603
|
+
EncoreError: Postgres cannot roll back the transaction.
|
|
604
|
+
"""
|
|
605
|
+
await self._impl.rollback()
|
|
606
|
+
|
|
607
|
+
async def __aenter__(self) -> Transaction:
|
|
608
|
+
"""Return this open transaction for use in an async context."""
|
|
609
|
+
if self._impl.is_closed():
|
|
610
|
+
raise EncoreError("database handle is closed")
|
|
611
|
+
return self
|
|
612
|
+
|
|
613
|
+
async def __aexit__(
|
|
614
|
+
self,
|
|
615
|
+
exc_type: type[BaseException] | None,
|
|
616
|
+
exc: BaseException | None,
|
|
617
|
+
tb: TracebackType | None,
|
|
618
|
+
) -> None:
|
|
619
|
+
"""Commit on a clean exit or roll back while an exception unwinds.
|
|
620
|
+
|
|
621
|
+
An already completed transaction needs no further action. If rollback
|
|
622
|
+
fails while handling an exception, the failure is logged and the
|
|
623
|
+
original exception propagates.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
exc_type: The exception type, or None on a clean exit.
|
|
627
|
+
exc: The exception instance, or None on a clean exit.
|
|
628
|
+
tb: The traceback, or None on a clean exit.
|
|
629
|
+
|
|
630
|
+
Raises:
|
|
631
|
+
EncoreError: Committing on a clean exit fails.
|
|
632
|
+
"""
|
|
633
|
+
if self._impl.is_closed():
|
|
634
|
+
return
|
|
635
|
+
if exc_type is None:
|
|
636
|
+
await self.commit()
|
|
637
|
+
return
|
|
638
|
+
try:
|
|
639
|
+
await self.rollback()
|
|
640
|
+
except Exception:
|
|
641
|
+
logger.warning(
|
|
642
|
+
"rolling back the transaction failed while unwinding an error",
|
|
643
|
+
exc_info=True,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class _BeginTransaction:
|
|
648
|
+
"""An awaitable and async context manager that starts a transaction.
|
|
649
|
+
|
|
650
|
+
No transaction starts until the result is awaited or entered.
|
|
651
|
+
"""
|
|
652
|
+
|
|
653
|
+
def __init__(self, impl: _native.PostgresHandle) -> None:
|
|
654
|
+
"""Initialize the deferred transaction operation.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
impl: A native database or acquired connection handle.
|
|
658
|
+
"""
|
|
659
|
+
self._impl = impl
|
|
660
|
+
self._tx: Transaction | None = None
|
|
661
|
+
|
|
662
|
+
async def _begin(self) -> Transaction:
|
|
663
|
+
"""Return a transaction after beginning it on the native handle."""
|
|
664
|
+
return Transaction(await self._impl.begin(source=active_ctx_bytes()))
|
|
665
|
+
|
|
666
|
+
def __await__(self) -> Generator[Any, Any, Transaction]:
|
|
667
|
+
"""Return the iterator that drives transaction creation when awaited."""
|
|
668
|
+
return self._begin().__await__()
|
|
669
|
+
|
|
670
|
+
async def __aenter__(self) -> Transaction:
|
|
671
|
+
"""Return a new transaction for the async context."""
|
|
672
|
+
self._tx = await self._begin()
|
|
673
|
+
return self._tx
|
|
674
|
+
|
|
675
|
+
async def __aexit__(
|
|
676
|
+
self,
|
|
677
|
+
exc_type: type[BaseException] | None,
|
|
678
|
+
exc: BaseException | None,
|
|
679
|
+
tb: TracebackType | None,
|
|
680
|
+
) -> None:
|
|
681
|
+
"""Commit on a clean exit or roll back on an exceptional exit.
|
|
682
|
+
|
|
683
|
+
Do nothing if the transaction has already finished. A rollback failure
|
|
684
|
+
is logged without suppressing the exception from the block.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
exc_type: The exception type, or None on a clean exit.
|
|
688
|
+
exc: The exception instance, or None on a clean exit.
|
|
689
|
+
tb: The traceback, or None on a clean exit.
|
|
690
|
+
"""
|
|
691
|
+
if self._tx is not None:
|
|
692
|
+
await self._tx.__aexit__(exc_type, exc, tb)
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class _AcquireConnection:
|
|
696
|
+
"""An awaitable and async context manager that acquires a connection.
|
|
697
|
+
|
|
698
|
+
No connection is acquired until the result is awaited or entered.
|
|
699
|
+
"""
|
|
700
|
+
|
|
701
|
+
def __init__(self, impl: _native.PostgresHandle) -> None:
|
|
702
|
+
"""Initialize the deferred connection acquisition.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
impl: A native database handle whose pool supplies the connection.
|
|
706
|
+
"""
|
|
707
|
+
self._impl = impl
|
|
708
|
+
self._conn: Connection | None = None
|
|
709
|
+
|
|
710
|
+
async def _acquire(self) -> Connection:
|
|
711
|
+
"""Return a connection after acquiring it from the native pool."""
|
|
712
|
+
return Connection(await self._impl.acquire(source=active_ctx_bytes()))
|
|
713
|
+
|
|
714
|
+
def __await__(self) -> Generator[Any, Any, Connection]:
|
|
715
|
+
"""Return the iterator that acquires the connection when awaited."""
|
|
716
|
+
return self._acquire().__await__()
|
|
717
|
+
|
|
718
|
+
async def __aenter__(self) -> Connection:
|
|
719
|
+
"""Return a newly acquired connection for the async context."""
|
|
720
|
+
self._conn = await self._acquire()
|
|
721
|
+
return self._conn
|
|
722
|
+
|
|
723
|
+
async def __aexit__(
|
|
724
|
+
self,
|
|
725
|
+
exc_type: type[BaseException] | None,
|
|
726
|
+
exc: BaseException | None,
|
|
727
|
+
tb: TracebackType | None,
|
|
728
|
+
) -> None:
|
|
729
|
+
"""Close the connection when the context exits.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
exc_type: The exception type, or None on a clean exit.
|
|
733
|
+
exc: The exception instance, or None on a clean exit.
|
|
734
|
+
tb: The traceback, or None on a clean exit.
|
|
735
|
+
"""
|
|
736
|
+
if self._conn is not None:
|
|
737
|
+
await self._conn.__aexit__(exc_type, exc, tb)
|