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 .github/workflows/builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ jobs:
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'

publish_container:
name: Publish meta container for ${{ matrix.registry }}
name: Publish to ${{ matrix.registry }}
environment: ${{ needs.init.outputs.channel }}
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
Expand Down
4 changes: 2 additions & 2 deletions CODEOWNERS

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

2 changes: 1 addition & 1 deletion homeassistant/components/acer_projector/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["serialx==1.7.0"]
"requirements": ["serialx==1.7.1"]
}
26 changes: 3 additions & 23 deletions homeassistant/components/button/trigger.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
"""Provides triggers for buttons."""

from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.core import HomeAssistant
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA,
EntityTriggerBase,
Trigger,
)
from homeassistant.helpers.trigger import StatelessEntityTriggerBase, Trigger

from . import DOMAIN


class ButtonPressedTrigger(EntityTriggerBase):
class ButtonPressedTrigger(StatelessEntityTriggerBase):
"""Trigger for button entity presses."""

_domain_specs = {DOMAIN: DomainSpec()}
_schema = ENTITY_STATE_TRIGGER_SCHEMA

def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and different from the current state."""

# UNKNOWN is a valid from_state, otherwise the first time the button is pressed
# would not trigger
if from_state.state == STATE_UNAVAILABLE:
return False

return from_state.state != to_state.state

def is_valid_state(self, state: State) -> bool:
"""Check if the new state is not invalid."""
return state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)


TRIGGERS: dict[str, type[Trigger]] = {
Expand Down
25 changes: 4 additions & 21 deletions homeassistant/components/doorbell/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,22 @@
DoorbellEventType,
EventDeviceClass,
)
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA,
EntityTriggerBase,
Trigger,
)
from homeassistant.helpers.trigger import StatelessEntityTriggerBase, Trigger


class DoorbellRangTrigger(EntityTriggerBase):
class DoorbellRangTrigger(StatelessEntityTriggerBase):
"""Trigger for doorbell event entity when a ring event is received."""

_domain_specs = {EVENT_DOMAIN: DomainSpec(device_class=EventDeviceClass.DOORBELL)}
_schema = ENTITY_STATE_TRIGGER_SCHEMA

def is_valid_state(self, state: State) -> bool:
"""Check if the entity is available and the event type is ring."""
return (
state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
and state.attributes.get(ATTR_EVENT_TYPE) == DoorbellEventType.RING
return super().is_valid_state(state) and (
state.attributes.get(ATTR_EVENT_TYPE) == DoorbellEventType.RING
)

def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and different from the current state."""

# UNKNOWN is a valid from_state, otherwise the first time the event is received
# would not trigger
if from_state.state == STATE_UNAVAILABLE:
return False

return from_state.state != to_state.state


TRIGGERS: dict[str, type[Trigger]] = {
"rang": DoorbellRangTrigger,
Expand Down
23 changes: 6 additions & 17 deletions homeassistant/components/event/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import voluptuous as vol

from homeassistant.const import CONF_OPTIONS, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import CONF_OPTIONS
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA,
EntityTriggerBase,
StatelessEntityTriggerBase,
Trigger,
TriggerConfig,
)
Expand All @@ -28,7 +28,7 @@
)


class EventReceivedTrigger(EntityTriggerBase):
class EventReceivedTrigger(StatelessEntityTriggerBase):
"""Trigger for event entity when it receives a matching event."""

_domain_specs = {DOMAIN: DomainSpec()}
Expand All @@ -39,21 +39,10 @@ def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
super().__init__(hass, config)
self._event_types = set(self._options[CONF_EVENT_TYPE])

def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and different from the current state."""

# UNKNOWN is a valid from_state, otherwise the first time the event is received
# would not trigger
if from_state.state == STATE_UNAVAILABLE:
return False

return from_state.state != to_state.state

def is_valid_state(self, state: State) -> bool:
"""Check if the event type is valid and matches one of the configured types."""
return (
state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
and state.attributes.get(ATTR_EVENT_TYPE) in self._event_types
"""Check if the event type matches one of the configured types."""
return super().is_valid_state(state) and (
state.attributes.get(ATTR_EVENT_TYPE) in self._event_types
)


Expand Down
21 changes: 13 additions & 8 deletions homeassistant/components/hassio/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from homeassistant.auth.models import User
from homeassistant.auth.providers import homeassistant as auth_ha
from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView
from homeassistant.components.http.const import is_supervisor_unix_socket_request
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
Expand Down Expand Up @@ -41,14 +42,18 @@ def __init__(self, hass: HomeAssistant, user: User) -> None:

def _check_access(self, request: web.Request) -> None:
"""Check if this call is from Supervisor."""
# Check caller IP
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
if ip_address(request.transport.get_extra_info("peername")[0]) != ip_address(
hassio_ip
):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized
# Requests over the Supervisor Unix socket are authenticated by the
# http auth middleware as the Supervisor user, so the caller-IP check
# below does not apply (and would crash, since `peername` is empty for
# Unix sockets). The user-ID check still runs to ensure only the
# Supervisor user can reach this endpoint.
if not is_supervisor_unix_socket_request(request):
hassio_ip = os.environ["SUPERVISOR"].split(":")[0]
assert request.transport
peername = request.transport.get_extra_info("peername")
if not peername or ip_address(peername[0]) != ip_address(hassio_ip):
_LOGGER.error("Invalid auth request from %s", request.remote)
raise HTTPUnauthorized

# Check caller token
if request[KEY_HASS_USER].id != self.user.id:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/holiday/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/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.95", "babel==2.15.0"]
"requirements": ["holidays==0.96", "babel==2.15.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/homematicip_cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.9.0"]
"requirements": ["homematicip==2.10.0"]
}
29 changes: 10 additions & 19 deletions homeassistant/components/imap/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,12 @@
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str,
# The default for new entries is to not include text and headers
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): EVENT_MESSAGE_DATA_SELECTOR,
vol.Optional(
CONF_SSL_CIPHER_LIST, default=SSLCipherList.PYTHON_DEFAULT
): CIPHER_SELECTOR,
vol.Optional(CONF_VERIFY_SSL, default=True): BOOLEAN_SELECTOR,
}
)
CONFIG_SCHEMA_ADVANCED = {
vol.Optional(
CONF_SSL_CIPHER_LIST, default=SSLCipherList.PYTHON_DEFAULT
): CIPHER_SELECTOR,
vol.Optional(CONF_VERIFY_SSL, default=True): BOOLEAN_SELECTOR,
}

OPTIONS_SCHEMA = vol.Schema(
{
Expand All @@ -93,18 +91,15 @@
vol.Optional(
CONF_EVENT_MESSAGE_DATA, default=MESSAGE_DATA_OPTIONS
): EVENT_MESSAGE_DATA_SELECTOR,
vol.Optional(CONF_CUSTOM_EVENT_DATA_TEMPLATE): TEMPLATE_SELECTOR,
vol.Optional(CONF_MAX_MESSAGE_SIZE, default=DEFAULT_MAX_MESSAGE_SIZE): vol.All(
cv.positive_int,
vol.Range(min=DEFAULT_MAX_MESSAGE_SIZE, max=MAX_MESSAGE_SIZE_LIMIT),
),
vol.Optional(CONF_ENABLE_PUSH, default=True): BOOLEAN_SELECTOR,
}
)

OPTIONS_SCHEMA_ADVANCED = {
vol.Optional(CONF_CUSTOM_EVENT_DATA_TEMPLATE): TEMPLATE_SELECTOR,
vol.Optional(CONF_MAX_MESSAGE_SIZE, default=DEFAULT_MAX_MESSAGE_SIZE): vol.All(
cv.positive_int,
vol.Range(min=DEFAULT_MAX_MESSAGE_SIZE, max=MAX_MESSAGE_SIZE_LIMIT),
),
vol.Optional(CONF_ENABLE_PUSH, default=True): BOOLEAN_SELECTOR,
}


async def validate_input(
hass: HomeAssistant, user_input: dict[str, Any]
Expand Down Expand Up @@ -151,8 +146,6 @@ async def async_step_user(
"""Handle the initial step."""

schema = CONFIG_SCHEMA
if self.show_advanced_options:
schema = schema.extend(CONFIG_SCHEMA_ADVANCED)

if user_input is None:
return self.async_show_form(step_id="user", data_schema=schema)
Expand Down Expand Up @@ -250,8 +243,6 @@ async def async_step_init(
return self.async_create_entry(data={})

schema = OPTIONS_SCHEMA
if self.show_advanced_options:
schema = schema.extend(OPTIONS_SCHEMA_ADVANCED)
schema = self.add_suggested_values_to_schema(schema, entry_data)

return self.async_show_form(step_id="init", data_schema=schema, errors=errors)
26 changes: 3 additions & 23 deletions homeassistant/components/mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from uuid import uuid4

import certifi
import paho.mqtt.client as mqtt
from paho.mqtt.matcher import MQTTMatcher

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -47,6 +49,7 @@
from homeassistant.util.collection import chunked_or_all
from homeassistant.util.logging import catch_log_exception, log_exception

from .async_client import AsyncMQTTClient
from .const import (
CONF_BIRTH_MESSAGE,
CONF_BROKER,
Expand Down Expand Up @@ -86,13 +89,6 @@
)
from .util import EnsureJobAfterCooldown, get_file_path, mqtt_config_entry_enabled

if TYPE_CHECKING:
# Only import for paho-mqtt type checking here, imports are done locally
# because integrations should be able to optionally rely on MQTT.
import paho.mqtt.client as mqtt

from .async_client import AsyncMQTTClient

_LOGGER = logging.getLogger(__name__)

MIN_BUFFER_SIZE = 131072 # Minimum buffer size to use if preferred size fails
Expand Down Expand Up @@ -323,12 +319,6 @@ def setup(self) -> None:
The setup of the MQTT client should be run in an executor job,
because it accesses files, so it does IO.
"""
# We don't import on the top because some integrations
# should be able to optionally rely on MQTT.
from paho.mqtt import client as mqtt # noqa: PLC0415

from .async_client import AsyncMQTTClient # noqa: PLC0415

config = self._config
clean_session: bool | None = None
# If no protocol setting is set in the config entry data
Expand Down Expand Up @@ -561,7 +551,6 @@ def _async_start_misc_periodic(self) -> None:
"""Start the misc periodic."""
assert self._misc_timer is None, "Misc periodic already started"
_LOGGER.debug("%s: Starting client misc loop", self.config_entry.title)
import paho.mqtt.client as mqtt # noqa: PLC0415

# Inner function to avoid having to check late import
# each time the function is called.
Expand Down Expand Up @@ -705,7 +694,6 @@ async def async_publish(

async def async_connect(self, client_available: asyncio.Future[bool]) -> None:
"""Connect to the host. Does not process messages yet."""
import paho.mqtt.client as mqtt # noqa: PLC0415

result: int | None = None
self._available_future = client_available
Expand Down Expand Up @@ -763,7 +751,6 @@ def _async_cancel_reconnect(self) -> None:

async def _reconnect_loop(self) -> None:
"""Reconnect to the MQTT server."""
import paho.mqtt.client as mqtt # noqa: PLC0415

while True:
if not self.connected:
Expand Down Expand Up @@ -1265,9 +1252,6 @@ def _async_get_mid_future(self, mid: int) -> asyncio.Future[None]:
@callback
def _async_handle_callback_exception(self, status: mqtt.MQTTErrorCode) -> None:
"""Handle a callback exception."""
# We don't import on the top because some integrations
# should be able to optionally rely on MQTT.
import paho.mqtt.client as mqtt # noqa: PLC0415

_LOGGER.warning(
"Error returned from MQTT server: %s",
Expand Down Expand Up @@ -1312,8 +1296,6 @@ async def _async_wait_for_mid_or_raise(
) -> None:
"""Wait for ACK from broker or raise on error."""
if result_code != 0:
import paho.mqtt.client as mqtt # noqa: PLC0415

raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="mqtt_broker_error",
Expand Down Expand Up @@ -1360,8 +1342,6 @@ async def _discovery_cooldown(self) -> None:


def _matcher_for_topic(subscription: str) -> Callable[[str], bool]:
from paho.mqtt.matcher import MQTTMatcher # noqa: PLC0415

matcher = MQTTMatcher() # type: ignore[no-untyped-call]
matcher[subscription] = True

Expand Down
5 changes: 1 addition & 4 deletions homeassistant/components/mqtt/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
load_pem_private_key,
)
from cryptography.x509 import load_der_x509_certificate, load_pem_x509_certificate
import paho.mqtt.client as mqtt
import voluptuous as vol
import yaml

Expand Down Expand Up @@ -5479,10 +5480,6 @@ def try_connection(
user_input: dict[str, Any],
) -> bool:
"""Test if we can connect to an MQTT broker."""
# We don't import on the top because some integrations
# should be able to optionally rely on MQTT.
import paho.mqtt.client as mqtt # noqa: PLC0415

mqtt_client_setup = MqttClientSetup(user_input)
mqtt_client_setup.setup()
client = mqtt_client_setup.client
Expand Down
Loading
Loading