Add typing to homeassistant/*.py and homeassistant/util/ (#15569)

* Add typing to homeassistant/*.py and homeassistant/util/

* Fix wrong merge

* Restore iterable in OrderedSet

* Fix tests
This commit is contained in:
Andrey 2018-07-23 11:24:39 +03:00 committed by Paulus Schoutsen
parent b7c336a687
commit 140a874917
27 changed files with 532 additions and 384 deletions

View file

@ -7,8 +7,9 @@ import os
import re
import shutil
# pylint: disable=unused-import
from typing import Any, Tuple, Optional # noqa: F401
from typing import ( # noqa: F401
Any, Tuple, Optional, Dict, List, Union, Callable)
from types import ModuleType
import voluptuous as vol
from voluptuous.humanize import humanize_error
@ -21,7 +22,7 @@ from homeassistant.const import (
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
__version__, CONF_CUSTOMIZE, CONF_CUSTOMIZE_DOMAIN, CONF_CUSTOMIZE_GLOB,
CONF_WHITELIST_EXTERNAL_DIRS, CONF_AUTH_PROVIDERS, CONF_TYPE)
from homeassistant.core import callback, DOMAIN as CONF_CORE
from homeassistant.core import callback, DOMAIN as CONF_CORE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import get_component, get_platform
from homeassistant.util.yaml import load_yaml, SECRET_YAML
@ -193,7 +194,7 @@ def ensure_config_exists(config_dir: str, detect_location: bool = True)\
return config_path
def create_default_config(config_dir: str, detect_location=True)\
def create_default_config(config_dir: str, detect_location: bool = True)\
-> Optional[str]:
"""Create a default configuration file in given configuration directory.
@ -276,7 +277,7 @@ def create_default_config(config_dir: str, detect_location=True)\
return None
async def async_hass_config_yaml(hass):
async def async_hass_config_yaml(hass: HomeAssistant) -> Dict:
"""Load YAML from a Home Assistant configuration file.
This function allow a component inside the asyncio loop to reload its
@ -284,23 +285,26 @@ async def async_hass_config_yaml(hass):
This method is a coroutine.
"""
def _load_hass_yaml_config():
def _load_hass_yaml_config() -> Dict:
path = find_config_file(hass.config.config_dir)
conf = load_yaml_config_file(path)
return conf
if path is None:
raise HomeAssistantError(
"Config file not found in: {}".format(hass.config.config_dir))
return load_yaml_config_file(path)
conf = await hass.async_add_job(_load_hass_yaml_config)
return conf
return await hass.async_add_executor_job(_load_hass_yaml_config)
def find_config_file(config_dir: str) -> Optional[str]:
def find_config_file(config_dir: Optional[str]) -> Optional[str]:
"""Look in given directory for supported configuration files."""
if config_dir is None:
return None
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
return config_path if os.path.isfile(config_path) else None
def load_yaml_config_file(config_path):
def load_yaml_config_file(config_path: str) -> Dict[Any, Any]:
"""Parse a YAML configuration file.
This method needs to run in an executor.
@ -323,7 +327,7 @@ def load_yaml_config_file(config_path):
return conf_dict
def process_ha_config_upgrade(hass):
def process_ha_config_upgrade(hass: HomeAssistant) -> None:
"""Upgrade configuration if necessary.
This method needs to run in an executor.
@ -360,7 +364,8 @@ def process_ha_config_upgrade(hass):
@callback
def async_log_exception(ex, domain, config, hass):
def async_log_exception(ex: vol.Invalid, domain: str, config: Dict,
hass: HomeAssistant) -> None:
"""Log an error for configuration validation.
This method must be run in the event loop.
@ -371,7 +376,7 @@ def async_log_exception(ex, domain, config, hass):
@callback
def _format_config_error(ex, domain, config):
def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str:
"""Generate log exception for configuration validation.
This method must be run in the event loop.
@ -396,7 +401,8 @@ def _format_config_error(ex, domain, config):
return message
async def async_process_ha_core_config(hass, config):
async def async_process_ha_core_config(
hass: HomeAssistant, config: Dict) -> None:
"""Process the [homeassistant] section from the configuration.
This method is a coroutine.
@ -405,12 +411,12 @@ async def async_process_ha_core_config(hass, config):
# Only load auth during startup.
if not hasattr(hass, 'auth'):
hass.auth = await auth.auth_manager_from_config(
hass, config.get(CONF_AUTH_PROVIDERS, []))
setattr(hass, 'auth', await auth.auth_manager_from_config(
hass, config.get(CONF_AUTH_PROVIDERS, [])))
hac = hass.config
def set_time_zone(time_zone_str):
def set_time_zone(time_zone_str: Optional[str]) -> None:
"""Help to set the time zone."""
if time_zone_str is None:
return
@ -430,11 +436,10 @@ async def async_process_ha_core_config(hass, config):
if key in config:
setattr(hac, attr, config[key])
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
set_time_zone(config.get(CONF_TIME_ZONE))
# Init whitelist external dir
hac.whitelist_external_dirs = set((hass.config.path('www'),))
hac.whitelist_external_dirs = {hass.config.path('www')}
if CONF_WHITELIST_EXTERNAL_DIRS in config:
hac.whitelist_external_dirs.update(
set(config[CONF_WHITELIST_EXTERNAL_DIRS]))
@ -484,12 +489,12 @@ async def async_process_ha_core_config(hass, config):
hac.time_zone, hac.elevation):
return
discovered = []
discovered = [] # type: List[Tuple[str, Any]]
# If we miss some of the needed values, auto detect them
if None in (hac.latitude, hac.longitude, hac.units,
hac.time_zone):
info = await hass.async_add_job(
info = await hass.async_add_executor_job(
loc_util.detect_location_info)
if info is None:
@ -515,7 +520,7 @@ async def async_process_ha_core_config(hass, config):
if hac.elevation is None and hac.latitude is not None and \
hac.longitude is not None:
elevation = await hass.async_add_job(
elevation = await hass.async_add_executor_job(
loc_util.elevation, hac.latitude, hac.longitude)
hac.elevation = elevation
discovered.append(('elevation', elevation))
@ -526,7 +531,8 @@ async def async_process_ha_core_config(hass, config):
", ".join('{}: {}'.format(key, val) for key, val in discovered))
def _log_pkg_error(package, component, config, message):
def _log_pkg_error(
package: str, component: str, config: Dict, message: str) -> None:
"""Log an error while merging packages."""
message = "Package {} setup failed. Component {} {}".format(
package, component, message)
@ -539,12 +545,13 @@ def _log_pkg_error(package, component, config, message):
_LOGGER.error(message)
def _identify_config_schema(module):
def _identify_config_schema(module: ModuleType) -> \
Tuple[Optional[str], Optional[Dict]]:
"""Extract the schema and identify list or dict based."""
try:
schema = module.CONFIG_SCHEMA.schema[module.DOMAIN]
schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore
except (AttributeError, KeyError):
return (None, None)
return None, None
t_schema = str(schema)
if t_schema.startswith('{'):
return ('dict', schema)
@ -553,9 +560,10 @@ def _identify_config_schema(module):
return '', schema
def _recursive_merge(conf, package):
def _recursive_merge(
conf: Dict[str, Any], package: Dict[str, Any]) -> Union[bool, str]:
"""Merge package into conf, recursively."""
error = False
error = False # type: Union[bool, str]
for key, pack_conf in package.items():
if isinstance(pack_conf, dict):
if not pack_conf:
@ -576,8 +584,8 @@ def _recursive_merge(conf, package):
return error
def merge_packages_config(hass, config, packages,
_log_pkg_error=_log_pkg_error):
def merge_packages_config(hass: HomeAssistant, config: Dict, packages: Dict,
_log_pkg_error: Callable = _log_pkg_error) -> Dict:
"""Merge packages into the top-level configuration. Mutate config."""
# pylint: disable=too-many-nested-blocks
PACKAGES_CONFIG_SCHEMA(packages)
@ -641,7 +649,8 @@ def merge_packages_config(hass, config, packages,
@callback
def async_process_component_config(hass, config, domain):
def async_process_component_config(
hass: HomeAssistant, config: Dict, domain: str) -> Optional[Dict]:
"""Check component configuration and return processed configuration.
Returns None on error.
@ -703,14 +712,14 @@ def async_process_component_config(hass, config, domain):
return config
async def async_check_ha_config_file(hass):
async def async_check_ha_config_file(hass: HomeAssistant) -> Optional[str]:
"""Check if Home Assistant configuration file is valid.
This method is a coroutine.
"""
from homeassistant.scripts.check_config import check_ha_config_file
res = await hass.async_add_job(
res = await hass.async_add_executor_job(
check_ha_config_file, hass)
if not res.errors:
@ -719,7 +728,9 @@ async def async_check_ha_config_file(hass):
@callback
def async_notify_setup_error(hass, component, display_link=False):
def async_notify_setup_error(
hass: HomeAssistant, component: str,
display_link: bool = False) -> None:
"""Print a persistent notification.
This method must be run in the event loop.