10000 2021.10.7 by balloob · Pull Request #58905 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

2021.10.7 #58905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 1, 2021
2 changes: 1 addition & 1 deletion homeassistant/components/dlna_dmr/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(self, hass: HomeAssistant) -> None:
"""Initialize global data."""
self.lock = asyncio.Lock()
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False)
self.requester = AiohttpSessionRequester(session, with_sleep=False)
self.requester = AiohttpSessionRequester(session, with_sleep=True)
self.upnp_factory = UpnpFactory(self.requester, non_strict=True)
self.event_notifiers = {}
self.event_notifier_refs = defaultdict(int)
Expand Down
11 changes: 10 additions & 1 deletion homeassistant/components/fritz/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import logging
import socket
from typing import Any
from urllib.parse import ParseResult, urlparse

Expand Down Expand Up @@ -85,8 +86,16 @@ async def fritz_tools_init(self) -> str | None:

async def async_check_configured_entry(self) -> ConfigEntry | None:
"""Check if entry is configured."""

current_host = await self.hass.async_add_executor_job(
socket.gethostbyname, self._host
)

for entry in self._async_current_entries(include_ignore=False):
if entry.data[CONF_HOST] == self._host:
entry_host = await self.hass.async_add_executor_job(
socket.gethostbyname, entry.data[CONF_HOST]
)
if entry_host == current_host:
return entry
return None

Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/keenetic_ndms2/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ async def async_step_ssdp(self, discovery_info: DiscoveryInfoType) -> FlowResult

host = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]).hostname
await self.async_set_unique_id(discovery_info[ssdp.ATTR_UPNP_UDN])
self._abort_if_unique_id_configured(updates={CONF_HOST: host})

self._async_abort_entries_match({CONF_HOST: host})

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mazda/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Mazda Connected Services",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/mazda",
"requirements": ["pymazda==0.2.1"],
"requirements": ["pymazda==0.2.2"],
"codeowners": ["@bdr99"],
"quality_scale": "platinum",
"iot_class": "cloud_polling"
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/netgear/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ async def async_step_ssdp(self, discovery_info: dict) -> FlowResult:

updated_data[CONF_PORT] = DEFAULT_PORT
for model in MODELS_V2:
if discovery_info.get(ssdp.ATTR_UPNP_MODEL_NUMBER, "").startswith(model):
if discovery_info.get(ssdp.ATTR_UPNP_MODEL_NUMBER, "").startswith(
model
) or discovery_info.get(ssdp.ATTR_UPNP_MODEL_NAME, "").startswith(model):
updated_data[CONF_PORT] = ORBI_PORT

self.placeholders.update(updated_data)
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/netgear/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ async def async_update_device_trackers(self, now=None) -> None:
ntg_devices = await self.async_get_attached_devices()
now = dt_util.utcnow()

if ntg_devices is None:
return

if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Netgear scan result: \n%s", ntg_devices)

Expand Down
2364
1 change: 1 addition & 0 deletions homeassistant/components/template/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ def __init__(

self._friendly_name_template = friendly_name_template

self._attr_name = None
# Try to render the name as it can influence the entity ID
if friendly_name_template:
friendly_name_template.hass = hass
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tuya/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "tuya",
"name": "Tuya",
"documentation": "https://github.com/tuya/tuya-home-assistant",
"documentation": "https://www.home-assistant.io/integrations/tuya",
"requirements": ["tuya-iot-py-sdk==0.5.0"],
"codeowners": ["@Tuya", "@zlinoliver", "@METISU"],
"config_flow": true,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/yamaha_musiccast/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ async def async_media_pause(self):
async def async_media_stop(self):
"""Send stop command."""
if self._is_netusb:
await self.coordinator.musiccast.netusb_pause()
await self.coordinator.musiccast.netusb_stop()
else:
raise HomeAssistantError(
"Service stop is not supported for non NetUSB sources."
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 10
PATCH_VERSION: Final = "6"
PATCH_VERSION: Final = "7"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
Expand Down
67 changes: 43 additions & 24 deletions homeassistant/util/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ def _lower_bound(arr: list[int], cmp: int) -> int | None:
return None
return arr[left]

result = now.replace(microsecond=0)
# Reset microseconds and fold; fold (for ambiguous DST times) will be handled later
result = now.replace(microsecond=0, fold=0)

# Match next second
if (next_second := _lower_bound(seconds, result.second)) is None:
Expand Down Expand Up @@ -309,40 +310,58 @@ def _lower_bound(arr: list[int], cmp: int) -> int | None:
result = result.replace(hour=next_hour)

if result.tzinfo in (None, UTC):
# Using UTC, no DST checking needed
return result

if _datetime_ambiguous(result):
# This happens when we're leaving daylight saving time and local
# clocks are rolled back. In this case, we want to trigger
# on both the DST and non-DST time. So when "now" is in the DST
# use the DST-on time, and if not, use the DST-off time.
fold = 1 if now.dst() else 0
if result.fold != fold:
result = result.replace(fold=fold)

if not _datetime_exists(result):
# This happens when we're entering daylight saving time and local
# clocks are rolled forward, thus there are local times that do
# not exist. In this case, we want to trigger on the next time
# that *does* exist.
# In the worst case, this will run through all the seconds in the
# time shift, but that's max 3600 operations for once per year
# When entering DST and clocks are turned forward.
# There are wall clock times that don't "exist" (an hour is skipped).

# -> trigger on the next time that 1. matches the pattern and 2. does exist
# for example:
# on 2021.03.28 02:00:00 in CET timezone clocks are turned forward an hour
# with pattern "02:30", don't run on 28 mar (such a wall time does not exist on this day)
# instead run at 02:30 the next day

# We solve this edge case by just iterating one second until the result exists
# (max. 3600 operations, which should be fine for an edge case that happens once a year)
return find_next_time_expression_time(
result + dt.timedelta(seconds=1), seconds, minutes, hours
)

# Another edge-case when leaving DST:
# When now is in DST and ambiguous *and* the next trigger time we *should*
# trigger is ambiguous and outside DST, the excepts above won't catch it.
# For example: if triggering on 2:30 and now is 28.10.2018 2:30 (in DST)
# we should trigger next on 28.10.2018 2:30 (out of DST), but our
# algo 10000 rithm above would produce 29.10.2018 2:30 (out of DST)
if _datetime_ambiguous(now):
now_is_ambiguous = _datetime_ambiguous(now)
result_is_ambiguous = _datetime_ambiguous(result)

# When leaving DST and clocks are turned backward.
# Then there are wall clock times that are ambiguous i.e. exist with DST and without DST
# The logic above does not take into account if a given pattern matches _twice_
# in a day.
# Example: on 2021.10.31 02:00:00 in CET timezone clocks are turned backward an hour

if now_is_ambiguous and result_is_ambiguous:
# `now` and `result` are both ambiguous, so the next match happens
# _within_ the current fold.

# Examples:
# 1. 2021.10.31 02:00:00+02:00 with pattern 02:30 -> 2021.10.31 02:30:00+02:00
# 2. 2021.10.31 02:00:00+01:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00
return result.replace(fold=now.fold)

if now_is_ambiguous and now.fold == 0 and not result_is_ambiguous:
# `now` is in the first fold, but result is not ambiguous (meaning it no longer matches
# within the fold).
# -> Check if result matches in the next fold. If so, emit that match

# Turn back the time by the DST offset, effectively run the algorithm on the first fold
# If it matches on the first fold, that means it will also match on the second one.

# Example: 2021.10.31 02:45:00+02:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00

check_result = find_next_time_expression_time(
now + _dst_offset_diff(now), seconds, minutes, hours
)
if _datetime_ambiguous(check_result):
return check_result
return check_result.replace(fold=1)

return result

Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1619,7 +1619,7 @@ pymailgunner==1.4
pymata-express==1.19

# homeassistant.components.mazda
pymazda==0.2.1
pymazda==0.2.2

# homeassistant.components.mediaroom
pymediaroom==0.6.4.1
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ pymailgunner==1.4
pymata-express==1.19

# homeassistant.components.mazda
pymazda==0.2.1
pymazda==0.2.2

# homeassistant.components.melcloud
pymelcloud==2.5.4
Expand Down
35 changes: 27 additions & 8 deletions tests/components/fritz/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
ATTR_NEW_SERIAL_NUMBER = "NewSerialNumber"

MOCK_HOST = "fake_host"
MOCK_IP = "192.168.178.1"
MOCK_SERIAL_NUMBER = "fake_serial_number"
MOCK_FIRMWARE_INFO = [True, "1.1.1"]

Expand All @@ -51,7 +52,7 @@
}
MOCK_IMPORT_CONFIG = {CONF_HOST: MOCK_HOST, CONF_USERNAME: "username"}
MOCK_SSDP_DATA = {
ATTR_SSDP_LOCATION: "https://fake_host:12345/test",
ATTR_SSDP_LOCATION: f"https://{MOCK_IP}:12345/test",
ATTR_UPNP_FRIENDLY_NAME: "fake_name",
ATTR_UPNP_UDN: "uuid:only-a-test",
}
Expand Down Expand Up @@ -81,7 +82,10 @@ async def test_user(hass: HomeAssistant, fc_class_mock, mock_get_source_ip):
"requests.get"
) as mock_request_get, patch(
"requests.post"
) as mock_request_post:
) as mock_request_post, patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

mock_request_get.return_value.status_code = 200
mock_request_get.return_value.content = MOCK_REQUEST
Expand Down Expand Up @@ -129,7 +133,10 @@ async def test_user_already_configured(
"requests.get"
) as mock_request_get, patch(
"requests.post"
) as mock_request_post:
) as mock_request_post, patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

mock_request_get.return_value.status_code = 200
mock_request_get.return_value.content = MOCK_REQUEST
Expand Down Expand Up @@ -319,7 +326,10 @@ async def test_ssdp_already_configured(
with patch(
"homeassistant.components.fritz.common.FritzConnection",
side_effect=fc_class_mock,
), patch("homeassistant.components.fritz.common.FritzStatus"):
), patch("homeassistant.components.fritz.common.FritzStatus"), patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA
Expand All @@ -343,7 +353,10 @@ async def test_ssdp_already_configured_host(
with patch(
"homeassistant.components.fritz.common.FritzConnection",
side_effect=fc_class_mock,
), patch("homeassistant.components.fritz.common.FritzStatus"):
), patch("homeassistant.components.fritz.common.FritzStatus"), patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA
Expand All @@ -367,7 +380,10 @@ async def test_ssdp_already_configured_host_uuid(
with patch(
"homeassistant.components.fritz.common.FritzConnection",
side_effect=fc_class_mock,
), patch("homeassistant.components.fritz.common.FritzStatus"):
), patch("homeassistant.components.fritz.common.FritzStatus"), patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA
Expand Down Expand Up @@ -436,7 +452,7 @@ async def test_ssdp(hass: HomeAssistant, fc_class_mock, mock_get_source_ip):
)

assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_HOST] == MOCK_IP
assert result["data"][CONF_PASSWORD] == "fake_pass"
assert result["data"][CONF_USERNAME] == "fake_user"

Expand Down Expand Up @@ -482,7 +498,10 @@ async def test_import(hass: HomeAssistant, fc_class_mock, mock_get_source_ip):
"requests.get"
) as mock_request_get, patch(
"requests.post"
) as mock_request_post:
) as mock_request_post, patch(
"homeassistant.components.fritz.config_flow.socket.gethostbyname",
return_value=MOCK_IP,
):

mock_request_get.return_value.status_code = 200
mock_request_get.return_value.content = MOCK_REQUEST
Expand Down
50 changes: 50 additions & 0 deletions tests/components/keenetic_ndms2/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,56 @@ async def test_ssdp_already_configured(hass: HomeAssistant) -> None:
assert result["reason"] == "already_configured"


async def test_ssdp_ignored(hass: HomeAssistant) -> None:
"""Test unique ID ignored and discovered."""

entry = MockConfigEntry(
domain=keenetic.DOMAIN,
source=config_entries.SOURCE_IGNORE,
unique_id=MOCK_SSDP_DISCOVERY_INFO[ssdp.ATTR_UPNP_UDN],
)
entry.add_to_hass(hass)

discovery_info = MOCK_SSDP_DISCOVERY_INFO.copy()
result = await hass.config_entries.flow.async_init(
keenetic.DOMAIN,
context={CONF_SOURCE: config_entries.SOURCE_SSDP},
data=discovery_info,
)

assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"


async def test_ssdp_update_host(hass: HomeAssistant) -> None:
"""Test unique ID configured and discovered with the new host."""

entry = MockConfigEntry(
domain=keenetic.DOMAIN,
data=MOCK_DATA,
options=MOCK_OPTIONS,
unique_id=MOCK_SSDP_DISCOVERY_INFO[ssdp.ATTR_UPNP_UDN],
)
entry.add_to_hass(hass)

new_ip = "10.10.10.10"

discovery_info = {
**MOCK_SSDP_DISCOVERY_INFO,
ssdp.ATTR_SSDP_LOCATION: f"http://{new_ip}/",
}

result = await hass.config_entries.flow.async_init(
keenetic.DOMAIN,
context={CONF_SOURCE: config_entries.SOURCE_SSDP},
data=discovery_info,
)

assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert entry.data[CONF_HOST] == new_ip


async def test_ssdp_reject_no_udn(hass: HomeAssistant) -> None:
"""Discovered device has no UDN."""

Expand Down
Loading
0