Deprecated old backports and typing aliases (#114883)

This commit is contained in:
Marc Mueller 2024-04-07 01:15:30 +02:00 committed by GitHub
parent 8e645c9b32
commit 8324fd5d1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 250 additions and 30 deletions

View File

@ -9,8 +9,21 @@ import it.
from __future__ import annotations from __future__ import annotations
from enum import StrEnum from enum import StrEnum as _StrEnum
from functools import partial
__all__ = [ from homeassistant.helpers.deprecation import (
"StrEnum", DeprecatedAlias,
] all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
# StrEnum deprecated as of 2024.5 use enum.StrEnum instead.
_DEPRECATED_StrEnum = DeprecatedAlias(_StrEnum, "enum.StrEnum", "2025.5")
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@ -9,8 +9,22 @@ import it.
from __future__ import annotations from __future__ import annotations
from functools import cached_property from functools import cached_property as _cached_property, partial
__all__ = [ from homeassistant.helpers.deprecation import (
"cached_property", DeprecatedAlias,
] all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
# cached_property deprecated as of 2024.5 use functools.cached_property instead.
_DEPRECATED_cached_property = DeprecatedAlias(
_cached_property, "functools.cached_property", "2025.5"
)
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@ -7,12 +7,12 @@ import dataclasses
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Literal, TypedDict, cast from typing import Literal, TypedDict, cast
from homeassistant.core import HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.util.ulid import ulid_now from homeassistant.util.ulid import ulid_now
from .registry import BaseRegistry from .registry import BaseRegistry
from .storage import Store from .storage import Store
from .typing import UNDEFINED, EventType, UndefinedType from .typing import UNDEFINED, UndefinedType
DATA_REGISTRY = "category_registry" DATA_REGISTRY = "category_registry"
EVENT_CATEGORY_REGISTRY_UPDATED = "category_registry_updated" EVENT_CATEGORY_REGISTRY_UPDATED = "category_registry_updated"
@ -28,7 +28,7 @@ class EventCategoryRegistryUpdatedData(TypedDict):
category_id: str category_id: str
EventCategoryRegistryUpdated = EventType[EventCategoryRegistryUpdatedData] EventCategoryRegistryUpdated = Event[EventCategoryRegistryUpdatedData]
@dataclass(slots=True, kw_only=True, frozen=True) @dataclass(slots=True, kw_only=True, frozen=True)

View File

@ -243,6 +243,14 @@ class DeprecatedConstantEnum(NamedTuple):
breaks_in_ha_version: str | None breaks_in_ha_version: str | None
class DeprecatedAlias(NamedTuple):
"""Deprecated alias."""
value: Any
replacement: str
breaks_in_ha_version: str | None
_PREFIX_DEPRECATED = "_DEPRECATED_" _PREFIX_DEPRECATED = "_DEPRECATED_"
@ -254,6 +262,7 @@ def check_if_deprecated_constant(name: str, module_globals: dict[str, Any]) -> A
""" """
module_name = module_globals.get("__name__") module_name = module_globals.get("__name__")
value = replacement = None value = replacement = None
description = "constant"
if (deprecated_const := module_globals.get(_PREFIX_DEPRECATED + name)) is None: if (deprecated_const := module_globals.get(_PREFIX_DEPRECATED + name)) is None:
raise AttributeError(f"Module {module_name!r} has no attribute {name!r}") raise AttributeError(f"Module {module_name!r} has no attribute {name!r}")
if isinstance(deprecated_const, DeprecatedConstant): if isinstance(deprecated_const, DeprecatedConstant):
@ -266,6 +275,11 @@ def check_if_deprecated_constant(name: str, module_globals: dict[str, Any]) -> A
f"{deprecated_const.enum.__class__.__name__}.{deprecated_const.enum.name}" f"{deprecated_const.enum.__class__.__name__}.{deprecated_const.enum.name}"
) )
breaks_in_ha_version = deprecated_const.breaks_in_ha_version breaks_in_ha_version = deprecated_const.breaks_in_ha_version
elif isinstance(deprecated_const, DeprecatedAlias):
description = "alias"
value = deprecated_const.value
replacement = deprecated_const.replacement
breaks_in_ha_version = deprecated_const.breaks_in_ha_version
if value is None or replacement is None: if value is None or replacement is None:
msg = ( msg = (
@ -284,7 +298,7 @@ def check_if_deprecated_constant(name: str, module_globals: dict[str, Any]) -> A
name, name,
module_name or __name__, module_name or __name__,
replacement, replacement,
"constant", description,
"used", "used",
breaks_in_ha_version, breaks_in_ha_version,
log_when_no_integration_is_found=False, log_when_no_integration_is_found=False,

View File

@ -2,15 +2,22 @@
from collections.abc import Mapping from collections.abc import Mapping
from enum import Enum from enum import Enum
from functools import partial
from typing import Any, TypeVar from typing import Any, TypeVar
import homeassistant.core import homeassistant.core
from .deprecation import (
DeprecatedAlias,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
_DataT = TypeVar("_DataT") _DataT = TypeVar("_DataT")
GPSType = tuple[float, float] GPSType = tuple[float, float]
ConfigType = dict[str, Any] ConfigType = dict[str, Any]
ContextType = homeassistant.core.Context
DiscoveryInfoType = dict[str, Any] DiscoveryInfoType = dict[str, Any]
ServiceDataType = dict[str, Any] ServiceDataType = dict[str, Any]
StateType = str | int | float | None StateType = str | int | float | None
@ -33,7 +40,23 @@ UNDEFINED = UndefinedType._singleton # pylint: disable=protected-access
# are not present in the core code base. # are not present in the core code base.
# They are kept in order not to break custom integrations # They are kept in order not to break custom integrations
# that may rely on them. # that may rely on them.
# In due time they will be removed. # Deprecated as of 2024.5 use types from homeassistant.core instead.
EventType = homeassistant.core.Event _DEPRECATED_ContextType = DeprecatedAlias(
HomeAssistantType = homeassistant.core.HomeAssistant homeassistant.core.Context, "homeassistant.core.Context", "2025.5"
ServiceCallType = homeassistant.core.ServiceCall )
_DEPRECATED_EventType = DeprecatedAlias(
homeassistant.core.Event, "homeassistant.core.Event", "2025.5"
)
_DEPRECATED_HomeAssistantType = DeprecatedAlias(
homeassistant.core.HomeAssistant, "homeassistant.core.HomeAssistant", "2025.5"
)
_DEPRECATED_ServiceCallType = DeprecatedAlias(
homeassistant.core.ServiceCall, "homeassistant.core.ServiceCall", "2025.5"
)
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@ -1642,6 +1642,40 @@ def import_and_test_deprecated_constant(
assert constant_name in module.__all__ assert constant_name in module.__all__
def import_and_test_deprecated_alias(
caplog: pytest.LogCaptureFixture,
module: ModuleType,
alias_name: str,
replacement: Any,
breaks_in_ha_version: str,
) -> None:
"""Import and test deprecated alias replaced by a value.
- Import deprecated alias
- Assert value is the same as the replacement
- Assert a warning is logged
- Assert the deprecated alias is included in the modules.__dir__()
- Assert the deprecated alias is included in the modules.__all__()
"""
replacement_name = f"{replacement.__module__}.{replacement.__name__}"
value = import_deprecated_constant(module, alias_name)
assert value == replacement
assert (
module.__name__,
logging.WARNING,
(
f"{alias_name} was used from test_constant_deprecation,"
f" this is a deprecated alias which will be removed in HA Core {breaks_in_ha_version}. "
f"Use {replacement_name} instead, please report "
"it to the author of the 'test_constant_deprecation' custom integration"
),
) in caplog.record_tuples
# verify deprecated alias is included in dir()
assert alias_name in dir(module)
assert alias_name in module.__all__
def help_test_all(module: ModuleType) -> None: def help_test_all(module: ModuleType) -> None:
"""Test module.__all__ is correctly set.""" """Test module.__all__ is correctly set."""
assert set(module.__all__) == { assert set(module.__all__) == {

View File

@ -10,6 +10,7 @@ import pytest
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.deprecation import ( from homeassistant.helpers.deprecation import (
DeprecatedAlias,
DeprecatedConstant, DeprecatedConstant,
DeprecatedConstantEnum, DeprecatedConstantEnum,
check_if_deprecated_constant, check_if_deprecated_constant,
@ -283,38 +284,59 @@ class TestDeprecatedConstantEnum(StrEnum):
TEST = "value" TEST = "value"
def _get_value(obj: DeprecatedConstant | DeprecatedConstantEnum | tuple) -> Any: def _get_value(
if isinstance(obj, tuple): obj: DeprecatedConstant
if len(obj) == 2: | DeprecatedConstantEnum
return obj[0].value | DeprecatedAlias
| tuple[Any, ...],
return obj[0] ) -> Any:
if isinstance(obj, DeprecatedConstant): if isinstance(obj, DeprecatedConstant):
return obj.value return obj.value
if isinstance(obj, DeprecatedConstantEnum): if isinstance(obj, DeprecatedConstantEnum):
return obj.enum.value return obj.enum.value
if isinstance(obj, DeprecatedAlias):
return obj.value
if len(obj) == 2:
return obj[0].value
return obj[0]
@pytest.mark.parametrize( @pytest.mark.parametrize(
("deprecated_constant", "extra_msg"), ("deprecated_constant", "extra_msg", "description"),
[ [
( (
DeprecatedConstant("value", "NEW_CONSTANT", None), DeprecatedConstant("value", "NEW_CONSTANT", None),
". Use NEW_CONSTANT instead", ". Use NEW_CONSTANT instead",
"constant",
), ),
( (
DeprecatedConstant(1, "NEW_CONSTANT", "2099.1"), DeprecatedConstant(1, "NEW_CONSTANT", "2099.1"),
" which will be removed in HA Core 2099.1. Use NEW_CONSTANT instead", " which will be removed in HA Core 2099.1. Use NEW_CONSTANT instead",
"constant",
), ),
( (
DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, None), DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, None),
". Use TestDeprecatedConstantEnum.TEST instead", ". Use TestDeprecatedConstantEnum.TEST instead",
"constant",
), ),
( (
DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, "2099.1"), DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, "2099.1"),
" which will be removed in HA Core 2099.1. Use TestDeprecatedConstantEnum.TEST instead", " which will be removed in HA Core 2099.1. Use TestDeprecatedConstantEnum.TEST instead",
"constant",
),
(
DeprecatedAlias(1, "new_alias", None),
". Use new_alias instead",
"alias",
),
(
DeprecatedAlias(1, "new_alias", "2099.1"),
" which will be removed in HA Core 2099.1. Use new_alias instead",
"alias",
), ),
], ],
) )
@ -330,10 +352,14 @@ def _get_value(obj: DeprecatedConstant | DeprecatedConstantEnum | tuple) -> Any:
) )
def test_check_if_deprecated_constant( def test_check_if_deprecated_constant(
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
deprecated_constant: DeprecatedConstant | DeprecatedConstantEnum | tuple, deprecated_constant: DeprecatedConstant
| DeprecatedConstantEnum
| DeprecatedAlias
| tuple,
extra_msg: str, extra_msg: str,
module_name: str, module_name: str,
extra_extra_msg: str, extra_extra_msg: str,
description: str,
) -> None: ) -> None:
"""Test check_if_deprecated_constant.""" """Test check_if_deprecated_constant."""
module_globals = { module_globals = {
@ -378,28 +404,42 @@ def test_check_if_deprecated_constant(
assert ( assert (
module_name, module_name,
logging.WARNING, logging.WARNING,
f"TEST_CONSTANT was used from hue, this is a deprecated constant{extra_msg}{extra_extra_msg}", f"TEST_CONSTANT was used from hue, this is a deprecated {description}{extra_msg}{extra_extra_msg}",
) in caplog.record_tuples ) in caplog.record_tuples
@pytest.mark.parametrize( @pytest.mark.parametrize(
("deprecated_constant", "extra_msg"), ("deprecated_constant", "extra_msg", "description"),
[ [
( (
DeprecatedConstant("value", "NEW_CONSTANT", None), DeprecatedConstant("value", "NEW_CONSTANT", None),
". Use NEW_CONSTANT instead", ". Use NEW_CONSTANT instead",
"constant",
), ),
( (
DeprecatedConstant(1, "NEW_CONSTANT", "2099.1"), DeprecatedConstant(1, "NEW_CONSTANT", "2099.1"),
" which will be removed in HA Core 2099.1. Use NEW_CONSTANT instead", " which will be removed in HA Core 2099.1. Use NEW_CONSTANT instead",
"constant",
), ),
( (
DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, None), DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, None),
". Use TestDeprecatedConstantEnum.TEST instead", ". Use TestDeprecatedConstantEnum.TEST instead",
"constant",
), ),
( (
DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, "2099.1"), DeprecatedConstantEnum(TestDeprecatedConstantEnum.TEST, "2099.1"),
" which will be removed in HA Core 2099.1. Use TestDeprecatedConstantEnum.TEST instead", " which will be removed in HA Core 2099.1. Use TestDeprecatedConstantEnum.TEST instead",
"constant",
),
(
DeprecatedAlias(1, "new_alias", None),
". Use new_alias instead",
"alias",
),
(
DeprecatedAlias(1, "new_alias", "2099.1"),
" which will be removed in HA Core 2099.1. Use new_alias instead",
"alias",
), ),
], ],
) )
@ -412,9 +452,13 @@ def test_check_if_deprecated_constant(
) )
def test_check_if_deprecated_constant_integration_not_found( def test_check_if_deprecated_constant_integration_not_found(
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
deprecated_constant: DeprecatedConstant | DeprecatedConstantEnum | tuple, deprecated_constant: DeprecatedConstant
| DeprecatedConstantEnum
| DeprecatedAlias
| tuple,
extra_msg: str, extra_msg: str,
module_name: str, module_name: str,
description: str,
) -> None: ) -> None:
"""Test check_if_deprecated_constant.""" """Test check_if_deprecated_constant."""
module_globals = { module_globals = {
@ -432,7 +476,7 @@ def test_check_if_deprecated_constant_integration_not_found(
assert ( assert (
module_name, module_name,
logging.WARNING, logging.WARNING,
f"TEST_CONSTANT is a deprecated constant{extra_msg}", f"TEST_CONSTANT is a deprecated {description}{extra_msg}",
) not in caplog.record_tuples ) not in caplog.record_tuples

View File

@ -0,0 +1,37 @@
"""Test typing helper module."""
from __future__ import annotations
from typing import Any
import pytest
from homeassistant.core import Context, Event, HomeAssistant, ServiceCall
from homeassistant.helpers import typing as ha_typing
from tests.common import import_and_test_deprecated_alias
@pytest.mark.parametrize(
("alias_name", "replacement", "breaks_in_ha_version"),
[
("ContextType", Context, "2025.5"),
("EventType", Event, "2025.5"),
("HomeAssistantType", HomeAssistant, "2025.5"),
("ServiceCallType", ServiceCall, "2025.5"),
],
)
def test_deprecated_aliases(
caplog: pytest.LogCaptureFixture,
alias_name: str,
replacement: Any,
breaks_in_ha_version: str,
) -> None:
"""Test deprecated aliases."""
import_and_test_deprecated_alias(
caplog,
ha_typing,
alias_name,
replacement,
breaks_in_ha_version,
)

41
tests/test_backports.py Normal file
View File

@ -0,0 +1,41 @@
"""Test backports package."""
from __future__ import annotations
from enum import StrEnum
from functools import cached_property
from types import ModuleType
from typing import Any
import pytest
from homeassistant.backports import (
enum as backports_enum,
functools as backports_functools,
)
from tests.common import import_and_test_deprecated_alias
@pytest.mark.parametrize(
("module", "replacement", "breaks_in_ha_version"),
[
(backports_enum, StrEnum, "2025.5"),
(backports_functools, cached_property, "2025.5"),
],
)
def test_deprecated_aliases(
caplog: pytest.LogCaptureFixture,
module: ModuleType,
replacement: Any,
breaks_in_ha_version: str,
) -> None:
"""Test deprecated aliases."""
alias_name = replacement.__name__
import_and_test_deprecated_alias(
caplog,
module,
alias_name,
replacement,
breaks_in_ha_version,
)