commit
b315b566e5
|
@ -256,22 +256,39 @@ async def async_setup_hass(
|
|||
runtime_config: RuntimeConfig,
|
||||
) -> core.HomeAssistant | None:
|
||||
"""Set up Home Assistant."""
|
||||
hass = core.HomeAssistant(runtime_config.config_dir)
|
||||
|
||||
async_enable_logging(
|
||||
hass,
|
||||
runtime_config.verbose,
|
||||
runtime_config.log_rotate_days,
|
||||
runtime_config.log_file,
|
||||
runtime_config.log_no_color,
|
||||
)
|
||||
def create_hass() -> core.HomeAssistant:
|
||||
"""Create the hass object and do basic setup."""
|
||||
hass = core.HomeAssistant(runtime_config.config_dir)
|
||||
loader.async_setup(hass)
|
||||
|
||||
if runtime_config.debug or hass.loop.get_debug():
|
||||
hass.config.debug = True
|
||||
async_enable_logging(
|
||||
hass,
|
||||
runtime_config.verbose,
|
||||
runtime_config.log_rotate_days,
|
||||
runtime_config.log_file,
|
||||
runtime_config.log_no_color,
|
||||
)
|
||||
|
||||
if runtime_config.debug or hass.loop.get_debug():
|
||||
hass.config.debug = True
|
||||
|
||||
hass.config.safe_mode = runtime_config.safe_mode
|
||||
hass.config.skip_pip = runtime_config.skip_pip
|
||||
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
|
||||
|
||||
return hass
|
||||
|
||||
async def stop_hass(hass: core.HomeAssistant) -> None:
|
||||
"""Stop hass."""
|
||||
# Ask integrations to shut down. It's messy but we can't
|
||||
# do a clean stop without knowing what is broken
|
||||
with contextlib.suppress(TimeoutError):
|
||||
async with hass.timeout.async_timeout(10):
|
||||
await hass.async_stop()
|
||||
|
||||
hass = create_hass()
|
||||
|
||||
hass.config.safe_mode = runtime_config.safe_mode
|
||||
hass.config.skip_pip = runtime_config.skip_pip
|
||||
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
|
||||
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
||||
_LOGGER.warning(
|
||||
"Skipping pip installation of required modules. This may cause issues"
|
||||
|
@ -283,7 +300,6 @@ async def async_setup_hass(
|
|||
|
||||
_LOGGER.info("Config directory: %s", runtime_config.config_dir)
|
||||
|
||||
loader.async_setup(hass)
|
||||
block_async_io.enable()
|
||||
|
||||
config_dict = None
|
||||
|
@ -309,27 +325,28 @@ async def async_setup_hass(
|
|||
|
||||
if config_dict is None:
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
|
||||
elif not basic_setup_success:
|
||||
_LOGGER.warning("Unable to set up core integrations. Activating recovery mode")
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
|
||||
elif any(domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS):
|
||||
_LOGGER.warning(
|
||||
"Detected that %s did not load. Activating recovery mode",
|
||||
",".join(CRITICAL_INTEGRATIONS),
|
||||
)
|
||||
# Ask integrations to shut down. It's messy but we can't
|
||||
# do a clean stop without knowing what is broken
|
||||
with contextlib.suppress(TimeoutError):
|
||||
async with hass.timeout.async_timeout(10):
|
||||
await hass.async_stop()
|
||||
|
||||
recovery_mode = True
|
||||
old_config = hass.config
|
||||
old_logging = hass.data.get(DATA_LOGGING)
|
||||
|
||||
hass = core.HomeAssistant(old_config.config_dir)
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
|
||||
if old_logging:
|
||||
hass.data[DATA_LOGGING] = old_logging
|
||||
hass.config.debug = old_config.debug
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aemet_opendata"],
|
||||
"requirements": ["AEMET-OpenData==0.5.1"]
|
||||
"requirements": ["AEMET-OpenData==0.5.2"]
|
||||
}
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/canary",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["canary"],
|
||||
"requirements": ["py-canary==0.5.3"]
|
||||
"requirements": ["py-canary==0.5.4"]
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.6.5"]
|
||||
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.6.21"]
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ class EcobeeWeather(WeatherEntity):
|
|||
_attr_native_pressure_unit = UnitOfPressure.HPA
|
||||
_attr_native_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||
_attr_native_visibility_unit = UnitOfLength.METERS
|
||||
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
|
||||
_attr_native_wind_speed_unit = UnitOfSpeed.MILES_PER_HOUR
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.6.2"]
|
||||
"requirements": ["env-canada==0.6.3"]
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.50", "babel==2.13.1"]
|
||||
"requirements": ["holidays==0.51", "babel==2.15.0"]
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pydrawise"],
|
||||
"requirements": ["pydrawise==2024.6.3"]
|
||||
"requirements": ["pydrawise==2024.6.4"]
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ def _get_zone_daily_active_water_use(sensor: HydrawiseSensor) -> float:
|
|||
return float(daily_water_summary.active_use_by_zone_id.get(sensor.zone.id, 0.0))
|
||||
|
||||
|
||||
def _get_controller_daily_active_water_use(sensor: HydrawiseSensor) -> float:
|
||||
def _get_controller_daily_active_water_use(sensor: HydrawiseSensor) -> float | None:
|
||||
"""Get active water use for the controller."""
|
||||
daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id]
|
||||
return daily_water_summary.total_active_use
|
||||
|
@ -71,7 +71,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
|
|||
key="daily_total_water_use",
|
||||
translation_key="daily_total_water_use",
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
native_unit_of_measurement=UnitOfVolume.GALLONS,
|
||||
suggested_display_precision=1,
|
||||
value_fn=_get_controller_daily_total_water_use,
|
||||
),
|
||||
|
@ -79,7 +78,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
|
|||
key="daily_active_water_use",
|
||||
translation_key="daily_active_water_use",
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
native_unit_of_measurement=UnitOfVolume.GALLONS,
|
||||
suggested_display_precision=1,
|
||||
value_fn=_get_controller_daily_active_water_use,
|
||||
),
|
||||
|
@ -87,7 +85,6 @@ FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = (
|
|||
key="daily_inactive_water_use",
|
||||
translation_key="daily_inactive_water_use",
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
native_unit_of_measurement=UnitOfVolume.GALLONS,
|
||||
suggested_display_precision=1,
|
||||
value_fn=_get_controller_daily_inactive_water_use,
|
||||
),
|
||||
|
@ -98,7 +95,6 @@ FLOW_ZONE_SENSORS: tuple[SensorEntityDescription, ...] = (
|
|||
key="daily_active_water_use",
|
||||
translation_key="daily_active_water_use",
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
native_unit_of_measurement=UnitOfVolume.GALLONS,
|
||||
suggested_display_precision=1,
|
||||
value_fn=_get_zone_daily_active_water_use,
|
||||
),
|
||||
|
@ -165,6 +161,17 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity):
|
|||
|
||||
entity_description: HydrawiseSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit_of_measurement of the sensor."""
|
||||
if self.entity_description.device_class != SensorDeviceClass.VOLUME:
|
||||
return self.entity_description.native_unit_of_measurement
|
||||
return (
|
||||
UnitOfVolume.GALLONS
|
||||
if self.coordinator.data.user.units.units_name == "imperial"
|
||||
else UnitOfVolume.LITERS
|
||||
)
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
"""Icon of the entity based on the value."""
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/imap",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioimaplib"],
|
||||
"requirements": ["aioimaplib==1.0.1"]
|
||||
"requirements": ["aioimaplib==1.1.0"]
|
||||
}
|
||||
|
|
|
@ -72,11 +72,14 @@ def get_unique_prefix(
|
|||
havdalah_offset: int | None,
|
||||
) -> str:
|
||||
"""Create a prefix for unique ids."""
|
||||
# location.altitude was unset before 2024.6 when this method
|
||||
# was used to create the unique id. As such it would always
|
||||
# use the default altitude of 754.
|
||||
config_properties = [
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.timezone,
|
||||
location.altitude,
|
||||
754,
|
||||
location.diaspora,
|
||||
language,
|
||||
candle_lighting_offset,
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/jewish_calendar",
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["hdate"],
|
||||
"requirements": ["hdate==0.10.8"],
|
||||
"requirements": ["hdate==0.10.9"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ TRANSITION_BLOCKLIST = (
|
|||
(5010, 769, "3.0", "1.0.0"),
|
||||
(4999, 25057, "1.0", "27.0"),
|
||||
(4448, 36866, "V1", "V1.0.0.5"),
|
||||
(5009, 514, "1.0", "1.0.0"),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -341,7 +341,7 @@ class OnkyoDevice(MediaPlayerEntity):
|
|||
del self._attr_extra_state_attributes[ATTR_PRESET]
|
||||
|
||||
self._attr_is_volume_muted = bool(mute_raw[1] == "on")
|
||||
# AMP_VOL/MAX_RECEIVER_VOL*(MAX_VOL/100)
|
||||
# AMP_VOL / (MAX_RECEIVER_VOL * (MAX_VOL / 100))
|
||||
self._attr_volume_level = volume_raw[1] / (
|
||||
self._receiver_max_volume * self._max_volume / 100
|
||||
)
|
||||
|
@ -511,9 +511,9 @@ class OnkyoDeviceZone(OnkyoDevice):
|
|||
elif ATTR_PRESET in self._attr_extra_state_attributes:
|
||||
del self._attr_extra_state_attributes[ATTR_PRESET]
|
||||
if self._supports_volume:
|
||||
# AMP_VOL/MAX_RECEIVER_VOL*(MAX_VOL/100)
|
||||
self._attr_volume_level = (
|
||||
volume_raw[1] / self._receiver_max_volume * (self._max_volume / 100)
|
||||
# AMP_VOL / (MAX_RECEIVER_VOL * (MAX_VOL / 100))
|
||||
self._attr_volume_level = volume_raw[1] / (
|
||||
self._receiver_max_volume * self._max_volume / 100
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["plugwise"],
|
||||
"requirements": ["plugwise==0.37.3"],
|
||||
"requirements": ["plugwise==0.37.4.1"],
|
||||
"zeroconf": ["_plugwise._tcp.local."]
|
||||
}
|
||||
|
|
|
@ -140,7 +140,12 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
|
||||
async def _get_sound_modes_info(self):
|
||||
"""Get available sound modes and the active one."""
|
||||
settings = await self._dev.get_sound_settings("soundField")
|
||||
for settings in await self._dev.get_sound_settings():
|
||||
if settings.target == "soundField":
|
||||
break
|
||||
else:
|
||||
return None, {}
|
||||
|
||||
if isinstance(settings, Setting):
|
||||
settings = [settings]
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
|
@ -22,6 +21,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||
|
||||
from .browse_media import async_browse_media
|
||||
from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES
|
||||
from .models import HomeAssistantSpotifyData
|
||||
from .util import (
|
||||
is_spotify_media_type,
|
||||
resolve_spotify_media_type,
|
||||
|
@ -39,16 +39,6 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class HomeAssistantSpotifyData:
|
||||
"""Spotify data stored in the Home Assistant data object."""
|
||||
|
||||
client: Spotify
|
||||
current_user: dict[str, Any]
|
||||
devices: DataUpdateCoordinator[list[dict[str, Any]]]
|
||||
session: OAuth2Session
|
||||
|
||||
|
||||
type SpotifyConfigEntry = ConfigEntry[HomeAssistantSpotifyData]
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||
from enum import StrEnum
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from spotipy import Spotify
|
||||
import yarl
|
||||
|
@ -20,11 +20,9 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
|
||||
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, MEDIA_TYPE_SHOW, PLAYABLE_MEDIA_TYPES
|
||||
from .models import HomeAssistantSpotifyData
|
||||
from .util import fetch_image_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import HomeAssistantSpotifyData
|
||||
|
||||
BROWSE_LIMIT = 48
|
||||
|
||||
|
||||
|
|
|
@ -29,9 +29,10 @@ from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import HomeAssistantSpotifyData, SpotifyConfigEntry
|
||||
from . import SpotifyConfigEntry
|
||||
from .browse_media import async_browse_media_internal
|
||||
from .const import DOMAIN, MEDIA_PLAYER_PREFIX, PLAYABLE_MEDIA_TYPES, SPOTIFY_SCOPES
|
||||
from .models import HomeAssistantSpotifyData
|
||||
from .util import fetch_image_url
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
"""Models for use in Spotify integration."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from spotipy import Spotify
|
||||
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass
|
||||
class HomeAssistantSpotifyData:
|
||||
"""Spotify data stored in the Home Assistant data object."""
|
||||
|
||||
client: Spotify
|
||||
current_user: dict[str, Any]
|
||||
devices: DataUpdateCoordinator[list[dict[str, Any]]]
|
||||
session: OAuth2Session
|
|
@ -46,6 +46,8 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED
|
|||
|
||||
ENTITY_UNIT_LOAD = "load"
|
||||
|
||||
SHARED_SUFFIX = "_shared"
|
||||
|
||||
# Signals
|
||||
SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed"
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from homeassistant.components.media_source import (
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SHARED_SUFFIX
|
||||
from .models import SynologyDSMData
|
||||
|
||||
|
||||
|
@ -45,6 +45,7 @@ class SynologyPhotosMediaSourceIdentifier:
|
|||
self.album_id = None
|
||||
self.cache_key = None
|
||||
self.file_name = None
|
||||
self.is_shared = False
|
||||
|
||||
if parts:
|
||||
self.unique_id = parts[0]
|
||||
|
@ -54,6 +55,9 @@ class SynologyPhotosMediaSourceIdentifier:
|
|||
self.cache_key = parts[2]
|
||||
if len(parts) > 3:
|
||||
self.file_name = parts[3]
|
||||
if self.file_name.endswith(SHARED_SUFFIX):
|
||||
self.is_shared = True
|
||||
self.file_name = self.file_name.removesuffix(SHARED_SUFFIX)
|
||||
|
||||
|
||||
class SynologyPhotosMediaSource(MediaSource):
|
||||
|
@ -160,10 +164,13 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
if isinstance(mime_type, str) and mime_type.startswith("image/"):
|
||||
# Force small small thumbnails
|
||||
album_item.thumbnail_size = "sm"
|
||||
suffix = ""
|
||||
if album_item.is_shared:
|
||||
suffix = SHARED_SUFFIX
|
||||
ret.append(
|
||||
BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}",
|
||||
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}{suffix}",
|
||||
media_class=MediaClass.IMAGE,
|
||||
media_content_type=mime_type,
|
||||
title=album_item.file_name,
|
||||
|
@ -186,8 +193,11 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
mime_type, _ = mimetypes.guess_type(identifier.file_name)
|
||||
if not isinstance(mime_type, str):
|
||||
raise Unresolvable("No file extension")
|
||||
suffix = ""
|
||||
if identifier.is_shared:
|
||||
suffix = SHARED_SUFFIX
|
||||
return PlayMedia(
|
||||
f"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}",
|
||||
f"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}{suffix}",
|
||||
mime_type,
|
||||
)
|
||||
|
||||
|
@ -223,13 +233,14 @@ class SynologyDsmMediaView(http.HomeAssistantView):
|
|||
# location: {cache_key}/{filename}
|
||||
cache_key, file_name = location.split("/")
|
||||
image_id = int(cache_key.split("_")[0])
|
||||
if shared := file_name.endswith(SHARED_SUFFIX):
|
||||
file_name = file_name.removesuffix(SHARED_SUFFIX)
|
||||
mime_type, _ = mimetypes.guess_type(file_name)
|
||||
if not isinstance(mime_type, str):
|
||||
raise web.HTTPNotFound
|
||||
diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id]
|
||||
|
||||
assert diskstation.api.photos is not None
|
||||
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", False)
|
||||
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", shared)
|
||||
try:
|
||||
image = await diskstation.api.photos.download_item(item)
|
||||
except SynologyDSMException as exc:
|
||||
|
|
|
@ -164,10 +164,14 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN):
|
|||
abort_reason = "reauth_successful"
|
||||
|
||||
if config_entry:
|
||||
hub = config_entry.runtime_data
|
||||
try:
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
if hub and hub.available:
|
||||
return self.async_abort(reason="already_configured")
|
||||
if hub and hub.available:
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return self.async_update_reload_and_abort(
|
||||
config_entry, data=self.config, reason=abort_reason
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weatherflow4py==0.2.20"]
|
||||
"requirements": ["weatherflow4py==0.2.21"]
|
||||
}
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.50"]
|
||||
"requirements": ["holidays==0.51"]
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 6
|
||||
PATCH_VERSION: Final = "3"
|
||||
PATCH_VERSION: Final = "4"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||
|
|
|
@ -7,7 +7,7 @@ aiohttp-fast-url-dispatcher==0.3.0
|
|||
aiohttp-fast-zlib==0.1.0
|
||||
aiohttp==3.9.5
|
||||
aiohttp_cors==0.7.0
|
||||
aiozoneinfo==0.1.0
|
||||
aiozoneinfo==0.2.0
|
||||
astral==2.2
|
||||
async-interrupt==1.1.1
|
||||
async-upnp-client==0.38.3
|
||||
|
@ -33,7 +33,7 @@ hass-nabucasa==0.81.1
|
|||
hassil==1.7.1
|
||||
home-assistant-bluetooth==1.12.0
|
||||
home-assistant-frontend==20240610.1
|
||||
home-assistant-intents==2024.6.5
|
||||
home-assistant-intents==2024.6.21
|
||||
httpx==0.27.0
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.4
|
||||
|
@ -197,3 +197,6 @@ scapy>=2.5.0
|
|||
# Only tuf>=4 includes a constraint to <1.0.
|
||||
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
|
||||
tuf>=4.0.0
|
||||
|
||||
# https://github.com/jd/tenacity/issues/471
|
||||
tenacity<8.4.0
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.6.3"
|
||||
version = "2024.6.4"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -28,7 +28,7 @@ dependencies = [
|
|||
"aiohttp_cors==0.7.0",
|
||||
"aiohttp-fast-url-dispatcher==0.3.0",
|
||||
"aiohttp-fast-zlib==0.1.0",
|
||||
"aiozoneinfo==0.1.0",
|
||||
"aiozoneinfo==0.2.0",
|
||||
"astral==2.2",
|
||||
"async-interrupt==1.1.1",
|
||||
"attrs==23.2.0",
|
||||
|
|
|
@ -8,7 +8,7 @@ aiohttp==3.9.5
|
|||
aiohttp_cors==0.7.0
|
||||
aiohttp-fast-url-dispatcher==0.3.0
|
||||
aiohttp-fast-zlib==0.1.0
|
||||
aiozoneinfo==0.1.0
|
||||
aiozoneinfo==0.2.0
|
||||
astral==2.2
|
||||
async-interrupt==1.1.1
|
||||
attrs==23.2.0
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
-r requirements.txt
|
||||
|
||||
# homeassistant.components.aemet
|
||||
AEMET-OpenData==0.5.1
|
||||
AEMET-OpenData==0.5.2
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
AIOSomecomfort==0.0.25
|
||||
|
@ -261,7 +261,7 @@ aiohomekit==3.1.5
|
|||
aiohue==4.7.1
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==1.0.1
|
||||
aioimaplib==1.1.0
|
||||
|
||||
# homeassistant.components.apache_kafka
|
||||
aiokafka==0.10.0
|
||||
|
@ -526,7 +526,7 @@ azure-kusto-ingest==3.1.0
|
|||
azure-servicebus==7.10.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
babel==2.13.1
|
||||
babel==2.15.0
|
||||
|
||||
# homeassistant.components.baidu
|
||||
baidu-aip==1.6.6
|
||||
|
@ -810,7 +810,7 @@ enocean==0.50
|
|||
enturclient==0.2.4
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.6.2
|
||||
env-canada==0.6.3
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.5
|
||||
|
@ -1056,7 +1056,7 @@ hass-splunk==0.1.1
|
|||
hassil==1.7.1
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.8
|
||||
hdate==0.10.9
|
||||
|
||||
# homeassistant.components.heatmiser
|
||||
heatmiserV3==1.1.18
|
||||
|
@ -1084,13 +1084,13 @@ hole==0.8.0
|
|||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.50
|
||||
holidays==0.51
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240610.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.6.5
|
||||
home-assistant-intents==2024.6.21
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.7.2
|
||||
|
@ -1566,7 +1566,7 @@ plexauth==0.0.6
|
|||
plexwebsocket==0.0.14
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==0.37.3
|
||||
plugwise==0.37.4.1
|
||||
|
||||
# homeassistant.components.plum_lightpad
|
||||
plumlightpad==0.0.11
|
||||
|
@ -1619,7 +1619,7 @@ pvo==2.1.1
|
|||
py-aosmith==1.0.8
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.3
|
||||
py-canary==0.5.4
|
||||
|
||||
# homeassistant.components.ccm15
|
||||
py-ccm15==0.0.9
|
||||
|
@ -1794,7 +1794,7 @@ pydiscovergy==3.0.1
|
|||
pydoods==1.0.2
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2024.6.3
|
||||
pydrawise==2024.6.4
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
|
@ -2867,7 +2867,7 @@ watchdog==2.3.1
|
|||
waterfurnace==1.1.0
|
||||
|
||||
# homeassistant.components.weatherflow_cloud
|
||||
weatherflow4py==0.2.20
|
||||
weatherflow4py==0.2.21
|
||||
|
||||
# homeassistant.components.webmin
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
-r requirements_test.txt
|
||||
|
||||
# homeassistant.components.aemet
|
||||
AEMET-OpenData==0.5.1
|
||||
AEMET-OpenData==0.5.2
|
||||
|
||||
# homeassistant.components.honeywell
|
||||
AIOSomecomfort==0.0.25
|
||||
|
@ -237,7 +237,7 @@ aiohomekit==3.1.5
|
|||
aiohue==4.7.1
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==1.0.1
|
||||
aioimaplib==1.1.0
|
||||
|
||||
# homeassistant.components.apache_kafka
|
||||
aiokafka==0.10.0
|
||||
|
@ -463,7 +463,7 @@ azure-kusto-data[aio]==3.1.0
|
|||
azure-kusto-ingest==3.1.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
babel==2.13.1
|
||||
babel==2.15.0
|
||||
|
||||
# homeassistant.components.homekit
|
||||
base36==0.1.1
|
||||
|
@ -664,7 +664,7 @@ energyzero==2.1.0
|
|||
enocean==0.50
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.6.2
|
||||
env-canada==0.6.3
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.5
|
||||
|
@ -867,7 +867,7 @@ hass-nabucasa==0.81.1
|
|||
hassil==1.7.1
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.8
|
||||
hdate==0.10.9
|
||||
|
||||
# homeassistant.components.here_travel_time
|
||||
here-routing==0.2.0
|
||||
|
@ -886,13 +886,13 @@ hole==0.8.0
|
|||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.50
|
||||
holidays==0.51
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240610.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.6.5
|
||||
home-assistant-intents==2024.6.21
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.7.2
|
||||
|
@ -1243,7 +1243,7 @@ plexauth==0.0.6
|
|||
plexwebsocket==0.0.14
|
||||
|
||||
# homeassistant.components.plugwise
|
||||
plugwise==0.37.3
|
||||
plugwise==0.37.4.1
|
||||
|
||||
# homeassistant.components.plum_lightpad
|
||||
plumlightpad==0.0.11
|
||||
|
@ -1284,7 +1284,7 @@ pvo==2.1.1
|
|||
py-aosmith==1.0.8
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.3
|
||||
py-canary==0.5.4
|
||||
|
||||
# homeassistant.components.ccm15
|
||||
py-ccm15==0.0.9
|
||||
|
@ -1405,7 +1405,7 @@ pydexcom==0.2.3
|
|||
pydiscovergy==3.0.1
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2024.6.3
|
||||
pydrawise==2024.6.4
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
|
@ -2226,7 +2226,7 @@ wallbox==0.6.0
|
|||
watchdog==2.3.1
|
||||
|
||||
# homeassistant.components.weatherflow_cloud
|
||||
weatherflow4py==0.2.20
|
||||
weatherflow4py==0.2.21
|
||||
|
||||
# homeassistant.components.webmin
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
|
|
@ -219,6 +219,9 @@ scapy>=2.5.0
|
|||
# Only tuf>=4 includes a constraint to <1.0.
|
||||
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
|
||||
tuf>=4.0.0
|
||||
|
||||
# https://github.com/jd/tenacity/issues/471
|
||||
tenacity<8.4.0
|
||||
"""
|
||||
|
||||
GENERATED_MESSAGE = (
|
||||
|
|
|
@ -563,7 +563,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen light',
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -703,7 +703,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called late added light',
|
||||
'speech': 'Sorry, I am not aware of any device called late added',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -783,7 +783,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen light',
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -803,7 +803,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called my cool light',
|
||||
'speech': 'Sorry, I am not aware of any device called my cool',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -943,7 +943,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen light',
|
||||
'speech': 'Sorry, I am not aware of any device called kitchen',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -993,7 +993,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any device called renamed light',
|
||||
'speech': 'Sorry, I am not aware of any device called renamed',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -15,6 +15,7 @@ from pydrawise.schema import (
|
|||
Sensor,
|
||||
SensorModel,
|
||||
SensorStatus,
|
||||
UnitsSummary,
|
||||
User,
|
||||
Zone,
|
||||
)
|
||||
|
@ -84,7 +85,11 @@ def mock_auth() -> Generator[AsyncMock, None, None]:
|
|||
@pytest.fixture
|
||||
def user() -> User:
|
||||
"""Hydrawise User fixture."""
|
||||
return User(customer_id=12345, email="asdf@asdf.com")
|
||||
return User(
|
||||
customer_id=12345,
|
||||
email="asdf@asdf.com",
|
||||
units=UnitsSummary(units_name="imperial"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -3,13 +3,18 @@
|
|||
from collections.abc import Awaitable, Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
from pydrawise.schema import Controller, Zone
|
||||
from pydrawise.schema import Controller, User, Zone
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util.unit_system import (
|
||||
METRIC_SYSTEM,
|
||||
US_CUSTOMARY_SYSTEM,
|
||||
UnitSystem,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
@ -45,7 +50,7 @@ async def test_suspended_state(
|
|||
assert next_cycle.state == "unknown"
|
||||
|
||||
|
||||
async def test_no_sensor_and_water_state2(
|
||||
async def test_no_sensor_and_water_state(
|
||||
hass: HomeAssistant,
|
||||
controller: Controller,
|
||||
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
|
||||
|
@ -63,3 +68,30 @@ async def test_no_sensor_and_water_state2(
|
|||
sensor = hass.states.get("binary_sensor.home_controller_connectivity")
|
||||
assert sensor is not None
|
||||
assert sensor.state == "on"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("hydrawise_unit_system", "unit_system", "expected_state"),
|
||||
[
|
||||
("imperial", METRIC_SYSTEM, "454.6279552584"),
|
||||
("imperial", US_CUSTOMARY_SYSTEM, "120.1"),
|
||||
("metric", METRIC_SYSTEM, "120.1"),
|
||||
("metric", US_CUSTOMARY_SYSTEM, "31.7270634882136"),
|
||||
],
|
||||
)
|
||||
async def test_volume_unit_conversion(
|
||||
hass: HomeAssistant,
|
||||
unit_system: UnitSystem,
|
||||
hydrawise_unit_system: str,
|
||||
expected_state: str,
|
||||
user: User,
|
||||
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
|
||||
) -> None:
|
||||
"""Test volume unit conversion."""
|
||||
hass.config.units = unit_system
|
||||
user.units.units_name = hydrawise_unit_system
|
||||
await mock_add_config_entry()
|
||||
|
||||
daily_active_water_use = hass.states.get("sensor.zone_one_daily_active_water_use")
|
||||
assert daily_active_water_use is not None
|
||||
assert daily_active_water_use.state == expected_state
|
||||
|
|
|
@ -38,7 +38,6 @@ async def test_import_unique_id_migration(hass: HomeAssistant) -> None:
|
|||
latitude=yaml_conf[DOMAIN][CONF_LATITUDE],
|
||||
longitude=yaml_conf[DOMAIN][CONF_LONGITUDE],
|
||||
timezone=hass.config.time_zone,
|
||||
altitude=hass.config.elevation,
|
||||
diaspora=DEFAULT_DIASPORA,
|
||||
)
|
||||
old_prefix = get_unique_prefix(location, DEFAULT_LANGUAGE, 20, 50)
|
||||
|
|
|
@ -23,7 +23,9 @@ CONF_DATA = {
|
|||
}
|
||||
|
||||
|
||||
def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=None):
|
||||
def _create_mocked_device(
|
||||
throw_exception=False, wired_mac=MAC, wireless_mac=None, no_soundfield=False
|
||||
):
|
||||
mocked_device = MagicMock()
|
||||
|
||||
type(mocked_device).get_supported_methods = AsyncMock(
|
||||
|
@ -101,7 +103,14 @@ def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=Non
|
|||
soundField = MagicMock()
|
||||
soundField.currentValue = "sound_mode2"
|
||||
soundField.candidate = [sound_mode1, sound_mode2, sound_mode3]
|
||||
type(mocked_device).get_sound_settings = AsyncMock(return_value=[soundField])
|
||||
|
||||
settings = MagicMock()
|
||||
settings.target = "soundField"
|
||||
settings.__iter__.return_value = [soundField]
|
||||
|
||||
type(mocked_device).get_sound_settings = AsyncMock(
|
||||
return_value=[] if no_soundfield else [settings]
|
||||
)
|
||||
|
||||
type(mocked_device).set_power = AsyncMock()
|
||||
type(mocked_device).set_sound_settings = AsyncMock()
|
||||
|
|
|
@ -159,6 +159,43 @@ async def test_state(
|
|||
assert entity.unique_id == MAC
|
||||
|
||||
|
||||
async def test_state_nosoundmode(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test state of the entity with no soundField in sound settings."""
|
||||
mocked_device = _create_mocked_device(no_soundfield=True)
|
||||
entry = MockConfigEntry(domain=songpal.DOMAIN, data=CONF_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with _patch_media_player_device(mocked_device):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.name == FRIENDLY_NAME
|
||||
assert state.state == STATE_ON
|
||||
attributes = state.as_dict()["attributes"]
|
||||
assert attributes["volume_level"] == 0.5
|
||||
assert attributes["is_volume_muted"] is False
|
||||
assert attributes["source_list"] == ["title1", "title2"]
|
||||
assert attributes["source"] == "title2"
|
||||
assert "sound_mode_list" not in attributes
|
||||
assert "sound_mode" not in attributes
|
||||
assert attributes["supported_features"] == SUPPORT_SONGPAL
|
||||
|
||||
device = device_registry.async_get_device(identifiers={(songpal.DOMAIN, MAC)})
|
||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, MAC)}
|
||||
assert device.manufacturer == "Sony Corporation"
|
||||
assert device.name == FRIENDLY_NAME
|
||||
assert device.sw_version == SW_VERSION
|
||||
assert device.model == MODEL
|
||||
|
||||
entity = entity_registry.async_get(ENTITY_ID)
|
||||
assert entity.unique_id == MAC
|
||||
|
||||
|
||||
async def test_state_wireless(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
|
|
@ -50,7 +50,8 @@ def dsm_with_photos() -> MagicMock:
|
|||
dsm.photos.get_albums = AsyncMock(return_value=[SynoPhotosAlbum(1, "Album 1", 10)])
|
||||
dsm.photos.get_items_from_album = AsyncMock(
|
||||
return_value=[
|
||||
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False)
|
||||
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False),
|
||||
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", True),
|
||||
]
|
||||
)
|
||||
dsm.photos.get_item_thumbnail_url = AsyncMock(
|
||||
|
@ -102,6 +103,11 @@ async def test_resolve_media_bad_identifier(
|
|||
"/synology_dsm/ABC012345/12631_47189/filename.png",
|
||||
"image/png",
|
||||
),
|
||||
(
|
||||
"ABC012345/12/12631_47189/filename.png_shared",
|
||||
"/synology_dsm/ABC012345/12631_47189/filename.png_shared",
|
||||
"image/png",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_resolve_media_success(
|
||||
|
@ -333,7 +339,7 @@ async def test_browse_media_get_items_thumbnail_error(
|
|||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
assert len(result.children) == 2
|
||||
item = result.children[0]
|
||||
assert isinstance(item, BrowseMedia)
|
||||
assert item.thumbnail is None
|
||||
|
@ -372,7 +378,7 @@ async def test_browse_media_get_items(
|
|||
result = await source.async_browse_media(item)
|
||||
|
||||
assert result
|
||||
assert len(result.children) == 1
|
||||
assert len(result.children) == 2
|
||||
item = result.children[0]
|
||||
assert isinstance(item, BrowseMedia)
|
||||
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg"
|
||||
|
@ -382,6 +388,15 @@ async def test_browse_media_get_items(
|
|||
assert item.can_play
|
||||
assert not item.can_expand
|
||||
assert item.thumbnail == "http://my.thumbnail.url"
|
||||
item = result.children[1]
|
||||
assert isinstance(item, BrowseMedia)
|
||||
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg_shared"
|
||||
assert item.title == "filename.jpg"
|
||||
assert item.media_class == MediaClass.IMAGE
|
||||
assert item.media_content_type == "image/jpeg"
|
||||
assert item.can_play
|
||||
assert not item.can_expand
|
||||
assert item.thumbnail == "http://my.thumbnail.url"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("setup_media_source")
|
||||
|
@ -435,3 +450,8 @@ async def test_media_view(
|
|||
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg"
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
with patch.object(tempfile, "tempdir", tmp_path):
|
||||
result = await view.get(
|
||||
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg_shared"
|
||||
)
|
||||
assert isinstance(result, web.Response)
|
||||
|
|
Loading…
Reference in New Issue