def find_best_kernel(notebook_file: str) -> str: """Determines the best kernel to use via the following algorithm: 1. Loads notebook and gets kernel_name and kernel_language from NB metadata. 2. Gets the list of configured kernels using KernelSpecManager. 3. If notebook kernel_name is in list, use that, else 4. If not found, load each configured kernel.json file and find a language match. 5. On first match, log info message regarding the switch and use that kernel. 6. If no language match is found, revert to notebook kernel and log warning message. """ import json import nbformat from jupyter_client.kernelspec import KernelSpecManager nb = nbformat.read(notebook_file, 4) nb_kspec = nb.metadata.kernelspec nb_kernel_name = nb_kspec.get('name') nb_kernel_lang = nb_kspec.get('language') kernel_specs = KernelSpecManager().find_kernel_specs() # see if we have a direct match... if nb_kernel_name in kernel_specs.keys(): return nb_kernel_name # no match found for kernel, try matching language... for name, file in kernel_specs.items(): # load file (JSON) and pick out language, if match, use first found with open(os.path.join(file, 'kernel.json')) as f: kspec = json.load(f) if kspec.get('language').lower() == nb_kernel_lang.lower(): matched_kernel = os.path.basename(file) logger.info( f"Matched kernel by language ({nb_kernel_lang}), using kernel " f"'{matched_kernel}' instead of the missing kernel '{nb_kernel_name}'." ) return matched_kernel # no match found for language, return notebook kernel and let execution fail logger.warning( f"Reverting back to missing notebook kernel '{nb_kernel_name}' since no " f"language match ({nb_kernel_lang}) was found in current kernel specifications." ) return nb_kernel_name
def run_notebook(nb_path, output_dir): """Run a notebook tests executes the notebook and stores the output in a file """ import nbformat from jupyter_client.kernelspec import KernelSpecManager from nbconvert.preprocessors.execute import executenb from datetime import datetime log.info("Testing notebook " + str(nb_path)) with open(nb_path) as f: nb = nbformat.read(f, as_version=4) kernel_specs = KernelSpecManager().get_all_specs() kernel_info = nb.metadata.get("kernelspec") or {} kernel_name = kernel_info.get("name", "") kernel_language = kernel_info.get("language") or "" if kernel_name in kernel_specs: log.info("Found kernel " + str(kernel_name)) elif kernel_language: log.warning("No such kernel " + str(kernel_name) + ", falling back on kernel language=" + str(kernel_language)) kernel_language = kernel_language.lower() # no exact name match, re-implement js notebook fallback, # using kernel language instead # nbconvert does not implement this, but it should for kernel_spec_name, kernel_info in kernel_specs.items(): if (kernel_info.get("spec", {}).get("language", "").lower() == kernel_language): log.warning("Using kernel " + str(kernel_spec_name) + " to provide language: " + str(kernel_language)) kernel_name = kernel_spec_name break else: log.warning("Found no matching kernel for name=" + str(kernel_name) + ", language=" + str(kernel_language)) summary_specs = [ "name=" + str(name) + ", language=" + str(info['spec'].get('language')) for name, info in kernel_specs.items() ] log.warning("Found kernel specs: " + '; '.join(summary_specs)) start_time = datetime.now() exported = executenb(nb, cwd=os.path.dirname(nb_path), kernel_name=kernel_name, timeout=600) execution_time = (datetime.now() - start_time).seconds log.info("Execution time is " + str(execution_time)) rel_path = os.path.relpath(nb_path, os.getcwd()) dest_path = os.path.join(output_dir, "notebooks", rel_path) log.info("Saving exported notebook to " + str(dest_path)) try: os.makedirs(os.path.dirname(dest_path)) except FileExistsError: pass with open(dest_path, "w") as f: nbformat.write(exported, f)