mirror of
https://github.com/home-assistant/core.git
synced 2026-05-07 00:56:50 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 813fa922e2 | |||
| 4b28928702 | |||
| 859ce55c96 | |||
| 9a9f19cb9e | |||
| d8b1bfb268 |
@@ -76,6 +76,7 @@ from .const import (
|
||||
ATTR_HEALTHY,
|
||||
ATTR_INTEGRATION_COUNT,
|
||||
ATTR_INTEGRATIONS,
|
||||
ATTR_ISSUE_TRACKER,
|
||||
ATTR_OPERATING_SYSTEM,
|
||||
ATTR_PROTECTED,
|
||||
ATTR_RECORDER,
|
||||
@@ -414,6 +415,7 @@ class Analytics:
|
||||
custom_integrations.append(
|
||||
{
|
||||
ATTR_DOMAIN: integration.domain,
|
||||
ATTR_ISSUE_TRACKER: integration.issue_tracker,
|
||||
ATTR_VERSION: integration.version,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ ATTR_HEALTHY = "healthy"
|
||||
ATTR_INSTALLATION_TYPE = "installation_type"
|
||||
ATTR_INTEGRATION_COUNT = "integration_count"
|
||||
ATTR_INTEGRATIONS = "integrations"
|
||||
ATTR_ISSUE_TRACKER = "issue_tracker"
|
||||
ATTR_ONBOARDED = "onboarded"
|
||||
ATTR_OPERATING_SYSTEM = "operating_system"
|
||||
ATTR_PREFERENCES = "preferences"
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["indevolt-api==1.6.4"]
|
||||
"requirements": ["indevolt-api==1.6.5"]
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ from .coordinator import (
|
||||
PrusaLinkConfigEntry,
|
||||
PrusaLinkUpdateCoordinator,
|
||||
StatusCoordinator,
|
||||
VersionUpdateCoordinator,
|
||||
)
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
@@ -54,6 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: PrusaLinkConfigEntry) ->
|
||||
"status": StatusCoordinator(hass, entry, api),
|
||||
"job": JobUpdateCoordinator(hass, entry, api),
|
||||
"info": InfoUpdateCoordinator(hass, entry, api),
|
||||
"version": VersionUpdateCoordinator(hass, entry, api),
|
||||
}
|
||||
for coordinator in coordinators.values():
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -16,6 +16,7 @@ from pyprusalink import (
|
||||
PrinterInfo,
|
||||
PrinterStatus,
|
||||
PrusaLink,
|
||||
VersionInfo,
|
||||
)
|
||||
from pyprusalink.types import InvalidAuth, PrusaLinkError
|
||||
|
||||
@@ -32,7 +33,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# rapidly-changing metrics.
|
||||
_MINIMUM_REFRESH_INTERVAL = 1.0
|
||||
|
||||
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo)
|
||||
T = TypeVar("T", PrinterStatus, LegacyPrinterStatus, JobInfo, PrinterInfo, VersionInfo)
|
||||
|
||||
|
||||
type PrusaLinkConfigEntry = ConfigEntry[dict[str, PrusaLinkUpdateCoordinator]]
|
||||
@@ -124,3 +125,11 @@ class InfoUpdateCoordinator(PrusaLinkUpdateCoordinator[PrinterInfo]):
|
||||
async def _fetch_data(self) -> PrinterInfo:
|
||||
"""Fetch the printer data."""
|
||||
return await self.api.get_info()
|
||||
|
||||
|
||||
class VersionUpdateCoordinator(PrusaLinkUpdateCoordinator[VersionInfo]):
|
||||
"""Version update coordinator."""
|
||||
|
||||
async def _fetch_data(self) -> VersionInfo:
|
||||
"""Fetch the version data."""
|
||||
return await self.api.get_version()
|
||||
|
||||
@@ -17,9 +17,14 @@ class PrusaLinkEntity(CoordinatorEntity[PrusaLinkUpdateCoordinator]):
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device information about this PrusaLink device."""
|
||||
coordinators = self.coordinator.config_entry.runtime_data
|
||||
info_data = coordinators["info"].data or {}
|
||||
version_data = coordinators["version"].data or {}
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.coordinator.config_entry.entry_id)},
|
||||
name=self.coordinator.config_entry.title,
|
||||
manufacturer="Prusa",
|
||||
serial_number=info_data.get("serial"),
|
||||
sw_version=version_data.get("firmware"),
|
||||
configuration_url=self.coordinator.api.client.host,
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
entity_domain=LOCK_DOMAIN,
|
||||
schema={
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Required("code"): cv.matches_regex(r"^\d{4,8}$"),
|
||||
vol.Required("code"): vol.All(cv.string, cv.matches_regex(r"^\d{4,8}$")),
|
||||
vol.Optional("notify_on_use", default=True): cv.boolean,
|
||||
},
|
||||
func=SERVICE_ADD_CODE,
|
||||
|
||||
@@ -137,7 +137,7 @@ DEFAULT_MAX_EXCEEDED = "WARNING"
|
||||
ATTR_CUR = "current"
|
||||
ATTR_MAX = "max"
|
||||
|
||||
DATA_SCRIPTS: HassKey[list[ScriptData]] = HassKey("helpers.script")
|
||||
DATA_SCRIPTS: HassKey[dict[int, ScriptData]] = HassKey("helpers.script")
|
||||
DATA_SCRIPT_BREAKPOINTS: HassKey[dict[str, dict[str, set[str]]]] = HassKey(
|
||||
"helpers.script_breakpoints"
|
||||
)
|
||||
@@ -1367,7 +1367,9 @@ async def _async_stop_scripts_after_shutdown(
|
||||
"""Stop running Script objects started after shutdown."""
|
||||
hass.data[DATA_NEW_SCRIPT_RUNS_NOT_ALLOWED] = None
|
||||
running_scripts = [
|
||||
script for script in hass.data[DATA_SCRIPTS] if script["instance"].is_running
|
||||
script
|
||||
for script in hass.data[DATA_SCRIPTS].values()
|
||||
if script["instance"].is_running
|
||||
]
|
||||
if running_scripts:
|
||||
names = ", ".join([script["instance"].name for script in running_scripts])
|
||||
@@ -1386,7 +1388,7 @@ async def _async_stop_scripts_at_shutdown(hass: HomeAssistant, event: Event) ->
|
||||
|
||||
running_scripts = [
|
||||
script
|
||||
for script in hass.data[DATA_SCRIPTS]
|
||||
for script in hass.data[DATA_SCRIPTS].values()
|
||||
if script["instance"].is_running and script["started_before_shutdown"]
|
||||
]
|
||||
if running_scripts:
|
||||
@@ -1467,16 +1469,17 @@ class Script:
|
||||
|
||||
enabled attribute is only used for non-top-level scripts.
|
||||
"""
|
||||
if not (all_scripts := hass.data.get(DATA_SCRIPTS)):
|
||||
all_scripts = hass.data[DATA_SCRIPTS] = []
|
||||
if (all_scripts := hass.data.get(DATA_SCRIPTS)) is None:
|
||||
all_scripts = hass.data[DATA_SCRIPTS] = {}
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass)
|
||||
)
|
||||
self.top_level = top_level
|
||||
if top_level:
|
||||
all_scripts.append(
|
||||
{"instance": self, "started_before_shutdown": not hass.is_stopping}
|
||||
)
|
||||
all_scripts[id(self)] = {
|
||||
"instance": self,
|
||||
"started_before_shutdown": not hass.is_stopping,
|
||||
}
|
||||
if DATA_SCRIPT_BREAKPOINTS not in hass.data:
|
||||
hass.data[DATA_SCRIPT_BREAKPOINTS] = {}
|
||||
|
||||
@@ -1786,6 +1789,12 @@ class Script:
|
||||
started_action: Callable[..., Any] | None = None,
|
||||
) -> ScriptRunResult | None:
|
||||
"""Run script."""
|
||||
# Prevent running an unloaded script
|
||||
if self._unloaded:
|
||||
raise RuntimeError(
|
||||
f"Cannot run script '{self.name}' after it has been unloaded"
|
||||
)
|
||||
|
||||
if context is None:
|
||||
self._log(
|
||||
"Running script requires passing in a context", level=logging.WARNING
|
||||
@@ -1919,6 +1928,10 @@ class Script:
|
||||
)
|
||||
self._unloaded = True
|
||||
|
||||
# Remove from global script registry
|
||||
if self.top_level:
|
||||
del self._hass.data[DATA_SCRIPTS][id(self)]
|
||||
|
||||
for cond in self._condition_cache.values():
|
||||
cond.async_unload()
|
||||
self._condition_cache.clear()
|
||||
|
||||
Generated
+1
-1
@@ -1329,7 +1329,7 @@ imgw_pib==2.1.1
|
||||
incomfort-client==0.7.0
|
||||
|
||||
# homeassistant.components.indevolt
|
||||
indevolt-api==1.6.4
|
||||
indevolt-api==1.6.5
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb-client==1.50.0
|
||||
|
||||
Generated
+1
-1
@@ -1181,7 +1181,7 @@ imgw_pib==2.1.1
|
||||
incomfort-client==0.7.0
|
||||
|
||||
# homeassistant.components.indevolt
|
||||
indevolt-api==1.6.4
|
||||
indevolt-api==1.6.5
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb-client==1.50.0
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
'custom_integrations': list([
|
||||
dict({
|
||||
'domain': 'test_package',
|
||||
'issue_tracker': 'http://github.com/test/test_package/issues',
|
||||
'version': <AwesomeVersion SemVer '1.2.3'>,
|
||||
}),
|
||||
]),
|
||||
|
||||
@@ -33,6 +33,7 @@ def mock_version_api() -> Generator[dict[str, str]]:
|
||||
"server": "2.1.2",
|
||||
"text": "PrusaLink",
|
||||
"hostname": "PrusaXL",
|
||||
"firmware": "6.1.2+11023",
|
||||
}
|
||||
with patch("pyprusalink.PrusaLink.get_version", return_value=resp):
|
||||
yield resp
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.components.prusalink.config_flow import ConfigFlow
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
@@ -20,6 +20,22 @@ from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
pytestmark = pytest.mark.usefixtures("mock_api")
|
||||
|
||||
|
||||
async def test_device_info(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test device info is populated with serial number and firmware version."""
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, mock_config_entry.entry_id)}
|
||||
)
|
||||
assert device is not None
|
||||
assert device.serial_number == "serial-1337"
|
||||
assert device.sw_version == "6.1.2+11023"
|
||||
|
||||
|
||||
async def test_unloading(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: ConfigEntry,
|
||||
|
||||
@@ -139,6 +139,35 @@ async def test_add_code_service(
|
||||
assert call_args.notify_on_use == notify_on_use
|
||||
|
||||
|
||||
async def test_add_code_service_integer_code(
|
||||
hass: HomeAssistant,
|
||||
mock_lock: Mock,
|
||||
mock_added_config_entry: MockSchlageConfigEntry,
|
||||
) -> None:
|
||||
"""Test add_code service with an integer code."""
|
||||
mock_lock.access_codes = {}
|
||||
mock_lock.add_access_code = Mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_CODE,
|
||||
service_data={
|
||||
"entity_id": "lock.vault_door",
|
||||
"name": "test_user",
|
||||
"code": 1234,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_lock.refresh_access_codes.assert_called_once()
|
||||
mock_lock.add_access_code.assert_called_once()
|
||||
call_args = mock_lock.add_access_code.call_args[0][0]
|
||||
assert isinstance(call_args, AccessCode)
|
||||
assert call_args.name == "test_user"
|
||||
assert call_args.code == "1234"
|
||||
|
||||
|
||||
async def test_add_code_service_default_notify_on_use_value(
|
||||
hass: HomeAssistant,
|
||||
mock_lock: Mock,
|
||||
|
||||
@@ -7143,3 +7143,47 @@ async def test_async_unload_raises_if_running(hass: HomeAssistant) -> None:
|
||||
|
||||
# Should succeed now
|
||||
script_obj.async_unload()
|
||||
|
||||
|
||||
async def test_async_unload_removes_from_data_scripts(hass: HomeAssistant) -> None:
|
||||
"""Test that async_unload removes the script from hass.data[DATA_SCRIPTS]."""
|
||||
sequence = cv.SCRIPT_SCHEMA([{"event": "test_event"}])
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
|
||||
all_scripts = hass.data[script.DATA_SCRIPTS]
|
||||
assert any(s["instance"] is script_obj for s in all_scripts.values())
|
||||
|
||||
script_obj.async_unload()
|
||||
|
||||
assert not any(s["instance"] is script_obj for s in all_scripts.values())
|
||||
|
||||
|
||||
async def test_async_unload_non_top_level_does_not_touch_data_scripts(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test that async_unload on a non-top-level script doesn't touch DATA_SCRIPTS."""
|
||||
sequence = cv.SCRIPT_SCHEMA([{"event": "test_event"}])
|
||||
script_obj = script.Script(
|
||||
hass, sequence, "Sub Script", "test_domain", top_level=False
|
||||
)
|
||||
|
||||
all_scripts = hass.data[script.DATA_SCRIPTS]
|
||||
count_before = len(all_scripts)
|
||||
|
||||
# Should not raise and should not modify DATA_SCRIPTS
|
||||
script_obj.async_unload()
|
||||
|
||||
assert len(all_scripts) == count_before
|
||||
|
||||
|
||||
async def test_async_run_raises_if_unloaded(hass: HomeAssistant) -> None:
|
||||
"""Test that async_run raises RuntimeError if the script has been unloaded."""
|
||||
sequence = cv.SCRIPT_SCHEMA([{"event": "test_event"}])
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
|
||||
script_obj.async_unload()
|
||||
|
||||
with pytest.raises(
|
||||
RuntimeError, match="Cannot run script.*after it has been unloaded"
|
||||
):
|
||||
await script_obj.async_run(context=Context())
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"requirements": [],
|
||||
"dependencies": [],
|
||||
"codeowners": [],
|
||||
"issue_tracker": "http://github.com/test/test_package/issues",
|
||||
"version": "1.2.3"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user