Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions neon_utils/packaging_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import sys
import re
import subprocess
import importlib.util
from typing import Tuple, Optional, List
from tempfile import mkstemp
Expand Down Expand Up @@ -267,19 +268,26 @@ def get_skill_license(): # TODO: Implement OSM version of this
skill_data["requirements"]["python"].sort()
return dict(dict_merge(skill_data, readme_data))

def install_packages_from_pip(core_module: str, packages: List[str]) -> int:
def install_packages_from_pip(core_module: str, packages: List[str],
force_reinstall: bool = False) -> int:
"""
Install a Python package using pip
:param core_module: string neon core module to install dependency for
:param packages: List(string) list of packages to install
:param force_reinstall: force re-installation of packages
:returns: int pip exit code
"""
import pip
def _pip_install(command_args: List[str]) -> int:
try:
result = subprocess.check_call([sys.executable, '-m', 'pip'] + command_args)
return result
except subprocess.CalledProcessError as e:
LOG.error(f"Error installing {command_args}: {e}")
return e.returncode

_, tmp_constraints_file = mkstemp()
_, tmp_requirements_file = mkstemp()

install_str = " ".join(packages)

with open(tmp_constraints_file, 'w', encoding="utf8") as f:
constraints = '\n'.join(get_package_dependencies(core_module))
f.write(constraints)
Expand All @@ -289,10 +297,14 @@ def install_packages_from_pip(core_module: str, packages: List[str]) -> int:
for pkg in packages:
f.write(f"{pkg}\n")

LOG.info(f"Requested installation of plugins: {install_str}")
LOG.info(f"Requested installation of plugins: {packages}")
pip_args = ['install', '-r', tmp_requirements_file, '-c', tmp_constraints_file]
result = pip.main(pip_args) if hasattr(pip, 'main') else pip._internal.main(pip_args)

if result != 0:
return result
return 0
if stat := _pip_install(pip_args) != 0:
return stat

if force_reinstall:
LOG.info(f"Requested forced re-installation of plugins: {packages}")
pip_args.extend(['--no-deps', '--force-reinstall'])
stat = _pip_install(pip_args)

return stat
13 changes: 6 additions & 7 deletions tests/packaging_util_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,25 @@ def test_build_skill_spec(self):
# TODO: Actually validate exception cases? DM

def test_install_packages_from_pip(self):
import pip
import subprocess
from neon_utils.packaging_utils import install_packages_from_pip

with patch.object(pip, 'main', return_value=0) as mock_method:
with patch.object(subprocess, 'check_call', return_value=0) as mock_method:
test_result = install_packages_from_pip("neon-utils", ["pip-install-test"])

args, kwargs = mock_method.call_args
self.assertEqual(0, test_result)
mock_method.assert_called_once_with(['install', '-r', args[0][2], '-c', args[0][4]])
mock_method.assert_called_once_with([sys.executable, '-m', 'pip', 'install', '-r', args[0][5], '-c', args[0][7]])

with open(args[0][2], "r", encoding="utf8") as f:
with open(args[0][5], "r", encoding="utf8") as f:
line = f.readline()
self.assertEqual("pip-install-test\n", line)

mock_method.reset_mock()
test_result = install_packages_from_pip("neon-utils", ["pip-install-test", "pip-install-another"])
test_result = install_packages_from_pip("neon-utils", ["pip-install-test", "pip-install-another"], force_reinstall=True)

self.assertEqual(0, test_result)
mock_method.assert_called_once()

self.assertEqual(mock_method.call_count, 2)


if __name__ == '__main__':
Expand Down