gh-90016: Deprecate default sqlite3 adapters and converters (#94276)

Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
Erlend Egeberg Aasland 2022-07-20 21:37:59 +02:00 committed by GitHub
parent 000a4eebe7
commit 6dadf6ca01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 49 deletions

View File

@ -1,22 +0,0 @@
import sqlite3
import datetime
con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
cur = con.cursor()
cur.execute("create table test(d date, ts timestamp)")
today = datetime.date.today()
now = datetime.datetime.now()
cur.execute("insert into test(d, ts) values (?, ?)", (today, now))
cur.execute("select d, ts from test")
row = cur.fetchone()
print(today, "=>", row[0], type(row[0]))
print(now, "=>", row[1], type(row[1]))
cur.execute('select current_date as "d [date]", current_timestamp as "ts [timestamp]"')
row = cur.fetchone()
print("current_date", row[0], type(row[0]))
print("current_timestamp", row[1], type(row[1]))
con.close()

View File

@ -1333,6 +1333,8 @@ This function can then be registered using :func:`register_adapter`.
.. literalinclude:: ../includes/sqlite3/adapter_point_2.py .. literalinclude:: ../includes/sqlite3/adapter_point_2.py
.. _sqlite3-converters:
Converting SQLite values to custom Python types Converting SQLite values to custom Python types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -1373,27 +1375,28 @@ The following example illustrates the implicit and explicit approaches:
.. literalinclude:: ../includes/sqlite3/converter_point.py .. literalinclude:: ../includes/sqlite3/converter_point.py
Default adapters and converters .. _sqlite3-default-converters:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are default adapters for the date and datetime types in the datetime Default adapters and converters (deprecated)
module. They will be sent as ISO dates/ISO timestamps to SQLite. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default converters are registered under the name "date" for .. note::
:class:`datetime.date` and under the name "timestamp" for
:class:`datetime.datetime`.
This way, you can use date/timestamps from Python without any additional The default adapters and converters are deprecated as of Python 3.12.
fiddling in most cases. The format of the adapters is also compatible with the Instead, use the :ref:`sqlite3-adapter-converter-recipes`
experimental SQLite date/time functions. and tailor them to your needs.
The following example demonstrates this. The deprecated default adapters and converters consist of:
.. literalinclude:: ../includes/sqlite3/pysqlite_datetime.py * An adapter for :class:`datetime.date` objects to :class:`strings <str>` in
`ISO 8601`_ format.
If a timestamp stored in SQLite has a fractional part longer than 6 * An adapter for :class:`datetime.datetime` objects to strings in
numbers, its value will be truncated to microsecond precision by the ISO 8601 format.
timestamp converter. * A converter for :ref:`declared <sqlite3-converters>` "date" types to
:class:`datetime.date` objects.
* A converter for declared "timestamp" types to
:class:`datetime.datetime` objects.
Fractional parts will be truncated to 6 digits (microsecond precision).
.. note:: .. note::
@ -1402,6 +1405,10 @@ timestamp converter.
offsets in timestamps, either leave converters disabled, or register an offsets in timestamps, either leave converters disabled, or register an
offset-aware converter with :func:`register_converter`. offset-aware converter with :func:`register_converter`.
.. deprecated:: 3.12
.. _ISO 8601: https://en.wikipedia.org/wiki/ISO_8601
.. _sqlite3-adapter-converter-recipes: .. _sqlite3-adapter-converter-recipes:

View File

@ -135,6 +135,12 @@ Deprecated
* :class:`typing.Hashable` and :class:`typing.Sized` aliases for :class:`collections.abc.Hashable` * :class:`typing.Hashable` and :class:`typing.Sized` aliases for :class:`collections.abc.Hashable`
and :class:`collections.abc.Sized`. (:gh:`94309`) and :class:`collections.abc.Sized`. (:gh:`94309`)
* The :mod:`sqlite3` :ref:`default adapters and converters
<sqlite3-default-converters>` are now deprecated.
Instead, use the :ref:`sqlite3-adapter-converter-recipes`
and tailor them to your needs.
(Contributed by Erlend E. Aasland in :gh:`90016`.)
Pending Removal in Python 3.13 Pending Removal in Python 3.13
------------------------------ ------------------------------

View File

@ -55,16 +55,25 @@ Binary = memoryview
collections.abc.Sequence.register(Row) collections.abc.Sequence.register(Row)
def register_adapters_and_converters(): def register_adapters_and_converters():
from warnings import warn
msg = ("The default {what} is deprecated as of Python 3.12; "
"see the sqlite3 documentation for suggested replacement recipes")
def adapt_date(val): def adapt_date(val):
warn(msg.format(what="date adapter"), DeprecationWarning, stacklevel=2)
return val.isoformat() return val.isoformat()
def adapt_datetime(val): def adapt_datetime(val):
warn(msg.format(what="datetime adapter"), DeprecationWarning, stacklevel=2)
return val.isoformat(" ") return val.isoformat(" ")
def convert_date(val): def convert_date(val):
warn(msg.format(what="date converter"), DeprecationWarning, stacklevel=2)
return datetime.date(*map(int, val.split(b"-"))) return datetime.date(*map(int, val.split(b"-")))
def convert_timestamp(val): def convert_timestamp(val):
warn(msg.format(what="timestamp converter"), DeprecationWarning, stacklevel=2)
datepart, timepart = val.split(b" ") datepart, timepart = val.split(b" ")
year, month, day = map(int, datepart.split(b"-")) year, month, day = map(int, datepart.split(b"-"))
timepart_full = timepart.split(b".") timepart_full = timepart.split(b".")

View File

@ -129,6 +129,7 @@ class RegressionTests(unittest.TestCase):
con = sqlite.connect(":memory:",detect_types=sqlite.PARSE_DECLTYPES) con = sqlite.connect(":memory:",detect_types=sqlite.PARSE_DECLTYPES)
cur = con.cursor() cur = con.cursor()
cur.execute("create table foo(bar timestamp)") cur.execute("create table foo(bar timestamp)")
with self.assertWarnsRegex(DeprecationWarning, "adapter"):
cur.execute("insert into foo(bar) values (?)", (datetime.datetime.now(),)) cur.execute("insert into foo(bar) values (?)", (datetime.datetime.now(),))
cur.execute(SELECT) cur.execute(SELECT)
cur.execute("drop table foo") cur.execute("drop table foo")
@ -305,6 +306,7 @@ class RegressionTests(unittest.TestCase):
cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.123456789')") cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.123456789')")
cur.execute("SELECT * FROM t") cur.execute("SELECT * FROM t")
with self.assertWarnsRegex(DeprecationWarning, "converter"):
values = [x[0] for x in cur.fetchall()] values = [x[0] for x in cur.fetchall()]
self.assertEqual(values, [ self.assertEqual(values, [

View File

@ -496,37 +496,50 @@ class DateTimeTests(unittest.TestCase):
def test_sqlite_date(self): def test_sqlite_date(self):
d = sqlite.Date(2004, 2, 14) d = sqlite.Date(2004, 2, 14)
with self.assertWarnsRegex(DeprecationWarning, "adapter") as cm:
self.cur.execute("insert into test(d) values (?)", (d,)) self.cur.execute("insert into test(d) values (?)", (d,))
self.assertEqual(cm.filename, __file__)
self.cur.execute("select d from test") self.cur.execute("select d from test")
with self.assertWarnsRegex(DeprecationWarning, "converter") as cm:
d2 = self.cur.fetchone()[0] d2 = self.cur.fetchone()[0]
self.assertEqual(cm.filename, __file__)
self.assertEqual(d, d2) self.assertEqual(d, d2)
def test_sqlite_timestamp(self): def test_sqlite_timestamp(self):
ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0) ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0)
with self.assertWarnsRegex(DeprecationWarning, "adapter") as cm:
self.cur.execute("insert into test(ts) values (?)", (ts,)) self.cur.execute("insert into test(ts) values (?)", (ts,))
self.assertEqual(cm.filename, __file__)
self.cur.execute("select ts from test") self.cur.execute("select ts from test")
with self.assertWarnsRegex(DeprecationWarning, "converter") as cm:
ts2 = self.cur.fetchone()[0] ts2 = self.cur.fetchone()[0]
self.assertEqual(cm.filename, __file__)
self.assertEqual(ts, ts2) self.assertEqual(ts, ts2)
def test_sql_timestamp(self): def test_sql_timestamp(self):
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
self.cur.execute("insert into test(ts) values (current_timestamp)") self.cur.execute("insert into test(ts) values (current_timestamp)")
self.cur.execute("select ts from test") self.cur.execute("select ts from test")
with self.assertWarnsRegex(DeprecationWarning, "converter"):
ts = self.cur.fetchone()[0] ts = self.cur.fetchone()[0]
self.assertEqual(type(ts), datetime.datetime) self.assertEqual(type(ts), datetime.datetime)
self.assertEqual(ts.year, now.year) self.assertEqual(ts.year, now.year)
def test_date_time_sub_seconds(self): def test_date_time_sub_seconds(self):
ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 500000) ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 500000)
with self.assertWarnsRegex(DeprecationWarning, "adapter"):
self.cur.execute("insert into test(ts) values (?)", (ts,)) self.cur.execute("insert into test(ts) values (?)", (ts,))
self.cur.execute("select ts from test") self.cur.execute("select ts from test")
with self.assertWarnsRegex(DeprecationWarning, "converter"):
ts2 = self.cur.fetchone()[0] ts2 = self.cur.fetchone()[0]
self.assertEqual(ts, ts2) self.assertEqual(ts, ts2)
def test_date_time_sub_seconds_floating_point(self): def test_date_time_sub_seconds_floating_point(self):
ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 510241) ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 510241)
with self.assertWarnsRegex(DeprecationWarning, "adapter"):
self.cur.execute("insert into test(ts) values (?)", (ts,)) self.cur.execute("insert into test(ts) values (?)", (ts,))
self.cur.execute("select ts from test") self.cur.execute("select ts from test")
with self.assertWarnsRegex(DeprecationWarning, "converter"):
ts2 = self.cur.fetchone()[0] ts2 = self.cur.fetchone()[0]
self.assertEqual(ts, ts2) self.assertEqual(ts, ts2)

View File

@ -0,0 +1,2 @@
Deprecate :mod:`sqlite3` :ref:`default adapters and converters
<sqlite3-default-converters>`. Patch by Erlend E. Aasland.