@@ -36,7 +36,7 @@ class ProcessJupyterCommand implements IJupyterCommand {
36
36
this . interpreterPromise = interpreterService . getInterpreterDetails ( this . exe ) . catch ( _e => undefined ) ;
37
37
}
38
38
39
- public interpreter ( ) : Promise < PythonInterpreter | undefined > {
39
+ public interpreter ( ) : Promise < PythonInterpreter | undefined > {
40
40
return this . interpreterPromise ;
41
41
}
42
42
@@ -56,7 +56,7 @@ class ProcessJupyterCommand implements IJupyterCommand {
56
56
return launcher . exec ( this . exe , newArgs , newOptions ) ;
57
57
}
58
58
59
- private fixupEnv ( _env ?: NodeJS . ProcessEnv ) : Promise < NodeJS . ProcessEnv | undefined > {
59
+ private fixupEnv ( _env ?: NodeJS . ProcessEnv ) : Promise < NodeJS . ProcessEnv | undefined > {
60
60
if ( this . activationHelper ) {
61
61
return this . activationHelper . getActivatedEnvironmentVariables ( undefined ) ;
62
62
}
@@ -85,18 +85,18 @@ class InterpreterJupyterCommand implements IJupyterCommand {
85
85
86
86
try {
87
87
const output = await svc . exec ( [ path . join ( EXTENSION_ROOT_DIR , 'pythonFiles' , 'datascience' , 'jupyter_nbInstalled.py' ) ] , { } ) ;
88
- if ( output . stdout . toLowerCase ( ) . includes ( 'available' ) ) {
88
+ if ( output . stdout . toLowerCase ( ) . includes ( 'available' ) ) {
89
89
return svc ;
90
90
}
91
- } catch ( ex ) {
91
+ } catch ( ex ) {
92
92
traceError ( 'Checking whether notebook is importable failed' , ex ) ;
93
93
}
94
94
}
95
95
}
96
- return pythonExecutionFactory . createActivatedEnvironment ( { interpreter : this . _interpreter } ) ;
96
+ return pythonExecutionFactory . createActivatedEnvironment ( { interpreter : this . _interpreter } ) ;
97
97
} ) ;
98
98
}
99
- public interpreter ( ) : Promise < PythonInterpreter | undefined > {
99
+ public interpreter ( ) : Promise < PythonInterpreter | undefined > {
100
100
return this . interpreterPromise ;
101
101
}
102
102
@@ -134,22 +134,96 @@ export class InterpreterJupyterNotebookCommand extends InterpreterJupyterCommand
134
134
}
135
135
}
136
136
137
+ /**
138
+ * This class is used to handle kernelspecs.
139
+ * I.e. anything to do with the command `python -m jupyter kernelspec`.
140
+ *
141
+ * @class InterpreterJupyterKernelSpecCommand
142
+ * @implements {IJupyterCommand}
143
+ */
144
+ // tslint:disable-next-line: max-classes-per-file
145
+ export class InterpreterJupyterKernelSpecCommand extends InterpreterJupyterCommand {
146
+ constructor ( moduleName : string , args : string [ ] , pythonExecutionFactory : IPythonExecutionFactory , interpreter : PythonInterpreter , isActiveInterpreter : boolean ) {
147
+ super ( moduleName , args , pythonExecutionFactory , interpreter , isActiveInterpreter ) ;
148
+ }
149
+
150
+ /**
151
+ * Kernelspec subcommand requires special treatment.
152
+ * Its possible the sub command hasn't been registered (i.e. jupyter kernelspec command hasn't been installed).
153
+ * However its possible the kernlspec modules are available.
154
+ * So here's what we have:
155
+ * - python -m jupyter kernelspec --version (throws an error, as kernelspect sub command not installed)
156
+ * - `import jupyter_client.kernelspec` (works, hence kernelspec modules are available)
157
+ * - Problem is daemon will say that `kernelspec` is avaiable, as daemon can work with the `jupyter_client.kernelspec`.
158
+ * But rest of extension will assume kernelspec is available and `python -m jupyter kenerlspec --version` will fall over.
159
+ * Solution:
160
+ * - Run using daemon wrapper code if possible (we don't know whether daemon or python process will run kernel spec).
161
+ * - 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.
162
+ * - However `python -m jupyter kernelspec` will fall over (as such a sub command hasn't been installed), hence calling daemon code will fail.
163
+ * - What we do in such an instance is run the python code `python xyz.py` to deal with kernels.
164
+ * If that works, great.
165
+ * If that fails, then we know that `kernelspec` sub command doesn't exist and `import jupyter_client.kernelspec` also doesn't work.
166
+ * In such a case re-throw the exception from the first execution (possibly the daemon wrapper).
167
+ * @param {string[] } args
168
+ * @param {SpawnOptions } options
169
+ * @returns {Promise<ExecutionResult<string>> }
170
+ * @memberof InterpreterJupyterKernelSpecCommand
171
+ */
172
+ public async exec ( args : string [ ] , options : SpawnOptions ) : Promise < ExecutionResult < string > > {
173
+ let exception : Error | undefined ;
174
+ let output : ExecutionResult < string > = { stdout : '' } ;
175
+ try {
176
+ output = await super . exec ( args , options ) ;
177
+ } catch ( ex ) {
178
+ exception = ex ;
179
+ }
180
+
181
+ if ( ! output . stderr && ! exception ) {
182
+ return output ;
183
+ }
184
+
185
+ // We're only interested in `python -m jupyter kernelspec list --json`
186
+ const interpreter = await this . interpreter ( ) ;
187
+ if ( ! interpreter || this . moduleName . toLowerCase ( ) !== 'jupyter' || this . args . join ( ' ' ) . toLowerCase ( ) !== `-m jupyter ${ JupyterCommands . KernelSpecCommand } ` . toLowerCase ( ) || args . join ( ' ' ) . toLowerCase ( ) !== 'list --json' ) {
188
+ if ( exception ) {
189
+ throw exception ;
190
+ }
191
+ return output ;
192
+ }
193
+ try {
194
+ // Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception.
195
+ const activatedEnv = await this . pythonExecutionFactory . createActivatedEnvironment ( { interpreter } ) ;
196
+ return activatedEnv . exec ( [ path . join ( EXTENSION_ROOT_DIR , 'pythonFiles' , 'datascience' , 'getJupyterKernels.py' ) ] , { ...options , throwOnStdErr : true } ) ;
197
+ } catch ( innerEx ) {
198
+ traceError ( 'Failed to get a list of the kernelspec using python script' , innerEx ) ;
199
+ // Rethrow original exception.
200
+ if ( exception ) {
201
+ throw exception ;
202
+ }
203
+ return output ;
204
+ }
205
+ }
206
+
207
+ }
208
+
137
209
// tslint:disable-next-line: max-classes-per-file
138
210
@injectable ( )
139
211
export class JupyterCommandFactory implements IJupyterCommandFactory {
140
212
141
213
constructor (
142
- @inject ( IPythonExecutionFactory ) private readonly executionFactory : IPythonExecutionFactory ,
143
- @inject ( IEnvironmentActivationService ) private readonly activationHelper : IEnvironmentActivationService ,
214
+ @inject ( IPythonExecutionFactory ) private readonly executionFactory : IPythonExecutionFactory ,
215
+ @inject ( IEnvironmentActivationService ) private readonly activationHelper : IEnvironmentActivationService ,
144
216
@inject ( IProcessServiceFactory ) private readonly processServiceFactory : IProcessServiceFactory ,
145
217
@inject ( IInterpreterService ) private readonly interpreterService : IInterpreterService
146
218
) {
147
219
148
220
}
149
221
150
222
public createInterpreterCommand ( command : JupyterCommands , moduleName : string , args : string [ ] , interpreter : PythonInterpreter , isActiveInterpreter : boolean ) : IJupyterCommand {
151
- if ( command === JupyterCommands . NotebookCommand ) {
223
+ if ( command === JupyterCommands . NotebookCommand ) {
152
224
return new InterpreterJupyterNotebookCommand ( moduleName , args , this . executionFactory , interpreter , isActiveInterpreter ) ;
225
+ } else if ( command === JupyterCommands . KernelSpecCommand ) {
226
+ return new InterpreterJupyterKernelSpecCommand ( moduleName , args , this . executionFactory , interpreter , isActiveInterpreter ) ;
153
227
}
154
228
return new InterpreterJupyterCommand ( moduleName , args , this . executionFactory , interpreter , isActiveInterpreter ) ;
155
229
}
0 commit comments