Type check all helpers (#25373)

* Type check all helpers, add inline exclusions for work in progress

* Remove unused Script._template_cache

* Add some missing type hints

* Remove unneeded type: ignore

* Type hint fixes

* Mypy assistance tweaks

* Don't look for None in deprecated config "at most once" check

* Avoid None name slugify attempt when generating entity id

* Avoid None state store attempt on entity remove
This commit is contained in:
Ville Skyttä 2019-07-21 19:59:02 +03:00 committed by Paulus Schoutsen
parent 0653f57fb4
commit d64f1e767c
19 changed files with 119 additions and 69 deletions

View file

@ -1,6 +1,6 @@
"""Helper to check the configuration file.""" """Helper to check the configuration file."""
from collections import OrderedDict, namedtuple from collections import OrderedDict, namedtuple
# from typing import Dict, List, Sequence from typing import List
import attr import attr
import voluptuous as vol import voluptuous as vol
@ -17,6 +17,9 @@ import homeassistant.util.yaml.loader as yaml_loader
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-warn-return-any
CheckConfigError = namedtuple( CheckConfigError = namedtuple(
'CheckConfigError', "message domain config") 'CheckConfigError', "message domain config")
@ -25,7 +28,8 @@ CheckConfigError = namedtuple(
class HomeAssistantConfig(OrderedDict): class HomeAssistantConfig(OrderedDict):
"""Configuration result with errors attribute.""" """Configuration result with errors attribute."""
errors = attr.ib(default=attr.Factory(list)) errors = attr.ib(
default=attr.Factory(list)) # type: List[CheckConfigError]
def add_error(self, message, domain=None, config=None): def add_error(self, message, domain=None, config=None):
"""Add a single error.""" """Add a single error."""
@ -114,9 +118,10 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> \
result.add_error("Component not found: {}".format(domain)) result.add_error("Component not found: {}".format(domain))
continue continue
if hasattr(component, 'CONFIG_SCHEMA'): config_schema = getattr(component, 'CONFIG_SCHEMA', None)
if config_schema is not None:
try: try:
config = component.CONFIG_SCHEMA(config) config = config_schema(config)
result[domain] = config[domain] result[domain] = config[domain]
except vol.Invalid as ex: except vol.Invalid as ex:
_comp_error(ex, domain, config) _comp_error(ex, domain, config)
@ -133,8 +138,7 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> \
for p_name, p_config in config_per_platform(config, domain): for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema # Validate component specific platform schema
try: try:
p_validated = component_platform_schema( # type: ignore p_validated = component_platform_schema(p_config)
p_config)
except vol.Invalid as ex: except vol.Invalid as ex:
_comp_error(ex, domain, config) _comp_error(ex, domain, config)
continue continue
@ -163,9 +167,10 @@ async def async_check_ha_config_file(hass: HomeAssistant) -> \
continue continue
# Validate platform specific schema # Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'): platform_schema = getattr(platform, 'PLATFORM_SCHEMA', None)
if platform_schema is not None:
try: try:
p_validated = platform.PLATFORM_SCHEMA(p_validated) p_validated = platform_schema(p_validated)
except vol.Invalid as ex: except vol.Invalid as ex:
_comp_error( _comp_error(
ex, '{}.{}'.format(domain, p_name), p_validated) ex, '{}.{}'.format(domain, p_name), p_validated)

View file

@ -4,6 +4,8 @@ from functools import partial
from homeassistant import config_entries from homeassistant import config_entries
# mypy: allow-incomplete-defs, allow-untyped-defs
def register_discovery_flow(domain, title, discovery_function, def register_discovery_flow(domain, title, discovery_function,
connection_class): connection_class):
"""Register flow for discovered integrations that not require auth.""" """Register flow for discovered integrations that not require auth."""

View file

@ -27,6 +27,9 @@ from homeassistant.exceptions import TemplateError
from homeassistant.helpers.logging import KeywordStyleAdapter from homeassistant.helpers.logging import KeywordStyleAdapter
from homeassistant.util import slugify as util_slugify from homeassistant.util import slugify as util_slugify
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
# pylint: disable=invalid-name # pylint: disable=invalid-name
TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'" TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM' or 'HH:MM:SS'"
@ -92,7 +95,8 @@ def boolean(value: Any) -> bool:
if value in ('0', 'false', 'no', 'off', 'disable'): if value in ('0', 'false', 'no', 'off', 'disable'):
return False return False
elif isinstance(value, Number): elif isinstance(value, Number):
return value != 0 # type ignore: https://github.com/python/mypy/issues/3186
return value != 0 # type: ignore
raise vol.Invalid('invalid boolean value {}'.format(value)) raise vol.Invalid('invalid boolean value {}'.format(value))
@ -161,7 +165,7 @@ def isdir(value: Any) -> str:
return dir_in return dir_in
def ensure_list(value: Union[T, Sequence[T]]) -> Sequence[T]: def ensure_list(value: Union[T, Sequence[T], None]) -> Sequence[T]:
"""Wrap value in list if it is not one.""" """Wrap value in list if it is not one."""
if value is None: if value is None:
return [] return []
@ -556,7 +560,8 @@ def deprecated(key: str,
else: else:
# Unclear when it is None, but it happens, so let's guard. # Unclear when it is None, but it happens, so let's guard.
# https://github.com/home-assistant/home-assistant/issues/24982 # https://github.com/home-assistant/home-assistant/issues/24982
module_name = __name__ # type ignore/unreachable: https://github.com/python/typeshed/pull/3137
module_name = __name__ # type: ignore
if replacement_key and invalidation_version: if replacement_key and invalidation_version:
warning = ("The '{key}' option (with value '{value}') is" warning = ("The '{key}' option (with value '{value}') is"
@ -606,13 +611,15 @@ def deprecated(key: str,
config.pop(key) config.pop(key)
else: else:
value = default value = default
if (replacement_key keys = [key]
and (replacement_key not in config if replacement_key:
or default == config.get(replacement_key)) keys.append(replacement_key)
and value is not None): if value is not None and (
config[replacement_key] = value replacement_key not in config or
default == config.get(replacement_key)):
config[replacement_key] = value
return has_at_most_one_key(key, replacement_key)(config) return has_at_most_one_key(*keys)(config)
return validator return validator
@ -739,7 +746,7 @@ CONDITION_SCHEMA = vol.Any(
ZONE_CONDITION_SCHEMA, ZONE_CONDITION_SCHEMA,
AND_CONDITION_SCHEMA, AND_CONDITION_SCHEMA,
OR_CONDITION_SCHEMA, OR_CONDITION_SCHEMA,
) ) # type: vol.Schema
_SCRIPT_DELAY_SCHEMA = vol.Schema({ _SCRIPT_DELAY_SCHEMA = vol.Schema({
vol.Optional(CONF_ALIAS): string, vol.Optional(CONF_ALIAS): string,

View file

@ -7,6 +7,8 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.data_validator import RequestDataValidator
# mypy: allow-untyped-calls, allow-untyped-defs
class _BaseFlowManagerView(HomeAssistantView): class _BaseFlowManagerView(HomeAssistantView):
"""Foundation for flow manager views.""" """Foundation for flow manager views."""

View file

@ -12,6 +12,10 @@ from homeassistant.loader import bind_hass
from .typing import HomeAssistantType from .typing import HomeAssistantType
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_UNDEF = object() _UNDEF = object()

View file

@ -13,6 +13,9 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import DEPENDENCY_BLACKLIST from homeassistant.loader import DEPENDENCY_BLACKLIST
from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.async_ import run_callback_threadsafe
# mypy: allow-untyped-defs, no-check-untyped-defs
EVENT_LOAD_PLATFORM = 'load_platform.{}' EVENT_LOAD_PLATFORM = 'load_platform.{}'
ATTR_PLATFORM = 'platform' ATTR_PLATFORM = 'platform'

View file

@ -10,14 +10,19 @@ from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON, ATTR_UNIT_OF_MEASUREMENT, DEVICE_DEFAULT_NAME, STATE_OFF, STATE_ON,
STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, ATTR_DEVICE_CLASS) ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, ATTR_DEVICE_CLASS)
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import (
from homeassistant.core import HomeAssistant, callback EVENT_ENTITY_REGISTRY_UPDATED, RegistryEntry)
from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE
from homeassistant.config import DATA_CUSTOMIZE from homeassistant.config import DATA_CUSTOMIZE
from homeassistant.exceptions import NoEntitySpecifiedError from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.util import ensure_unique_string, slugify from homeassistant.util import ensure_unique_string, slugify
from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
# mypy: allow-incomplete-defs, allow-untyped-defs, no-check-untyped-defs
# mypy: no-warn-return-any
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SLOW_UPDATE_WARNING = 10 SLOW_UPDATE_WARNING = 10
@ -34,7 +39,7 @@ def generate_entity_id(entity_id_format: str, name: Optional[str],
current_ids, hass current_ids, hass
).result() ).result()
name = (slugify(name) or slugify(DEVICE_DEFAULT_NAME)).lower() name = (slugify(name or "") or slugify(DEVICE_DEFAULT_NAME)).lower()
return ensure_unique_string( return ensure_unique_string(
entity_id_format.format(name), current_ids) entity_id_format.format(name), current_ids)
@ -80,10 +85,10 @@ class Entity:
parallel_updates = None parallel_updates = None
# Entry in the entity registry # Entry in the entity registry
registry_entry = None registry_entry = None # type: Optional[RegistryEntry]
# Hold list for functions to call on remove. # Hold list for functions to call on remove.
_on_remove = None _on_remove = None # type: Optional[List[CALLBACK_TYPE]]
# Context # Context
_context = None _context = None
@ -98,7 +103,7 @@ class Entity:
return True return True
@property @property
def unique_id(self) -> str: def unique_id(self) -> Optional[str]:
"""Return a unique ID.""" """Return a unique ID."""
return None return None
@ -137,7 +142,7 @@ class Entity:
return None return None
@property @property
def device_class(self) -> str: def device_class(self) -> Optional[str]:
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
return None return None
@ -181,7 +186,7 @@ class Entity:
return False return False
@property @property
def supported_features(self) -> int: def supported_features(self) -> Optional[int]:
"""Flag supported features.""" """Flag supported features."""
return None return None
@ -386,7 +391,7 @@ class Entity:
self.parallel_updates.release() self.parallel_updates.release()
@callback @callback
def async_on_remove(self, func): def async_on_remove(self, func: CALLBACK_TYPE) -> None:
"""Add a function to call when entity removed.""" """Add a function to call when entity removed."""
if self._on_remove is None: if self._on_remove is None:
self._on_remove = [] self._on_remove = []
@ -421,6 +426,7 @@ class Entity:
Not to be extended by integrations. Not to be extended by integrations.
""" """
if self.registry_entry is not None: if self.registry_entry is not None:
assert self.hass is not None
self.async_on_remove(self.hass.bus.async_listen( self.async_on_remove(self.hass.bus.async_listen(
EVENT_ENTITY_REGISTRY_UPDATED, self._async_registry_updated)) EVENT_ENTITY_REGISTRY_UPDATED, self._async_registry_updated))

View file

@ -17,6 +17,9 @@ from homeassistant.loader import bind_hass, async_get_integration
from homeassistant.util import slugify from homeassistant.util import slugify
from .entity_platform import EntityPlatform from .entity_platform import EntityPlatform
# mypy: allow-untyped-defs, no-check-untyped-defs
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15) DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
DATA_INSTANCES = 'entity_components' DATA_INSTANCES = 'entity_components'

View file

@ -9,6 +9,9 @@ from homeassistant.util.async_ import (
from .event import async_track_time_interval, async_call_later from .event import async_track_time_interval, async_call_later
# mypy: allow-untyped-defs, no-check-untyped-defs
SLOW_SETUP_WARNING = 10 SLOW_SETUP_WARNING = 10
SLOW_SETUP_MAX_WAIT = 60 SLOW_SETUP_MAX_WAIT = 60
PLATFORM_NOT_READY_RETRIES = 10 PLATFORM_NOT_READY_RETRIES = 10

View file

@ -23,6 +23,10 @@ from homeassistant.util.yaml import load_yaml
from .typing import HomeAssistantType from .typing import HomeAssistantType
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
PATH_REGISTRY = 'entity_registry.yaml' PATH_REGISTRY = 'entity_registry.yaml'
DATA_REGISTRY = 'entity_registry' DATA_REGISTRY = 'entity_registry'
EVENT_ENTITY_REGISTRY_UPDATED = 'entity_registry_updated' EVENT_ENTITY_REGISTRY_UPDATED = 'entity_registry_updated'
@ -48,7 +52,8 @@ class RegistryEntry:
config_entry_id = attr.ib(type=str, default=None) config_entry_id = attr.ib(type=str, default=None)
disabled_by = attr.ib( disabled_by = attr.ib(
type=str, default=None, type=str, default=None,
validator=attr.validators.in_((DISABLED_HASS, DISABLED_USER, None))) validator=attr.validators.in_((DISABLED_HASS, DISABLED_USER, None))
) # type: Optional[str]
domain = attr.ib(type=str, init=False, repr=False) domain = attr.ib(type=str, init=False, repr=False)
@domain.default @domain.default

View file

@ -7,13 +7,16 @@ import attr
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.helpers.sun import get_astral_event_next from homeassistant.helpers.sun import get_astral_event_next
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback, CALLBACK_TYPE
from homeassistant.const import ( from homeassistant.const import (
ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL,
SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, EVENT_CORE_CONFIG_UPDATE) SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, EVENT_CORE_CONFIG_UPDATE)
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.async_ import run_callback_threadsafe
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
# PyLint does not like the use of threaded_listener_factory # PyLint does not like the use of threaded_listener_factory
# pylint: disable=invalid-name # pylint: disable=invalid-name
@ -172,7 +175,7 @@ track_same_state = threaded_listener_factory(async_track_same_state)
@callback @callback
@bind_hass @bind_hass
def async_track_point_in_time(hass, action, point_in_time): def async_track_point_in_time(hass, action, point_in_time) -> CALLBACK_TYPE:
"""Add a listener that fires once after a specific point in time.""" """Add a listener that fires once after a specific point in time."""
utc_point_in_time = dt_util.as_utc(point_in_time) utc_point_in_time = dt_util.as_utc(point_in_time)
@ -190,7 +193,8 @@ track_point_in_time = threaded_listener_factory(async_track_point_in_time)
@callback @callback
@bind_hass @bind_hass
def async_track_point_in_utc_time(hass, action, point_in_time): def async_track_point_in_utc_time(
hass, action, point_in_time) -> CALLBACK_TYPE:
"""Add a listener that fires once after a specific point in UTC time.""" """Add a listener that fires once after a specific point in UTC time."""
# Ensure point_in_time is UTC # Ensure point_in_time is UTC
point_in_time = dt_util.as_utc(point_in_time) point_in_time = dt_util.as_utc(point_in_time)

View file

@ -3,6 +3,8 @@ import inspect
import logging import logging
# mypy: allow-untyped-defs, no-check-untyped-defs
class KeywordMessage: class KeywordMessage:
""" """
Represents a logging message with keyword arguments. Represents a logging message with keyword arguments.

View file

@ -15,6 +15,10 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.storage import Store # noqa pylint_disable=unused-import from homeassistant.helpers.storage import Store # noqa pylint_disable=unused-import
# mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs
# mypy: no-warn-return-any
DATA_RESTORE_STATE_TASK = 'restore_state_task' DATA_RESTORE_STATE_TASK = 'restore_state_task'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -182,8 +186,8 @@ class RestoreStateData():
# we're going to serialize it to JSON and then re-load it. # we're going to serialize it to JSON and then re-load it.
if state is not None: if state is not None:
state = State.from_dict(_encode_complex(state.as_dict())) state = State.from_dict(_encode_complex(state.as_dict()))
if state is not None:
self.last_states[entity_id] = StoredState(state, dt_util.utcnow()) self.last_states[entity_id] = StoredState(state, dt_util.utcnow())
self.entity_ids.remove(entity_id) self.entity_ids.remove(entity_id)
@ -219,6 +223,7 @@ class RestoreEntity(Entity):
async def async_internal_added_to_hass(self) -> None: async def async_internal_added_to_hass(self) -> None:
"""Register this entity as a restorable entity.""" """Register this entity as a restorable entity."""
assert self.hass is not None
_, data = await asyncio.gather( _, data = await asyncio.gather(
super().async_internal_added_to_hass(), super().async_internal_added_to_hass(),
RestoreStateData.async_get_instance(self.hass), RestoreStateData.async_get_instance(self.hass),
@ -227,6 +232,7 @@ class RestoreEntity(Entity):
async def async_internal_will_remove_from_hass(self) -> None: async def async_internal_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass.""" """Run when entity will be removed from hass."""
assert self.hass is not None
_, data = await asyncio.gather( _, data = await asyncio.gather(
super().async_internal_will_remove_from_hass(), super().async_internal_will_remove_from_hass(),
RestoreStateData.async_get_instance(self.hass), RestoreStateData.async_get_instance(self.hass),

View file

@ -2,12 +2,13 @@
import logging import logging
from contextlib import suppress from contextlib import suppress
from datetime import datetime
from itertools import islice from itertools import islice
from typing import Optional, Sequence from typing import Optional, Sequence, Callable, Dict, List, Set, Tuple
import voluptuous as vol import voluptuous as vol
from homeassistant.core import HomeAssistant, Context, callback from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
from homeassistant.const import CONF_CONDITION, CONF_TIMEOUT from homeassistant.const import CONF_CONDITION, CONF_TIMEOUT
from homeassistant import exceptions from homeassistant import exceptions
from homeassistant.helpers import ( from homeassistant.helpers import (
@ -20,6 +21,10 @@ import homeassistant.util.dt as date_util
from homeassistant.util.async_ import ( from homeassistant.util.async_ import (
run_coroutine_threadsafe, run_callback_threadsafe) run_coroutine_threadsafe, run_callback_threadsafe)
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ALIAS = 'alias' CONF_ALIAS = 'alias'
@ -76,7 +81,8 @@ class _SuspendScript(Exception):
class Script(): class Script():
"""Representation of a script.""" """Representation of a script."""
def __init__(self, hass: HomeAssistant, sequence, name: str = None, def __init__(self, hass: HomeAssistant, sequence,
name: Optional[str] = None,
change_listener=None) -> None: change_listener=None) -> None:
"""Initialize the script.""" """Initialize the script."""
self.hass = hass self.hass = hass
@ -85,14 +91,13 @@ class Script():
self.name = name self.name = name
self._change_listener = change_listener self._change_listener = change_listener
self._cur = -1 self._cur = -1
self._exception_step = None self._exception_step = None # type: Optional[int]
self.last_action = None self.last_action = None
self.last_triggered = None self.last_triggered = None # type: Optional[datetime]
self.can_cancel = any(CONF_DELAY in action or CONF_WAIT_TEMPLATE self.can_cancel = any(CONF_DELAY in action or CONF_WAIT_TEMPLATE
in action for action in self.sequence) in action for action in self.sequence)
self._async_listener = [] self._async_listener = [] # type: List[CALLBACK_TYPE]
self._template_cache = {} self._config_cache = {} # type: Dict[Set[Tuple], Callable[..., bool]]
self._config_cache = {}
self._actions = { self._actions = {
ACTION_DELAY: self._async_delay, ACTION_DELAY: self._async_delay,
ACTION_WAIT_TEMPLATE: self._async_wait_template, ACTION_WAIT_TEMPLATE: self._async_wait_template,

View file

@ -19,6 +19,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.util.async_ import run_coroutine_threadsafe
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
# mypy: allow-incomplete-defs, allow-untyped-defs, no-check-untyped-defs
CONF_SERVICE = 'service' CONF_SERVICE = 'service'
CONF_SERVICE_TEMPLATE = 'service_template' CONF_SERVICE_TEMPLATE = 'service_template'
CONF_SERVICE_ENTITY_ID = 'entity_id' CONF_SERVICE_ENTITY_ID = 'entity_id'

View file

@ -3,7 +3,7 @@ import asyncio
from json import JSONEncoder from json import JSONEncoder
import logging import logging
import os import os
from typing import Dict, List, Optional, Callable, Union from typing import Dict, List, Optional, Callable, Union, Any, Type
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback from homeassistant.core import callback
@ -11,6 +11,10 @@ from homeassistant.loader import bind_hass
from homeassistant.util import json as json_util from homeassistant.util import json as json_util
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-warn-return-any
STORAGE_DIR = '.storage' STORAGE_DIR = '.storage'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -48,17 +52,17 @@ class Store:
"""Class to help storing data.""" """Class to help storing data."""
def __init__(self, hass, version: int, key: str, private: bool = False, *, def __init__(self, hass, version: int, key: str, private: bool = False, *,
encoder: JSONEncoder = None): encoder: Optional[Type[JSONEncoder]] = None):
"""Initialize storage class.""" """Initialize storage class."""
self.version = version self.version = version
self.key = key self.key = key
self.hass = hass self.hass = hass
self._private = private self._private = private
self._data = None self._data = None # type: Optional[Dict[str, Any]]
self._unsub_delay_listener = None self._unsub_delay_listener = None
self._unsub_stop_listener = None self._unsub_stop_listener = None
self._write_lock = asyncio.Lock() self._write_lock = asyncio.Lock()
self._load_task = None self._load_task = None # type: Optional[asyncio.Future]
self._encoder = encoder self._encoder = encoder
@property @property

View file

@ -12,7 +12,7 @@ from typing import Iterable
import jinja2 import jinja2
from jinja2 import contextfilter, contextfunction from jinja2 import contextfilter, contextfunction
from jinja2.sandbox import ImmutableSandboxedEnvironment from jinja2.sandbox import ImmutableSandboxedEnvironment
from jinja2.utils import Namespace from jinja2.utils import Namespace # type: ignore
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_UNIT_OF_MEASUREMENT, ATTR_ENTITY_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_UNIT_OF_MEASUREMENT,
@ -26,6 +26,10 @@ from homeassistant.loader import bind_hass
from homeassistant.util import convert, dt as dt_util, location as loc_util from homeassistant.util import convert, dt as dt_util, location as loc_util
from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.async_ import run_callback_threadsafe
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_SENTINEL = object() _SENTINEL = object()
DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S" DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
@ -192,14 +196,13 @@ class Template:
This method must be run in the event loop. This method must be run in the event loop.
""" """
if self._compiled is None: compiled = self._compiled or self._ensure_compiled()
self._ensure_compiled()
if variables is not None: if variables is not None:
kwargs.update(variables) kwargs.update(variables)
try: try:
return self._compiled.render(kwargs).strip() return compiled.render(kwargs).strip()
except jinja2.TemplateError as err: except jinja2.TemplateError as err:
raise TemplateError(err) raise TemplateError(err)

View file

@ -1,6 +1,6 @@
"""JSON utility functions.""" """JSON utility functions."""
import logging import logging
from typing import Union, List, Dict, Optional from typing import Union, List, Dict, Optional, Type
import json import json
import os import os
@ -42,7 +42,7 @@ def load_json(filename: str, default: Union[List, Dict, None] = None) \
def save_json(filename: str, data: Union[List, Dict], def save_json(filename: str, data: Union[List, Dict],
private: bool = False, *, private: bool = False, *,
encoder: Optional[json.JSONEncoder] = None) -> None: encoder: Optional[Type[json.JSONEncoder]] = None) -> None:
"""Save JSON data to a file. """Save JSON data to a file.
Returns True on success. Returns True on success.

19
mypyrc
View file

@ -1,21 +1,4 @@
homeassistant/*.py homeassistant/*.py
homeassistant/auth/ homeassistant/auth/
homeassistant/helpers/
homeassistant/util/ homeassistant/util/
homeassistant/helpers/__init__.py
homeassistant/helpers/aiohttp_client.py
homeassistant/helpers/area_registry.py
homeassistant/helpers/condition.py
homeassistant/helpers/deprecation.py
homeassistant/helpers/dispatcher.py
homeassistant/helpers/entity_values.py
homeassistant/helpers/entityfilter.py
homeassistant/helpers/icon.py
homeassistant/helpers/intent.py
homeassistant/helpers/json.py
homeassistant/helpers/location.py
homeassistant/helpers/signal.py
homeassistant/helpers/state.py
homeassistant/helpers/sun.py
homeassistant/helpers/temperature.py
homeassistant/helpers/translation.py
homeassistant/helpers/typing.py