Bring back typing check. Meanwhile just for homeassistant/*.py (#14410)

* Bring back typing check. Meanwhile just for homeassistant/.py

* Change follow-imports to silent. Add a few more checks.
This commit is contained in:
Andrey 2018-05-13 00:44:53 +03:00 committed by Paulus Schoutsen
parent 70af7e5fad
commit 7aec098a05
11 changed files with 41 additions and 30 deletions

View File

@ -10,8 +10,8 @@ matrix:
env: TOXENV=lint env: TOXENV=lint
- python: "3.5.3" - python: "3.5.3"
env: TOXENV=pylint env: TOXENV=pylint
# - python: "3.5" - python: "3.5.3"
# env: TOXENV=typing env: TOXENV=typing
- python: "3.5.3" - python: "3.5.3"
env: TOXENV=py35 env: TOXENV=py35
- python: "3.6" - python: "3.6"

View File

@ -8,7 +8,8 @@ import subprocess
import sys import sys
import threading import threading
from typing import Optional, List from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import
from homeassistant import monkey_patch from homeassistant import monkey_patch
from homeassistant.const import ( from homeassistant.const import (
@ -259,7 +260,7 @@ def setup_and_run_hass(config_dir: str,
config = { config = {
'frontend': {}, 'frontend': {},
'demo': {} 'demo': {}
} } # type: Dict[str, Any]
hass = bootstrap.from_config_dict( hass = bootstrap.from_config_dict(
config, config_dir=config_dir, verbose=args.verbose, config, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days, skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days,

View File

@ -35,7 +35,7 @@ ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
DATA_REQS = 'auth_reqs_processed' DATA_REQS = 'auth_reqs_processed'
def generate_secret(entropy=32): def generate_secret(entropy: int = 32) -> str:
"""Generate a secret. """Generate a secret.
Backport of secrets.token_hex from Python 3.6 Backport of secrets.token_hex from Python 3.6

View File

@ -278,7 +278,8 @@ def async_enable_logging(hass: core.HomeAssistant,
if log_rotate_days: if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler( err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when='midnight', backupCount=log_rotate_days) err_log_path, when='midnight',
backupCount=log_rotate_days) # type: logging.FileHandler
else: else:
err_handler = logging.FileHandler( err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True) err_log_path, mode='w', delay=True)
@ -297,7 +298,7 @@ def async_enable_logging(hass: core.HomeAssistant,
EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler)
logger = logging.getLogger('') logger = logging.getLogger('')
logger.addHandler(async_handler) logger.addHandler(async_handler) # type: ignore
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# Save the log file location for access by other components. # Save the log file location for access by other components.

View File

@ -7,7 +7,7 @@ import os
import re import re
import shutil import shutil
# pylint: disable=unused-import # pylint: disable=unused-import
from typing import Any, List, Tuple # NOQA from typing import Any, List, Tuple, Optional # NOQA
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
@ -60,7 +60,7 @@ DEFAULT_CORE_CONFIG = (
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
'pedia.org/wiki/List_of_tz_database_time_zones'), 'pedia.org/wiki/List_of_tz_database_time_zones'),
(CONF_CUSTOMIZE, '!include customize.yaml', None, 'Customization file'), (CONF_CUSTOMIZE, '!include customize.yaml', None, 'Customization file'),
) # type: Tuple[Tuple[str, Any, Any, str], ...] ) # type: Tuple[Tuple[str, Any, Any, Optional[str]], ...]
DEFAULT_CONFIG = """ DEFAULT_CONFIG = """
# Show links to resources in log and frontend # Show links to resources in log and frontend
introduction: introduction:
@ -167,7 +167,7 @@ def get_default_config_dir() -> str:
"""Put together the default configuration directory based on the OS.""" """Put together the default configuration directory based on the OS."""
data_dir = os.getenv('APPDATA') if os.name == "nt" \ data_dir = os.getenv('APPDATA') if os.name == "nt" \
else os.path.expanduser('~') else os.path.expanduser('~')
return os.path.join(data_dir, CONFIG_DIR_NAME) return os.path.join(data_dir, CONFIG_DIR_NAME) # type: ignore
def ensure_config_exists(config_dir: str, detect_location: bool = True) -> str: def ensure_config_exists(config_dir: str, detect_location: bool = True) -> str:

View File

@ -17,7 +17,7 @@ import threading
from time import monotonic from time import monotonic
from types import MappingProxyType from types import MappingProxyType
from typing import Optional, Any, Callable, List # NOQA from typing import Optional, Any, Callable, List, TypeVar, Dict # NOQA
from async_timeout import timeout from async_timeout import timeout
import voluptuous as vol import voluptuous as vol
@ -41,6 +41,8 @@ import homeassistant.util.dt as dt_util
import homeassistant.util.location as location import homeassistant.util.location as location
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
T = TypeVar('T')
DOMAIN = 'homeassistant' DOMAIN = 'homeassistant'
# How long we wait for the result of a service call # How long we wait for the result of a service call
@ -70,16 +72,15 @@ def valid_state(state: str) -> bool:
return len(state) < 256 return len(state) < 256
def callback(func: Callable[..., None]) -> Callable[..., None]: def callback(func: Callable[..., T]) -> Callable[..., T]:
"""Annotation to mark method as safe to call from within the event loop.""" """Annotation to mark method as safe to call from within the event loop."""
# pylint: disable=protected-access setattr(func, '_hass_callback', True)
func._hass_callback = True
return func return func
def is_callback(func: Callable[..., Any]) -> bool: def is_callback(func: Callable[..., Any]) -> bool:
"""Check if function is safe to be called in the event loop.""" """Check if function is safe to be called in the event loop."""
return '_hass_callback' in getattr(func, '__dict__', {}) return getattr(func, '_hass_callback', False) is True
@callback @callback
@ -136,13 +137,14 @@ class HomeAssistant(object):
self.data = {} self.data = {}
self.state = CoreState.not_running self.state = CoreState.not_running
self.exit_code = None self.exit_code = None
self.config_entries = None
@property @property
def is_running(self) -> bool: def is_running(self) -> bool:
"""Return if Home Assistant is running.""" """Return if Home Assistant is running."""
return self.state in (CoreState.starting, CoreState.running) return self.state in (CoreState.starting, CoreState.running)
def start(self) -> None: def start(self) -> int:
"""Start home assistant.""" """Start home assistant."""
# Register the async start # Register the async start
fire_coroutine_threadsafe(self.async_start(), self.loop) fire_coroutine_threadsafe(self.async_start(), self.loop)
@ -152,13 +154,13 @@ class HomeAssistant(object):
# Block until stopped # Block until stopped
_LOGGER.info("Starting Home Assistant core loop") _LOGGER.info("Starting Home Assistant core loop")
self.loop.run_forever() self.loop.run_forever()
return self.exit_code
except KeyboardInterrupt: except KeyboardInterrupt:
self.loop.call_soon_threadsafe( self.loop.call_soon_threadsafe(
self.loop.create_task, self.async_stop()) self.loop.create_task, self.async_stop())
self.loop.run_forever() self.loop.run_forever()
finally: finally:
self.loop.close() self.loop.close()
return self.exit_code
async def async_start(self): async def async_start(self):
"""Finalize startup from inside the event loop. """Finalize startup from inside the event loop.
@ -200,7 +202,10 @@ class HomeAssistant(object):
self.loop.call_soon_threadsafe(self.async_add_job, target, *args) self.loop.call_soon_threadsafe(self.async_add_job, target, *args)
@callback @callback
def async_add_job(self, target: Callable[..., None], *args: Any) -> None: def async_add_job(
self,
target: Callable[..., Any],
*args: Any) -> Optional[asyncio.tasks.Task]:
"""Add a job from within the eventloop. """Add a job from within the eventloop.
This method must be run in the event loop. This method must be run in the event loop.
@ -354,7 +359,7 @@ class EventBus(object):
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize a new event bus.""" """Initialize a new event bus."""
self._listeners = {} self._listeners = {} # type: Dict[str, List[Callable]]
self._hass = hass self._hass = hass
@callback @callback
@ -1039,7 +1044,7 @@ class Config(object):
# List of allowed external dirs to access # List of allowed external dirs to access
self.whitelist_external_dirs = set() self.whitelist_external_dirs = set()
def distance(self: object, lat: float, lon: float) -> float: def distance(self, lat: float, lon: float) -> float:
"""Calculate distance from Home Assistant. """Calculate distance from Home Assistant.
Async friendly. Async friendly.

View File

@ -1,4 +1,5 @@
"""The exceptions used by Home Assistant.""" """The exceptions used by Home Assistant."""
import jinja2
class HomeAssistantError(Exception): class HomeAssistantError(Exception):
@ -22,7 +23,7 @@ class NoEntitySpecifiedError(HomeAssistantError):
class TemplateError(HomeAssistantError): class TemplateError(HomeAssistantError):
"""Error during template rendering.""" """Error during template rendering."""
def __init__(self, exception): def __init__(self, exception: jinja2.TemplateError) -> None:
"""Init the error.""" """Init the error."""
super().__init__('{}: {}'.format(exception.__class__.__name__, super().__init__('{}: {}'.format(exception.__class__.__name__,
exception)) exception))

View File

@ -93,7 +93,7 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]:
# This prevents that when only # This prevents that when only
# custom_components/switch/some_platform.py exists, # custom_components/switch/some_platform.py exists,
# the import custom_components.switch would succeed. # the import custom_components.switch would succeed.
if module.__spec__.origin == 'namespace': if module.__spec__ and module.__spec__.origin == 'namespace':
continue continue
_LOGGER.info("Loaded %s from %s", comp_or_platform, path) _LOGGER.info("Loaded %s from %s", comp_or_platform, path)

View File

@ -139,10 +139,11 @@ async def _async_setup_component(hass: core.HomeAssistant,
try: try:
if hasattr(component, 'async_setup'): if hasattr(component, 'async_setup'):
result = await component.async_setup(hass, processed_config) result = await component.async_setup( # type: ignore
hass, processed_config)
else: else:
result = await hass.async_add_job( result = await hass.async_add_job(
component.setup, hass, processed_config) component.setup, hass, processed_config) # type: ignore
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error during setup of component %s", domain) _LOGGER.exception("Error during setup of component %s", domain)
async_notify_setup_error(hass, domain, True) async_notify_setup_error(hass, domain, True)
@ -165,14 +166,15 @@ async def _async_setup_component(hass: core.HomeAssistant,
for entry in hass.config_entries.async_entries(domain): for entry in hass.config_entries.async_entries(domain):
await entry.async_setup(hass, component=component) await entry.async_setup(hass, component=component)
hass.config.components.add(component.DOMAIN) hass.config.components.add(component.DOMAIN) # type: ignore
# Cleanup # Cleanup
if domain in hass.data[DATA_SETUP]: if domain in hass.data[DATA_SETUP]:
hass.data[DATA_SETUP].pop(domain) hass.data[DATA_SETUP].pop(domain)
hass.bus.async_fire( hass.bus.async_fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} EVENT_COMPONENT_LOADED,
{ATTR_COMPONENT: component.DOMAIN} # type: ignore
) )
return True return True

View File

@ -24,7 +24,7 @@ logging.basicConfig(level=logging.INFO)
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
def test_real(func): def check_real(func):
"""Force a function to require a keyword _test_real to be passed in.""" """Force a function to require a keyword _test_real to be passed in."""
@functools.wraps(func) @functools.wraps(func)
def guard_func(*args, **kwargs): def guard_func(*args, **kwargs):
@ -40,8 +40,8 @@ def test_real(func):
# Guard a few functions that would make network connections # Guard a few functions that would make network connections
location.detect_location_info = test_real(location.detect_location_info) location.detect_location_info = check_real(location.detect_location_info)
location.elevation = test_real(location.elevation) location.elevation = check_real(location.elevation)
util.get_local_ip = lambda: '127.0.0.1' util.get_local_ip = lambda: '127.0.0.1'

View File

@ -38,7 +38,8 @@ commands =
[testenv:typing] [testenv:typing]
basepython = {env:PYTHON3_PATH:python3} basepython = {env:PYTHON3_PATH:python3}
whitelist_externals=/bin/bash
deps = deps =
-r{toxinidir}/requirements_test.txt -r{toxinidir}/requirements_test.txt
commands = commands =
mypy --ignore-missing-imports --follow-imports=skip homeassistant /bin/bash -c 'mypy --ignore-missing-imports --follow-imports=silent homeassistant/*.py'