SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,862 @@
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
+
9
+ r"""
10
+ .. dialect:: postgresql+psycopg
11
+ :name: psycopg (a.k.a. psycopg 3)
12
+ :dbapi: psycopg
13
+ :connectstring: postgresql+psycopg://user:password@host:port/dbname[?key=value&key=value...]
14
+ :url: https://pypi.org/project/psycopg/
15
+
16
+ ``psycopg`` is the package and module name for version 3 of the ``psycopg``
17
+ database driver, formerly known as ``psycopg2``. This driver is different
18
+ enough from its ``psycopg2`` predecessor that SQLAlchemy supports it
19
+ via a totally separate dialect; support for ``psycopg2`` is expected to remain
20
+ for as long as that package continues to function for modern Python versions,
21
+ and also remains the default dialect for the ``postgresql://`` dialect
22
+ series.
23
+
24
+ The SQLAlchemy ``psycopg`` dialect provides both a sync and an async
25
+ implementation under the same dialect name. The proper version is
26
+ selected depending on how the engine is created:
27
+
28
+ * calling :func:`_sa.create_engine` with ``postgresql+psycopg://...`` will
29
+ automatically select the sync version, e.g.::
30
+
31
+ from sqlalchemy import create_engine
32
+
33
+ sync_engine = create_engine(
34
+ "postgresql+psycopg://scott:tiger@localhost/test"
35
+ )
36
+
37
+ * calling :func:`_asyncio.create_async_engine` with
38
+ ``postgresql+psycopg://...`` will automatically select the async version,
39
+ e.g.::
40
+
41
+ from sqlalchemy.ext.asyncio import create_async_engine
42
+
43
+ asyncio_engine = create_async_engine(
44
+ "postgresql+psycopg://scott:tiger@localhost/test"
45
+ )
46
+
47
+ The asyncio version of the dialect may also be specified explicitly using the
48
+ ``psycopg_async`` suffix, as::
49
+
50
+ from sqlalchemy.ext.asyncio import create_async_engine
51
+
52
+ asyncio_engine = create_async_engine(
53
+ "postgresql+psycopg_async://scott:tiger@localhost/test"
54
+ )
55
+
56
+ .. seealso::
57
+
58
+ :ref:`postgresql_psycopg2` - The SQLAlchemy ``psycopg``
59
+ dialect shares most of its behavior with the ``psycopg2`` dialect.
60
+ Further documentation is available there.
61
+
62
+ Using psycopg Connection Pooling
63
+ --------------------------------
64
+
65
+ The ``psycopg`` driver provides its own connection pool implementation that
66
+ may be used in place of SQLAlchemy's pooling functionality.
67
+ This pool implementation provides support for fixed and dynamic pool sizes
68
+ (including automatic downsizing for unused connections), connection health
69
+ pre-checks, and support for both synchronous and asynchronous code
70
+ environments.
71
+
72
+ Here is an example that uses the sync version of the pool, using
73
+ ``psycopg_pool >= 3.3`` that introduces support for ``close_returns=True``::
74
+
75
+ import psycopg_pool
76
+ from sqlalchemy import create_engine
77
+ from sqlalchemy.pool import NullPool
78
+
79
+ # Create a psycopg_pool connection pool
80
+ my_pool = psycopg_pool.ConnectionPool(
81
+ conninfo="postgresql://scott:tiger@localhost/test",
82
+ close_returns=True, # Return "closed" active connections to the pool
83
+ # ... other pool parameters as desired ...
84
+ )
85
+
86
+ # Create an engine that uses the connection pool to get a connection
87
+ engine = create_engine(
88
+ url="postgresql+psycopg://", # Only need the dialect now
89
+ poolclass=NullPool, # Disable SQLAlchemy's default connection pool
90
+ creator=my_pool.getconn, # Use Psycopg 3 connection pool to obtain connections
91
+ )
92
+
93
+ Similarly an the async example::
94
+
95
+ import psycopg_pool
96
+ from sqlalchemy.ext.asyncio import create_async_engine
97
+ from sqlalchemy.pool import NullPool
98
+
99
+
100
+ async def define_engine():
101
+ # Create a psycopg_pool connection pool
102
+ my_pool = psycopg_pool.AsyncConnectionPool(
103
+ conninfo="postgresql://scott:tiger@localhost/test",
104
+ open=False, # See comment below
105
+ close_returns=True, # Return "closed" active connections to the pool
106
+ # ... other pool parameters as desired ...
107
+ )
108
+
109
+ # Must explicitly open AsyncConnectionPool outside constructor
110
+ # https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.AsyncConnectionPool
111
+ await my_pool.open()
112
+
113
+ # Create an engine that uses the connection pool to get a connection
114
+ engine = create_async_engine(
115
+ url="postgresql+psycopg://", # Only need the dialect now
116
+ poolclass=NullPool, # Disable SQLAlchemy's default connection pool
117
+ async_creator=my_pool.getconn, # Use Psycopg 3 connection pool to obtain connections
118
+ )
119
+
120
+ return engine, my_pool
121
+
122
+ The resulting engine may then be used normally. Internally, Psycopg 3 handles
123
+ connection pooling::
124
+
125
+ with engine.connect() as conn:
126
+ print(conn.scalar(text("select 42")))
127
+
128
+ .. seealso::
129
+
130
+ `Connection pools <https://www.psycopg.org/psycopg3/docs/advanced/pool.html>`_ -
131
+ the Psycopg 3 documentation for ``psycopg_pool.ConnectionPool``.
132
+
133
+ `Example for older version of psycopg_pool
134
+ <https://github.com/sqlalchemy/sqlalchemy/discussions/12522#discussioncomment-13024666>`_ -
135
+ An example about using the ``psycopg_pool<3.3`` that did not have the
136
+ ``close_returns``` parameter.
137
+
138
+ Using a different Cursor class
139
+ ------------------------------
140
+
141
+ One of the differences between ``psycopg`` and the older ``psycopg2``
142
+ is how bound parameters are handled: ``psycopg2`` would bind them
143
+ client side, while ``psycopg`` by default will bind them server side.
144
+
145
+ It's possible to configure ``psycopg`` to do client side binding by
146
+ specifying the ``cursor_factory`` to be ``ClientCursor`` when creating
147
+ the engine::
148
+
149
+ from psycopg import ClientCursor
150
+
151
+ client_side_engine = create_engine(
152
+ "postgresql+psycopg://...",
153
+ connect_args={"cursor_factory": ClientCursor},
154
+ )
155
+
156
+ Similarly when using an async engine the ``AsyncClientCursor`` can be
157
+ specified::
158
+
159
+ from psycopg import AsyncClientCursor
160
+
161
+ client_side_engine = create_async_engine(
162
+ "postgresql+psycopg://...",
163
+ connect_args={"cursor_factory": AsyncClientCursor},
164
+ )
165
+
166
+ .. seealso::
167
+
168
+ `Client-side-binding cursors <https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#client-side-binding-cursors>`_
169
+
170
+ """ # noqa
171
+ from __future__ import annotations
172
+
173
+ from collections import deque
174
+ import logging
175
+ import re
176
+ from typing import cast
177
+ from typing import TYPE_CHECKING
178
+
179
+ from . import ranges
180
+ from ._psycopg_common import _PGDialect_common_psycopg
181
+ from ._psycopg_common import _PGExecutionContext_common_psycopg
182
+ from .base import INTERVAL
183
+ from .base import PGCompiler
184
+ from .base import PGIdentifierPreparer
185
+ from .base import REGCONFIG
186
+ from .json import JSON
187
+ from .json import JSONB
188
+ from .json import JSONPathType
189
+ from .types import CITEXT
190
+ from ... import pool
191
+ from ... import util
192
+ from ...engine import AdaptedConnection
193
+ from ...sql import sqltypes
194
+ from ...util.concurrency import await_fallback
195
+ from ...util.concurrency import await_only
196
+
197
+ if TYPE_CHECKING:
198
+ from typing import Iterable
199
+
200
+ from psycopg import AsyncConnection
201
+
202
+ logger = logging.getLogger("sqlalchemy.dialects.postgresql")
203
+
204
+
205
+ class _PGString(sqltypes.String):
206
+ render_bind_cast = True
207
+
208
+
209
+ class _PGREGCONFIG(REGCONFIG):
210
+ render_bind_cast = True
211
+
212
+
213
+ class _PGJSON(JSON):
214
+ def bind_processor(self, dialect):
215
+ return self._make_bind_processor(None, dialect._psycopg_Json)
216
+
217
+ def result_processor(self, dialect, coltype):
218
+ return None
219
+
220
+
221
+ class _PGJSONB(JSONB):
222
+ def bind_processor(self, dialect):
223
+ return self._make_bind_processor(None, dialect._psycopg_Jsonb)
224
+
225
+ def result_processor(self, dialect, coltype):
226
+ return None
227
+
228
+
229
+ class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
230
+ __visit_name__ = "json_int_index"
231
+
232
+ render_bind_cast = True
233
+
234
+
235
+ class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
236
+ __visit_name__ = "json_str_index"
237
+
238
+ render_bind_cast = True
239
+
240
+
241
+ class _PGJSONPathType(JSONPathType):
242
+ pass
243
+
244
+
245
+ class _PGInterval(INTERVAL):
246
+ render_bind_cast = True
247
+
248
+
249
+ class _PGTimeStamp(sqltypes.DateTime):
250
+ render_bind_cast = True
251
+
252
+
253
+ class _PGDate(sqltypes.Date):
254
+ render_bind_cast = True
255
+
256
+
257
+ class _PGTime(sqltypes.Time):
258
+ render_bind_cast = True
259
+
260
+
261
+ class _PGInteger(sqltypes.Integer):
262
+ render_bind_cast = True
263
+
264
+
265
+ class _PGSmallInteger(sqltypes.SmallInteger):
266
+ render_bind_cast = True
267
+
268
+
269
+ class _PGNullType(sqltypes.NullType):
270
+ render_bind_cast = True
271
+
272
+
273
+ class _PGBigInteger(sqltypes.BigInteger):
274
+ render_bind_cast = True
275
+
276
+
277
+ class _PGBoolean(sqltypes.Boolean):
278
+ render_bind_cast = True
279
+
280
+
281
+ class _PsycopgRange(ranges.AbstractSingleRangeImpl):
282
+ def bind_processor(self, dialect):
283
+ psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
284
+
285
+ def to_range(value):
286
+ if isinstance(value, ranges.Range):
287
+ value = psycopg_Range(
288
+ value.lower, value.upper, value.bounds, value.empty
289
+ )
290
+ return value
291
+
292
+ return to_range
293
+
294
+ def result_processor(self, dialect, coltype):
295
+ def to_range(value):
296
+ if value is not None:
297
+ value = ranges.Range(
298
+ value._lower,
299
+ value._upper,
300
+ bounds=value._bounds if value._bounds else "[)",
301
+ empty=not value._bounds,
302
+ )
303
+ return value
304
+
305
+ return to_range
306
+
307
+
308
+ class _PsycopgMultiRange(ranges.AbstractMultiRangeImpl):
309
+ def bind_processor(self, dialect):
310
+ psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
311
+ psycopg_Multirange = cast(
312
+ PGDialect_psycopg, dialect
313
+ )._psycopg_Multirange
314
+
315
+ NoneType = type(None)
316
+
317
+ def to_range(value):
318
+ if isinstance(value, (str, NoneType, psycopg_Multirange)):
319
+ return value
320
+
321
+ return psycopg_Multirange(
322
+ [
323
+ psycopg_Range(
324
+ element.lower,
325
+ element.upper,
326
+ element.bounds,
327
+ element.empty,
328
+ )
329
+ for element in cast("Iterable[ranges.Range]", value)
330
+ ]
331
+ )
332
+
333
+ return to_range
334
+
335
+ def result_processor(self, dialect, coltype):
336
+ def to_range(value):
337
+ if value is None:
338
+ return None
339
+ else:
340
+ return ranges.MultiRange(
341
+ ranges.Range(
342
+ elem._lower,
343
+ elem._upper,
344
+ bounds=elem._bounds if elem._bounds else "[)",
345
+ empty=not elem._bounds,
346
+ )
347
+ for elem in value
348
+ )
349
+
350
+ return to_range
351
+
352
+
353
+ class PGExecutionContext_psycopg(_PGExecutionContext_common_psycopg):
354
+ pass
355
+
356
+
357
+ class PGCompiler_psycopg(PGCompiler):
358
+ pass
359
+
360
+
361
+ class PGIdentifierPreparer_psycopg(PGIdentifierPreparer):
362
+ pass
363
+
364
+
365
+ def _log_notices(diagnostic):
366
+ logger.info("%s: %s", diagnostic.severity, diagnostic.message_primary)
367
+
368
+
369
+ class PGDialect_psycopg(_PGDialect_common_psycopg):
370
+ driver = "psycopg"
371
+
372
+ supports_statement_cache = True
373
+ supports_server_side_cursors = True
374
+ default_paramstyle = "pyformat"
375
+ supports_sane_multi_rowcount = True
376
+
377
+ execution_ctx_cls = PGExecutionContext_psycopg
378
+ statement_compiler = PGCompiler_psycopg
379
+ preparer = PGIdentifierPreparer_psycopg
380
+ psycopg_version = (0, 0)
381
+
382
+ _has_native_hstore = True
383
+ _psycopg_adapters_map = None
384
+
385
+ colspecs = util.update_copy(
386
+ _PGDialect_common_psycopg.colspecs,
387
+ {
388
+ sqltypes.String: _PGString,
389
+ REGCONFIG: _PGREGCONFIG,
390
+ JSON: _PGJSON,
391
+ CITEXT: CITEXT,
392
+ sqltypes.JSON: _PGJSON,
393
+ JSONB: _PGJSONB,
394
+ sqltypes.JSON.JSONPathType: _PGJSONPathType,
395
+ sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
396
+ sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
397
+ sqltypes.Interval: _PGInterval,
398
+ INTERVAL: _PGInterval,
399
+ sqltypes.Date: _PGDate,
400
+ sqltypes.DateTime: _PGTimeStamp,
401
+ sqltypes.Time: _PGTime,
402
+ sqltypes.Integer: _PGInteger,
403
+ sqltypes.SmallInteger: _PGSmallInteger,
404
+ sqltypes.BigInteger: _PGBigInteger,
405
+ ranges.AbstractSingleRange: _PsycopgRange,
406
+ ranges.AbstractMultiRange: _PsycopgMultiRange,
407
+ },
408
+ )
409
+
410
+ def __init__(self, **kwargs):
411
+ super().__init__(**kwargs)
412
+
413
+ if self.dbapi:
414
+ m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", self.dbapi.__version__)
415
+ if m:
416
+ self.psycopg_version = tuple(
417
+ int(x) for x in m.group(1, 2, 3) if x is not None
418
+ )
419
+
420
+ if self.psycopg_version < (3, 0, 2):
421
+ raise ImportError(
422
+ "psycopg version 3.0.2 or higher is required."
423
+ )
424
+
425
+ from psycopg.adapt import AdaptersMap
426
+
427
+ self._psycopg_adapters_map = adapters_map = AdaptersMap(
428
+ self.dbapi.adapters
429
+ )
430
+
431
+ if self._native_inet_types is False:
432
+ import psycopg.types.string
433
+
434
+ adapters_map.register_loader(
435
+ "inet", psycopg.types.string.TextLoader
436
+ )
437
+ adapters_map.register_loader(
438
+ "cidr", psycopg.types.string.TextLoader
439
+ )
440
+
441
+ if self._json_deserializer:
442
+ from psycopg.types.json import set_json_loads
443
+
444
+ set_json_loads(self._json_deserializer, adapters_map)
445
+
446
+ if self._json_serializer:
447
+ from psycopg.types.json import set_json_dumps
448
+
449
+ set_json_dumps(self._json_serializer, adapters_map)
450
+
451
+ def create_connect_args(self, url):
452
+ # see https://github.com/psycopg/psycopg/issues/83
453
+ cargs, cparams = super().create_connect_args(url)
454
+
455
+ if self._psycopg_adapters_map:
456
+ cparams["context"] = self._psycopg_adapters_map
457
+ if self.client_encoding is not None:
458
+ cparams["client_encoding"] = self.client_encoding
459
+ return cargs, cparams
460
+
461
+ def _type_info_fetch(self, connection, name):
462
+ from psycopg.types import TypeInfo
463
+
464
+ return TypeInfo.fetch(connection.connection.driver_connection, name)
465
+
466
+ def initialize(self, connection):
467
+ super().initialize(connection)
468
+
469
+ # PGDialect.initialize() checks server version for <= 8.2 and sets
470
+ # this flag to False if so
471
+ if not self.insert_returning:
472
+ self.insert_executemany_returning = False
473
+
474
+ # HSTORE can't be registered until we have a connection so that
475
+ # we can look up its OID, so we set up this adapter in
476
+ # initialize()
477
+ if self.use_native_hstore:
478
+ info = self._type_info_fetch(connection, "hstore")
479
+ self._has_native_hstore = info is not None
480
+ if self._has_native_hstore:
481
+ from psycopg.types.hstore import register_hstore
482
+
483
+ # register the adapter for connections made subsequent to
484
+ # this one
485
+ assert self._psycopg_adapters_map
486
+ register_hstore(info, self._psycopg_adapters_map)
487
+
488
+ # register the adapter for this connection
489
+ assert connection.connection
490
+ register_hstore(info, connection.connection.driver_connection)
491
+
492
+ @classmethod
493
+ def import_dbapi(cls):
494
+ import psycopg
495
+
496
+ return psycopg
497
+
498
+ @classmethod
499
+ def get_async_dialect_cls(cls, url):
500
+ return PGDialectAsync_psycopg
501
+
502
+ @util.memoized_property
503
+ def _isolation_lookup(self):
504
+ return {
505
+ "READ COMMITTED": self.dbapi.IsolationLevel.READ_COMMITTED,
506
+ "READ UNCOMMITTED": self.dbapi.IsolationLevel.READ_UNCOMMITTED,
507
+ "REPEATABLE READ": self.dbapi.IsolationLevel.REPEATABLE_READ,
508
+ "SERIALIZABLE": self.dbapi.IsolationLevel.SERIALIZABLE,
509
+ }
510
+
511
+ @util.memoized_property
512
+ def _psycopg_Json(self):
513
+ from psycopg.types import json
514
+
515
+ return json.Json
516
+
517
+ @util.memoized_property
518
+ def _psycopg_Jsonb(self):
519
+ from psycopg.types import json
520
+
521
+ return json.Jsonb
522
+
523
+ @util.memoized_property
524
+ def _psycopg_TransactionStatus(self):
525
+ from psycopg.pq import TransactionStatus
526
+
527
+ return TransactionStatus
528
+
529
+ @util.memoized_property
530
+ def _psycopg_Range(self):
531
+ from psycopg.types.range import Range
532
+
533
+ return Range
534
+
535
+ @util.memoized_property
536
+ def _psycopg_Multirange(self):
537
+ from psycopg.types.multirange import Multirange
538
+
539
+ return Multirange
540
+
541
+ def _do_isolation_level(self, connection, autocommit, isolation_level):
542
+ connection.autocommit = autocommit
543
+ connection.isolation_level = isolation_level
544
+
545
+ def get_isolation_level(self, dbapi_connection):
546
+ status_before = dbapi_connection.info.transaction_status
547
+ value = super().get_isolation_level(dbapi_connection)
548
+
549
+ # don't rely on psycopg providing enum symbols, compare with
550
+ # eq/ne
551
+ if status_before == self._psycopg_TransactionStatus.IDLE:
552
+ dbapi_connection.rollback()
553
+ return value
554
+
555
+ def set_isolation_level(self, dbapi_connection, level):
556
+ if level == "AUTOCOMMIT":
557
+ self._do_isolation_level(
558
+ dbapi_connection, autocommit=True, isolation_level=None
559
+ )
560
+ else:
561
+ self._do_isolation_level(
562
+ dbapi_connection,
563
+ autocommit=False,
564
+ isolation_level=self._isolation_lookup[level],
565
+ )
566
+
567
+ def set_readonly(self, connection, value):
568
+ connection.read_only = value
569
+
570
+ def get_readonly(self, connection):
571
+ return connection.read_only
572
+
573
+ def on_connect(self):
574
+ def notices(conn):
575
+ conn.add_notice_handler(_log_notices)
576
+
577
+ fns = [notices]
578
+
579
+ if self.isolation_level is not None:
580
+
581
+ def on_connect(conn):
582
+ self.set_isolation_level(conn, self.isolation_level)
583
+
584
+ fns.append(on_connect)
585
+
586
+ # fns always has the notices function
587
+ def on_connect(conn):
588
+ for fn in fns:
589
+ fn(conn)
590
+
591
+ return on_connect
592
+
593
+ def is_disconnect(self, e, connection, cursor):
594
+ if isinstance(e, self.dbapi.Error) and connection is not None:
595
+ if connection.closed or connection.broken:
596
+ return True
597
+ return False
598
+
599
+ def _do_prepared_twophase(self, connection, command, recover=False):
600
+ dbapi_conn = connection.connection.dbapi_connection
601
+ if (
602
+ recover
603
+ # don't rely on psycopg providing enum symbols, compare with
604
+ # eq/ne
605
+ or dbapi_conn.info.transaction_status
606
+ != self._psycopg_TransactionStatus.IDLE
607
+ ):
608
+ dbapi_conn.rollback()
609
+ before_autocommit = dbapi_conn.autocommit
610
+ try:
611
+ if not before_autocommit:
612
+ self._do_autocommit(dbapi_conn, True)
613
+ dbapi_conn.execute(command)
614
+ finally:
615
+ if not before_autocommit:
616
+ self._do_autocommit(dbapi_conn, before_autocommit)
617
+
618
+ def do_rollback_twophase(
619
+ self, connection, xid, is_prepared=True, recover=False
620
+ ):
621
+ if is_prepared:
622
+ self._do_prepared_twophase(
623
+ connection, f"ROLLBACK PREPARED '{xid}'", recover=recover
624
+ )
625
+ else:
626
+ self.do_rollback(connection.connection)
627
+
628
+ def do_commit_twophase(
629
+ self, connection, xid, is_prepared=True, recover=False
630
+ ):
631
+ if is_prepared:
632
+ self._do_prepared_twophase(
633
+ connection, f"COMMIT PREPARED '{xid}'", recover=recover
634
+ )
635
+ else:
636
+ self.do_commit(connection.connection)
637
+
638
+ @util.memoized_property
639
+ def _dialect_specific_select_one(self):
640
+ return ";"
641
+
642
+
643
+ class AsyncAdapt_psycopg_cursor:
644
+ __slots__ = ("_cursor", "await_", "_rows")
645
+
646
+ _psycopg_ExecStatus = None
647
+
648
+ def __init__(self, cursor, await_) -> None:
649
+ self._cursor = cursor
650
+ self.await_ = await_
651
+ self._rows = deque()
652
+
653
+ def __getattr__(self, name):
654
+ return getattr(self._cursor, name)
655
+
656
+ @property
657
+ def arraysize(self):
658
+ return self._cursor.arraysize
659
+
660
+ @arraysize.setter
661
+ def arraysize(self, value):
662
+ self._cursor.arraysize = value
663
+
664
+ async def _async_soft_close(self) -> None:
665
+ return
666
+
667
+ def close(self):
668
+ self._rows.clear()
669
+ # Normal cursor just call _close() in a non-sync way.
670
+ self._cursor._close()
671
+
672
+ def execute(self, query, params=None, **kw):
673
+ result = self.await_(self._cursor.execute(query, params, **kw))
674
+ # sqlalchemy result is not async, so need to pull all rows here
675
+ res = self._cursor.pgresult
676
+
677
+ # don't rely on psycopg providing enum symbols, compare with
678
+ # eq/ne
679
+ if res and res.status == self._psycopg_ExecStatus.TUPLES_OK:
680
+ rows = self.await_(self._cursor.fetchall())
681
+ self._rows = deque(rows)
682
+ return result
683
+
684
+ def executemany(self, query, params_seq):
685
+ return self.await_(self._cursor.executemany(query, params_seq))
686
+
687
+ def __iter__(self):
688
+ while self._rows:
689
+ yield self._rows.popleft()
690
+
691
+ def fetchone(self):
692
+ if self._rows:
693
+ return self._rows.popleft()
694
+ else:
695
+ return None
696
+
697
+ def fetchmany(self, size=None):
698
+ if size is None:
699
+ size = self._cursor.arraysize
700
+
701
+ rr = self._rows
702
+ return [rr.popleft() for _ in range(min(size, len(rr)))]
703
+
704
+ def fetchall(self):
705
+ retval = list(self._rows)
706
+ self._rows.clear()
707
+ return retval
708
+
709
+
710
+ class AsyncAdapt_psycopg_ss_cursor(AsyncAdapt_psycopg_cursor):
711
+ def execute(self, query, params=None, **kw):
712
+ self.await_(self._cursor.execute(query, params, **kw))
713
+ return self
714
+
715
+ def close(self):
716
+ self.await_(self._cursor.close())
717
+
718
+ def fetchone(self):
719
+ return self.await_(self._cursor.fetchone())
720
+
721
+ def fetchmany(self, size=0):
722
+ return self.await_(self._cursor.fetchmany(size))
723
+
724
+ def fetchall(self):
725
+ return self.await_(self._cursor.fetchall())
726
+
727
+ def __iter__(self):
728
+ iterator = self._cursor.__aiter__()
729
+ while True:
730
+ try:
731
+ yield self.await_(iterator.__anext__())
732
+ except StopAsyncIteration:
733
+ break
734
+
735
+
736
+ class AsyncAdapt_psycopg_connection(AdaptedConnection):
737
+ _connection: AsyncConnection
738
+ __slots__ = ()
739
+ await_ = staticmethod(await_only)
740
+
741
+ def __init__(self, connection) -> None:
742
+ self._connection = connection
743
+
744
+ def __getattr__(self, name):
745
+ return getattr(self._connection, name)
746
+
747
+ def execute(self, query, params=None, **kw):
748
+ cursor = self.await_(self._connection.execute(query, params, **kw))
749
+ return AsyncAdapt_psycopg_cursor(cursor, self.await_)
750
+
751
+ def cursor(self, *args, **kw):
752
+ cursor = self._connection.cursor(*args, **kw)
753
+ if hasattr(cursor, "name"):
754
+ return AsyncAdapt_psycopg_ss_cursor(cursor, self.await_)
755
+ else:
756
+ return AsyncAdapt_psycopg_cursor(cursor, self.await_)
757
+
758
+ def commit(self):
759
+ self.await_(self._connection.commit())
760
+
761
+ def rollback(self):
762
+ self.await_(self._connection.rollback())
763
+
764
+ def close(self):
765
+ self.await_(self._connection.close())
766
+
767
+ @property
768
+ def autocommit(self):
769
+ return self._connection.autocommit
770
+
771
+ @autocommit.setter
772
+ def autocommit(self, value):
773
+ self.set_autocommit(value)
774
+
775
+ def set_autocommit(self, value):
776
+ self.await_(self._connection.set_autocommit(value))
777
+
778
+ def set_isolation_level(self, value):
779
+ self.await_(self._connection.set_isolation_level(value))
780
+
781
+ def set_read_only(self, value):
782
+ self.await_(self._connection.set_read_only(value))
783
+
784
+ def set_deferrable(self, value):
785
+ self.await_(self._connection.set_deferrable(value))
786
+
787
+
788
+ class AsyncAdaptFallback_psycopg_connection(AsyncAdapt_psycopg_connection):
789
+ __slots__ = ()
790
+ await_ = staticmethod(await_fallback)
791
+
792
+
793
+ class PsycopgAdaptDBAPI:
794
+ def __init__(self, psycopg) -> None:
795
+ self.psycopg = psycopg
796
+
797
+ for k, v in self.psycopg.__dict__.items():
798
+ if k != "connect":
799
+ self.__dict__[k] = v
800
+
801
+ def connect(self, *arg, **kw):
802
+ async_fallback = kw.pop("async_fallback", False)
803
+ creator_fn = kw.pop(
804
+ "async_creator_fn", self.psycopg.AsyncConnection.connect
805
+ )
806
+ if util.asbool(async_fallback):
807
+ return AsyncAdaptFallback_psycopg_connection(
808
+ await_fallback(creator_fn(*arg, **kw))
809
+ )
810
+ else:
811
+ return AsyncAdapt_psycopg_connection(
812
+ await_only(creator_fn(*arg, **kw))
813
+ )
814
+
815
+
816
+ class PGDialectAsync_psycopg(PGDialect_psycopg):
817
+ is_async = True
818
+ supports_statement_cache = True
819
+
820
+ @classmethod
821
+ def import_dbapi(cls):
822
+ import psycopg
823
+ from psycopg.pq import ExecStatus
824
+
825
+ AsyncAdapt_psycopg_cursor._psycopg_ExecStatus = ExecStatus
826
+
827
+ return PsycopgAdaptDBAPI(psycopg)
828
+
829
+ @classmethod
830
+ def get_pool_class(cls, url):
831
+ async_fallback = url.query.get("async_fallback", False)
832
+
833
+ if util.asbool(async_fallback):
834
+ return pool.FallbackAsyncAdaptedQueuePool
835
+ else:
836
+ return pool.AsyncAdaptedQueuePool
837
+
838
+ def _type_info_fetch(self, connection, name):
839
+ from psycopg.types import TypeInfo
840
+
841
+ adapted = connection.connection
842
+ return adapted.await_(TypeInfo.fetch(adapted.driver_connection, name))
843
+
844
+ def _do_isolation_level(self, connection, autocommit, isolation_level):
845
+ connection.set_autocommit(autocommit)
846
+ connection.set_isolation_level(isolation_level)
847
+
848
+ def _do_autocommit(self, connection, value):
849
+ connection.set_autocommit(value)
850
+
851
+ def set_readonly(self, connection, value):
852
+ connection.set_read_only(value)
853
+
854
+ def set_deferrable(self, connection, value):
855
+ connection.set_deferrable(value)
856
+
857
+ def get_driver_connection(self, connection):
858
+ return connection._connection
859
+
860
+
861
+ dialect = PGDialect_psycopg
862
+ dialect_async = PGDialectAsync_psycopg