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.
- sqlalchemy/__init__.py +298 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +171 -0
- sqlalchemy/connectors/asyncio.py +476 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +89 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4166 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +140 -0
- sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
- sqlalchemy/dialects/mssql/provision.py +196 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +698 -0
- sqlalchemy/dialects/mysql/__init__.py +106 -0
- sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
- sqlalchemy/dialects/mysql/aiomysql.py +226 -0
- sqlalchemy/dialects/mysql/asyncmy.py +214 -0
- sqlalchemy/dialects/mysql/base.py +3877 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +279 -0
- sqlalchemy/dialects/mysql/enumerated.py +277 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +92 -0
- sqlalchemy/dialects/mysql/mariadb.py +67 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
- sqlalchemy/dialects/mysql/mysqldb.py +312 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +157 -0
- sqlalchemy/dialects/mysql/pyodbc.py +156 -0
- sqlalchemy/dialects/mysql/reflection.py +724 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +845 -0
- sqlalchemy/dialects/oracle/__init__.py +85 -0
- sqlalchemy/dialects/oracle/base.py +3977 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/json.py +158 -0
- sqlalchemy/dialects/oracle/oracledb.py +909 -0
- sqlalchemy/dialects/oracle/provision.py +288 -0
- sqlalchemy/dialects/oracle/types.py +367 -0
- sqlalchemy/dialects/oracle/vector.py +368 -0
- sqlalchemy/dialects/postgresql/__init__.py +171 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
- sqlalchemy/dialects/postgresql/array.py +534 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
- sqlalchemy/dialects/postgresql/base.py +5789 -0
- sqlalchemy/dialects/postgresql/bitstring.py +327 -0
- sqlalchemy/dialects/postgresql/dml.py +360 -0
- sqlalchemy/dialects/postgresql/ext.py +593 -0
- sqlalchemy/dialects/postgresql/hstore.py +423 -0
- sqlalchemy/dialects/postgresql/json.py +408 -0
- sqlalchemy/dialects/postgresql/named_types.py +521 -0
- sqlalchemy/dialects/postgresql/operators.py +130 -0
- sqlalchemy/dialects/postgresql/pg8000.py +670 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
- sqlalchemy/dialects/postgresql/provision.py +184 -0
- sqlalchemy/dialects/postgresql/psycopg.py +799 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1002 -0
- sqlalchemy/dialects/postgresql/types.py +388 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
- sqlalchemy/dialects/sqlite/base.py +3063 -0
- sqlalchemy/dialects/sqlite/dml.py +279 -0
- sqlalchemy/dialects/sqlite/json.py +100 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_processors_cy.py +92 -0
- sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_result_cy.py +633 -0
- sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_row_cy.py +232 -0
- sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/engine/_util_cy.py +136 -0
- sqlalchemy/engine/base.py +3354 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +877 -0
- sqlalchemy/engine/cursor.py +2421 -0
- sqlalchemy/engine/default.py +2402 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3495 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +82 -0
- sqlalchemy/engine/reflection.py +2100 -0
- sqlalchemy/engine/result.py +1966 -0
- sqlalchemy/engine/row.py +397 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +922 -0
- sqlalchemy/engine/util.py +156 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +674 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +922 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2072 -0
- sqlalchemy/ext/asyncio/__init__.py +29 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1487 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +994 -0
- sqlalchemy/ext/asyncio/scoping.py +1679 -0
- sqlalchemy/ext/asyncio/session.py +2007 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +559 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +560 -0
- sqlalchemy/ext/horizontal_shard.py +481 -0
- sqlalchemy/ext/hybrid.py +1877 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1081 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +283 -0
- sqlalchemy/orm/__init__.py +176 -0
- sqlalchemy/orm/_orm_constructors.py +2694 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2868 -0
- sqlalchemy/orm/base.py +976 -0
- sqlalchemy/orm/bulk_persistence.py +2152 -0
- sqlalchemy/orm/clsregistry.py +582 -0
- sqlalchemy/orm/collections.py +1568 -0
- sqlalchemy/orm/context.py +3471 -0
- sqlalchemy/orm/decl_api.py +2280 -0
- sqlalchemy/orm/decl_base.py +2309 -0
- sqlalchemy/orm/dependency.py +1306 -0
- sqlalchemy/orm/descriptor_props.py +1183 -0
- sqlalchemy/orm/dynamic.py +307 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3386 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +746 -0
- sqlalchemy/orm/interfaces.py +1589 -0
- sqlalchemy/orm/loading.py +1684 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4411 -0
- sqlalchemy/orm/path_registry.py +829 -0
- sqlalchemy/orm/persistence.py +1789 -0
- sqlalchemy/orm/properties.py +973 -0
- sqlalchemy/orm/query.py +3528 -0
- sqlalchemy/orm/relationships.py +3570 -0
- sqlalchemy/orm/scoping.py +2232 -0
- sqlalchemy/orm/session.py +5403 -0
- sqlalchemy/orm/state.py +1175 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3492 -0
- sqlalchemy/orm/strategy_options.py +2562 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +798 -0
- sqlalchemy/orm/util.py +2438 -0
- sqlalchemy/orm/writeonly.py +694 -0
- sqlalchemy/pool/__init__.py +41 -0
- sqlalchemy/pool/base.py +1522 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +582 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +74 -0
- sqlalchemy/sql/__init__.py +156 -0
- sqlalchemy/sql/_annotated_cols.py +397 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +2164 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_selectable_constructors.py +840 -0
- sqlalchemy/sql/_typing.py +487 -0
- sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/sql/_util_cy.py +127 -0
- sqlalchemy/sql/annotation.py +590 -0
- sqlalchemy/sql/base.py +2699 -0
- sqlalchemy/sql/cache_key.py +1066 -0
- sqlalchemy/sql/coercions.py +1373 -0
- sqlalchemy/sql/compiler.py +8327 -0
- sqlalchemy/sql/crud.py +1815 -0
- sqlalchemy/sql/ddl.py +1928 -0
- sqlalchemy/sql/default_comparator.py +654 -0
- sqlalchemy/sql/dml.py +1977 -0
- sqlalchemy/sql/elements.py +6033 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +172 -0
- sqlalchemy/sql/functions.py +2305 -0
- sqlalchemy/sql/lambdas.py +1443 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2897 -0
- sqlalchemy/sql/roles.py +332 -0
- sqlalchemy/sql/schema.py +6703 -0
- sqlalchemy/sql/selectable.py +7553 -0
- sqlalchemy/sql/sqltypes.py +4093 -0
- sqlalchemy/sql/traversals.py +1042 -0
- sqlalchemy/sql/type_api.py +2446 -0
- sqlalchemy/sql/util.py +1495 -0
- sqlalchemy/sql/visitors.py +1157 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +1007 -0
- sqlalchemy/testing/assertsql.py +519 -0
- sqlalchemy/testing/asyncio.py +128 -0
- sqlalchemy/testing/config.py +440 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +30 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +247 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +538 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +613 -0
- sqlalchemy/testing/requirements.py +1978 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +420 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +660 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2112 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_table_via_select.py +686 -0
- sqlalchemy/testing/suite/test_types.py +2271 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +76 -0
- sqlalchemy/util/__init__.py +158 -0
- sqlalchemy/util/_collections.py +688 -0
- sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/util/_collections_cy.pxd +8 -0
- sqlalchemy/util/_collections_cy.py +516 -0
- sqlalchemy/util/_has_cython.py +46 -0
- sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
- sqlalchemy/util/_immutabledict_cy.py +240 -0
- sqlalchemy/util/compat.py +299 -0
- sqlalchemy/util/concurrency.py +322 -0
- sqlalchemy/util/cython.py +79 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2320 -0
- sqlalchemy/util/preloaded.py +152 -0
- sqlalchemy/util/queue.py +304 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +711 -0
- sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
- sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
- sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
- sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
- 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
|