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