44from collections import defaultdict
55from configparser import ConfigParser
66from enum import Enum
7+ import importlib
78import os
89import pip
910import 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
0 commit comments