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