SQLAlchemy 2.1.0rc1__cp315-cp315-win_arm64.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 (276) hide show
  1. sqlalchemy/__init__.py +299 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +248 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +29 -0
  8. sqlalchemy/dialects/mssql/__init__.py +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4833 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +345 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +130 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +697 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +260 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +241 -0
  21. sqlalchemy/dialects/mysql/base.py +3896 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +107 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +314 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +318 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +188 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3847 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +157 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +898 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +366 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +170 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
  50. sqlalchemy/dialects/postgresql/base.py +5935 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +599 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +422 -0
  55. sqlalchemy/dialects/postgresql/json.py +411 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +535 -0
  57. sqlalchemy/dialects/postgresql/operators.py +129 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +655 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
  60. sqlalchemy/dialects/postgresql/provision.py +202 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +800 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +59 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
  68. sqlalchemy/dialects/sqlite/base.py +3103 -0
  69. sqlalchemy/dialects/sqlite/dml.py +314 -0
  70. sqlalchemy/dialects/sqlite/json.py +134 -0
  71. sqlalchemy/dialects/sqlite/provision.py +237 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp315-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp315-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +711 -0
  80. sqlalchemy/engine/_row_cy.cp315-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp315-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3357 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2425 -0
  88. sqlalchemy/engine/default.py +2627 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3636 -0
  91. sqlalchemy/engine/mock.py +133 -0
  92. sqlalchemy/engine/processors.py +83 -0
  93. sqlalchemy/engine/reflection.py +2141 -0
  94. sqlalchemy/engine/result.py +2012 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +164 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +675 -0
  102. sqlalchemy/event/base.py +473 -0
  103. sqlalchemy/event/legacy.py +259 -0
  104. sqlalchemy/event/registry.py +391 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +939 -0
  107. sqlalchemy/ext/__init__.py +10 -0
  108. sqlalchemy/ext/associationproxy.py +2073 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2006 -0
  116. sqlalchemy/ext/automap.py +1702 -0
  117. sqlalchemy/ext/baked.py +558 -0
  118. sqlalchemy/ext/compiler.py +601 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +561 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +440 -0
  127. sqlalchemy/ext/serializer.py +184 -0
  128. sqlalchemy/future/__init__.py +17 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +188 -0
  131. sqlalchemy/log.py +279 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +180 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +991 -0
  137. sqlalchemy/orm/bulk_persistence.py +2168 -0
  138. sqlalchemy/orm/clsregistry.py +630 -0
  139. sqlalchemy/orm/collections.py +1569 -0
  140. sqlalchemy/orm/context.py +3475 -0
  141. sqlalchemy/orm/decl_api.py +2283 -0
  142. sqlalchemy/orm/decl_base.py +2320 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +306 -0
  146. sqlalchemy/orm/evaluator.py +378 -0
  147. sqlalchemy/orm/events.py +3387 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +749 -0
  151. sqlalchemy/orm/interfaces.py +1595 -0
  152. sqlalchemy/orm/loading.py +1712 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4465 -0
  155. sqlalchemy/orm/path_registry.py +907 -0
  156. sqlalchemy/orm/persistence.py +1790 -0
  157. sqlalchemy/orm/properties.py +972 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3608 -0
  160. sqlalchemy/orm/scoping.py +2233 -0
  161. sqlalchemy/orm/session.py +5468 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3552 -0
  165. sqlalchemy/orm/strategy_options.py +2648 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +797 -0
  168. sqlalchemy/orm/util.py +2461 -0
  169. sqlalchemy/orm/writeonly.py +701 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1540 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +583 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +75 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +402 -0
  178. sqlalchemy/sql/_cache_key_cy.cp315-win_arm64.pyd +0 -0
  179. sqlalchemy/sql/_cache_key_cy.py +363 -0
  180. sqlalchemy/sql/_dml_constructors.py +132 -0
  181. sqlalchemy/sql/_elements_constructors.py +2190 -0
  182. sqlalchemy/sql/_orm_types.py +19 -0
  183. sqlalchemy/sql/_selectable_constructors.py +840 -0
  184. sqlalchemy/sql/_typing.py +500 -0
  185. sqlalchemy/sql/_util_cy.cp315-win_arm64.pyd +0 -0
  186. sqlalchemy/sql/_util_cy.pxd +11 -0
  187. sqlalchemy/sql/_util_cy.py +127 -0
  188. sqlalchemy/sql/annotation.py +590 -0
  189. sqlalchemy/sql/base.py +2702 -0
  190. sqlalchemy/sql/cache_key.py +915 -0
  191. sqlalchemy/sql/coercions.py +1373 -0
  192. sqlalchemy/sql/compiler.py +8453 -0
  193. sqlalchemy/sql/crud.py +1816 -0
  194. sqlalchemy/sql/ddl.py +1962 -0
  195. sqlalchemy/sql/default_comparator.py +660 -0
  196. sqlalchemy/sql/dml.py +2018 -0
  197. sqlalchemy/sql/elements.py +6057 -0
  198. sqlalchemy/sql/events.py +458 -0
  199. sqlalchemy/sql/expression.py +171 -0
  200. sqlalchemy/sql/functions.py +2380 -0
  201. sqlalchemy/sql/lambdas.py +1442 -0
  202. sqlalchemy/sql/naming.py +204 -0
  203. sqlalchemy/sql/operators.py +2909 -0
  204. sqlalchemy/sql/roles.py +332 -0
  205. sqlalchemy/sql/schema.py +7075 -0
  206. sqlalchemy/sql/selectable.py +7634 -0
  207. sqlalchemy/sql/sqltypes.py +4130 -0
  208. sqlalchemy/sql/traversals.py +1041 -0
  209. sqlalchemy/sql/type_api.py +2450 -0
  210. sqlalchemy/sql/util.py +1496 -0
  211. sqlalchemy/sql/visitors.py +1153 -0
  212. sqlalchemy/testing/__init__.py +97 -0
  213. sqlalchemy/testing/assertions.py +1007 -0
  214. sqlalchemy/testing/assertsql.py +519 -0
  215. sqlalchemy/testing/asyncio.py +128 -0
  216. sqlalchemy/testing/cancellation.py +237 -0
  217. sqlalchemy/testing/config.py +440 -0
  218. sqlalchemy/testing/engines.py +482 -0
  219. sqlalchemy/testing/entities.py +117 -0
  220. sqlalchemy/testing/exclusions.py +501 -0
  221. sqlalchemy/testing/fixtures/__init__.py +30 -0
  222. sqlalchemy/testing/fixtures/base.py +426 -0
  223. sqlalchemy/testing/fixtures/mypy.py +247 -0
  224. sqlalchemy/testing/fixtures/orm.py +227 -0
  225. sqlalchemy/testing/fixtures/sql.py +538 -0
  226. sqlalchemy/testing/pickleable.py +155 -0
  227. sqlalchemy/testing/plugin/__init__.py +6 -0
  228. sqlalchemy/testing/plugin/bootstrap.py +50 -0
  229. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  230. sqlalchemy/testing/plugin/pytestplugin.py +896 -0
  231. sqlalchemy/testing/profiles_file.py +350 -0
  232. sqlalchemy/testing/profiling.py +294 -0
  233. sqlalchemy/testing/provision.py +633 -0
  234. sqlalchemy/testing/requirements.py +1971 -0
  235. sqlalchemy/testing/schema.py +198 -0
  236. sqlalchemy/testing/suite/__init__.py +19 -0
  237. sqlalchemy/testing/suite/test_cte.py +237 -0
  238. sqlalchemy/testing/suite/test_ddl.py +420 -0
  239. sqlalchemy/testing/suite/test_dialect.py +776 -0
  240. sqlalchemy/testing/suite/test_insert.py +630 -0
  241. sqlalchemy/testing/suite/test_reflection.py +3815 -0
  242. sqlalchemy/testing/suite/test_results.py +660 -0
  243. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  244. sqlalchemy/testing/suite/test_select.py +2112 -0
  245. sqlalchemy/testing/suite/test_sequence.py +317 -0
  246. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  247. sqlalchemy/testing/suite/test_types.py +2271 -0
  248. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  249. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  250. sqlalchemy/testing/util.py +575 -0
  251. sqlalchemy/testing/warnings.py +52 -0
  252. sqlalchemy/types.py +75 -0
  253. sqlalchemy/util/__init__.py +165 -0
  254. sqlalchemy/util/_collections.py +688 -0
  255. sqlalchemy/util/_collections_cy.cp315-win_arm64.pyd +0 -0
  256. sqlalchemy/util/_collections_cy.pxd +8 -0
  257. sqlalchemy/util/_collections_cy.py +516 -0
  258. sqlalchemy/util/_has_cython.py +48 -0
  259. sqlalchemy/util/_immutabledict_cy.cp315-win_arm64.pyd +0 -0
  260. sqlalchemy/util/_immutabledict_cy.py +240 -0
  261. sqlalchemy/util/compat.py +298 -0
  262. sqlalchemy/util/concurrency.py +272 -0
  263. sqlalchemy/util/cython.py +95 -0
  264. sqlalchemy/util/deprecations.py +401 -0
  265. sqlalchemy/util/langhelpers.py +2797 -0
  266. sqlalchemy/util/preloaded.py +153 -0
  267. sqlalchemy/util/queue.py +304 -0
  268. sqlalchemy/util/tool_support.py +202 -0
  269. sqlalchemy/util/topological.py +120 -0
  270. sqlalchemy/util/typing.py +709 -0
  271. sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
  272. sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
  273. sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
  274. sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
  275. sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
  276. sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,800 @@
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
+
172
+ from __future__ import annotations
173
+
174
+ import collections
175
+ import logging
176
+ from types import NoneType
177
+ from typing import cast
178
+ from typing import TYPE_CHECKING
179
+
180
+ from . import ranges
181
+ from ._psycopg_common import _PGDialect_common_psycopg
182
+ from ._psycopg_common import _PGExecutionContext_common_psycopg
183
+ from .base import INTERVAL
184
+ from .base import PGCompiler
185
+ from .base import PGIdentifierPreparer
186
+ from .base import REGCONFIG
187
+ from .json import JSON
188
+ from .json import JSONB
189
+ from .json import JSONPathType
190
+ from .types import CITEXT
191
+ from ... import util
192
+ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
193
+ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
194
+ from ...connectors.asyncio import AsyncAdapt_dbapi_module
195
+ from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
196
+ from ...sql import sqltypes
197
+ from ...util.concurrency import await_
198
+
199
+ if TYPE_CHECKING:
200
+ from typing import Iterable
201
+
202
+ from psycopg import AsyncConnection
203
+
204
+ logger = logging.getLogger("sqlalchemy.dialects.postgresql")
205
+
206
+
207
+ class _PGString(sqltypes.String):
208
+ render_bind_cast = True
209
+
210
+
211
+ class _PGREGCONFIG(REGCONFIG):
212
+ render_bind_cast = True
213
+
214
+
215
+ class _PGJSON(JSON):
216
+ def bind_processor(self, dialect):
217
+ """psycopg's bind processor is assembled on the type adapter,
218
+ but we still need to wrap the value in a psycopg.Json() object"""
219
+ return self._make_bind_processor(None, dialect._psycopg_Json)
220
+
221
+
222
+ class _PGJSONB(JSONB):
223
+ def bind_processor(self, dialect):
224
+ """psycopg's bind processor is assembled on the type adapter,
225
+ but we still need to wrap the value in a psycopg.Jsonb() object"""
226
+ return self._make_bind_processor(None, dialect._psycopg_Jsonb)
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
+ def to_range(value):
316
+ if isinstance(value, (str, NoneType, psycopg_Multirange)):
317
+ return value
318
+
319
+ return psycopg_Multirange(
320
+ [
321
+ psycopg_Range(
322
+ element.lower,
323
+ element.upper,
324
+ element.bounds,
325
+ element.empty,
326
+ )
327
+ for element in cast("Iterable[ranges.Range]", value)
328
+ ]
329
+ )
330
+
331
+ return to_range
332
+
333
+ def result_processor(self, dialect, coltype):
334
+ def to_range(value):
335
+ if value is None:
336
+ return None
337
+ else:
338
+ return ranges.MultiRange(
339
+ ranges.Range(
340
+ elem._lower,
341
+ elem._upper,
342
+ bounds=elem._bounds if elem._bounds else "[)",
343
+ empty=not elem._bounds,
344
+ )
345
+ for elem in value
346
+ )
347
+
348
+ return to_range
349
+
350
+
351
+ class PGExecutionContext_psycopg(_PGExecutionContext_common_psycopg):
352
+ pass
353
+
354
+
355
+ class PGCompiler_psycopg(PGCompiler):
356
+ pass
357
+
358
+
359
+ class PGIdentifierPreparer_psycopg(PGIdentifierPreparer):
360
+ pass
361
+
362
+
363
+ def _log_notices(diagnostic):
364
+ logger.info("%s: %s", diagnostic.severity, diagnostic.message_primary)
365
+
366
+
367
+ class PGDialect_psycopg(_PGDialect_common_psycopg):
368
+ driver = "psycopg"
369
+
370
+ minimum_dbapi_version = util.VersionInfo((3, 0, 2))
371
+
372
+ supports_statement_cache = True
373
+ supports_server_side_cursors = True
374
+ default_paramstyle = "pyformat"
375
+ supports_sane_multi_rowcount = True
376
+
377
+ supports_native_json_serialization = True
378
+ supports_native_json_deserialization = True
379
+ dialect_injects_custom_json_deserializer = True
380
+
381
+ execution_ctx_cls = PGExecutionContext_psycopg
382
+ statement_compiler = PGCompiler_psycopg
383
+ preparer = PGIdentifierPreparer_psycopg
384
+
385
+ _has_native_hstore = True
386
+ _psycopg_adapters_map = None
387
+
388
+ colspecs = util.update_copy(
389
+ _PGDialect_common_psycopg.colspecs,
390
+ {
391
+ sqltypes.String: _PGString,
392
+ REGCONFIG: _PGREGCONFIG,
393
+ JSON: _PGJSON,
394
+ CITEXT: CITEXT,
395
+ sqltypes.JSON: _PGJSON,
396
+ JSONB: _PGJSONB,
397
+ sqltypes.JSON.JSONPathType: _PGJSONPathType,
398
+ sqltypes.JSON.JSONIntIndexType: _PGJSONIntIndexType,
399
+ sqltypes.JSON.JSONStrIndexType: _PGJSONStrIndexType,
400
+ sqltypes.Interval: _PGInterval,
401
+ INTERVAL: _PGInterval,
402
+ sqltypes.Date: _PGDate,
403
+ sqltypes.DateTime: _PGTimeStamp,
404
+ sqltypes.Time: _PGTime,
405
+ sqltypes.Integer: _PGInteger,
406
+ sqltypes.SmallInteger: _PGSmallInteger,
407
+ sqltypes.BigInteger: _PGBigInteger,
408
+ ranges.AbstractSingleRange: _PsycopgRange,
409
+ ranges.AbstractMultiRange: _PsycopgMultiRange,
410
+ },
411
+ )
412
+
413
+ @property
414
+ def psycopg_version(self):
415
+ """Legacy accessor for :attr:`.Dialect.dbapi_version`.
416
+
417
+ Retained for backwards compatibility; ``(0, 0)`` is returned when
418
+ no version can be determined.
419
+
420
+ """
421
+ version = self._dbapi_version_or_none
422
+ return version if version is not None else (0, 0)
423
+
424
+ def __init__(self, **kwargs):
425
+ super().__init__(**kwargs)
426
+
427
+ if self.dbapi:
428
+ from psycopg.adapt import AdaptersMap
429
+
430
+ self._psycopg_adapters_map = adapters_map = AdaptersMap(
431
+ self.dbapi.adapters
432
+ )
433
+
434
+ if self._native_inet_types is False:
435
+ import psycopg.types.string
436
+
437
+ adapters_map.register_loader(
438
+ "inet", psycopg.types.string.TextLoader
439
+ )
440
+ adapters_map.register_loader(
441
+ "cidr", psycopg.types.string.TextLoader
442
+ )
443
+
444
+ if self._json_deserializer:
445
+ from psycopg.types.json import set_json_loads
446
+
447
+ set_json_loads(self._json_deserializer, adapters_map)
448
+
449
+ if self._json_serializer:
450
+ from psycopg.types.json import set_json_dumps
451
+
452
+ set_json_dumps(self._json_serializer, adapters_map)
453
+
454
+ def create_connect_args(self, url):
455
+ # see https://github.com/psycopg/psycopg/issues/83
456
+ cargs, cparams = super().create_connect_args(url)
457
+
458
+ if self._psycopg_adapters_map:
459
+ cparams["context"] = self._psycopg_adapters_map
460
+ if self.client_encoding is not None:
461
+ cparams["client_encoding"] = self.client_encoding
462
+ return cargs, cparams
463
+
464
+ def _type_info_fetch(self, connection, name):
465
+ from psycopg.types import TypeInfo
466
+
467
+ return TypeInfo.fetch(connection.connection.driver_connection, name)
468
+
469
+ def initialize(self, connection):
470
+ super().initialize(connection)
471
+
472
+ # PGDialect.initialize() checks server version for <= 8.2 and sets
473
+ # this flag to False if so
474
+ if not self.insert_returning:
475
+ self.insert_executemany_returning = False
476
+
477
+ # HSTORE can't be registered until we have a connection so that
478
+ # we can look up its OID, so we set up this adapter in
479
+ # initialize()
480
+ if self.use_native_hstore:
481
+ info = self._type_info_fetch(connection, "hstore")
482
+ self._has_native_hstore = info is not None
483
+ if self._has_native_hstore:
484
+ from psycopg.types.hstore import register_hstore
485
+
486
+ # register the adapter for connections made subsequent to
487
+ # this one
488
+ assert self._psycopg_adapters_map
489
+ register_hstore(info, self._psycopg_adapters_map)
490
+
491
+ # register the adapter for this connection
492
+ assert connection.connection
493
+ register_hstore(info, connection.connection.driver_connection)
494
+
495
+ @classmethod
496
+ def import_dbapi(cls):
497
+ import psycopg
498
+
499
+ return psycopg
500
+
501
+ @classmethod
502
+ def get_async_dialect_cls(cls, url):
503
+ return PGDialectAsync_psycopg
504
+
505
+ @util.memoized_property
506
+ def _isolation_lookup(self):
507
+ return {
508
+ "READ COMMITTED": self.dbapi.IsolationLevel.READ_COMMITTED,
509
+ "READ UNCOMMITTED": self.dbapi.IsolationLevel.READ_UNCOMMITTED,
510
+ "REPEATABLE READ": self.dbapi.IsolationLevel.REPEATABLE_READ,
511
+ "SERIALIZABLE": self.dbapi.IsolationLevel.SERIALIZABLE,
512
+ }
513
+
514
+ @util.memoized_property
515
+ def _psycopg_Json(self):
516
+ from psycopg.types import json
517
+
518
+ return json.Json
519
+
520
+ @util.memoized_property
521
+ def _psycopg_Jsonb(self):
522
+ from psycopg.types import json
523
+
524
+ return json.Jsonb
525
+
526
+ @util.memoized_property
527
+ def _psycopg_TransactionStatus(self):
528
+ from psycopg.pq import TransactionStatus
529
+
530
+ return TransactionStatus
531
+
532
+ @util.memoized_property
533
+ def _psycopg_Range(self):
534
+ from psycopg.types.range import Range
535
+
536
+ return Range
537
+
538
+ @util.memoized_property
539
+ def _psycopg_Multirange(self):
540
+ from psycopg.types.multirange import Multirange
541
+
542
+ return Multirange
543
+
544
+ def _do_isolation_level(self, connection, autocommit, isolation_level):
545
+ connection.autocommit = autocommit
546
+ connection.isolation_level = isolation_level
547
+
548
+ def get_isolation_level(self, dbapi_connection):
549
+ status_before = dbapi_connection.info.transaction_status
550
+ value = super().get_isolation_level(dbapi_connection)
551
+
552
+ # don't rely on psycopg providing enum symbols, compare with
553
+ # eq/ne
554
+ if status_before == self._psycopg_TransactionStatus.IDLE:
555
+ dbapi_connection.rollback()
556
+ return value
557
+
558
+ def set_isolation_level(self, dbapi_connection, level):
559
+ if level == "AUTOCOMMIT":
560
+ self._do_isolation_level(
561
+ dbapi_connection, autocommit=True, isolation_level=None
562
+ )
563
+ else:
564
+ self._do_isolation_level(
565
+ dbapi_connection,
566
+ autocommit=False,
567
+ isolation_level=self._isolation_lookup[level],
568
+ )
569
+
570
+ def set_readonly(self, connection, value):
571
+ connection.read_only = value
572
+
573
+ def get_readonly(self, connection):
574
+ return connection.read_only
575
+
576
+ def on_connect(self):
577
+ def notices(conn):
578
+ conn.add_notice_handler(_log_notices)
579
+
580
+ fns = [notices]
581
+
582
+ if self.isolation_level is not None:
583
+
584
+ def on_connect(conn):
585
+ self.set_isolation_level(conn, self.isolation_level)
586
+
587
+ fns.append(on_connect)
588
+
589
+ # fns always has the notices function
590
+ def on_connect(conn):
591
+ for fn in fns:
592
+ fn(conn)
593
+
594
+ return on_connect
595
+
596
+ def is_disconnect(self, e, connection, cursor):
597
+ if isinstance(e, self.dbapi.Error) and connection is not None:
598
+ if connection.closed or connection.broken:
599
+ return True
600
+ return False
601
+
602
+ def _twophase_idle_check(self, dbapi_conn):
603
+ # don't rely on psycopg providing enum symbols, compare with eq/ne
604
+ return (
605
+ dbapi_conn.info.transaction_status
606
+ == self._psycopg_TransactionStatus.IDLE
607
+ )
608
+
609
+ @util.memoized_property
610
+ def _dialect_specific_select_one(self):
611
+ return ";"
612
+
613
+
614
+ class AsyncAdapt_psycopg_cursor(AsyncAdapt_dbapi_cursor):
615
+ __slots__ = ()
616
+
617
+ _awaitable_cursor_close: bool = False
618
+
619
+ def close(self):
620
+ self._rows.clear()
621
+ # Normal cursor just call _close() in a non-sync way.
622
+ self._cursor._close()
623
+
624
+ async def _execute_async(self, operation, parameters):
625
+ # override to not use mutex, psycopg3 already has mutex
626
+
627
+ if parameters is None:
628
+ result = await self._cursor.execute(operation)
629
+ else:
630
+ result = await self._cursor.execute(operation, parameters)
631
+
632
+ # sqlalchemy result is not async, so need to pull all rows here
633
+ # (assuming not a server side cursor)
634
+ res = self._cursor.pgresult
635
+
636
+ # don't rely on psycopg providing enum symbols, compare with
637
+ # eq/ne
638
+ if (
639
+ not self.server_side
640
+ and res
641
+ and res.status == self._adapt_connection.dbapi.ExecStatus.TUPLES_OK
642
+ ):
643
+ self._rows = collections.deque(await self._cursor.fetchall())
644
+ return result
645
+
646
+ async def _executemany_async(
647
+ self,
648
+ operation,
649
+ seq_of_parameters,
650
+ ):
651
+ # override to not use mutex, psycopg3 already has mutex
652
+ return await self._cursor.executemany(operation, seq_of_parameters)
653
+
654
+
655
+ class AsyncAdapt_psycopg_ss_cursor(
656
+ AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_psycopg_cursor
657
+ ):
658
+ __slots__ = ("name",)
659
+
660
+ name: str
661
+
662
+ def __init__(self, adapt_connection, name):
663
+ self.name = name
664
+ super().__init__(adapt_connection)
665
+
666
+ def _make_new_cursor(self, connection):
667
+ return connection.cursor(self.name)
668
+
669
+
670
+ class AsyncAdapt_psycopg_connection(AsyncAdapt_dbapi_connection):
671
+ _connection: AsyncConnection
672
+ __slots__ = ()
673
+
674
+ _cursor_cls = AsyncAdapt_psycopg_cursor
675
+ _ss_cursor_cls = AsyncAdapt_psycopg_ss_cursor
676
+
677
+ def add_notice_handler(self, handler):
678
+ self._connection.add_notice_handler(handler)
679
+
680
+ @property
681
+ def info(self):
682
+ return self._connection.info
683
+
684
+ @property
685
+ def adapters(self):
686
+ return self._connection.adapters
687
+
688
+ @property
689
+ def closed(self):
690
+ return self._connection.closed
691
+
692
+ @property
693
+ def broken(self):
694
+ return self._connection.broken
695
+
696
+ @property
697
+ def read_only(self):
698
+ return self._connection.read_only
699
+
700
+ @property
701
+ def deferrable(self):
702
+ return self._connection.deferrable
703
+
704
+ @property
705
+ def autocommit(self):
706
+ return self._connection.autocommit
707
+
708
+ @autocommit.setter
709
+ def autocommit(self, value):
710
+ self.set_autocommit(value)
711
+
712
+ def set_autocommit(self, value):
713
+ await_(self._connection.set_autocommit(value))
714
+
715
+ def set_isolation_level(self, value):
716
+ await_(self._connection.set_isolation_level(value))
717
+
718
+ def set_read_only(self, value):
719
+ await_(self._connection.set_read_only(value))
720
+
721
+ def set_deferrable(self, value):
722
+ await_(self._connection.set_deferrable(value))
723
+
724
+ def cursor(self, name=None, /):
725
+ if name:
726
+ return AsyncAdapt_psycopg_ss_cursor(self, name)
727
+ else:
728
+ return AsyncAdapt_psycopg_cursor(self)
729
+
730
+ def tpc_begin(self, xid):
731
+ return await_(self._connection.tpc_begin(xid))
732
+
733
+ def tpc_prepare(self):
734
+ return await_(self._connection.tpc_prepare())
735
+
736
+ def tpc_commit(self, xid=None):
737
+ return await_(self._connection.tpc_commit(xid))
738
+
739
+ def tpc_rollback(self, xid=None):
740
+ return await_(self._connection.tpc_rollback(xid))
741
+
742
+ def tpc_recover(self):
743
+ return await_(self._connection.tpc_recover())
744
+
745
+
746
+ class PsycopgAdaptDBAPI(AsyncAdapt_dbapi_module):
747
+ def __init__(self, psycopg, ExecStatus) -> None:
748
+ super().__init__(psycopg)
749
+ self.psycopg = psycopg
750
+ self.ExecStatus = ExecStatus
751
+
752
+ for k, v in self.psycopg.__dict__.items():
753
+ if k != "connect":
754
+ self.__dict__[k] = v
755
+
756
+ def connect(self, *arg, **kw):
757
+ creator_fn = kw.pop(
758
+ "async_creator_fn", self.psycopg.AsyncConnection.connect
759
+ )
760
+ return await_(
761
+ AsyncAdapt_psycopg_connection.create(self, creator_fn(*arg, **kw))
762
+ )
763
+
764
+
765
+ class PGDialectAsync_psycopg(PGDialect_psycopg):
766
+ is_async = True
767
+ supports_statement_cache = True
768
+
769
+ @classmethod
770
+ def import_dbapi(cls):
771
+ import psycopg
772
+ from psycopg.pq import ExecStatus
773
+
774
+ return PsycopgAdaptDBAPI(psycopg, ExecStatus)
775
+
776
+ def _type_info_fetch(self, connection, name):
777
+ from psycopg.types import TypeInfo
778
+
779
+ adapted = connection.connection
780
+ return await_(TypeInfo.fetch(adapted.driver_connection, name))
781
+
782
+ def _do_isolation_level(self, connection, autocommit, isolation_level):
783
+ connection.set_autocommit(autocommit)
784
+ connection.set_isolation_level(isolation_level)
785
+
786
+ def _do_autocommit(self, connection, value):
787
+ connection.set_autocommit(value)
788
+
789
+ def set_readonly(self, connection, value):
790
+ connection.set_read_only(value)
791
+
792
+ def set_deferrable(self, connection, value):
793
+ connection.set_deferrable(value)
794
+
795
+ def get_driver_connection(self, connection):
796
+ return connection._connection
797
+
798
+
799
+ dialect = PGDialect_psycopg
800
+ dialect_async = PGDialectAsync_psycopg