Type Hints - __main__ (#2574)

* Add __main__ type hints

* Fix most errors of __main__

* Add ignore for script.run()

* Add type annotations for from_config_dict and from_config_file

* Fix errors

* Fix requirement error

* Add mypy type check to tests

* Enable travis typing check

* Messed up the tox deps

* Laxer type checker
pull/2584/head
Fabian Heredia Montiel 2016-07-21 00:38:52 -05:00 committed by Paulus Schoutsen
parent d570d38d5c
commit 08226a4864
10 changed files with 58 additions and 32 deletions

View File

@ -8,8 +8,13 @@ matrix:
env: TOXENV=requirements
- python: "3.5"
env: TOXENV=lint
- python: "3.5"
env: TOXENV=typing
- python: "3.5"
env: TOXENV=py35
allow_failures:
- python: "3.5"
env: TOXENV=typing
cache:
directories:
- $HOME/.cache/pip

View File

@ -8,6 +8,8 @@ import subprocess
import sys
import threading
from typing import Optional, List
from homeassistant.const import (
__version__,
EVENT_HOMEASSISTANT_START,
@ -16,7 +18,7 @@ from homeassistant.const import (
)
def validate_python():
def validate_python() -> None:
"""Validate we're running the right Python version."""
major, minor = sys.version_info[:2]
req_major, req_minor = REQUIRED_PYTHON_VER
@ -27,7 +29,7 @@ def validate_python():
sys.exit(1)
def ensure_config_path(config_dir):
def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
import homeassistant.config as config_util
lib_dir = os.path.join(config_dir, 'deps')
@ -56,7 +58,7 @@ def ensure_config_path(config_dir):
sys.exit(1)
def ensure_config_file(config_dir):
def ensure_config_file(config_dir: str) -> str:
"""Ensure configuration file exists."""
import homeassistant.config as config_util
config_path = config_util.ensure_config_exists(config_dir)
@ -68,7 +70,7 @@ def ensure_config_file(config_dir):
return config_path
def get_arguments():
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
import homeassistant.config as config_util
parser = argparse.ArgumentParser(
@ -125,12 +127,12 @@ def get_arguments():
arguments = parser.parse_args()
if os.name != "posix" or arguments.debug or arguments.runner:
arguments.daemon = False
setattr(arguments, 'daemon', False)
return arguments
def daemonize():
def daemonize() -> None:
"""Move current process to daemon process."""
# Create first fork
pid = os.fork()
@ -155,7 +157,7 @@ def daemonize():
os.dup2(outfd.fileno(), sys.stderr.fileno())
def check_pid(pid_file):
def check_pid(pid_file: str) -> None:
"""Check that HA is not already running."""
# Check pid file
try:
@ -177,7 +179,7 @@ def check_pid(pid_file):
sys.exit(1)
def write_pid(pid_file):
def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
@ -187,7 +189,7 @@ def write_pid(pid_file):
sys.exit(1)
def closefds_osx(min_fd, max_fd):
def closefds_osx(min_fd: int, max_fd: int) -> None:
"""Make sure file descriptors get closed when we restart.
We cannot call close on guarded fds, and we cannot easily test which fds
@ -205,7 +207,7 @@ def closefds_osx(min_fd, max_fd):
pass
def cmdline():
def cmdline() -> List[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if sys.argv[0].endswith('/__main__.py'):
modulepath = os.path.dirname(sys.argv[0])
@ -213,16 +215,17 @@ def cmdline():
return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon']
def setup_and_run_hass(config_dir, args):
def setup_and_run_hass(config_dir: str,
args: argparse.Namespace) -> Optional[int]:
"""Setup HASS and run."""
from homeassistant import bootstrap
# Run a simple daemon runner process on Windows to handle restarts
if os.name == 'nt' and '--runner' not in sys.argv:
args = cmdline() + ['--runner']
nt_args = cmdline() + ['--runner']
while True:
try:
subprocess.check_call(args)
subprocess.check_call(nt_args)
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
@ -244,7 +247,7 @@ def setup_and_run_hass(config_dir, args):
log_rotate_days=args.log_rotate_days)
if hass is None:
return
return None
if args.open_ui:
def open_browser(event):
@ -261,7 +264,7 @@ def setup_and_run_hass(config_dir, args):
return exit_code
def try_to_restart():
def try_to_restart() -> None:
"""Attempt to clean up state and start a new homeassistant instance."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
@ -303,7 +306,7 @@ def try_to_restart():
os.execv(args[0], args)
def main():
def main() -> int:
"""Start Home Assistant."""
validate_python()

View File

@ -6,9 +6,11 @@ import os
import sys
from collections import defaultdict
from threading import RLock
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
@ -202,9 +204,14 @@ def prepare_setup_platform(hass, config, domain, platform_name):
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, skip_pip=False,
log_rotate_days=None):
def from_config_dict(config: Dict[str, Any],
hass: Optional[core.HomeAssistant]=None,
config_dir: Optional[str]=None,
enable_log: bool=True,
verbose: bool=False,
skip_pip: bool=False,
log_rotate_days: Any=None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies.
@ -266,8 +273,11 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
return hass
def from_config_file(config_path, hass=None, verbose=False, skip_pip=True,
log_rotate_days=None):
def from_config_file(config_path: str,
hass: Optional[core.HomeAssistant]=None,
verbose: bool=False,
skip_pip: bool=True,
log_rotate_days: Any=None):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given,

View File

@ -73,14 +73,14 @@ CORE_CONFIG_SCHEMA = vol.Schema({
})
def get_default_config_dir():
def get_default_config_dir() -> str:
"""Put together the default configuration directory based on OS."""
data_dir = os.getenv('APPDATA') if os.name == "nt" \
else os.path.expanduser('~')
return os.path.join(data_dir, CONFIG_DIR_NAME)
def ensure_config_exists(config_dir, detect_location=True):
def ensure_config_exists(config_dir: str, detect_location: bool=True) -> str:
"""Ensure a config file exists in given configuration directory.
Creating a default one if needed.

View File

@ -57,7 +57,7 @@ class CoreState(enum.Enum):
running = "RUNNING"
stopping = "STOPPING"
def __str__(self):
def __str__(self) -> str:
"""Return the event."""
return self.value
@ -75,11 +75,11 @@ class HomeAssistant(object):
self.state = CoreState.not_running
@property
def is_running(self):
def is_running(self) -> bool:
"""Return if Home Assistant is running."""
return self.state == CoreState.running
def start(self):
def start(self) -> None:
"""Start home assistant."""
_LOGGER.info(
"Starting Home Assistant (%d threads)", self.pool.worker_count)
@ -90,7 +90,7 @@ class HomeAssistant(object):
self.pool.block_till_done()
self.state = CoreState.running
def block_till_stopped(self):
def block_till_stopped(self) -> int:
"""Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event()
request_restart = threading.Event()
@ -132,7 +132,7 @@ class HomeAssistant(object):
return RESTART_EXIT_CODE if request_restart.isSet() else 0
def stop(self):
def stop(self) -> None:
"""Stop Home Assistant and shuts down all threads."""
_LOGGER.info("Stopping")
self.state = CoreState.stopping
@ -290,7 +290,7 @@ class EventBus(object):
# available to execute this listener it might occur that the
# listener gets lined up twice to be executed.
# This will make sure the second time it does nothing.
onetime_listener.run = True
setattr(onetime_listener, 'run', True)
self.remove_listener(event_type, onetime_listener)

View File

@ -3,7 +3,7 @@ import importlib
import os
def run(args):
def run(args: str) -> int:
"""Run a script."""
scripts = [fil[:-3] for fil in os.listdir(os.path.dirname(__file__))
if fil.endswith('.py') and fil != '__init__.py']
@ -19,4 +19,4 @@ def run(args):
return 1
script = importlib.import_module('homeassistant.scripts.' + args[0])
return script.run(args[1:])
return script.run(args[1:]) # type: ignore

View File

@ -5,6 +5,7 @@ pytz>=2016.6.1
pip>=7.0.0
jinja2>=2.8
voluptuous==0.8.9
typing>=3,<4
sqlalchemy==1.0.14
# homeassistant.components.isy994

View File

@ -7,3 +7,4 @@ pytest-timeout>=1.0.0
pytest-capturelog>=0.7
pydocstyle>=1.0.0
requests_mock>=1.0
mypy-lang>=0.4

View File

@ -17,6 +17,7 @@ REQUIRES = [
'pip>=7.0.0',
'jinja2>=2.8',
'voluptuous==0.8.9',
'typing>=3,<4',
'sqlalchemy==1.0.14',
]

View File

@ -1,5 +1,5 @@
[tox]
envlist = py34, py35, lint, requirements
envlist = py34, py35, lint, requirements, typing
skip_missing_interpreters = True
[testenv]
@ -29,3 +29,8 @@ basepython = python3
deps =
commands =
python script/gen_requirements_all.py validate
[testenv:typing]
basepython = python3
commands =
mypy --silent-imports homeassistant