|
17 | 17 | import importlib
|
18 | 18 | import itertools
|
19 | 19 | import sys
|
20 |
| -from contextlib import redirect_stdout |
| 20 | +from contextlib import contextmanager, redirect_stdout |
21 | 21 | from io import StringIO
|
22 | 22 | from pathlib import Path
|
23 | 23 | from subprocess import CalledProcessError
|
@@ -247,6 +247,69 @@ def get_previously_imported_pkgs(install_cmd_stdout, installer):
|
247 | 247 | return prev_imported_pkgs
|
248 | 248 |
|
249 | 249 |
|
| 250 | +@contextmanager |
| 251 | +def handle_alternate_pip_executable(installed_name): |
| 252 | + """ |
| 253 | + Context manager that makes it possible to load packages installed |
| 254 | + into a different Python environment by changing the `pip` executable |
| 255 | + (`davos.config.pip_executable`). |
| 256 | +
|
| 257 | + Parameters |
| 258 | + ---------- |
| 259 | + installed_name : str |
| 260 | + Package name as passed to the `pip install` command. This is the |
| 261 | + value of self.install_name for the Onion object for the |
| 262 | + just-installed package. |
| 263 | +
|
| 264 | + Notes |
| 265 | + ----- |
| 266 | + super-duper edge case not handled by this: user has used alternate |
| 267 | + pip_executable to smuggle local package from CWD and supplies |
| 268 | + relative path in onion comment (i.e., "# pip: . <args>"). |
| 269 | + """ |
| 270 | + if '/' in installed_name: |
| 271 | + # handle local paths, local/remote VCS, PEP 440 direct ref, etc. |
| 272 | + dist_name = installed_name.split('@')[0].split('#')[0].split('/')[-1] |
| 273 | + else: |
| 274 | + # common case |
| 275 | + dist_name = installed_name |
| 276 | + |
| 277 | + # get install location from `pip show ___` command. |
| 278 | + # In most cases, this will be davos.config.pip_executable with |
| 279 | + # trailing 'bin/pip' replaced with python<major.minor>/site-packages |
| 280 | + # but since this runs only if non-default pip_executable is set, |
| 281 | + # it's worth the extra run time to check the safer way in order to |
| 282 | + # handle various edge cases (e.g., installing from VCS, etc.) |
| 283 | + pip_show_cmd = f'{config._pip_executable} show {dist_name}' |
| 284 | + try: |
| 285 | + pip_show_stdout = run_shell_command(pip_show_cmd, live_stdout=False) |
| 286 | + location_line = next( |
| 287 | + l for l in pip_show_stdout.strip().splitlines() |
| 288 | + if l.startswith('Location') |
| 289 | + ) |
| 290 | + except (CalledProcessError, StopIteration) as e: |
| 291 | + msg = ( |
| 292 | + "Unable to locate package installed installed with non-default " |
| 293 | + f"'pip' executable: {dist_name}. Package has been successfully " |
| 294 | + "installed but not loaded." |
| 295 | + ) |
| 296 | + raise SmugglerError(msg) from e |
| 297 | + |
| 298 | + install_location = location_line.split(': ', maxsplit=1)[1].strip() |
| 299 | + sys.path.insert(0, install_location) |
| 300 | + try: |
| 301 | + # reload pkg_resources so import system will search dir |
| 302 | + # containing just-installed package |
| 303 | + importlib.reload(sys.modules['pkg_resources']) |
| 304 | + yield |
| 305 | + finally: |
| 306 | + # remove temporary dir from path |
| 307 | + sys.path.pop(0) |
| 308 | + # reload pkg_resources again so import system no longer searches |
| 309 | + # dir used by alternate pip_executable |
| 310 | + importlib.reload(sys.modules['pkg_resources']) |
| 311 | + |
| 312 | + |
250 | 313 | def import_name(name):
|
251 | 314 | """
|
252 | 315 | Import an object by its qualified name.
|
@@ -458,6 +521,8 @@ def install_cmd(self):
|
458 | 521 | args = self.args_str.replace("<", "'<'").replace(">", "'>'")
|
459 | 522 | if self.installer == 'pip':
|
460 | 523 | install_exe = config._pip_executable
|
| 524 | + if config.noninteractive: |
| 525 | + args = f'{args} --no-input' |
461 | 526 | else:
|
462 | 527 | install_exe = self.installer
|
463 | 528 |
|
@@ -945,7 +1010,11 @@ def smuggle(
|
945 | 1010 | else:
|
946 | 1011 | prompt_restart_rerun_buttons(failed_reloads)
|
947 | 1012 |
|
948 |
| - smuggled_obj = import_name(name) |
| 1013 | + if config._pip_executable != config._pip_executable_orig: |
| 1014 | + with handle_alternate_pip_executable(onion.install_name): |
| 1015 | + smuggled_obj = import_name(name) |
| 1016 | + else: |
| 1017 | + smuggled_obj = import_name(name) |
949 | 1018 |
|
950 | 1019 | # add the object name/alias to the notebook's global namespace
|
951 | 1020 | if as_ is None:
|
|
0 commit comments