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
This commit is contained in:
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 env: TOXENV=requirements
- python: "3.5" - python: "3.5"
env: TOXENV=lint env: TOXENV=lint
- python: "3.5"
env: TOXENV=typing
- python: "3.5" - python: "3.5"
env: TOXENV=py35 env: TOXENV=py35
allow_failures:
- python: "3.5"
env: TOXENV=typing
cache: cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip

View file

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

View file

@ -6,9 +6,11 @@ import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from threading import RLock from threading import RLock
from typing import Any, Optional, Dict
import voluptuous as vol import voluptuous as vol
import homeassistant.components as core_components import homeassistant.components as core_components
from homeassistant.components import group, persistent_notification from homeassistant.components import group, persistent_notification
import homeassistant.config as conf_util 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 # pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True, def from_config_dict(config: Dict[str, Any],
verbose=False, skip_pip=False, hass: Optional[core.HomeAssistant]=None,
log_rotate_days=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. """Try to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies. 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 return hass
def from_config_file(config_path, hass=None, verbose=False, skip_pip=True, def from_config_file(config_path: str,
log_rotate_days=None): 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. """Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given, 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.""" """Put together the default configuration directory based on 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)
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. """Ensure a config file exists in given configuration directory.
Creating a default one if needed. Creating a default one if needed.

View file

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

View file

@ -3,7 +3,7 @@ import importlib
import os import os
def run(args): def run(args: str) -> int:
"""Run a script.""" """Run a script."""
scripts = [fil[:-3] for fil in os.listdir(os.path.dirname(__file__)) scripts = [fil[:-3] for fil in os.listdir(os.path.dirname(__file__))
if fil.endswith('.py') and fil != '__init__.py'] if fil.endswith('.py') and fil != '__init__.py']
@ -19,4 +19,4 @@ def run(args):
return 1 return 1
script = importlib.import_module('homeassistant.scripts.' + args[0]) 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 pip>=7.0.0
jinja2>=2.8 jinja2>=2.8
voluptuous==0.8.9 voluptuous==0.8.9
typing>=3,<4
sqlalchemy==1.0.14 sqlalchemy==1.0.14
# homeassistant.components.isy994 # homeassistant.components.isy994

View file

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

View file

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

View file

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