Skip to content

Commit 013be73

Browse files
committed
Check for requirements.txt and install any not found packages from it
1 parent c409d4a commit 013be73

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

custom_components/pyscript/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import json
55
import logging
66
import os
7+
import sys
78

9+
import pkg_resources
810
import voluptuous as vol
911

1012
from homeassistant.config import async_hass_config_yaml
@@ -19,6 +21,7 @@
1921
import homeassistant.helpers.config_validation as cv
2022
from homeassistant.helpers.restore_state import RestoreStateData
2123
from homeassistant.loader import bind_hass
24+
from homeassistant.requirements import async_process_requirements
2225

2326
from .const import (
2427
CONF_ALLOW_ALL_IMPORTS,
@@ -38,6 +41,17 @@
3841
from .state import State
3942
from .trigger import TrigTime
4043

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+
4155
_LOGGER = logging.getLogger(LOGGER_PATH)
4256

4357
PYSCRIPT_SCHEMA = vol.Schema(
@@ -133,6 +147,7 @@ async def async_setup_entry(hass, config_entry):
133147

134148
State.set_pyscript_config(config_entry.data)
135149

150+
await install_requirements(hass)
136151
await load_scripts(hass, config_entry.data)
137152

138153
async def reload_scripts_handler(call):
@@ -150,6 +165,7 @@ async def reload_scripts_handler(call):
150165

151166
await unload_scripts(global_ctx_only=global_ctx_only)
152167

168+
await install_requirements(hass)
153169
await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only)
154170

155171
start_global_contexts(global_ctx_only=global_ctx_only)
@@ -250,6 +266,55 @@ async def unload_scripts(global_ctx_only=None, unload_all=False):
250266
await GlobalContextMgr.delete(global_ctx_name)
251267

252268

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+
253318
@bind_hass
254319
async def load_scripts(hass, data, global_ctx_only=None):
255320
"""Load all python scripts in FOLDER."""

0 commit comments

Comments
 (0)