Use ciso8601 for parsing MySQLdb datetimes (#71818)
* Use ciso8601 for parsing MySQLDB datetimes
The default parser is this:
5340191feb/MySQLdb/times.py (L66)
* tweak
* tweak
* add coverage for building the MySQLdb connect conv param
This commit is contained in:
parent
abe78b1212
commit
a8f1dda004
4 changed files with 54 additions and 1 deletions
|
@ -10,6 +10,7 @@ from homeassistant.helpers.json import JSONEncoder
|
|||
|
||||
DATA_INSTANCE = "recorder_instance"
|
||||
SQLITE_URL_PREFIX = "sqlite://"
|
||||
MYSQLDB_URL_PREFIX = "mysql://"
|
||||
DOMAIN = "recorder"
|
||||
|
||||
CONF_DB_INTEGRITY_CHECK = "db_integrity_check"
|
||||
|
|
|
@ -41,6 +41,7 @@ from .const import (
|
|||
DB_WORKER_PREFIX,
|
||||
KEEPALIVE_TIME,
|
||||
MAX_QUEUE_BACKLOG,
|
||||
MYSQLDB_URL_PREFIX,
|
||||
SQLITE_URL_PREFIX,
|
||||
SupportedDialect,
|
||||
)
|
||||
|
@ -77,6 +78,7 @@ from .tasks import (
|
|||
WaitTask,
|
||||
)
|
||||
from .util import (
|
||||
build_mysqldb_conv,
|
||||
dburl_to_path,
|
||||
end_incomplete_runs,
|
||||
is_second_sunday,
|
||||
|
@ -1014,6 +1016,14 @@ class Recorder(threading.Thread):
|
|||
kwargs["pool_reset_on_return"] = None
|
||||
elif self.db_url.startswith(SQLITE_URL_PREFIX):
|
||||
kwargs["poolclass"] = RecorderPool
|
||||
elif self.db_url.startswith(MYSQLDB_URL_PREFIX):
|
||||
# If they have configured MySQLDB but don't have
|
||||
# the MySQLDB module installed this will throw
|
||||
# an ImportError which we suppress here since
|
||||
# sqlalchemy will give them a better error when
|
||||
# it tried to import it below.
|
||||
with contextlib.suppress(ImportError):
|
||||
kwargs["connect_args"] = {"conv": build_mysqldb_conv()}
|
||||
else:
|
||||
kwargs["echo"] = False
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ from awesomeversion import (
|
|||
AwesomeVersionException,
|
||||
AwesomeVersionStrategy,
|
||||
)
|
||||
import ciso8601
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.engine.cursor import CursorFetchStrategy
|
||||
from sqlalchemy.exc import OperationalError, SQLAlchemyError
|
||||
|
@ -331,6 +332,30 @@ def _extract_version_from_server_response(
|
|||
return None
|
||||
|
||||
|
||||
def _datetime_or_none(value: str) -> datetime | None:
|
||||
"""Fast version of mysqldb DateTime_or_None.
|
||||
|
||||
https://github.com/PyMySQL/mysqlclient/blob/v2.1.0/MySQLdb/times.py#L66
|
||||
"""
|
||||
try:
|
||||
return ciso8601.parse_datetime(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def build_mysqldb_conv() -> dict:
|
||||
"""Build a MySQLDB conv dict that uses cisco8601 to parse datetimes."""
|
||||
# Late imports since we only call this if they are using mysqldb
|
||||
from MySQLdb.constants import ( # pylint: disable=import-outside-toplevel,import-error
|
||||
FIELD_TYPE,
|
||||
)
|
||||
from MySQLdb.converters import ( # pylint: disable=import-outside-toplevel,import-error
|
||||
conversions,
|
||||
)
|
||||
|
||||
return {**conversions, FIELD_TYPE.DATETIME: _datetime_or_none}
|
||||
|
||||
|
||||
def setup_connection_for_dialect(
|
||||
instance: Recorder,
|
||||
dialect_name: str,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from datetime import datetime, timedelta
|
||||
import os
|
||||
import sqlite3
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
|
@ -641,3 +641,20 @@ def test_is_second_sunday():
|
|||
assert is_second_sunday(datetime(2022, 5, 8, 0, 0, 0, tzinfo=dt_util.UTC)) is True
|
||||
|
||||
assert is_second_sunday(datetime(2022, 1, 10, 0, 0, 0, tzinfo=dt_util.UTC)) is False
|
||||
|
||||
|
||||
def test_build_mysqldb_conv():
|
||||
"""Test building the MySQLdb connect conv param."""
|
||||
mock_converters = Mock(conversions={"original": "preserved"})
|
||||
mock_constants = Mock(FIELD_TYPE=Mock(DATETIME="DATETIME"))
|
||||
with patch.dict(
|
||||
"sys.modules",
|
||||
**{"MySQLdb.constants": mock_constants, "MySQLdb.converters": mock_converters},
|
||||
):
|
||||
conv = util.build_mysqldb_conv()
|
||||
|
||||
assert conv["original"] == "preserved"
|
||||
assert conv["DATETIME"]("INVALID") is None
|
||||
assert conv["DATETIME"]("2022-05-13T22:33:12.741") == datetime(
|
||||
2022, 5, 13, 22, 33, 12, 741000, tzinfo=None
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue