Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use PEP 695 misc #117788

Merged
merged 2 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 15 additions & 17 deletions homeassistant/components/deconz/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from collections.abc import Callable
from dataclasses import dataclass
from typing import Generic, TypeVar

from pydeconz.interfaces.sensors import SensorResources
from pydeconz.models.event import EventType
Expand Down Expand Up @@ -48,29 +47,28 @@
"water",
)

T = TypeVar(
"T",
Alarm,
CarbonMonoxide,
Fire,
GenericFlag,
OpenClose,
Presence,
Vibration,
Water,
PydeconzSensorBase,
)


@dataclass(frozen=True, kw_only=True)
class DeconzBinarySensorDescription(Generic[T], BinarySensorEntityDescription):
class DeconzBinarySensorDescription[
_T: (
Alarm,
CarbonMonoxide,
Fire,
GenericFlag,
OpenClose,
Presence,
Vibration,
Water,
PydeconzSensorBase,
)
](BinarySensorEntityDescription):
"""Class describing deCONZ binary sensor entities."""

instance_check: type[T] | None = None
instance_check: type[_T] | None = None
name_suffix: str = ""
old_unique_id_suffix: str = ""
update_key: str
value_fn: Callable[[T], bool | None]
value_fn: Callable[[_T], bool | None]


ENTITY_DESCRIPTIONS: tuple[DeconzBinarySensorDescription, ...] = (
Expand Down
18 changes: 8 additions & 10 deletions homeassistant/components/traccar_server/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from collections.abc import Callable
from dataclasses import dataclass
from typing import Generic, Literal, TypeVar, cast
from typing import Any, Literal

from pytraccar import DeviceModel

Expand All @@ -22,13 +22,9 @@
from .coordinator import TraccarServerCoordinator
from .entity import TraccarServerEntity

_T = TypeVar("_T")


@dataclass(frozen=True, kw_only=True)
class TraccarServerBinarySensorEntityDescription(
Generic[_T], BinarySensorEntityDescription
):
class TraccarServerBinarySensorEntityDescription[_T](BinarySensorEntityDescription):
"""Describe Traccar Server sensor entity."""

data_key: Literal["position", "device", "geofence", "attributes"]
Expand All @@ -37,7 +33,9 @@ class TraccarServerBinarySensorEntityDescription(
value_fn: Callable[[_T], bool | None]


TRACCAR_SERVER_BINARY_SENSOR_ENTITY_DESCRIPTIONS = (
TRACCAR_SERVER_BINARY_SENSOR_ENTITY_DESCRIPTIONS: tuple[
TraccarServerBinarySensorEntityDescription[Any], ...
] = (
TraccarServerBinarySensorEntityDescription[DeviceModel](
key="attributes.motion",
data_key="position",
Expand Down Expand Up @@ -65,18 +63,18 @@ async def async_setup_entry(
TraccarServerBinarySensor(
coordinator=coordinator,
device=entry["device"],
description=cast(TraccarServerBinarySensorEntityDescription, description),
description=description,
)
for entry in coordinator.data.values()
for description in TRACCAR_SERVER_BINARY_SENSOR_ENTITY_DESCRIPTIONS
)


class TraccarServerBinarySensor(TraccarServerEntity, BinarySensorEntity):
class TraccarServerBinarySensor[_T](TraccarServerEntity, BinarySensorEntity):
"""Represent a traccar server binary sensor."""

_attr_has_entity_name = True
entity_description: TraccarServerBinarySensorEntityDescription
entity_description: TraccarServerBinarySensorEntityDescription[_T]

def __init__(
self,
Expand Down
16 changes: 8 additions & 8 deletions homeassistant/components/traccar_server/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from collections.abc import Callable
from dataclasses import dataclass
from typing import Generic, Literal, TypeVar, cast
from typing import Any, Literal

from pytraccar import DeviceModel, GeofenceModel, PositionModel

Expand All @@ -24,11 +24,9 @@
from .coordinator import TraccarServerCoordinator
from .entity import TraccarServerEntity

_T = TypeVar("_T")


@dataclass(frozen=True, kw_only=True)
class TraccarServerSensorEntityDescription(Generic[_T], SensorEntityDescription):
class TraccarServerSensorEntityDescription[_T](SensorEntityDescription):
"""Describe Traccar Server sensor entity."""

data_key: Literal["position", "device", "geofence", "attributes"]
Expand All @@ -37,7 +35,9 @@ class TraccarServerSensorEntityDescription(Generic[_T], SensorEntityDescription)
value_fn: Callable[[_T], StateType]


TRACCAR_SERVER_SENSOR_ENTITY_DESCRIPTIONS = (
TRACCAR_SERVER_SENSOR_ENTITY_DESCRIPTIONS: tuple[
TraccarServerSensorEntityDescription[Any], ...
] = (
TraccarServerSensorEntityDescription[PositionModel](
key="attributes.batteryLevel",
data_key="position",
Expand Down Expand Up @@ -91,18 +91,18 @@ async def async_setup_entry(
TraccarServerSensor(
coordinator=coordinator,
device=entry["device"],
description=cast(TraccarServerSensorEntityDescription, description),
description=description,
)
for entry in coordinator.data.values()
for description in TRACCAR_SERVER_SENSOR_ENTITY_DESCRIPTIONS
)


class TraccarServerSensor(TraccarServerEntity, SensorEntity):
class TraccarServerSensor[_T](TraccarServerEntity, SensorEntity):
"""Represent a tracked device."""

_attr_has_entity_name = True
entity_description: TraccarServerSensorEntityDescription
entity_description: TraccarServerSensorEntityDescription[_T]

def __init__(
self,
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ def async_create_background_task[_R](
return task

@callback
def async_add_executor_job[_T, *_Ts](
def async_add_executor_job[*_Ts, _T](
self, target: Callable[[*_Ts], _T], *args: *_Ts
) -> asyncio.Future[_T]:
"""Add an executor job from within the event loop."""
Expand All @@ -871,7 +871,7 @@ def async_add_executor_job[_T, *_Ts](
return task

@callback
def async_add_import_executor_job[_T, *_Ts](
def async_add_import_executor_job[*_Ts, _T](
self, target: Callable[[*_Ts], _T], *args: *_Ts
) -> asyncio.Future[_T]:
"""Add an import executor job from within the event loop.
Expand Down
15 changes: 6 additions & 9 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from socket import ( # type: ignore[attr-defined] # private, not in typeshed
_GLOBAL_DEFAULT_TIMEOUT,
)
from typing import Any, TypeVar, cast, overload
from typing import Any, cast, overload
from urllib.parse import urlparse
from uuid import UUID

Expand Down Expand Up @@ -140,9 +140,6 @@ class UrlProtocolSchema(StrEnum):
sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE))
port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))

# typing typevar
_T = TypeVar("_T")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this PR introduced a weird issue which I only noticed when testing Google Generative AI integation.

You have removed _T which breaks calling get_type_hints on ensure_list.

  File "/home/vscode/.local/lib/python3.12/site-packages/voluptuous_openapi/__init__.py", line 106, in convert
    v = convert(validator, custom_serializer=custom_serializer)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.12/site-packages/voluptuous_openapi/__init__.py", line 236, in convert
    schema = get_type_hints(schema).get(
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/typing.py", line 2281, in get_type_hints
    hints[name] = _eval_type(value, globalns, localns)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/typing.py", line 414, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/typing.py", line 924, in _evaluate
    eval(self.__forward_code__, globalns, localns),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
NameError: name '_T' is not defined

This didn't fail in the tests because we don't serialize the full intent schemas there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❯ python3 -c 'from homeassistant.helpers.config_validation import ensure_list; from typing import get_type_hints; get_type_hints(ensure_list)'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python3.12/typing.py", line 2281, in get_type_hints
    hints[name] = _eval_type(value, globalns, localns)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/typing.py", line 414, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/typing.py", line 924, in _evaluate
    eval(self.__forward_code__, globalns, localns),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
NameError: name '_T' is not defined

Copy link
Member

@balloob balloob May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I reinstate this line, everything works but mypy gives me this error:

homeassistant/helpers/config_validation.py:291: error: Name "_T" already defined on line 144  [no-redef]
Found 1 error in 1 file (checked 1 source file)



def path(value: Any) -> str:
"""Validate it's a safe path."""
Expand Down Expand Up @@ -288,14 +285,14 @@ def ensure_list(value: None) -> list[Any]: ...


@overload
def ensure_list(value: list[_T]) -> list[_T]: ...
def ensure_list[_T](value: list[_T]) -> list[_T]: ...


@overload
def ensure_list(value: list[_T] | _T) -> list[_T]: ...
def ensure_list[_T](value: list[_T] | _T) -> list[_T]: ...


def ensure_list(value: _T | None) -> list[_T] | list[Any]:
def ensure_list[_T](value: _T | None) -> list[_T] | list[Any]:
"""Wrap value in list if it is not one."""
if value is None:
return []
Expand Down Expand Up @@ -540,7 +537,7 @@ def time_period_seconds(value: float | str) -> timedelta:
time_period = vol.Any(time_period_str, time_period_seconds, timedelta, time_period_dict)


def match_all(value: _T) -> _T:
def match_all[_T](value: _T) -> _T:
"""Validate that matches all values."""
return value

Expand All @@ -556,7 +553,7 @@ def positive_timedelta(value: timedelta) -> timedelta:
positive_time_period = vol.All(time_period, positive_timedelta)


def remove_falsy(value: list[_T]) -> list[_T]:
def remove_falsy[_T](value: list[_T]) -> list[_T]:
"""Remove falsy values from a list."""
return [v for v in value if v]

Expand Down
2 changes: 1 addition & 1 deletion tests/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ class MockHAClientWebSocket(ClientWebSocketResponse):
"""MagicMock for `homeassistant.components.mqtt.MQTT`."""
MqttMockHAClientGenerator = Callable[..., Coroutine[Any, Any, MqttMockHAClient]]
"""MagicMock generator for `homeassistant.components.mqtt.MQTT`."""
type RecorderInstanceGenerator = Callable[..., Coroutine[Any, Any, "Recorder"]]
type RecorderInstanceGenerator = Callable[..., Coroutine[Any, Any, Recorder]]
"""Instance generator for `homeassistant.components.recorder.Recorder`."""
WebSocketGenerator = Callable[..., Coroutine[Any, Any, MockHAClientWebSocket]]