Skip to content
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom

## Development Commands

- Run "python3" in current virtual environment to ensure the correct Python version is used for testing.
- When entering a new environment or worktree, run `script/setup` to set up the virtual environment with all development dependencies (pylint, pre-commit hooks, etc.). This is required before committing. If uv reports that no download was found for the required Python version, the environment is running an outdated version of uv; upgrade it with `curl -LsSf https://astral.sh/uv/install.sh | sh` and run `script/setup` again.
- .vscode/tasks.json contains useful commands used for development.
- After finishing a code session, run `uv run prek run --all-files` to check for linting and formatting issues.
Expand All @@ -162,7 +163,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
## Testing

- Use `uv run pytest` to run tests
- After modifying `strings.json` for an integration, regenerate the English translation file before running tests: `.venv/bin/python3 -m script.translations develop --integration <integration_name>`. Tests load translations from the generated `translations/en.json`, not directly from `strings.json`.
- After modifying `strings.json` for an integration, regenerate the English translation file before running tests: `python3 -m script.translations develop --integration <integration_name>`. Tests load translations from the generated `translations/en.json`, not directly from `strings.json`.
- When writing or modifying tests, ensure all test function parameters have type annotations.
- Prefer concrete types (for example, `HomeAssistant`, `MockConfigEntry`, etc.) over `Any`.
- Prefer `@pytest.mark.usefixtures` over arguments, if the argument is not going to be used.
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom

## Development Commands

- Run "python3" in current virtual environment to ensure the correct Python version is used for testing.
- When entering a new environment or worktree, run `script/setup` to set up the virtual environment with all development dependencies (pylint, pre-commit hooks, etc.). This is required before committing. If uv reports that no download was found for the required Python version, the environment is running an outdated version of uv; upgrade it with `curl -LsSf https://astral.sh/uv/install.sh | sh` and run `script/setup` again.
- .vscode/tasks.json contains useful commands used for development.
- After finishing a code session, run `uv run prek run --all-files` to check for linting and formatting issues.
Expand All @@ -26,7 +27,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
## Testing

- Use `uv run pytest` to run tests
- After modifying `strings.json` for an integration, regenerate the English translation file before running tests: `.venv/bin/python3 -m script.translations develop --integration <integration_name>`. Tests load translations from the generated `translations/en.json`, not directly from `strings.json`.
- After modifying `strings.json` for an integration, regenerate the English translation file before running tests: `python3 -m script.translations develop --integration <integration_name>`. Tests load translations from the generated `translations/en.json`, not directly from `strings.json`.
- When writing or modifying tests, ensure all test function parameters have type annotations.
- Prefer concrete types (for example, `HomeAssistant`, `MockConfigEntry`, etc.) over `Any`.
- Prefer `@pytest.mark.usefixtures` over arguments, if the argument is not going to be used.
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
USER vscode

ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
# Force "uv run" to use the configured virtual environment for all commands
# avoid using uv run --active for every command
# '.venv' is no longer created
ENV UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV
RUN --mount=type=bind,source=.python-version,target=.python-version \
uv python install \
&& uv venv $VIRTUAL_ENV
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/airgradient/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ async def _async_update_data(self) -> AirGradientData:
) from error
if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, self.serial_number)}
device_entry = device_registry.async_get_device_by_identifier(
(DOMAIN, self.serial_number), self.config_entry.entry_id
)
assert device_entry
device_registry.async_update_device(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/airos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> b
mac_adress = dr.format_mac(entry.unique_id)

device_registry = dr.async_get(hass)
if device_entry := device_registry.async_get_device(
connections={(dr.CONNECTION_NETWORK_MAC, mac_adress)}
if device_entry := device_registry.async_get_device_by_connection(
(dr.CONNECTION_NETWORK_MAC, mac_adress), entry.entry_id
):
old_device_id = next(
(
Expand Down
10 changes: 6 additions & 4 deletions homeassistant/components/airthings_ble/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ class AirthingsBLESensorEntityDescription(SensorEntityDescription):


@callback
def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
def async_migrate(
hass: HomeAssistant, entry_id: str, address: str, sensor_name: str
) -> None:
"""Migrate entities to new unique ids (with BLE Address)."""
ent_reg = er.async_get(hass)
unique_id_trailer = f"_{sensor_name}"
Expand All @@ -179,8 +181,8 @@ def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
return
dev_reg = dr.async_get(hass)
if not (
device := dev_reg.async_get_device(
connections={(CONNECTION_BLUETOOTH, address)}
device := dev_reg.async_get_device_by_connection(
(CONNECTION_BLUETOOTH, address), entry_id
)
):
return
Expand Down Expand Up @@ -221,7 +223,7 @@ async def async_setup_entry(
sensor_value,
)
continue
async_migrate(hass, coordinator.data.address, sensor_type)
async_migrate(hass, entry.entry_id, coordinator.data.address, sensor_type)
entities.append(
AirthingsSensor(
coordinator, coordinator.data, SENSORS_MAPPING_TEMPLATE[sensor_type]
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/alexa_devices/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ async def _async_remove_device_stale(
"Detected change in devices: serial %s removed",
serial_num,
)
device = device_registry.async_get_device(
identifiers={(DOMAIN, serial_num)}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, serial_num), self.config_entry.entry_id
)
if device:
device_registry.async_update_device(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/androidtv/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ async def async_get_config_entry_diagnostics(
# Gather information how this AndroidTV device is represented in Home Assistant
device_registry = dr.async_get(hass)
entity_registry = er.async_get(hass)
hass_device = device_registry.async_get_device(
identifiers={(DOMAIN, str(entry.unique_id))}
hass_device = device_registry.async_get_device_by_identifier(
(DOMAIN, str(entry.unique_id)), entry.entry_id
)
if not hass_device:
return data
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/anthropic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
DOMAIN,
entry.entry_id,
)
device = device_registry.async_get_device(
identifiers={(DOMAIN, entry.entry_id)}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, entry.entry_id), entry.entry_id
)

if conversation_entity_id is not None:
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/aqvify/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ async def _async_update_data(self) -> AqvifyCoordinatorData:
account_id = self.config_entry.unique_id
device_registry = dr.async_get(self.hass)
for device_id in stale_devices:
device = device_registry.async_get_device(
identifiers={(DOMAIN, f"{account_id}_{device_id}")}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, f"{account_id}_{device_id}"),
self.config_entry.entry_id,
)
if device:
device_registry.async_update_device(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/bosch_shc/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ async def async_remove_devices(
) -> None:
"""Get item that is removed from session."""
dev_registry = dr.async_get(hass)
device = dev_registry.async_get_device(identifiers={(DOMAIN, entity.device_id)})
device = dev_registry.async_get_device_by_identifier(
(DOMAIN, entity.device_id), entry_id
)
if device is not None:
dev_registry.async_update_device(device.id, remove_config_entry_id=entry_id)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/broadlink/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ async def async_update(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""
device_registry = dr.async_get(hass)
assert entry.unique_id
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, entry.unique_id)}
device_entry = device_registry.async_get_device_by_identifier(
(DOMAIN, entry.unique_id), entry.entry_id
)
assert device_entry
device_registry.async_update_device(device_entry.id, name=entry.title)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/comelit/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ async def _async_remove_stale_devices(
i,
)
identifier = f"{self.config_entry.entry_id}-{dev_type}-{i}"
device = device_registry.async_get_device(
identifiers={(DOMAIN, identifier)}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, identifier), self.config_entry.entry_id
)
if device:
device_registry.async_update_device(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/comelit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ def _async_remove_state_config_entry_from_devices(

device_registry = dr.async_get(hass)
for identifier in identifiers:
device = device_registry.async_get_device(identifiers={(DOMAIN, identifier)})
device = device_registry.async_get_device_by_identifier(
(DOMAIN, identifier), config_entry.entry_id
)
if device:
_LOGGER.info(
"Removing config entry %s from device %s",
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/deconz/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ async def async_remove_orphaned_entries_service(hub: DeconzHub) -> None:
]

# Don't remove the Gateway service entry
hub_service = device_registry.async_get_device(
identifiers={(DOMAIN, hub.api.config.bridge_id)}
hub_service = device_registry.async_get_device_by_identifier(
(DOMAIN, hub.api.config.bridge_id), hub.config_entry.entry_id
)
if hub_service and hub_service.id in devices_to_be_removed:
devices_to_be_removed.remove(hub_service.id)
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/devolo_home_network/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
class DevoloDataUpdateCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
"""Class to manage fetching data from devolo Home Network devices."""

config_entry: DevoloHomeNetworkConfigEntry

def __init__(
self,
hass: HomeAssistant,
Expand Down Expand Up @@ -86,8 +88,8 @@ def update_sw_version(self) -> None:
"""Update device registry with new firmware version."""
device_registry = dr.async_get(self.hass)
if (
device_entry := device_registry.async_get_device(
identifiers={(DOMAIN, self.device.serial_number)}
device_entry := device_registry.async_get_device_by_identifier(
(DOMAIN, self.device.serial_number), self.config_entry.entry_id
)
) and device_entry.sw_version != self.device.firmware_version:
device_registry.async_update_device(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/dsmr/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,9 @@ def rename_old_gas_to_mbus(
"""Rename old gas sensor to mbus variant."""
dev_reg = dr.async_get(hass)
for dev_id in (mbus_device_id, entry.entry_id):
device_entry_v1 = dev_reg.async_get_device(identifiers={(DOMAIN, dev_id)})
device_entry_v1 = dev_reg.async_get_device_by_identifier(
(DOMAIN, dev_id), entry.entry_id
)
if device_entry_v1 is not None:
device_id = device_entry_v1.id

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/duco/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ def _async_add_new_entities() -> None:
device_reg = dr.async_get(hass)
mac = entry.unique_id
for node_id in stale_node_ids:
device = device_reg.async_get_device(
identifiers={(DOMAIN, f"{mac}_{node_id}")}
device = device_reg.async_get_device_by_identifier(
(DOMAIN, f"{mac}_{node_id}"), entry.entry_id
)
if device:
device_reg.async_update_device(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/dwd_weather_warnings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ async def async_setup_entry(
) -> bool:
"""Set up a config entry."""
device_registry = dr.async_get(hass)
if device_registry.async_get_device(identifiers={(DOMAIN, entry.entry_id)}):
if device_registry.async_get_device_by_identifier(
(DOMAIN, entry.entry_id), entry.entry_id
):
device_registry.async_clear_config_entry(entry.entry_id, entry.domain)
coordinator = DwdWeatherWarningsCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/earn_e_p1/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
class EarnEP1Coordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Coordinator for the EARN-E P1 Meter."""

config_entry: EarnEP1ConfigEntry

def __init__(
self,
hass: HomeAssistant,
Expand Down Expand Up @@ -49,8 +51,8 @@ def _handle_update(self, device: EarnEP1Device, _raw: dict[str, Any]) -> None:
self.sw_version = device.sw_version
device_registry = dr.async_get(self.hass)
if (
device_entry := device_registry.async_get_device(
identifiers={(DOMAIN, self.identifier)}
device_entry := device_registry.async_get_device_by_identifier(
(DOMAIN, self.identifier), self.config_entry.entry_id
)
) is not None:
device_registry.async_update_device(
Expand Down
9 changes: 2 additions & 7 deletions homeassistant/components/enphase_envoy/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,8 @@ async def _async_fetch_and_compare_mac(self) -> None:

# Add to or update device registry connections as needed
device_registry = dr.async_get(self.hass)
envoy_device = device_registry.async_get_device(
identifiers={
(
DOMAIN,
self.envoy_serial_number,
)
}
envoy_device = device_registry.async_get_device_by_identifier(
(DOMAIN, self.envoy_serial_number), self.config_entry.entry_id
)
if envoy_device is None:
_LOGGER.error(
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/epson/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ async def set_unique_id(self) -> bool:
if old_entity_id is not None:
ent_reg.async_update_entity(old_entity_id, new_unique_id=uid)
dev_reg = dr.async_get(self.hass)
device = dev_reg.async_get_device({(DOMAIN, self._entry.entry_id)})
device = dev_reg.async_get_device_by_identifier(
(DOMAIN, self._entry.entry_id), self._entry.entry_id
)
if device is not None:
dev_reg.async_update_device(device.id, new_identifiers={(DOMAIN, uid)})
self.hass.async_create_task(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/freshr/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ async def _async_update_data(self) -> dict[str, DeviceSummary]:
if stale_ids:
device_registry = dr.async_get(self.hass)
for device_id in stale_ids:
if device := device_registry.async_get_device(
identifiers={(DOMAIN, device_id)}
if device := device_registry.async_get_device_by_identifier(
(DOMAIN, device_id), self.config_entry.entry_id
):
device_registry.async_update_device(
device.id,
Expand Down
10 changes: 3 additions & 7 deletions homeassistant/components/fyta/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,9 @@ def _async_add_remove_devices(self) -> None:

device_registry = dr.async_get(self.hass)
for plant_id in removed_plants:
if device := device_registry.async_get_device(
identifiers={
(
DOMAIN,
f"{self.config_entry.entry_id}-{plant_id}",
)
}
if device := device_registry.async_get_device_by_identifier(
(DOMAIN, f"{self.config_entry.entry_id}-{plant_id}"),
self.config_entry.entry_id,
):
device_registry.async_update_device(
device_id=device.id,
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/github/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: GithubConfigEntry) ->
unique_id=repository,
)
hass.config_entries.async_add_subentry(entry, subentry)
if device := dev_reg.async_get_device({(DOMAIN, repository)}):
if device := dev_reg.async_get_device_by_identifier(
(DOMAIN, repository), entry.entry_id
):
dev_reg.async_update_device(
device.id,
new_config_entry_id=entry.entry_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
DOMAIN,
entry.entry_id,
)
device = device_registry.async_get_device(
identifiers={(DOMAIN, entry.entry_id)}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, entry.entry_id), entry.entry_id
)

if conversation_entity_id is not None:
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/habitica/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ def _party_update_listener() -> None:
)
party_added_by_this_entry = None
if party:
identifier = {(DOMAIN, f"{config_entry.unique_id}_{party!s}")}
if device := device_reg.async_get_device(identifiers=identifier):
identifier = (DOMAIN, f"{config_entry.unique_id}_{party!s}")
if device := device_reg.async_get_device_by_identifier(
identifier, config_entry.entry_id
):
device_reg.async_update_device(
device.id, remove_config_entry_id=config_entry.entry_id
)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/home_connect/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ async def event_listener(self, event_message: EventMessage) -> None:
self.call_all_event_listeners()

case EventType.DEPAIRED:
device = self.device_registry.async_get_device(
identifiers={(DOMAIN, self.data.info.ha_id)}
device = self.device_registry.async_get_device_by_identifier(
(DOMAIN, self.data.info.ha_id), self._config_entry.entry_id
)
if device:
self.device_registry.async_update_device(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/homee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ async def _remove_node_callback(node: HomeeNode, add: bool) -> None:
"""Call when a node is removed."""
if add:
return
device = device_registry.async_get_device(
identifiers={(DOMAIN, f"{entry.runtime_data.settings.uid}-{node.id}")}
device = device_registry.async_get_device_by_identifier(
(DOMAIN, f"{entry.runtime_data.settings.uid}-{node.id}"), entry.entry_id
)
if device:
_LOGGER.info("Removing device %s", device.name)
Expand Down
Loading
Loading