mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
2023.3.6 (#90150)
This commit is contained in:
commit
ca5a88342b
@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==1.2.7", "yalexs_ble==2.0.4"]
|
||||
"requirements": ["yalexs==1.2.7", "yalexs-ble==2.1.1"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/easyenergy",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["easyenergy==0.1.2"]
|
||||
"requirements": ["easyenergy==0.2.2"]
|
||||
}
|
||||
|
@ -130,10 +130,15 @@ class RuntimeEntryData:
|
||||
)
|
||||
self.ble_connections_free = free
|
||||
self.ble_connections_limit = limit
|
||||
if free:
|
||||
for fut in self._ble_connection_free_futures:
|
||||
if not free:
|
||||
return
|
||||
for fut in self._ble_connection_free_futures:
|
||||
# If wait_for_ble_connections_free gets cancelled, it will
|
||||
# leave a future in the list. We need to check if it's done
|
||||
# before setting the result.
|
||||
if not fut.done():
|
||||
fut.set_result(free)
|
||||
self._ble_connection_free_futures.clear()
|
||||
self._ble_connection_free_futures.clear()
|
||||
|
||||
async def wait_for_ble_connections_free(self) -> int:
|
||||
"""Wait until there are free BLE connections."""
|
||||
|
@ -77,7 +77,6 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
# Check permissions
|
||||
await fbx.system.get_config()
|
||||
await fbx.lan.get_hosts_list()
|
||||
await self.hass.async_block_till_done()
|
||||
|
||||
# Close connection
|
||||
await fbx.close()
|
||||
|
@ -13,7 +13,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/harmony",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioharmony", "slixmpp"],
|
||||
"requirements": ["aioharmony==0.2.9"],
|
||||
"requirements": ["aioharmony==0.2.10"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Logitech",
|
||||
|
@ -60,9 +60,7 @@ def async_sign_path(
|
||||
|
||||
url = URL(path)
|
||||
now = dt_util.utcnow()
|
||||
params = dict(sorted(url.query.items()))
|
||||
for param in SAFE_QUERY_PARAMS:
|
||||
params.pop(param, None)
|
||||
params = [itm for itm in url.query.items() if itm[0] not in SAFE_QUERY_PARAMS]
|
||||
encoded = jwt.encode(
|
||||
{
|
||||
"iss": refresh_token_id,
|
||||
@ -75,7 +73,7 @@ def async_sign_path(
|
||||
algorithm="HS256",
|
||||
)
|
||||
|
||||
params[SIGN_QUERY_PARAM] = encoded
|
||||
params.append((SIGN_QUERY_PARAM, encoded))
|
||||
url = url.with_query(params)
|
||||
return f"{url.path}?{url.query_string}"
|
||||
|
||||
@ -184,10 +182,11 @@ async def async_setup_auth(hass: HomeAssistant, app: Application) -> None:
|
||||
if claims["path"] != request.path:
|
||||
return False
|
||||
|
||||
params = dict(sorted(request.query.items()))
|
||||
del params[SIGN_QUERY_PARAM]
|
||||
for param in SAFE_QUERY_PARAMS:
|
||||
params.pop(param, None)
|
||||
params = [
|
||||
list(itm) # claims stores tuples as lists
|
||||
for itm in request.query.items()
|
||||
if itm[0] not in SAFE_QUERY_PARAMS and itm[0] != SIGN_QUERY_PARAM
|
||||
]
|
||||
if claims["params"] != params:
|
||||
return False
|
||||
|
||||
|
@ -95,9 +95,25 @@ class EmailReader:
|
||||
self._folder = folder
|
||||
self._verify_ssl = verify_ssl
|
||||
self._last_id = None
|
||||
self._last_message = None
|
||||
self._unread_ids = deque([])
|
||||
self.connection = None
|
||||
|
||||
@property
|
||||
def last_id(self) -> int | None:
|
||||
"""Return last email uid that was processed."""
|
||||
return self._last_id
|
||||
|
||||
@property
|
||||
def last_unread_id(self) -> int | None:
|
||||
"""Return last email uid received."""
|
||||
# We assume the last id in the list is the last unread id
|
||||
# We cannot know if that is the newest one, because it could arrive later
|
||||
# https://stackoverflow.com/questions/12409862/python-imap-the-order-of-uids
|
||||
if self._unread_ids:
|
||||
return int(self._unread_ids[-1])
|
||||
return self._last_id
|
||||
|
||||
def connect(self):
|
||||
"""Login and setup the connection."""
|
||||
ssl_context = client_context() if self._verify_ssl else None
|
||||
@ -128,21 +144,21 @@ class EmailReader:
|
||||
try:
|
||||
self.connection.select(self._folder, readonly=True)
|
||||
|
||||
if not self._unread_ids:
|
||||
search = f"SINCE {datetime.date.today():%d-%b-%Y}"
|
||||
if self._last_id is not None:
|
||||
search = f"UID {self._last_id}:*"
|
||||
|
||||
_, data = self.connection.uid("search", None, search)
|
||||
self._unread_ids = deque(data[0].split())
|
||||
if self._last_id is None:
|
||||
# search for today and yesterday
|
||||
time_from = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
search = f"SINCE {time_from:%d-%b-%Y}"
|
||||
else:
|
||||
search = f"UID {self._last_id}:*"
|
||||
|
||||
_, data = self.connection.uid("search", None, search)
|
||||
self._unread_ids = deque(data[0].split())
|
||||
while self._unread_ids:
|
||||
message_uid = self._unread_ids.popleft()
|
||||
if self._last_id is None or int(message_uid) > self._last_id:
|
||||
self._last_id = int(message_uid)
|
||||
return self._fetch_message(message_uid)
|
||||
|
||||
return self._fetch_message(str(self._last_id))
|
||||
self._last_message = self._fetch_message(message_uid)
|
||||
return self._last_message
|
||||
|
||||
except imaplib.IMAP4.error:
|
||||
_LOGGER.info("Connection to %s lost, attempting to reconnect", self._server)
|
||||
@ -254,22 +270,30 @@ class EmailContentSensor(SensorEntity):
|
||||
def update(self) -> None:
|
||||
"""Read emails and publish state change."""
|
||||
email_message = self._email_reader.read_next()
|
||||
while (
|
||||
self._last_id is None or self._last_id != self._email_reader.last_unread_id
|
||||
):
|
||||
if email_message is None:
|
||||
self._message = None
|
||||
self._state_attributes = {}
|
||||
return
|
||||
|
||||
if email_message is None:
|
||||
self._message = None
|
||||
self._state_attributes = {}
|
||||
return
|
||||
self._last_id = self._email_reader.last_id
|
||||
|
||||
if self.sender_allowed(email_message):
|
||||
message = EmailContentSensor.get_msg_subject(email_message)
|
||||
if self.sender_allowed(email_message):
|
||||
message = EmailContentSensor.get_msg_subject(email_message)
|
||||
|
||||
if self._value_template is not None:
|
||||
message = self.render_template(email_message)
|
||||
if self._value_template is not None:
|
||||
message = self.render_template(email_message)
|
||||
|
||||
self._message = message
|
||||
self._state_attributes = {
|
||||
ATTR_FROM: EmailContentSensor.get_msg_sender(email_message),
|
||||
ATTR_SUBJECT: EmailContentSensor.get_msg_subject(email_message),
|
||||
ATTR_DATE: email_message["Date"],
|
||||
ATTR_BODY: EmailContentSensor.get_msg_text(email_message),
|
||||
}
|
||||
self._message = message
|
||||
self._state_attributes = {
|
||||
ATTR_FROM: EmailContentSensor.get_msg_sender(email_message),
|
||||
ATTR_SUBJECT: EmailContentSensor.get_msg_subject(email_message),
|
||||
ATTR_DATE: email_message["Date"],
|
||||
ATTR_BODY: EmailContentSensor.get_msg_text(email_message),
|
||||
}
|
||||
|
||||
if self._last_id == self._email_reader.last_unread_id:
|
||||
break
|
||||
email_message = self._email_reader.read_next()
|
||||
|
@ -706,7 +706,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
for component in PLATFORMS
|
||||
)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0)
|
||||
# Unsubscribe reload dispatchers
|
||||
while reload_dispatchers := mqtt_data.reload_dispatchers:
|
||||
reload_dispatchers.pop()()
|
||||
|
@ -62,13 +62,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Nibe Heat Pump from a config entry."""
|
||||
|
||||
heatpump = HeatPump(Model[entry.data[CONF_MODEL]])
|
||||
heatpump.word_swap = entry.data.get(CONF_WORD_SWAP, True)
|
||||
await heatpump.initialize()
|
||||
|
||||
connection: Connection
|
||||
connection_type = entry.data[CONF_CONNECTION_TYPE]
|
||||
|
||||
if connection_type == CONF_CONNECTION_TYPE_NIBEGW:
|
||||
heatpump.word_swap = entry.data[CONF_WORD_SWAP]
|
||||
connection = NibeGW(
|
||||
heatpump,
|
||||
entry.data[CONF_IP_ADDRESS],
|
||||
|
@ -11,5 +11,6 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/oralb",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["oralb-ble==0.17.5"]
|
||||
"loggers": ["oralb-ble"],
|
||||
"requirements": ["oralb-ble==0.17.6"]
|
||||
}
|
||||
|
@ -40,5 +40,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["switchbot"],
|
||||
"requirements": ["PySwitchbot==0.37.3"]
|
||||
"requirements": ["PySwitchbot==0.37.4"]
|
||||
}
|
||||
|
@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["yalexs-ble==2.0.4"]
|
||||
"requirements": ["yalexs-ble==2.1.1"]
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiomusiccast"],
|
||||
"requirements": ["aiomusiccast==0.14.7"],
|
||||
"requirements": ["aiomusiccast==0.14.8"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Yamaha Corporation"
|
||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "5"
|
||||
PATCH_VERSION: Final = "6"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.3.5"
|
||||
version = "2023.3.6"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -40,7 +40,7 @@ PyRMVtransport==0.3.3
|
||||
PySocks==1.7.1
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.37.3
|
||||
PySwitchbot==0.37.4
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -171,7 +171,7 @@ aiogithubapi==22.10.1
|
||||
aioguardian==2022.07.0
|
||||
|
||||
# homeassistant.components.harmony
|
||||
aioharmony==0.2.9
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==2.6.1
|
||||
@ -214,7 +214,7 @@ aiolyric==1.0.9
|
||||
aiomodernforms==0.1.8
|
||||
|
||||
# homeassistant.components.yamaha_musiccast
|
||||
aiomusiccast==0.14.7
|
||||
aiomusiccast==0.14.8
|
||||
|
||||
# homeassistant.components.nanoleaf
|
||||
aionanoleaf==0.2.1
|
||||
@ -625,7 +625,7 @@ dynalite_devices==0.1.47
|
||||
eagle100==0.1.1
|
||||
|
||||
# homeassistant.components.easyenergy
|
||||
easyenergy==0.1.2
|
||||
easyenergy==0.2.2
|
||||
|
||||
# homeassistant.components.ebusd
|
||||
ebusdpy==0.0.17
|
||||
@ -1299,7 +1299,7 @@ openwrt-luci-rpc==1.1.11
|
||||
openwrt-ubus-rpc==0.0.2
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.5
|
||||
oralb-ble==0.17.6
|
||||
|
||||
# homeassistant.components.oru
|
||||
oru==0.1.11
|
||||
@ -2669,15 +2669,13 @@ xs1-api-client==3.0.0
|
||||
# homeassistant.components.yale_smart_alarm
|
||||
yalesmartalarmclient==0.3.9
|
||||
|
||||
# homeassistant.components.august
|
||||
# homeassistant.components.yalexs_ble
|
||||
yalexs-ble==2.0.4
|
||||
yalexs-ble==2.1.1
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==1.2.7
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs_ble==2.0.4
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.10
|
||||
|
||||
|
@ -36,7 +36,7 @@ PyRMVtransport==0.3.3
|
||||
PySocks==1.7.1
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.37.3
|
||||
PySwitchbot==0.37.4
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -155,7 +155,7 @@ aiogithubapi==22.10.1
|
||||
aioguardian==2022.07.0
|
||||
|
||||
# homeassistant.components.harmony
|
||||
aioharmony==0.2.9
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==2.6.1
|
||||
@ -195,7 +195,7 @@ aiolyric==1.0.9
|
||||
aiomodernforms==0.1.8
|
||||
|
||||
# homeassistant.components.yamaha_musiccast
|
||||
aiomusiccast==0.14.7
|
||||
aiomusiccast==0.14.8
|
||||
|
||||
# homeassistant.components.nanoleaf
|
||||
aionanoleaf==0.2.1
|
||||
@ -490,7 +490,7 @@ dynalite_devices==0.1.47
|
||||
eagle100==0.1.1
|
||||
|
||||
# homeassistant.components.easyenergy
|
||||
easyenergy==0.1.2
|
||||
easyenergy==0.2.2
|
||||
|
||||
# homeassistant.components.elgato
|
||||
elgato==4.0.1
|
||||
@ -947,7 +947,7 @@ openai==0.26.2
|
||||
openerz-api==0.2.0
|
||||
|
||||
# homeassistant.components.oralb
|
||||
oralb-ble==0.17.5
|
||||
oralb-ble==0.17.6
|
||||
|
||||
# homeassistant.components.ovo_energy
|
||||
ovoenergy==1.2.0
|
||||
@ -1894,15 +1894,13 @@ xmltodict==0.13.0
|
||||
# homeassistant.components.yale_smart_alarm
|
||||
yalesmartalarmclient==0.3.9
|
||||
|
||||
# homeassistant.components.august
|
||||
# homeassistant.components.yalexs_ble
|
||||
yalexs-ble==2.0.4
|
||||
yalexs-ble==2.1.1
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==1.2.7
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs_ble==2.0.4
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.10
|
||||
|
||||
|
@ -352,6 +352,12 @@ async def test_auth_access_signed_path_with_query_param(
|
||||
data = await req.json()
|
||||
assert data["user_id"] == refresh_token.user.id
|
||||
|
||||
# Without query params not allowed
|
||||
url = yarl.URL(signed_path)
|
||||
signed_path = f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}"
|
||||
req = await client.get(signed_path)
|
||||
assert req.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_auth_access_signed_path_with_query_param_order(
|
||||
hass: HomeAssistant,
|
||||
@ -374,12 +380,24 @@ async def test_auth_access_signed_path_with_query_param_order(
|
||||
refresh_token_id=refresh_token.id,
|
||||
)
|
||||
url = yarl.URL(signed_path)
|
||||
signed_path = f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}&foo=bar&test=test"
|
||||
|
||||
req = await client.get(signed_path)
|
||||
assert req.status == HTTPStatus.OK
|
||||
data = await req.json()
|
||||
assert data["user_id"] == refresh_token.user.id
|
||||
# Change order
|
||||
req = await client.get(
|
||||
f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}&foo=bar&test=test"
|
||||
)
|
||||
assert req.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
# Duplicate a param
|
||||
req = await client.get(
|
||||
f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}&test=test&foo=aaa&foo=bar"
|
||||
)
|
||||
assert req.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
# Remove a param
|
||||
req = await client.get(
|
||||
f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}&test=test"
|
||||
)
|
||||
assert req.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_auth_access_signed_path_with_query_param_safe_param(
|
||||
|
@ -14,9 +14,16 @@ from homeassistant.helpers.template import Template
|
||||
class FakeEMailReader:
|
||||
"""A test class for sending test emails."""
|
||||
|
||||
def __init__(self, messages):
|
||||
def __init__(self, messages) -> None:
|
||||
"""Set up the fake email reader."""
|
||||
self._messages = messages
|
||||
self.last_id = 0
|
||||
self.last_unread_id = len(messages)
|
||||
|
||||
def add_test_message(self, message):
|
||||
"""Add a new message."""
|
||||
self.last_unread_id += 1
|
||||
self._messages.append(message)
|
||||
|
||||
def connect(self):
|
||||
"""Stay always Connected."""
|
||||
@ -26,6 +33,7 @@ class FakeEMailReader:
|
||||
"""Get the next email."""
|
||||
if len(self._messages) == 0:
|
||||
return None
|
||||
self.last_id += 1
|
||||
return self._messages.popleft()
|
||||
|
||||
|
||||
@ -146,7 +154,7 @@ async def test_multi_part_only_other_text(hass: HomeAssistant) -> None:
|
||||
|
||||
|
||||
async def test_multiple_emails(hass: HomeAssistant) -> None:
|
||||
"""Test multiple emails."""
|
||||
"""Test multiple emails, discarding stale states."""
|
||||
states = []
|
||||
|
||||
test_message1 = email.message.Message()
|
||||
@ -158,9 +166,15 @@ async def test_multiple_emails(hass: HomeAssistant) -> None:
|
||||
test_message2 = email.message.Message()
|
||||
test_message2["From"] = "sender@test.com"
|
||||
test_message2["Subject"] = "Test 2"
|
||||
test_message2["Date"] = datetime.datetime(2016, 1, 1, 12, 44, 57)
|
||||
test_message2["Date"] = datetime.datetime(2016, 1, 1, 12, 44, 58)
|
||||
test_message2.set_payload("Test Message 2")
|
||||
|
||||
test_message3 = email.message.Message()
|
||||
test_message3["From"] = "sender@test.com"
|
||||
test_message3["Subject"] = "Test 3"
|
||||
test_message3["Date"] = datetime.datetime(2016, 1, 1, 12, 50, 1)
|
||||
test_message3.set_payload("Test Message 2")
|
||||
|
||||
def state_changed_listener(entity_id, from_s, to_s):
|
||||
states.append(to_s)
|
||||
|
||||
@ -178,11 +192,13 @@ async def test_multiple_emails(hass: HomeAssistant) -> None:
|
||||
|
||||
sensor.async_schedule_update_ha_state(True)
|
||||
await hass.async_block_till_done()
|
||||
# Fake a new received message
|
||||
sensor._email_reader.add_test_message(test_message3)
|
||||
sensor.async_schedule_update_ha_state(True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert states[0].state == "Test"
|
||||
assert states[1].state == "Test 2"
|
||||
assert states[0].state == "Test 2"
|
||||
assert states[1].state == "Test 3"
|
||||
|
||||
assert sensor.extra_state_attributes["body"] == "Test Message 2"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user