From 40d0d18a8a56886151649145fe5ec6738033533a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 30 Oct 2020 23:21:14 -0400 Subject: [PATCH 1/5] Check for requirements.txt and install any not found packages from it --- custom_components/pyscript/__init__.py | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 47758bc..9efb589 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -4,7 +4,9 @@ import json import logging import os +import sys +import pkg_resources import voluptuous as vol from homeassistant.config import async_hass_config_yaml @@ -19,6 +21,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreStateData from homeassistant.loader import bind_hass +from homeassistant.requirements import async_process_requirements from .const import ( CONF_ALLOW_ALL_IMPORTS, @@ -38,6 +41,17 @@ from .state import State from .trigger import TrigTime +if sys.version_info[:2] >= (3, 8): + from importlib.metadata import ( # pylint: disable=no-name-in-module,import-error + PackageNotFoundError, + version, + ) +else: + from importlib_metadata import ( # pylint: disable=import-error + PackageNotFoundError, + version, + ) + _LOGGER = logging.getLogger(LOGGER_PATH) PYSCRIPT_SCHEMA = vol.Schema( @@ -133,6 +147,7 @@ async def async_setup_entry(hass, config_entry): State.set_pyscript_config(config_entry.data) + await install_requirements(hass) await load_scripts(hass, config_entry.data) async def reload_scripts_handler(call): @@ -150,6 +165,7 @@ async def reload_scripts_handler(call): await unload_scripts(global_ctx_only=global_ctx_only) + await install_requirements(hass) await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only) start_global_contexts(global_ctx_only=global_ctx_only) @@ -250,6 +266,55 @@ async def unload_scripts(global_ctx_only=None, unload_all=False): await GlobalContextMgr.delete(global_ctx_name) +@bind_hass +async def install_requirements(hass): + """Install missing requirements from requirements.txt.""" + requirements_path = os.path.join(hass.config.path(FOLDER), "requirements.txt") + + if os.path.exists(requirements_path): + with open(requirements_path, "r") as requirements_file: + requirements_to_install = [] + for pkg in requirements_file.readlines(): + # Remove inline comments which are accepted by pip but not by Home + # Assistant's installation method. + # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python + i = pkg.find("#") + if i >= 0: + pkg = pkg[:i].strip() + + try: + # Attempt to get version of package. Do nothing if it's found since + # we want to use the version that's already installed to be safe + requirement = pkg_resources.Requirement.parse(pkg) + requirement_installed_version = version(requirement.project_name) + + if requirement_installed_version in requirement: + _LOGGER.debug("`%s` already found", requirement.project_name) + else: + _LOGGER.debug( + ( + "`%s` already found but found version `%s` does not" + " match requirement. Keeping found version." + ), + requirement.project_name, + requirement_installed_version, + ) + except PackageNotFoundError: + # Since package wasn't found, add it to installation list + _LOGGER.debug("%s not found, adding it to package installation list", pkg) + requirements_to_install.append(pkg) + except ValueError: + # Not valid requirements line so it can be skipped + _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) + if requirements_to_install: + _LOGGER.info("Installing the following packages: %s", ",".join(requirements_to_install)) + await async_process_requirements(hass, DOMAIN, requirements_to_install) + else: + _LOGGER.info("All requirements are already available.") + else: + _LOGGER.info("No requirements.txt found so nothing to install.") + + @bind_hass async def load_scripts(hass, data, global_ctx_only=None): """Load all python scripts in FOLDER.""" From 30db0dc410bb5685a7f9c47de93f94f3c4b863f8 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 31 Oct 2020 00:13:26 -0400 Subject: [PATCH 2/5] small tweaks and add a test --- custom_components/pyscript/__init__.py | 11 ++++++----- tests/test_init.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 9efb589..961a4b0 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -44,12 +44,12 @@ if sys.version_info[:2] >= (3, 8): from importlib.metadata import ( # pylint: disable=no-name-in-module,import-error PackageNotFoundError, - version, + version as installed_version, ) else: from importlib_metadata import ( # pylint: disable=import-error PackageNotFoundError, - version, + version as installed_version, ) _LOGGER = logging.getLogger(LOGGER_PATH) @@ -280,13 +280,14 @@ async def install_requirements(hass): # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python i = pkg.find("#") if i >= 0: - pkg = pkg[:i].strip() + pkg = pkg[:i] + pkg = pkg.strip() try: # Attempt to get version of package. Do nothing if it's found since # we want to use the version that's already installed to be safe requirement = pkg_resources.Requirement.parse(pkg) - requirement_installed_version = version(requirement.project_name) + requirement_installed_version = installed_version(requirement.project_name) if requirement_installed_version in requirement: _LOGGER.debug("`%s` already found", requirement.project_name) @@ -307,7 +308,7 @@ async def install_requirements(hass): # Not valid requirements line so it can be skipped _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) if requirements_to_install: - _LOGGER.info("Installing the following packages: %s", ",".join(requirements_to_install)) + _LOGGER.info("Installing the following packages: %s", ", ".join(requirements_to_install)) await async_process_requirements(hass, DOMAIN, requirements_to_install) else: _LOGGER.info("All requirements are already available.") diff --git a/tests/test_init.py b/tests/test_init.py index a670998..d06a90c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -482,3 +482,27 @@ async def test_misc_errors(hass, caplog): assert "State class is not meant to be instantiated" in caplog.text assert "Event class is not meant to be instantiated" in caplog.text assert "TrigTime class is not meant to be instantiated" in caplog.text + + +async def test_install_requirements(hass): + """Test install_requirements function.""" + requirements = """ +pytube==9.7.0 +# another test comment +pykakasi==2.0.1 # test comment + +""" + + with patch("os.path.exists", return_value=True), patch( + "custom_components.pyscript.async_hass_config_yaml", return_value={} + ), patch("custom_components.pyscript.open", mock_open(read_data=requirements), create=True,), patch( + "custom_components.pyscript.async_process_requirements" + ) as install_requirements: + await setup_script(hass, None, dt(2020, 7, 1, 11, 59, 59, 999999), "") + assert install_requirements.call_args[0][2] == ["pytube==9.7.0", "pykakasi==2.0.1"] + install_requirements.reset_mock() + # Because in tests, packages are not installed, we fake that they are + # installed so we can test that we don't attempt to install them + with patch("custom_components.pyscript.installed_version", return_value="2.0.1"): + await hass.services.async_call("pyscript", "reload", {}, blocking=True) + assert not install_requirements.called From 858715043834d250ff5e3c419c3b58c33c6e71eb Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 31 Oct 2020 14:39:57 -0400 Subject: [PATCH 3/5] address most review comments --- custom_components/pyscript/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 961a4b0..7683e01 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -147,7 +147,7 @@ async def async_setup_entry(hass, config_entry): State.set_pyscript_config(config_entry.data) - await install_requirements(hass) + await hass.async_add_executor_job(install_requirements, hass) await load_scripts(hass, config_entry.data) async def reload_scripts_handler(call): @@ -165,7 +165,7 @@ async def reload_scripts_handler(call): await unload_scripts(global_ctx_only=global_ctx_only) - await install_requirements(hass) + await hass.async_add_executor_job(install_requirements, hass) await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only) start_global_contexts(global_ctx_only=global_ctx_only) @@ -292,7 +292,7 @@ async def install_requirements(hass): if requirement_installed_version in requirement: _LOGGER.debug("`%s` already found", requirement.project_name) else: - _LOGGER.debug( + _LOGGER.warning( ( "`%s` already found but found version `%s` does not" " match requirement. Keeping found version." @@ -308,12 +308,14 @@ async def install_requirements(hass): # Not valid requirements line so it can be skipped _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) if requirements_to_install: - _LOGGER.info("Installing the following packages: %s", ", ".join(requirements_to_install)) + _LOGGER.info( + "Installing the following packages from %s: %s", + requirements_path, + ", ".join(requirements_to_install), + ) await async_process_requirements(hass, DOMAIN, requirements_to_install) else: - _LOGGER.info("All requirements are already available.") - else: - _LOGGER.info("No requirements.txt found so nothing to install.") + _LOGGER.debug("All packages in %s are already available", requirements_path) @bind_hass From fed7cfdd0779c18d03f5f8f483f633640fcc6e6a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 31 Oct 2020 14:57:36 -0400 Subject: [PATCH 4/5] support requirements.txt and apps and modules directories --- custom_components/pyscript/__init__.py | 93 +++++++++++++------------- tests/test_init.py | 8 +-- 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 7683e01..0863e67 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -269,53 +269,52 @@ async def unload_scripts(global_ctx_only=None, unload_all=False): @bind_hass async def install_requirements(hass): """Install missing requirements from requirements.txt.""" - requirements_path = os.path.join(hass.config.path(FOLDER), "requirements.txt") - - if os.path.exists(requirements_path): - with open(requirements_path, "r") as requirements_file: - requirements_to_install = [] - for pkg in requirements_file.readlines(): - # Remove inline comments which are accepted by pip but not by Home - # Assistant's installation method. - # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python - i = pkg.find("#") - if i >= 0: - pkg = pkg[:i] - pkg = pkg.strip() - - try: - # Attempt to get version of package. Do nothing if it's found since - # we want to use the version that's already installed to be safe - requirement = pkg_resources.Requirement.parse(pkg) - requirement_installed_version = installed_version(requirement.project_name) - - if requirement_installed_version in requirement: - _LOGGER.debug("`%s` already found", requirement.project_name) - else: - _LOGGER.warning( - ( - "`%s` already found but found version `%s` does not" - " match requirement. Keeping found version." - ), - requirement.project_name, - requirement_installed_version, - ) - except PackageNotFoundError: - # Since package wasn't found, add it to installation list - _LOGGER.debug("%s not found, adding it to package installation list", pkg) - requirements_to_install.append(pkg) - except ValueError: - # Not valid requirements line so it can be skipped - _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) - if requirements_to_install: - _LOGGER.info( - "Installing the following packages from %s: %s", - requirements_path, - ", ".join(requirements_to_install), - ) - await async_process_requirements(hass, DOMAIN, requirements_to_install) - else: - _LOGGER.debug("All packages in %s are already available", requirements_path) + for root in ("", "apps/*", "modules/*"): + for requirements_path in glob.glob(os.path.join(hass.config.path(FOLDER), root, "requirements.txt")): + with open(requirements_path, "r") as requirements_file: + requirements_to_install = [] + for pkg in requirements_file.readlines(): + # Remove inline comments which are accepted by pip but not by Home + # Assistant's installation method. + # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python + i = pkg.find("#") + if i >= 0: + pkg = pkg[:i] + pkg = pkg.strip() + + try: + # Attempt to get version of package. Do nothing if it's found since + # we want to use the version that's already installed to be safe + requirement = pkg_resources.Requirement.parse(pkg) + requirement_installed_version = installed_version(requirement.project_name) + + if requirement_installed_version in requirement: + _LOGGER.debug("`%s` already found", requirement.project_name) + else: + _LOGGER.warning( + ( + "`%s` already found but found version `%s` does not" + " match requirement. Keeping found version." + ), + requirement.project_name, + requirement_installed_version, + ) + except PackageNotFoundError: + # Since package wasn't found, add it to installation list + _LOGGER.debug("%s not found, adding it to package installation list", pkg) + requirements_to_install.append(pkg) + except ValueError: + # Not valid requirements line so it can be skipped + _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) + if requirements_to_install: + _LOGGER.info( + "Installing the following packages from %s: %s", + requirements_path, + ", ".join(requirements_to_install), + ) + await async_process_requirements(hass, DOMAIN, requirements_to_install) + else: + _LOGGER.debug("All packages in %s are already available", requirements_path) @bind_hass diff --git a/tests/test_init.py b/tests/test_init.py index d06a90c..b04f022 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -493,11 +493,9 @@ async def test_install_requirements(hass): """ - with patch("os.path.exists", return_value=True), patch( - "custom_components.pyscript.async_hass_config_yaml", return_value={} - ), patch("custom_components.pyscript.open", mock_open(read_data=requirements), create=True,), patch( - "custom_components.pyscript.async_process_requirements" - ) as install_requirements: + with patch("custom_components.pyscript.async_hass_config_yaml", return_value={}), patch( + "custom_components.pyscript.open", mock_open(read_data=requirements), create=True, + ), patch("custom_components.pyscript.async_process_requirements") as install_requirements: await setup_script(hass, None, dt(2020, 7, 1, 11, 59, 59, 999999), "") assert install_requirements.call_args[0][2] == ["pytube==9.7.0", "pykakasi==2.0.1"] install_requirements.reset_mock() From c1c9345bee6acb972ff74be9241060ef95306b96 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 31 Oct 2020 16:32:26 -0400 Subject: [PATCH 5/5] move sync logic into dedicated function and update test --- custom_components/pyscript/__init__.py | 112 ++++++++++++++----------- custom_components/pyscript/const.py | 3 + tests/conftest.py | 10 +++ tests/test_decorator_errors.py | 10 +++ tests/test_function.py | 10 +++ tests/test_init.py | 32 +++++-- tests/test_jupyter.py | 10 +++ tests/test_unique.py | 10 +++ 8 files changed, 141 insertions(+), 56 deletions(-) create mode 100644 tests/conftest.py diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index 0863e67..8d3ad83 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -30,6 +30,8 @@ DOMAIN, FOLDER, LOGGER_PATH, + REQUIREMENTS_FILE, + REQUIREMENTS_PATHS, SERVICE_JUPYTER_KERNEL_START, UNSUB_LISTENERS, ) @@ -147,7 +149,7 @@ async def async_setup_entry(hass, config_entry): State.set_pyscript_config(config_entry.data) - await hass.async_add_executor_job(install_requirements, hass) + await install_requirements(hass) await load_scripts(hass, config_entry.data) async def reload_scripts_handler(call): @@ -165,7 +167,7 @@ async def reload_scripts_handler(call): await unload_scripts(global_ctx_only=global_ctx_only) - await hass.async_add_executor_job(install_requirements, hass) + await install_requirements(hass) await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only) start_global_contexts(global_ctx_only=global_ctx_only) @@ -266,55 +268,71 @@ async def unload_scripts(global_ctx_only=None, unload_all=False): await GlobalContextMgr.delete(global_ctx_name) +@bind_hass +def load_all_requirement_lines(hass, requirements_paths, requirements_file): + """Load all lines from requirements_file located in requirements_paths.""" + all_requirements = {} + for root in requirements_paths: + for requirements_path in glob.glob(os.path.join(hass.config.path(FOLDER), root, requirements_file)): + with open(requirements_path, "r") as requirements_fp: + all_requirements[requirements_path] = requirements_fp.readlines() + + return all_requirements + + @bind_hass async def install_requirements(hass): """Install missing requirements from requirements.txt.""" - for root in ("", "apps/*", "modules/*"): - for requirements_path in glob.glob(os.path.join(hass.config.path(FOLDER), root, "requirements.txt")): - with open(requirements_path, "r") as requirements_file: - requirements_to_install = [] - for pkg in requirements_file.readlines(): - # Remove inline comments which are accepted by pip but not by Home - # Assistant's installation method. - # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python - i = pkg.find("#") - if i >= 0: - pkg = pkg[:i] - pkg = pkg.strip() - - try: - # Attempt to get version of package. Do nothing if it's found since - # we want to use the version that's already installed to be safe - requirement = pkg_resources.Requirement.parse(pkg) - requirement_installed_version = installed_version(requirement.project_name) - - if requirement_installed_version in requirement: - _LOGGER.debug("`%s` already found", requirement.project_name) - else: - _LOGGER.warning( - ( - "`%s` already found but found version `%s` does not" - " match requirement. Keeping found version." - ), - requirement.project_name, - requirement_installed_version, - ) - except PackageNotFoundError: - # Since package wasn't found, add it to installation list - _LOGGER.debug("%s not found, adding it to package installation list", pkg) - requirements_to_install.append(pkg) - except ValueError: - # Not valid requirements line so it can be skipped - _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) - if requirements_to_install: - _LOGGER.info( - "Installing the following packages from %s: %s", - requirements_path, - ", ".join(requirements_to_install), - ) - await async_process_requirements(hass, DOMAIN, requirements_to_install) + all_requirements = await hass.async_add_executor_job( + load_all_requirement_lines, hass, REQUIREMENTS_PATHS, REQUIREMENTS_FILE + ) + requirements_to_install = [] + for requirements_path, pkg_lines in all_requirements.items(): + for pkg in pkg_lines: + # Remove inline comments which are accepted by pip but not by Home + # Assistant's installation method. + # https://rosettacode.org/wiki/Strip_comments_from_a_string#Python + i = pkg.find("#") + if i >= 0: + pkg = pkg[:i] + pkg = pkg.strip() + + if not pkg: + continue + + try: + # Attempt to get version of package. Do nothing if it's found since + # we want to use the version that's already installed to be safe + requirement = pkg_resources.Requirement.parse(pkg) + requirement_installed_version = installed_version(requirement.project_name) + + if requirement_installed_version in requirement: + _LOGGER.debug("`%s` already found", requirement.project_name) else: - _LOGGER.debug("All packages in %s are already available", requirements_path) + _LOGGER.warning( + ( + "`%s` already found but found version `%s` does not" + " match requirement. Keeping found version." + ), + requirement.project_name, + requirement_installed_version, + ) + except PackageNotFoundError: + # Since package wasn't found, add it to installation list + _LOGGER.debug("%s not found, adding it to package installation list", pkg) + requirements_to_install.append(pkg) + except ValueError: + # Not valid requirements line so it can be skipped + _LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg) + if requirements_to_install: + _LOGGER.info( + "Installing the following packages from %s: %s", + requirements_path, + ", ".join(requirements_to_install), + ) + await async_process_requirements(hass, DOMAIN, requirements_to_install) + else: + _LOGGER.debug("All packages in %s are already available", requirements_path) @bind_hass diff --git a/custom_components/pyscript/const.py b/custom_components/pyscript/const.py index d453189..82c0652 100644 --- a/custom_components/pyscript/const.py +++ b/custom_components/pyscript/const.py @@ -14,6 +14,9 @@ LOGGER_PATH = "custom_components.pyscript" +REQUIREMENTS_FILE = "requirements.txt" +REQUIREMENTS_PATHS = ("", "apps/*", "modules/*") + ALLOWED_IMPORTS = { "black", "cmath", diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..2e0369e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +"""Test configuration for pyscript.""" +from pytest import fixture +from pytest_homeassistant_custom_component.async_mock import patch + + +@fixture(autouse=True) +def bypass_package_install_fixture(): + """Bypass package installation.""" + with patch("custom_components.pyscript.async_process_requirements"): + yield diff --git a/tests/test_decorator_errors.py b/tests/test_decorator_errors.py index 9d0845e..fd48499 100644 --- a/tests/test_decorator_errors.py +++ b/tests/test_decorator_errors.py @@ -23,6 +23,16 @@ async def setup_script(hass, notify_q, now, source): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) diff --git a/tests/test_function.py b/tests/test_function.py index 29e5efe..4a333c0 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -109,6 +109,16 @@ async def setup_script(hass, notify_q, now, source): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={DOMAIN: {CONF_ALLOW_ALL_IMPORTS: True}} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {CONF_ALLOW_ALL_IMPORTS: True}}) diff --git a/tests/test_init.py b/tests/test_init.py index b04f022..cef329c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -32,6 +32,16 @@ async def setup_script(hass, notify_q, now, source): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) @@ -446,6 +456,16 @@ def func5(var_name=None, value=None): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): reload_param = {} if i % 2 == 1: @@ -486,17 +506,11 @@ async def test_misc_errors(hass, caplog): async def test_install_requirements(hass): """Test install_requirements function.""" - requirements = """ -pytube==9.7.0 -# another test comment -pykakasi==2.0.1 # test comment - -""" - with patch("custom_components.pyscript.async_hass_config_yaml", return_value={}), patch( - "custom_components.pyscript.open", mock_open(read_data=requirements), create=True, - ), patch("custom_components.pyscript.async_process_requirements") as install_requirements: + "custom_components.pyscript.async_process_requirements" + ) as install_requirements: await setup_script(hass, None, dt(2020, 7, 1, 11, 59, 59, 999999), "") + assert install_requirements.called assert install_requirements.call_args[0][2] == ["pytube==9.7.0", "pykakasi==2.0.1"] install_requirements.reset_mock() # Because in tests, packages are not installed, we fake that they are diff --git a/tests/test_jupyter.py b/tests/test_jupyter.py index db1d6fe..0c71459 100644 --- a/tests/test_jupyter.py +++ b/tests/test_jupyter.py @@ -117,6 +117,16 @@ async def setup_script(hass, now, source, no_connect=False): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) diff --git a/tests/test_unique.py b/tests/test_unique.py index a82b29e..f1536ce 100644 --- a/tests/test_unique.py +++ b/tests/test_unique.py @@ -23,6 +23,16 @@ async def setup_script(hass, notify_q, now, source): "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} + ), patch( + "custom_components.pyscript.load_all_requirement_lines", + return_value={ + "/some/config/dir/pyscript/requirements.txt": [ + "pytube==9.7.0\n", + "# another test comment\n", + "pykakasi==2.0.1 # test comment\n", + "\n", + ] + }, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})