psycopg.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. # dialects/postgresql/psycopg.py
  2. # Copyright (C) 2005-2026 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. r"""
  9. .. dialect:: postgresql+psycopg
  10. :name: psycopg (a.k.a. psycopg 3)
  11. :dbapi: psycopg
  12. :connectstring: postgresql+psycopg://user:password@host:port/dbname[?key=value&key=value...]
  13. :url: https://pypi.org/project/psycopg/
  14. ``psycopg`` is the package and module name for version 3 of the ``psycopg``
  15. database driver, formerly known as ``psycopg2``. This driver is different
  16. enough from its ``psycopg2`` predecessor that SQLAlchemy supports it
  17. via a totally separate dialect; support for ``psycopg2`` is expected to remain
  18. for as long as that package continues to function for modern Python versions,
  19. and also remains the default dialect for the ``postgresql://`` dialect
  20. series.
  21. The SQLAlchemy ``psycopg`` dialect provides both a sync and an async
  22. implementation under the same dialect name. The proper version is
  23. selected depending on how the engine is created:
  24. * calling :func:`_sa.create_engine` with ``postgresql+psycopg://...`` will
  25. automatically select the sync version, e.g.::
  26. from sqlalchemy import create_engine
  27. sync_engine = create_engine(
  28. "postgresql+psycopg://scott:tiger@localhost/test"
  29. )
  30. * calling :func:`_asyncio.create_async_engine` with
  31. ``postgresql+psycopg://...`` will automatically select the async version,
  32. e.g.::
  33. from sqlalchemy.ext.asyncio import create_async_engine
  34. asyncio_engine = create_async_engine(
  35. "postgresql+psycopg://scott:tiger@localhost/test"
  36. )
  37. The asyncio version of the dialect may also be specified explicitly using the
  38. ``psycopg_async`` suffix, as::
  39. from sqlalchemy.ext.asyncio import create_async_engine
  40. asyncio_engine = create_async_engine(
  41. "postgresql+psycopg_async://scott:tiger@localhost/test"
  42. )
  43. .. seealso::
  44. :ref:`postgresql_psycopg2` - The SQLAlchemy ``psycopg``
  45. dialect shares most of its behavior with the ``psycopg2`` dialect.
  46. Further documentation is available there.
  47. Using psycopg Connection Pooling
  48. --------------------------------
  49. The ``psycopg`` driver provides its own connection pool implementation that
  50. may be used in place of SQLAlchemy's pooling functionality.
  51. This pool implementation provides support for fixed and dynamic pool sizes
  52. (including automatic downsizing for unused connections), connection health
  53. pre-checks, and support for both synchronous and asynchronous code
  54. environments.
  55. Here is an example that uses the sync version of the pool, using
  56. ``psycopg_pool >= 3.3`` that introduces support for ``close_returns=True``::
  57. import psycopg_pool
  58. from sqlalchemy import create_engine
  59. from sqlalchemy.pool import NullPool
  60. # Create a psycopg_pool connection pool
  61. my_pool = psycopg_pool.ConnectionPool(
  62. conninfo="postgresql://scott:tiger@localhost/test",
  63. close_returns=True, # Return "closed" active connections to the pool
  64. # ... other pool parameters as desired ...
  65. )
  66. # Create an engine that uses the connection pool to get a connection
  67. engine = create_engine(
  68. url="postgresql+psycopg://", # Only need the dialect now
  69. poolclass=NullPool, # Disable SQLAlchemy's default connection pool
  70. creator=my_pool.getconn, # Use Psycopg 3 connection pool to obtain connections
  71. )
  72. Similarly an the async example::
  73. import psycopg_pool
  74. from sqlalchemy.ext.asyncio import create_async_engine
  75. from sqlalchemy.pool import NullPool
  76. async def define_engine():
  77. # Create a psycopg_pool connection pool
  78. my_pool = psycopg_pool.AsyncConnectionPool(
  79. conninfo="postgresql://scott:tiger@localhost/test",
  80. open=False, # See comment below
  81. close_returns=True, # Return "closed" active connections to the pool
  82. # ... other pool parameters as desired ...
  83. )
  84. # Must explicitly open AsyncConnectionPool outside constructor
  85. # https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.AsyncConnectionPool
  86. await my_pool.open()
  87. # Create an engine that uses the connection pool to get a connection
  88. engine = create_async_engine(
  89. url="postgresql+psycopg://", # Only need the dialect now
  90. poolclass=NullPool, # Disable SQLAlchemy's default connection pool
  91. async_creator=my_pool.getconn, # Use Psycopg 3 connection pool to obtain connections
  92. )
  93. return engine, my_pool
  94. The resulting engine may then be used normally. Internally, Psycopg 3 handles
  95. connection pooling::
  96. with engine.connect() as conn:
  97. print(conn.scalar(text("select 42")))
  98. .. seealso::
  99. `Connection pools <https://www.psycopg.org/psycopg3/docs/advanced/pool.html>`_ -
  100. the Psycopg 3 documentation for ``psycopg_pool.ConnectionPool``.
  101. `Example for older version of psycopg_pool
  102. <https://github.com/sqlalchemy/sqlalchemy/discussions/12522#discussioncomment-13024666>`_ -
  103. An example about using the ``psycopg_pool<3.3`` that did not have the
  104. ``close_returns``` parameter.
  105. Using a different Cursor class
  106. ------------------------------
  107. One of the differences between ``psycopg`` and the older ``psycopg2``
  108. is how bound parameters are handled: ``psycopg2`` would bind them
  109. client side, while ``psycopg`` by default will bind them server side.
  110. It's possible to configure ``psycopg`` to do client side binding by
  111. specifying the ``cursor_factory`` to be ``ClientCursor`` when creating
  112. the engine::
  113. from psycopg import ClientCursor
  114. client_side_engine = create_engine(
  115. "postgresql+psycopg://...",
  116. connect_args={"cursor_factory": ClientCursor},
  117. )
  118. Similarly when using an async engine the ``AsyncClientCursor`` can be
  119. specified::
  120. from psycopg import AsyncClientCursor
  121. client_side_engine = create_async_engine(
  122. "postgresql+psycopg://...",
  123. connect_args={"cursor_factory": AsyncClientCursor},
  124. )
  125. .. seealso::
  126. `Client-side-binding cursors <https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#client-side-binding-cursors>`_
  127. """ # noqa
  128. from __future__ import annotations
  129. from collections import deque
  130. import logging
  131. import re
  132. from typing import cast
  133. from typing import TYPE_CHECKING
  134. from . import ranges
  135. from ._psycopg_common import _PGDialect_common_psycopg
  136. from ._psycopg_common import _PGExecutionContext_common_psycopg
  137. from .base import INTERVAL
  138. from .base import PGCompiler
  139. from .base import PGIdentifierPreparer
  140. from .base import REGCONFIG
  141. from .json import JSON
  142. from .json import JSONB
  143. from .json import JSONPathType
  144. from .types import CITEXT
  145. from ... import pool
  146. from ... import util
  147. from ...engine import AdaptedConnection
  148. from ...sql import sqltypes
  149. from ...util.concurrency import await_fallback
  150. from ...util.concurrency import await_only
  151. if TYPE_CHECKING:
  152. from typing import Iterable
  153. from psycopg import AsyncConnection
  154. logger = logging.getLogger("sqlalchemy.dialects.postgresql")
  155. class _PGString(sqltypes.String):
  156. render_bind_cast = True
  157. class _PGREGCONFIG(REGCONFIG):
  158. render_bind_cast = True
  159. class _PGJSON(JSON):
  160. def bind_processor(self, dialect):
  161. return self._make_bind_processor(None, dialect._psycopg_Json)
  162. def result_processor(self, dialect, coltype):
  163. return None
  164. class _PGJSONB(JSONB):
  165. def bind_processor(self, dialect):
  166. return self._make_bind_processor(None, dialect._psycopg_Jsonb)
  167. def result_processor(self, dialect, coltype):
  168. return None
  169. class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
  170. __visit_name__ = "json_int_index"
  171. render_bind_cast = True
  172. class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
  173. __visit_name__ = "json_str_index"
  174. render_bind_cast = True
  175. class _PGJSONPathType(JSONPathType):
  176. pass
  177. class _PGInterval(INTERVAL):
  178. render_bind_cast = True
  179. class _PGTimeStamp(sqltypes.DateTime):
  180. render_bind_cast = True
  181. class _PGDate(sqltypes.Date):
  182. render_bind_cast = True
  183. class _PGTime(sqltypes.Time):
  184. render_bind_cast = True
  185. class _PGInteger(sqltypes.Integer):
  186. render_bind_cast = True
  187. class _PGSmallInteger(sqltypes.SmallInteger):
  188. render_bind_cast = True
  189. class _PGNullType(sqltypes.NullType):
  190. render_bind_cast = True
  191. class _PGBigInteger(sqltypes.BigInteger):
  192. render_bind_cast = True
  193. class _PGBoolean(sqltypes.Boolean):
  194. render_bind_cast = True
  195. class _PsycopgRange(ranges.AbstractSingleRangeImpl):
  196. def bind_processor(self, dialect):
  197. psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
  198. def to_range(value):
  199. if isinstance(value, ranges.Range):
  200. value = psycopg_Range(
  201. value.lower, value.upper, value.bounds, value.empty
  202. )
  203. return value
  204. return to_range
  205. def result_processor(self, dialect, coltype):
  206. def to_range(value):
  207. if value is not None:
  208. value = ranges.Range(
  209. value._lower,
  210. value._upper,
  211. bounds=value._bounds if value._bounds else "[)",
  212. empty=not value._bounds,
  213. )
  214. return value
  215. return to_range
  216. class _PsycopgMultiRange(ranges.AbstractMultiRangeImpl):
  217. def bind_processor(self, dialect):
  218. psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
  219. psycopg_Multirange = cast(
  220. PGDialect_psycopg, dialect
  221. )._psycopg_Multirange
  222. NoneType = type(None)
  223. def to_range(value):
  224. if isinstance(value, (str, NoneType, psycopg_Multirange)):
  225. return value
  226. return psycopg_Multirange(
  227. [
  228. psycopg_Range(
  229. element.lower,
  230. element.upper,
  231. element.bounds,
  232. element.empty,
  233. )
  234. for element in cast("Iterable[ranges.Range]", value)
  235. ]
  236. )
  237. return to_range
  238. def result_processor(self, dialect, coltype):
  239. def to_range(value):
  240. if value is None:
  241. return None
  242. else:
  243. return ranges.MultiRange(
  244. ranges.Range(
  245. elem._lower,
  246. elem._upper,
  247. bounds=elem._bounds if elem._bounds else "[)",
  248. empty=not elem._bounds,
  249. )
  250. for elem in value
  251. )
  252. return to_range
  253. class PGExecutionContext_psycopg(_PGExecutionContext_common_psycopg):
  254. pass
  255. class PGCompiler_psycopg(PGCompiler):
  256. pass
  257. class PGIdentifierPreparer_psycopg(PGIdentifierPreparer):
  258. pass
  259. def _log_notices(diagnostic):
  260. logger.info("%s: %s", diagnostic.severity, diagnostic.message_primary)
  261. class PGDialect_psycopg(_PGDialect_common_psycopg):
  262. driver = "psycopg"
  263. supports_statement_cache = True
  264. supports_server_side_cursors = True
  265. default_paramstyle = "pyformat"
  266. supports_sane_multi_rowcount = True
  267. execution_ctx_cls = PGExecutionContext_psycopg
  268. statement_compiler = PGCompiler_psycopg
  269. preparer = PGIdentifierPreparer_psycopg
  270. psycopg_version = (0, 0)
  271. _has_native_hstore = True
  272. _psycopg_adapters_map = None
  273. colspecs = util.update_copy(
  274. _PGDialect_common_psycopg.colspecs,
  275. {
  276. sqltypes.String: _PGString,
  277. REGCONFIG: _PGREGCONFIG,
  278. JSON: _PGJSON,
  279. CITEXT: CITEXT,
  280. sqltypes.JSON: _PGJSON,
  281. JSONB: _PGJSONB,
  282. sqltypes.JSON.JSONPathType: _PGJSONPathType,
  283. sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
  284. sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
  285. sqltypes.Interval: _PGInterval,
  286. INTERVAL: _PGInterval,
  287. sqltypes.Date: _PGDate,
  288. sqltypes.DateTime: _PGTimeStamp,
  289. sqltypes.Time: _PGTime,
  290. sqltypes.Integer: _PGInteger,
  291. sqltypes.SmallInteger: _PGSmallInteger,
  292. sqltypes.BigInteger: _PGBigInteger,
  293. ranges.AbstractSingleRange: _PsycopgRange,
  294. ranges.AbstractMultiRange: _PsycopgMultiRange,
  295. },
  296. )
  297. def __init__(self, **kwargs):
  298. super().__init__(**kwargs)
  299. if self.dbapi:
  300. m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
  301. if m:
  302. self.psycopg_version = tuple(
  303. int(x) for x in m.group(1, 2, 3) if x is not None
  304. )
  305. if self.psycopg_version < (3, 0, 2):
  306. raise ImportError(
  307. "psycopg version 3.0.2 or higher is required."
  308. )
  309. from psycopg.adapt import AdaptersMap
  310. self._psycopg_adapters_map = adapters_map = AdaptersMap(
  311. self.dbapi.adapters
  312. )
  313. if self._native_inet_types is False:
  314. import psycopg.types.string
  315. adapters_map.register_loader(
  316. "inet", psycopg.types.string.TextLoader
  317. )
  318. adapters_map.register_loader(
  319. "cidr", psycopg.types.string.TextLoader
  320. )
  321. if self._json_deserializer:
  322. from psycopg.types.json import set_json_loads
  323. set_json_loads(self._json_deserializer, adapters_map)
  324. if self._json_serializer:
  325. from psycopg.types.json import set_json_dumps
  326. set_json_dumps(self._json_serializer, adapters_map)
  327. def create_connect_args(self, url):
  328. # see https://github.com/psycopg/psycopg/issues/83
  329. cargs, cparams = super().create_connect_args(url)
  330. if self._psycopg_adapters_map:
  331. cparams["context"] = self._psycopg_adapters_map
  332. if self.client_encoding is not None:
  333. cparams["client_encoding"] = self.client_encoding
  334. return cargs, cparams
  335. def _type_info_fetch(self, connection, name):
  336. from psycopg.types import TypeInfo
  337. return TypeInfo.fetch(connection.connection.driver_connection, name)
  338. def initialize(self, connection):
  339. super().initialize(connection)
  340. # PGDialect.initialize() checks server version for <= 8.2 and sets
  341. # this flag to False if so
  342. if not self.insert_returning:
  343. self.insert_executemany_returning = False
  344. # HSTORE can't be registered until we have a connection so that
  345. # we can look up its OID, so we set up this adapter in
  346. # initialize()
  347. if self.use_native_hstore:
  348. info = self._type_info_fetch(connection, "hstore")
  349. self._has_native_hstore = info is not None
  350. if self._has_native_hstore:
  351. from psycopg.types.hstore import register_hstore
  352. # register the adapter for connections made subsequent to
  353. # this one
  354. assert self._psycopg_adapters_map
  355. register_hstore(info, self._psycopg_adapters_map)
  356. # register the adapter for this connection
  357. assert connection.connection
  358. register_hstore(info, connection.connection.driver_connection)
  359. @classmethod
  360. def import_dbapi(cls):
  361. import psycopg
  362. return psycopg
  363. @classmethod
  364. def get_async_dialect_cls(cls, url):
  365. return PGDialectAsync_psycopg
  366. @util.memoized_property
  367. def _isolation_lookup(self):
  368. return {
  369. "READ COMMITTED": self.dbapi.IsolationLevel.READ_COMMITTED,
  370. "READ UNCOMMITTED": self.dbapi.IsolationLevel.READ_UNCOMMITTED,
  371. "REPEATABLE READ": self.dbapi.IsolationLevel.REPEATABLE_READ,
  372. "SERIALIZABLE": self.dbapi.IsolationLevel.SERIALIZABLE,
  373. }
  374. @util.memoized_property
  375. def _psycopg_Json(self):
  376. from psycopg.types import json
  377. return json.Json
  378. @util.memoized_property
  379. def _psycopg_Jsonb(self):
  380. from psycopg.types import json
  381. return json.Jsonb
  382. @util.memoized_property
  383. def _psycopg_TransactionStatus(self):
  384. from psycopg.pq import TransactionStatus
  385. return TransactionStatus
  386. @util.memoized_property
  387. def _psycopg_Range(self):
  388. from psycopg.types.range import Range
  389. return Range
  390. @util.memoized_property
  391. def _psycopg_Multirange(self):
  392. from psycopg.types.multirange import Multirange
  393. return Multirange
  394. def _do_isolation_level(self, connection, autocommit, isolation_level):
  395. connection.autocommit = autocommit
  396. connection.isolation_level = isolation_level
  397. def get_isolation_level(self, dbapi_connection):
  398. status_before = dbapi_connection.info.transaction_status
  399. value = super().get_isolation_level(dbapi_connection)
  400. # don't rely on psycopg providing enum symbols, compare with
  401. # eq/ne
  402. if status_before == self._psycopg_TransactionStatus.IDLE:
  403. dbapi_connection.rollback()
  404. return value
  405. def set_isolation_level(self, dbapi_connection, level):
  406. if level == "AUTOCOMMIT":
  407. self._do_isolation_level(
  408. dbapi_connection, autocommit=True, isolation_level=None
  409. )
  410. else:
  411. self._do_isolation_level(
  412. dbapi_connection,
  413. autocommit=False,
  414. isolation_level=self._isolation_lookup[level],
  415. )
  416. def set_readonly(self, connection, value):
  417. connection.read_only = value
  418. def get_readonly(self, connection):
  419. return connection.read_only
  420. def on_connect(self):
  421. def notices(conn):
  422. conn.add_notice_handler(_log_notices)
  423. fns = [notices]
  424. if self.isolation_level is not None:
  425. def on_connect(conn):
  426. self.set_isolation_level(conn, self.isolation_level)
  427. fns.append(on_connect)
  428. # fns always has the notices function
  429. def on_connect(conn):
  430. for fn in fns:
  431. fn(conn)
  432. return on_connect
  433. def is_disconnect(self, e, connection, cursor):
  434. if isinstance(e, self.dbapi.Error) and connection is not None:
  435. if connection.closed or connection.broken:
  436. return True
  437. return False
  438. def _do_prepared_twophase(self, connection, command, recover=False):
  439. dbapi_conn = connection.connection.dbapi_connection
  440. if (
  441. recover
  442. # don't rely on psycopg providing enum symbols, compare with
  443. # eq/ne
  444. or dbapi_conn.info.transaction_status
  445. != self._psycopg_TransactionStatus.IDLE
  446. ):
  447. dbapi_conn.rollback()
  448. before_autocommit = dbapi_conn.autocommit
  449. try:
  450. if not before_autocommit:
  451. self._do_autocommit(dbapi_conn, True)
  452. dbapi_conn.execute(command)
  453. finally:
  454. if not before_autocommit:
  455. self._do_autocommit(dbapi_conn, before_autocommit)
  456. def do_rollback_twophase(
  457. self, connection, xid, is_prepared=True, recover=False
  458. ):
  459. if is_prepared:
  460. self._do_prepared_twophase(
  461. connection, f"ROLLBACK PREPARED '{xid}'", recover=recover
  462. )
  463. else:
  464. self.do_rollback(connection.connection)
  465. def do_commit_twophase(
  466. self, connection, xid, is_prepared=True, recover=False
  467. ):
  468. if is_prepared:
  469. self._do_prepared_twophase(
  470. connection, f"COMMIT PREPARED '{xid}'", recover=recover
  471. )
  472. else:
  473. self.do_commit(connection.connection)
  474. @util.memoized_property
  475. def _dialect_specific_select_one(self):
  476. return ";"
  477. class AsyncAdapt_psycopg_cursor:
  478. __slots__ = ("_cursor", "await_", "_rows")
  479. _psycopg_ExecStatus = None
  480. def __init__(self, cursor, await_) -> None:
  481. self._cursor = cursor
  482. self.await_ = await_
  483. self._rows = deque()
  484. def __getattr__(self, name):
  485. return getattr(self._cursor, name)
  486. @property
  487. def arraysize(self):
  488. return self._cursor.arraysize
  489. @arraysize.setter
  490. def arraysize(self, value):
  491. self._cursor.arraysize = value
  492. async def _async_soft_close(self) -> None:
  493. return
  494. def close(self):
  495. self._rows.clear()
  496. # Normal cursor just call _close() in a non-sync way.
  497. self._cursor._close()
  498. def execute(self, query, params=None, **kw):
  499. result = self.await_(self._cursor.execute(query, params, **kw))
  500. # sqlalchemy result is not async, so need to pull all rows here
  501. res = self._cursor.pgresult
  502. # don't rely on psycopg providing enum symbols, compare with
  503. # eq/ne
  504. if res and res.status == self._psycopg_ExecStatus.TUPLES_OK:
  505. rows = self.await_(self._cursor.fetchall())
  506. self._rows = deque(rows)
  507. return result
  508. def executemany(self, query, params_seq):
  509. return self.await_(self._cursor.executemany(query, params_seq))
  510. def __iter__(self):
  511. while self._rows:
  512. yield self._rows.popleft()
  513. def fetchone(self):
  514. if self._rows:
  515. return self._rows.popleft()
  516. else:
  517. return None
  518. def fetchmany(self, size=None):
  519. if size is None:
  520. size = self._cursor.arraysize
  521. rr = self._rows
  522. return [rr.popleft() for _ in range(min(size, len(rr)))]
  523. def fetchall(self):
  524. retval = list(self._rows)
  525. self._rows.clear()
  526. return retval
  527. class AsyncAdapt_psycopg_ss_cursor(AsyncAdapt_psycopg_cursor):
  528. def execute(self, query, params=None, **kw):
  529. self.await_(self._cursor.execute(query, params, **kw))
  530. return self
  531. def close(self):
  532. self.await_(self._cursor.close())
  533. def fetchone(self):
  534. return self.await_(self._cursor.fetchone())
  535. def fetchmany(self, size=0):
  536. return self.await_(self._cursor.fetchmany(size))
  537. def fetchall(self):
  538. return self.await_(self._cursor.fetchall())
  539. def __iter__(self):
  540. iterator = self._cursor.__aiter__()
  541. while True:
  542. try:
  543. yield self.await_(iterator.__anext__())
  544. except StopAsyncIteration:
  545. break
  546. class AsyncAdapt_psycopg_connection(AdaptedConnection):
  547. _connection: AsyncConnection
  548. __slots__ = ()
  549. await_ = staticmethod(await_only)
  550. def __init__(self, connection) -> None:
  551. self._connection = connection
  552. def __getattr__(self, name):
  553. return getattr(self._connection, name)
  554. def execute(self, query, params=None, **kw):
  555. cursor = self.await_(self._connection.execute(query, params, **kw))
  556. return AsyncAdapt_psycopg_cursor(cursor, self.await_)
  557. def cursor(self, *args, **kw):
  558. cursor = self._connection.cursor(*args, **kw)
  559. if hasattr(cursor, "name"):
  560. return AsyncAdapt_psycopg_ss_cursor(cursor, self.await_)
  561. else:
  562. return AsyncAdapt_psycopg_cursor(cursor, self.await_)
  563. def commit(self):
  564. self.await_(self._connection.commit())
  565. def rollback(self):
  566. self.await_(self._connection.rollback())
  567. def close(self):
  568. self.await_(self._connection.close())
  569. @property
  570. def autocommit(self):
  571. return self._connection.autocommit
  572. @autocommit.setter
  573. def autocommit(self, value):
  574. self.set_autocommit(value)
  575. def set_autocommit(self, value):
  576. self.await_(self._connection.set_autocommit(value))
  577. def set_isolation_level(self, value):
  578. self.await_(self._connection.set_isolation_level(value))
  579. def set_read_only(self, value):
  580. self.await_(self._connection.set_read_only(value))
  581. def set_deferrable(self, value):
  582. self.await_(self._connection.set_deferrable(value))
  583. class AsyncAdaptFallback_psycopg_connection(AsyncAdapt_psycopg_connection):
  584. __slots__ = ()
  585. await_ = staticmethod(await_fallback)
  586. class PsycopgAdaptDBAPI:
  587. def __init__(self, psycopg) -> None:
  588. self.psycopg = psycopg
  589. for k, v in self.psycopg.__dict__.items():
  590. if k != "connect":
  591. self.__dict__[k] = v
  592. def connect(self, *arg, **kw):
  593. async_fallback = kw.pop("async_fallback", False)
  594. creator_fn = kw.pop(
  595. "async_creator_fn", self.psycopg.AsyncConnection.connect
  596. )
  597. if util.asbool(async_fallback):
  598. return AsyncAdaptFallback_psycopg_connection(
  599. await_fallback(creator_fn(*arg, **kw))
  600. )
  601. else:
  602. return AsyncAdapt_psycopg_connection(
  603. await_only(creator_fn(*arg, **kw))
  604. )
  605. class PGDialectAsync_psycopg(PGDialect_psycopg):
  606. is_async = True
  607. supports_statement_cache = True
  608. @classmethod
  609. def import_dbapi(cls):
  610. import psycopg
  611. from psycopg.pq import ExecStatus
  612. AsyncAdapt_psycopg_cursor._psycopg_ExecStatus = ExecStatus
  613. return PsycopgAdaptDBAPI(psycopg)
  614. @classmethod
  615. def get_pool_class(cls, url):
  616. async_fallback = url.query.get("async_fallback", False)
  617. if util.asbool(async_fallback):
  618. return pool.FallbackAsyncAdaptedQueuePool
  619. else:
  620. return pool.AsyncAdaptedQueuePool
  621. def _type_info_fetch(self, connection, name):
  622. from psycopg.types import TypeInfo
  623. adapted = connection.connection
  624. return adapted.await_(TypeInfo.fetch(adapted.driver_connection, name))
  625. def _do_isolation_level(self, connection, autocommit, isolation_level):
  626. connection.set_autocommit(autocommit)
  627. connection.set_isolation_level(isolation_level)
  628. def _do_autocommit(self, connection, value):
  629. connection.set_autocommit(value)
  630. def set_readonly(self, connection, value):
  631. connection.set_read_only(value)
  632. def set_deferrable(self, connection, value):
  633. connection.set_deferrable(value)
  634. def get_driver_connection(self, connection):
  635. return connection._connection
  636. dialect = PGDialect_psycopg
  637. dialect_async = PGDialectAsync_psycopg