From 7aec098a05bf424f06988aa2d4fbeac0eb41269f Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 13 May 2018 00:44:53 +0300 Subject: [PATCH] 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. --- .travis.yml | 4 ++-- homeassistant/__main__.py | 5 +++-- homeassistant/auth.py | 2 +- homeassistant/bootstrap.py | 5 +++-- homeassistant/config.py | 6 +++--- homeassistant/core.py | 25 +++++++++++++++---------- homeassistant/exceptions.py | 3 ++- homeassistant/loader.py | 2 +- homeassistant/setup.py | 10 ++++++---- tests/conftest.py | 6 +++--- tox.ini | 3 ++- 11 files changed, 41 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf2d05bb185..b089d3f89be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ matrix: env: TOXENV=lint - python: "3.5.3" env: TOXENV=pylint - # - python: "3.5" - # env: TOXENV=typing + - python: "3.5.3" + env: TOXENV=typing - python: "3.5.3" env: TOXENV=py35 - python: "3.6" diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index deb1746c167..7d3d2d2af88 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -8,7 +8,8 @@ import subprocess import sys 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.const import ( @@ -259,7 +260,7 @@ def setup_and_run_hass(config_dir: str, config = { 'frontend': {}, 'demo': {} - } + } # type: Dict[str, Any] hass = bootstrap.from_config_dict( config, config_dir=config_dir, verbose=args.verbose, skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days, diff --git a/homeassistant/auth.py b/homeassistant/auth.py index 2c6c95f9b42..7c01776b7b1 100644 --- a/homeassistant/auth.py +++ b/homeassistant/auth.py @@ -35,7 +35,7 @@ ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30) DATA_REQS = 'auth_reqs_processed' -def generate_secret(entropy=32): +def generate_secret(entropy: int = 32) -> str: """Generate a secret. Backport of secrets.token_hex from Python 3.6 diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 826cc563e82..a405362d368 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -278,7 +278,8 @@ def async_enable_logging(hass: core.HomeAssistant, if log_rotate_days: 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: err_handler = logging.FileHandler( 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) logger = logging.getLogger('') - logger.addHandler(async_handler) + logger.addHandler(async_handler) # type: ignore logger.setLevel(logging.INFO) # Save the log file location for access by other components. diff --git a/homeassistant/config.py b/homeassistant/config.py index 5c432490f6a..2f916e69b76 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,7 +7,7 @@ import os import re import shutil # pylint: disable=unused-import -from typing import Any, List, Tuple # NOQA +from typing import Any, List, Tuple, Optional # NOQA import voluptuous as vol 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' 'pedia.org/wiki/List_of_tz_database_time_zones'), (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 = """ # Show links to resources in log and frontend introduction: @@ -167,7 +167,7 @@ def get_default_config_dir() -> str: """Put together the default configuration directory based on the OS.""" data_dir = os.getenv('APPDATA') if os.name == "nt" \ 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: diff --git a/homeassistant/core.py b/homeassistant/core.py index feb8d331ae8..bc3b598180c 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -17,7 +17,7 @@ import threading from time import monotonic 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 import voluptuous as vol @@ -41,6 +41,8 @@ import homeassistant.util.dt as dt_util import homeassistant.util.location as location from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA +T = TypeVar('T') + DOMAIN = 'homeassistant' # 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 -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.""" - # pylint: disable=protected-access - func._hass_callback = True + setattr(func, '_hass_callback', True) return func def is_callback(func: Callable[..., Any]) -> bool: """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 @@ -136,13 +137,14 @@ class HomeAssistant(object): self.data = {} self.state = CoreState.not_running self.exit_code = None + self.config_entries = None @property def is_running(self) -> bool: """Return if Home Assistant is running.""" return self.state in (CoreState.starting, CoreState.running) - def start(self) -> None: + def start(self) -> int: """Start home assistant.""" # Register the async start fire_coroutine_threadsafe(self.async_start(), self.loop) @@ -152,13 +154,13 @@ class HomeAssistant(object): # Block until stopped _LOGGER.info("Starting Home Assistant core loop") self.loop.run_forever() - return self.exit_code except KeyboardInterrupt: self.loop.call_soon_threadsafe( self.loop.create_task, self.async_stop()) self.loop.run_forever() finally: self.loop.close() + return self.exit_code async def async_start(self): """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) @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. This method must be run in the event loop. @@ -354,7 +359,7 @@ class EventBus(object): def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" - self._listeners = {} + self._listeners = {} # type: Dict[str, List[Callable]] self._hass = hass @callback @@ -1039,7 +1044,7 @@ class Config(object): # List of allowed external dirs to access 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. Async friendly. diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index cb8a3c87820..73bd2377950 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,4 +1,5 @@ """The exceptions used by Home Assistant.""" +import jinja2 class HomeAssistantError(Exception): @@ -22,7 +23,7 @@ class NoEntitySpecifiedError(HomeAssistantError): class TemplateError(HomeAssistantError): """Error during template rendering.""" - def __init__(self, exception): + def __init__(self, exception: jinja2.TemplateError) -> None: """Init the error.""" super().__init__('{}: {}'.format(exception.__class__.__name__, exception)) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 67647a323c9..ce93c8705b5 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -93,7 +93,7 @@ def get_component(hass, comp_or_platform) -> Optional[ModuleType]: # This prevents that when only # custom_components/switch/some_platform.py exists, # the import custom_components.switch would succeed. - if module.__spec__.origin == 'namespace': + if module.__spec__ and module.__spec__.origin == 'namespace': continue _LOGGER.info("Loaded %s from %s", comp_or_platform, path) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index f26aa9b61f1..1664653f2a7 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -139,10 +139,11 @@ async def _async_setup_component(hass: core.HomeAssistant, try: if hasattr(component, 'async_setup'): - result = await component.async_setup(hass, processed_config) + result = await component.async_setup( # type: ignore + hass, processed_config) else: result = await hass.async_add_job( - component.setup, hass, processed_config) + component.setup, hass, processed_config) # type: ignore except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) 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): await entry.async_setup(hass, component=component) - hass.config.components.add(component.DOMAIN) + hass.config.components.add(component.DOMAIN) # type: ignore # Cleanup if domain in hass.data[DATA_SETUP]: hass.data[DATA_SETUP].pop(domain) hass.bus.async_fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} + EVENT_COMPONENT_LOADED, + {ATTR_COMPONENT: component.DOMAIN} # type: ignore ) return True diff --git a/tests/conftest.py b/tests/conftest.py index 73e69605eae..4d619c5ef61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,7 +24,7 @@ logging.basicConfig(level=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.""" @functools.wraps(func) def guard_func(*args, **kwargs): @@ -40,8 +40,8 @@ def test_real(func): # Guard a few functions that would make network connections -location.detect_location_info = test_real(location.detect_location_info) -location.elevation = test_real(location.elevation) +location.detect_location_info = check_real(location.detect_location_info) +location.elevation = check_real(location.elevation) util.get_local_ip = lambda: '127.0.0.1' diff --git a/tox.ini b/tox.ini index fb1f7c8bda3..d4bea81a2f5 100644 --- a/tox.ini +++ b/tox.ini @@ -38,7 +38,8 @@ commands = [testenv:typing] basepython = {env:PYTHON3_PATH:python3} +whitelist_externals=/bin/bash deps = -r{toxinidir}/requirements_test.txt commands = - mypy --ignore-missing-imports --follow-imports=skip homeassistant + /bin/bash -c 'mypy --ignore-missing-imports --follow-imports=silent homeassistant/*.py'