@jupyterlab/services
Version:
Client APIs for the Jupyter services REST APIs
447 lines (400 loc) • 11.6 kB
text/typescript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ISignal, Signal } from '@lumino/signaling';
import { Kernel, KernelMessage } from '../kernel';
import { ServerConnection } from '..';
import * as Session from './session';
import { shutdownSession, updateSession } from './restapi';
import { UUID } from '@lumino/coreutils';
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
/**
* Session object for accessing the session REST api. The session
* should be used to start kernels and then shut them down -- for
* all other kernel operations, the kernel object should be used.
*/
export class SessionConnection implements Session.ISessionConnection {
/**
* Construct a new session.
*/
constructor(options: Session.ISessionConnection.IOptions) {
this._id = options.model.id;
this._name = options.model.name;
this._path = options.model.path;
this._type = options.model.type;
this._username = options.username ?? '';
this._clientId = options.clientId ?? UUID.uuid4();
this._connectToKernel = options.connectToKernel;
this._kernelConnectionOptions = options.kernelConnectionOptions ?? {};
this.serverSettings =
options.serverSettings ?? ServerConnection.makeSettings();
this.setupKernel(options.model.kernel);
}
/**
* A signal emitted when the session is disposed.
*/
get disposed(): ISignal<this, void> {
return this._disposed;
}
/**
* A signal emitted when the kernel changes.
*/
get kernelChanged(): ISignal<
this,
Session.ISessionConnection.IKernelChangedArgs
> {
return this._kernelChanged;
}
/**
* A signal proxied from the connection about the kernel status.
*/
get statusChanged(): ISignal<this, Kernel.Status> {
return this._statusChanged;
}
/**
* A signal proxied from the kernel about the connection status.
*/
get connectionStatusChanged(): ISignal<this, Kernel.ConnectionStatus> {
return this._connectionStatusChanged;
}
/**
* A signal proxied from the kernel pending input.
*/
get pendingInput(): ISignal<this, boolean> {
return this._pendingInput;
}
/**
* A signal proxied from the kernel about iopub kernel messages.
*/
get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
return this._iopubMessage;
}
/**
* A signal proxied from the kernel for an unhandled kernel message.
*/
get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
return this._unhandledMessage;
}
/**
* A signal proxied from the kernel emitted for any kernel message.
*
* #### Notes
* The behavior is undefined if the message is modified during message
* handling. As such, it should be treated as read-only.
*/
get anyMessage(): ISignal<this, Kernel.IAnyMessageArgs> {
return this._anyMessage;
}
/**
* A signal emitted when a session property changes.
*/
get propertyChanged(): ISignal<this, 'path' | 'name' | 'type'> {
return this._propertyChanged;
}
/**
* Get the session id.
*/
get id(): string {
return this._id;
}
/**
* Get the session kernel connection object.
*
* #### Notes
* This is a read-only property, and can be altered by [changeKernel].
*/
get kernel(): Kernel.IKernelConnection | null {
return this._kernel;
}
/**
* Get the session path.
*/
get path(): string {
return this._path;
}
/**
* Get the session type.
*/
get type(): string {
return this._type;
}
/**
* Get the session name.
*/
get name(): string {
return this._name;
}
/**
* Get the model associated with the session.
*/
get model(): Session.IModel {
return {
id: this.id,
kernel: this.kernel && { id: this.kernel.id, name: this.kernel.name },
path: this._path,
type: this._type,
name: this._name
};
}
/**
* The server settings of the session.
*/
readonly serverSettings: ServerConnection.ISettings;
/**
* Test whether the session has been disposed.
*/
get isDisposed(): boolean {
return this._isDisposed;
}
/**
* Update the session based on a session model from the server.
*
* #### Notes
* This only updates this session connection instance. Use `setPath`,
* `setName`, `setType`, and `changeKernel` to change the session values on
* the server.
*/
update(model: Session.IModel): void {
const oldModel = this.model;
this._path = model.path;
this._name = model.name;
this._type = model.type;
if (
(this._kernel === null && model.kernel !== null) ||
(this._kernel !== null && model.kernel === null) ||
(this._kernel !== null &&
model.kernel !== null &&
this._kernel.id !== model.kernel.id)
) {
if (this._kernel !== null) {
this._kernel.dispose();
}
const oldValue = this._kernel || null;
this.setupKernel(model.kernel);
const newValue = this._kernel || null;
this._kernelChanged.emit({ name: 'kernel', oldValue, newValue });
}
this._handleModelChange(oldModel);
}
/**
* Dispose of the resources held by the session.
*/
dispose(): void {
if (this.isDisposed) {
return;
}
this._isDisposed = true;
this._disposed.emit();
if (this._kernel) {
this._kernel.dispose();
const oldValue = this._kernel;
this._kernel = null;
const newValue = this._kernel;
this._kernelChanged.emit({ name: 'kernel', oldValue, newValue });
}
Signal.clearData(this);
}
/**
* Change the session path.
*
* @param path - The new session path.
*
* @returns A promise that resolves when the session has renamed.
*
* #### Notes
* This uses the Jupyter REST API, and the response is validated.
* The promise is fulfilled on a valid response and rejected otherwise.
*/
async setPath(path: string): Promise<void> {
if (this.isDisposed) {
throw new Error('Session is disposed');
}
await this._patch({ path });
}
/**
* Change the session name.
*/
async setName(name: string): Promise<void> {
if (this.isDisposed) {
throw new Error('Session is disposed');
}
await this._patch({ name });
}
/**
* Change the session type.
*/
async setType(type: string): Promise<void> {
if (this.isDisposed) {
throw new Error('Session is disposed');
}
await this._patch({ type });
}
/**
* Change the kernel.
*
* @param options - The name or id of the new kernel.
*
* #### Notes
* This shuts down the existing kernel and creates a new kernel,
* keeping the existing session ID and session path.
*/
async changeKernel(
options: Partial<Kernel.IModel>
): Promise<Kernel.IKernelConnection | null> {
if (this.isDisposed) {
throw new Error('Session is disposed');
}
await this._patch({ kernel: options });
return this.kernel;
}
/**
* Kill the kernel and shutdown the session.
*
* @returns - The promise fulfilled on a valid response from the server.
*
* #### Notes
* Uses the [Jupyter Server API](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter-server/jupyter_server/main/jupyter_server/services/api/api.yaml#!/sessions), and validates the response.
* Disposes of the session and emits a [sessionDied] signal on success.
*/
async shutdown(): Promise<void> {
if (this.isDisposed) {
throw new Error('Session is disposed');
}
await shutdownSession(this.id, this.serverSettings);
this.dispose();
}
/**
* Create a new kernel connection and connect to its signals.
*
* #### Notes
* This method is not meant to be subclassed.
*/
protected setupKernel(model: Kernel.IModel | null): void {
if (model === null) {
this._kernel = null;
return;
}
const kc = this._connectToKernel({
...this._kernelConnectionOptions,
model,
username: this._username,
clientId: this._clientId,
serverSettings: this.serverSettings
});
this._kernel = kc;
kc.statusChanged.connect(this.onKernelStatus, this);
kc.connectionStatusChanged.connect(this.onKernelConnectionStatus, this);
kc.pendingInput.connect(this.onPendingInput, this);
kc.unhandledMessage.connect(this.onUnhandledMessage, this);
kc.iopubMessage.connect(this.onIOPubMessage, this);
kc.anyMessage.connect(this.onAnyMessage, this);
}
/**
* Handle to changes in the Kernel status.
*/
protected onKernelStatus(
sender: Kernel.IKernelConnection,
state: Kernel.Status
): void {
this._statusChanged.emit(state);
}
/**
* Handle to changes in the Kernel status.
*/
protected onKernelConnectionStatus(
sender: Kernel.IKernelConnection,
state: Kernel.ConnectionStatus
): void {
this._connectionStatusChanged.emit(state);
}
/**
* Handle a change in the pendingInput.
*/
protected onPendingInput(sender: Kernel.IKernelConnection, state: boolean) {
this._pendingInput.emit(state);
}
/**
* Handle iopub kernel messages.
*/
protected onIOPubMessage(
sender: Kernel.IKernelConnection,
msg: KernelMessage.IIOPubMessage
): void {
this._iopubMessage.emit(msg);
}
/**
* Handle unhandled kernel messages.
*/
protected onUnhandledMessage(
sender: Kernel.IKernelConnection,
msg: KernelMessage.IMessage
): void {
this._unhandledMessage.emit(msg);
}
/**
* Handle any kernel messages.
*/
protected onAnyMessage(
sender: Kernel.IKernelConnection,
args: Kernel.IAnyMessageArgs
): void {
this._anyMessage.emit(args);
}
/**
* Send a PATCH to the server, updating the session path or the kernel.
*/
private async _patch(
body: DeepPartial<Session.IModel>
): Promise<Session.IModel> {
const model = await updateSession(
{ ...body, id: this._id },
this.serverSettings
);
this.update(model);
return model;
}
/**
* Handle a change to the model.
*/
private _handleModelChange(oldModel: Session.IModel): void {
if (oldModel.name !== this._name) {
this._propertyChanged.emit('name');
}
if (oldModel.type !== this._type) {
this._propertyChanged.emit('type');
}
if (oldModel.path !== this._path) {
this._propertyChanged.emit('path');
}
}
private _id = '';
private _path = '';
private _name = '';
private _type = '';
private _username: string;
private _clientId: string;
private _kernel: Kernel.IKernelConnection | null = null;
private _isDisposed = false;
private _disposed = new Signal<this, void>(this);
private _kernelChanged = new Signal<
this,
Session.ISessionConnection.IKernelChangedArgs
>(this);
private _statusChanged = new Signal<this, Kernel.Status>(this);
private _connectionStatusChanged = new Signal<this, Kernel.ConnectionStatus>(
this
);
private _pendingInput = new Signal<this, boolean>(this);
private _iopubMessage = new Signal<this, KernelMessage.IIOPubMessage>(this);
private _unhandledMessage = new Signal<this, KernelMessage.IMessage>(this);
private _anyMessage = new Signal<this, Kernel.IAnyMessageArgs>(this);
private _propertyChanged = new Signal<this, 'path' | 'name' | 'type'>(this);
private _connectToKernel: (
options: Kernel.IKernelConnection.IOptions
) => Kernel.IKernelConnection;
private _kernelConnectionOptions: Omit<
Kernel.IKernelConnection.IOptions,
'model' | 'username' | 'clientId' | 'serverSettings'
>;
}