Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

193 statements  

1# dialects/postgresql/psycopg2.py 

2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7# mypy: ignore-errors 

8 

9r""" 

10.. dialect:: postgresql+psycopg2 

11 :name: psycopg2 

12 :dbapi: psycopg2 

13 :connectstring: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...] 

14 :url: https://pypi.org/project/psycopg2/ 

15 

16.. _psycopg2_toplevel: 

17 

18psycopg2 Connect Arguments 

19-------------------------- 

20 

21Keyword arguments that are specific to the SQLAlchemy psycopg2 dialect 

22may be passed to :func:`_sa.create_engine()`, and include the following: 

23 

24 

25* ``isolation_level``: This option, available for all PostgreSQL dialects, 

26 includes the ``AUTOCOMMIT`` isolation level when using the psycopg2 

27 dialect. This option sets the **default** isolation level for the 

28 connection that is set immediately upon connection to the database before 

29 the connection is pooled. This option is generally superseded by the more 

30 modern :paramref:`_engine.Connection.execution_options.isolation_level` 

31 execution option, detailed at :ref:`dbapi_autocommit`. 

32 

33 .. seealso:: 

34 

35 :ref:`psycopg2_isolation_level` 

36 

37 :ref:`dbapi_autocommit` 

38 

39 

40* ``client_encoding``: sets the client encoding in a libpq-agnostic way, 

41 using psycopg2's ``set_client_encoding()`` method. 

42 

43 .. seealso:: 

44 

45 :ref:`psycopg2_unicode` 

46 

47 

48* ``executemany_mode``, ``executemany_batch_page_size``, 

49 ``executemany_values_page_size``: Allows use of psycopg2 

50 extensions for optimizing "executemany"-style queries. See the referenced 

51 section below for details. 

52 

53 .. seealso:: 

54 

55 :ref:`psycopg2_executemany_mode` 

56 

57.. tip:: 

58 

59 The above keyword arguments are **dialect** keyword arguments, meaning 

60 that they are passed as explicit keyword arguments to :func:`_sa.create_engine()`:: 

61 

62 engine = create_engine( 

63 "postgresql+psycopg2://scott:tiger@localhost/test", 

64 isolation_level="SERIALIZABLE", 

65 ) 

66 

67 These should not be confused with **DBAPI** connect arguments, which 

68 are passed as part of the :paramref:`_sa.create_engine.connect_args` 

69 dictionary and/or are passed in the URL query string, as detailed in 

70 the section :ref:`custom_dbapi_args`. 

71 

72.. _psycopg2_ssl: 

73 

74SSL Connections 

75--------------- 

76 

77The psycopg2 module has a connection argument named ``sslmode`` for 

78controlling its behavior regarding secure (SSL) connections. The default is 

79``sslmode=prefer``; it will attempt an SSL connection and if that fails it 

80will fall back to an unencrypted connection. ``sslmode=require`` may be used 

81to ensure that only secure connections are established. Consult the 

82psycopg2 / libpq documentation for further options that are available. 

83 

84Note that ``sslmode`` is specific to psycopg2 so it is included in the 

85connection URI:: 

86 

87 engine = sa.create_engine( 

88 "postgresql+psycopg2://scott:tiger@192.168.0.199:5432/test?sslmode=require" 

89 ) 

90 

91Unix Domain Connections 

92------------------------ 

93 

94psycopg2 supports connecting via Unix domain connections. When the ``host`` 

95portion of the URL is omitted, SQLAlchemy passes ``None`` to psycopg2, 

96which specifies Unix-domain communication rather than TCP/IP communication:: 

97 

98 create_engine("postgresql+psycopg2://user:password@/dbname") 

99 

100By default, the socket file used is to connect to a Unix-domain socket 

101in ``/tmp``, or whatever socket directory was specified when PostgreSQL 

102was built. This value can be overridden by passing a pathname to psycopg2, 

103using ``host`` as an additional keyword argument:: 

104 

105 create_engine( 

106 "postgresql+psycopg2://user:password@/dbname?host=/var/lib/postgresql" 

107 ) 

108 

109.. warning:: The format accepted here allows for a hostname in the main URL 

110 in addition to the "host" query string argument. **When using this URL 

111 format, the initial host is silently ignored**. That is, this URL:: 

112 

113 engine = create_engine( 

114 "postgresql+psycopg2://user:password@myhost1/dbname?host=myhost2" 

115 ) 

116 

117 Above, the hostname ``myhost1`` is **silently ignored and discarded.** The 

118 host which is connected is the ``myhost2`` host. 

119 

120 This is to maintain some degree of compatibility with PostgreSQL's own URL 

121 format which has been tested to behave the same way and for which tools like 

122 PifPaf hardcode two hostnames. 

123 

124.. seealso:: 

125 

126 `PQconnectdbParams \ 

127 <https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS>`_ 

128 

129.. _psycopg2_multi_host: 

130 

131Specifying multiple fallback hosts 

132----------------------------------- 

133 

134psycopg2 supports multiple connection points in the connection string. 

135When the ``host`` parameter is used multiple times in the query section of 

136the URL, SQLAlchemy will create a single string of the host and port 

137information provided to make the connections. Tokens may consist of 

138``host::port`` or just ``host``; in the latter case, the default port 

139is selected by libpq. In the example below, three host connections 

140are specified, for ``HostA::PortA``, ``HostB`` connecting to the default port, 

141and ``HostC::PortC``:: 

142 

143 create_engine( 

144 "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC" 

145 ) 

146 

147As an alternative, libpq query string format also may be used; this specifies 

148``host`` and ``port`` as single query string arguments with comma-separated 

149lists - the default port can be chosen by indicating an empty value 

150in the comma separated list:: 

151 

152 create_engine( 

153 "postgresql+psycopg2://user:password@/dbname?host=HostA,HostB,HostC&port=PortA,,PortC" 

154 ) 

155 

156With either URL style, connections to each host is attempted based on a 

157configurable strategy, which may be configured using the libpq 

158``target_session_attrs`` parameter. Per libpq this defaults to ``any`` 

159which indicates a connection to each host is then attempted until a connection is successful. 

160Other strategies include ``primary``, ``prefer-standby``, etc. The complete 

161list is documented by PostgreSQL at 

162`libpq connection strings <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_. 

163 

164For example, to indicate two hosts using the ``primary`` strategy:: 

165 

166 create_engine( 

167 "postgresql+psycopg2://user:password@/dbname?host=HostA:PortA&host=HostB&host=HostC:PortC&target_session_attrs=primary" 

168 ) 

169 

170.. versionchanged:: 1.4.40 Port specification in psycopg2 multiple host format 

171 is repaired, previously ports were not correctly interpreted in this context. 

172 libpq comma-separated format is also now supported. 

173 

174.. versionadded:: 1.3.20 Support for multiple hosts in PostgreSQL connection 

175 string. 

176 

177.. seealso:: 

178 

179 `libpq connection strings <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_ - please refer 

180 to this section in the libpq documentation for complete background on multiple host support. 

181 

182 

183Empty DSN Connections / Environment Variable Connections 

184--------------------------------------------------------- 

185 

186The psycopg2 DBAPI can connect to PostgreSQL by passing an empty DSN to the 

187libpq client library, which by default indicates to connect to a localhost 

188PostgreSQL database that is open for "trust" connections. This behavior can be 

189further tailored using a particular set of environment variables which are 

190prefixed with ``PG_...``, which are consumed by ``libpq`` to take the place of 

191any or all elements of the connection string. 

192 

193For this form, the URL can be passed without any elements other than the 

194initial scheme:: 

195 

196 engine = create_engine("postgresql+psycopg2://") 

197 

198In the above form, a blank "dsn" string is passed to the ``psycopg2.connect()`` 

199function which in turn represents an empty DSN passed to libpq. 

200 

201.. versionadded:: 1.3.2 support for parameter-less connections with psycopg2. 

202 

203.. seealso:: 

204 

205 `Environment Variables\ 

206 <https://www.postgresql.org/docs/current/libpq-envars.html>`_ - 

207 PostgreSQL documentation on how to use ``PG_...`` 

208 environment variables for connections. 

209 

210.. _psycopg2_execution_options: 

211 

212Per-Statement/Connection Execution Options 

213------------------------------------------- 

214 

215The following DBAPI-specific options are respected when used with 

216:meth:`_engine.Connection.execution_options`, 

217:meth:`.Executable.execution_options`, 

218:meth:`_query.Query.execution_options`, 

219in addition to those not specific to DBAPIs: 

220 

221* ``isolation_level`` - Set the transaction isolation level for the lifespan 

222 of a :class:`_engine.Connection` (can only be set on a connection, 

223 not a statement 

224 or query). See :ref:`psycopg2_isolation_level`. 

225 

226* ``stream_results`` - Enable or disable usage of psycopg2 server side 

227 cursors - this feature makes use of "named" cursors in combination with 

228 special result handling methods so that result rows are not fully buffered. 

229 Defaults to False, meaning cursors are buffered by default. 

230 

231* ``max_row_buffer`` - when using ``stream_results``, an integer value that 

232 specifies the maximum number of rows to buffer at a time. This is 

233 interpreted by the :class:`.BufferedRowCursorResult`, and if omitted the 

234 buffer will grow to ultimately store 1000 rows at a time. 

235 

236 .. versionchanged:: 1.4 The ``max_row_buffer`` size can now be greater than 

237 1000, and the buffer will grow to that size. 

238 

239.. _psycopg2_batch_mode: 

240 

241.. _psycopg2_executemany_mode: 

242 

243Psycopg2 Fast Execution Helpers 

244------------------------------- 

245 

246Modern versions of psycopg2 include a feature known as 

247`Fast Execution Helpers \ 

248<https://www.psycopg.org/docs/extras.html#fast-execution-helpers>`_, which 

249have been shown in benchmarking to improve psycopg2's executemany() 

250performance, primarily with INSERT statements, by at least 

251an order of magnitude. 

252 

253SQLAlchemy implements a native form of the "insert many values" 

254handler that will rewrite a single-row INSERT statement to accommodate for 

255many values at once within an extended VALUES clause; this handler is 

256equivalent to psycopg2's ``execute_values()`` handler; an overview of this 

257feature and its configuration are at :ref:`engine_insertmanyvalues`. 

258 

259.. versionadded:: 2.0 Replaced psycopg2's ``execute_values()`` fast execution 

260 helper with a native SQLAlchemy mechanism known as 

261 :ref:`insertmanyvalues <engine_insertmanyvalues>`. 

262 

263The psycopg2 dialect retains the ability to use the psycopg2-specific 

264``execute_batch()`` feature, although it is not expected that this is a widely 

265used feature. The use of this extension may be enabled using the 

266``executemany_mode`` flag which may be passed to :func:`_sa.create_engine`:: 

267 

268 engine = create_engine( 

269 "postgresql+psycopg2://scott:tiger@host/dbname", 

270 executemany_mode="values_plus_batch", 

271 ) 

272 

273Possible options for ``executemany_mode`` include: 

274 

275* ``values_only`` - this is the default value. SQLAlchemy's native 

276 :ref:`insertmanyvalues <engine_insertmanyvalues>` handler is used for qualifying 

277 INSERT statements, assuming 

278 :paramref:`_sa.create_engine.use_insertmanyvalues` is left at 

279 its default value of ``True``. This handler rewrites simple 

280 INSERT statements to include multiple VALUES clauses so that many 

281 parameter sets can be inserted with one statement. 

282 

283* ``'values_plus_batch'``- SQLAlchemy's native 

284 :ref:`insertmanyvalues <engine_insertmanyvalues>` handler is used for qualifying 

285 INSERT statements, assuming 

286 :paramref:`_sa.create_engine.use_insertmanyvalues` is left at its default 

287 value of ``True``. Then, psycopg2's ``execute_batch()`` handler is used for 

288 qualifying UPDATE and DELETE statements when executed with multiple parameter 

289 sets. When using this mode, the :attr:`_engine.CursorResult.rowcount` 

290 attribute will not contain a value for executemany-style executions against 

291 UPDATE and DELETE statements. 

292 

293.. versionchanged:: 2.0 Removed the ``'batch'`` and ``'None'`` options 

294 from psycopg2 ``executemany_mode``. Control over batching for INSERT 

295 statements is now configured via the 

296 :paramref:`_sa.create_engine.use_insertmanyvalues` engine-level parameter. 

297 

298The term "qualifying statements" refers to the statement being executed 

299being a Core :func:`_expression.insert`, :func:`_expression.update` 

300or :func:`_expression.delete` construct, and **not** a plain textual SQL 

301string or one constructed using :func:`_expression.text`. It also may **not** be 

302a special "extension" statement such as an "ON CONFLICT" "upsert" statement. 

303When using the ORM, all insert/update/delete statements used by the ORM flush process 

304are qualifying. 

305 

306The "page size" for the psycopg2 "batch" strategy can be affected 

307by using the ``executemany_batch_page_size`` parameter, which defaults to 

308100. 

309 

310For the "insertmanyvalues" feature, the page size can be controlled using the 

311:paramref:`_sa.create_engine.insertmanyvalues_page_size` parameter, 

312which defaults to 1000. An example of modifying both parameters 

313is below:: 

314 

315 engine = create_engine( 

316 "postgresql+psycopg2://scott:tiger@host/dbname", 

317 executemany_mode="values_plus_batch", 

318 insertmanyvalues_page_size=5000, 

319 executemany_batch_page_size=500, 

320 ) 

321 

322.. seealso:: 

323 

324 :ref:`engine_insertmanyvalues` - background on "insertmanyvalues" 

325 

326 :ref:`tutorial_multiple_parameters` - General information on using the 

327 :class:`_engine.Connection` 

328 object to execute statements in such a way as to make 

329 use of the DBAPI ``.executemany()`` method. 

330 

331 

332.. _psycopg2_unicode: 

333 

334Unicode with Psycopg2 

335---------------------- 

336 

337The psycopg2 DBAPI driver supports Unicode data transparently. 

338 

339The client character encoding can be controlled for the psycopg2 dialect 

340in the following ways: 

341 

342* For PostgreSQL 9.1 and above, the ``client_encoding`` parameter may be 

343 passed in the database URL; this parameter is consumed by the underlying 

344 ``libpq`` PostgreSQL client library:: 

345 

346 engine = create_engine( 

347 "postgresql+psycopg2://user:pass@host/dbname?client_encoding=utf8" 

348 ) 

349 

350 Alternatively, the above ``client_encoding`` value may be passed using 

351 :paramref:`_sa.create_engine.connect_args` for programmatic establishment with 

352 ``libpq``:: 

353 

354 engine = create_engine( 

355 "postgresql+psycopg2://user:pass@host/dbname", 

356 connect_args={"client_encoding": "utf8"}, 

357 ) 

358 

359* For all PostgreSQL versions, psycopg2 supports a client-side encoding 

360 value that will be passed to database connections when they are first 

361 established. The SQLAlchemy psycopg2 dialect supports this using the 

362 ``client_encoding`` parameter passed to :func:`_sa.create_engine`:: 

363 

364 engine = create_engine( 

365 "postgresql+psycopg2://user:pass@host/dbname", client_encoding="utf8" 

366 ) 

367 

368 .. tip:: The above ``client_encoding`` parameter admittedly is very similar 

369 in appearance to usage of the parameter within the 

370 :paramref:`_sa.create_engine.connect_args` dictionary; the difference 

371 above is that the parameter is consumed by psycopg2 and is 

372 passed to the database connection using ``SET client_encoding TO 

373 'utf8'``; in the previously mentioned style, the parameter is instead 

374 passed through psycopg2 and consumed by the ``libpq`` library. 

375 

376* A common way to set up client encoding with PostgreSQL databases is to 

377 ensure it is configured within the server-side postgresql.conf file; 

378 this is the recommended way to set encoding for a server that is 

379 consistently of one encoding in all databases:: 

380 

381 # postgresql.conf file 

382 

383 # client_encoding = sql_ascii # actually, defaults to database 

384 # encoding 

385 client_encoding = utf8 

386 

387Transactions 

388------------ 

389 

390The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations. 

391 

392.. _psycopg2_isolation_level: 

393 

394Psycopg2 Transaction Isolation Level 

395------------------------------------- 

396 

397As discussed in :ref:`postgresql_isolation_level`, 

398all PostgreSQL dialects support setting of transaction isolation level 

399both via the ``isolation_level`` parameter passed to :func:`_sa.create_engine` 

400, 

401as well as the ``isolation_level`` argument used by 

402:meth:`_engine.Connection.execution_options`. When using the psycopg2 dialect 

403, these 

404options make use of psycopg2's ``set_isolation_level()`` connection method, 

405rather than emitting a PostgreSQL directive; this is because psycopg2's 

406API-level setting is always emitted at the start of each transaction in any 

407case. 

408 

409The psycopg2 dialect supports these constants for isolation level: 

410 

411* ``READ COMMITTED`` 

412* ``READ UNCOMMITTED`` 

413* ``REPEATABLE READ`` 

414* ``SERIALIZABLE`` 

415* ``AUTOCOMMIT`` 

416 

417.. seealso:: 

418 

419 :ref:`postgresql_isolation_level` 

420 

421 :ref:`pg8000_isolation_level` 

422 

423 

424NOTICE logging 

425--------------- 

426 

427The psycopg2 dialect will log PostgreSQL NOTICE messages 

428via the ``sqlalchemy.dialects.postgresql`` logger. When this logger 

429is set to the ``logging.INFO`` level, notice messages will be logged:: 

430 

431 import logging 

432 

433 logging.getLogger("sqlalchemy.dialects.postgresql").setLevel(logging.INFO) 

434 

435Above, it is assumed that logging is configured externally. If this is not 

436the case, configuration such as ``logging.basicConfig()`` must be utilized:: 

437 

438 import logging 

439 

440 logging.basicConfig() # log messages to stdout 

441 logging.getLogger("sqlalchemy.dialects.postgresql").setLevel(logging.INFO) 

442 

443.. seealso:: 

444 

445 `Logging HOWTO <https://docs.python.org/3/howto/logging.html>`_ - on the python.org website 

446 

447.. _psycopg2_hstore: 

448 

449HSTORE type 

450------------ 

451 

452The ``psycopg2`` DBAPI includes an extension to natively handle marshalling of 

453the HSTORE type. The SQLAlchemy psycopg2 dialect will enable this extension 

454by default when psycopg2 version 2.4 or greater is used, and 

455it is detected that the target database has the HSTORE type set up for use. 

456In other words, when the dialect makes the first 

457connection, a sequence like the following is performed: 

458 

4591. Request the available HSTORE oids using 

460 ``psycopg2.extras.HstoreAdapter.get_oids()``. 

461 If this function returns a list of HSTORE identifiers, we then determine 

462 that the ``HSTORE`` extension is present. 

463 This function is **skipped** if the version of psycopg2 installed is 

464 less than version 2.4. 

465 

4662. If the ``use_native_hstore`` flag is at its default of ``True``, and 

467 we've detected that ``HSTORE`` oids are available, the 

468 ``psycopg2.extensions.register_hstore()`` extension is invoked for all 

469 connections. 

470 

471The ``register_hstore()`` extension has the effect of **all Python 

472dictionaries being accepted as parameters regardless of the type of target 

473column in SQL**. The dictionaries are converted by this extension into a 

474textual HSTORE expression. If this behavior is not desired, disable the 

475use of the hstore extension by setting ``use_native_hstore`` to ``False`` as 

476follows:: 

477 

478 engine = create_engine( 

479 "postgresql+psycopg2://scott:tiger@localhost/test", 

480 use_native_hstore=False, 

481 ) 

482 

483The ``HSTORE`` type is **still supported** when the 

484``psycopg2.extensions.register_hstore()`` extension is not used. It merely 

485means that the coercion between Python dictionaries and the HSTORE 

486string format, on both the parameter side and the result side, will take 

487place within SQLAlchemy's own marshalling logic, and not that of ``psycopg2`` 

488which may be more performant. 

489 

490""" # noqa 

491from __future__ import annotations 

492 

493import collections.abc as collections_abc 

494import logging 

495import re 

496from typing import cast 

497 

498from . import ranges 

499from ._psycopg_common import _PGDialect_common_psycopg 

500from ._psycopg_common import _PGExecutionContext_common_psycopg 

501from .base import PGIdentifierPreparer 

502from .json import JSON 

503from .json import JSONB 

504from ... import types as sqltypes 

505from ... import util 

506from ...util import FastIntFlag 

507from ...util import parse_user_argument_for_enum 

508 

509logger = logging.getLogger("sqlalchemy.dialects.postgresql") 

510 

511 

512class _PGJSON(JSON): 

513 def result_processor(self, dialect, coltype): 

514 return None 

515 

516 

517class _PGJSONB(JSONB): 

518 def result_processor(self, dialect, coltype): 

519 return None 

520 

521 

522class _Psycopg2Range(ranges.AbstractSingleRangeImpl): 

523 _psycopg2_range_cls = "none" 

524 

525 def bind_processor(self, dialect): 

526 psycopg2_Range = getattr( 

527 cast(PGDialect_psycopg2, dialect)._psycopg2_extras, 

528 self._psycopg2_range_cls, 

529 ) 

530 

531 def to_range(value): 

532 if isinstance(value, ranges.Range): 

533 value = psycopg2_Range( 

534 value.lower, value.upper, value.bounds, value.empty 

535 ) 

536 return value 

537 

538 return to_range 

539 

540 def result_processor(self, dialect, coltype): 

541 def to_range(value): 

542 if value is not None: 

543 value = ranges.Range( 

544 value._lower, 

545 value._upper, 

546 bounds=value._bounds if value._bounds else "[)", 

547 empty=not value._bounds, 

548 ) 

549 return value 

550 

551 return to_range 

552 

553 

554class _Psycopg2NumericRange(_Psycopg2Range): 

555 _psycopg2_range_cls = "NumericRange" 

556 

557 

558class _Psycopg2DateRange(_Psycopg2Range): 

559 _psycopg2_range_cls = "DateRange" 

560 

561 

562class _Psycopg2DateTimeRange(_Psycopg2Range): 

563 _psycopg2_range_cls = "DateTimeRange" 

564 

565 

566class _Psycopg2DateTimeTZRange(_Psycopg2Range): 

567 _psycopg2_range_cls = "DateTimeTZRange" 

568 

569 

570class PGExecutionContext_psycopg2(_PGExecutionContext_common_psycopg): 

571 _psycopg2_fetched_rows = None 

572 

573 def post_exec(self): 

574 self._log_notices(self.cursor) 

575 

576 def _log_notices(self, cursor): 

577 # check also that notices is an iterable, after it's already 

578 # established that we will be iterating through it. This is to get 

579 # around test suites such as SQLAlchemy's using a Mock object for 

580 # cursor 

581 if not cursor.connection.notices or not isinstance( 

582 cursor.connection.notices, collections_abc.Iterable 

583 ): 

584 return 

585 

586 for notice in cursor.connection.notices: 

587 # NOTICE messages have a 

588 # newline character at the end 

589 logger.info(notice.rstrip()) 

590 

591 cursor.connection.notices[:] = [] 

592 

593 

594class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer): 

595 pass 

596 

597 

598class ExecutemanyMode(FastIntFlag): 

599 EXECUTEMANY_VALUES = 0 

600 EXECUTEMANY_VALUES_PLUS_BATCH = 1 

601 

602 

603( 

604 EXECUTEMANY_VALUES, 

605 EXECUTEMANY_VALUES_PLUS_BATCH, 

606) = ExecutemanyMode.__members__.values() 

607 

608 

609class PGDialect_psycopg2(_PGDialect_common_psycopg): 

610 driver = "psycopg2" 

611 

612 supports_statement_cache = True 

613 supports_server_side_cursors = True 

614 

615 default_paramstyle = "pyformat" 

616 # set to true based on psycopg2 version 

617 supports_sane_multi_rowcount = False 

618 execution_ctx_cls = PGExecutionContext_psycopg2 

619 preparer = PGIdentifierPreparer_psycopg2 

620 psycopg2_version = (0, 0) 

621 use_insertmanyvalues_wo_returning = True 

622 

623 returns_native_bytes = False 

624 

625 _has_native_hstore = True 

626 

627 colspecs = util.update_copy( 

628 _PGDialect_common_psycopg.colspecs, 

629 { 

630 JSON: _PGJSON, 

631 sqltypes.JSON: _PGJSON, 

632 JSONB: _PGJSONB, 

633 ranges.INT4RANGE: _Psycopg2NumericRange, 

634 ranges.INT8RANGE: _Psycopg2NumericRange, 

635 ranges.NUMRANGE: _Psycopg2NumericRange, 

636 ranges.DATERANGE: _Psycopg2DateRange, 

637 ranges.TSRANGE: _Psycopg2DateTimeRange, 

638 ranges.TSTZRANGE: _Psycopg2DateTimeTZRange, 

639 }, 

640 ) 

641 

642 def __init__( 

643 self, 

644 executemany_mode="values_only", 

645 executemany_batch_page_size=100, 

646 **kwargs, 

647 ): 

648 _PGDialect_common_psycopg.__init__(self, **kwargs) 

649 

650 if self._native_inet_types: 

651 raise NotImplementedError( 

652 "The psycopg2 dialect does not implement " 

653 "ipaddress type handling; native_inet_types cannot be set " 

654 "to ``True`` when using this dialect." 

655 ) 

656 

657 # Parse executemany_mode argument, allowing it to be only one of the 

658 # symbol names 

659 self.executemany_mode = parse_user_argument_for_enum( 

660 executemany_mode, 

661 { 

662 EXECUTEMANY_VALUES: ["values_only"], 

663 EXECUTEMANY_VALUES_PLUS_BATCH: ["values_plus_batch"], 

664 }, 

665 "executemany_mode", 

666 ) 

667 

668 self.executemany_batch_page_size = executemany_batch_page_size 

669 

670 if self.dbapi and hasattr(self.dbapi, "__version__"): 

671 m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__) 

672 if m: 

673 self.psycopg2_version = tuple( 

674 int(x) for x in m.group(1, 2, 3) if x is not None 

675 ) 

676 

677 if self.psycopg2_version < (2, 7): 

678 raise ImportError( 

679 "psycopg2 version 2.7 or higher is required." 

680 ) 

681 

682 def initialize(self, connection): 

683 super().initialize(connection) 

684 self._has_native_hstore = ( 

685 self.use_native_hstore 

686 and self._hstore_oids(connection.connection.dbapi_connection) 

687 is not None 

688 ) 

689 

690 self.supports_sane_multi_rowcount = ( 

691 self.executemany_mode is not EXECUTEMANY_VALUES_PLUS_BATCH 

692 ) 

693 

694 @classmethod 

695 def import_dbapi(cls): 

696 import psycopg2 

697 

698 return psycopg2 

699 

700 @util.memoized_property 

701 def _psycopg2_extensions(cls): 

702 from psycopg2 import extensions 

703 

704 return extensions 

705 

706 @util.memoized_property 

707 def _psycopg2_extras(cls): 

708 from psycopg2 import extras 

709 

710 return extras 

711 

712 @util.memoized_property 

713 def _isolation_lookup(self): 

714 extensions = self._psycopg2_extensions 

715 return { 

716 "AUTOCOMMIT": extensions.ISOLATION_LEVEL_AUTOCOMMIT, 

717 "READ COMMITTED": extensions.ISOLATION_LEVEL_READ_COMMITTED, 

718 "READ UNCOMMITTED": extensions.ISOLATION_LEVEL_READ_UNCOMMITTED, 

719 "REPEATABLE READ": extensions.ISOLATION_LEVEL_REPEATABLE_READ, 

720 "SERIALIZABLE": extensions.ISOLATION_LEVEL_SERIALIZABLE, 

721 } 

722 

723 def set_isolation_level(self, dbapi_connection, level): 

724 dbapi_connection.set_isolation_level(self._isolation_lookup[level]) 

725 

726 def set_readonly(self, connection, value): 

727 connection.readonly = value 

728 

729 def get_readonly(self, connection): 

730 return connection.readonly 

731 

732 def set_deferrable(self, connection, value): 

733 connection.deferrable = value 

734 

735 def get_deferrable(self, connection): 

736 return connection.deferrable 

737 

738 def on_connect(self): 

739 extras = self._psycopg2_extras 

740 

741 fns = [] 

742 if self.client_encoding is not None: 

743 

744 def on_connect(dbapi_conn): 

745 dbapi_conn.set_client_encoding(self.client_encoding) 

746 

747 fns.append(on_connect) 

748 

749 if self.dbapi: 

750 

751 def on_connect(dbapi_conn): 

752 extras.register_uuid(None, dbapi_conn) 

753 

754 fns.append(on_connect) 

755 

756 if self.dbapi and self.use_native_hstore: 

757 

758 def on_connect(dbapi_conn): 

759 hstore_oids = self._hstore_oids(dbapi_conn) 

760 if hstore_oids is not None: 

761 oid, array_oid = hstore_oids 

762 kw = {"oid": oid} 

763 kw["array_oid"] = array_oid 

764 extras.register_hstore(dbapi_conn, **kw) 

765 

766 fns.append(on_connect) 

767 

768 if self.dbapi and self._json_deserializer: 

769 

770 def on_connect(dbapi_conn): 

771 extras.register_default_json( 

772 dbapi_conn, loads=self._json_deserializer 

773 ) 

774 extras.register_default_jsonb( 

775 dbapi_conn, loads=self._json_deserializer 

776 ) 

777 

778 fns.append(on_connect) 

779 

780 if fns: 

781 

782 def on_connect(dbapi_conn): 

783 for fn in fns: 

784 fn(dbapi_conn) 

785 

786 return on_connect 

787 else: 

788 return None 

789 

790 def do_executemany(self, cursor, statement, parameters, context=None): 

791 if self.executemany_mode is EXECUTEMANY_VALUES_PLUS_BATCH: 

792 if self.executemany_batch_page_size: 

793 kwargs = {"page_size": self.executemany_batch_page_size} 

794 else: 

795 kwargs = {} 

796 self._psycopg2_extras.execute_batch( 

797 cursor, statement, parameters, **kwargs 

798 ) 

799 else: 

800 cursor.executemany(statement, parameters) 

801 

802 def do_begin_twophase(self, connection, xid): 

803 connection.connection.tpc_begin(xid) 

804 

805 def do_prepare_twophase(self, connection, xid): 

806 connection.connection.tpc_prepare() 

807 

808 def _do_twophase(self, dbapi_conn, operation, xid, recover=False): 

809 if recover: 

810 if dbapi_conn.status != self._psycopg2_extensions.STATUS_READY: 

811 dbapi_conn.rollback() 

812 operation(xid) 

813 else: 

814 operation() 

815 

816 def do_rollback_twophase( 

817 self, connection, xid, is_prepared=True, recover=False 

818 ): 

819 dbapi_conn = connection.connection.dbapi_connection 

820 self._do_twophase( 

821 dbapi_conn, dbapi_conn.tpc_rollback, xid, recover=recover 

822 ) 

823 

824 def do_commit_twophase( 

825 self, connection, xid, is_prepared=True, recover=False 

826 ): 

827 dbapi_conn = connection.connection.dbapi_connection 

828 self._do_twophase( 

829 dbapi_conn, dbapi_conn.tpc_commit, xid, recover=recover 

830 ) 

831 

832 @util.memoized_instancemethod 

833 def _hstore_oids(self, dbapi_connection): 

834 extras = self._psycopg2_extras 

835 oids = extras.HstoreAdapter.get_oids(dbapi_connection) 

836 if oids is not None and oids[0]: 

837 return oids[0:2] 

838 else: 

839 return None 

840 

841 def is_disconnect(self, e, connection, cursor): 

842 if isinstance(e, self.dbapi.Error): 

843 # check the "closed" flag. this might not be 

844 # present on old psycopg2 versions. Also, 

845 # this flag doesn't actually help in a lot of disconnect 

846 # situations, so don't rely on it. 

847 if getattr(connection, "closed", False): 

848 return True 

849 

850 # checks based on strings. in the case that .closed 

851 # didn't cut it, fall back onto these. 

852 str_e = str(e).partition("\n")[0] 

853 for msg in self._is_disconnect_messages: 

854 idx = str_e.find(msg) 

855 if idx >= 0 and '"' not in str_e[:idx]: 

856 return True 

857 return False 

858 

859 @util.memoized_property 

860 def _is_disconnect_messages(self): 

861 return ( 

862 # these error messages from libpq: interfaces/libpq/fe-misc.c 

863 # and interfaces/libpq/fe-secure.c. 

864 "terminating connection", 

865 "closed the connection", 

866 "connection not open", 

867 "could not receive data from server", 

868 "could not send data to server", 

869 # psycopg2 client errors, psycopg2/connection.h, 

870 # psycopg2/cursor.h 

871 "connection already closed", 

872 "cursor already closed", 

873 # not sure where this path is originally from, it may 

874 # be obsolete. It really says "losed", not "closed". 

875 "losed the connection unexpectedly", 

876 # these can occur in newer SSL 

877 "connection has been closed unexpectedly", 

878 "SSL error: decryption failed or bad record mac", 

879 "SSL SYSCALL error: Bad file descriptor", 

880 "SSL SYSCALL error: EOF detected", 

881 "SSL SYSCALL error: Operation timed out", 

882 "SSL SYSCALL error: Bad address", 

883 # This can occur in OpenSSL 1 when an unexpected EOF occurs. 

884 # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html#BUGS 

885 # It may also occur in newer OpenSSL for a non-recoverable I/O 

886 # error as a result of a system call that does not set 'errno' 

887 # in libc. 

888 "SSL SYSCALL error: Success", 

889 ) 

890 

891 

892dialect = PGDialect_psycopg2