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.
Files changed (92) hide show
  1. {psycopg-3.2.2 → psycopg-3.2.4}/PKG-INFO +7 -6
  2. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_cmodule.py +16 -5
  3. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_connection_base.py +12 -4
  4. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy_base.py +9 -6
  5. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_cursor_base.py +1 -1
  6. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_oids.py +1 -1
  7. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/connection.py +38 -23
  8. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/connection_async.py +41 -23
  9. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/errors.py +4 -0
  10. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/generators.py +16 -1
  11. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/postgres.py +1 -1
  12. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_debug.py +12 -7
  13. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_enums.py +4 -1
  14. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_pq_ctypes.py +4 -4
  15. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/misc.py +2 -0
  16. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/pq_ctypes.py +2 -0
  17. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/waiting.py +7 -7
  18. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/PKG-INFO +7 -6
  19. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/requires.txt +5 -4
  20. {psycopg-3.2.2 → psycopg-3.2.4}/setup.cfg +6 -5
  21. {psycopg-3.2.2 → psycopg-3.2.4}/LICENSE.txt +0 -0
  22. {psycopg-3.2.2 → psycopg-3.2.4}/README.rst +0 -0
  23. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/__init__.py +0 -0
  24. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_acompat.py +0 -0
  25. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_adapters_map.py +0 -0
  26. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_capabilities.py +0 -0
  27. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_column.py +0 -0
  28. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_compat.py +0 -0
  29. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_connection_info.py +0 -0
  30. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_attempts.py +0 -0
  31. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_attempts_async.py +0 -0
  32. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_conninfo_utils.py +0 -0
  33. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy.py +0 -0
  34. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_copy_async.py +0 -0
  35. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_dns.py +0 -0
  36. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_encodings.py +0 -0
  37. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_enums.py +0 -0
  38. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_pipeline.py +0 -0
  39. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_preparing.py +0 -0
  40. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_py_transformer.py +0 -0
  41. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_queries.py +0 -0
  42. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_struct.py +0 -0
  43. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_tpc.py +0 -0
  44. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_transformer.py +0 -0
  45. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_typeinfo.py +0 -0
  46. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_typemod.py +0 -0
  47. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_tz.py +0 -0
  48. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/_wrappers.py +0 -0
  49. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/abc.py +0 -0
  50. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/adapt.py +0 -0
  51. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/client_cursor.py +0 -0
  52. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/conninfo.py +0 -0
  53. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/copy.py +0 -0
  54. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/__init__.py +0 -0
  55. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/_types.py +0 -0
  56. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/crdb/connection.py +0 -0
  57. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/cursor.py +0 -0
  58. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/cursor_async.py +0 -0
  59. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/dbapi20.py +0 -0
  60. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/__init__.py +0 -0
  61. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/_pq_ctypes.pyi +0 -0
  62. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/pq/abc.py +0 -0
  63. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/py.typed +0 -0
  64. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/raw_cursor.py +0 -0
  65. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/rows.py +0 -0
  66. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/server_cursor.py +0 -0
  67. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/sql.py +0 -0
  68. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/transaction.py +0 -0
  69. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/__init__.py +0 -0
  70. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/array.py +0 -0
  71. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/bool.py +0 -0
  72. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/composite.py +0 -0
  73. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/datetime.py +0 -0
  74. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/enum.py +0 -0
  75. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/hstore.py +0 -0
  76. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/json.py +0 -0
  77. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/multirange.py +0 -0
  78. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/net.py +0 -0
  79. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/none.py +0 -0
  80. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/numeric.py +0 -0
  81. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/numpy.py +0 -0
  82. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/range.py +0 -0
  83. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/shapely.py +0 -0
  84. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/string.py +0 -0
  85. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/types/uuid.py +0 -0
  86. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg/version.py +0 -0
  87. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/SOURCES.txt +0 -0
  88. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/dependency_links.txt +0 -0
  89. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/not-zip-safe +0 -0
  90. {psycopg-3.2.2 → psycopg-3.2.4}/psycopg.egg-info/top_level.txt +0 -0
  91. {psycopg-3.2.2 → psycopg-3.2.4}/pyproject.toml +0 -0
  92. {psycopg-3.2.2 → psycopg-3.2.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: psycopg
3
- Version: 3.2.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.2; implementation_name != "pypy" and extra == "c"
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.2; implementation_name != "pypy" and extra == "binary"
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.11; extra == "test"
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.11; extra == "dev"
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
- from psycopg_c import _psycopg as _psycopg
17
- from psycopg_c import __version__ as __version__ # noqa: F401
19
+ import psycopg_c._psycopg
20
+
21
+ _psycopg = psycopg_c._psycopg
22
+ __version__ = psycopg_c.__version__
23
+
18
24
  elif pq.__impl__ == "binary":
19
- from psycopg_binary import _psycopg as _psycopg # type: ignore
20
- from psycopg_binary import __version__ as __version__ # type: ignore # noqa: F401
25
+ import psycopg_binary._psycopg
26
+
27
+ _psycopg = psycopg_binary._psycopg
28
+ __version__ = psycopg_binary.__version__
29
+
21
30
  elif pq.__impl__ == "python":
22
- _psycopg = None # type: ignore
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 = wself()
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
- return parse_row_text(data, self.transformer)
221
- else:
222
- return None
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
- elif data == _binary_trailer:
272
- return None
274
+ if data != _binary_trailer:
275
+ rv = parse_row_binary(data, self.transformer)
273
276
 
274
- return parse_row_binary(data, self.transformer)
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
 
@@ -15,7 +15,7 @@ TEXT_ARRAY_OID = 1009
15
15
  # Use tools/update_oids.py to update this data.
16
16
  # autogenerated: start
17
17
 
18
- # Generated from PostgreSQL 16.2
18
+ # Generated from PostgreSQL 17.0
19
19
 
20
20
  ACLITEM_OID = 1033
21
21
  BIT_OID = 1560
@@ -338,31 +338,46 @@ class Connection(BaseConnection[Row]):
338
338
 
339
339
  with self.lock:
340
340
  enc = self.pgconn._encoding
341
- while True:
342
- try:
343
- ns = self.wait(notifies(self.pgconn), interval=interval)
344
- except e._NO_TRACEBACK as ex:
345
- raise ex.with_traceback(None)
346
-
347
- # Emit the notifications received.
348
- for pgn in ns:
349
- n = Notify(
350
- pgn.relname.decode(enc), pgn.extra.decode(enc), pgn.be_pid
351
- )
352
- yield n
353
- nreceived += 1
354
-
355
- # Stop if we have received enough notifications.
356
- if stop_after is not None and nreceived >= stop_after:
357
- break
358
-
359
- # Check the deadline after the loop to ensure that timeout=0
360
- # polls at least once.
361
- if deadline:
362
- interval = min(_WAIT_INTERVAL, deadline - monotonic())
363
- if interval < 0.0:
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
- while True:
362
- try:
363
- ns = await self.wait(notifies(self.pgconn), interval=interval)
364
- except e._NO_TRACEBACK as ex:
365
- raise ex.with_traceback(None)
366
-
367
- # Emit the notifications received.
368
- for pgn in ns:
369
- n = Notify(
370
- pgn.relname.decode(enc), pgn.extra.decode(enc), pgn.be_pid
371
- )
372
- yield n
373
- nreceived += 1
374
-
375
- # Stop if we have received enough notifications.
376
- if stop_after is not None and nreceived >= stop_after:
377
- break
378
-
379
- # Check the deadline after the loop to ensure that timeout=0
380
- # polls at least once.
381
- if deadline:
382
- interval = min(_WAIT_INTERVAL, deadline - monotonic())
383
- if interval < 0.0:
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
- res = yield from _fetch(pgconn)
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 16.2
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
- rv = f(*args, **kwargs)
97
- # Display the return value only if the function is declared to return
98
- # something else than None.
99
- ra = inspect.signature(f).return_annotation
100
- if ra is not None or rv is not None:
101
- logger.info(" <- %r", rv)
102
- return rv
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_: list[tuple[str, type]] = []
61
+ _fields_ = []
62
62
 
63
63
 
64
64
  class PGresult_struct(Structure):
65
- _fields_: list[tuple[str, type]] = []
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_: list[tuple[str, type]] = []
89
+ _fields_ = []
90
90
 
91
91
 
92
92
  class PGcancel_struct(Structure):
93
- _fields_: list[tuple[str, type]] = []
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")
@@ -1,3 +1,5 @@
1
+ # mypy: ignore-errors
2
+
1
3
  """
2
4
  libpq Python wrapper using ctypes bindings.
3
5
 
@@ -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.args[0] if ex.args else None
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.args[0] if ex.args else None
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.args[0] if ex.args else None
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.args[0] if ex.args else None
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.args[0] if ex.args else None
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.args[0] if ex.args else None
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.args[0] if ex.args else None
362
+ rv: RV = ex.value
363
363
  return rv
364
364
 
365
365
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: psycopg
3
- Version: 3.2.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.2; implementation_name != "pypy" and extra == "c"
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.2; implementation_name != "pypy" and extra == "binary"
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.11; extra == "test"
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.11; extra == "dev"
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.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.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.11
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.11
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.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.2; implementation_name != "pypy"
51
+ psycopg-c == 3.2.4; implementation_name != "pypy"
52
52
  binary =
53
- psycopg-binary == 3.2.2; implementation_name != "pypy"
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.11
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.11
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