psycopg 3.3.2__tar.gz → 3.3.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.3.2 → psycopg-3.3.4}/PKG-INFO +8 -8
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_adapters_map.py +3 -1
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_capabilities.py +2 -2
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_column.py +2 -5
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_cursor_base.py +14 -9
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_queries.py +4 -6
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_tstrings.py +1 -1
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_typeinfo.py +2 -4
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/connection.py +2 -1
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/connection_async.py +2 -1
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/conninfo.py +1 -1
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/__init__.py +2 -4
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/array.py +4 -10
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/composite.py +7 -9
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/datetime.py +8 -16
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/enum.py +10 -8
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/multirange.py +2 -4
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/range.py +2 -4
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/uuid.py +4 -8
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/PKG-INFO +8 -8
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/requires.txt +7 -7
- {psycopg-3.3.2 → psycopg-3.3.4}/pyproject.toml +8 -8
- {psycopg-3.3.2 → psycopg-3.3.4}/LICENSE.txt +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/README.rst +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/__init__.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_acompat.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_cmodule.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_compat.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_connection_base.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_connection_info.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_conninfo_attempts.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_conninfo_attempts_async.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_conninfo_utils.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_copy.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_copy_async.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_copy_base.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_dns.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_encodings.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_enums.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_oids.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_pipeline.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_pipeline_async.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_pipeline_base.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_preparing.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_py_transformer.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_server_cursor.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_server_cursor_async.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_server_cursor_base.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_struct.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_tpc.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_transformer.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_typemod.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_tz.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/_wrappers.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/abc.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/adapt.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/client_cursor.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/copy.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/crdb/__init__.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/crdb/_types.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/crdb/connection.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/cursor.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/cursor_async.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/dbapi20.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/errors.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/generators.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/postgres.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/_debug.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/_enums.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/_pq_ctypes.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/_pq_ctypes.pyi +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/abc.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/misc.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/pq/pq_ctypes.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/py.typed +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/raw_cursor.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/rows.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/sql.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/transaction.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/__init__.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/bool.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/hstore.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/json.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/net.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/none.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/numeric.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/numpy.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/shapely.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/types/string.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/version.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg/waiting.py +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/SOURCES.txt +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/dependency_links.txt +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/not-zip-safe +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/psycopg.egg-info/top_level.txt +0 -0
- {psycopg-3.3.2 → psycopg-3.3.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: psycopg
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.4
|
|
4
4
|
Summary: PostgreSQL database adapter for Python
|
|
5
5
|
Author-email: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0-only
|
|
@@ -33,9 +33,9 @@ License-File: LICENSE.txt
|
|
|
33
33
|
Requires-Dist: typing-extensions>=4.6; python_version < "3.13"
|
|
34
34
|
Requires-Dist: tzdata; sys_platform == "win32"
|
|
35
35
|
Provides-Extra: c
|
|
36
|
-
Requires-Dist: psycopg-c==3.3.
|
|
36
|
+
Requires-Dist: psycopg-c==3.3.4; implementation_name != "pypy" and extra == "c"
|
|
37
37
|
Provides-Extra: binary
|
|
38
|
-
Requires-Dist: psycopg-binary==3.3.
|
|
38
|
+
Requires-Dist: psycopg-binary==3.3.4; implementation_name != "pypy" and extra == "binary"
|
|
39
39
|
Provides-Extra: pool
|
|
40
40
|
Requires-Dist: psycopg-pool; extra == "pool"
|
|
41
41
|
Provides-Extra: test
|
|
@@ -47,7 +47,7 @@ Requires-Dist: pytest-cov>=3.0; extra == "test"
|
|
|
47
47
|
Requires-Dist: pytest-randomly>=3.5; extra == "test"
|
|
48
48
|
Provides-Extra: dev
|
|
49
49
|
Requires-Dist: ast-comments>=1.1.2; extra == "dev"
|
|
50
|
-
Requires-Dist: black>=
|
|
50
|
+
Requires-Dist: black>=26.1.0; extra == "dev"
|
|
51
51
|
Requires-Dist: codespell>=2.2; extra == "dev"
|
|
52
52
|
Requires-Dist: cython-lint>=0.16; extra == "dev"
|
|
53
53
|
Requires-Dist: dnspython>=2.1; extra == "dev"
|
|
@@ -60,10 +60,10 @@ Requires-Dist: types-setuptools>=57.4; extra == "dev"
|
|
|
60
60
|
Requires-Dist: types-shapely>=2.0; extra == "dev"
|
|
61
61
|
Requires-Dist: wheel>=0.37; extra == "dev"
|
|
62
62
|
Provides-Extra: docs
|
|
63
|
-
Requires-Dist: Sphinx>=
|
|
64
|
-
Requires-Dist: furo==
|
|
65
|
-
Requires-Dist: sphinx-autobuild>=
|
|
66
|
-
Requires-Dist: sphinx-autodoc-typehints>=
|
|
63
|
+
Requires-Dist: Sphinx>=9.1; extra == "docs"
|
|
64
|
+
Requires-Dist: furo==2025.12.19; extra == "docs"
|
|
65
|
+
Requires-Dist: sphinx-autobuild>=2025.8.25; extra == "docs"
|
|
66
|
+
Requires-Dist: sphinx-autodoc-typehints>=3.10.2; extra == "docs"
|
|
67
67
|
Dynamic: license-file
|
|
68
68
|
|
|
69
69
|
Psycopg 3: PostgreSQL database adapter for Python
|
|
@@ -212,8 +212,10 @@ class AdaptersMap:
|
|
|
212
212
|
|
|
213
213
|
# Look for the right class, including looking at superclasses
|
|
214
214
|
for scls in cls.__mro__:
|
|
215
|
-
|
|
215
|
+
try:
|
|
216
216
|
return dmap[scls]
|
|
217
|
+
except KeyError:
|
|
218
|
+
pass
|
|
217
219
|
|
|
218
220
|
# If the adapter is not found, look for its name as a string
|
|
219
221
|
fqn = scls.__module__ + "." + scls.__qualname__
|
|
@@ -98,9 +98,9 @@ class Capabilities:
|
|
|
98
98
|
|
|
99
99
|
The expletive messages, are left to the user.
|
|
100
100
|
"""
|
|
101
|
-
|
|
101
|
+
try:
|
|
102
102
|
msg = self._cache[feature]
|
|
103
|
-
|
|
103
|
+
except KeyError:
|
|
104
104
|
msg = self._get_unsupported_message(feature, want_version)
|
|
105
105
|
self._cache[feature] = msg
|
|
106
106
|
|
|
@@ -32,12 +32,9 @@ class Column(Sequence[Any]):
|
|
|
32
32
|
self._fmod = res.fmod(index)
|
|
33
33
|
self._fsize = res.fsize(index)
|
|
34
34
|
|
|
35
|
-
_attrs = tuple(
|
|
36
|
-
attrgetter(attr)
|
|
37
|
-
for attr in """
|
|
35
|
+
_attrs = tuple(attrgetter(attr) for attr in """
|
|
38
36
|
name type_code display_size internal_size precision scale null_ok
|
|
39
|
-
""".split()
|
|
40
|
-
)
|
|
37
|
+
""".split())
|
|
41
38
|
|
|
42
39
|
def __repr__(self) -> str:
|
|
43
40
|
return (
|
|
@@ -49,7 +49,7 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
49
49
|
__slots__ = """
|
|
50
50
|
_conn format _adapters arraysize _closed _results pgresult _pos
|
|
51
51
|
_iresult _rowcount _query _tx _last_query _row_factory _make_row
|
|
52
|
-
_pgconn _execmany_returning
|
|
52
|
+
_pgconn _execmany_returning _statusmessage
|
|
53
53
|
__weakref__
|
|
54
54
|
""".split()
|
|
55
55
|
|
|
@@ -79,6 +79,7 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
79
79
|
self._pos = 0
|
|
80
80
|
self._iresult = 0
|
|
81
81
|
self._rowcount = -1
|
|
82
|
+
self._statusmessage: bytes | None = None
|
|
82
83
|
self._query: PostgresQuery | None
|
|
83
84
|
# None if executemany() not executing, True/False according to returning state
|
|
84
85
|
self._execmany_returning: bool | None = None
|
|
@@ -178,8 +179,7 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
178
179
|
|
|
179
180
|
`!None` if the cursor doesn't have a result available.
|
|
180
181
|
"""
|
|
181
|
-
|
|
182
|
-
return msg.decode() if msg else None
|
|
182
|
+
return self._statusmessage.decode() if self._statusmessage else None
|
|
183
183
|
|
|
184
184
|
def _make_row_maker(self) -> RowMaker[Row]:
|
|
185
185
|
raise NotImplementedError
|
|
@@ -289,9 +289,10 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
289
289
|
if prep is Prepare.SHOULD:
|
|
290
290
|
self._send_prepare(name, pgq)
|
|
291
291
|
if not self._conn._pipeline:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
results = yield from execute(self._pgconn)
|
|
293
|
+
for result in results:
|
|
294
|
+
if result.status == FATAL_ERROR:
|
|
295
|
+
raise e.error_from_result(result, encoding=self._encoding)
|
|
295
296
|
# Then execute it.
|
|
296
297
|
self._send_query_prepared(name, pgq, binary=binary)
|
|
297
298
|
|
|
@@ -524,6 +525,7 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
524
525
|
nrows = self.pgresult.command_tuples
|
|
525
526
|
self._rowcount = nrows if nrows is not None else -1
|
|
526
527
|
|
|
528
|
+
self._statusmessage = res.command_status
|
|
527
529
|
self._make_row = self._make_row_maker()
|
|
528
530
|
|
|
529
531
|
def _set_results(self, results: list[PGresult]) -> None:
|
|
@@ -539,9 +541,12 @@ class BaseCursor(Generic[ConnectionType, Row]):
|
|
|
539
541
|
self._select_current_result(0)
|
|
540
542
|
else:
|
|
541
543
|
# In non-returning case, set rowcount to the cumulated number of
|
|
542
|
-
# rows of executed queries.
|
|
543
|
-
|
|
544
|
-
|
|
544
|
+
# rows of executed queries. Keep the last result's command_status
|
|
545
|
+
# so that statusmessage is available after the batch completes.
|
|
546
|
+
if results:
|
|
547
|
+
self._statusmessage = results[-1].command_status
|
|
548
|
+
for res in results:
|
|
549
|
+
self._rowcount += res.command_tuples or 0
|
|
545
550
|
|
|
546
551
|
@classmethod
|
|
547
552
|
def _loaders_changed(
|
|
@@ -82,7 +82,7 @@ class PostgresQuery:
|
|
|
82
82
|
else:
|
|
83
83
|
f = _query2pg_nocache
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
self.query, self._want_formats, self._order, self._parts = f(
|
|
86
86
|
query, self._tx.encoding
|
|
87
87
|
)
|
|
88
88
|
else:
|
|
@@ -281,7 +281,7 @@ class PostgresClientQuery(PostgresQuery):
|
|
|
281
281
|
else:
|
|
282
282
|
f = _query2pg_client_nocache
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
self.template, self._order, self._parts = f(query, self._tx.encoding)
|
|
285
285
|
else:
|
|
286
286
|
self.query = query
|
|
287
287
|
self._order = None
|
|
@@ -360,8 +360,7 @@ def _query2pg_client_nocache(
|
|
|
360
360
|
_query2pg_client = lru_cache(_query2pg_client_nocache)
|
|
361
361
|
|
|
362
362
|
|
|
363
|
-
_re_placeholder = re.compile(
|
|
364
|
-
rb"""(?x)
|
|
363
|
+
_re_placeholder = re.compile(rb"""(?x)
|
|
365
364
|
% # a literal %
|
|
366
365
|
(?:
|
|
367
366
|
(?:
|
|
@@ -371,8 +370,7 @@ _re_placeholder = re.compile(
|
|
|
371
370
|
|
|
|
372
371
|
(?:.) # or any char, really
|
|
373
372
|
)
|
|
374
|
-
"""
|
|
375
|
-
)
|
|
373
|
+
""")
|
|
376
374
|
|
|
377
375
|
|
|
378
376
|
def _split_query(
|
|
@@ -83,7 +83,7 @@ class TemplateProcessor:
|
|
|
83
83
|
elif fmt == FMT_SQL:
|
|
84
84
|
# It must have been processed already
|
|
85
85
|
raise e.ProgrammingError(
|
|
86
|
-
"sql values must be sql.
|
|
86
|
+
"sql values must be sql.Composed, sql.SQL, or Template;"
|
|
87
87
|
f" got {type(item.value).__qualname__}"
|
|
88
88
|
f" in {{{item.expression}:{fmt}}}"
|
|
89
89
|
)
|
|
@@ -158,16 +158,14 @@ class TypeInfo:
|
|
|
158
158
|
|
|
159
159
|
@classmethod
|
|
160
160
|
def _get_info_query(cls, conn: BaseConnection[Any]) -> QueryNoTemplate:
|
|
161
|
-
return sql.SQL(
|
|
162
|
-
"""\
|
|
161
|
+
return sql.SQL("""\
|
|
163
162
|
SELECT
|
|
164
163
|
typname AS name, oid, typarray AS array_oid,
|
|
165
164
|
oid::regtype::text AS regtype, typdelim AS delimiter
|
|
166
165
|
FROM pg_type t
|
|
167
166
|
WHERE t.oid = {regtype}
|
|
168
167
|
ORDER BY t.oid
|
|
169
|
-
"""
|
|
170
|
-
).format(regtype=cls._to_regtype(conn))
|
|
168
|
+
""").format(regtype=cls._to_regtype(conn))
|
|
171
169
|
|
|
172
170
|
@classmethod
|
|
173
171
|
def _has_to_regtype_function(cls, conn: BaseConnection[Any]) -> bool:
|
|
@@ -126,7 +126,8 @@ class Connection(BaseConnection[Row]):
|
|
|
126
126
|
lines = [str(last_ex)]
|
|
127
127
|
lines.append("Multiple connection attempts failed. All failures were:")
|
|
128
128
|
lines.extend((f"- {descr}: {error}" for error, descr in conn_errors))
|
|
129
|
-
|
|
129
|
+
new_ex = type(last_ex)("\n".join(lines), pgconn=last_ex.pgconn)
|
|
130
|
+
raise new_ex.with_traceback(None)
|
|
130
131
|
|
|
131
132
|
if (
|
|
132
133
|
capabilities.has_used_gssapi()
|
|
@@ -142,7 +142,8 @@ class AsyncConnection(BaseConnection[Row]):
|
|
|
142
142
|
lines = [str(last_ex)]
|
|
143
143
|
lines.append("Multiple connection attempts failed. All failures were:")
|
|
144
144
|
lines.extend(f"- {descr}: {error}" for error, descr in conn_errors)
|
|
145
|
-
|
|
145
|
+
new_ex = type(last_ex)("\n".join(lines), pgconn=last_ex.pgconn)
|
|
146
|
+
raise new_ex.with_traceback(None)
|
|
146
147
|
|
|
147
148
|
if (
|
|
148
149
|
capabilities.has_used_gssapi()
|
|
@@ -13,7 +13,7 @@ from . import errors as e
|
|
|
13
13
|
from . import pq
|
|
14
14
|
from .abc import ConnDict, ConnParam
|
|
15
15
|
|
|
16
|
-
# re-
|
|
16
|
+
# re-exports
|
|
17
17
|
conninfo_attempts = _conninfo_attempts.conninfo_attempts
|
|
18
18
|
conninfo_attempts_async = _conninfo_attempts_async.conninfo_attempts_async
|
|
19
19
|
|
|
@@ -105,12 +105,10 @@ def import_from_libpq() -> None:
|
|
|
105
105
|
raise ImportError(f"requested psycopg implementation '{impl}' unknown")
|
|
106
106
|
else:
|
|
107
107
|
sattempts = "\n".join(f"- {attempt}" for attempt in attempts)
|
|
108
|
-
raise ImportError(
|
|
109
|
-
f"""\
|
|
108
|
+
raise ImportError(f"""\
|
|
110
109
|
no pq wrapper available.
|
|
111
110
|
Attempts made:
|
|
112
|
-
{sattempts}"""
|
|
113
|
-
)
|
|
111
|
+
{sattempts}""")
|
|
114
112
|
|
|
115
113
|
|
|
116
114
|
import_from_libpq()
|
|
@@ -204,14 +204,11 @@ def _get_needs_quotes_regexp(delimiter: bytes) -> re.Pattern[bytes]:
|
|
|
204
204
|
they are empty strings, contain curly braces, delimiter characters,
|
|
205
205
|
double quotes, backslashes, or white space, or match the word NULL.
|
|
206
206
|
"""
|
|
207
|
-
return re.compile(
|
|
208
|
-
rb"""(?xi)
|
|
207
|
+
return re.compile(rb"""(?xi)
|
|
209
208
|
^$ # the empty string
|
|
210
209
|
| ["{}%s\\\s] # or a char to escape
|
|
211
210
|
| ^null$ # or the word NULL
|
|
212
|
-
"""
|
|
213
|
-
% delimiter
|
|
214
|
-
)
|
|
211
|
+
""" % delimiter)
|
|
215
212
|
|
|
216
213
|
|
|
217
214
|
class ListBinaryDumper(BaseListDumper):
|
|
@@ -431,15 +428,12 @@ def _get_array_parse_regexp(delimiter: bytes) -> re.Pattern[bytes]:
|
|
|
431
428
|
"""
|
|
432
429
|
Return a regexp to tokenize an array representation into item and brackets
|
|
433
430
|
"""
|
|
434
|
-
return re.compile(
|
|
435
|
-
rb"""(?xi)
|
|
431
|
+
return re.compile(rb"""(?xi)
|
|
436
432
|
( [{}] # open or closed bracket
|
|
437
433
|
| " (?: [^"\\] | \\. )* " # or a quoted string
|
|
438
434
|
| [^"{}%s\\]+ # or an unquoted non-empty string
|
|
439
435
|
) ,?
|
|
440
|
-
"""
|
|
441
|
-
% delimiter
|
|
442
|
-
)
|
|
436
|
+
""" % delimiter)
|
|
443
437
|
|
|
444
438
|
|
|
445
439
|
def _load_binary(data: Buffer, tx: Transformer) -> list[Any]:
|
|
@@ -63,8 +63,7 @@ class CompositeInfo(TypeInfo):
|
|
|
63
63
|
|
|
64
64
|
@classmethod
|
|
65
65
|
def _get_info_query(cls, conn: BaseConnection[Any]) -> abc.QueryNoTemplate:
|
|
66
|
-
return sql.SQL(
|
|
67
|
-
"""\
|
|
66
|
+
return sql.SQL("""\
|
|
68
67
|
SELECT
|
|
69
68
|
t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
|
|
70
69
|
t.oid::regtype::text AS regtype,
|
|
@@ -88,8 +87,7 @@ LEFT JOIN (
|
|
|
88
87
|
GROUP BY attrelid
|
|
89
88
|
) a ON a.attrelid = t.typrelid
|
|
90
89
|
WHERE t.oid = {regtype}
|
|
91
|
-
"""
|
|
92
|
-
).format(regtype=cls._to_regtype(conn))
|
|
90
|
+
""").format(regtype=cls._to_regtype(conn))
|
|
93
91
|
|
|
94
92
|
|
|
95
93
|
class TupleDumper(RecursiveDumper):
|
|
@@ -208,8 +206,10 @@ class RecordBinaryLoader(Loader):
|
|
|
208
206
|
return tx.load_sequence(record)
|
|
209
207
|
|
|
210
208
|
def _get_transformer(self, key: tuple[int, ...]) -> abc.Transformer:
|
|
211
|
-
|
|
209
|
+
try:
|
|
212
210
|
return self._txs[key]
|
|
211
|
+
except KeyError:
|
|
212
|
+
pass
|
|
213
213
|
|
|
214
214
|
tx = Transformer(self._ctx)
|
|
215
215
|
tx.set_loader_types([*key], self.format)
|
|
@@ -472,13 +472,11 @@ def _parse_text_record(data: abc.Buffer) -> list[bytes | None]:
|
|
|
472
472
|
return record
|
|
473
473
|
|
|
474
474
|
|
|
475
|
-
_re_tokenize = re.compile(
|
|
476
|
-
rb"""(?x)
|
|
475
|
+
_re_tokenize = re.compile(rb"""(?x)
|
|
477
476
|
(,) # an empty token, representing NULL
|
|
478
477
|
| " ((?: [^"] | "")*) " ,? # or a quoted string
|
|
479
478
|
| ([^",)]+) ,? # or an unquoted string
|
|
480
|
-
"""
|
|
481
|
-
)
|
|
479
|
+
""")
|
|
482
480
|
_re_undouble = re.compile(rb'(["\\])\1')
|
|
483
481
|
|
|
484
482
|
|
|
@@ -325,14 +325,12 @@ class TimeBinaryLoader(Loader):
|
|
|
325
325
|
|
|
326
326
|
|
|
327
327
|
class TimetzLoader(Loader):
|
|
328
|
-
_re_format = re.compile(
|
|
329
|
-
rb"""(?ix)
|
|
328
|
+
_re_format = re.compile(rb"""(?ix)
|
|
330
329
|
^
|
|
331
330
|
(\d+) : (\d+) : (\d+) (?: \. (\d+) )? # Time and micros
|
|
332
331
|
([-+]) (\d+) (?: : (\d+) )? (?: : (\d+) )? # Timezone
|
|
333
332
|
$
|
|
334
|
-
"""
|
|
335
|
-
)
|
|
333
|
+
""")
|
|
336
334
|
|
|
337
335
|
def load(self, data: Buffer) -> time:
|
|
338
336
|
if not (m := self._re_format.match(data)):
|
|
@@ -381,18 +379,15 @@ class TimetzBinaryLoader(Loader):
|
|
|
381
379
|
|
|
382
380
|
|
|
383
381
|
class TimestampLoader(Loader):
|
|
384
|
-
_re_format = re.compile(
|
|
385
|
-
rb"""(?ix)
|
|
382
|
+
_re_format = re.compile(rb"""(?ix)
|
|
386
383
|
^
|
|
387
384
|
(\d+) [^a-z0-9] (\d+) [^a-z0-9] (\d+) # Date
|
|
388
385
|
(?: T | [^a-z0-9] ) # Separator, including T
|
|
389
386
|
(\d+) [^a-z0-9] (\d+) [^a-z0-9] (\d+) # Time
|
|
390
387
|
(?: \.(\d+) )? # Micros
|
|
391
388
|
$
|
|
392
|
-
"""
|
|
393
|
-
)
|
|
394
|
-
_re_format_pg = re.compile(
|
|
395
|
-
rb"""(?ix)
|
|
389
|
+
""")
|
|
390
|
+
_re_format_pg = re.compile(rb"""(?ix)
|
|
396
391
|
^
|
|
397
392
|
[a-z]+ [^a-z0-9] # DoW, separator
|
|
398
393
|
(\d+|[a-z]+) [^a-z0-9] # Month or day
|
|
@@ -401,8 +396,7 @@ class TimestampLoader(Loader):
|
|
|
401
396
|
(?: \.(\d+) )? # Micros
|
|
402
397
|
[^a-z0-9] (\d+) # Year
|
|
403
398
|
$
|
|
404
|
-
"""
|
|
405
|
-
)
|
|
399
|
+
""")
|
|
406
400
|
|
|
407
401
|
_ORDER_YMD = 0
|
|
408
402
|
_ORDER_DMY = 1
|
|
@@ -477,8 +471,7 @@ class TimestampBinaryLoader(Loader):
|
|
|
477
471
|
|
|
478
472
|
|
|
479
473
|
class TimestamptzLoader(Loader):
|
|
480
|
-
_re_format = re.compile(
|
|
481
|
-
rb"""(?ix)
|
|
474
|
+
_re_format = re.compile(rb"""(?ix)
|
|
482
475
|
^
|
|
483
476
|
(\d+) [^a-z0-9] (\d+) [^a-z0-9] (\d+) # Date
|
|
484
477
|
(?: T | [^a-z0-9] ) # Separator, including T
|
|
@@ -486,8 +479,7 @@ class TimestamptzLoader(Loader):
|
|
|
486
479
|
(?: \.(\d+) )? # Micros
|
|
487
480
|
([-+]) (\d+) (?: : (\d+) )? (?: : (\d+) )? # Timezone
|
|
488
481
|
$
|
|
489
|
-
"""
|
|
490
|
-
)
|
|
482
|
+
""")
|
|
491
483
|
|
|
492
484
|
def __init__(self, oid: int, context: AdaptContext | None = None):
|
|
493
485
|
super().__init__(oid, context)
|
|
@@ -43,31 +43,33 @@ class EnumInfo(TypeInfo):
|
|
|
43
43
|
name: str,
|
|
44
44
|
oid: int,
|
|
45
45
|
array_oid: int,
|
|
46
|
+
# A bit ugly: this should have been a keyword-only argument, but it has
|
|
47
|
+
# been it the wild accepting a positional argument for too long to fix.
|
|
46
48
|
labels: Sequence[str],
|
|
49
|
+
*,
|
|
50
|
+
regtype: str = "",
|
|
47
51
|
):
|
|
48
|
-
super().__init__(name, oid, array_oid)
|
|
52
|
+
super().__init__(name, oid, array_oid, regtype=regtype)
|
|
49
53
|
self.labels = labels
|
|
50
54
|
# Will be set by register_enum()
|
|
51
55
|
self.enum: type[Enum] | None = None
|
|
52
56
|
|
|
53
57
|
@classmethod
|
|
54
58
|
def _get_info_query(cls, conn: BaseConnection[Any]) -> QueryNoTemplate:
|
|
55
|
-
return sql.SQL(
|
|
56
|
-
|
|
57
|
-
SELECT name, oid, array_oid, array_agg(label) AS labels
|
|
59
|
+
return sql.SQL("""\
|
|
60
|
+
SELECT name, oid, array_oid, regtype, array_agg(label) AS labels
|
|
58
61
|
FROM (
|
|
59
62
|
SELECT
|
|
60
63
|
t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
|
|
61
|
-
e.enumlabel AS label
|
|
64
|
+
t.oid::regtype::text AS regtype, e.enumlabel AS label
|
|
62
65
|
FROM pg_type t
|
|
63
66
|
LEFT JOIN pg_enum e
|
|
64
67
|
ON e.enumtypid = t.oid
|
|
65
68
|
WHERE t.oid = {regtype}
|
|
66
69
|
ORDER BY e.enumsortorder
|
|
67
70
|
) x
|
|
68
|
-
GROUP BY name, oid, array_oid
|
|
69
|
-
"""
|
|
70
|
-
).format(regtype=cls._to_regtype(conn))
|
|
71
|
+
GROUP BY name, oid, array_oid, regtype
|
|
72
|
+
""").format(regtype=cls._to_regtype(conn))
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
class _BaseEnumLoader(Loader, Generic[E]):
|
|
@@ -51,16 +51,14 @@ class MultirangeInfo(TypeInfo):
|
|
|
51
51
|
raise e.NotSupportedError(
|
|
52
52
|
"multirange types are only available from PostgreSQL 14"
|
|
53
53
|
)
|
|
54
|
-
return sql.SQL(
|
|
55
|
-
"""\
|
|
54
|
+
return sql.SQL("""\
|
|
56
55
|
SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
|
|
57
56
|
t.oid::regtype::text AS regtype,
|
|
58
57
|
r.rngtypid AS range_oid, r.rngsubtype AS subtype_oid
|
|
59
58
|
FROM pg_type t
|
|
60
59
|
JOIN pg_range r ON t.oid = r.rngmultitypid
|
|
61
60
|
WHERE t.oid = {regtype}
|
|
62
|
-
"""
|
|
63
|
-
).format(regtype=cls._to_regtype(conn))
|
|
61
|
+
""").format(regtype=cls._to_regtype(conn))
|
|
64
62
|
|
|
65
63
|
def _added(self, registry: TypesRegistry) -> None:
|
|
66
64
|
# Map multiranges ranges and subtypes to info
|
|
@@ -55,16 +55,14 @@ class RangeInfo(TypeInfo):
|
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
57
|
def _get_info_query(cls, conn: BaseConnection[Any]) -> QueryNoTemplate:
|
|
58
|
-
return sql.SQL(
|
|
59
|
-
"""\
|
|
58
|
+
return sql.SQL("""\
|
|
60
59
|
SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
|
|
61
60
|
t.oid::regtype::text AS regtype,
|
|
62
61
|
r.rngsubtype AS subtype_oid
|
|
63
62
|
FROM pg_type t
|
|
64
63
|
JOIN pg_range r ON t.oid = r.rngtypid
|
|
65
64
|
WHERE t.oid = {regtype}
|
|
66
|
-
"""
|
|
67
|
-
).format(regtype=cls._to_regtype(conn))
|
|
65
|
+
""").format(regtype=cls._to_regtype(conn))
|
|
68
66
|
|
|
69
67
|
def _added(self, registry: TypesRegistry) -> None:
|
|
70
68
|
# Map ranges subtypes to info
|
|
@@ -25,14 +25,14 @@ class UUIDDumper(Dumper):
|
|
|
25
25
|
oid = _oids.UUID_OID
|
|
26
26
|
|
|
27
27
|
def dump(self, obj: uuid.UUID) -> Buffer | None:
|
|
28
|
-
return obj.
|
|
28
|
+
return b"%032x" % obj.int
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class UUIDBinaryDumper(UUIDDumper):
|
|
32
32
|
format = Format.BINARY
|
|
33
33
|
|
|
34
34
|
def dump(self, obj: uuid.UUID) -> Buffer | None:
|
|
35
|
-
return obj.
|
|
35
|
+
return obj.int.to_bytes(16, "big")
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class UUIDLoader(Loader):
|
|
@@ -43,18 +43,14 @@ class UUIDLoader(Loader):
|
|
|
43
43
|
from uuid import UUID
|
|
44
44
|
|
|
45
45
|
def load(self, data: Buffer) -> uuid.UUID:
|
|
46
|
-
if isinstance(data, memoryview)
|
|
47
|
-
data = bytes(data)
|
|
48
|
-
return UUID(data.decode())
|
|
46
|
+
return UUID((bytes(data) if isinstance(data, memoryview) else data).decode())
|
|
49
47
|
|
|
50
48
|
|
|
51
49
|
class UUIDBinaryLoader(UUIDLoader):
|
|
52
50
|
format = Format.BINARY
|
|
53
51
|
|
|
54
52
|
def load(self, data: Buffer) -> uuid.UUID:
|
|
55
|
-
if isinstance(data, memoryview)
|
|
56
|
-
data = bytes(data)
|
|
57
|
-
return UUID(bytes=data)
|
|
53
|
+
return UUID(bytes=(bytes(data) if isinstance(data, memoryview) else data))
|
|
58
54
|
|
|
59
55
|
|
|
60
56
|
def register_default_adapters(context: AdaptContext) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: psycopg
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.4
|
|
4
4
|
Summary: PostgreSQL database adapter for Python
|
|
5
5
|
Author-email: Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0-only
|
|
@@ -33,9 +33,9 @@ License-File: LICENSE.txt
|
|
|
33
33
|
Requires-Dist: typing-extensions>=4.6; python_version < "3.13"
|
|
34
34
|
Requires-Dist: tzdata; sys_platform == "win32"
|
|
35
35
|
Provides-Extra: c
|
|
36
|
-
Requires-Dist: psycopg-c==3.3.
|
|
36
|
+
Requires-Dist: psycopg-c==3.3.4; implementation_name != "pypy" and extra == "c"
|
|
37
37
|
Provides-Extra: binary
|
|
38
|
-
Requires-Dist: psycopg-binary==3.3.
|
|
38
|
+
Requires-Dist: psycopg-binary==3.3.4; implementation_name != "pypy" and extra == "binary"
|
|
39
39
|
Provides-Extra: pool
|
|
40
40
|
Requires-Dist: psycopg-pool; extra == "pool"
|
|
41
41
|
Provides-Extra: test
|
|
@@ -47,7 +47,7 @@ Requires-Dist: pytest-cov>=3.0; extra == "test"
|
|
|
47
47
|
Requires-Dist: pytest-randomly>=3.5; extra == "test"
|
|
48
48
|
Provides-Extra: dev
|
|
49
49
|
Requires-Dist: ast-comments>=1.1.2; extra == "dev"
|
|
50
|
-
Requires-Dist: black>=
|
|
50
|
+
Requires-Dist: black>=26.1.0; extra == "dev"
|
|
51
51
|
Requires-Dist: codespell>=2.2; extra == "dev"
|
|
52
52
|
Requires-Dist: cython-lint>=0.16; extra == "dev"
|
|
53
53
|
Requires-Dist: dnspython>=2.1; extra == "dev"
|
|
@@ -60,10 +60,10 @@ Requires-Dist: types-setuptools>=57.4; extra == "dev"
|
|
|
60
60
|
Requires-Dist: types-shapely>=2.0; extra == "dev"
|
|
61
61
|
Requires-Dist: wheel>=0.37; extra == "dev"
|
|
62
62
|
Provides-Extra: docs
|
|
63
|
-
Requires-Dist: Sphinx>=
|
|
64
|
-
Requires-Dist: furo==
|
|
65
|
-
Requires-Dist: sphinx-autobuild>=
|
|
66
|
-
Requires-Dist: sphinx-autodoc-typehints>=
|
|
63
|
+
Requires-Dist: Sphinx>=9.1; extra == "docs"
|
|
64
|
+
Requires-Dist: furo==2025.12.19; extra == "docs"
|
|
65
|
+
Requires-Dist: sphinx-autobuild>=2025.8.25; extra == "docs"
|
|
66
|
+
Requires-Dist: sphinx-autodoc-typehints>=3.10.2; extra == "docs"
|
|
67
67
|
Dynamic: license-file
|
|
68
68
|
|
|
69
69
|
Psycopg 3: PostgreSQL database adapter for Python
|
|
@@ -8,16 +8,16 @@ tzdata
|
|
|
8
8
|
[binary]
|
|
9
9
|
|
|
10
10
|
[binary:implementation_name != "pypy"]
|
|
11
|
-
psycopg-binary==3.3.
|
|
11
|
+
psycopg-binary==3.3.4
|
|
12
12
|
|
|
13
13
|
[c]
|
|
14
14
|
|
|
15
15
|
[c:implementation_name != "pypy"]
|
|
16
|
-
psycopg-c==3.3.
|
|
16
|
+
psycopg-c==3.3.4
|
|
17
17
|
|
|
18
18
|
[dev]
|
|
19
19
|
ast-comments>=1.1.2
|
|
20
|
-
black>=
|
|
20
|
+
black>=26.1.0
|
|
21
21
|
codespell>=2.2
|
|
22
22
|
cython-lint>=0.16
|
|
23
23
|
dnspython>=2.1
|
|
@@ -31,10 +31,10 @@ types-shapely>=2.0
|
|
|
31
31
|
wheel>=0.37
|
|
32
32
|
|
|
33
33
|
[docs]
|
|
34
|
-
Sphinx>=
|
|
35
|
-
furo==
|
|
36
|
-
sphinx-autobuild>=
|
|
37
|
-
sphinx-autodoc-typehints>=
|
|
34
|
+
Sphinx>=9.1
|
|
35
|
+
furo==2025.12.19
|
|
36
|
+
sphinx-autobuild>=2025.8.25
|
|
37
|
+
sphinx-autodoc-typehints>=3.10.2
|
|
38
38
|
|
|
39
39
|
[pool]
|
|
40
40
|
psycopg-pool
|
|
@@ -7,7 +7,7 @@ name = "psycopg"
|
|
|
7
7
|
description = "PostgreSQL database adapter for Python"
|
|
8
8
|
|
|
9
9
|
# STOP AND READ! if you change:
|
|
10
|
-
version = "3.3.
|
|
10
|
+
version = "3.3.4"
|
|
11
11
|
# also change:
|
|
12
12
|
# - `docs/news.rst` to declare this as the current version or an unreleased one;
|
|
13
13
|
# - `psycopg_c/pyproject.toml` to the same version;
|
|
@@ -60,10 +60,10 @@ content-type = "text/x-rst"
|
|
|
60
60
|
|
|
61
61
|
[project.optional-dependencies]
|
|
62
62
|
c = [
|
|
63
|
-
"psycopg-c == 3.3.
|
|
63
|
+
"psycopg-c == 3.3.4; implementation_name != \"pypy\"",
|
|
64
64
|
]
|
|
65
65
|
binary = [
|
|
66
|
-
"psycopg-binary == 3.3.
|
|
66
|
+
"psycopg-binary == 3.3.4; implementation_name != \"pypy\"",
|
|
67
67
|
]
|
|
68
68
|
pool = [
|
|
69
69
|
"psycopg-pool",
|
|
@@ -79,7 +79,7 @@ test = [
|
|
|
79
79
|
]
|
|
80
80
|
dev = [
|
|
81
81
|
"ast-comments >= 1.1.2",
|
|
82
|
-
"black >=
|
|
82
|
+
"black >= 26.1.0",
|
|
83
83
|
"codespell >= 2.2",
|
|
84
84
|
"cython-lint >= 0.16",
|
|
85
85
|
"dnspython >= 2.1",
|
|
@@ -93,10 +93,10 @@ dev = [
|
|
|
93
93
|
"wheel >= 0.37",
|
|
94
94
|
]
|
|
95
95
|
docs = [
|
|
96
|
-
"Sphinx >=
|
|
97
|
-
"furo ==
|
|
98
|
-
"sphinx-autobuild >=
|
|
99
|
-
"sphinx-autodoc-typehints >=
|
|
96
|
+
"Sphinx >= 9.1",
|
|
97
|
+
"furo == 2025.12.19",
|
|
98
|
+
"sphinx-autobuild >= 2025.8.25",
|
|
99
|
+
"sphinx-autodoc-typehints >= 3.10.2",
|
|
100
100
|
]
|
|
101
101
|
|
|
102
102
|
[tool.setuptools]
|
|
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
|
|
File without changes
|
|
File without changes
|