mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Bump SQLAlchemy to 2.0.36 (#126683)
* Bump SQLAlchemy to 2.0.35 changelog: https://docs.sqlalchemy.org/en/20/changelog/changelog_20.html#change-2.0.35 * fix mocking * adjust to .36 * remove ignored as these are now typed * fix SQLAlchemy
This commit is contained in:
parent
44ed83a829
commit
ffeefd4856
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@ -143,7 +143,7 @@ jobs:
|
|||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
env-file: true
|
||||||
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-dev"
|
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-dev"
|
||||||
skip-binary: aiohttp;multidict;yarl
|
skip-binary: aiohttp;multidict;yarl;SQLAlchemy
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
|
@ -162,14 +162,14 @@ class Unused(CHAR):
|
|||||||
"""An unused column type that behaves like a string."""
|
"""An unused column type that behaves like a string."""
|
||||||
|
|
||||||
|
|
||||||
@compiles(UnusedDateTime, "mysql", "mariadb", "sqlite") # type: ignore[misc,no-untyped-call]
|
@compiles(UnusedDateTime, "mysql", "mariadb", "sqlite")
|
||||||
@compiles(Unused, "mysql", "mariadb", "sqlite") # type: ignore[misc,no-untyped-call]
|
@compiles(Unused, "mysql", "mariadb", "sqlite")
|
||||||
def compile_char_zero(type_: TypeDecorator, compiler: Any, **kw: Any) -> str:
|
def compile_char_zero(type_: TypeDecorator, compiler: Any, **kw: Any) -> str:
|
||||||
"""Compile UnusedDateTime and Unused as CHAR(0) on mysql, mariadb, and sqlite."""
|
"""Compile UnusedDateTime and Unused as CHAR(0) on mysql, mariadb, and sqlite."""
|
||||||
return "CHAR(0)" # Uses 1 byte on MySQL (no change on sqlite)
|
return "CHAR(0)" # Uses 1 byte on MySQL (no change on sqlite)
|
||||||
|
|
||||||
|
|
||||||
@compiles(Unused, "postgresql") # type: ignore[misc,no-untyped-call]
|
@compiles(Unused, "postgresql")
|
||||||
def compile_char_one(type_: TypeDecorator, compiler: Any, **kw: Any) -> str:
|
def compile_char_one(type_: TypeDecorator, compiler: Any, **kw: Any) -> str:
|
||||||
"""Compile Unused as CHAR(1) on postgresql."""
|
"""Compile Unused as CHAR(1) on postgresql."""
|
||||||
return "CHAR(1)" # Uses 1 byte
|
return "CHAR(1)" # Uses 1 byte
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"SQLAlchemy==2.0.31",
|
"SQLAlchemy==2.0.36",
|
||||||
"fnv-hash-fast==1.0.2",
|
"fnv-hash-fast==1.0.2",
|
||||||
"psutil-home-assistant==0.0.1"
|
"psutil-home-assistant==0.0.1"
|
||||||
]
|
]
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sql",
|
"documentation": "https://www.home-assistant.io/integrations/sql",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["SQLAlchemy==2.0.31", "sqlparse==0.5.0"]
|
"requirements": ["SQLAlchemy==2.0.36", "sqlparse==0.5.0"]
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ pyudev==0.24.1
|
|||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
securetar==2024.11.0
|
securetar==2024.11.0
|
||||||
SQLAlchemy==2.0.31
|
SQLAlchemy==2.0.36
|
||||||
standard-aifc==3.13.0;python_version>='3.13'
|
standard-aifc==3.13.0;python_version>='3.13'
|
||||||
standard-telnetlib==3.13.0;python_version>='3.13'
|
standard-telnetlib==3.13.0;python_version>='3.13'
|
||||||
typing-extensions>=4.12.2,<5.0
|
typing-extensions>=4.12.2,<5.0
|
||||||
|
@ -66,7 +66,7 @@ dependencies = [
|
|||||||
"PyYAML==6.0.2",
|
"PyYAML==6.0.2",
|
||||||
"requests==2.32.3",
|
"requests==2.32.3",
|
||||||
"securetar==2024.11.0",
|
"securetar==2024.11.0",
|
||||||
"SQLAlchemy==2.0.31",
|
"SQLAlchemy==2.0.36",
|
||||||
"standard-aifc==3.13.0;python_version>='3.13'",
|
"standard-aifc==3.13.0;python_version>='3.13'",
|
||||||
"standard-telnetlib==3.13.0;python_version>='3.13'",
|
"standard-telnetlib==3.13.0;python_version>='3.13'",
|
||||||
"typing-extensions>=4.12.2,<5.0",
|
"typing-extensions>=4.12.2,<5.0",
|
||||||
|
@ -37,7 +37,7 @@ python-slugify==8.0.4
|
|||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
securetar==2024.11.0
|
securetar==2024.11.0
|
||||||
SQLAlchemy==2.0.31
|
SQLAlchemy==2.0.36
|
||||||
standard-aifc==3.13.0;python_version>='3.13'
|
standard-aifc==3.13.0;python_version>='3.13'
|
||||||
standard-telnetlib==3.13.0;python_version>='3.13'
|
standard-telnetlib==3.13.0;python_version>='3.13'
|
||||||
typing-extensions>=4.12.2,<5.0
|
typing-extensions>=4.12.2,<5.0
|
||||||
|
@ -116,7 +116,7 @@ RtmAPI==0.7.2
|
|||||||
|
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
SQLAlchemy==2.0.31
|
SQLAlchemy==2.0.36
|
||||||
|
|
||||||
# homeassistant.components.tami4
|
# homeassistant.components.tami4
|
||||||
Tami4EdgeAPI==3.0
|
Tami4EdgeAPI==3.0
|
||||||
|
@ -110,7 +110,7 @@ RtmAPI==0.7.2
|
|||||||
|
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
SQLAlchemy==2.0.31
|
SQLAlchemy==2.0.36
|
||||||
|
|
||||||
# homeassistant.components.tami4
|
# homeassistant.components.tami4
|
||||||
Tami4EdgeAPI==3.0
|
Tami4EdgeAPI==3.0
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
@ -597,9 +598,6 @@ async def test_options_flow_db_url_empty(
|
|||||||
"homeassistant.components.sql.async_setup_entry",
|
"homeassistant.components.sql.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.components.sql.config_flow.sqlalchemy.create_engine",
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.options.async_configure(
|
result = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -621,7 +619,9 @@ async def test_options_flow_db_url_empty(
|
|||||||
|
|
||||||
|
|
||||||
async def test_full_flow_not_recorder_db(
|
async def test_full_flow_not_recorder_db(
|
||||||
recorder_mock: Recorder, hass: HomeAssistant
|
recorder_mock: Recorder,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test full config flow with not using recorder db."""
|
"""Test full config flow with not using recorder db."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -629,20 +629,19 @@ async def test_full_flow_not_recorder_db(
|
|||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
|
db_path = tmp_path / "db.db"
|
||||||
|
db_path_str = f"sqlite:///{db_path}"
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.sql.async_setup_entry",
|
"homeassistant.components.sql.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.components.sql.config_flow.sqlalchemy.create_engine",
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"name": "Get Value",
|
"name": "Get Value",
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"column": "value",
|
"column": "value",
|
||||||
@ -654,7 +653,7 @@ async def test_full_flow_not_recorder_db(
|
|||||||
assert result2["title"] == "Get Value"
|
assert result2["title"] == "Get Value"
|
||||||
assert result2["options"] == {
|
assert result2["options"] == {
|
||||||
"name": "Get Value",
|
"name": "Get Value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"column": "value",
|
"column": "value",
|
||||||
}
|
}
|
||||||
@ -671,15 +670,12 @@ async def test_full_flow_not_recorder_db(
|
|||||||
"homeassistant.components.sql.async_setup_entry",
|
"homeassistant.components.sql.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"homeassistant.components.sql.config_flow.sqlalchemy.create_engine",
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.options.async_configure(
|
result = await hass.config_entries.options.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"column": "value",
|
"column": "value",
|
||||||
"unit_of_measurement": "MiB",
|
"unit_of_measurement": "MiB",
|
||||||
},
|
},
|
||||||
@ -689,7 +685,7 @@ async def test_full_flow_not_recorder_db(
|
|||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
"name": "Get Value",
|
"name": "Get Value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"column": "value",
|
"column": "value",
|
||||||
"unit_of_measurement": "MiB",
|
"unit_of_measurement": "MiB",
|
||||||
@ -697,24 +693,22 @@ async def test_full_flow_not_recorder_db(
|
|||||||
|
|
||||||
# Need to test same again to mitigate issue with db_url removal
|
# Need to test same again to mitigate issue with db_url removal
|
||||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||||
with patch(
|
|
||||||
"homeassistant.components.sql.config_flow.sqlalchemy.create_engine",
|
result = await hass.config_entries.options.async_configure(
|
||||||
):
|
result["flow_id"],
|
||||||
result = await hass.config_entries.options.async_configure(
|
user_input={
|
||||||
result["flow_id"],
|
"query": "SELECT 5 as value",
|
||||||
user_input={
|
"db_url": db_path_str,
|
||||||
"query": "SELECT 5 as value",
|
"column": "value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"unit_of_measurement": "MB",
|
||||||
"column": "value",
|
},
|
||||||
"unit_of_measurement": "MB",
|
)
|
||||||
},
|
await hass.async_block_till_done()
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
"name": "Get Value",
|
"name": "Get Value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"column": "value",
|
"column": "value",
|
||||||
"unit_of_measurement": "MB",
|
"unit_of_measurement": "MB",
|
||||||
@ -722,7 +716,7 @@ async def test_full_flow_not_recorder_db(
|
|||||||
|
|
||||||
assert entry.options == {
|
assert entry.options == {
|
||||||
"name": "Get Value",
|
"name": "Get Value",
|
||||||
"db_url": "sqlite://path/to/db.db",
|
"db_url": db_path_str,
|
||||||
"query": "SELECT 5 as value",
|
"query": "SELECT 5 as value",
|
||||||
"column": "value",
|
"column": "value",
|
||||||
"unit_of_measurement": "MB",
|
"unit_of_measurement": "MB",
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
import sqlite3
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import text as sql_text
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from homeassistant.components.recorder import Recorder
|
from homeassistant.components.recorder import Recorder
|
||||||
@ -143,29 +144,37 @@ async def test_query_no_value(
|
|||||||
assert text in caplog.text
|
assert text in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_query_mssql_no_result(
|
async def test_query_on_disk_sqlite_no_result(
|
||||||
recorder_mock: Recorder, hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
recorder_mock: Recorder,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the SQL sensor with a query that returns no value."""
|
"""Test the SQL sensor with a query that returns no value."""
|
||||||
config = {
|
db_path = tmp_path / "test.db"
|
||||||
"db_url": "mssql://",
|
db_path_str = f"sqlite:///{db_path}"
|
||||||
"query": "SELECT 5 as value where 1=2",
|
|
||||||
"column": "value",
|
|
||||||
"name": "count_tables",
|
|
||||||
}
|
|
||||||
with (
|
|
||||||
patch("homeassistant.components.sql.sensor.sqlalchemy"),
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.sql.sensor.sqlalchemy.text",
|
|
||||||
return_value=sql_text("SELECT TOP 1 5 as value where 1=2"),
|
|
||||||
),
|
|
||||||
):
|
|
||||||
await init_integration(hass, config)
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.count_tables")
|
def make_test_db():
|
||||||
|
"""Create a test database."""
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
conn.execute("CREATE TABLE users (value INTEGER)")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(make_test_db)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"db_url": db_path_str,
|
||||||
|
"query": "SELECT value from users",
|
||||||
|
"column": "value",
|
||||||
|
"name": "count_users",
|
||||||
|
}
|
||||||
|
await init_integration(hass, config)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.count_users")
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
text = "SELECT TOP 1 5 AS VALUE WHERE 1=2 returned no results"
|
text = "SELECT value from users LIMIT 1; returned no results"
|
||||||
assert text in caplog.text
|
assert text in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user