latch-postgres 0.2.0__py3-none-any.whl → 0.2.1__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.
- latch_postgres/postgres.py +65 -18
- {latch_postgres-0.2.0.dist-info → latch_postgres-0.2.1.dist-info}/METADATA +2 -2
- latch_postgres-0.2.1.dist-info/RECORD +8 -0
- latch_postgres-0.2.0.dist-info/RECORD +0 -8
- {latch_postgres-0.2.0.dist-info → latch_postgres-0.2.1.dist-info}/WHEEL +0 -0
- {latch_postgres-0.2.0.dist-info → latch_postgres-0.2.1.dist-info}/licenses/LICENSE +0 -0
latch_postgres/postgres.py
CHANGED
|
@@ -6,15 +6,15 @@ from contextlib import asynccontextmanager
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from datetime import timedelta
|
|
8
8
|
from textwrap import dedent
|
|
9
|
-
from typing import Any, Concatenate, ParamSpec, TypeVar, cast
|
|
9
|
+
from typing import Any, Concatenate, ParamSpec, Self, TypeVar, cast, overload, override
|
|
10
10
|
|
|
11
|
-
import psycopg.sql as sql
|
|
12
11
|
from latch_config.config import PostgresConnectionConfig
|
|
13
12
|
from latch_data_validation.data_validation import JsonObject, validate
|
|
14
13
|
from latch_o11y.o11y import dict_to_attrs, trace_function
|
|
15
14
|
from opentelemetry.sdk.resources import Attributes
|
|
16
15
|
from opentelemetry.trace import SpanKind, get_tracer
|
|
17
|
-
from psycopg import AsyncConnection, AsyncCursor, IsolationLevel
|
|
16
|
+
from psycopg import AsyncConnection, AsyncCursor, AsyncServerCursor, IsolationLevel, sql
|
|
17
|
+
from psycopg._connection_base import CursorRow
|
|
18
18
|
from psycopg.abc import AdaptContext, Params, Query
|
|
19
19
|
from psycopg.conninfo import conninfo_to_dict, make_conninfo
|
|
20
20
|
from psycopg.errors import (
|
|
@@ -44,11 +44,10 @@ from psycopg.errors import (
|
|
|
44
44
|
UndefinedFile,
|
|
45
45
|
)
|
|
46
46
|
from psycopg.errors import Error as PGError
|
|
47
|
-
from psycopg.rows import AsyncRowFactory, Row, dict_row, kwargs_row
|
|
47
|
+
from psycopg.rows import AsyncRowFactory, Row, TupleRow, dict_row, kwargs_row
|
|
48
48
|
from psycopg.types.composite import CompositeInfo, register_composite
|
|
49
49
|
from psycopg.types.enum import EnumInfo, register_enum
|
|
50
50
|
from psycopg_pool import AsyncConnectionPool
|
|
51
|
-
from typing_extensions import Self
|
|
52
51
|
|
|
53
52
|
from latch_postgres.retries import CABackoff
|
|
54
53
|
|
|
@@ -129,7 +128,10 @@ class TracedAsyncCursor(AsyncCursor[Row]):
|
|
|
129
128
|
finally:
|
|
130
129
|
span.set_attributes({"db.rowcount": self.rowcount})
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
@override
|
|
132
|
+
async def executemany(
|
|
133
|
+
self, query: Query, params_seq: Iterable[Params], *, returning: bool = False
|
|
134
|
+
) -> None:
|
|
133
135
|
with tracer.start_as_current_span(
|
|
134
136
|
"postgres.query.many",
|
|
135
137
|
kind=SpanKind.CLIENT,
|
|
@@ -140,7 +142,7 @@ class TracedAsyncCursor(AsyncCursor[Row]):
|
|
|
140
142
|
},
|
|
141
143
|
) as span:
|
|
142
144
|
try:
|
|
143
|
-
return await super().executemany(query, params_seq)
|
|
145
|
+
return await super().executemany(query, params_seq, returning=returning)
|
|
144
146
|
finally:
|
|
145
147
|
span.set_attributes({"db.rowcount": self.rowcount})
|
|
146
148
|
|
|
@@ -148,10 +150,52 @@ class TracedAsyncCursor(AsyncCursor[Row]):
|
|
|
148
150
|
class LatchAsyncConnection(AsyncConnection[Row]):
|
|
149
151
|
trace_attributes: Attributes
|
|
150
152
|
|
|
153
|
+
@overload
|
|
154
|
+
def cursor(self, *, binary: bool = False) -> AsyncCursor[Row]: ...
|
|
155
|
+
|
|
156
|
+
@overload
|
|
157
|
+
def cursor(
|
|
158
|
+
self, *, binary: bool = False, row_factory: AsyncRowFactory[CursorRow]
|
|
159
|
+
) -> AsyncCursor[CursorRow]: ...
|
|
160
|
+
|
|
161
|
+
@overload
|
|
162
|
+
def cursor(
|
|
163
|
+
self,
|
|
164
|
+
name: str,
|
|
165
|
+
*,
|
|
166
|
+
binary: bool = False,
|
|
167
|
+
scrollable: bool | None = None,
|
|
168
|
+
withhold: bool = False,
|
|
169
|
+
) -> AsyncServerCursor[Row]: ...
|
|
170
|
+
|
|
171
|
+
@overload
|
|
151
172
|
def cursor(
|
|
152
|
-
self,
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
self,
|
|
174
|
+
name: str,
|
|
175
|
+
*,
|
|
176
|
+
binary: bool = False,
|
|
177
|
+
row_factory: AsyncRowFactory[CursorRow],
|
|
178
|
+
scrollable: bool | None = None,
|
|
179
|
+
withhold: bool = False,
|
|
180
|
+
) -> AsyncServerCursor[CursorRow]: ...
|
|
181
|
+
|
|
182
|
+
@override
|
|
183
|
+
def cursor(
|
|
184
|
+
self,
|
|
185
|
+
name: str = "",
|
|
186
|
+
*,
|
|
187
|
+
binary: bool = False,
|
|
188
|
+
row_factory: AsyncRowFactory[Any] | None = None,
|
|
189
|
+
scrollable: bool | None = None,
|
|
190
|
+
withhold: bool = False,
|
|
191
|
+
) -> AsyncCursor[Any] | AsyncServerCursor[Any]:
|
|
192
|
+
res = super().cursor(
|
|
193
|
+
name=name,
|
|
194
|
+
binary=binary,
|
|
195
|
+
row_factory=row_factory,
|
|
196
|
+
scrollable=scrollable,
|
|
197
|
+
withhold=withhold,
|
|
198
|
+
)
|
|
155
199
|
assert isinstance(res, TracedAsyncCursor)
|
|
156
200
|
res.trace_attributes = self.trace_attributes
|
|
157
201
|
return res
|
|
@@ -163,9 +207,9 @@ class LatchAsyncConnection(AsyncConnection[Row]):
|
|
|
163
207
|
def model_(**kwargs: JsonObject) -> T:
|
|
164
208
|
return validate(kwargs, model)
|
|
165
209
|
|
|
166
|
-
async with self.cursor(kwargs_row(model_)) as curs:
|
|
210
|
+
async with self.cursor(row_factory=kwargs_row(model_)) as curs:
|
|
167
211
|
curs = cast(AsyncCursor[T], curs)
|
|
168
|
-
await curs.execute(query, params=kwargs)
|
|
212
|
+
_ = await curs.execute(query, params=kwargs)
|
|
169
213
|
|
|
170
214
|
yield curs
|
|
171
215
|
|
|
@@ -173,8 +217,8 @@ class LatchAsyncConnection(AsyncConnection[Row]):
|
|
|
173
217
|
async def _query_no_validate(
|
|
174
218
|
self, query: sql.SQL, **kwargs: Any
|
|
175
219
|
) -> AsyncGenerator[AsyncCursor[Any], None]:
|
|
176
|
-
async with self.cursor(dict_row) as curs:
|
|
177
|
-
await curs.execute(query, params=kwargs)
|
|
220
|
+
async with self.cursor(row_factory=dict_row) as curs:
|
|
221
|
+
_ = await curs.execute(query, params=kwargs)
|
|
178
222
|
|
|
179
223
|
yield curs
|
|
180
224
|
|
|
@@ -210,7 +254,7 @@ class LatchAsyncConnection(AsyncConnection[Row]):
|
|
|
210
254
|
return results[0]
|
|
211
255
|
|
|
212
256
|
async def query_void(self, query: sql.SQL, **kwargs: Any) -> None:
|
|
213
|
-
await self.queryn(type(None), query, **kwargs)
|
|
257
|
+
_ = await self.queryn(type(None), query, **kwargs)
|
|
214
258
|
|
|
215
259
|
async def query_unknown(self, query: sql.SQL, **kwargs: Any) -> Any:
|
|
216
260
|
async with self._query_no_validate(query, **kwargs) as curs:
|
|
@@ -379,19 +423,22 @@ class TracedAsyncConnectionPool(AsyncConnectionPool):
|
|
|
379
423
|
if self._real_configure is not None:
|
|
380
424
|
await self._real_configure(conn)
|
|
381
425
|
|
|
382
|
-
|
|
426
|
+
@override
|
|
427
|
+
async def getconn(self, timeout: float | None = None) -> AsyncConnection[TupleRow]:
|
|
383
428
|
with tracer.start_as_current_span(
|
|
384
429
|
"postgres.connect", kind=SpanKind.CLIENT, attributes=self._trace_attributes
|
|
385
430
|
):
|
|
386
431
|
return await super().getconn(timeout)
|
|
387
432
|
|
|
388
433
|
# todo(maximsmol): somehow track progress per-connection
|
|
434
|
+
@override
|
|
389
435
|
async def open(self, wait: bool = False, timeout: float = 30) -> None:
|
|
390
436
|
with tracer.start_as_current_span(
|
|
391
437
|
"open db pool", attributes=conninfo_attributes(self.conninfo)
|
|
392
438
|
):
|
|
393
439
|
return await super().open(wait, timeout)
|
|
394
440
|
|
|
441
|
+
@override
|
|
395
442
|
async def close(self, timeout: float = 5) -> None:
|
|
396
443
|
with tracer.start_as_current_span(
|
|
397
444
|
"close db pool", attributes=conninfo_attributes(self.conninfo)
|
|
@@ -593,7 +640,7 @@ def get_with_conn_retry(
|
|
|
593
640
|
return functools.partial(with_conn_retry, pool=pool, db_config=db_config)
|
|
594
641
|
|
|
595
642
|
|
|
596
|
-
def sqlq(x: str):
|
|
643
|
+
def sqlq(x: str) -> sql.SQL:
|
|
597
644
|
return sql.SQL(dedent(x))
|
|
598
645
|
|
|
599
646
|
|
|
@@ -603,7 +650,7 @@ async def reset_conn(
|
|
|
603
650
|
x: AsyncConnection[object],
|
|
604
651
|
read_only: bool = False,
|
|
605
652
|
isolation_level: IsolationLevel = IsolationLevel.SERIALIZABLE,
|
|
606
|
-
):
|
|
653
|
+
) -> None:
|
|
607
654
|
x.prepare_threshold = 0
|
|
608
655
|
|
|
609
656
|
if x.read_only != read_only:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: latch-postgres
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Postges wrapper for latch python backend services
|
|
5
5
|
Author-email: maximsmol <max@latch.bio>
|
|
6
6
|
License: CC0-1.0
|
|
@@ -11,7 +11,7 @@ Requires-Dist: latch-data-validation<1.0.0,>=0.1.10
|
|
|
11
11
|
Requires-Dist: latch-o11y<2.0.0,>=1.0.0
|
|
12
12
|
Requires-Dist: opentelemetry-api<2.0.0,>=1.15.0
|
|
13
13
|
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.15.0
|
|
14
|
-
Requires-Dist: psycopg[binary,pool]<4.0.0,>=3.
|
|
14
|
+
Requires-Dist: psycopg[binary,pool]<4.0.0,>=3.2.9
|
|
15
15
|
Requires-Dist: typing-extensions<5.0.0,>=4.4.0
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
latch_postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
latch_postgres/postgres.py,sha256=erhccX2Mg-KUUwwyQSg7Ao_ShGRpxB-0n7NNCRvc2NY,26159
|
|
3
|
+
latch_postgres/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
latch_postgres/retries.py,sha256=r5yH00fd_6EJNYFXAq3dAljJBphScpoeST-_oMvyj8Q,1308
|
|
5
|
+
latch_postgres-0.2.1.dist-info/METADATA,sha256=uDBJsNn2Mc_0OFPP3F7XIw3vYNtl6DEkIdBfN8OsRP0,606
|
|
6
|
+
latch_postgres-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
latch_postgres-0.2.1.dist-info/licenses/LICENSE,sha256=wh3JZu6ITG7ceN-1g404ekupj5JXAw4pnQ_Qr4sJfew,7052
|
|
8
|
+
latch_postgres-0.2.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
latch_postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
latch_postgres/postgres.py,sha256=FKYt0C66xDSOEc63x7z1HuOAylgPcZWGPDNlD2Htbz8,24922
|
|
3
|
-
latch_postgres/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
latch_postgres/retries.py,sha256=r5yH00fd_6EJNYFXAq3dAljJBphScpoeST-_oMvyj8Q,1308
|
|
5
|
-
latch_postgres-0.2.0.dist-info/METADATA,sha256=GFxp7RS1vAyE60rlXgxY9KV0ETD_titcMqK57a-jzWM,606
|
|
6
|
-
latch_postgres-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
latch_postgres-0.2.0.dist-info/licenses/LICENSE,sha256=wh3JZu6ITG7ceN-1g404ekupj5JXAw4pnQ_Qr4sJfew,7052
|
|
8
|
-
latch_postgres-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|