|
1 | 1 | import os
|
2 | 2 | import sys
|
3 | 3 | from itertools import chain
|
4 |
| -from pathlib import PurePath |
| 4 | +from pathlib import Path, PurePath |
5 | 5 | from urllib import request
|
6 | 6 |
|
7 | 7 | import orjson
|
@@ -67,6 +67,113 @@ def running_servers():
|
67 | 67 | return servers_nbapp, servers_juserv
|
68 | 68 |
|
69 | 69 |
|
| 70 | +def find_nb_path_via_parent_process(): |
| 71 | + """Tries to find the notebook path by inspecting the parent process's command line. |
| 72 | +
|
| 73 | + Requires the 'psutil' library. Heuristic and potentially fragile. |
| 74 | + """ |
| 75 | + import psutil |
| 76 | + |
| 77 | + try: |
| 78 | + current_process = psutil.Process(os.getpid()) |
| 79 | + parent_process = current_process.parent() |
| 80 | + |
| 81 | + if parent_process is None: |
| 82 | + logger.warning("psutil: Could not get parent process.") |
| 83 | + return None |
| 84 | + |
| 85 | + # Get parent command line arguments |
| 86 | + cmdline = parent_process.cmdline() |
| 87 | + if not cmdline: |
| 88 | + logger.warning( |
| 89 | + f"psutil: Parent process ({parent_process.pid}) has empty cmdline." |
| 90 | + ) |
| 91 | + # Maybe check grandparent? This gets complicated quickly. |
| 92 | + return None |
| 93 | + |
| 94 | + logger.info(f"psutil: Parent cmdline: {cmdline}") |
| 95 | + |
| 96 | + # Heuristic parsing: Look for 'nbconvert' and '.ipynb' |
| 97 | + # This is fragile and depends on how nbconvert was invoked. |
| 98 | + is_nbconvert_call = False |
| 99 | + potential_path = None |
| 100 | + |
| 101 | + for i, arg in enumerate(cmdline): |
| 102 | + # Check if 'nbconvert' command is present |
| 103 | + if "nbconvert" in arg.lower(): |
| 104 | + # Check if it's the main command (e.g. /path/to/jupyter-nbconvert) |
| 105 | + # or a subcommand (e.g. ['jupyter', 'nbconvert', ...]) |
| 106 | + # or a module call (e.g. ['python', '-m', 'nbconvert', ...]) |
| 107 | + base_arg = os.path.basename(arg).lower() # noqa: PTH119 |
| 108 | + if ( |
| 109 | + "jupyter-nbconvert" in base_arg |
| 110 | + or arg == "nbconvert" |
| 111 | + or ( |
| 112 | + cmdline[i - 1].endswith("python") |
| 113 | + and arg == "-m" |
| 114 | + and cmdline[i + 1] == "nbconvert" |
| 115 | + ) |
| 116 | + ): |
| 117 | + is_nbconvert_call = True |
| 118 | + |
| 119 | + # Find the argument ending in .ipynb AFTER 'nbconvert' is likely found |
| 120 | + # Or just find the last argument ending in .ipynb as a guess |
| 121 | + if arg.endswith(".ipynb"): |
| 122 | + potential_path = arg # Store the last one found |
| 123 | + |
| 124 | + if is_nbconvert_call and potential_path: |
| 125 | + # We found something that looks like an nbconvert call and an ipynb file |
| 126 | + # The path might be relative to the parent process's CWD. |
| 127 | + # Try to resolve it. Parent CWD might not be notebook dir if called like |
| 128 | + # jupyter nbconvert --execute /abs/path/to/notebook.ipynb |
| 129 | + try: |
| 130 | + # Get parent's CWD |
| 131 | + parent_cwd = parent_process.cwd() |
| 132 | + resolved_path = Path(parent_cwd) / Path(potential_path) |
| 133 | + if resolved_path.is_file(): |
| 134 | + logger.info(f"psutil: Found potential path: {resolved_path}") |
| 135 | + return resolved_path.resolve() # Return absolute path |
| 136 | + else: |
| 137 | + # Maybe the path was already absolute? |
| 138 | + abs_path = Path(potential_path) |
| 139 | + if abs_path.is_absolute() and abs_path.is_file(): |
| 140 | + logger.info( |
| 141 | + f"psutil: Found potential absolute path: {abs_path}" |
| 142 | + ) |
| 143 | + return abs_path.resolve() |
| 144 | + else: |
| 145 | + logger.warning( |
| 146 | + f"psutil: Potential path '{potential_path}' not found relative to parent CWD '{parent_cwd}' or as absolute path." |
| 147 | + ) |
| 148 | + return None |
| 149 | + |
| 150 | + except psutil.AccessDenied: |
| 151 | + logger.warning("psutil: Access denied when getting parent CWD.") |
| 152 | + # Fallback: assume path might be relative to kernel's CWD (less likely) |
| 153 | + maybe_path = Path(potential_path) |
| 154 | + if maybe_path.is_file(): |
| 155 | + return maybe_path.resolve() |
| 156 | + return None # Give up trying to resolve relative path |
| 157 | + except Exception as e: |
| 158 | + logger.warning(f"psutil: Error resolving path '{potential_path}': {e}") |
| 159 | + return None |
| 160 | + |
| 161 | + logger.warning( |
| 162 | + "psutil: Could not reliably identify notebook path from parent cmdline." |
| 163 | + ) |
| 164 | + return None |
| 165 | + |
| 166 | + except ImportError: |
| 167 | + logger.warning("psutil library not found. Cannot inspect parent process.") |
| 168 | + return None |
| 169 | + except psutil.Error as e: |
| 170 | + logger.warning(f"psutil error: {e}") |
| 171 | + return None |
| 172 | + except Exception as e: |
| 173 | + logger.warning(f"Unexpected error during psutil check: {e}") |
| 174 | + return None |
| 175 | + |
| 176 | + |
70 | 177 | def notebook_path(return_env=False):
|
71 | 178 | """Return the path to the current notebook.
|
72 | 179 |
|
@@ -157,6 +264,16 @@ def notebook_path(return_env=False):
|
157 | 264 | nb_path = PurePath(os.environ["JPY_SESSION_NAME"])
|
158 | 265 | return (nb_path, "lab" if env is None else env) if return_env else nb_path
|
159 | 266 |
|
| 267 | + # try inspecting parent process using psutil, needed if notebook is run via nbconvert |
| 268 | + nb_path_psutil = find_nb_path_via_parent_process() |
| 269 | + if nb_path_psutil is not None: |
| 270 | + logger.info("Detected path via psutil parent process inspection.") |
| 271 | + return ( |
| 272 | + (nb_path_psutil, "nbconvert" if env is None else env) |
| 273 | + if return_env |
| 274 | + else nb_path_psutil |
| 275 | + ) |
| 276 | + |
160 | 277 | # no running servers
|
161 | 278 | if servers_nbapp == [] and servers_juserv == []:
|
162 | 279 | logger.warning("Can not find any servers running.")
|
|
0 commit comments