psycopg 3.2.2__tar.gz → 3.2.4__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.
- {psycopg-3.2.2 → psycopg-3.2.4}/PKG-INFO +7 -6
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_cmodule.py +16 -5
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_connection_base.py +12 -4
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy_base.py +9 -6
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_cursor_base.py +1 -1
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_oids.py +1 -1
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/connection.py +38 -23
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/connection_async.py +41 -23
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/errors.py +4 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/generators.py +16 -1
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/postgres.py +1 -1
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_debug.py +12 -7
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_enums.py +4 -1
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_pq_ctypes.py +4 -4
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/misc.py +2 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/pq_ctypes.py +2 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/waiting.py +7 -7
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/PKG-INFO +7 -6
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/requires.txt +5 -4
- {psycopg-3.2.2 → psycopg-3.2.4}/setup.cfg +6 -5
- {psycopg-3.2.2 → psycopg-3.2.4}/LICENSE.txt +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/README.rst +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/__init__.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_acompat.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_adapters_map.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_capabilities.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_column.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_compat.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_connection_info.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_attempts.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_attempts_async.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_utils.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy_async.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_dns.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_encodings.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_enums.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_pipeline.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_preparing.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_py_transformer.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_queries.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_struct.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_tpc.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_transformer.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_typeinfo.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_typemod.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_tz.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_wrappers.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/abc.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/adapt.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/client_cursor.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/conninfo.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/copy.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/__init__.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/_types.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/connection.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/cursor.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/cursor_async.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/dbapi20.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/__init__.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_pq_ctypes.pyi +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/abc.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/py.typed +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/raw_cursor.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/rows.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/server_cursor.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/sql.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/transaction.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/__init__.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/array.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/bool.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/composite.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/datetime.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/enum.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/hstore.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/json.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/multirange.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/net.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/none.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/numeric.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/numpy.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/range.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/shapely.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/string.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/uuid.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/version.py +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/SOURCES.txt +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/dependency_links.txt +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/not-zip-safe +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/top_level.txt +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/pyproject.toml +0 -0
- {psycopg-3.2.2 → psycopg-3.2.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: psycopg
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Summary: PostgreSQL database adapter for Python
|
|
5
5
|
Home-page: https://psycopg.org/psycopg3/
|
|
6
6
|
Author: Daniele Varrazzo
|
|
@@ -38,14 +38,14 @@ Requires-Dist: backports.zoneinfo>=0.2.0; python_version < "3.9"
|
|
|
38
38
|
Requires-Dist: typing-extensions>=4.6; python_version < "3.13"
|
|
39
39
|
Requires-Dist: tzdata; sys_platform == "win32"
|
|
40
40
|
Provides-Extra: c
|
|
41
|
-
Requires-Dist: psycopg-c==3.2.
|
|
41
|
+
Requires-Dist: psycopg-c==3.2.4; implementation_name != "pypy" and extra == "c"
|
|
42
42
|
Provides-Extra: binary
|
|
43
|
-
Requires-Dist: psycopg-binary==3.2.
|
|
43
|
+
Requires-Dist: psycopg-binary==3.2.4; implementation_name != "pypy" and extra == "binary"
|
|
44
44
|
Provides-Extra: pool
|
|
45
45
|
Requires-Dist: psycopg-pool; extra == "pool"
|
|
46
46
|
Provides-Extra: test
|
|
47
47
|
Requires-Dist: anyio>=4.0; extra == "test"
|
|
48
|
-
Requires-Dist: mypy>=1.
|
|
48
|
+
Requires-Dist: mypy>=1.14; extra == "test"
|
|
49
49
|
Requires-Dist: pproxy>=2.7; extra == "test"
|
|
50
50
|
Requires-Dist: pytest>=6.2.5; extra == "test"
|
|
51
51
|
Requires-Dist: pytest-cov>=3.0; extra == "test"
|
|
@@ -56,7 +56,8 @@ Requires-Dist: black>=24.1.0; extra == "dev"
|
|
|
56
56
|
Requires-Dist: codespell>=2.2; extra == "dev"
|
|
57
57
|
Requires-Dist: dnspython>=2.1; extra == "dev"
|
|
58
58
|
Requires-Dist: flake8>=4.0; extra == "dev"
|
|
59
|
-
Requires-Dist: mypy>=1.
|
|
59
|
+
Requires-Dist: mypy>=1.14; extra == "dev"
|
|
60
|
+
Requires-Dist: pre-commit>=4.0.1; extra == "dev"
|
|
60
61
|
Requires-Dist: types-setuptools>=57.4; extra == "dev"
|
|
61
62
|
Requires-Dist: wheel>=0.37; extra == "dev"
|
|
62
63
|
Provides-Extra: docs
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# mypy: disable-error-code="import-not-found, attr-defined"
|
|
1
2
|
"""
|
|
2
3
|
Simplify access to the _psycopg module
|
|
3
4
|
"""
|
|
@@ -6,19 +7,29 @@ Simplify access to the _psycopg module
|
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
10
|
+
from types import ModuleType
|
|
9
11
|
from . import pq
|
|
10
12
|
|
|
11
13
|
__version__: str | None = None
|
|
14
|
+
_psycopg: ModuleType
|
|
12
15
|
|
|
13
16
|
# Note: "c" must the first attempt so that mypy associates the variable the
|
|
14
17
|
# right module interface. It will not result Optional, but hey.
|
|
15
18
|
if pq.__impl__ == "c":
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
import psycopg_c._psycopg
|
|
20
|
+
|
|
21
|
+
_psycopg = psycopg_c._psycopg
|
|
22
|
+
__version__ = psycopg_c.__version__
|
|
23
|
+
|
|
18
24
|
elif pq.__impl__ == "binary":
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
import psycopg_binary._psycopg
|
|
26
|
+
|
|
27
|
+
_psycopg = psycopg_binary._psycopg
|
|
28
|
+
__version__ = psycopg_binary.__version__
|
|
29
|
+
|
|
21
30
|
elif pq.__impl__ == "python":
|
|
22
|
-
|
|
31
|
+
|
|
32
|
+
_psycopg = None # type: ignore[assignment]
|
|
33
|
+
|
|
23
34
|
else:
|
|
24
35
|
raise ImportError(f"can't find _psycopg optimised module in {pq.__impl__!r}")
|
|
@@ -23,7 +23,7 @@ from ._tpc import Xid
|
|
|
23
23
|
from .rows import Row
|
|
24
24
|
from .adapt import AdaptersMap
|
|
25
25
|
from ._enums import IsolationLevel
|
|
26
|
-
from ._compat import LiteralString, Self, TypeAlias, TypeVar
|
|
26
|
+
from ._compat import Deque, LiteralString, Self, TypeAlias, TypeVar
|
|
27
27
|
from .pq.misc import connection_summary
|
|
28
28
|
from ._pipeline import BasePipeline
|
|
29
29
|
from ._preparing import PrepareManager
|
|
@@ -112,6 +112,10 @@ class BaseConnection(Generic[Row]):
|
|
|
112
112
|
self._prepared: PrepareManager = PrepareManager()
|
|
113
113
|
self._tpc: tuple[Xid, bool] | None = None # xid, prepared
|
|
114
114
|
|
|
115
|
+
# Gather notifies when the notifies() generator is not running.
|
|
116
|
+
# It will be set to None during `notifies()` generator run.
|
|
117
|
+
self._notifies_backlog: Deque[Notify] | None = Deque()
|
|
118
|
+
|
|
115
119
|
wself = ref(self)
|
|
116
120
|
pgconn.notice_handler = partial(BaseConnection._notice_handler, wself)
|
|
117
121
|
pgconn.notify_handler = partial(BaseConnection._notify_handler, wself)
|
|
@@ -368,12 +372,16 @@ class BaseConnection(Generic[Row]):
|
|
|
368
372
|
def _notify_handler(
|
|
369
373
|
wself: ReferenceType[BaseConnection[Row]], pgn: pq.PGnotify
|
|
370
374
|
) -> None:
|
|
371
|
-
self
|
|
372
|
-
if not (self and self._notify_handlers):
|
|
375
|
+
if not (self := wself()):
|
|
373
376
|
return
|
|
374
377
|
|
|
375
378
|
enc = self.pgconn._encoding
|
|
376
379
|
n = Notify(pgn.relname.decode(enc), pgn.extra.decode(enc), pgn.be_pid)
|
|
380
|
+
|
|
381
|
+
# `_notifies_backlog` is None if the `notifies()` generator is running
|
|
382
|
+
if (d := self._notifies_backlog) is not None:
|
|
383
|
+
d.append(n)
|
|
384
|
+
|
|
377
385
|
for cb in self._notify_handlers:
|
|
378
386
|
cb(n)
|
|
379
387
|
|
|
@@ -466,7 +474,7 @@ class BaseConnection(Generic[Row]):
|
|
|
466
474
|
else:
|
|
467
475
|
self.pgconn.send_query_params(command, None, result_format=result_format)
|
|
468
476
|
|
|
469
|
-
result = (yield from generators.execute(self.pgconn))[-1]
|
|
477
|
+
result: PGresult = (yield from generators.execute(self.pgconn))[-1]
|
|
470
478
|
if result.status != COMMAND_OK and result.status != TUPLES_OK:
|
|
471
479
|
if result.status == FATAL_ERROR:
|
|
472
480
|
raise e.error_from_result(result, encoding=self.pgconn._encoding)
|
|
@@ -216,10 +216,11 @@ class TextFormatter(Formatter):
|
|
|
216
216
|
self._encoding = encoding
|
|
217
217
|
|
|
218
218
|
def parse_row(self, data: Buffer) -> tuple[Any, ...] | None:
|
|
219
|
+
rv: tuple[Any, ...] | None = None
|
|
219
220
|
if data:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
rv = parse_row_text(data, self.transformer)
|
|
222
|
+
|
|
223
|
+
return rv
|
|
223
224
|
|
|
224
225
|
def write(self, buffer: Buffer | str) -> Buffer:
|
|
225
226
|
data = self._ensure_bytes(buffer)
|
|
@@ -260,6 +261,8 @@ class BinaryFormatter(Formatter):
|
|
|
260
261
|
self._signature_sent = False
|
|
261
262
|
|
|
262
263
|
def parse_row(self, data: Buffer) -> tuple[Any, ...] | None:
|
|
264
|
+
rv: tuple[Any, ...] | None = None
|
|
265
|
+
|
|
263
266
|
if not self._signature_sent:
|
|
264
267
|
if data[: len(_binary_signature)] != _binary_signature:
|
|
265
268
|
raise e.DataError(
|
|
@@ -268,10 +271,10 @@ class BinaryFormatter(Formatter):
|
|
|
268
271
|
self._signature_sent = True
|
|
269
272
|
data = data[len(_binary_signature) :]
|
|
270
273
|
|
|
271
|
-
|
|
272
|
-
|
|
274
|
+
if data != _binary_trailer:
|
|
275
|
+
rv = parse_row_binary(data, self.transformer)
|
|
273
276
|
|
|
274
|
-
return
|
|
277
|
+
return rv
|
|
275
278
|
|
|
276
279
|
def write(self, buffer: Buffer | str) -> Buffer:
|
|
277
280
|
data = self._ensure_bytes(buffer)
|
|
@@ -334,7 +334,7 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
334
334
|
yield from send(self._pgconn)
|
|
335
335
|
|
|
336
336
|
def _stream_fetchone_gen(self, first: bool) -> PQGen[PGresult | None]:
|
|
337
|
-
res = yield from fetch(self._pgconn)
|
|
337
|
+
res: PGresult | None = yield from fetch(self._pgconn)
|
|
338
338
|
if res is None:
|
|
339
339
|
return None
|
|
340
340
|
|
|
@@ -338,31 +338,46 @@ class Connection(BaseConnection[Row]):
|
|
|
338
338
|
|
|
339
339
|
with self.lock:
|
|
340
340
|
enc = self.pgconn._encoding
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
341
|
+
|
|
342
|
+
# Remove the backlog deque for the duration of this critical
|
|
343
|
+
# section to avoid reporting notifies twice.
|
|
344
|
+
self._notifies_backlog, d = (None, self._notifies_backlog)
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
while True:
|
|
348
|
+
# if notifies were received when the generator was off,
|
|
349
|
+
# return them in a first batch.
|
|
350
|
+
if d:
|
|
351
|
+
while d:
|
|
352
|
+
yield d.popleft()
|
|
353
|
+
nreceived += 1
|
|
354
|
+
else:
|
|
355
|
+
try:
|
|
356
|
+
pgns = self.wait(notifies(self.pgconn), interval=interval)
|
|
357
|
+
except e._NO_TRACEBACK as ex:
|
|
358
|
+
raise ex.with_traceback(None)
|
|
359
|
+
# Emit the notifications received.
|
|
360
|
+
for pgn in pgns:
|
|
361
|
+
yield Notify(
|
|
362
|
+
pgn.relname.decode(enc),
|
|
363
|
+
pgn.extra.decode(enc),
|
|
364
|
+
pgn.be_pid,
|
|
365
|
+
)
|
|
366
|
+
nreceived += 1
|
|
367
|
+
|
|
368
|
+
# Stop if we have received enough notifications.
|
|
369
|
+
if stop_after is not None and nreceived >= stop_after:
|
|
364
370
|
break
|
|
365
371
|
|
|
372
|
+
# Check the deadline after the loop to ensure that timeout=0
|
|
373
|
+
# polls at least once.
|
|
374
|
+
if deadline:
|
|
375
|
+
interval = min(_WAIT_INTERVAL, deadline - monotonic())
|
|
376
|
+
if interval < 0.0:
|
|
377
|
+
break
|
|
378
|
+
finally:
|
|
379
|
+
self._notifies_backlog = d
|
|
380
|
+
|
|
366
381
|
@contextmanager
|
|
367
382
|
def pipeline(self) -> Iterator[Pipeline]:
|
|
368
383
|
"""Context manager to switch the connection into pipeline mode."""
|
|
@@ -358,31 +358,49 @@ class AsyncConnection(BaseConnection[Row]):
|
|
|
358
358
|
|
|
359
359
|
async with self.lock:
|
|
360
360
|
enc = self.pgconn._encoding
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
361
|
+
|
|
362
|
+
# Remove the backlog deque for the duration of this critical
|
|
363
|
+
# section to avoid reporting notifies twice.
|
|
364
|
+
self._notifies_backlog, d = None, self._notifies_backlog
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
while True:
|
|
368
|
+
# if notifies were received when the generator was off,
|
|
369
|
+
# return them in a first batch.
|
|
370
|
+
if d:
|
|
371
|
+
while d:
|
|
372
|
+
yield d.popleft()
|
|
373
|
+
nreceived += 1
|
|
374
|
+
else:
|
|
375
|
+
try:
|
|
376
|
+
pgns = await self.wait(
|
|
377
|
+
notifies(self.pgconn), interval=interval
|
|
378
|
+
)
|
|
379
|
+
except e._NO_TRACEBACK as ex:
|
|
380
|
+
raise ex.with_traceback(None)
|
|
381
|
+
|
|
382
|
+
# Emit the notifications received.
|
|
383
|
+
for pgn in pgns:
|
|
384
|
+
yield Notify(
|
|
385
|
+
pgn.relname.decode(enc),
|
|
386
|
+
pgn.extra.decode(enc),
|
|
387
|
+
pgn.be_pid,
|
|
388
|
+
)
|
|
389
|
+
nreceived += 1
|
|
390
|
+
|
|
391
|
+
# Stop if we have received enough notifications.
|
|
392
|
+
if stop_after is not None and nreceived >= stop_after:
|
|
384
393
|
break
|
|
385
394
|
|
|
395
|
+
# Check the deadline after the loop to ensure that timeout=0
|
|
396
|
+
# polls at least once.
|
|
397
|
+
if deadline:
|
|
398
|
+
interval = min(_WAIT_INTERVAL, deadline - monotonic())
|
|
399
|
+
if interval < 0.0:
|
|
400
|
+
break
|
|
401
|
+
finally:
|
|
402
|
+
self._notifies_backlog = d
|
|
403
|
+
|
|
386
404
|
@asynccontextmanager
|
|
387
405
|
async def pipeline(self) -> AsyncIterator[AsyncPipeline]:
|
|
388
406
|
"""Context manager to switch the connection into pipeline mode."""
|
|
@@ -1103,6 +1103,10 @@ class IdleInTransactionSessionTimeout(InternalError,
|
|
|
1103
1103
|
code='25P03', name='IDLE_IN_TRANSACTION_SESSION_TIMEOUT'):
|
|
1104
1104
|
pass
|
|
1105
1105
|
|
|
1106
|
+
class TransactionTimeout(InternalError,
|
|
1107
|
+
code='25P04', name='TRANSACTION_TIMEOUT'):
|
|
1108
|
+
pass
|
|
1109
|
+
|
|
1106
1110
|
|
|
1107
1111
|
# Class 26 - Invalid SQL Statement Name
|
|
1108
1112
|
|
|
@@ -46,6 +46,7 @@ COMMAND_OK = pq.ExecStatus.COMMAND_OK
|
|
|
46
46
|
COPY_OUT = pq.ExecStatus.COPY_OUT
|
|
47
47
|
COPY_IN = pq.ExecStatus.COPY_IN
|
|
48
48
|
COPY_BOTH = pq.ExecStatus.COPY_BOTH
|
|
49
|
+
FATAL_ERROR = pq.ExecStatus.FATAL_ERROR
|
|
49
50
|
PIPELINE_SYNC = pq.ExecStatus.PIPELINE_SYNC
|
|
50
51
|
|
|
51
52
|
WAIT_R = Wait.R
|
|
@@ -176,7 +177,19 @@ def _fetch_many(pgconn: PGconn) -> PQGen[list[PGresult]]:
|
|
|
176
177
|
"""
|
|
177
178
|
results: list[PGresult] = []
|
|
178
179
|
while True:
|
|
179
|
-
|
|
180
|
+
try:
|
|
181
|
+
res = yield from _fetch(pgconn)
|
|
182
|
+
except e.DatabaseError:
|
|
183
|
+
# What might have happened here is that a previuos error disconnected
|
|
184
|
+
# the connection, for example a idle in transaction timeout.
|
|
185
|
+
# Check if we had received an error before: if that's the case
|
|
186
|
+
# just exit the loop. Our callers will handle this error and raise
|
|
187
|
+
# it as an exception.
|
|
188
|
+
if any(res.status == FATAL_ERROR for res in results):
|
|
189
|
+
break
|
|
190
|
+
else:
|
|
191
|
+
raise
|
|
192
|
+
|
|
180
193
|
if not res:
|
|
181
194
|
break
|
|
182
195
|
|
|
@@ -298,6 +311,8 @@ def notifies(pgconn: PGconn) -> PQGen[list[pq.PGnotify]]:
|
|
|
298
311
|
n = pgconn.notifies()
|
|
299
312
|
if n:
|
|
300
313
|
ns.append(n)
|
|
314
|
+
if pgconn.notify_handler:
|
|
315
|
+
pgconn.notify_handler(n)
|
|
301
316
|
else:
|
|
302
317
|
break
|
|
303
318
|
|
|
@@ -25,7 +25,7 @@ def register_default_types(types: TypesRegistry) -> None:
|
|
|
25
25
|
for t in [
|
|
26
26
|
TypeInfo('"char"', 18, 1002, typemod=CharTypeModifier),
|
|
27
27
|
# autogenerated: start
|
|
28
|
-
# Generated from PostgreSQL
|
|
28
|
+
# Generated from PostgreSQL 17.0
|
|
29
29
|
TypeInfo("aclitem", 1033, 1034),
|
|
30
30
|
TypeInfo("bit", 1560, 1561, typemod=BitTypeModifier),
|
|
31
31
|
TypeInfo("bool", 16, 1000, regtype="boolean"),
|
|
@@ -93,12 +93,17 @@ def debugging(f: Func) -> Func:
|
|
|
93
93
|
reprs.append(f"{k}={v!r}")
|
|
94
94
|
|
|
95
95
|
logger.info("PGconn.%s(%s)", f.__name__, ", ".join(reprs))
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
try:
|
|
97
|
+
rv = f(*args, **kwargs)
|
|
98
|
+
except Exception as ex:
|
|
99
|
+
logger.info(" <- %r", ex)
|
|
100
|
+
raise
|
|
101
|
+
else:
|
|
102
|
+
# Display the return value only if the function is declared to return
|
|
103
|
+
# something else than None.
|
|
104
|
+
ra = inspect.signature(f).return_annotation
|
|
105
|
+
if ra is not None or rv is not None:
|
|
106
|
+
logger.info(" <- %r", rv)
|
|
107
|
+
return rv
|
|
103
108
|
|
|
104
109
|
return debugging_ # type: ignore
|
|
@@ -6,6 +6,8 @@ libpq enum definitions for psycopg
|
|
|
6
6
|
|
|
7
7
|
from enum import IntEnum, IntFlag, auto
|
|
8
8
|
|
|
9
|
+
# Check in src/interfaces/libpq/libpq-fe.h for updates.
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class ConnStatus(IntEnum):
|
|
11
13
|
"""
|
|
@@ -205,7 +207,8 @@ class DiagnosticField(IntEnum):
|
|
|
205
207
|
|
|
206
208
|
__module__ = "psycopg.pq"
|
|
207
209
|
|
|
208
|
-
# from postgres_ext.h
|
|
210
|
+
# from src/include/postgres_ext.h
|
|
211
|
+
|
|
209
212
|
SEVERITY = ord("S")
|
|
210
213
|
SEVERITY_NONLOCALIZED = ord("V")
|
|
211
214
|
SQLSTATE = ord("C")
|
|
@@ -58,11 +58,11 @@ Oid = c_uint
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class PGconn_struct(Structure):
|
|
61
|
-
_fields_
|
|
61
|
+
_fields_ = []
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class PGresult_struct(Structure):
|
|
65
|
-
_fields_
|
|
65
|
+
_fields_ = []
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
class PQconninfoOption_struct(Structure):
|
|
@@ -86,11 +86,11 @@ class PGnotify_struct(Structure):
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
class PGcancelConn_struct(Structure):
|
|
89
|
-
_fields_
|
|
89
|
+
_fields_ = []
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
class PGcancel_struct(Structure):
|
|
93
|
-
_fields_
|
|
93
|
+
_fields_ = []
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
class PGresAttDesc_struct(Structure):
|
|
@@ -12,6 +12,7 @@ import sys
|
|
|
12
12
|
import logging
|
|
13
13
|
import ctypes.util
|
|
14
14
|
from typing import NamedTuple
|
|
15
|
+
from pathlib import Path
|
|
15
16
|
|
|
16
17
|
from . import abc
|
|
17
18
|
from ._enums import ConnStatus, TransactionStatus, PipelineStatus
|
|
@@ -52,6 +53,7 @@ class PGresAttDesc(NamedTuple):
|
|
|
52
53
|
def find_libpq_full_path() -> str | None:
|
|
53
54
|
if sys.platform == "win32":
|
|
54
55
|
libname = ctypes.util.find_library("libpq.dll")
|
|
56
|
+
libname = str(Path(libname).resolve())
|
|
55
57
|
|
|
56
58
|
elif sys.platform == "darwin":
|
|
57
59
|
libname = ctypes.util.find_library("libpq.dylib")
|
|
@@ -64,7 +64,7 @@ def wait_selector(gen: PQGen[RV], fileno: int, interval: float | None = None) ->
|
|
|
64
64
|
sel.register(fileno, s)
|
|
65
65
|
|
|
66
66
|
except StopIteration as ex:
|
|
67
|
-
rv: RV = ex.
|
|
67
|
+
rv: RV = ex.value
|
|
68
68
|
return rv
|
|
69
69
|
|
|
70
70
|
|
|
@@ -99,7 +99,7 @@ def wait_conn(gen: PQGenConn[RV], interval: float | None = None) -> RV:
|
|
|
99
99
|
sel.register(fileno, s)
|
|
100
100
|
|
|
101
101
|
except StopIteration as ex:
|
|
102
|
-
rv: RV = ex.
|
|
102
|
+
rv: RV = ex.value
|
|
103
103
|
return rv
|
|
104
104
|
|
|
105
105
|
|
|
@@ -160,7 +160,7 @@ async def wait_async(gen: PQGen[RV], fileno: int, interval: float | None = None)
|
|
|
160
160
|
# Assume the connection was closed
|
|
161
161
|
raise e.OperationalError(str(ex))
|
|
162
162
|
except StopIteration as ex:
|
|
163
|
-
rv: RV = ex.
|
|
163
|
+
rv: RV = ex.value
|
|
164
164
|
return rv
|
|
165
165
|
|
|
166
166
|
|
|
@@ -218,7 +218,7 @@ async def wait_conn_async(gen: PQGenConn[RV], interval: float | None = None) ->
|
|
|
218
218
|
fileno, s = gen.send(ready)
|
|
219
219
|
|
|
220
220
|
except StopIteration as ex:
|
|
221
|
-
rv: RV = ex.
|
|
221
|
+
rv: RV = ex.value
|
|
222
222
|
return rv
|
|
223
223
|
|
|
224
224
|
|
|
@@ -255,7 +255,7 @@ def wait_select(gen: PQGen[RV], fileno: int, interval: float | None = None) -> R
|
|
|
255
255
|
s = gen.send(ready)
|
|
256
256
|
|
|
257
257
|
except StopIteration as ex:
|
|
258
|
-
rv: RV = ex.
|
|
258
|
+
rv: RV = ex.value
|
|
259
259
|
return rv
|
|
260
260
|
|
|
261
261
|
|
|
@@ -311,7 +311,7 @@ def wait_epoll(gen: PQGen[RV], fileno: int, interval: float | None = None) -> RV
|
|
|
311
311
|
epoll.modify(fileno, evmask)
|
|
312
312
|
|
|
313
313
|
except StopIteration as ex:
|
|
314
|
-
rv: RV = ex.
|
|
314
|
+
rv: RV = ex.value
|
|
315
315
|
return rv
|
|
316
316
|
|
|
317
317
|
|
|
@@ -359,7 +359,7 @@ def wait_poll(gen: PQGen[RV], fileno: int, interval: float | None = None) -> RV:
|
|
|
359
359
|
poll.modify(fileno, evmask)
|
|
360
360
|
|
|
361
361
|
except StopIteration as ex:
|
|
362
|
-
rv: RV = ex.
|
|
362
|
+
rv: RV = ex.value
|
|
363
363
|
return rv
|
|
364
364
|
|
|
365
365
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: psycopg
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Summary: PostgreSQL database adapter for Python
|
|
5
5
|
Home-page: https://psycopg.org/psycopg3/
|
|
6
6
|
Author: Daniele Varrazzo
|
|
@@ -38,14 +38,14 @@ Requires-Dist: backports.zoneinfo>=0.2.0; python_version < "3.9"
|
|
|
38
38
|
Requires-Dist: typing-extensions>=4.6; python_version < "3.13"
|
|
39
39
|
Requires-Dist: tzdata; sys_platform == "win32"
|
|
40
40
|
Provides-Extra: c
|
|
41
|
-
Requires-Dist: psycopg-c==3.2.
|
|
41
|
+
Requires-Dist: psycopg-c==3.2.4; implementation_name != "pypy" and extra == "c"
|
|
42
42
|
Provides-Extra: binary
|
|
43
|
-
Requires-Dist: psycopg-binary==3.2.
|
|
43
|
+
Requires-Dist: psycopg-binary==3.2.4; implementation_name != "pypy" and extra == "binary"
|
|
44
44
|
Provides-Extra: pool
|
|
45
45
|
Requires-Dist: psycopg-pool; extra == "pool"
|
|
46
46
|
Provides-Extra: test
|
|
47
47
|
Requires-Dist: anyio>=4.0; extra == "test"
|
|
48
|
-
Requires-Dist: mypy>=1.
|
|
48
|
+
Requires-Dist: mypy>=1.14; extra == "test"
|
|
49
49
|
Requires-Dist: pproxy>=2.7; extra == "test"
|
|
50
50
|
Requires-Dist: pytest>=6.2.5; extra == "test"
|
|
51
51
|
Requires-Dist: pytest-cov>=3.0; extra == "test"
|
|
@@ -56,7 +56,8 @@ Requires-Dist: black>=24.1.0; extra == "dev"
|
|
|
56
56
|
Requires-Dist: codespell>=2.2; extra == "dev"
|
|
57
57
|
Requires-Dist: dnspython>=2.1; extra == "dev"
|
|
58
58
|
Requires-Dist: flake8>=4.0; extra == "dev"
|
|
59
|
-
Requires-Dist: mypy>=1.
|
|
59
|
+
Requires-Dist: mypy>=1.14; extra == "dev"
|
|
60
|
+
Requires-Dist: pre-commit>=4.0.1; extra == "dev"
|
|
60
61
|
Requires-Dist: types-setuptools>=57.4; extra == "dev"
|
|
61
62
|
Requires-Dist: wheel>=0.37; extra == "dev"
|
|
62
63
|
Provides-Extra: docs
|
|
@@ -11,12 +11,12 @@ tzdata
|
|
|
11
11
|
[binary]
|
|
12
12
|
|
|
13
13
|
[binary:implementation_name != "pypy"]
|
|
14
|
-
psycopg-binary==3.2.
|
|
14
|
+
psycopg-binary==3.2.4
|
|
15
15
|
|
|
16
16
|
[c]
|
|
17
17
|
|
|
18
18
|
[c:implementation_name != "pypy"]
|
|
19
|
-
psycopg-c==3.2.
|
|
19
|
+
psycopg-c==3.2.4
|
|
20
20
|
|
|
21
21
|
[dev]
|
|
22
22
|
ast-comments>=1.1.2
|
|
@@ -24,7 +24,8 @@ black>=24.1.0
|
|
|
24
24
|
codespell>=2.2
|
|
25
25
|
dnspython>=2.1
|
|
26
26
|
flake8>=4.0
|
|
27
|
-
mypy>=1.
|
|
27
|
+
mypy>=1.14
|
|
28
|
+
pre-commit>=4.0.1
|
|
28
29
|
types-setuptools>=57.4
|
|
29
30
|
wheel>=0.37
|
|
30
31
|
|
|
@@ -39,7 +40,7 @@ psycopg-pool
|
|
|
39
40
|
|
|
40
41
|
[test]
|
|
41
42
|
anyio>=4.0
|
|
42
|
-
mypy>=1.
|
|
43
|
+
mypy>=1.14
|
|
43
44
|
pproxy>=2.7
|
|
44
45
|
pytest>=6.2.5
|
|
45
46
|
pytest-cov>=3.0
|
|
@@ -5,7 +5,7 @@ url = https://psycopg.org/psycopg3/
|
|
|
5
5
|
author = Daniele Varrazzo
|
|
6
6
|
author_email = daniele.varrazzo@gmail.com
|
|
7
7
|
license = GNU Lesser General Public License v3 (LGPLv3)
|
|
8
|
-
version = 3.2.
|
|
8
|
+
version = 3.2.4
|
|
9
9
|
project_urls =
|
|
10
10
|
Homepage = https://psycopg.org/
|
|
11
11
|
Documentation = https://psycopg.org/psycopg3/docs/
|
|
@@ -48,14 +48,14 @@ install_requires =
|
|
|
48
48
|
|
|
49
49
|
[options.extras_require]
|
|
50
50
|
c =
|
|
51
|
-
psycopg-c == 3.2.
|
|
51
|
+
psycopg-c == 3.2.4; implementation_name != "pypy"
|
|
52
52
|
binary =
|
|
53
|
-
psycopg-binary == 3.2.
|
|
53
|
+
psycopg-binary == 3.2.4; implementation_name != "pypy"
|
|
54
54
|
pool =
|
|
55
55
|
psycopg-pool
|
|
56
56
|
test =
|
|
57
57
|
anyio >= 4.0
|
|
58
|
-
mypy >= 1.
|
|
58
|
+
mypy >= 1.14
|
|
59
59
|
pproxy >= 2.7
|
|
60
60
|
pytest >= 6.2.5
|
|
61
61
|
pytest-cov >= 3.0
|
|
@@ -66,7 +66,8 @@ dev =
|
|
|
66
66
|
codespell >= 2.2
|
|
67
67
|
dnspython >= 2.1
|
|
68
68
|
flake8 >= 4.0
|
|
69
|
-
mypy >= 1.
|
|
69
|
+
mypy >= 1.14
|
|
70
|
+
pre-commit >= 4.0.1
|
|
70
71
|
types-setuptools >= 57.4
|
|
71
72
|
wheel >= 0.37
|
|
72
73
|
docs =
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|