diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 40cde95751f..59c2cd11abb 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -10,9 +10,6 @@ from homeassistant import bootstrap import homeassistant.config as config_util from homeassistant.components import frontend, demo -USER_DATA_DIR = os.getenv('APPDATA') if os.name == "nt" \ - else os.path.expanduser('~') - def validate_python(): """ Validate we're running the right Python version. """ @@ -83,7 +80,7 @@ def get_arguments(): parser.add_argument( '-c', '--config', metavar='path_to_config_dir', - default=os.path.join(USER_DATA_DIR, '.homeassistant'), + default=config_util.get_default_config_dir(), help="Directory that contains the Home Assistant configuration") parser.add_argument( '--demo-mode', @@ -112,7 +109,7 @@ def main(): hass = bootstrap.from_config_dict({ frontend.DOMAIN: {}, demo.DOMAIN: {} - }) + }, config_dir=config_dir) else: hass = bootstrap.from_config_file(config_path) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e5f6d2b9672..e4cd307019e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -61,13 +61,13 @@ def setup_component(hass, domain, config=None): return True -def _handle_requirements(component, name): +def _handle_requirements(hass, component, name): """ Installs requirements for component. """ if not hasattr(component, 'REQUIREMENTS'): return True for req in component.REQUIREMENTS: - if not pkg_util.install_package(req): + if not pkg_util.install_package(req, target=hass.config.path('lib')): _LOGGER.error('Not initializing %s because could not install ' 'dependency %s', name, req) return False @@ -88,7 +88,7 @@ def _setup_component(hass, domain, config): domain, ", ".join(missing_deps)) return False - if not _handle_requirements(component, domain): + if not _handle_requirements(hass, component, domain): return False try: @@ -138,14 +138,14 @@ def prepare_setup_platform(hass, config, domain, platform_name): component) return None - if not _handle_requirements(platform, platform_path): + if not _handle_requirements(hass, platform, platform_path): return None return platform # pylint: disable=too-many-branches, too-many-statements -def from_config_dict(config, hass=None): +def from_config_dict(config, hass=None, config_dir=None): """ Tries to configure Home Assistant from a config dict. @@ -153,6 +153,9 @@ def from_config_dict(config, hass=None): """ if hass is None: hass = core.HomeAssistant() + if config_dir is not None: + hass.config.config_dir = os.path.abspath(config_dir) + hass.config.mount_local_path() process_ha_core_config(hass, config.get(core.DOMAIN, {})) @@ -196,6 +199,7 @@ def from_config_file(config_path, hass=None): # Set config dir to directory holding config file hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) + hass.config.mount_local_path() config_dict = config_util.load_config_file(config_path) diff --git a/homeassistant/config.py b/homeassistant/config.py index 54ad297e62a..ca2d43eeb40 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,7 +7,7 @@ Module to help with parsing and generating configuration files. import logging import os -from homeassistant.core import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE) @@ -16,6 +16,7 @@ import homeassistant.util.location as loc_util _LOGGER = logging.getLogger(__name__) YAML_CONFIG_FILE = 'configuration.yaml' +CONFIG_DIR_NAME = '.homeassistant' DEFAULT_CONFIG = ( # Tuples (attribute, default, auto detect property, description) @@ -39,6 +40,13 @@ DEFAULT_COMPONENTS = { } +def get_default_config_dir(): + """ Put together the default configuration directory based on OS. """ + data_dir = os.getenv('APPDATA') if os.name == "nt" \ + else os.path.expanduser('~') + return os.path.join(data_dir, CONFIG_DIR_NAME) + + def ensure_config_exists(config_dir, detect_location=True): """ Ensures a config file exists in given config dir. Creating a default one if needed. diff --git a/homeassistant/core.py b/homeassistant/core.py index 76b4b38f3fc..309c9336706 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -7,6 +7,7 @@ of entities and react to changes. """ import os +import sys import time import logging import threading @@ -21,9 +22,12 @@ from homeassistant.const import ( EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) +from homeassistant.exceptions import ( + HomeAssistantError, InvalidEntityFormatError, NoEntitySpecifiedError) import homeassistant.util as util import homeassistant.util.dt as date_util import homeassistant.helpers.temperature as temp_helper +from homeassistant.config import get_default_config_dir DOMAIN = "homeassistant" @@ -660,7 +664,11 @@ class Config(object): self.api = None # Directory that holds the configuration - self.config_dir = os.path.join(os.getcwd(), 'config') + self.config_dir = get_default_config_dir() + + def mount_local_path(self): + """ Add local library to Python Path """ + sys.path.insert(0, self.path('lib')) def path(self, *path): """ Returns path to the file within the config dir. """ @@ -695,21 +703,6 @@ class Config(object): } -class HomeAssistantError(Exception): - """ General Home Assistant exception occured. """ - pass - - -class InvalidEntityFormatError(HomeAssistantError): - """ When an invalid formatted entity is encountered. """ - pass - - -class NoEntitySpecifiedError(HomeAssistantError): - """ When no entity is specified. """ - pass - - def create_timer(hass, interval=TIMER_INTERVAL): """ Creates a timer. Timer will start on HOMEASSISTANT_START. """ # We want to be able to fire every time a minute starts (seconds=0). diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py new file mode 100644 index 00000000000..4ecd22f9e43 --- /dev/null +++ b/homeassistant/exceptions.py @@ -0,0 +1,15 @@ +""" Exceptions used by Home Assistant """ + +class HomeAssistantError(Exception): + """ General Home Assistant exception occured. """ + pass + + +class InvalidEntityFormatError(HomeAssistantError): + """ When an invalid formatted entity is encountered. """ + pass + + +class NoEntitySpecifiedError(HomeAssistantError): + """ When no entity is specified. """ + pass diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index d220a5a7e61..3719fecb9ff 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,4 +1,5 @@ """Helpers to install PyPi packages.""" +import os import subprocess import sys @@ -8,15 +9,16 @@ from . import environment as env INSTALL_USER = not env.is_virtual() -def install_package(package, upgrade=False, user=INSTALL_USER): +def install_package(package, upgrade=False, target=None): """Install a package on PyPi. Accepts pip compatible package strings. Return boolean if install successfull.""" # Not using 'import pip; pip.main([])' because it breaks the logger - args = [sys.executable, '-m', 'pip', 'install', '--quiet', package] + args = [sys.executable, '-m', 'pip', 'install', '--quiet', + '--isolated', '-I', package] if upgrade: args.append('--upgrade') - if user: - args.append('--user') + if target: + args += ['--target', os.path.abspath(target)] try: return 0 == subprocess.call(args) except subprocess.SubprocessError: diff --git a/requirements.txt b/requirements.txt index f851f70e86f..14c276aa5be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,119 +1,3 @@ -# Required for Home Assistant core -requests>=2.0 -pyyaml>=3.11 -pytz>=2015.2 - -# Optional, needed for specific components - -# Sun (sun) -astral>=0.8.1 - -# Philips Hue library (lights.hue) -phue>=0.8 - -# Limitlessled/Easybulb/Milight library (lights.limitlessled) -ledcontroller>=1.0.7 - -# Chromecast bindings (media_player.cast) -pychromecast>=0.6.10 - -# Keyboard (keyboard) -pyuserinput>=0.1.9 - -# Tellstick bindings (*.tellstick) -tellcore-py>=1.0.4 - -# Nmap bindings (device_tracker.nmap) -python-libnmap>=0.6.3 - -# PushBullet bindings (notify.pushbullet) -pushbullet.py>=0.7.1 - -# Nest Thermostat bindings (thermostat.nest) -python-nest>=2.4.0 - -# Z-Wave (*.zwave) -pydispatcher>=2.0.5 - -# ISY994 bindings (*.isy994) -PyISY>=1.0.5 - -# PSutil (sensor.systemmonitor) -psutil>=3.0.0 - -# Pushover bindings (notify.pushover) -python-pushover>=0.2 - -# Transmission Torrent Client (*.transmission) -transmissionrpc>=0.11 - -# OpenWeatherMap Web API (sensor.openweathermap) -pyowm>=2.2.1 - -# XMPP Bindings (notify.xmpp) -sleekxmpp>=1.3.1 -dnspython3>=1.12.0 - -# Blockchain (sensor.bitcoin) -blockchain>=1.1.2 - -# MPD Bindings (media_player.mpd) -python-mpd2>=0.5.4 - -# Hikvision (switch.hikvisioncam) -hikvision>=0.4 - -# console log coloring -colorlog>=2.6.0 - -# JSON-RPC interface (media_player.kodi) -jsonrpc-requests>=0.1 - -# Forecast.io Bindings (sensor.forecast) -python-forecastio>=1.3.3 - -# Firmata Bindings (*.arduino) -PyMata==2.07a - -# Rfxtrx sensor (sensor.rfxtrx) -https://github.com/Danielhiversen/pyRFXtrx/archive/master.zip - -# Mysensors -https://github.com/theolind/pymysensors/archive/master.zip#egg=pymysensors-0.1 - -# Netgear (device_tracker.netgear) -pynetgear>=0.3 - -# Netdisco (discovery) -netdisco>=0.3 - -# Wemo (switch.wemo) -pywemo>=0.2 - -# Wink (*.wink) -https://github.com/balloob/python-wink/archive/master.zip#pywink>=0.1 - -# Slack notifier (notify.slack) -slacker>=0.6.8 - -# Temper sensors (sensor.temper) -https://github.com/rkabadi/temper-python/archive/master.zip - -# PyEdimax -https://github.com/rkabadi/pyedimax/archive/master.zip - -# RPI-GPIO platform (*.rpi_gpio) -RPi.GPIO >=0.5.11 - -# Adafruit temperature/humidity sensor -# uncomment on a Raspberry Pi / Beaglebone -#git+git://github.com/mala-zaba/Adafruit_Python_DHT - -# PAHO MQTT Binding (mqtt) -paho-mqtt>=1.1 - -# PyModbus (modbus) -https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0 - -# Verisure (verisure) -https://github.com/persandstrom/python-verisure/archive/master.zip +requests==2.7.0 +pyyaml==3.11 +pytz==2015.4