Skip to content
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
10 changes: 5 additions & 5 deletions .claude/skills/bump-dependency/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Follow these systematic steps to successfully bump a python package requirement
- [ ] **2. Discover Codebase References**: Search the codebase to find all `manifest.json` and requirements files referencing the package.
- [ ] **3. Resolve Version/Tag Details**: Run the integrated validation helper script to resolve version details, GitHub repo, release tag format, and formatted PR links:
```bash
uv run python3 ./.claude/skills/bump-dependency/scripts/resolve_dependency.py <package> <old_version> [--new-version <new_version>]
uv run --no-sync python3 ./.claude/skills/bump-dependency/scripts/resolve_dependency.py <package> <old_version> [--new-version <new_version>]
```
- [ ] **4. Plan-Validate-Execute (Draft Plan)**: Before modifying any files, write a brief, structured plan outlining the integrations to change, old version, new version, and the resolved comparison link. Show this draft plan to the user.

Expand All @@ -33,7 +33,7 @@ Follow these systematic steps to successfully bump a python package requirement
- [ ] **7. Apply Bump to manifests**: Update the version constraint string in all identified `manifest.json` files (e.g., change `"package==1.0.0"` to `"package==1.1.0"`).
- [ ] **8. Regenerate Core Requirements**: Run the requirements generator to update all derivative requirements and constraint files:
```bash
uv run python3 -m script.gen_requirements_all
uv run --no-sync python3 -m script.gen_requirements_all
```
- [ ] **9. Validate Requirements**: Check `git diff` to ensure that only the targeted `manifest.json` files and `requirements_all.txt` (and potentially standard constraints) were modified. No unrelated files must be affected.
- [ ] **10. Local Venv Verification**: Install the exact targeted package version directly inside the virtual environment:
Expand All @@ -44,14 +44,14 @@ Follow these systematic steps to successfully bump a python package requirement
### Phase C: Validation Loop (Tests & Lint)
- [ ] **11. Run Integration Tests**: Execute the pytest suite for all integrations that consume the bumped package:
```bash
uv run pytest tests/components/<integration_name>
uv run --no-sync pytest tests/components/<integration_name>
```
- *Validation Loop*: If tests fail, analyze the error, apply appropriate fixes, and re-run pytest until all tests pass cleanly.
- [ ] **12. Run prek Lint Checks**: Run the local prek hooks on modified files:
```bash
uv run prek run
uv run --no-sync prek run
```
- *Validation Loop*: If prek checks report any formatting or linting violations, fix them and repeat `uv run prek run` until it passes completely without errors.
- *Validation Loop*: If prek checks report any formatting or linting violations, fix them and repeat `uv run --no-sync prek run` until it passes completely without errors.

### Phase D: User Confirmation & PR Creation
- [ ] **13. Commit Changes**: Commit the clean changes:
Expand Down
4 changes: 2 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- 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.
- After finishing a code session, run `uv run --no-sync prek run --all-files` to check for linting and formatting issues.

## Python Syntax Notes

Expand All @@ -163,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
- Use `uv run --no-sync pytest` to run tests
- 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`.
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- 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.
- After finishing a code session, run `uv run --no-sync prek run --all-files` to check for linting and formatting issues.

## Python Syntax Notes

Expand All @@ -26,7 +26,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom

## Testing

- Use `uv run pytest` to run tests
- Use `uv run --no-sync pytest` to run tests
- 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`.
Expand Down
16 changes: 9 additions & 7 deletions homeassistant/components/drop_connect/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import TYPE_CHECKING

from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

Expand Down Expand Up @@ -42,11 +43,12 @@ def __init__(
identifiers={(DOMAIN, unique_id)},
)
if entry_data[CONF_DEVICE_TYPE] != DEV_HUB:
self._attr_device_info.update(
{
"via_device": (
DOMAIN,
entry_data[CONF_DEVICE_OWNER_ID],
)
}
# The owner hub lives in a separate config entry created independently
# by MQTT discovery, so it may not exist yet. Link best-effort when
# present; an identifier can match devices from several config entries
# and we can't tell which is the owner hub, so link to the first.
via_devices = dr.async_get(coordinator.hass).async_get_devices(
identifiers={(DOMAIN, entry_data[CONF_DEVICE_OWNER_ID])}
)
if via_devices:
self._attr_device_info["via_device_id"] = via_devices[0].id
2 changes: 1 addition & 1 deletion homeassistant/components/google_health/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["google-health-api==0.6.0"]
"requirements": ["google-health-api==0.8.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/infrared/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/infrared",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["infrared-protocols==8.2.1"]
"requirements": ["infrared-protocols==9.0.0"]
}
14 changes: 12 additions & 2 deletions homeassistant/components/proxmoxve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from homeassistant.const import CONF_TOKEN, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er

from .const import (
AUTH_OTHER,
Expand All @@ -14,7 +14,7 @@
CONF_REALM,
DEFAULT_REALM,
)
from .coordinator import ProxmoxConfigEntry, ProxmoxCoordinator
from .coordinator import ProxmoxConfigEntry, ProxmoxCoordinator, node_device_info

PLATFORMS = [
Platform.BINARY_SENSOR,
Expand All @@ -32,6 +32,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ProxmoxConfigEntry) -> b
await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator

# Register node devices before forwarding platforms so that child devices
# (VMs, containers, storages) can deterministically resolve their via_device.
device_registry = dr.async_get(hass)
for node_data in coordinator.data.values():
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
**node_device_info(coordinator, node_data),
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True
Expand Down
33 changes: 33 additions & 0 deletions homeassistant/components/proxmoxve/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from proxmoxer.core import ResourceException
import requests
from requests.exceptions import ConnectTimeout, SSLError
from yarl import URL

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -64,6 +65,32 @@ class ProxmoxNodeData:
backups: list[dict[str, Any]] = field(default_factory=list)


def proxmox_base_url(coordinator: ProxmoxCoordinator) -> URL:
"""Return the base URL for the Proxmox VE."""
data = coordinator.config_entry.data
return URL.build(
scheme="https",
host=data[CONF_HOST],
port=data[CONF_PORT],
)


def node_device_info(
coordinator: ProxmoxCoordinator, node_data: ProxmoxNodeData
) -> dr.DeviceInfo:
"""Return the device info for a Proxmox VE node device."""
return dr.DeviceInfo(
identifiers={
(DOMAIN, f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}")
},
name=node_data.node.get("node", str(node_data.node["id"])),
model="Node",
configuration_url=proxmox_base_url(coordinator).with_fragment(
f"v1:0:=node/{node_data.node['node']}"
),
)


class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
"""Data Update Coordinator for Proxmox VE integration."""

Expand Down Expand Up @@ -268,6 +295,12 @@ def _async_add_remove_nodes(self, data: dict[str, ProxmoxNodeData]) -> None:
_LOGGER.debug("New nodes found: %s", new_nodes)
self.known_nodes.update(new_nodes)
new_node_data = [data[node_name] for node_name in new_nodes]
device_registry = dr.async_get(self.hass)
for node_data in new_node_data:
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
**node_device_info(self, node_data),
)
for nodes_callback in self.new_nodes_callbacks:
nodes_callback(new_node_data)

Expand Down
68 changes: 32 additions & 36 deletions homeassistant/components/proxmoxve/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,18 @@

from typing import Any, override

from yarl import URL

from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import ProxmoxCoordinator, ProxmoxNodeData


def _proxmox_base_url(coordinator: ProxmoxCoordinator) -> URL:
"""Return the base URL for the Proxmox VE."""
data = coordinator.config_entry.data
return URL.build(
scheme="https",
host=data[CONF_HOST],
port=data[CONF_PORT],
)
from .coordinator import (
ProxmoxCoordinator,
ProxmoxNodeData,
node_device_info,
proxmox_base_url,
)


class ProxmoxCoordinatorEntity(CoordinatorEntity[ProxmoxCoordinator]):
Expand All @@ -44,16 +37,7 @@ def __init__(
self.device_id = node_data.node["id"]
self.device_name = node_data.node["node"]
self.entity_description = entity_description
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, f"{coordinator.config_entry.entry_id}_node_{self.device_id}")
},
name=node_data.node.get("node", str(self.device_id)),
model="Node",
configuration_url=_proxmox_base_url(coordinator).with_fragment(
f"v1:0:=node/{node_data.node['node']}"
),
)
self._attr_device_info = node_device_info(coordinator, node_data)

self._attr_unique_id = (
f"{coordinator.config_entry.entry_id}"
Expand Down Expand Up @@ -95,12 +79,16 @@ def __init__(
},
name=f"Storage ({self.device_name})",
model="Storage",
configuration_url=_proxmox_base_url(coordinator).with_fragment(
configuration_url=proxmox_base_url(coordinator).with_fragment(
f"v1:0:=storage/{self._node_name}/{storage_data['storage']}"
),
via_device=(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
via_device_id=dr.async_get_device_id_by_identifier(
coordinator.hass,
(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
),
config_entry_id=coordinator.config_entry.entry_id,
),
)

Expand Down Expand Up @@ -150,12 +138,16 @@ def __init__(
},
name=self.device_name,
model="VM",
configuration_url=_proxmox_base_url(coordinator).with_fragment(
configuration_url=proxmox_base_url(coordinator).with_fragment(
f"v1:0:=qemu/{vm_data['vmid']}"
),
via_device=(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
via_device_id=dr.async_get_device_id_by_identifier(
coordinator.hass,
(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
),
config_entry_id=coordinator.config_entry.entry_id,
),
)

Expand Down Expand Up @@ -207,12 +199,16 @@ def __init__(
},
name=self.device_name,
model="Container",
configuration_url=_proxmox_base_url(coordinator).with_fragment(
configuration_url=proxmox_base_url(coordinator).with_fragment(
f"v1:0:=lxc/{container_data['vmid']}"
),
via_device=(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
via_device_id=dr.async_get_device_id_by_identifier(
coordinator.hass,
(
DOMAIN,
f"{coordinator.config_entry.entry_id}_node_{node_data.node['id']}",
),
config_entry_id=coordinator.config_entry.entry_id,
),
)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tesla_fleet/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["tesla-fleet-api"],
"requirements": ["tesla-fleet-api==1.7.6"]
"requirements": ["tesla-fleet-api==1.8.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/teslemetry/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["tesla_fleet_api", "teslemetry_stream"],
"quality_scale": "platinum",
"requirements": ["tesla-fleet-api==1.7.6", "teslemetry-stream==0.10.0"]
"requirements": ["tesla-fleet-api==1.8.0", "teslemetry-stream==0.10.0"]
}
Loading
Loading