Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pythonFiles/datascience/getJupyterKernels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import json
import jupyter_client.kernelspec
import sys


specs = jupyter_client.kernelspec.KernelSpecManager().get_all_specs()
all_specs = {
"kernelspecs": specs
}

sys.stdout.write(json.dumps(all_specs))
sys.stdout.flush()
9 changes: 0 additions & 9 deletions pythonFiles/datascience/jupyter_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,6 @@ def _print_kernelspec_version(self):
sys.stdout.write(jupyter_client.__version__)
sys.stdout.flush()

def _print_kernelspec_version(self):
import jupyter_client

# Check whether kernelspec module exists.
import jupyter_client.kernelspec

sys.stdout.write(jupyter_client.__version__)
sys.stdout.flush()

def _print_kernel_list(self):
self.log.info("listing kernels")
# Get kernel specs.
Expand Down
92 changes: 83 additions & 9 deletions src/client/datascience/jupyter/jupyterCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ProcessJupyterCommand implements IJupyterCommand {
this.interpreterPromise = interpreterService.getInterpreterDetails(this.exe).catch(_e => undefined);
}

public interpreter() : Promise<PythonInterpreter | undefined> {
public interpreter(): Promise<PythonInterpreter | undefined> {
return this.interpreterPromise;
}

Expand All @@ -56,7 +56,7 @@ class ProcessJupyterCommand implements IJupyterCommand {
return launcher.exec(this.exe, newArgs, newOptions);
}

private fixupEnv(_env?: NodeJS.ProcessEnv) : Promise<NodeJS.ProcessEnv | undefined> {
private fixupEnv(_env?: NodeJS.ProcessEnv): Promise<NodeJS.ProcessEnv | undefined> {
if (this.activationHelper) {
return this.activationHelper.getActivatedEnvironmentVariables(undefined);
}
Expand Down Expand Up @@ -85,18 +85,18 @@ class InterpreterJupyterCommand implements IJupyterCommand {

try {
const output = await svc.exec([path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'jupyter_nbInstalled.py')], {});
if (output.stdout.toLowerCase().includes('available')){
if (output.stdout.toLowerCase().includes('available')) {
return svc;
}
} catch (ex){
} catch (ex) {
traceError('Checking whether notebook is importable failed', ex);
}
}
}
return pythonExecutionFactory.createActivatedEnvironment({interpreter: this._interpreter});
return pythonExecutionFactory.createActivatedEnvironment({ interpreter: this._interpreter });
});
}
public interpreter() : Promise<PythonInterpreter | undefined> {
public interpreter(): Promise<PythonInterpreter | undefined> {
return this.interpreterPromise;
}

Expand Down Expand Up @@ -134,22 +134,96 @@ export class InterpreterJupyterNotebookCommand extends InterpreterJupyterCommand
}
}

/**
* This class is used to handle kernelspecs.
* I.e. anything to do with the command `python -m jupyter kernelspec`.
*
* @class InterpreterJupyterKernelSpecCommand
* @implements {IJupyterCommand}
*/
// tslint:disable-next-line: max-classes-per-file
export class InterpreterJupyterKernelSpecCommand extends InterpreterJupyterCommand {
constructor(moduleName: string, args: string[], pythonExecutionFactory: IPythonExecutionFactory, interpreter: PythonInterpreter, isActiveInterpreter: boolean) {
super(moduleName, args, pythonExecutionFactory, interpreter, isActiveInterpreter);
}

/**
* Kernelspec subcommand requires special treatment.
* Its possible the sub command hasn't been registered (i.e. jupyter kernelspec command hasn't been installed).
* However its possible the kernlspec modules are available.
* So here's what we have:
* - python -m jupyter kernelspec --version (throws an error, as kernelspect sub command not installed)
* - `import jupyter_client.kernelspec` (works, hence kernelspec modules are available)
* - Problem is daemon will say that `kernelspec` is avaiable, as daemon can work with the `jupyter_client.kernelspec`.
* But rest of extension will assume kernelspec is available and `python -m jupyter kenerlspec --version` will fall over.
* Solution:
* - Run using daemon wrapper code if possible (we don't know whether daemon or python process will run kernel spec).
* - Now, its possible the python daemon process is busy in which case we fall back (in daemon wrapper) to using a python process to run the code.
* - However `python -m jupyter kernelspec` will fall over (as such a sub command hasn't been installed), hence calling daemon code will fail.
* - What we do in such an instance is run the python code `python xyz.py` to deal with kernels.
* If that works, great.
* If that fails, then we know that `kernelspec` sub command doesn't exist and `import jupyter_client.kernelspec` also doesn't work.
* In such a case re-throw the exception from the first execution (possibly the daemon wrapper).
* @param {string[]} args
* @param {SpawnOptions} options
* @returns {Promise<ExecutionResult<string>>}
* @memberof InterpreterJupyterKernelSpecCommand
*/
public async exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>> {
let exception: Error | undefined;
let output: ExecutionResult<string> = { stdout: '' };
try {
output = await super.exec(args, options);
} catch (ex) {
exception = ex;
}

if (!output.stderr && !exception) {
return output;
}

// We're only interested in `python -m jupyter kernelspec list --json`
const interpreter = await this.interpreter();
if (!interpreter || this.moduleName.toLowerCase() !== 'jupyter' || this.args.join(' ').toLowerCase() !== `-m jupyter ${JupyterCommands.KernelSpecCommand}`.toLowerCase() || args.join(' ').toLowerCase() !== 'list --json') {
if (exception) {
throw exception;
}
return output;
}
try {
// Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception.
const activatedEnv = await this.pythonExecutionFactory.createActivatedEnvironment({ interpreter });
return activatedEnv.exec([path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'datascience', 'getJupyterKernels.py')], { ...options, throwOnStdErr: true });
} catch (innerEx) {
traceError('Failed to get a list of the kernelspec using python script', innerEx);
// Rethrow original exception.
if (exception) {
throw exception;
}
return output;
}
}

}

// tslint:disable-next-line: max-classes-per-file
@injectable()
export class JupyterCommandFactory implements IJupyterCommandFactory {

constructor(
@inject(IPythonExecutionFactory) private readonly executionFactory : IPythonExecutionFactory,
@inject(IEnvironmentActivationService) private readonly activationHelper : IEnvironmentActivationService,
@inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory,
@inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService,
@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory,
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService
) {

}

public createInterpreterCommand(command: JupyterCommands, moduleName: string, args: string[], interpreter: PythonInterpreter, isActiveInterpreter: boolean): IJupyterCommand {
if (command === JupyterCommands.NotebookCommand){
if (command === JupyterCommands.NotebookCommand) {
return new InterpreterJupyterNotebookCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter);
} else if (command === JupyterCommands.KernelSpecCommand) {
return new InterpreterJupyterKernelSpecCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter);
}
return new InterpreterJupyterCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter);
}
Expand Down