SQLAlchemy 2.1.0rc1__cp315-cp315-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +299 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +171 -0
- sqlalchemy/connectors/asyncio.py +476 -0
- sqlalchemy/connectors/pyodbc.py +248 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +29 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4833 -0
- sqlalchemy/dialects/mssql/information_schema.py +345 -0
- sqlalchemy/dialects/mssql/json.py +140 -0
- sqlalchemy/dialects/mssql/mssqlpython.py +242 -0
- sqlalchemy/dialects/mssql/provision.py +196 -0
- sqlalchemy/dialects/mssql/pymssql.py +130 -0
- sqlalchemy/dialects/mssql/pyodbc.py +697 -0
- sqlalchemy/dialects/mysql/__init__.py +106 -0
- sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
- sqlalchemy/dialects/mysql/aiomysql.py +260 -0
- sqlalchemy/dialects/mysql/asyncmy.py +241 -0
- sqlalchemy/dialects/mysql/base.py +3896 -0
- sqlalchemy/dialects/mysql/cymysql.py +107 -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 +314 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +291 -0
- sqlalchemy/dialects/mysql/mysqldb.py +318 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +188 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -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 +3847 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1736 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/json.py +157 -0
- sqlalchemy/dialects/oracle/oracledb.py +898 -0
- sqlalchemy/dialects/oracle/provision.py +288 -0
- sqlalchemy/dialects/oracle/types.py +367 -0
- sqlalchemy/dialects/oracle/vector.py +366 -0
- sqlalchemy/dialects/postgresql/__init__.py +170 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +232 -0
- sqlalchemy/dialects/postgresql/array.py +534 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1318 -0
- sqlalchemy/dialects/postgresql/base.py +5935 -0
- sqlalchemy/dialects/postgresql/bitstring.py +327 -0
- sqlalchemy/dialects/postgresql/dml.py +360 -0
- sqlalchemy/dialects/postgresql/ext.py +599 -0
- sqlalchemy/dialects/postgresql/hstore.py +422 -0
- sqlalchemy/dialects/postgresql/json.py +411 -0
- sqlalchemy/dialects/postgresql/named_types.py +535 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +655 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +345 -0
- sqlalchemy/dialects/postgresql/provision.py +202 -0
- sqlalchemy/dialects/postgresql/psycopg.py +800 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +62 -0
- sqlalchemy/dialects/postgresql/ranges.py +1002 -0
- sqlalchemy/dialects/postgresql/types.py +388 -0
- sqlalchemy/dialects/sqlite/__init__.py +59 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +375 -0
- sqlalchemy/dialects/sqlite/base.py +3103 -0
- sqlalchemy/dialects/sqlite/dml.py +314 -0
- sqlalchemy/dialects/sqlite/json.py +134 -0
- sqlalchemy/dialects/sqlite/provision.py +237 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +166 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +959 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_processors_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/engine/_processors_cy.py +92 -0
- sqlalchemy/engine/_result_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/engine/_result_cy.py +711 -0
- sqlalchemy/engine/_row_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/engine/_row_cy.py +232 -0
- sqlalchemy/engine/_util_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/engine/_util_cy.py +136 -0
- sqlalchemy/engine/base.py +3357 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +877 -0
- sqlalchemy/engine/cursor.py +2425 -0
- sqlalchemy/engine/default.py +2627 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3636 -0
- sqlalchemy/engine/mock.py +133 -0
- sqlalchemy/engine/processors.py +83 -0
- sqlalchemy/engine/reflection.py +2141 -0
- sqlalchemy/engine/result.py +2012 -0
- sqlalchemy/engine/row.py +397 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +922 -0
- sqlalchemy/engine/util.py +164 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +675 -0
- sqlalchemy/event/base.py +473 -0
- sqlalchemy/event/legacy.py +259 -0
- sqlalchemy/event/registry.py +391 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +939 -0
- sqlalchemy/ext/__init__.py +10 -0
- sqlalchemy/ext/associationproxy.py +2073 -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 +2006 -0
- sqlalchemy/ext/automap.py +1702 -0
- sqlalchemy/ext/baked.py +558 -0
- sqlalchemy/ext/compiler.py +601 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +561 -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 +440 -0
- sqlalchemy/ext/serializer.py +184 -0
- sqlalchemy/future/__init__.py +17 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +188 -0
- sqlalchemy/log.py +279 -0
- sqlalchemy/orm/__init__.py +176 -0
- sqlalchemy/orm/_orm_constructors.py +2694 -0
- sqlalchemy/orm/_typing.py +180 -0
- sqlalchemy/orm/attributes.py +2868 -0
- sqlalchemy/orm/base.py +991 -0
- sqlalchemy/orm/bulk_persistence.py +2168 -0
- sqlalchemy/orm/clsregistry.py +630 -0
- sqlalchemy/orm/collections.py +1569 -0
- sqlalchemy/orm/context.py +3475 -0
- sqlalchemy/orm/decl_api.py +2283 -0
- sqlalchemy/orm/decl_base.py +2320 -0
- sqlalchemy/orm/dependency.py +1306 -0
- sqlalchemy/orm/descriptor_props.py +1183 -0
- sqlalchemy/orm/dynamic.py +306 -0
- sqlalchemy/orm/evaluator.py +378 -0
- sqlalchemy/orm/events.py +3387 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +749 -0
- sqlalchemy/orm/interfaces.py +1595 -0
- sqlalchemy/orm/loading.py +1712 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4465 -0
- sqlalchemy/orm/path_registry.py +907 -0
- sqlalchemy/orm/persistence.py +1790 -0
- sqlalchemy/orm/properties.py +972 -0
- sqlalchemy/orm/query.py +3528 -0
- sqlalchemy/orm/relationships.py +3608 -0
- sqlalchemy/orm/scoping.py +2233 -0
- sqlalchemy/orm/session.py +5468 -0
- sqlalchemy/orm/state.py +1175 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3552 -0
- sqlalchemy/orm/strategy_options.py +2648 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +797 -0
- sqlalchemy/orm/util.py +2461 -0
- sqlalchemy/orm/writeonly.py +701 -0
- sqlalchemy/pool/__init__.py +41 -0
- sqlalchemy/pool/base.py +1540 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +583 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +75 -0
- sqlalchemy/sql/__init__.py +156 -0
- sqlalchemy/sql/_annotated_cols.py +402 -0
- sqlalchemy/sql/_cache_key_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/sql/_cache_key_cy.py +363 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +2190 -0
- sqlalchemy/sql/_orm_types.py +19 -0
- sqlalchemy/sql/_selectable_constructors.py +840 -0
- sqlalchemy/sql/_typing.py +500 -0
- sqlalchemy/sql/_util_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/sql/_util_cy.pxd +11 -0
- sqlalchemy/sql/_util_cy.py +127 -0
- sqlalchemy/sql/annotation.py +590 -0
- sqlalchemy/sql/base.py +2702 -0
- sqlalchemy/sql/cache_key.py +915 -0
- sqlalchemy/sql/coercions.py +1373 -0
- sqlalchemy/sql/compiler.py +8453 -0
- sqlalchemy/sql/crud.py +1816 -0
- sqlalchemy/sql/ddl.py +1962 -0
- sqlalchemy/sql/default_comparator.py +660 -0
- sqlalchemy/sql/dml.py +2018 -0
- sqlalchemy/sql/elements.py +6057 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +171 -0
- sqlalchemy/sql/functions.py +2380 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +204 -0
- sqlalchemy/sql/operators.py +2909 -0
- sqlalchemy/sql/roles.py +332 -0
- sqlalchemy/sql/schema.py +7075 -0
- sqlalchemy/sql/selectable.py +7634 -0
- sqlalchemy/sql/sqltypes.py +4130 -0
- sqlalchemy/sql/traversals.py +1041 -0
- sqlalchemy/sql/type_api.py +2450 -0
- sqlalchemy/sql/util.py +1496 -0
- sqlalchemy/sql/visitors.py +1153 -0
- sqlalchemy/testing/__init__.py +97 -0
- sqlalchemy/testing/assertions.py +1007 -0
- sqlalchemy/testing/assertsql.py +519 -0
- sqlalchemy/testing/asyncio.py +128 -0
- sqlalchemy/testing/cancellation.py +237 -0
- sqlalchemy/testing/config.py +440 -0
- sqlalchemy/testing/engines.py +482 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +501 -0
- sqlalchemy/testing/fixtures/__init__.py +30 -0
- sqlalchemy/testing/fixtures/base.py +426 -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 +50 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +896 -0
- sqlalchemy/testing/profiles_file.py +350 -0
- sqlalchemy/testing/profiling.py +294 -0
- sqlalchemy/testing/provision.py +633 -0
- sqlalchemy/testing/requirements.py +1971 -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 +3815 -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 +575 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +75 -0
- sqlalchemy/util/__init__.py +165 -0
- sqlalchemy/util/_collections.py +688 -0
- sqlalchemy/util/_collections_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/util/_collections_cy.pxd +8 -0
- sqlalchemy/util/_collections_cy.py +516 -0
- sqlalchemy/util/_has_cython.py +48 -0
- sqlalchemy/util/_immutabledict_cy.cp315-win_arm64.pyd +0 -0
- sqlalchemy/util/_immutabledict_cy.py +240 -0
- sqlalchemy/util/compat.py +298 -0
- sqlalchemy/util/concurrency.py +272 -0
- sqlalchemy/util/cython.py +95 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2797 -0
- sqlalchemy/util/preloaded.py +153 -0
- sqlalchemy/util/queue.py +304 -0
- sqlalchemy/util/tool_support.py +202 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +709 -0
- sqlalchemy-2.1.0rc1.dist-info/METADATA +270 -0
- sqlalchemy-2.1.0rc1.dist-info/RECORD +276 -0
- sqlalchemy-2.1.0rc1.dist-info/WHEEL +5 -0
- sqlalchemy-2.1.0rc1.dist-info/licenses/AUTHORS +30 -0
- sqlalchemy-2.1.0rc1.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.1.0rc1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
# dialects/sqlite/pysqlite.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
|
+
|
|
8
|
+
|
|
9
|
+
r"""
|
|
10
|
+
.. dialect:: sqlite+pysqlite
|
|
11
|
+
:name: pysqlite
|
|
12
|
+
:dbapi: sqlite3
|
|
13
|
+
:connectstring: sqlite+pysqlite:///file_path
|
|
14
|
+
:url: https://docs.python.org/library/sqlite3.html
|
|
15
|
+
|
|
16
|
+
Note that ``pysqlite`` is the same driver as the ``sqlite3``
|
|
17
|
+
module included with the Python distribution.
|
|
18
|
+
|
|
19
|
+
Driver
|
|
20
|
+
------
|
|
21
|
+
|
|
22
|
+
The ``sqlite3`` Python DBAPI is standard on all modern Python versions;
|
|
23
|
+
for cPython and Pypy, no additional installation is necessary.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Connect Strings
|
|
27
|
+
---------------
|
|
28
|
+
|
|
29
|
+
The file specification for the SQLite database is taken as the "database"
|
|
30
|
+
portion of the URL. Note that the format of a SQLAlchemy url is:
|
|
31
|
+
|
|
32
|
+
.. sourcecode:: text
|
|
33
|
+
|
|
34
|
+
driver://user:pass@host/database
|
|
35
|
+
|
|
36
|
+
This means that the actual filename to be used starts with the characters to
|
|
37
|
+
the **right** of the third slash. So connecting to a relative filepath
|
|
38
|
+
looks like::
|
|
39
|
+
|
|
40
|
+
# relative path
|
|
41
|
+
e = create_engine("sqlite:///path/to/database.db")
|
|
42
|
+
|
|
43
|
+
An absolute path, which is denoted by starting with a slash, means you
|
|
44
|
+
need **four** slashes::
|
|
45
|
+
|
|
46
|
+
# absolute path
|
|
47
|
+
e = create_engine("sqlite:////path/to/database.db")
|
|
48
|
+
|
|
49
|
+
To use a Windows path, regular drive specifications and backslashes can be
|
|
50
|
+
used. Double backslashes are probably needed::
|
|
51
|
+
|
|
52
|
+
# absolute path on Windows
|
|
53
|
+
e = create_engine("sqlite:///C:\\path\\to\\database.db")
|
|
54
|
+
|
|
55
|
+
To use sqlite ``:memory:`` database specify it as the filename using
|
|
56
|
+
``sqlite:///:memory:``. It's also the default if no filepath is
|
|
57
|
+
present, specifying only ``sqlite://`` and nothing else::
|
|
58
|
+
|
|
59
|
+
# in-memory database (note three slashes)
|
|
60
|
+
e = create_engine("sqlite:///:memory:")
|
|
61
|
+
# also in-memory database
|
|
62
|
+
e2 = create_engine("sqlite://")
|
|
63
|
+
|
|
64
|
+
.. _pysqlite_uri_connections:
|
|
65
|
+
|
|
66
|
+
URI Connections
|
|
67
|
+
^^^^^^^^^^^^^^^
|
|
68
|
+
|
|
69
|
+
Modern versions of SQLite support an alternative system of connecting using a
|
|
70
|
+
`driver level URI <https://www.sqlite.org/uri.html>`_, which has the advantage
|
|
71
|
+
that additional driver-level arguments can be passed including options such as
|
|
72
|
+
"read only". The Python sqlite3 driver supports this mode under modern Python
|
|
73
|
+
3 versions. The SQLAlchemy pysqlite driver supports this mode of use by
|
|
74
|
+
specifying "uri=true" in the URL query string. The SQLite-level "URI" is kept
|
|
75
|
+
as the "database" portion of the SQLAlchemy url (that is, following a slash)::
|
|
76
|
+
|
|
77
|
+
e = create_engine("sqlite:///file:path/to/database?mode=ro&uri=true")
|
|
78
|
+
|
|
79
|
+
.. note:: The "uri=true" parameter must appear in the **query string**
|
|
80
|
+
of the URL. It will not currently work as expected if it is only
|
|
81
|
+
present in the :paramref:`_sa.create_engine.connect_args`
|
|
82
|
+
parameter dictionary.
|
|
83
|
+
|
|
84
|
+
The logic reconciles the simultaneous presence of SQLAlchemy's query string and
|
|
85
|
+
SQLite's query string by separating out the parameters that belong to the
|
|
86
|
+
Python sqlite3 driver vs. those that belong to the SQLite URI. This is
|
|
87
|
+
achieved through the use of a fixed list of parameters known to be accepted by
|
|
88
|
+
the Python side of the driver. For example, to include a URL that indicates
|
|
89
|
+
the Python sqlite3 "timeout" and "check_same_thread" parameters, along with the
|
|
90
|
+
SQLite "mode" and "nolock" parameters, they can all be passed together on the
|
|
91
|
+
query string::
|
|
92
|
+
|
|
93
|
+
e = create_engine(
|
|
94
|
+
"sqlite:///file:path/to/database?"
|
|
95
|
+
"check_same_thread=true&timeout=10&mode=ro&nolock=1&uri=true"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
Above, the pysqlite / sqlite3 DBAPI would be passed arguments as::
|
|
99
|
+
|
|
100
|
+
sqlite3.connect(
|
|
101
|
+
"file:path/to/database?mode=ro&nolock=1",
|
|
102
|
+
check_same_thread=True,
|
|
103
|
+
timeout=10,
|
|
104
|
+
uri=True,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
Regarding future parameters added to either the Python or native drivers. new
|
|
108
|
+
parameter names added to the SQLite URI scheme should be automatically
|
|
109
|
+
accommodated by this scheme. New parameter names added to the Python driver
|
|
110
|
+
side can be accommodated by specifying them in the
|
|
111
|
+
:paramref:`_sa.create_engine.connect_args` dictionary,
|
|
112
|
+
until dialect support is
|
|
113
|
+
added by SQLAlchemy. For the less likely case that the native SQLite driver
|
|
114
|
+
adds a new parameter name that overlaps with one of the existing, known Python
|
|
115
|
+
driver parameters (such as "timeout" perhaps), SQLAlchemy's dialect would
|
|
116
|
+
require adjustment for the URL scheme to continue to support this.
|
|
117
|
+
|
|
118
|
+
As is always the case for all SQLAlchemy dialects, the entire "URL" process
|
|
119
|
+
can be bypassed in :func:`_sa.create_engine` through the use of the
|
|
120
|
+
:paramref:`_sa.create_engine.creator`
|
|
121
|
+
parameter which allows for a custom callable
|
|
122
|
+
that creates a Python sqlite3 driver level connection directly.
|
|
123
|
+
|
|
124
|
+
.. seealso::
|
|
125
|
+
|
|
126
|
+
`Uniform Resource Identifiers <https://www.sqlite.org/uri.html>`_ - in
|
|
127
|
+
the SQLite documentation
|
|
128
|
+
|
|
129
|
+
.. _pysqlite_regexp:
|
|
130
|
+
|
|
131
|
+
Regular Expression Support
|
|
132
|
+
---------------------------
|
|
133
|
+
|
|
134
|
+
.. versionadded:: 1.4
|
|
135
|
+
|
|
136
|
+
Support for the :meth:`_sql.ColumnOperators.regexp_match` operator is provided
|
|
137
|
+
using Python's re.search_ function. SQLite itself does not include a working
|
|
138
|
+
regular expression operator; instead, it includes a non-implemented placeholder
|
|
139
|
+
operator ``REGEXP`` that calls a user-defined function that must be provided.
|
|
140
|
+
|
|
141
|
+
SQLAlchemy's implementation makes use of the pysqlite create_function_ hook
|
|
142
|
+
as follows::
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def regexp(a, b):
|
|
146
|
+
return re.search(a, b) is not None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
sqlite_connection.create_function(
|
|
150
|
+
"regexp",
|
|
151
|
+
2,
|
|
152
|
+
regexp,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
There is currently no support for regular expression flags as a separate
|
|
156
|
+
argument, as these are not supported by SQLite's REGEXP operator, however these
|
|
157
|
+
may be included inline within the regular expression string. See `Python regular expressions`_ for
|
|
158
|
+
details.
|
|
159
|
+
|
|
160
|
+
.. seealso::
|
|
161
|
+
|
|
162
|
+
`Python regular expressions`_: Documentation for Python's regular expression syntax.
|
|
163
|
+
|
|
164
|
+
.. _create_function: https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
|
|
165
|
+
|
|
166
|
+
.. _re.search: https://docs.python.org/3/library/re.html#re.search
|
|
167
|
+
|
|
168
|
+
.. _Python regular expressions: https://docs.python.org/3/library/re.html#re.search
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
Compatibility with sqlite3 "native" date and datetime types
|
|
173
|
+
-----------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
The pysqlite driver includes the sqlite3.PARSE_DECLTYPES and
|
|
176
|
+
sqlite3.PARSE_COLNAMES options, which have the effect of any column
|
|
177
|
+
or expression explicitly cast as "date" or "timestamp" will be converted
|
|
178
|
+
to a Python date or datetime object. The date and datetime types provided
|
|
179
|
+
with the pysqlite dialect are not currently compatible with these options,
|
|
180
|
+
since they render the ISO date/datetime including microseconds, which
|
|
181
|
+
pysqlite's driver does not. Additionally, SQLAlchemy does not at
|
|
182
|
+
this time automatically render the "cast" syntax required for the
|
|
183
|
+
freestanding functions "current_timestamp" and "current_date" to return
|
|
184
|
+
datetime/date types natively. Unfortunately, pysqlite
|
|
185
|
+
does not provide the standard DBAPI types in ``cursor.description``,
|
|
186
|
+
leaving SQLAlchemy with no way to detect these types on the fly
|
|
187
|
+
without expensive per-row type checks.
|
|
188
|
+
|
|
189
|
+
Keeping in mind that pysqlite's parsing option is not recommended,
|
|
190
|
+
nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
|
191
|
+
can be forced if one configures "native_datetime=True" on create_engine()::
|
|
192
|
+
|
|
193
|
+
engine = create_engine(
|
|
194
|
+
"sqlite://",
|
|
195
|
+
connect_args={
|
|
196
|
+
"detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
|
|
197
|
+
},
|
|
198
|
+
native_datetime=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
With this flag enabled, the DATE and TIMESTAMP types (but note - not the
|
|
202
|
+
DATETIME or TIME types...confused yet ?) will not perform any bind parameter
|
|
203
|
+
or result processing. Execution of "func.current_date()" will return a string.
|
|
204
|
+
"func.current_timestamp()" is registered as returning a DATETIME type in
|
|
205
|
+
SQLAlchemy, so this function still receives SQLAlchemy-level result
|
|
206
|
+
processing.
|
|
207
|
+
|
|
208
|
+
.. _pysqlite_threading_pooling:
|
|
209
|
+
|
|
210
|
+
Concurrency/Threading/Pooling Behavior
|
|
211
|
+
--------------------------------------
|
|
212
|
+
|
|
213
|
+
The ``sqlite3`` DBAPI by default prohibits the use of a particular connection
|
|
214
|
+
in a thread which is not the one in which it was created. As SQLite has
|
|
215
|
+
matured, it's behavior under multiple threads has improved, and even includes
|
|
216
|
+
options for memory only databases to be used in multiple threads.
|
|
217
|
+
|
|
218
|
+
The thread prohibition is known as "check same thread" and may be controlled
|
|
219
|
+
using the ``sqlite3`` parameter ``check_same_thread``, which will disable or
|
|
220
|
+
enable this check. SQLAlchemy's default behavior here is to set
|
|
221
|
+
``check_same_thread`` to ``False`` automatically whenever a file-based database
|
|
222
|
+
is in use, to establish compatibility with the default pool class
|
|
223
|
+
:class:`.QueuePool`.
|
|
224
|
+
|
|
225
|
+
The SQLAlchemy ``pysqlite`` DBAPI establishes the connection pool differently
|
|
226
|
+
based on the kind of SQLite database that's requested:
|
|
227
|
+
|
|
228
|
+
* When a ``:memory:`` SQLite database is specified, the dialect by default
|
|
229
|
+
will use :class:`.SingletonThreadPool`. This pool maintains a single
|
|
230
|
+
connection per thread, so that all access to the engine within the current
|
|
231
|
+
thread use the same ``:memory:`` database - other threads would access a
|
|
232
|
+
different ``:memory:`` database. The ``check_same_thread`` parameter
|
|
233
|
+
defaults to ``True``.
|
|
234
|
+
* When a file-based database is specified, the dialect will use
|
|
235
|
+
:class:`.QueuePool` as the source of connections. at the same time,
|
|
236
|
+
the ``check_same_thread`` flag is set to False by default unless overridden.
|
|
237
|
+
|
|
238
|
+
.. versionchanged:: 2.0
|
|
239
|
+
|
|
240
|
+
SQLite file database engines now use :class:`.QueuePool` by default.
|
|
241
|
+
Previously, :class:`.NullPool` were used. The :class:`.NullPool` class
|
|
242
|
+
may be used by specifying it via the
|
|
243
|
+
:paramref:`_sa.create_engine.poolclass` parameter.
|
|
244
|
+
|
|
245
|
+
This selection is made based on the database name alone. Where a
|
|
246
|
+
particular pool class is desired, it should be stated explicitly using the
|
|
247
|
+
:paramref:`_sa.create_engine.poolclass` parameter, in which case no
|
|
248
|
+
selection takes place at all.
|
|
249
|
+
|
|
250
|
+
.. deprecated:: 2.1
|
|
251
|
+
|
|
252
|
+
A URL that passes ``mode=memory`` in the query string is currently also
|
|
253
|
+
given a single-connection pool class. This behavior is deprecated and
|
|
254
|
+
will be removed in a future release, at which point such URLs will
|
|
255
|
+
receive :class:`.QueuePool` like any other. This affects URLs such as
|
|
256
|
+
``sqlite:///file:mydb?mode=memory&cache=shared&uri=true``, for which
|
|
257
|
+
:class:`.QueuePool` is in fact the appropriate class, as a shared cache
|
|
258
|
+
database supports multiple concurrent connections; see
|
|
259
|
+
:ref:`pysqlite_uri_shared_cache`. Applications relying on the present
|
|
260
|
+
behavior should state the pool class explicitly.
|
|
261
|
+
|
|
262
|
+
Disabling Connection Pooling for File Databases
|
|
263
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
264
|
+
|
|
265
|
+
Pooling may be disabled for a file based database by specifying the
|
|
266
|
+
:class:`.NullPool` implementation for the :func:`_sa.create_engine.poolclass`
|
|
267
|
+
parameter::
|
|
268
|
+
|
|
269
|
+
from sqlalchemy import NullPool
|
|
270
|
+
|
|
271
|
+
engine = create_engine("sqlite:///myfile.db", poolclass=NullPool)
|
|
272
|
+
|
|
273
|
+
It's been observed that the :class:`.NullPool` implementation incurs an
|
|
274
|
+
extremely small performance overhead for repeated checkouts due to the lack of
|
|
275
|
+
connection reuse implemented by :class:`.QueuePool`. However, it still
|
|
276
|
+
may be beneficial to use this class if the application is experiencing
|
|
277
|
+
issues with files being locked.
|
|
278
|
+
|
|
279
|
+
Using a Memory Database in Multiple Threads or Coroutines
|
|
280
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
281
|
+
|
|
282
|
+
A ``:memory:`` SQLite database exists only within the scope of a single
|
|
283
|
+
DBAPI connection. It is not possible for two separate ``sqlite3``
|
|
284
|
+
connection objects to access the same ``:memory:`` database unless SQLite's
|
|
285
|
+
shared-cache feature is enabled. Without shared cache, each connection
|
|
286
|
+
creates its own independent in-memory database.
|
|
287
|
+
|
|
288
|
+
This means a ``:memory:`` database is **not suitable** for use with
|
|
289
|
+
multiple concurrent threads or coroutines unless either:
|
|
290
|
+
|
|
291
|
+
* All workers are fully serialized (mutexed) against each other
|
|
292
|
+
such that only one is using the database at a time, or
|
|
293
|
+
* SQLite's shared-cache URI feature is used to allow multiple
|
|
294
|
+
independent connections to access the same in-memory database.
|
|
295
|
+
|
|
296
|
+
The same considerations apply when using the :ref:`aiosqlite <aiosqlite>`
|
|
297
|
+
dialect, which wraps ``pysqlite`` connections in an async interface —
|
|
298
|
+
without shared cache, each connection still creates its own independent
|
|
299
|
+
in-memory database.
|
|
300
|
+
|
|
301
|
+
The recommended approach for multithreaded or async in-memory
|
|
302
|
+
use is the shared-cache URI feature, described below.
|
|
303
|
+
|
|
304
|
+
.. _pysqlite_uri_shared_cache:
|
|
305
|
+
|
|
306
|
+
Using a Shared-Cache Memory Database
|
|
307
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
308
|
+
|
|
309
|
+
SQLite's
|
|
310
|
+
`URI shared-cache <https://www.sqlite.org/sharedcache.html>`_
|
|
311
|
+
mode allows multiple independent DBAPI connections, each with
|
|
312
|
+
their own transaction state, to access the same in-memory
|
|
313
|
+
database. This is enabled by using a ``file:`` URI with
|
|
314
|
+
``cache=shared`` and passing ``uri=true`` in the query
|
|
315
|
+
string::
|
|
316
|
+
|
|
317
|
+
engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true")
|
|
318
|
+
|
|
319
|
+
For async use with aiosqlite, use
|
|
320
|
+
:func:`_asyncio.create_async_engine`::
|
|
321
|
+
|
|
322
|
+
engine = create_async_engine(
|
|
323
|
+
"sqlite+aiosqlite:///file::memory:?cache=shared&uri=true"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
Because this URL form is treated as a file-based database by the
|
|
327
|
+
dialect, :class:`.QueuePool` is used automatically and
|
|
328
|
+
``check_same_thread`` defaults to ``False``, so no additional pool
|
|
329
|
+
or connect_args configuration is needed. Each checkout from the
|
|
330
|
+
pool is a distinct DBAPI connection with its own transaction state.
|
|
331
|
+
|
|
332
|
+
The shared-cache database is scoped by the filename component of
|
|
333
|
+
the URI. ``file::memory:`` (empty name) is process-global — all
|
|
334
|
+
engines in the process that use this URI share the same database.
|
|
335
|
+
To maintain multiple independent in-memory databases within the
|
|
336
|
+
same process, supply a distinct name for each. A named database of
|
|
337
|
+
this kind requires ``mode=memory``, which presently causes a
|
|
338
|
+
single-connection pool to be selected; :class:`.QueuePool` should
|
|
339
|
+
therefore be requested explicitly::
|
|
340
|
+
|
|
341
|
+
from sqlalchemy.pool import QueuePool
|
|
342
|
+
|
|
343
|
+
engine_a = create_engine(
|
|
344
|
+
"sqlite:///file:db_a?mode=memory&cache=shared&uri=true",
|
|
345
|
+
poolclass=QueuePool,
|
|
346
|
+
)
|
|
347
|
+
engine_b = create_engine(
|
|
348
|
+
"sqlite:///file:db_b?mode=memory&cache=shared&uri=true",
|
|
349
|
+
poolclass=QueuePool,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
For :func:`_asyncio.create_async_engine`, use
|
|
353
|
+
:class:`.AsyncAdaptedQueuePool` in the same way.
|
|
354
|
+
|
|
355
|
+
.. deprecated:: 2.1
|
|
356
|
+
|
|
357
|
+
Selection of a single-connection pool class based on ``mode=memory``
|
|
358
|
+
is deprecated; a future release will use :class:`.QueuePool` for these
|
|
359
|
+
URLs, at which point stating
|
|
360
|
+
:paramref:`_sa.create_engine.poolclass` explicitly will no longer be
|
|
361
|
+
necessary. See :ref:`pysqlite_threading_pooling`.
|
|
362
|
+
|
|
363
|
+
.. _pysqlite_shared_cache_lifespan:
|
|
364
|
+
|
|
365
|
+
Lifespan of a Shared-Cache Memory Database
|
|
366
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
367
|
+
|
|
368
|
+
A shared-cache in-memory database exists only for as long as at least one
|
|
369
|
+
connection to it remains open; when the last connection is closed, the
|
|
370
|
+
database and all of its contents are discarded. A subsequent connection
|
|
371
|
+
using the same URI then opens a new, empty database, which typically
|
|
372
|
+
surfaces as ``no such table`` errors.
|
|
373
|
+
|
|
374
|
+
:class:`.QueuePool` retains connections that have been returned to it, so
|
|
375
|
+
in a default configuration the database will normally persist once the
|
|
376
|
+
first connection has been established. This is a consequence of pool
|
|
377
|
+
behavior rather than a guarantee, however, and the database will be
|
|
378
|
+
discarded by ordinary pool operations including:
|
|
379
|
+
|
|
380
|
+
* :meth:`_engine.Engine.dispose`, which closes all connections currently
|
|
381
|
+
in the pool
|
|
382
|
+
* :paramref:`_sa.create_engine.pool_recycle`, as a recycled connection is
|
|
383
|
+
closed before its replacement is opened
|
|
384
|
+
* connection invalidation, including that performed by
|
|
385
|
+
:paramref:`_sa.create_engine.pool_pre_ping`
|
|
386
|
+
* use of :class:`.NullPool`, which closes each connection as it is
|
|
387
|
+
returned
|
|
388
|
+
|
|
389
|
+
Where the database must survive independently of pool activity, hold a
|
|
390
|
+
single connection open for as long as the database is needed::
|
|
391
|
+
|
|
392
|
+
engine = create_engine("sqlite:///file::memory:?cache=shared&uri=true")
|
|
393
|
+
|
|
394
|
+
# keep the database alive for the lifetime of the engine
|
|
395
|
+
keepalive = engine.connect()
|
|
396
|
+
|
|
397
|
+
The same consideration applies to the :ref:`aiosqlite <aiosqlite>`
|
|
398
|
+
dialect, using :meth:`_asyncio.AsyncEngine.connect`.
|
|
399
|
+
|
|
400
|
+
Using StaticPool for Single-Connection Memory Databases
|
|
401
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
402
|
+
|
|
403
|
+
An older approach for sharing a ``:memory:`` database among threads
|
|
404
|
+
is to force all checkouts to return the same DBAPI connection using
|
|
405
|
+
:class:`.StaticPool`. **This approach does not support any form of
|
|
406
|
+
concurrency** and is only useful when all access to the engine is
|
|
407
|
+
fully serialized, such as in single-threaded test suites::
|
|
408
|
+
|
|
409
|
+
from sqlalchemy.pool import StaticPool
|
|
410
|
+
|
|
411
|
+
engine = create_engine(
|
|
412
|
+
"sqlite://",
|
|
413
|
+
connect_args={"check_same_thread": False},
|
|
414
|
+
poolclass=StaticPool,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
.. warning::
|
|
418
|
+
|
|
419
|
+
Because :class:`.StaticPool` maintains a single DBAPI connection,
|
|
420
|
+
all :class:`.Session` or :class:`.Connection` objects that use
|
|
421
|
+
this engine share that one underlying connection and its single
|
|
422
|
+
SQLite transaction state. A ``ROLLBACK`` issued by one session
|
|
423
|
+
(e.g. during error handling) will also roll back uncommitted work
|
|
424
|
+
from any other session, and concurrent ``COMMIT`` / ``ROLLBACK``
|
|
425
|
+
calls can interfere with each other unpredictably. This approach
|
|
426
|
+
is only appropriate when access to the engine is fully serialized,
|
|
427
|
+
such as in single-threaded test suites. For concurrent workloads,
|
|
428
|
+
use the shared-cache URI approach described above.
|
|
429
|
+
|
|
430
|
+
Using Temporary Tables with SQLite
|
|
431
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
432
|
+
|
|
433
|
+
Due to the way SQLite deals with temporary tables, if you wish to use a
|
|
434
|
+
temporary table in a file-based SQLite database across multiple checkouts
|
|
435
|
+
from the connection pool, such as when using an ORM :class:`.Session` where
|
|
436
|
+
the temporary table should continue to remain after :meth:`.Session.commit` or
|
|
437
|
+
:meth:`.Session.rollback` is called, a pool which maintains a single
|
|
438
|
+
connection must be used. Use :class:`.SingletonThreadPool` if the scope is
|
|
439
|
+
only needed within the current thread, or :class:`.StaticPool` is scope is
|
|
440
|
+
needed within multiple threads for this case::
|
|
441
|
+
|
|
442
|
+
# maintain the same connection per thread
|
|
443
|
+
from sqlalchemy.pool import SingletonThreadPool
|
|
444
|
+
|
|
445
|
+
engine = create_engine("sqlite:///mydb.db", poolclass=SingletonThreadPool)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# maintain the same connection across all threads
|
|
449
|
+
from sqlalchemy.pool import StaticPool
|
|
450
|
+
|
|
451
|
+
engine = create_engine("sqlite:///mydb.db", poolclass=StaticPool)
|
|
452
|
+
|
|
453
|
+
Note that :class:`.SingletonThreadPool` should be configured for the number
|
|
454
|
+
of threads that are to be used; beyond that number, connections will be
|
|
455
|
+
closed out in a non deterministic way.
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
Dealing with Mixed String / Binary Columns
|
|
459
|
+
------------------------------------------------------
|
|
460
|
+
|
|
461
|
+
The SQLite database is weakly typed, and as such it is possible when using
|
|
462
|
+
binary values, which in Python are represented as ``b'some string'``, that a
|
|
463
|
+
particular SQLite database can have data values within different rows where
|
|
464
|
+
some of them will be returned as a ``b''`` value by the Pysqlite driver, and
|
|
465
|
+
others will be returned as Python strings, e.g. ``''`` values. This situation
|
|
466
|
+
is not known to occur if the SQLAlchemy :class:`.LargeBinary` datatype is used
|
|
467
|
+
consistently, however if a particular SQLite database has data that was
|
|
468
|
+
inserted using the Pysqlite driver directly, or when using the SQLAlchemy
|
|
469
|
+
:class:`.String` type which was later changed to :class:`.LargeBinary`, the
|
|
470
|
+
table will not be consistently readable because SQLAlchemy's
|
|
471
|
+
:class:`.LargeBinary` datatype does not handle strings so it has no way of
|
|
472
|
+
"encoding" a value that is in string format.
|
|
473
|
+
|
|
474
|
+
To deal with a SQLite table that has mixed string / binary data in the
|
|
475
|
+
same column, use a custom type that will check each row individually::
|
|
476
|
+
|
|
477
|
+
from sqlalchemy import String
|
|
478
|
+
from sqlalchemy import TypeDecorator
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class MixedBinary(TypeDecorator):
|
|
482
|
+
impl = String
|
|
483
|
+
cache_ok = True
|
|
484
|
+
|
|
485
|
+
def process_result_value(self, value, dialect):
|
|
486
|
+
if isinstance(value, str):
|
|
487
|
+
value = bytes(value, "utf-8")
|
|
488
|
+
elif value is not None:
|
|
489
|
+
value = bytes(value)
|
|
490
|
+
|
|
491
|
+
return value
|
|
492
|
+
|
|
493
|
+
Then use the above ``MixedBinary`` datatype in the place where
|
|
494
|
+
:class:`.LargeBinary` would normally be used.
|
|
495
|
+
|
|
496
|
+
.. _pysqlite_serializable:
|
|
497
|
+
|
|
498
|
+
Serializable isolation / Savepoints / Transactional DDL
|
|
499
|
+
-------------------------------------------------------
|
|
500
|
+
|
|
501
|
+
A newly revised version of this important section is now available
|
|
502
|
+
at the top level of the SQLAlchemy SQLite documentation, in the section
|
|
503
|
+
:ref:`sqlite_transactions`.
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
.. _pysqlite_udfs:
|
|
507
|
+
|
|
508
|
+
User-Defined Functions
|
|
509
|
+
----------------------
|
|
510
|
+
|
|
511
|
+
pysqlite supports a `create_function() <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function>`_
|
|
512
|
+
method that allows us to create our own user-defined functions (UDFs) in Python and use them directly in SQLite queries.
|
|
513
|
+
These functions are registered with a specific DBAPI Connection.
|
|
514
|
+
|
|
515
|
+
SQLAlchemy uses connection pooling with file-based SQLite databases, so we need to ensure that the UDF is attached to the
|
|
516
|
+
connection when it is created. That is accomplished with an event listener::
|
|
517
|
+
|
|
518
|
+
from sqlalchemy import create_engine
|
|
519
|
+
from sqlalchemy import event
|
|
520
|
+
from sqlalchemy import text
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def udf():
|
|
524
|
+
return "udf-ok"
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
engine = create_engine("sqlite:///./db_file")
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@event.listens_for(engine, "connect")
|
|
531
|
+
def connect(conn, rec):
|
|
532
|
+
conn.create_function("udf", 0, udf)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
for i in range(5):
|
|
536
|
+
with engine.connect() as conn:
|
|
537
|
+
print(conn.scalar(text("SELECT UDF()")))
|
|
538
|
+
|
|
539
|
+
""" # noqa
|
|
540
|
+
|
|
541
|
+
from __future__ import annotations
|
|
542
|
+
|
|
543
|
+
import math
|
|
544
|
+
import os
|
|
545
|
+
import re
|
|
546
|
+
import sys
|
|
547
|
+
from typing import Any
|
|
548
|
+
from typing import Callable
|
|
549
|
+
from typing import cast
|
|
550
|
+
from typing import Optional
|
|
551
|
+
from typing import Pattern
|
|
552
|
+
from typing import TYPE_CHECKING
|
|
553
|
+
from typing import TypeVar
|
|
554
|
+
from typing import Union
|
|
555
|
+
|
|
556
|
+
from .base import DATE
|
|
557
|
+
from .base import DATETIME
|
|
558
|
+
from .base import SQLiteDialect
|
|
559
|
+
from ... import exc
|
|
560
|
+
from ... import pool
|
|
561
|
+
from ... import types as sqltypes
|
|
562
|
+
from ... import util
|
|
563
|
+
from ...util.typing import Self
|
|
564
|
+
|
|
565
|
+
if TYPE_CHECKING:
|
|
566
|
+
from ...engine.interfaces import ConnectArgsType
|
|
567
|
+
from ...engine.interfaces import DBAPIConnection
|
|
568
|
+
from ...engine.interfaces import DBAPICursor
|
|
569
|
+
from ...engine.interfaces import DBAPIModule
|
|
570
|
+
from ...engine.interfaces import IsolationLevel
|
|
571
|
+
from ...engine.interfaces import ServerVersionInfoType
|
|
572
|
+
from ...engine.url import URL
|
|
573
|
+
from ...pool.base import PoolProxiedConnection
|
|
574
|
+
from ...sql.type_api import _BindProcessorType
|
|
575
|
+
from ...sql.type_api import _ResultProcessorType
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class _SQLite_pysqliteTimeStamp(DATETIME):
|
|
579
|
+
def bind_processor( # type: ignore[override]
|
|
580
|
+
self, dialect: SQLiteDialect
|
|
581
|
+
) -> Optional[_BindProcessorType[Any]]:
|
|
582
|
+
if dialect.native_datetime:
|
|
583
|
+
return None
|
|
584
|
+
else:
|
|
585
|
+
return DATETIME.bind_processor(self, dialect)
|
|
586
|
+
|
|
587
|
+
def result_processor( # type: ignore[override]
|
|
588
|
+
self, dialect: SQLiteDialect, coltype: object
|
|
589
|
+
) -> Optional[_ResultProcessorType[Any]]:
|
|
590
|
+
if dialect.native_datetime:
|
|
591
|
+
return None
|
|
592
|
+
else:
|
|
593
|
+
return DATETIME.result_processor(self, dialect, coltype)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
class _SQLite_pysqliteDate(DATE):
|
|
597
|
+
def bind_processor( # type: ignore[override]
|
|
598
|
+
self, dialect: SQLiteDialect
|
|
599
|
+
) -> Optional[_BindProcessorType[Any]]:
|
|
600
|
+
if dialect.native_datetime:
|
|
601
|
+
return None
|
|
602
|
+
else:
|
|
603
|
+
return DATE.bind_processor(self, dialect)
|
|
604
|
+
|
|
605
|
+
def result_processor( # type: ignore[override]
|
|
606
|
+
self, dialect: SQLiteDialect, coltype: object
|
|
607
|
+
) -> Optional[_ResultProcessorType[Any]]:
|
|
608
|
+
if dialect.native_datetime:
|
|
609
|
+
return None
|
|
610
|
+
else:
|
|
611
|
+
return DATE.result_processor(self, dialect, coltype)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
class SQLiteDialect_pysqlite(SQLiteDialect):
|
|
615
|
+
default_paramstyle = "qmark"
|
|
616
|
+
supports_statement_cache = True
|
|
617
|
+
returns_native_bytes = True
|
|
618
|
+
|
|
619
|
+
colspecs = util.update_copy(
|
|
620
|
+
SQLiteDialect.colspecs,
|
|
621
|
+
{
|
|
622
|
+
sqltypes.Date: _SQLite_pysqliteDate,
|
|
623
|
+
sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
|
|
624
|
+
},
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
description_encoding = None
|
|
628
|
+
|
|
629
|
+
driver = "pysqlite"
|
|
630
|
+
|
|
631
|
+
@classmethod
|
|
632
|
+
def import_dbapi(cls) -> DBAPIModule:
|
|
633
|
+
from sqlite3 import dbapi2 as sqlite
|
|
634
|
+
|
|
635
|
+
return cast("DBAPIModule", sqlite)
|
|
636
|
+
|
|
637
|
+
@classmethod
|
|
638
|
+
def _is_url_file_db(cls, url: URL) -> bool:
|
|
639
|
+
if (url.database and url.database != ":memory:") and (
|
|
640
|
+
url.query.get("mode", None) != "memory"
|
|
641
|
+
):
|
|
642
|
+
return True
|
|
643
|
+
else:
|
|
644
|
+
return False
|
|
645
|
+
|
|
646
|
+
@classmethod
|
|
647
|
+
def _warn_memory_mode_pool_selection(
|
|
648
|
+
cls,
|
|
649
|
+
url: URL,
|
|
650
|
+
current_pool: type[pool.Pool],
|
|
651
|
+
future_pool: type[pool.Pool],
|
|
652
|
+
) -> None:
|
|
653
|
+
"""Warn when the ``mode=memory`` query string argument is what
|
|
654
|
+
caused a single-connection pool class to be selected.
|
|
655
|
+
|
|
656
|
+
See :ticket:`13433`.
|
|
657
|
+
|
|
658
|
+
"""
|
|
659
|
+
if url.query.get("mode", None) != "memory":
|
|
660
|
+
return
|
|
661
|
+
|
|
662
|
+
util.warn_deprecated(
|
|
663
|
+
"Selection of the %s pool class based on the 'mode=memory' "
|
|
664
|
+
"query string argument is deprecated; a future release will "
|
|
665
|
+
"use %s for this URL. Indicate the intended pool class "
|
|
666
|
+
"using the create_engine.poolclass parameter."
|
|
667
|
+
% (current_pool.__name__, future_pool.__name__),
|
|
668
|
+
"2.1",
|
|
669
|
+
code="sqmp",
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
@classmethod
|
|
673
|
+
def get_pool_class(cls, url: URL) -> type[pool.Pool]:
|
|
674
|
+
if cls._is_url_file_db(url):
|
|
675
|
+
return pool.QueuePool
|
|
676
|
+
else:
|
|
677
|
+
cls._warn_memory_mode_pool_selection(
|
|
678
|
+
url, pool.SingletonThreadPool, pool.QueuePool
|
|
679
|
+
)
|
|
680
|
+
return pool.SingletonThreadPool
|
|
681
|
+
|
|
682
|
+
def _get_server_version_info(
|
|
683
|
+
self, connection: Any
|
|
684
|
+
) -> ServerVersionInfoType:
|
|
685
|
+
return self.dbapi.sqlite_version_info # type: ignore[no-any-return, union-attr] # noqa: E501
|
|
686
|
+
|
|
687
|
+
def retrieve_dbapi_version(self, dbapi: DBAPIModule) -> util.VersionInfo:
|
|
688
|
+
# the ``sqlite3`` module ships with CPython and has no version of
|
|
689
|
+
# its own (the legacy ``sqlite3.version`` attribute was frozen at
|
|
690
|
+
# 2.6.0 and removed in Python 3.14), so the Python version is used
|
|
691
|
+
# here, which is what its feature set actually tracks. The version
|
|
692
|
+
# of the SQLite library itself is available as
|
|
693
|
+
# :attr:`.Dialect.server_version_info`.
|
|
694
|
+
return util.VersionInfo(sys.version_info[:3])
|
|
695
|
+
|
|
696
|
+
_isolation_lookup = SQLiteDialect._isolation_lookup.union(
|
|
697
|
+
{
|
|
698
|
+
"AUTOCOMMIT": None,
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
def set_isolation_level(
|
|
703
|
+
self, dbapi_connection: DBAPIConnection, level: IsolationLevel
|
|
704
|
+
) -> None:
|
|
705
|
+
if level == "AUTOCOMMIT":
|
|
706
|
+
dbapi_connection.isolation_level = None
|
|
707
|
+
else:
|
|
708
|
+
dbapi_connection.isolation_level = ""
|
|
709
|
+
return super().set_isolation_level(dbapi_connection, level)
|
|
710
|
+
|
|
711
|
+
def detect_autocommit_setting(self, dbapi_conn: DBAPIConnection) -> bool:
|
|
712
|
+
return dbapi_conn.isolation_level is None
|
|
713
|
+
|
|
714
|
+
def on_connect(self) -> Callable[[DBAPIConnection], None]:
|
|
715
|
+
def regexp(a: str, b: Optional[str]) -> Optional[bool]:
|
|
716
|
+
if b is None:
|
|
717
|
+
return None
|
|
718
|
+
return re.search(a, b) is not None
|
|
719
|
+
|
|
720
|
+
if self._get_server_version_info(None) >= (3, 9):
|
|
721
|
+
# sqlite must be greater than 3.8.3 for deterministic=True
|
|
722
|
+
# https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.create_function
|
|
723
|
+
# the check is more conservative since there were still issues
|
|
724
|
+
# with following 3.8 sqlite versions
|
|
725
|
+
create_func_kw = {"deterministic": True}
|
|
726
|
+
else:
|
|
727
|
+
create_func_kw = {}
|
|
728
|
+
|
|
729
|
+
def set_regexp(dbapi_connection: DBAPIConnection) -> None:
|
|
730
|
+
dbapi_connection.create_function(
|
|
731
|
+
"regexp", 2, regexp, **create_func_kw
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
def floor_func(dbapi_connection: DBAPIConnection) -> None:
|
|
735
|
+
# NOTE: floor is optionally present in sqlite 3.35+ , however
|
|
736
|
+
# as it is normally non-present we deliver floor() unconditionally
|
|
737
|
+
# for now.
|
|
738
|
+
# https://www.sqlite.org/lang_mathfunc.html
|
|
739
|
+
dbapi_connection.create_function(
|
|
740
|
+
"floor", 1, math.floor, **create_func_kw
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
fns = [set_regexp, floor_func]
|
|
744
|
+
|
|
745
|
+
def connect(conn: DBAPIConnection) -> None:
|
|
746
|
+
for fn in fns:
|
|
747
|
+
fn(conn)
|
|
748
|
+
|
|
749
|
+
return connect
|
|
750
|
+
|
|
751
|
+
def create_connect_args(self, url: URL) -> ConnectArgsType:
|
|
752
|
+
if url.username or url.password or url.host or url.port:
|
|
753
|
+
raise exc.ArgumentError(
|
|
754
|
+
"Invalid SQLite URL: %s\n"
|
|
755
|
+
"Valid SQLite URL forms are:\n"
|
|
756
|
+
" sqlite:///:memory: (or, sqlite://)\n"
|
|
757
|
+
" sqlite:///relative/path/to/file.db\n"
|
|
758
|
+
" sqlite:////absolute/path/to/file.db" % (url,)
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
# theoretically, this list can be augmented, at least as far as
|
|
762
|
+
# parameter names accepted by sqlite3/pysqlite, using
|
|
763
|
+
# inspect.getfullargspec(). for the moment this seems like overkill
|
|
764
|
+
# as these parameters don't change very often, and as always,
|
|
765
|
+
# parameters passed to connect_args will always go to the
|
|
766
|
+
# sqlite3/pysqlite driver.
|
|
767
|
+
pysqlite_args = [
|
|
768
|
+
("uri", bool),
|
|
769
|
+
("timeout", float),
|
|
770
|
+
("isolation_level", str),
|
|
771
|
+
("detect_types", int),
|
|
772
|
+
("check_same_thread", bool),
|
|
773
|
+
("cached_statements", int),
|
|
774
|
+
]
|
|
775
|
+
opts = url.query
|
|
776
|
+
pysqlite_opts: dict[str, Any] = {}
|
|
777
|
+
for key, type_ in pysqlite_args:
|
|
778
|
+
util.coerce_kw_type(opts, key, type_, dest=pysqlite_opts)
|
|
779
|
+
|
|
780
|
+
if pysqlite_opts.get("uri", False):
|
|
781
|
+
uri_opts = dict(opts)
|
|
782
|
+
# here, we are actually separating the parameters that go to
|
|
783
|
+
# sqlite3/pysqlite vs. those that go the SQLite URI. What if
|
|
784
|
+
# two names conflict? again, this seems to be not the case right
|
|
785
|
+
# now, and in the case that new names are added to
|
|
786
|
+
# either side which overlap, again the sqlite3/pysqlite parameters
|
|
787
|
+
# can be passed through connect_args instead of in the URL.
|
|
788
|
+
# If SQLite native URIs add a parameter like "timeout" that
|
|
789
|
+
# we already have listed here for the python driver, then we need
|
|
790
|
+
# to adjust for that here.
|
|
791
|
+
for key, type_ in pysqlite_args:
|
|
792
|
+
uri_opts.pop(key, None)
|
|
793
|
+
filename: str = url.database # type: ignore[assignment]
|
|
794
|
+
if uri_opts:
|
|
795
|
+
# sorting of keys is for unit test support
|
|
796
|
+
filename += "?" + (
|
|
797
|
+
"&".join(
|
|
798
|
+
"%s=%s" % (key, uri_opts[key])
|
|
799
|
+
for key in sorted(uri_opts)
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
else:
|
|
803
|
+
# without uri=True, the SQLite URI query string is not in
|
|
804
|
+
# play at all, so anything left over here is silently
|
|
805
|
+
# discarded; warn rather than have it appear to take effect
|
|
806
|
+
ignored = sorted(
|
|
807
|
+
set(opts).difference(key for key, _ in pysqlite_args)
|
|
808
|
+
)
|
|
809
|
+
if ignored:
|
|
810
|
+
util.warn(
|
|
811
|
+
"Query string argument(s) %s are not accepted by the "
|
|
812
|
+
"pysqlite driver and are being ignored; SQLite URI "
|
|
813
|
+
"arguments require that 'uri=true' also be present "
|
|
814
|
+
"in the URL."
|
|
815
|
+
% (", ".join("'%s'" % key for key in ignored),),
|
|
816
|
+
code="squa",
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
filename = url.database or ":memory:"
|
|
820
|
+
if filename != ":memory:":
|
|
821
|
+
filename = os.path.abspath(filename)
|
|
822
|
+
|
|
823
|
+
pysqlite_opts.setdefault(
|
|
824
|
+
"check_same_thread", not self._is_url_file_db(url)
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
return ([filename], pysqlite_opts)
|
|
828
|
+
|
|
829
|
+
def is_disconnect(
|
|
830
|
+
self,
|
|
831
|
+
e: DBAPIModule.Error,
|
|
832
|
+
connection: Optional[Union[PoolProxiedConnection, DBAPIConnection]],
|
|
833
|
+
cursor: Optional[DBAPICursor],
|
|
834
|
+
) -> bool:
|
|
835
|
+
self.dbapi = cast("DBAPIModule", self.dbapi)
|
|
836
|
+
return isinstance(
|
|
837
|
+
e, self.dbapi.ProgrammingError
|
|
838
|
+
) and "Cannot operate on a closed database." in str(e)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
dialect = SQLiteDialect_pysqlite
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class _SQLiteDialect_pysqlite_numeric(SQLiteDialect_pysqlite):
|
|
845
|
+
"""numeric dialect for testing only
|
|
846
|
+
|
|
847
|
+
internal use only. This dialect is **NOT** supported by SQLAlchemy
|
|
848
|
+
and may change at any time.
|
|
849
|
+
|
|
850
|
+
"""
|
|
851
|
+
|
|
852
|
+
supports_statement_cache = True
|
|
853
|
+
default_paramstyle = "numeric"
|
|
854
|
+
driver = "pysqlite_numeric"
|
|
855
|
+
|
|
856
|
+
_first_bind = ":1"
|
|
857
|
+
_not_in_statement_regexp: Optional[Pattern[str]] = None
|
|
858
|
+
|
|
859
|
+
def __init__(self, *arg: Any, **kw: Any) -> None:
|
|
860
|
+
kw.setdefault("paramstyle", "numeric")
|
|
861
|
+
super().__init__(*arg, **kw)
|
|
862
|
+
|
|
863
|
+
def create_connect_args(self, url: URL) -> ConnectArgsType:
|
|
864
|
+
arg, opts = super().create_connect_args(url)
|
|
865
|
+
opts["factory"] = self._fix_sqlite_issue_99953()
|
|
866
|
+
return arg, opts
|
|
867
|
+
|
|
868
|
+
def _fix_sqlite_issue_99953(self) -> Any:
|
|
869
|
+
import sqlite3
|
|
870
|
+
|
|
871
|
+
first_bind = self._first_bind
|
|
872
|
+
if self._not_in_statement_regexp:
|
|
873
|
+
nis = self._not_in_statement_regexp
|
|
874
|
+
|
|
875
|
+
def _test_sql(sql: str) -> None:
|
|
876
|
+
m = nis.search(sql)
|
|
877
|
+
assert not m, f"Found {nis.pattern!r} in {sql!r}"
|
|
878
|
+
|
|
879
|
+
else:
|
|
880
|
+
|
|
881
|
+
def _test_sql(sql: str) -> None:
|
|
882
|
+
pass
|
|
883
|
+
|
|
884
|
+
def _numeric_param_as_dict(
|
|
885
|
+
parameters: Any,
|
|
886
|
+
) -> Union[dict[str, Any], tuple[Any, ...]]:
|
|
887
|
+
if parameters:
|
|
888
|
+
assert isinstance(parameters, tuple)
|
|
889
|
+
return {
|
|
890
|
+
str(idx): value for idx, value in enumerate(parameters, 1)
|
|
891
|
+
}
|
|
892
|
+
else:
|
|
893
|
+
return ()
|
|
894
|
+
|
|
895
|
+
class SQLiteFix99953Cursor(sqlite3.Cursor):
|
|
896
|
+
def execute(self, sql: str, parameters: Any = ()) -> Self:
|
|
897
|
+
_test_sql(sql)
|
|
898
|
+
if first_bind in sql:
|
|
899
|
+
parameters = _numeric_param_as_dict(parameters)
|
|
900
|
+
return super().execute(sql, parameters)
|
|
901
|
+
|
|
902
|
+
def executemany(self, sql: str, parameters: Any) -> Self:
|
|
903
|
+
_test_sql(sql)
|
|
904
|
+
if first_bind in sql:
|
|
905
|
+
parameters = [
|
|
906
|
+
_numeric_param_as_dict(p) for p in parameters
|
|
907
|
+
]
|
|
908
|
+
return super().executemany(sql, parameters)
|
|
909
|
+
|
|
910
|
+
class SQLiteFix99953Connection(sqlite3.Connection):
|
|
911
|
+
_CursorT = TypeVar("_CursorT", bound=sqlite3.Cursor)
|
|
912
|
+
|
|
913
|
+
def cursor(
|
|
914
|
+
self,
|
|
915
|
+
factory: Optional[
|
|
916
|
+
Callable[[sqlite3.Connection], _CursorT]
|
|
917
|
+
] = None,
|
|
918
|
+
) -> _CursorT:
|
|
919
|
+
if factory is None:
|
|
920
|
+
factory = SQLiteFix99953Cursor # type: ignore[assignment]
|
|
921
|
+
return super().cursor(factory=factory) # type: ignore[return-value] # noqa[E501]
|
|
922
|
+
|
|
923
|
+
def execute(
|
|
924
|
+
self, sql: str, parameters: Any = ()
|
|
925
|
+
) -> sqlite3.Cursor:
|
|
926
|
+
_test_sql(sql)
|
|
927
|
+
if first_bind in sql:
|
|
928
|
+
parameters = _numeric_param_as_dict(parameters)
|
|
929
|
+
return super().execute(sql, parameters)
|
|
930
|
+
|
|
931
|
+
def executemany(self, sql: str, parameters: Any) -> sqlite3.Cursor:
|
|
932
|
+
_test_sql(sql)
|
|
933
|
+
if first_bind in sql:
|
|
934
|
+
parameters = [
|
|
935
|
+
_numeric_param_as_dict(p) for p in parameters
|
|
936
|
+
]
|
|
937
|
+
return super().executemany(sql, parameters)
|
|
938
|
+
|
|
939
|
+
return SQLiteFix99953Connection
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
class _SQLiteDialect_pysqlite_dollar(_SQLiteDialect_pysqlite_numeric):
|
|
943
|
+
"""numeric dialect that uses $ for testing only
|
|
944
|
+
|
|
945
|
+
internal use only. This dialect is **NOT** supported by SQLAlchemy
|
|
946
|
+
and may change at any time.
|
|
947
|
+
|
|
948
|
+
"""
|
|
949
|
+
|
|
950
|
+
supports_statement_cache = True
|
|
951
|
+
default_paramstyle = "numeric_dollar"
|
|
952
|
+
driver = "pysqlite_dollar"
|
|
953
|
+
|
|
954
|
+
_first_bind = "$1"
|
|
955
|
+
_not_in_statement_regexp = re.compile(r"[^\d]:\d+")
|
|
956
|
+
|
|
957
|
+
def __init__(self, *arg: Any, **kw: Any) -> None:
|
|
958
|
+
kw.setdefault("paramstyle", "numeric_dollar")
|
|
959
|
+
super().__init__(*arg, **kw)
|