mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
commit
3701ac292c
@ -13,8 +13,6 @@ from voluptuous.humanize import humanize_error
|
|||||||
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
import homeassistant.loader as loader
|
|
||||||
|
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
@ -22,13 +20,11 @@ from homeassistant.components.http import HomeAssistantView
|
|||||||
from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
|
from homeassistant.const import (HTTP_INTERNAL_SERVER_ERROR,
|
||||||
HTTP_BAD_REQUEST)
|
HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
from homeassistant.components.notify import DOMAIN as NotifyDomain
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "ios"
|
DOMAIN = "ios"
|
||||||
|
|
||||||
DEPENDENCIES = ["http"]
|
DEPENDENCIES = ["device_tracker", "http", "zeroconf"]
|
||||||
|
|
||||||
CONF_PUSH = "push"
|
CONF_PUSH = "push"
|
||||||
CONF_PUSH_CATEGORIES = "categories"
|
CONF_PUSH_CATEGORIES = "categories"
|
||||||
@ -245,34 +241,17 @@ def setup(hass, config):
|
|||||||
if CONFIG_FILE == {}:
|
if CONFIG_FILE == {}:
|
||||||
CONFIG_FILE[ATTR_DEVICES] = {}
|
CONFIG_FILE[ATTR_DEVICES] = {}
|
||||||
|
|
||||||
device_tracker = loader.get_component("device_tracker")
|
# Notify needs to have discovery
|
||||||
if device_tracker.DOMAIN not in hass.config.components:
|
# notify_config = {"notify": {CONF_PLATFORM: "ios"}}
|
||||||
device_tracker.setup(hass, {})
|
# bootstrap.setup_component(hass, "notify", notify_config)
|
||||||
# Need this to enable requirements checking in the app.
|
|
||||||
hass.config.components.append(device_tracker.DOMAIN)
|
|
||||||
|
|
||||||
if "notify.ios" not in hass.config.components:
|
|
||||||
notify = loader.get_component("notify.ios")
|
|
||||||
notify.get_service(hass, {})
|
|
||||||
# Need this to enable requirements checking in the app.
|
|
||||||
if NotifyDomain not in hass.config.components:
|
|
||||||
hass.config.components.append(NotifyDomain)
|
|
||||||
|
|
||||||
zeroconf = loader.get_component("zeroconf")
|
|
||||||
if zeroconf.DOMAIN not in hass.config.components:
|
|
||||||
zeroconf.setup(hass, config)
|
|
||||||
# Need this to enable requirements checking in the app.
|
|
||||||
hass.config.components.append(zeroconf.DOMAIN)
|
|
||||||
|
|
||||||
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
|
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
|
||||||
|
|
||||||
hass.wsgi.register_view(iOSIdentifyDeviceView(hass))
|
hass.wsgi.register_view(iOSIdentifyDeviceView(hass))
|
||||||
|
|
||||||
if config.get(DOMAIN) is not None:
|
app_config = config.get(DOMAIN, {})
|
||||||
app_config = config[DOMAIN]
|
hass.wsgi.register_view(iOSPushConfigView(hass,
|
||||||
if app_config.get(CONF_PUSH) is not None:
|
app_config.get(CONF_PUSH, {})))
|
||||||
push_config = app_config[CONF_PUSH]
|
|
||||||
hass.wsgi.register_view(iOSPushConfigView(hass, push_config))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -23,6 +23,22 @@ PUSH_URL = "https://ios-push.home-assistant.io/push"
|
|||||||
DEPENDENCIES = ["ios"]
|
DEPENDENCIES = ["ios"]
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
def log_rate_limits(target, resp, level=20):
|
||||||
|
"""Output rate limit log line at given level."""
|
||||||
|
rate_limits = resp["rateLimits"]
|
||||||
|
resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"])
|
||||||
|
resetsAtTime = resetsAt - datetime.now(timezone.utc)
|
||||||
|
rate_limit_msg = ("iOS push notification rate limits for %s: "
|
||||||
|
"%d sent, %d allowed, %d errors, "
|
||||||
|
"resets in %s")
|
||||||
|
_LOGGER.log(level, rate_limit_msg,
|
||||||
|
ios.device_name_for_push_id(target),
|
||||||
|
rate_limits["successful"],
|
||||||
|
rate_limits["maximum"], rate_limits["errors"],
|
||||||
|
str(resetsAtTime).split(".")[0])
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
"""Get the iOS notification service."""
|
"""Get the iOS notification service."""
|
||||||
if "notify.ios" not in hass.config.components:
|
if "notify.ios" not in hass.config.components:
|
||||||
@ -66,22 +82,17 @@ class iOSNotificationService(BaseNotificationService):
|
|||||||
|
|
||||||
req = requests.post(PUSH_URL, json=data, timeout=10)
|
req = requests.post(PUSH_URL, json=data, timeout=10)
|
||||||
|
|
||||||
if req.status_code is not 201:
|
if req.status_code != 201:
|
||||||
message = req.json()["message"]
|
fallback_error = req.json().get("errorMessage",
|
||||||
if req.status_code is 429:
|
"Unknown error")
|
||||||
|
fallback_message = ("Internal server error, "
|
||||||
|
"please try again later: "
|
||||||
|
"{}").format(fallback_error)
|
||||||
|
message = req.json().get("message", fallback_message)
|
||||||
|
if req.status_code == 429:
|
||||||
_LOGGER.warning(message)
|
_LOGGER.warning(message)
|
||||||
elif req.status_code is 400 or 500:
|
log_rate_limits(target, req.json(), 30)
|
||||||
|
else:
|
||||||
_LOGGER.error(message)
|
_LOGGER.error(message)
|
||||||
|
else:
|
||||||
if req.status_code in (201, 429):
|
log_rate_limits(target, req.json())
|
||||||
rate_limits = req.json()["rateLimits"]
|
|
||||||
resetsAt = dt_util.parse_datetime(rate_limits["resetsAt"])
|
|
||||||
resetsAtTime = resetsAt - datetime.now(timezone.utc)
|
|
||||||
rate_limit_msg = ("iOS push notification rate limits for %s: "
|
|
||||||
"%d sent, %d allowed, %d errors, "
|
|
||||||
"resets in %s")
|
|
||||||
_LOGGER.info(rate_limit_msg,
|
|
||||||
ios.device_name_for_push_id(target),
|
|
||||||
rate_limits["successful"],
|
|
||||||
rate_limits["maximum"], rate_limits["errors"],
|
|
||||||
str(resetsAtTime).split(".")[0])
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 31
|
MINOR_VERSION = 31
|
||||||
PATCH_VERSION = '0'
|
PATCH_VERSION = '1'
|
||||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||||
REQUIRED_PYTHON_VER = (3, 4, 2)
|
REQUIRED_PYTHON_VER = (3, 4, 2)
|
||||||
|
@ -283,7 +283,7 @@ def async_template(hass, value_template, variables=None):
|
|||||||
try:
|
try:
|
||||||
value = value_template.async_render(variables)
|
value = value_template.async_render(variables)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
_LOGGER.error('Error duriong template condition: %s', ex)
|
_LOGGER.error('Error during template condition: %s', ex)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return value.lower() == 'true'
|
return value.lower() == 'true'
|
||||||
|
@ -43,6 +43,9 @@ def load_yaml(fname: str) -> Union[List, Dict]:
|
|||||||
except yaml.YAMLError as exc:
|
except yaml.YAMLError as exc:
|
||||||
_LOGGER.error(exc)
|
_LOGGER.error(exc)
|
||||||
raise HomeAssistantError(exc)
|
raise HomeAssistantError(exc)
|
||||||
|
except UnicodeDecodeError as exc:
|
||||||
|
_LOGGER.error('Unable to read file %s: %s', fname, exc)
|
||||||
|
raise HomeAssistantError(exc)
|
||||||
|
|
||||||
|
|
||||||
def clear_secret_cache() -> None:
|
def clear_secret_cache() -> None:
|
||||||
@ -61,11 +64,17 @@ def _include_yaml(loader: SafeLineLoader,
|
|||||||
return load_yaml(fname)
|
return load_yaml(fname)
|
||||||
|
|
||||||
|
|
||||||
def _find_files(directory, pattern):
|
def _is_file_valid(name: str) -> bool:
|
||||||
|
"""Decide if a file is valid."""
|
||||||
|
return not name.startswith('.')
|
||||||
|
|
||||||
|
|
||||||
|
def _find_files(directory: str, pattern: str):
|
||||||
"""Recursively load files in a directory."""
|
"""Recursively load files in a directory."""
|
||||||
for root, _dirs, files in os.walk(directory):
|
for root, dirs, files in os.walk(directory, topdown=True):
|
||||||
|
dirs[:] = [d for d in dirs if _is_file_valid(d)]
|
||||||
for basename in files:
|
for basename in files:
|
||||||
if fnmatch.fnmatch(basename, pattern):
|
if _is_file_valid(basename) and fnmatch.fnmatch(basename, pattern):
|
||||||
filename = os.path.join(root, basename)
|
filename = os.path.join(root, basename)
|
||||||
yield filename
|
yield filename
|
||||||
|
|
||||||
|
@ -76,10 +76,13 @@ class TestYaml(unittest.TestCase):
|
|||||||
@patch('homeassistant.util.yaml.os.walk')
|
@patch('homeassistant.util.yaml.os.walk')
|
||||||
def test_include_dir_list(self, mock_walk):
|
def test_include_dir_list(self, mock_walk):
|
||||||
"""Test include dir list yaml."""
|
"""Test include dir list yaml."""
|
||||||
mock_walk.return_value = [['/tmp', [], ['one.yaml', 'two.yaml']]]
|
mock_walk.return_value = [
|
||||||
|
['/tmp', [], ['one.yaml', 'two.yaml']],
|
||||||
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
'/tmp/one.yaml': 'one', '/tmp/two.yaml': 'two'
|
'/tmp/one.yaml': 'one',
|
||||||
|
'/tmp/two.yaml': 'two',
|
||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_list /tmp"
|
conf = "key: !include_dir_list /tmp"
|
||||||
with io.StringIO(conf) as file:
|
with io.StringIO(conf) as file:
|
||||||
@ -90,26 +93,35 @@ class TestYaml(unittest.TestCase):
|
|||||||
def test_include_dir_list_recursive(self, mock_walk):
|
def test_include_dir_list_recursive(self, mock_walk):
|
||||||
"""Test include dir recursive list yaml."""
|
"""Test include dir recursive list yaml."""
|
||||||
mock_walk.return_value = [
|
mock_walk.return_value = [
|
||||||
['/tmp', ['tmp2'], ['zero.yaml']],
|
['/tmp', ['tmp2', '.ignore', 'ignore'], ['zero.yaml']],
|
||||||
['/tmp/tmp2', [], ['one.yaml', 'two.yaml']],
|
['/tmp/tmp2', [], ['one.yaml', 'two.yaml']],
|
||||||
|
['/tmp/ignore', [], ['.ignore.yaml']]
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
'/tmp/zero.yaml': 'zero', '/tmp/tmp2/one.yaml': 'one',
|
'/tmp/zero.yaml': 'zero',
|
||||||
|
'/tmp/tmp2/one.yaml': 'one',
|
||||||
'/tmp/tmp2/two.yaml': 'two'
|
'/tmp/tmp2/two.yaml': 'two'
|
||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_list /tmp"
|
conf = "key: !include_dir_list /tmp"
|
||||||
with io.StringIO(conf) as file:
|
with io.StringIO(conf) as file:
|
||||||
|
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||||
|
"Expecting .ignore in here"
|
||||||
doc = yaml.yaml.safe_load(file)
|
doc = yaml.yaml.safe_load(file)
|
||||||
|
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||||
|
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||||
assert sorted(doc["key"]) == sorted(["zero", "one", "two"])
|
assert sorted(doc["key"]) == sorted(["zero", "one", "two"])
|
||||||
|
|
||||||
@patch('homeassistant.util.yaml.os.walk')
|
@patch('homeassistant.util.yaml.os.walk')
|
||||||
def test_include_dir_named(self, mock_walk):
|
def test_include_dir_named(self, mock_walk):
|
||||||
"""Test include dir named yaml."""
|
"""Test include dir named yaml."""
|
||||||
mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]]
|
mock_walk.return_value = [
|
||||||
|
['/tmp', [], ['first.yaml', 'second.yaml']]
|
||||||
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
'/tmp/first.yaml': 'one', '/tmp/second.yaml': 'two'
|
'/tmp/first.yaml': 'one',
|
||||||
|
'/tmp/second.yaml': 'two'
|
||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_named /tmp"
|
conf = "key: !include_dir_named /tmp"
|
||||||
correct = {'first': 'one', 'second': 'two'}
|
correct = {'first': 'one', 'second': 'two'}
|
||||||
@ -121,18 +133,24 @@ class TestYaml(unittest.TestCase):
|
|||||||
def test_include_dir_named_recursive(self, mock_walk):
|
def test_include_dir_named_recursive(self, mock_walk):
|
||||||
"""Test include dir named yaml."""
|
"""Test include dir named yaml."""
|
||||||
mock_walk.return_value = [
|
mock_walk.return_value = [
|
||||||
['/tmp', ['tmp2'], ['first.yaml']],
|
['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']],
|
||||||
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
||||||
|
['/tmp/ignore', [], ['.ignore.yaml']]
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
'/tmp/first.yaml': 'one', '/tmp/tmp2/second.yaml': 'two',
|
'/tmp/first.yaml': 'one',
|
||||||
|
'/tmp/tmp2/second.yaml': 'two',
|
||||||
'/tmp/tmp2/third.yaml': 'three'
|
'/tmp/tmp2/third.yaml': 'three'
|
||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_named /tmp"
|
conf = "key: !include_dir_named /tmp"
|
||||||
correct = {'first': 'one', 'second': 'two', 'third': 'three'}
|
correct = {'first': 'one', 'second': 'two', 'third': 'three'}
|
||||||
with io.StringIO(conf) as file:
|
with io.StringIO(conf) as file:
|
||||||
|
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||||
|
"Expecting .ignore in here"
|
||||||
doc = yaml.yaml.safe_load(file)
|
doc = yaml.yaml.safe_load(file)
|
||||||
|
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||||
|
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||||
assert doc["key"] == correct
|
assert doc["key"] == correct
|
||||||
|
|
||||||
@patch('homeassistant.util.yaml.os.walk')
|
@patch('homeassistant.util.yaml.os.walk')
|
||||||
@ -153,17 +171,23 @@ class TestYaml(unittest.TestCase):
|
|||||||
def test_include_dir_merge_list_recursive(self, mock_walk):
|
def test_include_dir_merge_list_recursive(self, mock_walk):
|
||||||
"""Test include dir merge list yaml."""
|
"""Test include dir merge list yaml."""
|
||||||
mock_walk.return_value = [
|
mock_walk.return_value = [
|
||||||
['/tmp', ['tmp2'], ['first.yaml']],
|
['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']],
|
||||||
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
||||||
|
['/tmp/ignore', [], ['.ignore.yaml']]
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
'/tmp/first.yaml': '- one', '/tmp/tmp2/second.yaml': '- two',
|
'/tmp/first.yaml': '- one',
|
||||||
|
'/tmp/tmp2/second.yaml': '- two',
|
||||||
'/tmp/tmp2/third.yaml': '- three\n- four'
|
'/tmp/tmp2/third.yaml': '- three\n- four'
|
||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_merge_list /tmp"
|
conf = "key: !include_dir_merge_list /tmp"
|
||||||
with io.StringIO(conf) as file:
|
with io.StringIO(conf) as file:
|
||||||
|
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||||
|
"Expecting .ignore in here"
|
||||||
doc = yaml.yaml.safe_load(file)
|
doc = yaml.yaml.safe_load(file)
|
||||||
|
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||||
|
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||||
assert sorted(doc["key"]) == sorted(["one", "two",
|
assert sorted(doc["key"]) == sorted(["one", "two",
|
||||||
"three", "four"])
|
"three", "four"])
|
||||||
|
|
||||||
@ -189,8 +213,9 @@ class TestYaml(unittest.TestCase):
|
|||||||
def test_include_dir_merge_named_recursive(self, mock_walk):
|
def test_include_dir_merge_named_recursive(self, mock_walk):
|
||||||
"""Test include dir merge named yaml."""
|
"""Test include dir merge named yaml."""
|
||||||
mock_walk.return_value = [
|
mock_walk.return_value = [
|
||||||
['/tmp', ['tmp2'], ['first.yaml']],
|
['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']],
|
||||||
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
['/tmp/tmp2', [], ['second.yaml', 'third.yaml']],
|
||||||
|
['/tmp/ignore', [], ['.ignore.yaml']]
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch_yaml_files({
|
with patch_yaml_files({
|
||||||
@ -200,7 +225,11 @@ class TestYaml(unittest.TestCase):
|
|||||||
}):
|
}):
|
||||||
conf = "key: !include_dir_merge_named /tmp"
|
conf = "key: !include_dir_merge_named /tmp"
|
||||||
with io.StringIO(conf) as file:
|
with io.StringIO(conf) as file:
|
||||||
|
assert '.ignore' in mock_walk.return_value[0][1], \
|
||||||
|
"Expecting .ignore in here"
|
||||||
doc = yaml.yaml.safe_load(file)
|
doc = yaml.yaml.safe_load(file)
|
||||||
|
assert 'tmp2' in mock_walk.return_value[0][1]
|
||||||
|
assert '.ignore' not in mock_walk.return_value[0][1]
|
||||||
assert doc["key"] == {
|
assert doc["key"] == {
|
||||||
"key1": "one",
|
"key1": "one",
|
||||||
"key2": "two",
|
"key2": "two",
|
||||||
@ -208,6 +237,13 @@ class TestYaml(unittest.TestCase):
|
|||||||
"key4": "four"
|
"key4": "four"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@patch('homeassistant.util.yaml.open', create=True)
|
||||||
|
def test_load_yaml_encoding_error(self, mock_open):
|
||||||
|
"""Test raising a UnicodeDecodeError."""
|
||||||
|
mock_open.side_effect = UnicodeDecodeError('', b'', 1, 0, '')
|
||||||
|
self.assertRaises(HomeAssistantError, yaml.load_yaml, 'test')
|
||||||
|
|
||||||
|
|
||||||
FILES = {}
|
FILES = {}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user