Skip to content

Commit c1c9345

Browse files
committed
move sync logic into dedicated function and update test
1 parent fed7cfd commit c1c9345

File tree

8 files changed

+141
-56
lines changed

8 files changed

+141
-56
lines changed

custom_components/pyscript/__init__.py

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
DOMAIN,
3131
FOLDER,
3232
LOGGER_PATH,
33+
REQUIREMENTS_FILE,
34+
REQUIREMENTS_PATHS,
3335
SERVICE_JUPYTER_KERNEL_START,
3436
UNSUB_LISTENERS,
3537
)
@@ -147,7 +149,7 @@ async def async_setup_entry(hass, config_entry):
147149

148150
State.set_pyscript_config(config_entry.data)
149151

150-
await hass.async_add_executor_job(install_requirements, hass)
152+
await install_requirements(hass)
151153
await load_scripts(hass, config_entry.data)
152154

153155
async def reload_scripts_handler(call):
@@ -165,7 +167,7 @@ async def reload_scripts_handler(call):
165167

166168
await unload_scripts(global_ctx_only=global_ctx_only)
167169

168-
await hass.async_add_executor_job(install_requirements, hass)
170+
await install_requirements(hass)
169171
await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only)
170172

171173
start_global_contexts(global_ctx_only=global_ctx_only)
@@ -266,55 +268,71 @@ async def unload_scripts(global_ctx_only=None, unload_all=False):
266268
await GlobalContextMgr.delete(global_ctx_name)
267269

268270

271+
@bind_hass
272+
def load_all_requirement_lines(hass, requirements_paths, requirements_file):
273+
"""Load all lines from requirements_file located in requirements_paths."""
274+
all_requirements = {}
275+
for root in requirements_paths:
276+
for requirements_path in glob.glob(os.path.join(hass.config.path(FOLDER), root, requirements_file)):
277+
with open(requirements_path, "r") as requirements_fp:
278+
all_requirements[requirements_path] = requirements_fp.readlines()
279+
280+
return all_requirements
281+
282+
269283
@bind_hass
270284
async def install_requirements(hass):
271285
"""Install missing requirements from requirements.txt."""
272-
for root in ("", "apps/*", "modules/*"):
273-
for requirements_path in glob.glob(os.path.join(hass.config.path(FOLDER), root, "requirements.txt")):
274-
with open(requirements_path, "r") as requirements_file:
275-
requirements_to_install = []
276-
for pkg in requirements_file.readlines():
277-
# Remove inline comments which are accepted by pip but not by Home
278-
# Assistant's installation method.
279-
# https://rosettacode.org/wiki/Strip_comments_from_a_string#Python
280-
i = pkg.find("#")
281-
if i >= 0:
282-
pkg = pkg[:i]
283-
pkg = pkg.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 = installed_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.warning(
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(
311-
"Installing the following packages from %s: %s",
312-
requirements_path,
313-
", ".join(requirements_to_install),
314-
)
315-
await async_process_requirements(hass, DOMAIN, requirements_to_install)
286+
all_requirements = await hass.async_add_executor_job(
287+
load_all_requirement_lines, hass, REQUIREMENTS_PATHS, REQUIREMENTS_FILE
288+
)
289+
requirements_to_install = []
290+
for requirements_path, pkg_lines in all_requirements.items():
291+
for pkg in pkg_lines:
292+
# Remove inline comments which are accepted by pip but not by Home
293+
# Assistant's installation method.
294+
# https://rosettacode.org/wiki/Strip_comments_from_a_string#Python
295+
i = pkg.find("#")
296+
if i >= 0:
297+
pkg = pkg[:i]
298+
pkg = pkg.strip()
299+
300+
if not pkg:
301+
continue
302+
303+
try:
304+
# Attempt to get version of package. Do nothing if it's found since
305+
# we want to use the version that's already installed to be safe
306+
requirement = pkg_resources.Requirement.parse(pkg)
307+
requirement_installed_version = installed_version(requirement.project_name)
308+
309+
if requirement_installed_version in requirement:
310+
_LOGGER.debug("`%s` already found", requirement.project_name)
316311
else:
317-
_LOGGER.debug("All packages in %s are already available", requirements_path)
312+
_LOGGER.warning(
313+
(
314+
"`%s` already found but found version `%s` does not"
315+
" match requirement. Keeping found version."
316+
),
317+
requirement.project_name,
318+
requirement_installed_version,
319+
)
320+
except PackageNotFoundError:
321+
# Since package wasn't found, add it to installation list
322+
_LOGGER.debug("%s not found, adding it to package installation list", pkg)
323+
requirements_to_install.append(pkg)
324+
except ValueError:
325+
# Not valid requirements line so it can be skipped
326+
_LOGGER.debug("Ignoring `%s` because it is not a valid package", pkg)
327+
if requirements_to_install:
328+
_LOGGER.info(
329+
"Installing the following packages from %s: %s",
330+
requirements_path,
331+
", ".join(requirements_to_install),
332+
)
333+
await async_process_requirements(hass, DOMAIN, requirements_to_install)
334+
else:
335+
_LOGGER.debug("All packages in %s are already available", requirements_path)
318336

319337

320338
@bind_hass

custom_components/pyscript/const.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
LOGGER_PATH = "custom_components.pyscript"
1616

17+
REQUIREMENTS_FILE = "requirements.txt"
18+
REQUIREMENTS_PATHS = ("", "apps/*", "modules/*")
19+
1720
ALLOWED_IMPORTS = {
1821
"black",
1922
"cmath",

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Test configuration for pyscript."""
2+
from pytest import fixture
3+
from pytest_homeassistant_custom_component.async_mock import patch
4+
5+
6+
@fixture(autouse=True)
7+
def bypass_package_install_fixture():
8+
"""Bypass package installation."""
9+
with patch("custom_components.pyscript.async_process_requirements"):
10+
yield

tests/test_decorator_errors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ async def setup_script(hass, notify_q, now, source):
2323
"custom_components.pyscript.trigger.dt_now", return_value=now
2424
), patch(
2525
"homeassistant.config.load_yaml_config_file", return_value={}
26+
), patch(
27+
"custom_components.pyscript.load_all_requirement_lines",
28+
return_value={
29+
"/some/config/dir/pyscript/requirements.txt": [
30+
"pytube==9.7.0\n",
31+
"# another test comment\n",
32+
"pykakasi==2.0.1 # test comment\n",
33+
"\n",
34+
]
35+
},
2636
):
2737
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})
2838

tests/test_function.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ async def setup_script(hass, notify_q, now, source):
109109
"custom_components.pyscript.trigger.dt_now", return_value=now
110110
), patch(
111111
"homeassistant.config.load_yaml_config_file", return_value={DOMAIN: {CONF_ALLOW_ALL_IMPORTS: True}}
112+
), patch(
113+
"custom_components.pyscript.load_all_requirement_lines",
114+
return_value={
115+
"/some/config/dir/pyscript/requirements.txt": [
116+
"pytube==9.7.0\n",
117+
"# another test comment\n",
118+
"pykakasi==2.0.1 # test comment\n",
119+
"\n",
120+
]
121+
},
112122
):
113123
assert await async_setup_component(hass, "pyscript", {DOMAIN: {CONF_ALLOW_ALL_IMPORTS: True}})
114124

tests/test_init.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ async def setup_script(hass, notify_q, now, source):
3232
"custom_components.pyscript.trigger.dt_now", return_value=now
3333
), patch(
3434
"homeassistant.config.load_yaml_config_file", return_value={}
35+
), patch(
36+
"custom_components.pyscript.load_all_requirement_lines",
37+
return_value={
38+
"/some/config/dir/pyscript/requirements.txt": [
39+
"pytube==9.7.0\n",
40+
"# another test comment\n",
41+
"pykakasi==2.0.1 # test comment\n",
42+
"\n",
43+
]
44+
},
3545
):
3646
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})
3747

@@ -446,6 +456,16 @@ def func5(var_name=None, value=None):
446456
"custom_components.pyscript.trigger.dt_now", return_value=now
447457
), patch(
448458
"homeassistant.config.load_yaml_config_file", return_value={}
459+
), patch(
460+
"custom_components.pyscript.load_all_requirement_lines",
461+
return_value={
462+
"/some/config/dir/pyscript/requirements.txt": [
463+
"pytube==9.7.0\n",
464+
"# another test comment\n",
465+
"pykakasi==2.0.1 # test comment\n",
466+
"\n",
467+
]
468+
},
449469
):
450470
reload_param = {}
451471
if i % 2 == 1:
@@ -486,17 +506,11 @@ async def test_misc_errors(hass, caplog):
486506

487507
async def test_install_requirements(hass):
488508
"""Test install_requirements function."""
489-
requirements = """
490-
pytube==9.7.0
491-
# another test comment
492-
pykakasi==2.0.1 # test comment
493-
494-
"""
495-
496509
with patch("custom_components.pyscript.async_hass_config_yaml", return_value={}), patch(
497-
"custom_components.pyscript.open", mock_open(read_data=requirements), create=True,
498-
), patch("custom_components.pyscript.async_process_requirements") as install_requirements:
510+
"custom_components.pyscript.async_process_requirements"
511+
) as install_requirements:
499512
await setup_script(hass, None, dt(2020, 7, 1, 11, 59, 59, 999999), "")
513+
assert install_requirements.called
500514
assert install_requirements.call_args[0][2] == ["pytube==9.7.0", "pykakasi==2.0.1"]
501515
install_requirements.reset_mock()
502516
# Because in tests, packages are not installed, we fake that they are

tests/test_jupyter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ async def setup_script(hass, now, source, no_connect=False):
117117
"custom_components.pyscript.trigger.dt_now", return_value=now
118118
), patch(
119119
"homeassistant.config.load_yaml_config_file", return_value={}
120+
), patch(
121+
"custom_components.pyscript.load_all_requirement_lines",
122+
return_value={
123+
"/some/config/dir/pyscript/requirements.txt": [
124+
"pytube==9.7.0\n",
125+
"# another test comment\n",
126+
"pykakasi==2.0.1 # test comment\n",
127+
"\n",
128+
]
129+
},
120130
):
121131
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})
122132

tests/test_unique.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ async def setup_script(hass, notify_q, now, source):
2323
"custom_components.pyscript.trigger.dt_now", return_value=now
2424
), patch(
2525
"homeassistant.config.load_yaml_config_file", return_value={}
26+
), patch(
27+
"custom_components.pyscript.load_all_requirement_lines",
28+
return_value={
29+
"/some/config/dir/pyscript/requirements.txt": [
30+
"pytube==9.7.0\n",
31+
"# another test comment\n",
32+
"pykakasi==2.0.1 # test comment\n",
33+
"\n",
34+
]
35+
},
2636
):
2737
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})
2838

0 commit comments

Comments
 (0)