1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Add support for Python 3.14 (#153939)

This commit is contained in:
Marc Mueller
2025-10-15 20:50:16 +02:00
committed by GitHub
parent 7829c2d03e
commit 7abe289681
21 changed files with 215 additions and 69 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ env:
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.11"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
ALL_PYTHON_VERSIONS: "['3.13', '3.14']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support
+47 -26
View File
@@ -5,14 +5,9 @@ from __future__ import annotations
import asyncio
import logging
from random import randrange
import sys
from typing import Any, cast
from pyatv import connect, exceptions, scan
from pyatv.conf import AppleTV
from pyatv.const import DeviceModel, Protocol
from pyatv.convert import model_str
from pyatv.interface import AppleTV as AppleTVInterface, DeviceListener
from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -29,7 +24,11 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send
@@ -43,6 +42,18 @@ from .const import (
SIGNAL_DISCONNECTED,
)
if sys.version_info < (3, 14):
from pyatv import connect, exceptions, scan
from pyatv.conf import AppleTV
from pyatv.const import DeviceModel, Protocol
from pyatv.convert import model_str
from pyatv.interface import AppleTV as AppleTVInterface, DeviceListener
else:
class DeviceListener:
"""Dummy class."""
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME_TV = "Apple TV"
@@ -53,31 +64,41 @@ BACKOFF_TIME_UPPER_LIMIT = 300 # Five minutes
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
AUTH_EXCEPTIONS = (
exceptions.AuthenticationError,
exceptions.InvalidCredentialsError,
exceptions.NoCredentialsError,
)
CONNECTION_TIMEOUT_EXCEPTIONS = (
OSError,
asyncio.CancelledError,
TimeoutError,
exceptions.ConnectionLostError,
exceptions.ConnectionFailedError,
)
DEVICE_EXCEPTIONS = (
exceptions.ProtocolError,
exceptions.NoServiceError,
exceptions.PairingError,
exceptions.BackOffError,
exceptions.DeviceIdMissingError,
)
if sys.version_info < (3, 14):
AUTH_EXCEPTIONS = (
exceptions.AuthenticationError,
exceptions.InvalidCredentialsError,
exceptions.NoCredentialsError,
)
CONNECTION_TIMEOUT_EXCEPTIONS = (
OSError,
asyncio.CancelledError,
TimeoutError,
exceptions.ConnectionLostError,
exceptions.ConnectionFailedError,
)
DEVICE_EXCEPTIONS = (
exceptions.ProtocolError,
exceptions.NoServiceError,
exceptions.PairingError,
exceptions.BackOffError,
exceptions.DeviceIdMissingError,
)
else:
AUTH_EXCEPTIONS = ()
CONNECTION_TIMEOUT_EXCEPTIONS = ()
DEVICE_EXCEPTIONS = ()
type AppleTvConfigEntry = ConfigEntry[AppleTVManager]
async def async_setup_entry(hass: HomeAssistant, entry: AppleTvConfigEntry) -> bool:
"""Set up a config entry for Apple TV."""
if sys.version_info >= (3, 14):
raise HomeAssistantError(
"Apple TV is not supported on Python 3.14. Please use Python 3.13."
)
manager = AppleTVManager(hass, entry)
if manager.is_on:
@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push",
"loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.16.1"],
"requirements": ["pyatv==0.16.1;python_version<'3.14'"],
"zeroconf": [
"_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.",
+11 -2
View File
@@ -1,15 +1,20 @@
"""The Logitech Harmony Hub integration."""
from __future__ import annotations
import logging
import sys
from homeassistant.components.remote import ATTR_ACTIVITY, ATTR_DELAY_SECS
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import HARMONY_OPTIONS_UPDATE, PLATFORMS
from .data import HarmonyConfigEntry, HarmonyData
if sys.version_info < (3, 14):
from .const import HARMONY_OPTIONS_UPDATE, PLATFORMS
from .data import HarmonyConfigEntry, HarmonyData
_LOGGER = logging.getLogger(__name__)
@@ -20,6 +25,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: HarmonyConfigEntry) -> b
# when setting up a config entry, we fallback to adding
# the options to the config entry and pull them out here if
# they are missing from the options
if sys.version_info >= (3, 14):
raise HomeAssistantError(
"Logitech Harmony Hub is not supported on Python 3.14. Please use Python 3.13."
)
_async_import_options_from_data_if_missing(hass, entry)
address = entry.data[CONF_HOST]
@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/harmony",
"iot_class": "local_push",
"loggers": ["aioharmony", "slixmpp"],
"requirements": ["aioharmony==0.5.3"],
"requirements": ["aioharmony==0.5.3;python_version<'3.14'"],
"ssdp": [
{
"manufacturer": "Logitech",
@@ -453,6 +453,10 @@ async def _async_generate_memory_profile(hass: HomeAssistant, call: ServiceCall)
# Imports deferred to avoid loading modules
# in memory since usually only one part of this
# integration is used at a time
if sys.version_info >= (3, 14):
raise HomeAssistantError(
"Memory profiling is not supported on Python 3.14. Please use Python 3.13."
)
from guppy import hpy # noqa: PLC0415
start_time = int(time.time() * 1000000)
@@ -7,7 +7,7 @@
"quality_scale": "internal",
"requirements": [
"pyprof2calltree==1.4.5",
"guppy3==3.1.5",
"guppy3==3.1.5;python_version<'3.14'",
"objgraph==3.5.0"
],
"single_config_entry": true
@@ -5,5 +5,8 @@
"documentation": "https://www.home-assistant.io/integrations/python_script",
"loggers": ["RestrictedPython"],
"quality_scale": "internal",
"requirements": ["RestrictedPython==8.0"]
"requirements": [
"RestrictedPython==8.0;python_version<'3.14'",
"RestrictedPython==8.1a1.dev0;python_version>='3.14'"
]
}
+14 -3
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from concurrent.futures.thread import _threads_queues, _worker
import sys
import threading
from typing import Any
import weakref
@@ -53,6 +54,18 @@ class DBInterruptibleThreadPoolExecutor(InterruptibleThreadPoolExecutor):
) -> None:
q.put(None)
if sys.version_info >= (3, 14):
additional_args = (
self._create_worker_context(),
self._work_queue,
)
else:
additional_args = (
self._work_queue,
self._initializer,
self._initargs,
)
num_threads = len(self._threads)
if num_threads < self._max_workers:
thread_name = f"{self._thread_name_prefix or self}_{num_threads}"
@@ -63,9 +76,7 @@ class DBInterruptibleThreadPoolExecutor(InterruptibleThreadPoolExecutor):
self._shutdown_hook,
self.recorder_and_worker_thread_ids,
weakref.ref(self, weakref_cb),
self._work_queue,
self._initializer,
self._initargs,
*(additional_args),
),
)
executor_thread.start()
+4 -1
View File
@@ -6,5 +6,8 @@
"iot_class": "cloud_push",
"loggers": ["pyasn1", "slixmpp"],
"quality_scale": "legacy",
"requirements": ["slixmpp==1.10.0", "emoji==2.8.0"]
"requirements": [
"slixmpp==1.10.0;python_version<'3.14'",
"emoji==2.8.0;python_version<'3.14'"
]
}
+16 -8
View File
@@ -9,16 +9,9 @@ import mimetypes
import pathlib
import random
import string
import sys
import requests
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout, XMPPError
from slixmpp.plugins.xep_0363.http_upload import (
FileTooBig,
FileUploadError,
UploadServiceNotFound,
)
from slixmpp.xmlstream.xmlstream import NotConnectedError
import voluptuous as vol
from homeassistant.components.notify import (
@@ -35,9 +28,20 @@ from homeassistant.const import (
CONF_SENDER,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, template as template_helper
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
if sys.version_info < (3, 14):
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout, XMPPError
from slixmpp.plugins.xep_0363.http_upload import (
FileTooBig,
FileUploadError,
UploadServiceNotFound,
)
from slixmpp.xmlstream.xmlstream import NotConnectedError
_LOGGER = logging.getLogger(__name__)
ATTR_DATA = "data"
@@ -74,6 +78,10 @@ async def async_get_service(
discovery_info: DiscoveryInfoType | None = None,
) -> XmppNotificationService:
"""Get the Jabber (XMPP) notification service."""
if sys.version_info >= (3, 14):
raise HomeAssistantError(
"Jabber (XMPP) is not supported on Python 3.14. Please use Python 3.13."
)
return XmppNotificationService(
config.get(CONF_SENDER),
config.get(CONF_RESOURCE),
+2 -2
View File
@@ -132,8 +132,8 @@ backoff>=2.0
# ensure pydantic version does not float since it might have breaking changes
pydantic==2.12.2
# Required for Python 3.12.4 compatibility (#119223).
mashumaro>=3.13.1
# Required for Python 3.14.0 compatibility (#119223).
mashumaro>=3.17.0
# Breaks asyncio
# https://github.com/pubnub/python/issues/130
+16 -3
View File
@@ -10,6 +10,11 @@ import dataclasses
import sys
from typing import TYPE_CHECKING, Any, cast, dataclass_transform
if sys.version_info >= (3, 14):
from annotationlib import Format, get_annotations
else:
from typing_extensions import Format, get_annotations
if TYPE_CHECKING:
from _typeshed import DataclassInstance
@@ -19,7 +24,7 @@ def _class_fields(cls: type, kw_only: bool) -> list[tuple[str, Any, Any]]:
Extracted from dataclasses._process_class.
"""
cls_annotations = cls.__dict__.get("__annotations__", {})
cls_annotations = get_annotations(cls, format=Format.FORWARDREF)
cls_fields: list[dataclasses.Field[Any]] = []
@@ -96,8 +101,16 @@ class FrozenOrThawed(type):
for parent in cls.__mro__[::-1]:
if parent is object:
continue
annotations |= parent.__annotations__
cls.__annotations__ = annotations
annotations |= get_annotations(parent, format=Format.FORWARDREF)
if "__annotations__" in cls.__dict__ or sys.version_info < (3, 14):
cls.__annotations__ = annotations
else:
def wrapped_annotate(format: Format) -> dict:
return annotations
cls.__annotate__ = wrapped_annotate
return
# First try without setting the kw_only flag, and if that fails, try setting it
+28
View File
@@ -19,6 +19,7 @@ classifiers = [
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Home Automation",
]
requires-python = ">=3.13.2"
@@ -569,6 +570,11 @@ filterwarnings = [
# https://pypi.org/project/opuslib/ - v3.0.1 - 2018-01-16
# https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:importlib._bootstrap",
# - SyntaxWarning - return in finally
# https://github.com/nextcord/nextcord/pull/1268 - v3.1.1 - 2025-08-16
# https://github.com/Python-roborock/python-roborock/ - >=2.50.4
# https://pypi.org/project/sleekxmppfs/ - v1.4.1 - 2022-08-18
"ignore:'return' in a 'finally' block:SyntaxWarning:importlib._bootstrap",
# -- New in Python 3.13
# https://github.com/youknowone/python-deadlib - Backports for aifc, telnetlib
@@ -577,6 +583,28 @@ filterwarnings = [
"ignore:telnetlib was removed in Python 3.13.*'standard-telnetlib':DeprecationWarning:ndms2_client.connection",
"ignore:telnetlib was removed in Python 3.13.*'standard-telnetlib':DeprecationWarning:pyws66i",
# -- New in Python 3.14
# https://github.com/kumaraditya303/aioshutil - v1.5 - 2024-07-20
"ignore:'shutil.ExecError' is deprecated and slated for removal in Python 3.16:DeprecationWarning:aioshutil",
# https://github.com/litl/backoff/pull/220 - v2.2.1 - 2022-10-05 (archived)
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:(backoff._decorator|backoff._async)",
# https://github.com/albertogeniola/elmax-api - v0.0.6.3 - 2024-11-30
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:elmax_api.http",
# https://github.com/py-mine/mcstatus - v12.0.5 - 2025-08-13
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:mcstatus.utils",
# https://github.com/nextcord/nextcord/pull/1269 - v3.1.1 - 2025-08-16
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:nextcord.member",
# https://github.com/andrewsayre/pyheos/pull/124 - >1.0.5
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:pyheos.dispatch",
# https://github.com/SteveEasley/pykaleidescape/pull/7 - v2022.2.6 - 2022-03-07
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:kaleidescape.dispatcher",
# https://github.com/svinota/pyroute2
"ignore:Due to '_pack_', the '.*' Structure will use memory layout compatible with MSVC:DeprecationWarning:pyroute2.ethtool.ioctl",
# https://github.com/googleapis/python-genai
"ignore:'_UnionGenericAlias' is deprecated and slated for removal in Python 3.17:DeprecationWarning:google.genai.types",
# https://github.com/pyusb/pyusb
"ignore:Due to '_pack_', the '.*' Structure will use memory layout compatible with MSVC:DeprecationWarning:usb.backend.libusb0",
# -- Websockets 14.1
# https://websockets.readthedocs.io/en/stable/howto/upgrade.html
"ignore:websockets.legacy is deprecated:DeprecationWarning:websockets.legacy",
+9 -6
View File
@@ -109,7 +109,10 @@ PyXiaomiGateway==0.14.3
RachioPy==1.1.0
# homeassistant.components.python_script
RestrictedPython==8.0
RestrictedPython==8.0;python_version<'3.14'
# homeassistant.components.python_script
RestrictedPython==8.1a1.dev0;python_version>='3.14'
# homeassistant.components.remember_the_milk
RtmAPI==0.7.2
@@ -268,7 +271,7 @@ aiogithubapi==24.6.0
aioguardian==2022.07.0
# homeassistant.components.harmony
aioharmony==0.5.3
aioharmony==0.5.3;python_version<'3.14'
# homeassistant.components.hassio
aiohasupervisor==0.3.3
@@ -883,7 +886,7 @@ elmax-api==0.0.6.4rc0
elvia==0.1.0
# homeassistant.components.xmpp
emoji==2.8.0
emoji==2.8.0;python_version<'3.14'
# homeassistant.components.emulated_roku
emulated-roku==0.3.0
@@ -1127,7 +1130,7 @@ gspread==5.5.0
gstreamer-player==1.1.2
# homeassistant.components.profiler
guppy3==3.1.5
guppy3==3.1.5;python_version<'3.14'
# homeassistant.components.iaqualink
h2==4.3.0
@@ -1885,7 +1888,7 @@ pyatag==0.3.5.3
pyatmo==9.2.3
# homeassistant.components.apple_tv
pyatv==0.16.1
pyatv==0.16.1;python_version<'3.14'
# homeassistant.components.aussie_broadband
pyaussiebb==0.1.5
@@ -2846,7 +2849,7 @@ skyboxremote==0.0.6
slack_sdk==3.33.4
# homeassistant.components.xmpp
slixmpp==1.10.0
slixmpp==1.10.0;python_version<'3.14'
# homeassistant.components.smart_meter_texas
smart-meter-texas==0.5.5
+7 -4
View File
@@ -103,7 +103,10 @@ PyXiaomiGateway==0.14.3
RachioPy==1.1.0
# homeassistant.components.python_script
RestrictedPython==8.0
RestrictedPython==8.0;python_version<'3.14'
# homeassistant.components.python_script
RestrictedPython==8.1a1.dev0;python_version>='3.14'
# homeassistant.components.remember_the_milk
RtmAPI==0.7.2
@@ -253,7 +256,7 @@ aiogithubapi==24.6.0
aioguardian==2022.07.0
# homeassistant.components.harmony
aioharmony==0.5.3
aioharmony==0.5.3;python_version<'3.14'
# homeassistant.components.hassio
aiohasupervisor==0.3.3
@@ -988,7 +991,7 @@ gspread==5.5.0
gstreamer-player==1.1.2
# homeassistant.components.profiler
guppy3==3.1.5
guppy3==3.1.5;python_version<'3.14'
# homeassistant.components.iaqualink
h2==4.3.0
@@ -1593,7 +1596,7 @@ pyatag==0.3.5.3
pyatmo==9.2.3
# homeassistant.components.apple_tv
pyatv==0.16.1
pyatv==0.16.1;python_version<'3.14'
# homeassistant.components.aussie_broadband
pyaussiebb==0.1.5
+2 -2
View File
@@ -157,8 +157,8 @@ backoff>=2.0
# ensure pydantic version does not float since it might have breaking changes
pydantic==2.12.2
# Required for Python 3.12.4 compatibility (#119223).
mashumaro>=3.13.1
# Required for Python 3.14.0 compatibility (#119223).
mashumaro>=3.17.0
# Breaks asyncio
# https://github.com/pubnub/python/issues/130
+5 -2
View File
@@ -1,6 +1,9 @@
"""Tests for Apple TV."""
import sys
import pytest
# Make asserts in the common module display differences
pytest.register_assert_rewrite("tests.components.apple_tv.common")
if sys.version_info < (3, 14):
# Make asserts in the common module display differences
pytest.register_assert_rewrite("tests.components.apple_tv.common")
+10 -4
View File
@@ -1,14 +1,20 @@
"""Fixtures for component."""
from collections.abc import Generator
import sys
from unittest.mock import AsyncMock, MagicMock, patch
from pyatv import conf
from pyatv.const import PairingRequirement, Protocol
from pyatv.support import http
import pytest
from .common import MockPairingHandler, airplay_service, create_conf, mrp_service
if sys.version_info < (3, 14):
from pyatv import conf
from pyatv.const import PairingRequirement, Protocol
from pyatv.support import http
from .common import MockPairingHandler, airplay_service, create_conf, mrp_service
if sys.version_info >= (3, 14):
collect_ignore_glob = ["test_*.py"]
@pytest.fixture(autouse=True, name="mock_scan")
+10 -1
View File
@@ -1,9 +1,11 @@
"""Fixtures for harmony tests."""
from __future__ import annotations
from collections.abc import Generator
import sys
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
from aioharmony.const import ClientCallbackType
import pytest
from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF, DOMAIN
@@ -18,6 +20,13 @@ from .const import (
from tests.common import MockConfigEntry
if sys.version_info < (3, 14):
from aioharmony.const import ClientCallbackType
if sys.version_info >= (3, 14):
collect_ignore_glob = ["test_*.py"]
ACTIVITIES_TO_IDS = {
ACTIVITY_POWER_OFF: -1,
"Watch TV": WATCH_TV_ACTIVITY_ID,
+22
View File
@@ -6,6 +6,7 @@ import logging
import os
from pathlib import Path
import socket
import sys
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
@@ -72,6 +73,9 @@ async def test_basic_usage(hass: HomeAssistant, tmp_path: Path) -> None:
await hass.async_block_till_done()
@pytest.mark.skipif(
sys.version_info >= (3, 14), reason="not yet available on Python 3.14"
)
async def test_memory_usage(hass: HomeAssistant, tmp_path: Path) -> None:
"""Test we can setup and the service is registered."""
test_dir = tmp_path / "profiles"
@@ -103,6 +107,24 @@ async def test_memory_usage(hass: HomeAssistant, tmp_path: Path) -> None:
await hass.async_block_till_done()
@pytest.mark.skipif(sys.version_info < (3, 14), reason="still works on python 3.13")
async def test_memory_usage_py313(hass: HomeAssistant, tmp_path: Path) -> None:
"""Test raise an error on python3.13."""
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert hass.services.has_service(DOMAIN, SERVICE_MEMORY)
with pytest.raises(
HomeAssistantError,
match="Memory profiling is not supported on Python 3.14. Please use Python 3.13.",
):
await hass.services.async_call(
DOMAIN, SERVICE_MEMORY, {CONF_SECONDS: 0.000001}, blocking=True
)
async def test_object_growth_logging(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,