SQLAlchemy 2.1.0b1__cp313-cp313-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 (267) hide show
  1. sqlalchemy/__init__.py +295 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +161 -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 +88 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4110 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +129 -0
  13. sqlalchemy/dialects/mssql/provision.py +185 -0
  14. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  15. sqlalchemy/dialects/mssql/pyodbc.py +758 -0
  16. sqlalchemy/dialects/mysql/__init__.py +106 -0
  17. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  18. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  19. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  20. sqlalchemy/dialects/mysql/base.py +3870 -0
  21. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  22. sqlalchemy/dialects/mysql/dml.py +279 -0
  23. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  24. sqlalchemy/dialects/mysql/expression.py +146 -0
  25. sqlalchemy/dialects/mysql/json.py +91 -0
  26. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  27. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  28. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  29. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  30. sqlalchemy/dialects/mysql/provision.py +147 -0
  31. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  32. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  33. sqlalchemy/dialects/mysql/reflection.py +724 -0
  34. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  35. sqlalchemy/dialects/mysql/types.py +845 -0
  36. sqlalchemy/dialects/oracle/__init__.py +83 -0
  37. sqlalchemy/dialects/oracle/base.py +3871 -0
  38. sqlalchemy/dialects/oracle/cx_oracle.py +1522 -0
  39. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  40. sqlalchemy/dialects/oracle/oracledb.py +894 -0
  41. sqlalchemy/dialects/oracle/provision.py +288 -0
  42. sqlalchemy/dialects/oracle/types.py +350 -0
  43. sqlalchemy/dialects/oracle/vector.py +368 -0
  44. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  45. sqlalchemy/dialects/postgresql/_psycopg_common.py +193 -0
  46. sqlalchemy/dialects/postgresql/array.py +534 -0
  47. sqlalchemy/dialects/postgresql/asyncpg.py +1331 -0
  48. sqlalchemy/dialects/postgresql/base.py +5729 -0
  49. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  50. sqlalchemy/dialects/postgresql/dml.py +360 -0
  51. sqlalchemy/dialects/postgresql/ext.py +593 -0
  52. sqlalchemy/dialects/postgresql/hstore.py +413 -0
  53. sqlalchemy/dialects/postgresql/json.py +407 -0
  54. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  55. sqlalchemy/dialects/postgresql/operators.py +130 -0
  56. sqlalchemy/dialects/postgresql/pg8000.py +672 -0
  57. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  58. sqlalchemy/dialects/postgresql/provision.py +175 -0
  59. sqlalchemy/dialects/postgresql/psycopg.py +815 -0
  60. sqlalchemy/dialects/postgresql/psycopg2.py +887 -0
  61. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  62. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  63. sqlalchemy/dialects/postgresql/types.py +388 -0
  64. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  65. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  66. sqlalchemy/dialects/sqlite/base.py +3050 -0
  67. sqlalchemy/dialects/sqlite/dml.py +279 -0
  68. sqlalchemy/dialects/sqlite/json.py +89 -0
  69. sqlalchemy/dialects/sqlite/provision.py +223 -0
  70. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  71. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  72. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  73. sqlalchemy/engine/__init__.py +62 -0
  74. sqlalchemy/engine/_processors_cy.cp313-win_arm64.pyd +0 -0
  75. sqlalchemy/engine/_processors_cy.py +92 -0
  76. sqlalchemy/engine/_result_cy.cp313-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_result_cy.py +633 -0
  78. sqlalchemy/engine/_row_cy.cp313-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_row_cy.py +232 -0
  80. sqlalchemy/engine/_util_cy.cp313-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_util_cy.py +136 -0
  82. sqlalchemy/engine/base.py +3334 -0
  83. sqlalchemy/engine/characteristics.py +155 -0
  84. sqlalchemy/engine/create.py +869 -0
  85. sqlalchemy/engine/cursor.py +2416 -0
  86. sqlalchemy/engine/default.py +2393 -0
  87. sqlalchemy/engine/events.py +965 -0
  88. sqlalchemy/engine/interfaces.py +3465 -0
  89. sqlalchemy/engine/mock.py +134 -0
  90. sqlalchemy/engine/processors.py +82 -0
  91. sqlalchemy/engine/reflection.py +2100 -0
  92. sqlalchemy/engine/result.py +1932 -0
  93. sqlalchemy/engine/row.py +397 -0
  94. sqlalchemy/engine/strategies.py +16 -0
  95. sqlalchemy/engine/url.py +922 -0
  96. sqlalchemy/engine/util.py +156 -0
  97. sqlalchemy/event/__init__.py +26 -0
  98. sqlalchemy/event/api.py +220 -0
  99. sqlalchemy/event/attr.py +674 -0
  100. sqlalchemy/event/base.py +472 -0
  101. sqlalchemy/event/legacy.py +258 -0
  102. sqlalchemy/event/registry.py +390 -0
  103. sqlalchemy/events.py +17 -0
  104. sqlalchemy/exc.py +922 -0
  105. sqlalchemy/ext/__init__.py +11 -0
  106. sqlalchemy/ext/associationproxy.py +2072 -0
  107. sqlalchemy/ext/asyncio/__init__.py +29 -0
  108. sqlalchemy/ext/asyncio/base.py +281 -0
  109. sqlalchemy/ext/asyncio/engine.py +1475 -0
  110. sqlalchemy/ext/asyncio/exc.py +21 -0
  111. sqlalchemy/ext/asyncio/result.py +994 -0
  112. sqlalchemy/ext/asyncio/scoping.py +1667 -0
  113. sqlalchemy/ext/asyncio/session.py +1993 -0
  114. sqlalchemy/ext/automap.py +1701 -0
  115. sqlalchemy/ext/baked.py +559 -0
  116. sqlalchemy/ext/compiler.py +600 -0
  117. sqlalchemy/ext/declarative/__init__.py +65 -0
  118. sqlalchemy/ext/declarative/extensions.py +560 -0
  119. sqlalchemy/ext/horizontal_shard.py +481 -0
  120. sqlalchemy/ext/hybrid.py +1877 -0
  121. sqlalchemy/ext/indexable.py +364 -0
  122. sqlalchemy/ext/instrumentation.py +450 -0
  123. sqlalchemy/ext/mutable.py +1081 -0
  124. sqlalchemy/ext/orderinglist.py +439 -0
  125. sqlalchemy/ext/serializer.py +185 -0
  126. sqlalchemy/future/__init__.py +16 -0
  127. sqlalchemy/future/engine.py +15 -0
  128. sqlalchemy/inspection.py +174 -0
  129. sqlalchemy/log.py +283 -0
  130. sqlalchemy/orm/__init__.py +175 -0
  131. sqlalchemy/orm/_orm_constructors.py +2694 -0
  132. sqlalchemy/orm/_typing.py +179 -0
  133. sqlalchemy/orm/attributes.py +2868 -0
  134. sqlalchemy/orm/base.py +970 -0
  135. sqlalchemy/orm/bulk_persistence.py +2152 -0
  136. sqlalchemy/orm/clsregistry.py +582 -0
  137. sqlalchemy/orm/collections.py +1568 -0
  138. sqlalchemy/orm/context.py +3471 -0
  139. sqlalchemy/orm/decl_api.py +2257 -0
  140. sqlalchemy/orm/decl_base.py +2304 -0
  141. sqlalchemy/orm/dependency.py +1306 -0
  142. sqlalchemy/orm/descriptor_props.py +1183 -0
  143. sqlalchemy/orm/dynamic.py +300 -0
  144. sqlalchemy/orm/evaluator.py +379 -0
  145. sqlalchemy/orm/events.py +3386 -0
  146. sqlalchemy/orm/exc.py +237 -0
  147. sqlalchemy/orm/identity.py +302 -0
  148. sqlalchemy/orm/instrumentation.py +746 -0
  149. sqlalchemy/orm/interfaces.py +1589 -0
  150. sqlalchemy/orm/loading.py +1684 -0
  151. sqlalchemy/orm/mapped_collection.py +557 -0
  152. sqlalchemy/orm/mapper.py +4406 -0
  153. sqlalchemy/orm/path_registry.py +814 -0
  154. sqlalchemy/orm/persistence.py +1789 -0
  155. sqlalchemy/orm/properties.py +973 -0
  156. sqlalchemy/orm/query.py +3521 -0
  157. sqlalchemy/orm/relationships.py +3570 -0
  158. sqlalchemy/orm/scoping.py +2220 -0
  159. sqlalchemy/orm/session.py +5389 -0
  160. sqlalchemy/orm/state.py +1175 -0
  161. sqlalchemy/orm/state_changes.py +196 -0
  162. sqlalchemy/orm/strategies.py +3480 -0
  163. sqlalchemy/orm/strategy_options.py +2544 -0
  164. sqlalchemy/orm/sync.py +164 -0
  165. sqlalchemy/orm/unitofwork.py +798 -0
  166. sqlalchemy/orm/util.py +2435 -0
  167. sqlalchemy/orm/writeonly.py +694 -0
  168. sqlalchemy/pool/__init__.py +41 -0
  169. sqlalchemy/pool/base.py +1514 -0
  170. sqlalchemy/pool/events.py +372 -0
  171. sqlalchemy/pool/impl.py +582 -0
  172. sqlalchemy/py.typed +0 -0
  173. sqlalchemy/schema.py +72 -0
  174. sqlalchemy/sql/__init__.py +153 -0
  175. sqlalchemy/sql/_dml_constructors.py +132 -0
  176. sqlalchemy/sql/_elements_constructors.py +2147 -0
  177. sqlalchemy/sql/_orm_types.py +20 -0
  178. sqlalchemy/sql/_selectable_constructors.py +773 -0
  179. sqlalchemy/sql/_typing.py +486 -0
  180. sqlalchemy/sql/_util_cy.cp313-win_arm64.pyd +0 -0
  181. sqlalchemy/sql/_util_cy.py +127 -0
  182. sqlalchemy/sql/annotation.py +590 -0
  183. sqlalchemy/sql/base.py +2602 -0
  184. sqlalchemy/sql/cache_key.py +1066 -0
  185. sqlalchemy/sql/coercions.py +1373 -0
  186. sqlalchemy/sql/compiler.py +8259 -0
  187. sqlalchemy/sql/crud.py +1807 -0
  188. sqlalchemy/sql/ddl.py +1928 -0
  189. sqlalchemy/sql/default_comparator.py +654 -0
  190. sqlalchemy/sql/dml.py +1974 -0
  191. sqlalchemy/sql/elements.py +6016 -0
  192. sqlalchemy/sql/events.py +458 -0
  193. sqlalchemy/sql/expression.py +170 -0
  194. sqlalchemy/sql/functions.py +2257 -0
  195. sqlalchemy/sql/lambdas.py +1443 -0
  196. sqlalchemy/sql/naming.py +209 -0
  197. sqlalchemy/sql/operators.py +2897 -0
  198. sqlalchemy/sql/roles.py +332 -0
  199. sqlalchemy/sql/schema.py +6560 -0
  200. sqlalchemy/sql/selectable.py +7497 -0
  201. sqlalchemy/sql/sqltypes.py +4050 -0
  202. sqlalchemy/sql/traversals.py +1042 -0
  203. sqlalchemy/sql/type_api.py +2425 -0
  204. sqlalchemy/sql/util.py +1495 -0
  205. sqlalchemy/sql/visitors.py +1157 -0
  206. sqlalchemy/testing/__init__.py +96 -0
  207. sqlalchemy/testing/assertions.py +1007 -0
  208. sqlalchemy/testing/assertsql.py +519 -0
  209. sqlalchemy/testing/asyncio.py +128 -0
  210. sqlalchemy/testing/config.py +440 -0
  211. sqlalchemy/testing/engines.py +478 -0
  212. sqlalchemy/testing/entities.py +117 -0
  213. sqlalchemy/testing/exclusions.py +476 -0
  214. sqlalchemy/testing/fixtures/__init__.py +30 -0
  215. sqlalchemy/testing/fixtures/base.py +366 -0
  216. sqlalchemy/testing/fixtures/mypy.py +247 -0
  217. sqlalchemy/testing/fixtures/orm.py +227 -0
  218. sqlalchemy/testing/fixtures/sql.py +538 -0
  219. sqlalchemy/testing/pickleable.py +155 -0
  220. sqlalchemy/testing/plugin/__init__.py +6 -0
  221. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  222. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  223. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  224. sqlalchemy/testing/profiling.py +329 -0
  225. sqlalchemy/testing/provision.py +596 -0
  226. sqlalchemy/testing/requirements.py +1973 -0
  227. sqlalchemy/testing/schema.py +198 -0
  228. sqlalchemy/testing/suite/__init__.py +19 -0
  229. sqlalchemy/testing/suite/test_cte.py +237 -0
  230. sqlalchemy/testing/suite/test_ddl.py +420 -0
  231. sqlalchemy/testing/suite/test_dialect.py +776 -0
  232. sqlalchemy/testing/suite/test_insert.py +630 -0
  233. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  234. sqlalchemy/testing/suite/test_results.py +660 -0
  235. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  236. sqlalchemy/testing/suite/test_select.py +2112 -0
  237. sqlalchemy/testing/suite/test_sequence.py +317 -0
  238. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  239. sqlalchemy/testing/suite/test_types.py +2253 -0
  240. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  241. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  242. sqlalchemy/testing/util.py +535 -0
  243. sqlalchemy/testing/warnings.py +52 -0
  244. sqlalchemy/types.py +76 -0
  245. sqlalchemy/util/__init__.py +157 -0
  246. sqlalchemy/util/_collections.py +693 -0
  247. sqlalchemy/util/_collections_cy.cp313-win_arm64.pyd +0 -0
  248. sqlalchemy/util/_collections_cy.pxd +8 -0
  249. sqlalchemy/util/_collections_cy.py +516 -0
  250. sqlalchemy/util/_has_cython.py +46 -0
  251. sqlalchemy/util/_immutabledict_cy.cp313-win_arm64.pyd +0 -0
  252. sqlalchemy/util/_immutabledict_cy.py +240 -0
  253. sqlalchemy/util/compat.py +287 -0
  254. sqlalchemy/util/concurrency.py +322 -0
  255. sqlalchemy/util/cython.py +79 -0
  256. sqlalchemy/util/deprecations.py +401 -0
  257. sqlalchemy/util/langhelpers.py +2256 -0
  258. sqlalchemy/util/preloaded.py +152 -0
  259. sqlalchemy/util/queue.py +304 -0
  260. sqlalchemy/util/tool_support.py +201 -0
  261. sqlalchemy/util/topological.py +120 -0
  262. sqlalchemy/util/typing.py +711 -0
  263. sqlalchemy-2.1.0b1.dist-info/METADATA +267 -0
  264. sqlalchemy-2.1.0b1.dist-info/RECORD +267 -0
  265. sqlalchemy-2.1.0b1.dist-info/WHEEL +5 -0
  266. sqlalchemy-2.1.0b1.dist-info/licenses/LICENSE +19 -0
  267. sqlalchemy-2.1.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,815 @@
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
+ return self._make_bind_processor(None, dialect._psycopg_Json)
218
+
219
+ def result_processor(self, dialect, coltype):
220
+ return None
221
+
222
+
223
+ class _PGJSONB(JSONB):
224
+ def bind_processor(self, dialect):
225
+ return self._make_bind_processor(None, dialect._psycopg_Jsonb)
226
+
227
+ def result_processor(self, dialect, coltype):
228
+ return None
229
+
230
+
231
+ class _PGJSONIntIndexType(sqltypes.JSON.JSONIntIndexType):
232
+ __visit_name__ = "json_int_index"
233
+
234
+ render_bind_cast = True
235
+
236
+
237
+ class _PGJSONStrIndexType(sqltypes.JSON.JSONStrIndexType):
238
+ __visit_name__ = "json_str_index"
239
+
240
+ render_bind_cast = True
241
+
242
+
243
+ class _PGJSONPathType(JSONPathType):
244
+ pass
245
+
246
+
247
+ class _PGInterval(INTERVAL):
248
+ render_bind_cast = True
249
+
250
+
251
+ class _PGTimeStamp(sqltypes.DateTime):
252
+ render_bind_cast = True
253
+
254
+
255
+ class _PGDate(sqltypes.Date):
256
+ render_bind_cast = True
257
+
258
+
259
+ class _PGTime(sqltypes.Time):
260
+ render_bind_cast = True
261
+
262
+
263
+ class _PGInteger(sqltypes.Integer):
264
+ render_bind_cast = True
265
+
266
+
267
+ class _PGSmallInteger(sqltypes.SmallInteger):
268
+ render_bind_cast = True
269
+
270
+
271
+ class _PGNullType(sqltypes.NullType):
272
+ render_bind_cast = True
273
+
274
+
275
+ class _PGBigInteger(sqltypes.BigInteger):
276
+ render_bind_cast = True
277
+
278
+
279
+ class _PGBoolean(sqltypes.Boolean):
280
+ render_bind_cast = True
281
+
282
+
283
+ class _PsycopgRange(ranges.AbstractSingleRangeImpl):
284
+ def bind_processor(self, dialect):
285
+ psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
286
+
287
+ def to_range(value):
288
+ if isinstance(value, ranges.Range):
289
+ value = psycopg_Range(
290
+ value.lower, value.upper, value.bounds, value.empty
291
+ )
292
+ return value
293
+
294
+ return to_range
295
+
296
+ def result_processor(self, dialect, coltype):
297
+ def to_range(value):
298
+ if value is not None:
299
+ value = ranges.Range(
300
+ value._lower,
301
+ value._upper,
302
+ bounds=value._bounds if value._bounds else "[)",
303
+ empty=not value._bounds,
304
+ )
305
+ return value
306
+
307
+ return to_range
308
+
309
+
310
+ class _PsycopgMultiRange(ranges.AbstractMultiRangeImpl):
311
+ def bind_processor(self, dialect):
312
+ psycopg_Range = cast(PGDialect_psycopg, dialect)._psycopg_Range
313
+ psycopg_Multirange = cast(
314
+ PGDialect_psycopg, dialect
315
+ )._psycopg_Multirange
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
+ with dbapi_conn.cursor() as cursor:
614
+ cursor.execute(command)
615
+ finally:
616
+ if not before_autocommit:
617
+ self._do_autocommit(dbapi_conn, before_autocommit)
618
+
619
+ def do_rollback_twophase(
620
+ self, connection, xid, is_prepared=True, recover=False
621
+ ):
622
+ if is_prepared:
623
+ self._do_prepared_twophase(
624
+ connection, f"ROLLBACK PREPARED '{xid}'", recover=recover
625
+ )
626
+ else:
627
+ self.do_rollback(connection.connection)
628
+
629
+ def do_commit_twophase(
630
+ self, connection, xid, is_prepared=True, recover=False
631
+ ):
632
+ if is_prepared:
633
+ self._do_prepared_twophase(
634
+ connection, f"COMMIT PREPARED '{xid}'", recover=recover
635
+ )
636
+ else:
637
+ self.do_commit(connection.connection)
638
+
639
+ @util.memoized_property
640
+ def _dialect_specific_select_one(self):
641
+ return ";"
642
+
643
+
644
+ class AsyncAdapt_psycopg_cursor(AsyncAdapt_dbapi_cursor):
645
+ __slots__ = ()
646
+
647
+ _awaitable_cursor_close: bool = False
648
+
649
+ def close(self):
650
+ self._rows.clear()
651
+ # Normal cursor just call _close() in a non-sync way.
652
+ self._cursor._close()
653
+
654
+ async def _execute_async(self, operation, parameters):
655
+ # override to not use mutex, psycopg3 already has mutex
656
+
657
+ if parameters is None:
658
+ result = await self._cursor.execute(operation)
659
+ else:
660
+ result = await self._cursor.execute(operation, parameters)
661
+
662
+ # sqlalchemy result is not async, so need to pull all rows here
663
+ # (assuming not a server side cursor)
664
+ res = self._cursor.pgresult
665
+
666
+ # don't rely on psycopg providing enum symbols, compare with
667
+ # eq/ne
668
+ if (
669
+ not self.server_side
670
+ and res
671
+ and res.status == self._adapt_connection.dbapi.ExecStatus.TUPLES_OK
672
+ ):
673
+ self._rows = collections.deque(await self._cursor.fetchall())
674
+ return result
675
+
676
+ async def _executemany_async(
677
+ self,
678
+ operation,
679
+ seq_of_parameters,
680
+ ):
681
+ # override to not use mutex, psycopg3 already has mutex
682
+ return await self._cursor.executemany(operation, seq_of_parameters)
683
+
684
+
685
+ class AsyncAdapt_psycopg_ss_cursor(
686
+ AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_psycopg_cursor
687
+ ):
688
+ __slots__ = ("name",)
689
+
690
+ name: str
691
+
692
+ def __init__(self, adapt_connection, name):
693
+ self.name = name
694
+ super().__init__(adapt_connection)
695
+
696
+ def _make_new_cursor(self, connection):
697
+ return connection.cursor(self.name)
698
+
699
+
700
+ class AsyncAdapt_psycopg_connection(AsyncAdapt_dbapi_connection):
701
+ _connection: AsyncConnection
702
+ __slots__ = ()
703
+
704
+ _cursor_cls = AsyncAdapt_psycopg_cursor
705
+ _ss_cursor_cls = AsyncAdapt_psycopg_ss_cursor
706
+
707
+ def add_notice_handler(self, handler):
708
+ self._connection.add_notice_handler(handler)
709
+
710
+ @property
711
+ def info(self):
712
+ return self._connection.info
713
+
714
+ @property
715
+ def adapters(self):
716
+ return self._connection.adapters
717
+
718
+ @property
719
+ def closed(self):
720
+ return self._connection.closed
721
+
722
+ @property
723
+ def broken(self):
724
+ return self._connection.broken
725
+
726
+ @property
727
+ def read_only(self):
728
+ return self._connection.read_only
729
+
730
+ @property
731
+ def deferrable(self):
732
+ return self._connection.deferrable
733
+
734
+ @property
735
+ def autocommit(self):
736
+ return self._connection.autocommit
737
+
738
+ @autocommit.setter
739
+ def autocommit(self, value):
740
+ self.set_autocommit(value)
741
+
742
+ def set_autocommit(self, value):
743
+ await_(self._connection.set_autocommit(value))
744
+
745
+ def set_isolation_level(self, value):
746
+ await_(self._connection.set_isolation_level(value))
747
+
748
+ def set_read_only(self, value):
749
+ await_(self._connection.set_read_only(value))
750
+
751
+ def set_deferrable(self, value):
752
+ await_(self._connection.set_deferrable(value))
753
+
754
+ def cursor(self, name=None, /):
755
+ if name:
756
+ return AsyncAdapt_psycopg_ss_cursor(self, name)
757
+ else:
758
+ return AsyncAdapt_psycopg_cursor(self)
759
+
760
+
761
+ class PsycopgAdaptDBAPI(AsyncAdapt_dbapi_module):
762
+ def __init__(self, psycopg, ExecStatus) -> None:
763
+ super().__init__(psycopg)
764
+ self.psycopg = psycopg
765
+ self.ExecStatus = ExecStatus
766
+
767
+ for k, v in self.psycopg.__dict__.items():
768
+ if k != "connect":
769
+ self.__dict__[k] = v
770
+
771
+ def connect(self, *arg, **kw):
772
+ creator_fn = kw.pop(
773
+ "async_creator_fn", self.psycopg.AsyncConnection.connect
774
+ )
775
+ return await_(
776
+ AsyncAdapt_psycopg_connection.create(self, creator_fn(*arg, **kw))
777
+ )
778
+
779
+
780
+ class PGDialectAsync_psycopg(PGDialect_psycopg):
781
+ is_async = True
782
+ supports_statement_cache = True
783
+
784
+ @classmethod
785
+ def import_dbapi(cls):
786
+ import psycopg
787
+ from psycopg.pq import ExecStatus
788
+
789
+ return PsycopgAdaptDBAPI(psycopg, ExecStatus)
790
+
791
+ def _type_info_fetch(self, connection, name):
792
+ from psycopg.types import TypeInfo
793
+
794
+ adapted = connection.connection
795
+ return await_(TypeInfo.fetch(adapted.driver_connection, name))
796
+
797
+ def _do_isolation_level(self, connection, autocommit, isolation_level):
798
+ connection.set_autocommit(autocommit)
799
+ connection.set_isolation_level(isolation_level)
800
+
801
+ def _do_autocommit(self, connection, value):
802
+ connection.set_autocommit(value)
803
+
804
+ def set_readonly(self, connection, value):
805
+ connection.set_read_only(value)
806
+
807
+ def set_deferrable(self, connection, value):
808
+ connection.set_deferrable(value)
809
+
810
+ def get_driver_connection(self, connection):
811
+ return connection._connection
812
+
813
+
814
+ dialect = PGDialect_psycopg
815
+ dialect_async = PGDialectAsync_psycopg