mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Add Apprise notification integration (#26868)
* Added apprise notification component * flake-8 fixes; black formatting + import merged to 1 line * pylint issues resolved * added github name to manifest.json * import moved to top as per code review request * manifest formatting to avoid failing ci * .coveragerc updated to include apprise * removed block for written tests * more test coverage * formatting as per code review * tests converted to async style as per code review * increased coverage * bumped version of apprise to 0.8.1 * test that mocked entries are called * added tests for hass.service loading * support tags for those who identify the TARGET option * renamed variable as per code review * 'assert not' used instead of 'is False' * added period (in case linter isn't happy)
This commit is contained in:
committed by
Martin Hjelmare
parent
3231e22ddf
commit
3cb844f22c
@ -26,6 +26,7 @@ homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/androidtv/* @JeffLIrion
|
||||
homeassistant/components/apache_kafka/* @bachya
|
||||
homeassistant/components/api/* @home-assistant/core
|
||||
homeassistant/components/apprise/* @caronc
|
||||
homeassistant/components/aprs/* @PhilRW
|
||||
homeassistant/components/arcam_fmj/* @elupus
|
||||
homeassistant/components/arduino/* @fabaff
|
||||
|
1
homeassistant/components/apprise/__init__.py
Normal file
1
homeassistant/components/apprise/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""The apprise component."""
|
12
homeassistant/components/apprise/manifest.json
Normal file
12
homeassistant/components/apprise/manifest.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"domain": "apprise",
|
||||
"name": "Apprise",
|
||||
"documentation": "https://www.home-assistant.io/components/apprise",
|
||||
"requirements": [
|
||||
"apprise==0.8.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@caronc"
|
||||
]
|
||||
}
|
73
homeassistant/components/apprise/notify.py
Normal file
73
homeassistant/components/apprise/notify.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""Apprise platform for notify component."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import apprise
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_TARGET,
|
||||
ATTR_TITLE,
|
||||
ATTR_TITLE_DEFAULT,
|
||||
PLATFORM_SCHEMA,
|
||||
BaseNotificationService,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FILE = "config"
|
||||
CONF_URL = "url"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_URL): vol.All(cv.ensure_list, [str]),
|
||||
vol.Optional(CONF_FILE): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_service(hass, config, discovery_info=None):
|
||||
"""Get the Apprise notification service."""
|
||||
|
||||
# Create our object
|
||||
a_obj = apprise.Apprise()
|
||||
|
||||
if config.get(CONF_FILE):
|
||||
# Sourced from a Configuration File
|
||||
a_config = apprise.AppriseConfig()
|
||||
if not a_config.add(config[CONF_FILE]):
|
||||
_LOGGER.error("Invalid Apprise config url provided")
|
||||
return None
|
||||
|
||||
if not a_obj.add(a_config):
|
||||
_LOGGER.error("Invalid Apprise config url provided")
|
||||
return None
|
||||
|
||||
if config.get(CONF_URL):
|
||||
# Ordered list of URLs
|
||||
if not a_obj.add(config[CONF_URL]):
|
||||
_LOGGER.error("Invalid Apprise URL(s) supplied")
|
||||
return None
|
||||
|
||||
return AppriseNotificationService(a_obj)
|
||||
|
||||
|
||||
class AppriseNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for Apprise."""
|
||||
|
||||
def __init__(self, a_obj):
|
||||
"""Initialize the service."""
|
||||
self.apprise = a_obj
|
||||
|
||||
def send_message(self, message="", **kwargs):
|
||||
"""Send a message to a specified target.
|
||||
|
||||
If no target/tags are specified, then services are notified as is
|
||||
However, if any tags are specified, then they will be applied
|
||||
to the notification causing filtering (if set up that way).
|
||||
"""
|
||||
targets = kwargs.get(ATTR_TARGET)
|
||||
title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
|
||||
self.apprise.notify(body=message, title=title, tag=targets)
|
@ -217,6 +217,9 @@ apcaccess==0.0.13
|
||||
# homeassistant.components.apns
|
||||
apns2==0.3.0
|
||||
|
||||
# homeassistant.components.apprise
|
||||
apprise==0.8.1
|
||||
|
||||
# homeassistant.components.aprs
|
||||
aprslib==0.6.46
|
||||
|
||||
|
@ -103,6 +103,9 @@ androidtv==0.0.30
|
||||
# homeassistant.components.apns
|
||||
apns2==0.3.0
|
||||
|
||||
# homeassistant.components.apprise
|
||||
apprise==0.8.1
|
||||
|
||||
# homeassistant.components.aprs
|
||||
aprslib==0.6.46
|
||||
|
||||
|
1
tests/components/apprise/__init__.py
Normal file
1
tests/components/apprise/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the apprise component."""
|
148
tests/components/apprise/test_notify.py
Normal file
148
tests/components/apprise/test_notify.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""The tests for the apprise notification platform."""
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
BASE_COMPONENT = "notify"
|
||||
|
||||
|
||||
async def test_apprise_config_load_fail01(hass):
|
||||
"""Test apprise configuration failures 1."""
|
||||
|
||||
config = {
|
||||
BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"}
|
||||
}
|
||||
|
||||
with patch("apprise.AppriseConfig.add", return_value=False):
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test that our service failed to load
|
||||
assert not hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
|
||||
async def test_apprise_config_load_fail02(hass):
|
||||
"""Test apprise configuration failures 2."""
|
||||
|
||||
config = {
|
||||
BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"}
|
||||
}
|
||||
|
||||
with patch("apprise.Apprise.add", return_value=False):
|
||||
with patch("apprise.AppriseConfig.add", return_value=True):
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test that our service failed to load
|
||||
assert not hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
|
||||
async def test_apprise_config_load_okay(hass, tmp_path):
|
||||
"""Test apprise configuration failures."""
|
||||
|
||||
# Test cases where our URL is invalid
|
||||
d = tmp_path / "apprise-config"
|
||||
d.mkdir()
|
||||
f = d / "apprise"
|
||||
f.write_text("mailto://user:pass@example.com/")
|
||||
|
||||
config = {BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": str(f)}}
|
||||
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Valid configuration was loaded; our service is good
|
||||
assert hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
|
||||
async def test_apprise_url_load_fail(hass):
|
||||
"""Test apprise url failure."""
|
||||
|
||||
config = {
|
||||
BASE_COMPONENT: {
|
||||
"name": "test",
|
||||
"platform": "apprise",
|
||||
"url": "mailto://user:pass@example.com",
|
||||
}
|
||||
}
|
||||
with patch("apprise.Apprise.add", return_value=False):
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test that our service failed to load
|
||||
assert not hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
|
||||
async def test_apprise_notification(hass):
|
||||
"""Test apprise notification."""
|
||||
|
||||
config = {
|
||||
BASE_COMPONENT: {
|
||||
"name": "test",
|
||||
"platform": "apprise",
|
||||
"url": "mailto://user:pass@example.com",
|
||||
}
|
||||
}
|
||||
|
||||
# Our Message
|
||||
data = {"title": "Test Title", "message": "Test Message"}
|
||||
|
||||
with patch("apprise.Apprise") as mock_apprise:
|
||||
obj = MagicMock()
|
||||
obj.add.return_value = True
|
||||
obj.notify.return_value = True
|
||||
mock_apprise.return_value = obj
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test the existance of our service
|
||||
assert hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
# Test the call to our underlining notify() call
|
||||
await hass.services.async_call(BASE_COMPONENT, "test", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Validate calls were made under the hood correctly
|
||||
obj.add.assert_called_once_with([config[BASE_COMPONENT]["url"]])
|
||||
obj.notify.assert_called_once_with(
|
||||
**{"body": data["message"], "title": data["title"], "tag": None}
|
||||
)
|
||||
|
||||
|
||||
async def test_apprise_notification_with_target(hass, tmp_path):
|
||||
"""Test apprise notification with a target."""
|
||||
|
||||
# Test cases where our URL is invalid
|
||||
d = tmp_path / "apprise-config"
|
||||
d.mkdir()
|
||||
f = d / "apprise"
|
||||
|
||||
# Write 2 config entries each assigned to different tags
|
||||
f.write_text("devops=mailto://user:pass@example.com/\r\n")
|
||||
f.write_text("system,alert=syslog://\r\n")
|
||||
|
||||
config = {BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": str(f)}}
|
||||
|
||||
# Our Message, only notify the services tagged with "devops"
|
||||
data = {"title": "Test Title", "message": "Test Message", "target": ["devops"]}
|
||||
|
||||
with patch("apprise.Apprise") as mock_apprise:
|
||||
apprise_obj = MagicMock()
|
||||
apprise_obj.add.return_value = True
|
||||
apprise_obj.notify.return_value = True
|
||||
mock_apprise.return_value = apprise_obj
|
||||
assert await async_setup_component(hass, BASE_COMPONENT, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test the existance of our service
|
||||
assert hass.services.has_service(BASE_COMPONENT, "test")
|
||||
|
||||
# Test the call to our underlining notify() call
|
||||
await hass.services.async_call(BASE_COMPONENT, "test", data)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Validate calls were made under the hood correctly
|
||||
apprise_obj.notify.assert_called_once_with(
|
||||
**{"body": data["message"], "title": data["title"], "tag": data["target"]}
|
||||
)
|
Reference in New Issue
Block a user