Merge branch 'pep257-utils' into dev

This commit is contained in:
Fabian Affolter 2016-03-08 08:34:50 +01:00
commit 7f1e181c9b
22 changed files with 301 additions and 333 deletions

View File

@ -0,0 +1 @@
"""Init file for Home Assistant."""

View File

@ -1,4 +1,4 @@
""" Starts home assistant. """ """Starts home assistant."""
from __future__ import print_function from __future__ import print_function
import argparse import argparse
@ -20,7 +20,7 @@ from homeassistant.const import (
def validate_python(): def validate_python():
""" 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
@ -31,8 +31,7 @@ def validate_python():
def ensure_config_path(config_dir): def ensure_config_path(config_dir):
""" Validates configuration directory. """ """Validate the configuration directory."""
lib_dir = os.path.join(config_dir, 'lib') lib_dir = os.path.join(config_dir, 'lib')
# Test if configuration directory exists # Test if configuration directory exists
@ -60,7 +59,7 @@ def ensure_config_path(config_dir):
def ensure_config_file(config_dir): def ensure_config_file(config_dir):
""" Ensure configuration file exists. """ """Ensure configuration file exists."""
config_path = config_util.ensure_config_exists(config_dir) config_path = config_util.ensure_config_exists(config_dir)
if config_path is None: if config_path is None:
@ -71,7 +70,7 @@ def ensure_config_file(config_dir):
def get_arguments(): def get_arguments():
""" Get parsed passed in arguments. """ """Get parsed passed in arguments."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.") description="Home Assistant: Observe, Control, Automate.")
parser.add_argument('--version', action='version', version=__version__) parser.add_argument('--version', action='version', version=__version__)
@ -136,25 +135,25 @@ def get_arguments():
def daemonize(): def daemonize():
""" Move current process to daemon process """ """Move current process to daemon process."""
# create first fork # Create first fork
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
sys.exit(0) sys.exit(0)
# decouple fork # Decouple fork
os.setsid() os.setsid()
os.umask(0) os.umask(0)
# create second fork # Create second fork
pid = os.fork() pid = os.fork()
if pid > 0: if pid > 0:
sys.exit(0) sys.exit(0)
def check_pid(pid_file): def check_pid(pid_file):
""" Check that HA is not already running """ """Check that HA is not already running."""
# check pid file # Check pid file
try: try:
pid = int(open(pid_file, 'r').readline()) pid = int(open(pid_file, 'r').readline())
except IOError: except IOError:
@ -171,7 +170,7 @@ def check_pid(pid_file):
def write_pid(pid_file): def write_pid(pid_file):
""" Create PID File """ """Create a PID File."""
pid = os.getpid() pid = os.getpid()
try: try:
open(pid_file, 'w').write(str(pid)) open(pid_file, 'w').write(str(pid))
@ -181,7 +180,7 @@ def write_pid(pid_file):
def install_osx(): def install_osx():
""" Setup to run via launchd on OS X """ """Setup to run via launchd on OS X."""
with os.popen('which hass') as inp: with os.popen('which hass') as inp:
hass_path = inp.read().strip() hass_path = inp.read().strip()
@ -213,7 +212,7 @@ def install_osx():
def uninstall_osx(): def uninstall_osx():
""" Unload from launchd on OS X """ """Unload from launchd on OS X."""
path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist") path = os.path.expanduser("~/Library/LaunchAgents/org.homeassistant.plist")
os.popen('launchctl unload ' + path) os.popen('launchctl unload ' + path)
@ -221,9 +220,10 @@ def uninstall_osx():
def setup_and_run_hass(config_dir, args, top_process=False): def setup_and_run_hass(config_dir, args, top_process=False):
""" """Setup HASS and run.
Setup HASS and run. Block until stopped. Will assume it is running in a
subprocess unless top_process is set to true. Block until stopped. Will assume it is running in a subprocess unless
top_process is set to true.
""" """
if args.demo_mode: if args.demo_mode:
config = { config = {
@ -243,7 +243,7 @@ def setup_and_run_hass(config_dir, args, top_process=False):
if args.open_ui: if args.open_ui:
def open_browser(event): def open_browser(event):
""" Open the webinterface in a browser. """ """Open the webinterface in a browser."""
if hass.config.api is not None: if hass.config.api is not None:
import webbrowser import webbrowser
webbrowser.open(hass.config.api.base_url) webbrowser.open(hass.config.api.base_url)
@ -259,12 +259,12 @@ def setup_and_run_hass(config_dir, args, top_process=False):
def run_hass_process(hass_proc): def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """ """Run a child hass process. Returns True if it should be restarted."""
requested_stop = threading.Event() requested_stop = threading.Event()
hass_proc.daemon = True hass_proc.daemon = True
def request_stop(*args): def request_stop(*args):
""" request hass stop, *args is for signal handler callback """ """Request hass stop, *args is for signal handler callback."""
requested_stop.set() requested_stop.set()
hass_proc.terminate() hass_proc.terminate()
@ -289,7 +289,7 @@ def run_hass_process(hass_proc):
def main(): def main():
""" Starts Home Assistant. """ """Start Home Assistant."""
validate_python() validate_python()
args = get_arguments() args = get_arguments()
@ -297,7 +297,7 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir) ensure_config_path(config_dir)
# os x launchd functions # OS X launchd functions
if args.install_osx: if args.install_osx:
install_osx() install_osx()
return 0 return 0
@ -311,7 +311,7 @@ def main():
install_osx() install_osx()
return 0 return 0
# daemon functions # Daemon functions
if args.pid_file: if args.pid_file:
check_pid(args.pid_file) check_pid(args.pid_file)
if args.daemon: if args.daemon:

View File

@ -34,8 +34,7 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
def setup_component(hass, domain, config=None): def setup_component(hass, domain, config=None):
""" Setup a component and all its dependencies. """ """Setup a component and all its dependencies."""
if domain in hass.config.components: if domain in hass.config.components:
return True return True
@ -58,7 +57,7 @@ def setup_component(hass, domain, config=None):
def _handle_requirements(hass, component, name): def _handle_requirements(hass, component, name):
""" Installs requirements for component. """ """Install the requirements for a component."""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'): if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True return True
@ -126,7 +125,7 @@ def _setup_component(hass, domain, config):
def prepare_setup_platform(hass, config, domain, platform_name): def prepare_setup_platform(hass, config, domain, platform_name):
""" Loads a platform and makes sure dependencies are setup. """ """Load a platform and makes sure dependencies are setup."""
_ensure_loader_prepared(hass) _ensure_loader_prepared(hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name) platform_path = PLATFORM_FORMAT.format(domain, platform_name)
@ -158,7 +157,7 @@ def prepare_setup_platform(hass, config, domain, platform_name):
def mount_local_lib_path(config_dir): def mount_local_lib_path(config_dir):
""" Add local library to Python Path """ """Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'lib')) sys.path.insert(0, os.path.join(config_dir, 'lib'))
@ -166,8 +165,7 @@ def mount_local_lib_path(config_dir):
def from_config_dict(config, hass=None, config_dir=None, enable_log=True, def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False, skip_pip=False, verbose=False, daemon=False, skip_pip=False,
log_rotate_days=None): log_rotate_days=None):
""" """Try to configure Home Assistant from a config dict.
Tries to configure Home Assistant from a config dict.
Dynamically loads required components and its dependencies. Dynamically loads required components and its dependencies.
""" """
@ -209,7 +207,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
_LOGGER.info('Home Assistant core initialized') _LOGGER.info('Home Assistant core initialized')
# give event decorators access to HASS # Give event decorators access to HASS
event_decorators.HASS = hass event_decorators.HASS = hass
service.HASS = hass service.HASS = hass
@ -222,9 +220,9 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
def from_config_file(config_path, hass=None, verbose=False, daemon=False, def from_config_file(config_path, hass=None, verbose=False, daemon=False,
skip_pip=True, log_rotate_days=None): skip_pip=True, log_rotate_days=None):
""" """Read the configuration file and try to start all the functionality.
Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given, Will add functionality to 'hass' parameter if given,
instantiates a new Home Assistant object if 'hass' is not given. instantiates a new Home Assistant object if 'hass' is not given.
""" """
if hass is None: if hass is None:
@ -244,7 +242,7 @@ def from_config_file(config_path, hass=None, verbose=False, daemon=False,
def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None): def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
""" Setup the logging for home assistant. """ """Setup the logging."""
if not daemon: if not daemon:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
@ -297,7 +295,7 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
def process_ha_config_upgrade(hass): def process_ha_config_upgrade(hass):
""" Upgrade config if necessary. """ """Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION') version_path = hass.config.path('.HA_VERSION')
try: try:
@ -322,11 +320,11 @@ def process_ha_config_upgrade(hass):
def process_ha_core_config(hass, config): def process_ha_core_config(hass, config):
""" Processes the [homeassistant] section from the config. """ """Process the [homeassistant] section from the config."""
hac = hass.config hac = hass.config
def set_time_zone(time_zone_str): def set_time_zone(time_zone_str):
""" Helper method to set time zone in HA. """ """Helper method to set time zone."""
if time_zone_str is None: if time_zone_str is None:
return return
@ -397,6 +395,6 @@ def process_ha_core_config(hass, config):
def _ensure_loader_prepared(hass): def _ensure_loader_prepared(hass):
""" Ensure Home Assistant loader is prepared. """ """Ensure Home Assistant loader is prepared."""
if not loader.PREPARED: if not loader.PREPARED:
loader.prepare(hass) loader.prepare(hass)

View File

@ -1,9 +1,4 @@
""" """Module to help with parsing and generating configuration files."""
homeassistant.config
~~~~~~~~~~~~~~~~~~~~
Module to help with parsing and generating configuration files.
"""
import logging import logging
import os import os
@ -43,16 +38,18 @@ DEFAULT_COMPONENTS = {
def get_default_config_dir(): def get_default_config_dir():
""" 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, detect_location=True):
""" Ensures a config file exists in given config dir. """Ensure a config file exists in given configuration directory.
Creating a default one if needed.
Returns path to the config file. """ Creating a default one if needed.
Return path to the config file.
"""
config_path = find_config_file(config_dir) config_path = find_config_file(config_dir)
if config_path is None: if config_path is None:
@ -64,8 +61,10 @@ def ensure_config_exists(config_dir, detect_location=True):
def create_default_config(config_dir, detect_location=True): def create_default_config(config_dir, detect_location=True):
""" Creates a default configuration file in given config dir. """Create a default configuration file in given configuration directory.
Returns path to new config file if success, None if failed. """
Return path to new config file if success, None if failed.
"""
config_path = os.path.join(config_dir, YAML_CONFIG_FILE) config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG} info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
@ -108,14 +107,14 @@ def create_default_config(config_dir, detect_location=True):
def find_config_file(config_dir): def find_config_file(config_dir):
""" Looks in given directory for supported config files. """ """Look in given directory for supported configuration files."""
config_path = os.path.join(config_dir, YAML_CONFIG_FILE) config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
return config_path if os.path.isfile(config_path) else None return config_path if os.path.isfile(config_path) else None
def load_yaml_config_file(config_path): def load_yaml_config_file(config_path):
""" Parse a YAML configuration file. """ """Parse a YAML configuration file."""
conf_dict = load_yaml(config_path) conf_dict = load_yaml(config_path)
if not isinstance(conf_dict, dict): if not isinstance(conf_dict, dict):

View File

@ -132,12 +132,13 @@ class JobPriority(util.OrderedEnum):
class EventOrigin(enum.Enum): class EventOrigin(enum.Enum):
"""Represents origin of an event.""" """Represent the origin of an event."""
local = "LOCAL" local = "LOCAL"
remote = "REMOTE" remote = "REMOTE"
def __str__(self): def __str__(self):
"""Return the event."""
return self.value return self.value
@ -166,6 +167,7 @@ class Event(object):
} }
def __repr__(self): def __repr__(self):
"""Return the representation."""
# pylint: disable=maybe-no-member # pylint: disable=maybe-no-member
if self.data: if self.data:
return "<Event {}[{}]: {}>".format( return "<Event {}[{}]: {}>".format(
@ -176,6 +178,7 @@ class Event(object):
str(self.origin)[0]) str(self.origin)[0])
def __eq__(self, other): def __eq__(self, other):
"""Return the comparison."""
return (self.__class__ == other.__class__ and return (self.__class__ == other.__class__ and
self.event_type == other.event_type and self.event_type == other.event_type and
self.data == other.data and self.data == other.data and
@ -246,7 +249,7 @@ class EventBus(object):
""" """
@ft.wraps(listener) @ft.wraps(listener)
def onetime_listener(event): def onetime_listener(event):
"""Remove listener from eventbus and then fires listener.""" """Remove listener from eventbus and then fire listener."""
if hasattr(onetime_listener, 'run'): if hasattr(onetime_listener, 'run'):
return return
# Set variable so that we will never run twice. # Set variable so that we will never run twice.
@ -281,8 +284,7 @@ class EventBus(object):
class State(object): class State(object):
""" """Object to represent a state within the state machine.
Object to represent a state within the state machine.
entity_id: the entity that is represented. entity_id: the entity that is represented.
state: the state of the entity state: the state of the entity
@ -369,12 +371,14 @@ class State(object):
json_dict.get('attributes'), last_changed, last_updated) json_dict.get('attributes'), last_changed, last_updated)
def __eq__(self, other): def __eq__(self, other):
"""Return the comparison of the state."""
return (self.__class__ == other.__class__ and return (self.__class__ == other.__class__ and
self.entity_id == other.entity_id and self.entity_id == other.entity_id and
self.state == other.state and self.state == other.state and
self.attributes == other.attributes) self.attributes == other.attributes)
def __repr__(self): def __repr__(self):
"""Return the representation of the states."""
attr = "; {}".format(util.repr_helper(self.attributes)) \ attr = "; {}".format(util.repr_helper(self.attributes)) \
if self.attributes else "" if self.attributes else ""
@ -524,6 +528,7 @@ class ServiceCall(object):
self.call_id = call_id self.call_id = call_id
def __repr__(self): def __repr__(self):
"""Return the represenation of the service."""
if self.data: if self.data:
return "<ServiceCall {}.{}: {}>".format( return "<ServiceCall {}.{}: {}>".format(
self.domain, self.service, util.repr_helper(self.data)) self.domain, self.service, util.repr_helper(self.data))

View File

@ -1,23 +1,28 @@
""" Exceptions used by Home Assistant """ """Exceptions used by Home Assistant."""
class HomeAssistantError(Exception): class HomeAssistantError(Exception):
""" General Home Assistant exception occured. """ """General Home Assistant exception occurred."""
pass pass
class InvalidEntityFormatError(HomeAssistantError): class InvalidEntityFormatError(HomeAssistantError):
""" When an invalid formatted entity is encountered. """ """When an invalid formatted entity is encountered."""
pass pass
class NoEntitySpecifiedError(HomeAssistantError): class NoEntitySpecifiedError(HomeAssistantError):
""" When no entity is specified. """ """When no entity is specified."""
pass pass
class TemplateError(HomeAssistantError): class TemplateError(HomeAssistantError):
""" Error during template rendering. """ """Error during template rendering."""
def __init__(self, exception): def __init__(self, exception):
"""Initalize the error."""
super().__init__('{}: {}'.format(exception.__class__.__name__, super().__init__('{}: {}'.format(exception.__class__.__name__,
exception)) exception))

View File

@ -1,20 +1,17 @@
""" """Helper methods for components within Home Assistant."""
Helper methods for components within Home Assistant.
"""
import re import re
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
def validate_config(config, items, logger): def validate_config(config, items, logger):
""" """Validate if all items are available in the configuration.
Validates if all items are available in the configuration.
config is the general dictionary with all the configurations. config is the general dictionary with all the configurations.
items is a dict with per domain which attributes we require. items is a dict with per domain which attributes we require.
logger is the logger from the caller to log the errors to. logger is the logger from the caller to log the errors to.
Returns True if all required items were found. Return True if all required items were found.
""" """
errors_found = False errors_found = False
for domain in items.keys(): for domain in items.keys():
@ -33,8 +30,8 @@ def validate_config(config, items, logger):
def config_per_platform(config, domain, logger): def config_per_platform(config, domain, logger):
""" """Generator to break a component config into different platforms.
Generator to break a component config into different platforms.
For example, will find 'switch', 'switch 2', 'switch 3', .. etc For example, will find 'switch', 'switch 2', 'switch 3', .. etc
""" """
config_key = domain config_key = domain
@ -59,6 +56,6 @@ def config_per_platform(config, domain, logger):
def extract_domain_configs(config, domain): def extract_domain_configs(config, domain):
""" Extract keys from config for given domain name. """ """Extract keys from config for given domain name."""
pattern = re.compile(r'^{}(| .+)$'.format(domain)) pattern = re.compile(r'^{}(| .+)$'.format(domain))
return [key for key in config.keys() if pattern.match(key)] return [key for key in config.keys() if pattern.match(key)]

View File

@ -1,8 +1,4 @@
""" """An abstract class for entities in HA."""
homeassistant.helpers.entity.
Provides ABC for entities in HA.
"""
import re import re
from collections import defaultdict from collections import defaultdict
@ -49,15 +45,12 @@ class Entity(object):
"""ABC for Home Assistant entities.""" """ABC for Home Assistant entities."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
# SAFE TO OVERWRITE # SAFE TO OVERWRITE
# The properties and methods here are safe to overwrite when inherting this # The properties and methods here are safe to overwrite when inherting this
# class. These may be used to customize the behavior of the entity. # class. These may be used to customize the behavior of the entity.
@property @property
def should_poll(self): def should_poll(self):
""" """Return True if entity has to be polled for state.
Return True if entity has to be polled for state.
False if entity pushes its state to HA. False if entity pushes its state to HA.
""" """
@ -80,8 +73,7 @@ class Entity(object):
@property @property
def state_attributes(self): def state_attributes(self):
""" """Return the state attributes.
Return the state attributes.
Implemented by component base class. Implemented by component base class.
""" """
@ -89,8 +81,7 @@ class Entity(object):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
""" """Return device specific state attributes.
Return device specific state attributes.
Implemented by platform classes. Implemented by platform classes.
""" """
@ -140,8 +131,7 @@ class Entity(object):
hass = None hass = None
def update_ha_state(self, force_refresh=False): def update_ha_state(self, force_refresh=False):
""" """Update Home Assistant with current state of entity.
Update Home Assistant with current state of entity.
If force_refresh == True will update entity before setting state. If force_refresh == True will update entity before setting state.
""" """
@ -176,10 +166,10 @@ class Entity(object):
self._attr_setter('hidden', bool, ATTR_HIDDEN, attr) self._attr_setter('hidden', bool, ATTR_HIDDEN, attr)
self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr) self._attr_setter('assumed_state', bool, ATTR_ASSUMED_STATE, attr)
# overwrite properties that have been set in the config file # Overwrite properties that have been set in the config file.
attr.update(_OVERWRITE.get(self.entity_id, {})) attr.update(_OVERWRITE.get(self.entity_id, {}))
# remove hidden property if false so it won't show up # Remove hidden property if false so it won't show up.
if not attr.get(ATTR_HIDDEN, True): if not attr.get(ATTR_HIDDEN, True):
attr.pop(ATTR_HIDDEN) attr.pop(ATTR_HIDDEN)
@ -210,16 +200,17 @@ class Entity(object):
pass pass
def __eq__(self, other): def __eq__(self, other):
"""Return the comparison."""
return (isinstance(other, Entity) and return (isinstance(other, Entity) and
other.unique_id == self.unique_id) other.unique_id == self.unique_id)
def __repr__(self): def __repr__(self):
"""Return the representation."""
return "<Entity {}: {}>".format(self.name, self.state) return "<Entity {}: {}>".format(self.name, self.state)
@staticmethod @staticmethod
def overwrite_attribute(entity_id, attrs, vals): def overwrite_attribute(entity_id, attrs, vals):
""" """Overwrite any attribute of an entity.
Overwrite any attribute of an entity.
This function should receive a list of attributes and a This function should receive a list of attributes and a
list of values. Set attribute to None to remove any overwritten list of values. Set attribute to None to remove any overwritten
@ -236,7 +227,6 @@ class ToggleEntity(Entity):
"""ABC for entities that can be turned on and off.""" """ABC for entities that can be turned on and off."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
@property @property
def state(self): def state(self):
"""Return the state.""" """Return the state."""
@ -244,7 +234,7 @@ class ToggleEntity(Entity):
@property @property
def is_on(self): def is_on(self):
"""True if entity is on.""" """Return True if entity is on."""
return False return False
def turn_on(self, **kwargs): def turn_on(self, **kwargs):

View File

@ -41,8 +41,7 @@ class EntityComponent(object):
self.scan_interval).add_entities self.scan_interval).add_entities
def setup(self, config): def setup(self, config):
""" """Set up a full entity component.
Set up a full entity component.
Loads the platforms from the config and will listen for supported Loads the platforms from the config and will listen for supported
discovered platforms. discovered platforms.
@ -63,8 +62,7 @@ class EntityComponent(object):
info)) info))
def extract_from_service(self, service): def extract_from_service(self, service):
""" """Extract all known entities from a service call.
Extract all known entities from a service call.
Will return all entities if no entities specified in call. Will return all entities if no entities specified in call.
Will return an empty list if entities specified but unknown. Will return an empty list if entities specified but unknown.
@ -134,6 +132,7 @@ class EntityPlatform(object):
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def __init__(self, component, scan_interval): def __init__(self, component, scan_interval):
"""Initalize the entity platform."""
self.component = component self.component = component
self.scan_interval = scan_interval self.scan_interval = scan_interval
self.platform_entities = [] self.platform_entities = []

View File

@ -1,6 +1,4 @@
""" """Helpers for listening to events."""
Helpers for listening to events
"""
import functools as ft import functools as ft
from datetime import timedelta from datetime import timedelta
@ -11,8 +9,8 @@ from ..util import dt as dt_util
def track_state_change(hass, entity_ids, action, from_state=None, def track_state_change(hass, entity_ids, action, from_state=None,
to_state=None): to_state=None):
""" """Track specific state changes.
Track specific state changes.
entity_ids, from_state and to_state can be string or list. entity_ids, from_state and to_state can be string or list.
Use list to match multiple. Use list to match multiple.
@ -30,7 +28,7 @@ def track_state_change(hass, entity_ids, action, from_state=None,
@ft.wraps(action) @ft.wraps(action)
def state_change_listener(event): def state_change_listener(event):
""" The listener that listens for specific state changes. """ """The listener that listens for specific state changes."""
if event.data['entity_id'] not in entity_ids: if event.data['entity_id'] not in entity_ids:
return return
@ -55,29 +53,25 @@ def track_state_change(hass, entity_ids, action, from_state=None,
def track_point_in_time(hass, action, point_in_time): def track_point_in_time(hass, action, point_in_time):
""" """Add a listener that fires once after a spefic point in time."""
Adds a listener that fires once after a spefic 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)
@ft.wraps(action) @ft.wraps(action)
def utc_converter(utc_now): def utc_converter(utc_now):
""" Converts passed in UTC now to local now. """ """Convert passed in UTC now to local now."""
action(dt_util.as_local(utc_now)) action(dt_util.as_local(utc_now))
return track_point_in_utc_time(hass, utc_converter, utc_point_in_time) return track_point_in_utc_time(hass, utc_converter, utc_point_in_time)
def track_point_in_utc_time(hass, action, point_in_time): def track_point_in_utc_time(hass, action, point_in_time):
""" """Add a listener that fires once after a specific point in UTC time."""
Adds 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)
@ft.wraps(action) @ft.wraps(action)
def point_in_time_listener(event): def point_in_time_listener(event):
""" Listens for matching time_changed events. """ """Listen for matching time_changed events."""
now = event.data[ATTR_NOW] now = event.data[ATTR_NOW]
if now >= point_in_time and \ if now >= point_in_time and \
@ -100,14 +94,12 @@ def track_point_in_utc_time(hass, action, point_in_time):
def track_sunrise(hass, action, offset=None): def track_sunrise(hass, action, offset=None):
""" """Add a listener that will fire a specified offset from sunrise daily."""
Adds a listener that will fire a specified offset from sunrise daily.
"""
from homeassistant.components import sun from homeassistant.components import sun
offset = offset or timedelta() offset = offset or timedelta()
def next_rise(): def next_rise():
""" Returns next sunrise. """ """Return the next sunrise."""
next_time = sun.next_rising_utc(hass) + offset next_time = sun.next_rising_utc(hass) + offset
while next_time < dt_util.utcnow(): while next_time < dt_util.utcnow():
@ -116,7 +108,7 @@ def track_sunrise(hass, action, offset=None):
return next_time return next_time
def sunrise_automation_listener(now): def sunrise_automation_listener(now):
""" Called when it's time for action. """ """Called when it's time for action."""
track_point_in_utc_time(hass, sunrise_automation_listener, next_rise()) track_point_in_utc_time(hass, sunrise_automation_listener, next_rise())
action() action()
@ -124,14 +116,12 @@ def track_sunrise(hass, action, offset=None):
def track_sunset(hass, action, offset=None): def track_sunset(hass, action, offset=None):
""" """Add a listener that will fire a specified offset from sunset daily."""
Adds a listener that will fire a specified offset from sunset daily.
"""
from homeassistant.components import sun from homeassistant.components import sun
offset = offset or timedelta() offset = offset or timedelta()
def next_set(): def next_set():
""" Returns next sunrise. """ """Return next sunrise."""
next_time = sun.next_setting_utc(hass) + offset next_time = sun.next_setting_utc(hass) + offset
while next_time < dt_util.utcnow(): while next_time < dt_util.utcnow():
@ -140,7 +130,7 @@ def track_sunset(hass, action, offset=None):
return next_time return next_time
def sunset_automation_listener(now): def sunset_automation_listener(now):
""" Called when it's time for action. """ """Called when it's time for action."""
track_point_in_utc_time(hass, sunset_automation_listener, next_set()) track_point_in_utc_time(hass, sunset_automation_listener, next_set())
action() action()
@ -150,13 +140,13 @@ def track_sunset(hass, action, offset=None):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def track_utc_time_change(hass, action, year=None, month=None, day=None, def track_utc_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None, local=False): hour=None, minute=None, second=None, local=False):
""" Adds a listener that will fire if time matches a pattern. """ """Add a listener that will fire if time matches a pattern."""
# We do not have to wrap the function with time pattern matching logic # We do not have to wrap the function with time pattern matching logic
# if no pattern given # if no pattern given
if all(val is None for val in (year, month, day, hour, minute, second)): if all(val is None for val in (year, month, day, hour, minute, second)):
@ft.wraps(action) @ft.wraps(action)
def time_change_listener(event): def time_change_listener(event):
""" Fires every time event that comes in. """ """Fire every time event that comes in."""
action(event.data[ATTR_NOW]) action(event.data[ATTR_NOW])
hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener) hass.bus.listen(EVENT_TIME_CHANGED, time_change_listener)
@ -168,7 +158,7 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None,
@ft.wraps(action) @ft.wraps(action)
def pattern_time_change_listener(event): def pattern_time_change_listener(event):
""" Listens for matching time_changed events. """ """Listen for matching time_changed events."""
now = event.data[ATTR_NOW] now = event.data[ATTR_NOW]
if local: if local:
@ -192,13 +182,13 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None,
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def track_time_change(hass, action, year=None, month=None, day=None, def track_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None): hour=None, minute=None, second=None):
""" Adds a listener that will fire if UTC time matches a pattern. """ """Add a listener that will fire if UTC time matches a pattern."""
track_utc_time_change(hass, action, year, month, day, hour, minute, second, track_utc_time_change(hass, action, year, month, day, hour, minute, second,
local=True) local=True)
def _process_match_param(parameter): def _process_match_param(parameter):
""" Wraps parameter in a tuple if it is not one and returns it. """ """Wrap parameter in a tuple if it is not one and returns it."""
if parameter is None or parameter == MATCH_ALL: if parameter is None or parameter == MATCH_ALL:
return MATCH_ALL return MATCH_ALL
elif isinstance(parameter, str) and parameter.startswith('/'): elif isinstance(parameter, str) and parameter.startswith('/'):
@ -210,7 +200,7 @@ def _process_match_param(parameter):
def _matcher(subject, pattern): def _matcher(subject, pattern):
""" Returns True if subject matches the pattern. """Return True if subject matches the pattern.
Pattern is either a tuple of allowed subjects or a `MATCH_ALL`. Pattern is either a tuple of allowed subjects or a `MATCH_ALL`.
""" """

View File

@ -1,5 +1,4 @@
""" Event Decorators for custom components """ """Event Decorators for custom components."""
import functools import functools
from homeassistant.helpers import event from homeassistant.helpers import event
@ -8,10 +7,9 @@ HASS = None
def track_state_change(entity_ids, from_state=None, to_state=None): def track_state_change(entity_ids, from_state=None, to_state=None):
""" Decorator factory to track state changes for entity id """ """Decorator factory to track state changes for entity id."""
def track_state_change_decorator(action): def track_state_change_decorator(action):
""" Decorator to track state changes """ """Decorator to track state changes."""
event.track_state_change(HASS, entity_ids, event.track_state_change(HASS, entity_ids,
functools.partial(action, HASS), functools.partial(action, HASS),
from_state, to_state) from_state, to_state)
@ -21,10 +19,9 @@ def track_state_change(entity_ids, from_state=None, to_state=None):
def track_sunrise(offset=None): def track_sunrise(offset=None):
""" Decorator factory to track sunrise events """ """Decorator factory to track sunrise events."""
def track_sunrise_decorator(action): def track_sunrise_decorator(action):
""" Decorator to track sunrise events """ """Decorator to track sunrise events."""
event.track_sunrise(HASS, event.track_sunrise(HASS,
functools.partial(action, HASS), functools.partial(action, HASS),
offset) offset)
@ -34,10 +31,9 @@ def track_sunrise(offset=None):
def track_sunset(offset=None): def track_sunset(offset=None):
""" Decorator factory to track sunset events """ """Decorator factory to track sunset events."""
def track_sunset_decorator(action): def track_sunset_decorator(action):
""" Decorator to track sunset events """ """Decorator to track sunset events."""
event.track_sunset(HASS, event.track_sunset(HASS,
functools.partial(action, HASS), functools.partial(action, HASS),
offset) offset)
@ -49,10 +45,9 @@ def track_sunset(offset=None):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def track_time_change(year=None, month=None, day=None, hour=None, minute=None, def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
second=None): second=None):
""" Decorator factory to track time changes """ """Decorator factory to track time changes."""
def track_time_change_decorator(action): def track_time_change_decorator(action):
""" Decorator to track time changes """ """Decorator to track time changes."""
event.track_time_change(HASS, event.track_time_change(HASS,
functools.partial(action, HASS), functools.partial(action, HASS),
year, month, day, hour, minute, second) year, month, day, hour, minute, second)
@ -64,10 +59,9 @@ def track_time_change(year=None, month=None, day=None, hour=None, minute=None,
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def track_utc_time_change(year=None, month=None, day=None, hour=None, def track_utc_time_change(year=None, month=None, day=None, hour=None,
minute=None, second=None): minute=None, second=None):
""" Decorator factory to track time changes """ """Decorator factory to track time changes."""
def track_utc_time_change_decorator(action): def track_utc_time_change_decorator(action):
""" Decorator to track time changes """ """Decorator to track time changes."""
event.track_utc_time_change(HASS, event.track_utc_time_change(HASS,
functools.partial(action, HASS), functools.partial(action, HASS),
year, month, day, hour, minute, second) year, month, day, hour, minute, second)

View File

@ -16,10 +16,9 @@ _LOGGER = logging.getLogger(__name__)
def service(domain, service_name): def service(domain, service_name):
""" Decorator factory to register a service """ """Decorator factory to register a service."""
def register_service_decorator(action): def register_service_decorator(action):
""" Decorator to register a service """ """Decorator to register a service."""
HASS.services.register(domain, service_name, HASS.services.register(domain, service_name,
functools.partial(action, HASS)) functools.partial(action, HASS))
return action return action
@ -60,8 +59,8 @@ def call_from_config(hass, config, blocking=False):
def extract_entity_ids(hass, service_call): def extract_entity_ids(hass, service_call):
""" """Helper method to extract a list of entity ids from a service call.
Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents. Will convert group entity ids to the entity ids it represents.
""" """
if not (service_call.data and ATTR_ENTITY_ID in service_call.data): if not (service_call.data and ATTR_ENTITY_ID in service_call.data):

View File

@ -18,10 +18,10 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=too-few-public-methods, attribute-defined-outside-init # pylint: disable=too-few-public-methods, attribute-defined-outside-init
class TrackStates(object): class TrackStates(object):
""" """Record the time when the with-block is entered.
Records the time when the with-block is entered. Will add all states
that have changed since the start time to the return list when with-block Will add all states that have changed since the start time to the return
is exited. list when with-block is exited.
""" """
def __init__(self, hass): def __init__(self, hass):
@ -100,7 +100,6 @@ def state_as_number(state):
Raises ValueError if this is not possible. Raises ValueError if this is not possible.
""" """
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON, if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON,
STATE_OPEN): STATE_OPEN):
return 1 return 1

View File

@ -1,16 +1,10 @@
""" """Methods to help handle temperature in Home Assistant."""
homeassistant.helpers.temperature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Methods to help handle temperature in Home Assistant.
"""
import homeassistant.util.temperature as temp_util import homeassistant.util.temperature as temp_util
from homeassistant.const import TEMP_CELCIUS from homeassistant.const import TEMP_CELCIUS
def convert(temperature, unit, to_unit): def convert(temperature, unit, to_unit):
""" Converts temperature to correct unit. """ """Convert temperature to correct unit."""
if unit == to_unit or unit is None or to_unit is None: if unit == to_unit or unit is None or to_unit is None:
return temperature return temperature
elif unit == TEMP_CELCIUS: elif unit == TEMP_CELCIUS:

View File

@ -1,6 +1,4 @@
""" """Template helper methods for rendering strings with HA data."""
Template helper methods for rendering strings with HA data.
"""
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import json import json
import logging import logging
@ -21,8 +19,8 @@ _SENTINEL = object()
def render_with_possible_json_value(hass, template, value, def render_with_possible_json_value(hass, template, value,
error_value=_SENTINEL): error_value=_SENTINEL):
""" """Render template with value exposed.
Renders template with value exposed.
If valid JSON will expose value_json too. If valid JSON will expose value_json too.
""" """
variables = { variables = {
@ -65,17 +63,22 @@ def render(hass, template, variables=None, **kwargs):
class AllStates(object): class AllStates(object):
"""Class to expose all HA states as attributes.""" """Class to expose all HA states as attributes."""
def __init__(self, hass): def __init__(self, hass):
"""Initialize all states."""
self._hass = hass self._hass = hass
def __getattr__(self, name): def __getattr__(self, name):
"""Return the domain state."""
return DomainStates(self._hass, name) return DomainStates(self._hass, name)
def __iter__(self): def __iter__(self):
"""Return all states."""
return iter(sorted(self._hass.states.all(), return iter(sorted(self._hass.states.all(),
key=lambda state: state.entity_id)) key=lambda state: state.entity_id))
def __call__(self, entity_id): def __call__(self, entity_id):
"""Return the states."""
state = self._hass.states.get(entity_id) state = self._hass.states.get(entity_id)
return STATE_UNKNOWN if state is None else state.state return STATE_UNKNOWN if state is None else state.state
@ -84,13 +87,16 @@ class DomainStates(object):
"""Class to expose a specific HA domain as attributes.""" """Class to expose a specific HA domain as attributes."""
def __init__(self, hass, domain): def __init__(self, hass, domain):
"""Initialize the domain states."""
self._hass = hass self._hass = hass
self._domain = domain self._domain = domain
def __getattr__(self, name): def __getattr__(self, name):
"""Return the states."""
return self._hass.states.get('{}.{}'.format(self._domain, name)) return self._hass.states.get('{}.{}'.format(self._domain, name))
def __iter__(self): def __iter__(self):
"""Return the iteration over all the states."""
return iter(sorted( return iter(sorted(
(state for state in self._hass.states.all() (state for state in self._hass.states.all()
if state.domain == self._domain), if state.domain == self._domain),
@ -101,7 +107,7 @@ class LocationMethods(object):
"""Class to expose distance helpers to templates.""" """Class to expose distance helpers to templates."""
def __init__(self, hass): def __init__(self, hass):
"""Initialize distance helpers.""" """Initialize the distance helpers."""
self._hass = hass self._hass = hass
def closest(self, *args): def closest(self, *args):
@ -118,7 +124,6 @@ class LocationMethods(object):
closest('zone.school', 'group.children') closest('zone.school', 'group.children')
closest(states.zone.school, 'group.children') closest(states.zone.school, 'group.children')
""" """
if len(args) == 1: if len(args) == 1:
latitude = self._hass.config.latitude latitude = self._hass.config.latitude
longitude = self._hass.config.longitude longitude = self._hass.config.longitude
@ -250,8 +255,7 @@ def forgiving_float(value):
class TemplateEnvironment(ImmutableSandboxedEnvironment): class TemplateEnvironment(ImmutableSandboxedEnvironment):
"""Home Assistant template environment.""" """The Home Assistant template environment."""
def is_safe_callable(self, obj): def is_safe_callable(self, obj):
"""Test if callback is safe.""" """Test if callback is safe."""
return isinstance(obj, AllStates) or super().is_safe_callable(obj) return isinstance(obj, AllStates) or super().is_safe_callable(obj)

View File

@ -1,7 +1,4 @@
""" """
homeassistant.loader
~~~~~~~~~~~~~~~~~~~~
Provides methods for loading Home Assistant components. Provides methods for loading Home Assistant components.
This module has quite some complex parts. I have tried to add as much This module has quite some complex parts. I have tried to add as much
@ -33,7 +30,7 @@ _LOGGER = logging.getLogger(__name__)
def prepare(hass): def prepare(hass):
""" Prepares the loading of components. """ """Prepare the loading of components."""
global PREPARED # pylint: disable=global-statement global PREPARED # pylint: disable=global-statement
# Load the built-in components # Load the built-in components
@ -74,17 +71,18 @@ def prepare(hass):
def set_component(comp_name, component): def set_component(comp_name, component):
""" Sets a component in the cache. """ """Set a component in the cache."""
_check_prepared() _check_prepared()
_COMPONENT_CACHE[comp_name] = component _COMPONENT_CACHE[comp_name] = component
def get_component(comp_name): def get_component(comp_name):
""" Tries to load specified component. """Try to load specified component.
Looks in config dir first, then built-in components.
Only returns it if also found to be valid. """
Looks in config dir first, then built-in components.
Only returns it if also found to be valid.
"""
if comp_name in _COMPONENT_CACHE: if comp_name in _COMPONENT_CACHE:
return _COMPONENT_CACHE[comp_name] return _COMPONENT_CACHE[comp_name]
@ -145,14 +143,14 @@ def get_component(comp_name):
def load_order_components(components): def load_order_components(components):
""" """Take in a list of components we want to load.
Takes in a list of components we want to load:
- filters out components we cannot load - filters out components we cannot load
- filters out components that have invalid/circular dependencies - filters out components that have invalid/circular dependencies
- Will make sure the recorder component is loaded first - Will make sure the recorder component is loaded first
- Will ensure that all components that do not directly depend on - Will ensure that all components that do not directly depend on
the group component will be loaded before the group component. the group component will be loaded before the group component.
- returns an OrderedSet load order. - returns an OrderedSet load order.
""" """
_check_prepared() _check_prepared()
@ -175,8 +173,8 @@ def load_order_components(components):
def load_order_component(comp_name): def load_order_component(comp_name):
""" """Return an OrderedSet of components in the correct order of loading.
Returns an OrderedSet of components in the correct order of loading.
Raises HomeAssistantError if a circular dependency is detected. Raises HomeAssistantError if a circular dependency is detected.
Returns an empty list if component could not be loaded. Returns an empty list if component could not be loaded.
""" """
@ -184,10 +182,10 @@ def load_order_component(comp_name):
def _load_order_component(comp_name, load_order, loading): def _load_order_component(comp_name, load_order, loading):
""" Recursive function to get load order of components. """ """Recursive function to get load order of components."""
component = get_component(comp_name) component = get_component(comp_name)
# if None it does not exist, error already thrown by get_component # If None it does not exist, error already thrown by get_component.
if component is None: if component is None:
return OrderedSet() return OrderedSet()
@ -198,7 +196,7 @@ def _load_order_component(comp_name, load_order, loading):
if dependency in load_order: if dependency in load_order:
continue continue
# If we are already loading it, we have a circular dependency # If we are already loading it, we have a circular dependency.
if dependency in loading: if dependency in loading:
_LOGGER.error('Circular dependency detected: %s -> %s', _LOGGER.error('Circular dependency detected: %s -> %s',
comp_name, dependency) comp_name, dependency)
@ -221,7 +219,7 @@ def _load_order_component(comp_name, load_order, loading):
def _check_prepared(): def _check_prepared():
""" Issues a warning if loader.prepare() has never been called. """ """Issue a warning if loader.prepare() has never been called."""
if not PREPARED: if not PREPARED:
_LOGGER.warning(( _LOGGER.warning((
"You did not call loader.prepare() yet. " "You did not call loader.prepare() yet. "

View File

@ -1,8 +1,5 @@
""" """
homeassistant.remote Support for an interface to work with a remote instance of Home Assistant.
~~~~~~~~~~~~~~~~~~~~
A module containing drop in replacements for core parts that will interface
with a remote instance of Home Assistant.
If a connection error occurs while communicating with the API a If a connection error occurs while communicating with the API a
HomeAssistantError will be raised. HomeAssistantError will be raised.
@ -34,23 +31,25 @@ _LOGGER = logging.getLogger(__name__)
class APIStatus(enum.Enum): class APIStatus(enum.Enum):
""" Represents API status. """ """Represent API status."""
# pylint: disable=no-init,invalid-name,too-few-public-methods
# pylint: disable=no-init,invalid-name,too-few-public-methods
OK = "ok" OK = "ok"
INVALID_PASSWORD = "invalid_password" INVALID_PASSWORD = "invalid_password"
CANNOT_CONNECT = "cannot_connect" CANNOT_CONNECT = "cannot_connect"
UNKNOWN = "unknown" UNKNOWN = "unknown"
def __str__(self): def __str__(self):
"""Return the state."""
return self.value return self.value
class API(object): class API(object):
""" Object to pass around Home Assistant API location and credentials. """ """Object to pass around Home Assistant API location and credentials."""
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods
def __init__(self, host, api_password=None, port=None, use_ssl=False): def __init__(self, host, api_password=None, port=None, use_ssl=False):
"""Initalize the API."""
self.host = host self.host = host
self.port = port or SERVER_PORT self.port = port or SERVER_PORT
self.api_password = api_password self.api_password = api_password
@ -65,14 +64,14 @@ class API(object):
self._headers[HTTP_HEADER_HA_AUTH] = api_password self._headers[HTTP_HEADER_HA_AUTH] = api_password
def validate_api(self, force_validate=False): def validate_api(self, force_validate=False):
""" Tests if we can communicate with the API. """ """Test if we can communicate with the API."""
if self.status is None or force_validate: if self.status is None or force_validate:
self.status = validate_api(self) self.status = validate_api(self)
return self.status == APIStatus.OK return self.status == APIStatus.OK
def __call__(self, method, path, data=None): def __call__(self, method, path, data=None):
""" Makes a call to the Home Assistant API. """ """Make a call to the Home Assistant API."""
if data is not None: if data is not None:
data = json.dumps(data, cls=JSONEncoder) data = json.dumps(data, cls=JSONEncoder)
@ -96,15 +95,17 @@ class API(object):
raise HomeAssistantError(error) raise HomeAssistantError(error)
def __repr__(self): def __repr__(self):
"""Return the representation of the API."""
return "API({}, {}, {})".format( return "API({}, {}, {})".format(
self.host, self.api_password, self.port) self.host, self.api_password, self.port)
class HomeAssistant(ha.HomeAssistant): class HomeAssistant(ha.HomeAssistant):
""" Home Assistant that forwards work. """ """Home Assistant that forwards work."""
# pylint: disable=super-init-not-called,too-many-instance-attributes
# pylint: disable=super-init-not-called,too-many-instance-attributes
def __init__(self, remote_api, local_api=None): def __init__(self, remote_api, local_api=None):
"""Initalize the forward instance."""
if not remote_api.validate_api(): if not remote_api.validate_api():
raise HomeAssistantError( raise HomeAssistantError(
"Remote API at {}:{} not valid: {}".format( "Remote API at {}:{} not valid: {}".format(
@ -122,6 +123,7 @@ class HomeAssistant(ha.HomeAssistant):
self.config.api = local_api self.config.api = local_api
def start(self): def start(self):
"""Start the instance."""
# Ensure a local API exists to connect with remote # Ensure a local API exists to connect with remote
if self.config.api is None: if self.config.api is None:
if not bootstrap.setup_component(self, 'api'): if not bootstrap.setup_component(self, 'api'):
@ -141,7 +143,7 @@ class HomeAssistant(ha.HomeAssistant):
'local api {}').format(self.remote_api, self.config.api)) 'local api {}').format(self.remote_api, self.config.api))
def stop(self): def stop(self):
""" Stops Home Assistant and shuts down all threads. """ """Stop Home Assistant and shuts down all threads."""
_LOGGER.info("Stopping") _LOGGER.info("Stopping")
self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP, self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP,
@ -154,16 +156,19 @@ class HomeAssistant(ha.HomeAssistant):
class EventBus(ha.EventBus): class EventBus(ha.EventBus):
""" EventBus implementation that forwards fire_event to remote API. """ """EventBus implementation that forwards fire_event to remote API."""
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods
def __init__(self, api, pool=None): def __init__(self, api, pool=None):
"""Initalize the eventbus."""
super().__init__(pool) super().__init__(pool)
self._api = api self._api = api
def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local): def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local):
""" Forward local events to remote target, """Forward local events to remote target.
handles remote event as usual. """
Handles remote event as usual.
"""
# All local events that are not TIME_CHANGED are forwarded to API # All local events that are not TIME_CHANGED are forwarded to API
if origin == ha.EventOrigin.local and \ if origin == ha.EventOrigin.local and \
event_type != ha.EVENT_TIME_CHANGED: event_type != ha.EVENT_TIME_CHANGED:
@ -175,9 +180,10 @@ class EventBus(ha.EventBus):
class EventForwarder(object): class EventForwarder(object):
""" Listens for events and forwards to specified APIs. """ """Listens for events and forwards to specified APIs."""
def __init__(self, hass, restrict_origin=None): def __init__(self, hass, restrict_origin=None):
"""Initalize the event forwarder."""
self.hass = hass self.hass = hass
self.restrict_origin = restrict_origin self.restrict_origin = restrict_origin
@ -188,8 +194,7 @@ class EventForwarder(object):
self._lock = threading.Lock() self._lock = threading.Lock()
def connect(self, api): def connect(self, api):
""" """Attach to a Home Assistant instance and forward events.
Attach to a Home Assistant instance and forward events.
Will overwrite old target if one exists with same host/port. Will overwrite old target if one exists with same host/port.
""" """
@ -203,7 +208,7 @@ class EventForwarder(object):
self._targets[key] = api self._targets[key] = api
def disconnect(self, api): def disconnect(self, api):
""" Removes target from being forwarded to. """ """Remove target from being forwarded to."""
with self._lock: with self._lock:
key = (api.host, api.port) key = (api.host, api.port)
@ -217,7 +222,7 @@ class EventForwarder(object):
return did_remove return did_remove
def _event_listener(self, event): def _event_listener(self, event):
""" Listen and forwards all events. """ """Listen and forward all events."""
with self._lock: with self._lock:
# We don't forward time events or, if enabled, non-local events # We don't forward time events or, if enabled, non-local events
if event.event_type == ha.EVENT_TIME_CHANGED or \ if event.event_type == ha.EVENT_TIME_CHANGED or \
@ -229,16 +234,12 @@ class EventForwarder(object):
class StateMachine(ha.StateMachine): class StateMachine(ha.StateMachine):
""" """Fire set events to an API. Uses state_change events to track states."""
Fires set events to an API.
Uses state_change events to track states.
"""
def __init__(self, bus, api): def __init__(self, bus, api):
"""Initalize the statemachine."""
super().__init__(None) super().__init__(None)
self._api = api self._api = api
self.mirror() self.mirror()
bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener) bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener)
@ -251,16 +252,16 @@ class StateMachine(ha.StateMachine):
return remove_state(self._api, entity_id) return remove_state(self._api, entity_id)
def set(self, entity_id, new_state, attributes=None): def set(self, entity_id, new_state, attributes=None):
""" Calls set_state on remote API . """ """Call set_state on remote API."""
set_state(self._api, entity_id, new_state, attributes) set_state(self._api, entity_id, new_state, attributes)
def mirror(self): def mirror(self):
""" Discards current data and mirrors the remote state machine. """ """Discard current data and mirrors the remote state machine."""
self._states = {state.entity_id: state for state self._states = {state.entity_id: state for state
in get_states(self._api)} in get_states(self._api)}
def _state_changed_listener(self, event): def _state_changed_listener(self, event):
""" Listens for state changed events and applies them. """ """Listen for state changed events and applies them."""
if event.data['new_state'] is None: if event.data['new_state'] is None:
self._states.pop(event.data['entity_id'], None) self._states.pop(event.data['entity_id'], None)
else: else:
@ -268,12 +269,14 @@ class StateMachine(ha.StateMachine):
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
""" JSONEncoder that supports Home Assistant objects. """ """JSONEncoder that supports Home Assistant objects."""
# pylint: disable=too-few-public-methods,method-hidden
# pylint: disable=too-few-public-methods,method-hidden
def default(self, obj): def default(self, obj):
""" Converts Home Assistant objects and hands """Convert Home Assistant objects.
other objects to the original method. """
Hand other objects to the original method.
"""
if hasattr(obj, 'as_dict'): if hasattr(obj, 'as_dict'):
return obj.as_dict() return obj.as_dict()
@ -291,7 +294,7 @@ class JSONEncoder(json.JSONEncoder):
def validate_api(api): def validate_api(api):
""" Makes a call to validate API. """ """Make a call to validate API."""
try: try:
req = api(METHOD_GET, URL_API) req = api(METHOD_GET, URL_API)
@ -309,8 +312,7 @@ def validate_api(api):
def connect_remote_events(from_api, to_api): def connect_remote_events(from_api, to_api):
""" Sets up from_api to forward all events to to_api. """ """Setup from_api to forward all events to to_api."""
data = { data = {
'host': to_api.host, 'host': to_api.host,
'api_password': to_api.api_password, 'api_password': to_api.api_password,
@ -335,7 +337,7 @@ def connect_remote_events(from_api, to_api):
def disconnect_remote_events(from_api, to_api): def disconnect_remote_events(from_api, to_api):
""" Disconnects forwarding events from from_api to to_api. """ """Disconnect forwarding events from from_api to to_api."""
data = { data = {
'host': to_api.host, 'host': to_api.host,
'port': to_api.port 'port': to_api.port
@ -359,7 +361,7 @@ def disconnect_remote_events(from_api, to_api):
def get_event_listeners(api): def get_event_listeners(api):
""" List of events that is being listened for. """ """List of events that is being listened for."""
try: try:
req = api(METHOD_GET, URL_API_EVENTS) req = api(METHOD_GET, URL_API_EVENTS)
@ -373,8 +375,7 @@ def get_event_listeners(api):
def fire_event(api, event_type, data=None): def fire_event(api, event_type, data=None):
""" Fire an event at remote API. """ """Fire an event at remote API."""
try: try:
req = api(METHOD_POST, URL_API_EVENTS_EVENT.format(event_type), data) req = api(METHOD_POST, URL_API_EVENTS_EVENT.format(event_type), data)
@ -387,8 +388,7 @@ def fire_event(api, event_type, data=None):
def get_state(api, entity_id): def get_state(api, entity_id):
""" Queries given API for state of entity_id. """ """Query given API for state of entity_id."""
try: try:
req = api(METHOD_GET, URL_API_STATES_ENTITY.format(entity_id)) req = api(METHOD_GET, URL_API_STATES_ENTITY.format(entity_id))
@ -405,8 +405,7 @@ def get_state(api, entity_id):
def get_states(api): def get_states(api):
""" Queries given API for all states. """ """Query given API for all states."""
try: try:
req = api(METHOD_GET, req = api(METHOD_GET,
URL_API_STATES) URL_API_STATES)
@ -424,7 +423,7 @@ def get_states(api):
def remove_state(api, entity_id): def remove_state(api, entity_id):
"""Call API to remove state for entity_id. """Call API to remove state for entity_id.
Returns True if entity is gone (removed/never existed). Return True if entity is gone (removed/never existed).
""" """
try: try:
req = api(METHOD_DELETE, URL_API_STATES_ENTITY.format(entity_id)) req = api(METHOD_DELETE, URL_API_STATES_ENTITY.format(entity_id))
@ -442,11 +441,10 @@ def remove_state(api, entity_id):
def set_state(api, entity_id, new_state, attributes=None): def set_state(api, entity_id, new_state, attributes=None):
""" """Tell API to update state for entity_id.
Tells API to update state for entity_id.
Returns True if success.
"""
Return True if success.
"""
attributes = attributes or {} attributes = attributes or {}
data = {'state': new_state, data = {'state': new_state,
@ -471,16 +469,16 @@ def set_state(api, entity_id, new_state, attributes=None):
def is_state(api, entity_id, state): def is_state(api, entity_id, state):
""" Queries API to see if entity_id is specified state. """ """Query API to see if entity_id is specified state."""
cur_state = get_state(api, entity_id) cur_state = get_state(api, entity_id)
return cur_state and cur_state.state == state return cur_state and cur_state.state == state
def get_services(api): def get_services(api):
""" """Return a list of dicts.
Returns a list of dicts. Each dict has a string "domain" and
a list of strings "services". Each dict has a string "domain" and a list of strings "services".
""" """
try: try:
req = api(METHOD_GET, URL_API_SERVICES) req = api(METHOD_GET, URL_API_SERVICES)
@ -495,7 +493,7 @@ def get_services(api):
def call_service(api, domain, service, service_data=None): def call_service(api, domain, service, service_data=None):
""" Calls a service at the remote API. """ """Call a service at the remote API."""
try: try:
req = api(METHOD_POST, req = api(METHOD_POST,
URL_API_SERVICES_SERVICE.format(domain, service), URL_API_SERVICES_SERVICE.format(domain, service),

View File

@ -1,9 +1,4 @@
""" """Helper methods for various modules."""
homeassistant.util
~~~~~~~~~~~~~~~~~~
Helper methods for various modules.
"""
import collections import collections
from itertools import chain from itertools import chain
import threading import threading
@ -25,24 +20,24 @@ RE_SLUGIFY = re.compile(r'[^a-z0-9_]+')
def sanitize_filename(filename): def sanitize_filename(filename):
""" Sanitizes a filename by removing .. / and \\. """ r"""Sanitize a filename by removing .. / and \\."""
return RE_SANITIZE_FILENAME.sub("", filename) return RE_SANITIZE_FILENAME.sub("", filename)
def sanitize_path(path): def sanitize_path(path):
""" Sanitizes a path by removing ~ and .. """ """Sanitize a path by removing ~ and .."""
return RE_SANITIZE_PATH.sub("", path) return RE_SANITIZE_PATH.sub("", path)
def slugify(text): def slugify(text):
""" Slugifies a given text. """ """Slugify a given text."""
text = text.lower().replace(" ", "_") text = text.lower().replace(" ", "_")
return RE_SLUGIFY.sub("", text) return RE_SLUGIFY.sub("", text)
def repr_helper(inp): def repr_helper(inp):
""" Helps creating a more readable string representation of objects. """ """Help creating a more readable string representation of objects."""
if isinstance(inp, (dict, MappingProxyType)): if isinstance(inp, (dict, MappingProxyType)):
return ", ".join( return ", ".join(
repr_helper(key)+"="+repr_helper(item) for key, item repr_helper(key)+"="+repr_helper(item) for key, item
@ -54,7 +49,7 @@ def repr_helper(inp):
def convert(value, to_type, default=None): def convert(value, to_type, default=None):
""" Converts value to to_type, returns default if fails. """ """Convert value to to_type, returns default if fails."""
try: try:
return default if value is None else to_type(value) return default if value is None else to_type(value)
except (ValueError, TypeError): except (ValueError, TypeError):
@ -63,8 +58,10 @@ def convert(value, to_type, default=None):
def ensure_unique_string(preferred_string, current_strings): def ensure_unique_string(preferred_string, current_strings):
""" Returns a string that is not present in current_strings. """Return a string that is not present in current_strings.
If preferred string exists will append _2, _3, .. """
If preferred string exists will append _2, _3, ..
"""
test_string = preferred_string test_string = preferred_string
current_strings = set(current_strings) current_strings = set(current_strings)
@ -79,7 +76,7 @@ def ensure_unique_string(preferred_string, current_strings):
# Taken from: http://stackoverflow.com/a/11735897 # Taken from: http://stackoverflow.com/a/11735897
def get_local_ip(): def get_local_ip():
""" Tries to determine the local IP address of the machine. """ """Try to determine the local IP address of the machine."""
try: try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -95,7 +92,7 @@ def get_local_ip():
# Taken from http://stackoverflow.com/a/23728630 # Taken from http://stackoverflow.com/a/23728630
def get_random_string(length=10): def get_random_string(length=10):
""" Returns a random string with letters and digits. """ """Return a random string with letters and digits."""
generator = random.SystemRandom() generator = random.SystemRandom()
source_chars = string.ascii_letters + string.digits source_chars = string.ascii_letters + string.digits
@ -103,34 +100,39 @@ def get_random_string(length=10):
class OrderedEnum(enum.Enum): class OrderedEnum(enum.Enum):
""" Taken from Python 3.4.0 docs. """ """Taken from Python 3.4.0 docs."""
# pylint: disable=no-init, too-few-public-methods
# pylint: disable=no-init, too-few-public-methods
def __ge__(self, other): def __ge__(self, other):
"""Return the greater than element."""
if self.__class__ is other.__class__: if self.__class__ is other.__class__:
return self.value >= other.value return self.value >= other.value
return NotImplemented return NotImplemented
def __gt__(self, other): def __gt__(self, other):
"""Return the greater element."""
if self.__class__ is other.__class__: if self.__class__ is other.__class__:
return self.value > other.value return self.value > other.value
return NotImplemented return NotImplemented
def __le__(self, other): def __le__(self, other):
"""Return the lower than element."""
if self.__class__ is other.__class__: if self.__class__ is other.__class__:
return self.value <= other.value return self.value <= other.value
return NotImplemented return NotImplemented
def __lt__(self, other): def __lt__(self, other):
"""Return the lower element."""
if self.__class__ is other.__class__: if self.__class__ is other.__class__:
return self.value < other.value return self.value < other.value
return NotImplemented return NotImplemented
class OrderedSet(collections.MutableSet): class OrderedSet(collections.MutableSet):
""" Ordered set taken from http://code.activestate.com/recipes/576694/ """ """Ordered set taken from http://code.activestate.com/recipes/576694/."""
def __init__(self, iterable=None): def __init__(self, iterable=None):
"""Initialize the set."""
self.end = end = [] self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next] self.map = {} # key --> [key, prev, next]
@ -138,20 +140,22 @@ class OrderedSet(collections.MutableSet):
self |= iterable self |= iterable
def __len__(self): def __len__(self):
"""Return the length of the set."""
return len(self.map) return len(self.map)
def __contains__(self, key): def __contains__(self, key):
"""Check if key is in set."""
return key in self.map return key in self.map
def add(self, key): def add(self, key):
""" Add an element to the end of the set. """ """Add an element to the end of the set."""
if key not in self.map: if key not in self.map:
end = self.end end = self.end
curr = end[1] curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end] curr[2] = end[1] = self.map[key] = [key, curr, end]
def promote(self, key): def promote(self, key):
""" Promote element to beginning of the set, add if not there. """ """Promote element to beginning of the set, add if not there."""
if key in self.map: if key in self.map:
self.discard(key) self.discard(key)
@ -160,13 +164,14 @@ class OrderedSet(collections.MutableSet):
curr[2] = begin[1] = self.map[key] = [key, curr, begin] curr[2] = begin[1] = self.map[key] = [key, curr, begin]
def discard(self, key): def discard(self, key):
""" Discard an element from the set. """ """Discard an element from the set."""
if key in self.map: if key in self.map:
key, prev_item, next_item = self.map.pop(key) key, prev_item, next_item = self.map.pop(key)
prev_item[2] = next_item prev_item[2] = next_item
next_item[1] = prev_item next_item[1] = prev_item
def __iter__(self): def __iter__(self):
"""Iteration of the set."""
end = self.end end = self.end
curr = end[2] curr = end[2]
while curr is not end: while curr is not end:
@ -174,6 +179,7 @@ class OrderedSet(collections.MutableSet):
curr = curr[2] curr = curr[2]
def __reversed__(self): def __reversed__(self):
"""reverse the ordering."""
end = self.end end = self.end
curr = end[1] curr = end[1]
while curr is not end: while curr is not end:
@ -181,8 +187,8 @@ class OrderedSet(collections.MutableSet):
curr = curr[1] curr = curr[1]
def pop(self, last=True): # pylint: disable=arguments-differ def pop(self, last=True): # pylint: disable=arguments-differ
""" """Pop element of the end of the set.
Pops element of the end of the set.
Set last=False to pop from the beginning. Set last=False to pop from the beginning.
""" """
if not self: if not self:
@ -192,24 +198,27 @@ class OrderedSet(collections.MutableSet):
return key return key
def update(self, *args): def update(self, *args):
""" Add elements from args to the set. """ """Add elements from args to the set."""
for item in chain(*args): for item in chain(*args):
self.add(item) self.add(item)
def __repr__(self): def __repr__(self):
"""Return the representation."""
if not self: if not self:
return '%s()' % (self.__class__.__name__,) return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self)) return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other): def __eq__(self, other):
"""Return the comparision."""
if isinstance(other, OrderedSet): if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other) return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other) return set(self) == set(other)
class Throttle(object): class Throttle(object):
""" """A class for throttling the execution of tasks.
A method decorator to add a cooldown to a method to prevent it from being
This method decorator adds a cooldown to a method to prevent it from being
called more then 1 time within the timedelta interval `min_time` after it called more then 1 time within the timedelta interval `min_time` after it
returned its result. returned its result.
@ -223,13 +232,15 @@ class Throttle(object):
Adds a datetime attribute `last_call` to the method. Adds a datetime attribute `last_call` to the method.
""" """
# pylint: disable=too-few-public-methods
# pylint: disable=too-few-public-methods
def __init__(self, min_time, limit_no_throttle=None): def __init__(self, min_time, limit_no_throttle=None):
"""Initialize the throttle."""
self.min_time = min_time self.min_time = min_time
self.limit_no_throttle = limit_no_throttle self.limit_no_throttle = limit_no_throttle
def __call__(self, method): def __call__(self, method):
"""Caller for the throttle."""
if self.limit_no_throttle is not None: if self.limit_no_throttle is not None:
method = Throttle(self.limit_no_throttle)(method) method = Throttle(self.limit_no_throttle)(method)
@ -248,8 +259,8 @@ class Throttle(object):
@wraps(method) @wraps(method)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
""" """Wrapper that allows wrapped to be called only once per min_time.
Wrapper that allows wrapped to be called only once per min_time.
If we cannot acquire the lock, it is running so return None. If we cannot acquire the lock, it is running so return None.
""" """
# pylint: disable=protected-access # pylint: disable=protected-access
@ -288,10 +299,11 @@ class Throttle(object):
class ThreadPool(object): class ThreadPool(object):
"""A priority queue-based thread pool.""" """A priority queue-based thread pool."""
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, job_handler, worker_count=0, busy_callback=None): def __init__(self, job_handler, worker_count=0, busy_callback=None):
""" """Initialize the pool.
job_handler: method to be called from worker thread to handle job job_handler: method to be called from worker thread to handle job
worker_count: number of threads to run that handle jobs worker_count: number of threads to run that handle jobs
busy_callback: method to be called when queue gets too big. busy_callback: method to be called when queue gets too big.
@ -338,18 +350,18 @@ class ThreadPool(object):
self.busy_warning_limit = self.worker_count * 3 self.busy_warning_limit = self.worker_count * 3
def add_job(self, priority, job): def add_job(self, priority, job):
""" Add a job to the queue. """ """Add a job to the queue."""
with self._lock: with self._lock:
if not self.running: if not self.running:
raise RuntimeError("ThreadPool not running") raise RuntimeError("ThreadPool not running")
self._work_queue.put(PriorityQueueItem(priority, job)) self._work_queue.put(PriorityQueueItem(priority, job))
# check if our queue is getting too big # Check if our queue is getting too big.
if self._work_queue.qsize() > self.busy_warning_limit \ if self._work_queue.qsize() > self.busy_warning_limit \
and self._busy_callback is not None: and self._busy_callback is not None:
# Increase limit we will issue next warning # Increase limit we will issue next warning.
self.busy_warning_limit *= 2 self.busy_warning_limit *= 2
self._busy_callback( self._busy_callback(
@ -404,12 +416,14 @@ class ThreadPool(object):
class PriorityQueueItem(object): class PriorityQueueItem(object):
""" Holds a priority and a value. Used within PriorityQueue. """ """Holds a priority and a value. Used within PriorityQueue."""
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def __init__(self, priority, item): def __init__(self, priority, item):
"""Initialize the queue."""
self.priority = priority self.priority = priority
self.item = item self.item = item
def __lt__(self, other): def __lt__(self, other):
"""Return the ordering."""
return self.priority < other.priority return self.priority < other.priority

View File

@ -1,10 +1,4 @@
""" """Provides helper methods to handle the time in HA."""
homeassistant.util.dt
~~~~~~~~~~~~~~~~~~~~~
Provides helper methods to handle the time in HA.
"""
import datetime as dt import datetime as dt
import pytz import pytz
@ -16,7 +10,7 @@ UTC = DEFAULT_TIME_ZONE = pytz.utc
def set_default_time_zone(time_zone): def set_default_time_zone(time_zone):
""" Sets a default time zone to be used when none is specified. """ """Set a default time zone to be used when none is specified."""
global DEFAULT_TIME_ZONE # pylint: disable=global-statement global DEFAULT_TIME_ZONE # pylint: disable=global-statement
assert isinstance(time_zone, dt.tzinfo) assert isinstance(time_zone, dt.tzinfo)
@ -25,7 +19,7 @@ def set_default_time_zone(time_zone):
def get_time_zone(time_zone_str): def get_time_zone(time_zone_str):
""" Get time zone from string. Return None if unable to determine. """ """Get time zone from string. Return None if unable to determine."""
try: try:
return pytz.timezone(time_zone_str) return pytz.timezone(time_zone_str)
except pytz.exceptions.UnknownTimeZoneError: except pytz.exceptions.UnknownTimeZoneError:
@ -33,18 +27,20 @@ def get_time_zone(time_zone_str):
def utcnow(): def utcnow():
""" Get now in UTC time. """ """Get now in UTC time."""
return dt.datetime.now(UTC) return dt.datetime.now(UTC)
def now(time_zone=None): def now(time_zone=None):
""" Get now in specified time zone. """ """Get now in specified time zone."""
return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE) return dt.datetime.now(time_zone or DEFAULT_TIME_ZONE)
def as_utc(dattim): def as_utc(dattim):
""" Return a datetime as UTC time. """Return a datetime as UTC time.
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE. """
Assumes datetime without tzinfo to be in the DEFAULT_TIME_ZONE.
"""
if dattim.tzinfo == UTC: if dattim.tzinfo == UTC:
return dattim return dattim
elif dattim.tzinfo is None: elif dattim.tzinfo is None:
@ -54,7 +50,7 @@ def as_utc(dattim):
def as_local(dattim): def as_local(dattim):
""" Converts a UTC datetime object to local time_zone. """ """Convert a UTC datetime object to local time zone."""
if dattim.tzinfo == DEFAULT_TIME_ZONE: if dattim.tzinfo == DEFAULT_TIME_ZONE:
return dattim return dattim
elif dattim.tzinfo is None: elif dattim.tzinfo is None:
@ -64,12 +60,12 @@ def as_local(dattim):
def utc_from_timestamp(timestamp): def utc_from_timestamp(timestamp):
""" Returns a UTC time from a timestamp. """ """Return a UTC time from a timestamp."""
return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC) return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
def start_of_local_day(dt_or_d=None): def start_of_local_day(dt_or_d=None):
""" Return local datetime object of start of day from date or datetime. """ """Return local datetime object of start of day from date or datetime."""
if dt_or_d is None: if dt_or_d is None:
dt_or_d = now().date() dt_or_d = now().date()
elif isinstance(dt_or_d, dt.datetime): elif isinstance(dt_or_d, dt.datetime):
@ -80,12 +76,12 @@ def start_of_local_day(dt_or_d=None):
def datetime_to_local_str(dattim): def datetime_to_local_str(dattim):
""" Converts datetime to specified time_zone and returns a string. """ """Convert datetime to specified time_zone and returns a string."""
return datetime_to_str(as_local(dattim)) return datetime_to_str(as_local(dattim))
def datetime_to_str(dattim): def datetime_to_str(dattim):
""" Converts datetime to a string format. """Convert datetime to a string format.
@rtype : str @rtype : str
""" """
@ -93,7 +89,7 @@ def datetime_to_str(dattim):
def datetime_to_time_str(dattim): def datetime_to_time_str(dattim):
""" Converts datetime to a string containing only the time. """Convert datetime to a string containing only the time.
@rtype : str @rtype : str
""" """
@ -101,7 +97,7 @@ def datetime_to_time_str(dattim):
def datetime_to_date_str(dattim): def datetime_to_date_str(dattim):
""" Converts datetime to a string containing only the date. """Convert datetime to a string containing only the date.
@rtype : str @rtype : str
""" """
@ -109,7 +105,7 @@ def datetime_to_date_str(dattim):
def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT): def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
""" Converts a string to a UTC datetime object. """Convert a string to a UTC datetime object.
@rtype: datetime @rtype: datetime
""" """
@ -121,7 +117,7 @@ def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT):
def date_str_to_date(dt_str): def date_str_to_date(dt_str):
""" Converts a date string to a date object. """ """Convert a date string to a date object."""
try: try:
return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date() return dt.datetime.strptime(dt_str, DATE_STR_FORMAT).date()
except ValueError: # If dt_str did not match our format except ValueError: # If dt_str did not match our format
@ -129,13 +125,14 @@ def date_str_to_date(dt_str):
def strip_microseconds(dattim): def strip_microseconds(dattim):
""" Returns a copy of dattime object but with microsecond set to 0. """ """Return a copy of dattime object but with microsecond set to 0."""
return dattim.replace(microsecond=0) return dattim.replace(microsecond=0)
def parse_time_str(time_str): def parse_time_str(time_str):
""" Parse a time string (00:20:00) into Time object. """Parse a time string (00:20:00) into Time object.
Return None if invalid.
Return None if invalid.
""" """
parts = str(time_str).split(':') parts = str(time_str).split(':')
if len(parts) < 2: if len(parts) < 2:

View File

@ -1,8 +1,4 @@
""" """Helpers to install PyPi packages."""
homeassistant.util.package
~~~~~~~~~~~~~~~~~~~~~~~~~~
Helpers to install PyPi packages.
"""
import logging import logging
import os import os
import subprocess import subprocess
@ -17,8 +13,8 @@ INSTALL_LOCK = threading.Lock()
def install_package(package, upgrade=True, target=None): def install_package(package, upgrade=True, target=None):
""" """Install a package on PyPi. Accepts pip compatible package strings.
Install a package on PyPi. Accepts pip compatible package strings.
Return boolean if install successful. Return boolean if install successful.
""" """
# Not using 'import pip; pip.main([])' because it breaks the logger # Not using 'import pip; pip.main([])' because it breaks the logger
@ -41,8 +37,8 @@ def install_package(package, upgrade=True, target=None):
def check_package_exists(package, lib_dir): def check_package_exists(package, lib_dir):
""" """Check if a package is installed globally or in lib_dir.
Check if a package is installed globally or in lib_dir.
Returns True when the requirement is met. Returns True when the requirement is met.
Returns False when the package is not installed or doesn't meet req. Returns False when the package is not installed or doesn't meet req.
""" """

View File

@ -1,15 +1,11 @@
""" """Temperature util functions."""
homeassistant.util.temperature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Temperature util functions.
"""
def fahrenheit_to_celcius(fahrenheit): def fahrenheit_to_celcius(fahrenheit):
""" Convert a Fahrenheit temperature to Celcius. """ """Convert a Fahrenheit temperature to Celsius."""
return (fahrenheit - 32.0) / 1.8 return (fahrenheit - 32.0) / 1.8
def celcius_to_fahrenheit(celcius): def celcius_to_fahrenheit(celcius):
""" Convert a Celcius temperature to Fahrenheit. """ """Convert a Celsius temperature to Fahrenheit."""
return celcius * 1.8 + 32.0 return celcius * 1.8 + 32.0

View File

@ -1,6 +1,4 @@
""" """YAML utility functions."""
YAML utility functions.
"""
import logging import logging
import os import os
from collections import OrderedDict from collections import OrderedDict
@ -26,8 +24,7 @@ def load_yaml(fname):
def _include_yaml(loader, node): def _include_yaml(loader, node):
""" """Load another YAML file and embeds it using the !include tag.
Loads another YAML file and embeds it using the !include tag.
Example: Example:
device_tracker: !include device_tracker.yaml device_tracker: !include device_tracker.yaml
@ -37,9 +34,7 @@ def _include_yaml(loader, node):
def _ordered_dict(loader, node): def _ordered_dict(loader, node):
""" """Load YAML mappings into an ordered dict to preserve key order."""
Loads YAML mappings into an ordered dict to preserve key order.
"""
loader.flatten_mapping(node) loader.flatten_mapping(node)
return OrderedDict(loader.construct_pairs(node)) return OrderedDict(loader.construct_pairs(node))