Skip to content

Commit

Permalink
Add typing to homeassistant/*.py and homeassistant/util/ (home-assist…
Browse files Browse the repository at this point in the history
…ant#15569)

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

* Fix wrong merge

* Restore iterable in OrderedSet

* Fix tests
  • Loading branch information
andrey-git authored and Jacob Mansfield committed Sep 4, 2018
1 parent f48aa08 commit 640169e
Show file tree
Hide file tree
Showing 27 changed files with 530 additions and 382 deletions.
6 changes: 3 additions & 3 deletions homeassistant/__main__.py
Expand Up @@ -20,7 +20,7 @@
)


def attempt_use_uvloop():
def attempt_use_uvloop() -> None:
"""Attempt to use uvloop."""
import asyncio

Expand Down Expand Up @@ -280,8 +280,8 @@ def setup_and_run_hass(config_dir: str,
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async_ import run_callback_threadsafe

def open_browser(event):
"""Open the webinterface in a browser."""
def open_browser(_: Any) -> None:
"""Open the web interface in a browser."""
if hass.config.api is not None: # type: ignore
import webbrowser
webbrowser.open(hass.config.api.base_url) # type: ignore
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/bootstrap.py
Expand Up @@ -221,8 +221,8 @@ async def async_from_config_file(config_path: str,
@core.callback
def async_enable_logging(hass: core.HomeAssistant,
verbose: bool = False,
log_rotate_days=None,
log_file=None,
log_rotate_days: Optional[int] = None,
log_file: Optional[str] = None,
log_no_color: bool = False) -> None:
"""Set up the logging.
Expand Down Expand Up @@ -291,7 +291,7 @@ def async_enable_logging(hass: core.HomeAssistant,

async_handler = AsyncHandler(hass.loop, err_handler)

async def async_stop_async_handler(event):
async def async_stop_async_handler(_: Any) -> None:
"""Cleanup async handler."""
logging.getLogger('').removeHandler(async_handler) # type: ignore
await async_handler.async_close(blocking=True)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/rachio.py
Expand Up @@ -9,7 +9,7 @@

from aiohttp import web
import voluptuous as vol

from typing import Optional
from homeassistant.auth.util import generate_secret
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
Expand Down Expand Up @@ -241,7 +241,7 @@ def list_zones(self, include_disabled=False) -> list:
# Only enabled zones
return [z for z in self._zones if z[KEY_ENABLED]]

def get_zone(self, zone_id) -> dict or None:
def get_zone(self, zone_id) -> Optional[dict]:
"""Return the zone with the given ID."""
for zone in self.list_zones(include_disabled=True):
if zone[KEY_ID] == zone_id:
Expand Down
85 changes: 48 additions & 37 deletions homeassistant/config.py
Expand Up @@ -7,8 +7,9 @@
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

Expand All @@ -21,7 +22,7 @@
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -276,31 +277,34 @@ 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
configuration by itself.
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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -430,11 +436,10 @@ def set_time_zone(time_zone_str):
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]))
Expand Down Expand Up @@ -484,12 +489,12 @@ def set_time_zone(time_zone_str):
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:
Expand All @@ -515,7 +520,7 @@ def set_time_zone(time_zone_str):

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))
Expand All @@ -526,7 +531,8 @@ def set_time_zone(time_zone_str):
", ".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)
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down
12 changes: 7 additions & 5 deletions homeassistant/config_entries.py
Expand Up @@ -113,10 +113,10 @@ async def async_step_discovery(info):

import logging
import uuid
from typing import Set # noqa pylint: disable=unused-import
from typing import Set, Optional # noqa pylint: disable=unused-import

from homeassistant import data_entry_flow
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component, async_process_deps_reqs
from homeassistant.util.decorator import Registry
Expand Down Expand Up @@ -164,8 +164,9 @@ class ConfigEntry:
__slots__ = ('entry_id', 'version', 'domain', 'title', 'data', 'source',
'state')

def __init__(self, version, domain, title, data, source, entry_id=None,
state=ENTRY_STATE_NOT_LOADED):
def __init__(self, version: str, domain: str, title: str, data: dict,
source: str, entry_id: Optional[str] = None,
state: str = ENTRY_STATE_NOT_LOADED) -> None:
"""Initialize a config entry."""
# Unique id of the config entry
self.entry_id = entry_id or uuid.uuid4().hex
Expand All @@ -188,7 +189,8 @@ def __init__(self, version, domain, title, data, source, entry_id=None,
# State of the entry (LOADED, NOT_LOADED)
self.state = state

async def async_setup(self, hass, *, component=None):
async def async_setup(
self, hass: HomeAssistant, *, component=None) -> None:
"""Set up an entry."""
if component is None:
component = getattr(hass.components, self.domain)
Expand Down

0 comments on commit 640169e

Please sign in to comment.