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
2 changes: 1 addition & 1 deletion homeassistant/components/paperless_ngx/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "local_polling",
"loggers": ["pypaperless"],
"quality_scale": "silver",
"requirements": ["pypaperless==4.1.0"]
"requirements": ["pypaperless==4.1.1"]
}
41 changes: 40 additions & 1 deletion homeassistant/components/rest/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any

import aiohttp
from aiohttp import hdrs
from multidict import CIMultiDictProxy
import xmltodict

Expand Down Expand Up @@ -77,14 +78,20 @@ def set_url(self, url: str) -> None:
"""Set url."""
self._resource = url

def _is_expected_content_type(self, content_type: str) -> bool:
"""Check if the content type is one we expect (JSON or XML)."""
return content_type.startswith(
("application/json", "text/json", *XML_MIME_TYPES)
)

def data_without_xml(self) -> str | None:
"""If the data is an XML string, convert it to a JSON string."""
_LOGGER.debug("Data fetched from resource: %s", self.data)
if (
(value := self.data) is not None
# If the http request failed, headers will be None
and (headers := self.headers) is not None
and (content_type := headers.get("content-type"))
and (content_type := headers.get(hdrs.CONTENT_TYPE))
and content_type.startswith(XML_MIME_TYPES)
):
value = json_dumps(xmltodict.parse(value))
Expand Down Expand Up @@ -120,6 +127,7 @@ async def async_update(self, log_errors: bool = True) -> None:
# Handle data/content
if self._request_data:
request_kwargs["data"] = self._request_data
response = None
try:
# Make the request
async with self._session.request(
Expand All @@ -143,3 +151,34 @@ async def async_update(self, log_errors: bool = True) -> None:
self.last_exception = ex
self.data = None
self.headers = None

# Log response details outside the try block so we always get logging
if response is None:
return

# Log response details for debugging
content_type = response.headers.get(hdrs.CONTENT_TYPE)
_LOGGER.debug(
"REST response from %s: status=%s, content-type=%s, length=%s",
self._resource,
response.status,
content_type or "not set",
len(self.data) if self.data else 0,
)

# If we got an error response with non-JSON/XML content, log a sample
# This helps debug issues like servers blocking with HTML error pages
if (
response.status >= 400
and content_type
and not self._is_expected_content_type(content_type)
):
sample = self.data[:500] if self.data else "<empty>"
_LOGGER.warning(
"REST request to %s returned status %s with %s response: %s%s",
self._resource,
response.status,
content_type,
sample,
"..." if self.data and len(self.data) > 500 else "",
)
5 changes: 4 additions & 1 deletion homeassistant/components/shelly/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ def async_setup_block_attribute_entities(
coordinator.device.settings, block
):
domain = sensor_class.__module__.split(".")[-1]
unique_id = f"{coordinator.mac}-{block.description}-{sensor_id}"
unique_id = sensor_class(
coordinator, block, sensor_id, description
).unique_id
LOGGER.debug("Removing Shelly entity with unique_id: %s", unique_id)
async_remove_shelly_entity(hass, domain, unique_id)
else:
entities.append(
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/components/stream/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
MAX_SEGMENTS,
OUTPUT_FORMATS,
OUTPUT_IDLE_TIMEOUT,
OUTPUT_STARTUP_TIMEOUT,
RECORDER_PROVIDER,
RTSP_TRANSPORTS,
SEGMENT_DURATION_ADJUSTER,
Expand Down Expand Up @@ -363,11 +364,14 @@ def outputs(self) -> Mapping[str, StreamOutput]:
# without concern about self._outputs being modified from another thread.
return MappingProxyType(self._outputs.copy())

def add_provider(
self, fmt: str, timeout: int = OUTPUT_IDLE_TIMEOUT
) -> StreamOutput:
def add_provider(self, fmt: str, timeout: int | None = None) -> StreamOutput:
"""Add provider output stream."""
if not (provider := self._outputs.get(fmt)):
startup_timeout = OUTPUT_STARTUP_TIMEOUT
if timeout is None:
timeout = OUTPUT_IDLE_TIMEOUT
else:
startup_timeout = timeout

async def idle_callback() -> None:
if (
Expand All @@ -379,7 +383,7 @@ async def idle_callback() -> None:

provider = PROVIDERS[fmt](
self.hass,
IdleTimer(self.hass, timeout, idle_callback),
IdleTimer(self.hass, timeout, idle_callback, startup_timeout),
self._stream_settings,
self.dynamic_stream_settings,
)
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/stream/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

FORMAT_CONTENT_TYPE = {HLS_PROVIDER: "application/vnd.apple.mpegurl"}

OUTPUT_IDLE_TIMEOUT = 300 # Idle timeout due to inactivity
OUTPUT_STARTUP_TIMEOUT = 60 # timeout due to no startup
OUTPUT_IDLE_TIMEOUT = 30 # Idle timeout due to inactivity

NUM_PLAYLIST_SEGMENTS = 3 # Number of segments to use in HLS playlist
MAX_SEGMENTS = 5 # Max number of segments to keep around
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/stream/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,12 @@ def __init__(
hass: HomeAssistant,
timeout: int,
idle_callback: Callable[[], Coroutine[Any, Any, None]],
startup_timeout: int | None = None,
) -> None:
"""Initialize IdleTimer."""
self._hass = hass
self._timeout = timeout
self._startup_timeout = startup_timeout or timeout
self._callback = idle_callback
self._unsub: CALLBACK_TYPE | None = None
self.idle = False
Expand All @@ -246,7 +248,7 @@ def start(self) -> None:
"""Start the idle timer if not already started."""
self.idle = False
if self._unsub is None:
self._unsub = async_call_later(self._hass, self._timeout, self.fire)
self._unsub = async_call_later(self._hass, self._startup_timeout, self.fire)

def awake(self) -> None:
"""Keep the idle time alive by resetting the timeout."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/vulcan/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/vulcan",
"iot_class": "cloud_polling",
"requirements": ["vulcan-api==2.3.2"]
"requirements": ["vulcan-api==2.4.2"]
}
2 changes: 2 additions & 0 deletions homeassistant/components/wmspro/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class WebControlProCover(WebControlProGenericEntity, CoverEntity):
def current_cover_position(self) -> int | None:
"""Return current position of cover."""
action = self._dest.action(self._drive_action_desc)
if action is None or action["percentage"] is None:
return None
return 100 - action["percentage"]

async def async_set_cover_position(self, **kwargs: Any) -> None:
Expand Down
4 changes: 2 additions & 2 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tests/components/homeassistant_yellow/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ async def test_option_flow_led_settings_fail_2(
(STEP_PICK_FIRMWARE_THREAD, ApplicationType.SPINEL, "2.4.4.0"),
],
)
@pytest.mark.usefixtures("addon_store_info")
async def test_firmware_options_flow(
step: str, fw_type: ApplicationType, fw_version: str, hass: HomeAssistant
) -> None:
Expand Down Expand Up @@ -371,7 +372,7 @@ async def mock_install_firmware_step(
side_effect=mock_async_step_pick_firmware_zigbee,
),
patch(
"homeassistant.components.homeassistant_hardware.firmware_config_flow.BaseFirmwareConfigFlow._ensure_thread_addon_setup",
"homeassistant.components.homeassistant_hardware.firmware_config_flow.BaseFirmwareInstallFlow._ensure_thread_addon_setup",
return_value=None,
),
patch(
Expand Down
70 changes: 35 additions & 35 deletions tests/components/ista_ecotrend/snapshots/test_util.ambr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_get_statistics
# name: test_get_statistics[heating-None]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
Expand All @@ -11,31 +11,31 @@
}),
])
# ---
# name: test_get_statistics.1
# name: test_get_statistics[heating-costs]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
'value': 113.0,
'value': 62,
}),
dict({
'date': datetime.datetime(2024, 5, 31, 0, 0, tzinfo=datetime.timezone.utc),
'value': 38.0,
'value': 21,
}),
])
# ---
# name: test_get_statistics.2
# name: test_get_statistics[heating-energy]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
'value': 62,
'value': 113.0,
}),
dict({
'date': datetime.datetime(2024, 5, 31, 0, 0, tzinfo=datetime.timezone.utc),
'value': 21,
'value': 38.0,
}),
])
# ---
# name: test_get_statistics.3
# name: test_get_statistics[warmwater-None]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
Expand All @@ -47,31 +47,31 @@
}),
])
# ---
# name: test_get_statistics.4
# name: test_get_statistics[warmwater-costs]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
'value': 61.1,
'value': 7,
}),
dict({
'date': datetime.datetime(2024, 5, 31, 0, 0, tzinfo=datetime.timezone.utc),
'value': 57.0,
'value': 7,
}),
])
# ---
# name: test_get_statistics.5
# name: test_get_statistics[warmwater-energy]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
'value': 7,
'value': 61.1,
}),
dict({
'date': datetime.datetime(2024, 5, 31, 0, 0, tzinfo=datetime.timezone.utc),
'value': 7,
'value': 57.0,
}),
])
# ---
# name: test_get_statistics.6
# name: test_get_statistics[water-None]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
Expand All @@ -83,11 +83,7 @@
}),
])
# ---
# name: test_get_statistics.7
list([
])
# ---
# name: test_get_statistics.8
# name: test_get_statistics[water-costs]
list([
dict({
'date': datetime.datetime(2024, 4, 30, 0, 0, tzinfo=datetime.timezone.utc),
Expand All @@ -99,39 +95,43 @@
}),
])
# ---
# name: test_get_values_by_type
# name: test_get_statistics[water-energy]
list([
])
# ---
# name: test_get_values_by_type[heating]
dict({
'additionalValue': '38,0',
'type': 'heating',
'value': '35',
})
# ---
# name: test_get_values_by_type.1
# name: test_get_values_by_type[heating].1
dict({
'additionalValue': '57,0',
'type': 'warmwater',
'value': '1,0',
'type': 'heating',
'value': 21,
})
# ---
# name: test_get_values_by_type.2
# name: test_get_values_by_type[warmwater]
dict({
'type': 'water',
'value': '5,0',
'additionalValue': '57,0',
'type': 'warmwater',
'value': '1,0',
})
# ---
# name: test_get_values_by_type.3
# name: test_get_values_by_type[warmwater].1
dict({
'type': 'heating',
'value': 21,
'type': 'warmwater',
'value': 7,
})
# ---
# name: test_get_values_by_type.4
# name: test_get_values_by_type[water]
dict({
'type': 'warmwater',
'value': 7,
'type': 'water',
'value': '5,0',
})
# ---
# name: test_get_values_by_type.5
# name: test_get_values_by_type[water].1
dict({
'type': 'water',
'value': 3,
Expand Down
Loading
Loading