66from ruamel .yaml import YAML
77
88from ..base import BaseImage
9+ from ...utils import is_local_pip_requirement
910
1011# pattern for parsing conda dependency line
1112PYTHON_REGEX = re .compile (r"python\s*=+\s*([\d\.]*)" )
@@ -127,6 +128,50 @@ def get_build_script_files(self):
127128 files .update (super ().get_build_script_files ())
128129 return files
129130
131+ _environment_yaml = None
132+
133+ @property
134+ def environment_yaml (self ):
135+ if self ._environment_yaml is not None :
136+ return self ._environment_yaml
137+
138+ environment_yml = self .binder_path ("environment.yml" )
139+ if not os .path .exists (environment_yml ):
140+ self ._environment_yaml = {}
141+ return self ._environment_yaml
142+
143+ with open (environment_yml ) as f :
144+ env = YAML ().load (f )
145+ # check if the env file is empty, if so instantiate an empty dictionary.
146+ if env is None :
147+ env = {}
148+ # check if the env file provided a dict-like thing not a list or other data structure.
149+ if not isinstance (env , Mapping ):
150+ raise TypeError (
151+ "environment.yml should contain a dictionary. Got %r" % type (env )
152+ )
153+ self ._environment_yaml = env
154+
155+ return self ._environment_yaml
156+
157+ @property
158+ def _should_preassemble_env (self ):
159+ """Check for local pip requirements in environment.yaml
160+
161+ If there are any local references, e.g. `-e .`,
162+ stage the whole repo prior to installation.
163+ """
164+ dependencies = self .environment_yaml .get ("dependencies" , [])
165+ pip_requirements = None
166+ for dep in dependencies :
167+ if isinstance (dep , dict ) and dep .get ("pip" ):
168+ pip_requirements = dep ["pip" ]
169+ if isinstance (pip_requirements , list ):
170+ for line in pip_requirements :
171+ if is_local_pip_requirement (line ):
172+ return False
173+ return True
174+
130175 @property
131176 def python_version (self ):
132177 """Detect the Python version for a given `environment.yml`
@@ -135,31 +180,17 @@ def python_version(self):
135180 or a Falsy empty string '' if not found.
136181
137182 """
138- environment_yml = self .binder_path ("environment.yml" )
139- if not os .path .exists (environment_yml ):
140- return ""
141-
142183 if not hasattr (self , "_python_version" ):
143184 py_version = None
144- with open (environment_yml ) as f :
145- env = YAML ().load (f )
146- # check if the env file is empty, if so instantiate an empty dictionary.
147- if env is None :
148- env = {}
149- # check if the env file provided a dick-like thing not a list or other data structure.
150- if not isinstance (env , Mapping ):
151- raise TypeError (
152- "environment.yml should contain a dictionary. Got %r"
153- % type (env )
154- )
155- for dep in env .get ("dependencies" , []):
156- if not isinstance (dep , str ):
157- continue
158- match = PYTHON_REGEX .match (dep )
159- if not match :
160- continue
161- py_version = match .group (1 )
162- break
185+ env = self .environment_yaml
186+ for dep in env .get ("dependencies" , []):
187+ if not isinstance (dep , str ):
188+ continue
189+ match = PYTHON_REGEX .match (dep )
190+ if not match :
191+ continue
192+ py_version = match .group (1 )
193+ break
163194
164195 # extract major.minor
165196 if py_version :
@@ -178,14 +209,27 @@ def py2(self):
178209 """Am I building a Python 2 kernel environment?"""
179210 return self .python_version and self .python_version .split ("." )[0 ] == "2"
180211
181- def get_assemble_scripts (self ):
212+ def get_preassemble_script_files (self ):
213+ """preassembly only requires environment.yml
214+
215+ enables caching assembly result even when
216+ repo contents change
217+ """
218+ assemble_files = super ().get_preassemble_script_files ()
219+ if self ._should_preassemble_env :
220+ environment_yml = self .binder_path ("environment.yml" )
221+ if os .path .exists (environment_yml ):
222+ assemble_files [environment_yml ] = environment_yml
223+ return assemble_files
224+
225+ def get_env_scripts (self ):
182226 """Return series of build-steps specific to this source repository.
183227 """
184- assembly_scripts = []
228+ scripts = []
185229 environment_yml = self .binder_path ("environment.yml" )
186230 env_prefix = "${KERNEL_PYTHON_PREFIX}" if self .py2 else "${NB_PYTHON_PREFIX}"
187231 if os .path .exists (environment_yml ):
188- assembly_scripts .append (
232+ scripts .append (
189233 (
190234 "${NB_USER}" ,
191235 r"""
@@ -197,7 +241,19 @@ def get_assemble_scripts(self):
197241 ),
198242 )
199243 )
200- return super ().get_assemble_scripts () + assembly_scripts
244+ return scripts
245+
246+ def get_preassemble_scripts (self ):
247+ scripts = super ().get_preassemble_scripts ()
248+ if self ._should_preassemble_env :
249+ scripts .extend (self .get_env_scripts ())
250+ return scripts
251+
252+ def get_assemble_scripts (self ):
253+ scripts = super ().get_assemble_scripts ()
254+ if not self ._should_preassemble_env :
255+ scripts .extend (self .get_env_scripts ())
256+ return scripts
201257
202258 def detect (self ):
203259 """Check if current repo should be built with the Conda BuildPack.
0 commit comments