Skip to content

Commit

Permalink
Improvement typing core (home-assistant#2624)
Browse files Browse the repository at this point in the history
* Add package typing

* Add util/location typing

* FIX: lint wrong order of imports

* Fix sometyping and add helpers/entity typing

* Mypy import trick

* Add asteroid to test requiremts to fix pylint issue

* Fix deprecated function isSet for is_set

* Add loader.py typing

* Improve typing bootstrap
  • Loading branch information
fabianhjr authored and balloob committed Jul 28, 2016
1 parent 8c728d1 commit ae97218
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 56 deletions.
21 changes: 13 additions & 8 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
import sys
from collections import defaultdict
from threading import RLock

from types import ModuleType
from typing import Any, Optional, Dict

import voluptuous as vol


import homeassistant.components as core_components
from homeassistant.components import group, persistent_notification
import homeassistant.config as conf_util
Expand All @@ -32,7 +33,8 @@
ERROR_LOG_FILENAME = 'home-assistant.log'


def setup_component(hass, domain, config=None):
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
if domain in hass.config.components:
return True
Expand All @@ -55,7 +57,8 @@ def setup_component(hass, domain, config=None):
return True


def _handle_requirements(hass, component, name):
def _handle_requirements(hass: core.HomeAssistant, component,
name: str) -> bool:
"""Install the requirements for a component."""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
Expand All @@ -69,7 +72,7 @@ def _handle_requirements(hass, component, name):
return True


def _setup_component(hass, domain, config):
def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
"""Setup a component for Home Assistant."""
# pylint: disable=too-many-return-statements,too-many-branches
# pylint: disable=too-many-statements
Expand Down Expand Up @@ -178,7 +181,8 @@ def _setup_component(hass, domain, config):
return True


def prepare_setup_platform(hass, config, domain, platform_name):
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass)

Expand Down Expand Up @@ -309,7 +313,8 @@ def from_config_file(config_path: str,
skip_pip=skip_pip)


def enable_logging(hass, verbose=False, log_rotate_days=None):
def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
log_rotate_days=None) -> None:
"""Setup the logging."""
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
Expand Down Expand Up @@ -360,12 +365,12 @@ def enable_logging(hass, verbose=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path)


def _ensure_loader_prepared(hass):
def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)


def _mount_local_lib_path(config_dir):
def _mount_local_lib_path(config_dir: str) -> None:
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))
10 changes: 5 additions & 5 deletions homeassistant/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,14 @@ def restart_homeassistant(*args):
except AttributeError:
pass
try:
while not request_shutdown.isSet():
while not request_shutdown.is_set():
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
self.stop()

return RESTART_EXIT_CODE if request_restart.isSet() else 0
return RESTART_EXIT_CODE if request_restart.is_set() else 0

def stop(self) -> None:
"""Stop Home Assistant and shuts down all threads."""
Expand Down Expand Up @@ -233,7 +233,7 @@ def __eq__(self, other):
class EventBus(object):
"""Allows firing of and listening for events."""

def __init__(self, pool: util.ThreadPool):
def __init__(self, pool: util.ThreadPool) -> None:
"""Initialize a new event bus."""
self._listeners = {}
self._lock = threading.Lock()
Expand Down Expand Up @@ -792,7 +792,7 @@ def stop_timer(event):

calc_now = dt_util.utcnow

while not stop_event.isSet():
while not stop_event.is_set():
now = calc_now()

# First check checks if we are not on a second matching the
Expand All @@ -816,7 +816,7 @@ def stop_timer(event):
last_fired_on_second = now.second

# Event might have been set while sleeping
if not stop_event.isSet():
if not stop_event.is_set():
try:
hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now})
except HomeAssistantError:
Expand Down
17 changes: 14 additions & 3 deletions homeassistant/helpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
"""Helper methods for components within Home Assistant."""
import re

from typing import Any, Iterable, Tuple, List, Dict

from homeassistant.const import CONF_PLATFORM

# Typing Imports and TypeAlias
# pylint: disable=using-constant-test,unused-import
if False:
from logging import Logger # NOQA

# pylint: disable=invalid-name
ConfigType = Dict[str, Any]


def validate_config(config, items, logger):
def validate_config(config: ConfigType, items: Dict, logger: 'Logger') -> bool:
"""Validate if all items are available in the configuration.
config is the general dictionary with all the configurations.
Expand All @@ -29,7 +39,8 @@ def validate_config(config, items, logger):
return not errors_found


def config_per_platform(config, domain):
def config_per_platform(config: ConfigType,
domain: str) -> Iterable[Tuple[Any, Any]]:
"""Generator to break a component config into different platforms.
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
Expand All @@ -48,7 +59,7 @@ def config_per_platform(config, domain):
yield platform, item


def extract_domain_configs(config, domain):
def extract_domain_configs(config: ConfigType, domain: str) -> List[str]:
"""Extract keys from config for given domain name."""
pattern = re.compile(r'^{}(| .+)$'.format(domain))
return [key for key in config.keys() if pattern.match(key)]
48 changes: 28 additions & 20 deletions homeassistant/helpers/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import logging
import re

from typing import Any, Optional, List, Dict

from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
Expand All @@ -10,16 +12,22 @@
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify

# pylint: disable=using-constant-test,unused-import
if False:
from homeassistant.core import HomeAssistant # NOQA

# Entity attributes that we will overwrite
_OVERWRITE = {}
_OVERWRITE = {} # type: Dict[str, Any]

_LOGGER = logging.getLogger(__name__)

# Pattern for validating entity IDs (format: <domain>.<entity>)
ENTITY_ID_PATTERN = re.compile(r"^(\w+)\.(\w+)$")


def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
def generate_entity_id(entity_id_format: str, name: Optional[str],
current_ids: Optional[List[str]]=None,
hass: 'Optional[HomeAssistant]'=None) -> str:
"""Generate a unique entity ID based on given entity IDs or used IDs."""
name = (name or DEVICE_DEFAULT_NAME).lower()
if current_ids is None:
Expand All @@ -32,19 +40,19 @@ def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
entity_id_format.format(slugify(name)), current_ids)


def set_customize(customize):
def set_customize(customize: Dict[str, Any]) -> None:
"""Overwrite all current customize settings."""
global _OVERWRITE

_OVERWRITE = {key.lower(): val for key, val in customize.items()}


def split_entity_id(entity_id):
def split_entity_id(entity_id: str) -> List[str]:
"""Split a state entity_id into domain, object_id."""
return entity_id.split(".", 1)


def valid_entity_id(entity_id):
def valid_entity_id(entity_id: str) -> bool:
"""Test if an entity ID is a valid format."""
return ENTITY_ID_PATTERN.match(entity_id) is not None

Expand All @@ -57,25 +65,25 @@ class Entity(object):
# The properties and methods here are safe to overwrite when inheriting
# this class. These may be used to customize the behavior of the entity.
@property
def should_poll(self):
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
False if entity pushes its state to HA.
"""
return True

@property
def unique_id(self):
def unique_id(self) -> str:
"""Return an unique ID."""
return "{}.{}".format(self.__class__, id(self))

@property
def name(self):
def name(self) -> Optional[str]:
"""Return the name of the entity."""
return None

@property
def state(self):
def state(self) -> str:
"""Return the state of the entity."""
return STATE_UNKNOWN

Expand Down Expand Up @@ -111,22 +119,22 @@ def entity_picture(self):
return None

@property
def hidden(self):
def hidden(self) -> bool:
"""Return True if the entity should be hidden from UIs."""
return False

@property
def available(self):
def available(self) -> bool:
"""Return True if entity is available."""
return True

@property
def assumed_state(self):
def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity."""
return False

@property
def force_update(self):
def force_update(self) -> bool:
"""Return True if state updates should be forced.
If True, a state change will be triggered anytime the state property is
Expand All @@ -138,14 +146,14 @@ def update(self):
"""Retrieve latest state."""
pass

entity_id = None
entity_id = None # type: str

# DO NOT OVERWRITE
# These properties and methods are either managed by Home Assistant or they
# are used to perform a very specific function. Overwriting these may
# produce undesirable effects in the entity's operation.

hass = None
hass = None # type: Optional[HomeAssistant]

def update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity.
Expand Down Expand Up @@ -232,24 +240,24 @@ class ToggleEntity(Entity):

# pylint: disable=no-self-use
@property
def state(self):
def state(self) -> str:
"""Return the state."""
return STATE_ON if self.is_on else STATE_OFF

@property
def is_on(self):
def is_on(self) -> bool:
"""Return True if entity is on."""
raise NotImplementedError()

def turn_on(self, **kwargs):
def turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
raise NotImplementedError()

def turn_off(self, **kwargs):
def turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
raise NotImplementedError()

def toggle(self, **kwargs):
def toggle(self, **kwargs) -> None:
"""Toggle the entity off."""
if self.is_on:
self.turn_off(**kwargs)
Expand Down
Loading

0 comments on commit ae97218

Please sign in to comment.