latch-postgres 0.2.0__py3-none-any.whl → 0.2.2a0__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.
@@ -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
- async def executemany(self, query: Query, params_seq: Iterable[Params]) -> None:
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, row_factory: AsyncRowFactory[Any], *, binary: bool = True
153
- ) -> AsyncCursor[Any]:
154
- res = super().cursor(row_factory=row_factory, binary=binary)
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
- async def getconn(self, timeout: float | None = None) -> AsyncConnection[object]:
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,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: latch-postgres
3
- Version: 0.2.0
3
+ Version: 0.2.2a0
4
4
  Summary: Postges wrapper for latch python backend services
5
5
  Author-email: maximsmol <max@latch.bio>
6
6
  License: CC0-1.0
7
7
  License-File: LICENSE
8
- Requires-Python: >=3.11
8
+ Requires-Python: >=3.14
9
9
  Requires-Dist: latch-config<1.0.0,>=0.1.6
10
10
  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.1.8
14
+ Requires-Dist: psycopg[c,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.2a0.dist-info/METADATA,sha256=Vh4zVZLrDpMi9hCyXWM1qfywAuL1qccGb8Bnypjrr9Q,603
6
+ latch_postgres-0.2.2a0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
+ latch_postgres-0.2.2a0.dist-info/licenses/LICENSE,sha256=wh3JZu6ITG7ceN-1g404ekupj5JXAw4pnQ_Qr4sJfew,7052
8
+ latch_postgres-0.2.2a0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,