Skip to content

Commit 6b810ce

Browse files
author
Alex Burke
committed
Add logic to detect and skip already installed packages.
1 parent 0adf688 commit 6b810ce

2 files changed

Lines changed: 63 additions & 9 deletions

File tree

mig/install/features.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from collections import defaultdict
55
from configparser import ConfigParser
66
from enum import Enum
7+
import importlib
78
import os
89
import pip
910
import sys
@@ -80,27 +81,32 @@ def feature_is_enabled(self, feature_name):
8081

8182
return self._enabled_by_feature[feature_name]
8283

83-
def generate_pip_args(self):
84+
def generate_pip_args(self, detect_installed=False):
8485
"""
8586
Create pip arguments for each enabled feature.
8687
"""
8788

8889
per_package_args = []
8990

9091
for feature_name in self.list_enabled_features():
91-
per_package_args.append(self.generate_pip_args_for_feature(feature_name))
92+
installable_packages = self.generate_pip_args_for_feature(feature_name,
93+
detect_installed=detect_installed)
94+
if not installable_packages:
95+
continue
96+
per_package_args.append(installable_packages)
9297

9398
return per_package_args
9499

95-
def generate_pip_args_for_feature(self, feature_name):
100+
def generate_pip_args_for_feature(self, feature_name, *, detect_installed):
96101
"""
97102
Create pip arguments for a particular feature.
98103
"""
99104

100-
overrides = self._overrides_by_feature.get(feature_name, None)
105+
overrides = self._overrides_by_feature.get(feature_name, {})
101106

102-
if not overrides:
103-
# no overiddes detected therefore we can install
107+
if not (overrides or detect_installed):
108+
# no overrides detected and we do not need to check for the
109+
# dependencies being already installed therefore we can install
104110
# by simply using the requirements file as-is
105111
return ['-r', self._requirements_file_by_feature[feature_name]]
106112

@@ -111,11 +117,25 @@ def generate_pip_args_for_feature(self, feature_name):
111117
for package_name in overridden_package_names:
112118
package_args.append(f"{package_name}=={overrides[package_name]}")
113119

120+
if detect_installed:
121+
packages_to_detect = self.required_package_names(feature_name)
122+
else:
123+
packages_to_detect = set()
124+
114125
# add the remaining packages based on the requirements file
115126
for entry in self._requirements_by_feature[feature_name]:
116127
package_name = Features._strip_version_if_present(entry.requirement)
117128
if package_name in overridden_package_names:
118129
continue
130+
131+
should_detect = package_name in packages_to_detect
132+
if should_detect:
133+
skip_installation = Features._is_package_present(package_name)
134+
else:
135+
skip_installation = False
136+
137+
if skip_installation:
138+
continue
119139
package_args.append(entry.requirement)
120140

121141
return package_args
@@ -128,6 +148,10 @@ def list_enabled_features(self, return_as=list):
128148
return return_as((feature_name for feature_name in self.feature_names
129149
if self._enabled_by_feature[feature_name]))
130150

151+
def required_package_names(self, feature_name):
152+
return set((Features._strip_version_if_present(entry.requirement)
153+
for entry in self._requirements_by_feature[feature_name]))
154+
131155
@staticmethod
132156
def _interpret_feature_definition(feature_name, feature_definition, requirements_dir):
133157
"""
@@ -150,6 +174,15 @@ def _interpret_feature_definition(feature_name, feature_definition, requirements
150174
requirements_file=requirements_file,
151175
)
152176

177+
@staticmethod
178+
def _is_package_present(package_name):
179+
try:
180+
importlib.util.find_spec(package_name)
181+
182+
return True
183+
except ModuleNotFoundError as exc:
184+
return False
185+
153186
@staticmethod
154187
def _strip_version_if_present(requirement):
155188
"""
@@ -270,7 +303,7 @@ def main_install(features, args, print=print, warn=warn):
270303
warn("no feature coniguration available; showing those enabled by default only")
271304
warn()
272305

273-
all_pip_args = features.generate_pip_args()
306+
all_pip_args = features.generate_pip_args(detect_installed=args.detect_installed)
274307

275308
if args.check:
276309
for pip_args in all_pip_args:
@@ -305,6 +338,7 @@ def main(argv):
305338
install_command = subparsers.add_parser('install')
306339
install_command.add_argument('-c', default=None)
307340
install_command.add_argument('--check', action='store_true', default=False)
341+
install_command.add_argument('--detect_installed', action='store_true', default=False)
308342
install_command.add_argument('--dotenv', default=None, type=os.path.abspath)
309343
install_command.add_argument('--env', action='store_const', const=os.environ)
310344

tests/test_mig_install_features.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def test_command_install_check(self):
148148
args = SimpleNamespace(
149149
command='install',
150150
check=True,
151+
detect_installed=False,
151152
c=None,
152153
dotenv=os.path.join(example_dir, '.env--enable-foo'),
153154
env=None
@@ -161,7 +162,26 @@ def test_command_install_check(self):
161162
f"pip install -r {os.path.join(example_dir, 'requirements/foo-requirements.txt')}",
162163
])
163164

164-
def test_overridden_package_version(self):
165+
def test_command_install_conflicting_package_versions(self):
166+
fake_print = FakePrint()
167+
example_dir, features = _make_example_features_instance('conflict')
168+
args = SimpleNamespace(
169+
command='install',
170+
check=True,
171+
detect_installed=True,
172+
c=None,
173+
dotenv=None,
174+
env={
175+
'ENABLE_CONFLICTING': 'true'
176+
}
177+
)
178+
179+
ret = args_main(args, print=fake_print, features=features)
180+
181+
self.assertEqual(ret, 0)
182+
self.assertOutputLines(fake_print, [])
183+
184+
def test_command_install_overridden_package_version(self):
165185
fake_print = FakePrint()
166186
example_dir, features = _make_example_features_instance('basic',
167187
overrides_supported={
@@ -172,6 +192,7 @@ def test_overridden_package_version(self):
172192
args = SimpleNamespace(
173193
command='install',
174194
check=True,
195+
detect_installed=False,
175196
c=None,
176197
dotenv=None,
177198
env={
@@ -189,7 +210,6 @@ def test_overridden_package_version(self):
189210
])
190211

191212

192-
193213
class MigInstallFeatures_smoke(MigTestCase):
194214
"""Unit test helper for the migrid code pointed to in class name"""
195215

0 commit comments

Comments
 (0)