4
4
import json
5
5
import logging
6
6
import os
7
+ import sys
7
8
9
+ import pkg_resources
8
10
import voluptuous as vol
9
11
10
12
from homeassistant .config import async_hass_config_yaml
19
21
import homeassistant .helpers .config_validation as cv
20
22
from homeassistant .helpers .restore_state import RestoreStateData
21
23
from homeassistant .loader import bind_hass
24
+ from homeassistant .requirements import async_process_requirements
22
25
23
26
from .const import (
24
27
CONF_ALLOW_ALL_IMPORTS ,
38
41
from .state import State
39
42
from .trigger import TrigTime
40
43
44
+ if sys .version_info [:2 ] >= (3 , 8 ):
45
+ from importlib .metadata import ( # pylint: disable=no-name-in-module,import-error
46
+ PackageNotFoundError ,
47
+ version ,
48
+ )
49
+ else :
50
+ from importlib_metadata import ( # pylint: disable=import-error
51
+ PackageNotFoundError ,
52
+ version ,
53
+ )
54
+
41
55
_LOGGER = logging .getLogger (LOGGER_PATH )
42
56
43
57
PYSCRIPT_SCHEMA = vol .Schema (
@@ -133,6 +147,7 @@ async def async_setup_entry(hass, config_entry):
133
147
134
148
State .set_pyscript_config (config_entry .data )
135
149
150
+ await install_requirements (hass )
136
151
await load_scripts (hass , config_entry .data )
137
152
138
153
async def reload_scripts_handler (call ):
@@ -150,6 +165,7 @@ async def reload_scripts_handler(call):
150
165
151
166
await unload_scripts (global_ctx_only = global_ctx_only )
152
167
168
+ await install_requirements (hass )
153
169
await load_scripts (hass , config_entry .data , global_ctx_only = global_ctx_only )
154
170
155
171
start_global_contexts (global_ctx_only = global_ctx_only )
@@ -250,6 +266,55 @@ async def unload_scripts(global_ctx_only=None, unload_all=False):
250
266
await GlobalContextMgr .delete (global_ctx_name )
251
267
252
268
269
+ @bind_hass
270
+ async def install_requirements (hass ):
271
+ """Install missing requirements from requirements.txt."""
272
+ requirements_path = os .path .join (hass .config .path (FOLDER ), "requirements.txt" )
273
+
274
+ if os .path .exists (requirements_path ):
275
+ with open (requirements_path , "r" ) as requirements_file :
276
+ requirements_to_install = []
277
+ for pkg in requirements_file .readlines ():
278
+ # Remove inline comments which are accepted by pip but not by Home
279
+ # Assistant's installation method.
280
+ # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python
281
+ i = pkg .find ("#" )
282
+ if i >= 0 :
283
+ pkg = pkg [:i ].strip ()
284
+
285
+ try :
286
+ # Attempt to get version of package. Do nothing if it's found since
287
+ # we want to use the version that's already installed to be safe
288
+ requirement = pkg_resources .Requirement .parse (pkg )
289
+ requirement_installed_version = version (requirement .project_name )
290
+
291
+ if requirement_installed_version in requirement :
292
+ _LOGGER .debug ("`%s` already found" , requirement .project_name )
293
+ else :
294
+ _LOGGER .debug (
295
+ (
296
+ "`%s` already found but found version `%s` does not"
297
+ " match requirement. Keeping found version."
298
+ ),
299
+ requirement .project_name ,
300
+ requirement_installed_version ,
301
+ )
302
+ except PackageNotFoundError :
303
+ # Since package wasn't found, add it to installation list
304
+ _LOGGER .debug ("%s not found, adding it to package installation list" , pkg )
305
+ requirements_to_install .append (pkg )
306
+ except ValueError :
307
+ # Not valid requirements line so it can be skipped
308
+ _LOGGER .debug ("Ignoring `%s` because it is not a valid package" , pkg )
309
+ if requirements_to_install :
310
+ _LOGGER .info ("Installing the following packages: %s" , "," .join (requirements_to_install ))
311
+ await async_process_requirements (hass , DOMAIN , requirements_to_install )
312
+ else :
313
+ _LOGGER .info ("All requirements are already available." )
314
+ else :
315
+ _LOGGER .info ("No requirements.txt found so nothing to install." )
316
+
317
+
253
318
@bind_hass
254
319
async def load_scripts (hass , data , global_ctx_only = None ):
255
320
"""Load all python scripts in FOLDER."""
0 commit comments