@jupyterlab/services
Version:
Client APIs for the Jupyter services REST APIs
738 lines • 26.5 kB
JavaScript
"use strict";
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FakeUserManager = exports.changeKernel = exports.MockShellFuture = exports.ServiceManagerMock = exports.KernelSpecManagerMock = exports.SessionManagerMock = exports.ContentsManagerMock = exports.SessionConnectionMock = exports.KernelMock = exports.cloneKernel = exports.NOTEBOOK_PATHS = exports.KERNEL_MODELS = exports.KERNELSPECS = exports.DEFAULT_NAME = void 0;
// We explicitly reference the jest typings since the jest.d.ts file shipped
// with jest 26 masks the @types/jest typings
/// <reference types="jest" />
const coreutils_1 = require("@jupyterlab/coreutils");
const coreutils_2 = require("@lumino/coreutils");
const properties_1 = require("@lumino/properties");
const signaling_1 = require("@lumino/signaling");
const basemanager_1 = require("./basemanager");
const contents_1 = require("./contents");
const kernel_1 = require("./kernel");
const serverconnection_1 = require("./serverconnection");
// The default kernel name
exports.DEFAULT_NAME = 'python3';
exports.KERNELSPECS = {
[exports.DEFAULT_NAME]: {
argv: [
'/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
'-m',
'ipykernel_launcher',
'-f',
'{connection_file}'
],
display_name: 'Python 3',
language: 'python',
metadata: {},
name: exports.DEFAULT_NAME,
resources: {}
},
irkernel: {
argv: [
'/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
'-m',
'ipykernel_launcher',
'-f',
'{connection_file}'
],
display_name: 'R',
language: 'r',
metadata: {},
name: 'irkernel',
resources: {}
}
};
exports.KERNEL_MODELS = [
{
name: exports.DEFAULT_NAME,
id: coreutils_2.UUID.uuid4()
},
{
name: 'r',
id: coreutils_2.UUID.uuid4()
},
{
name: exports.DEFAULT_NAME,
id: coreutils_2.UUID.uuid4()
}
];
// Notebook Paths for certain kernel name
exports.NOTEBOOK_PATHS = {
python3: ['Untitled.ipynb', 'Untitled1.ipynb', 'Untitled2.ipynb'],
r: ['Visualization.ipynb', 'Analysis.ipynb', 'Conclusion.ipynb']
};
/**
* Clone a kernel connection.
*/
function cloneKernel(kernel) {
return kernel.clone();
}
exports.cloneKernel = cloneKernel;
/**
* A mock kernel object.
*
* @param model The model of the kernel
*/
exports.KernelMock = jest.fn(options => {
const model = { id: 'foo', name: exports.DEFAULT_NAME, ...options.model };
options = {
clientId: coreutils_2.UUID.uuid4(),
username: coreutils_2.UUID.uuid4(),
...options,
model
};
let executionCount = 0;
const spec = Private.kernelSpecForKernelName(model.name);
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
...options,
...model,
model,
serverSettings: serverconnection_1.ServerConnection.makeSettings(options.serverSettings),
status: 'idle',
spec: Promise.resolve(spec),
dispose: jest.fn(),
clone: jest.fn(() => {
const newKernel = Private.cloneKernel(options);
newKernel.iopubMessage.connect((_, args) => {
iopubMessageSignal.emit(args);
});
newKernel.statusChanged.connect((_, args) => {
thisObject.status = args;
statusChangedSignal.emit(args);
});
return newKernel;
}),
info: Promise.resolve(Private.getInfo(model.name)),
shutdown: jest.fn(() => Promise.resolve(void 0)),
requestHistory: jest.fn(() => {
const historyReply = kernel_1.KernelMessage.createMessage({
channel: 'shell',
msgType: 'history_reply',
session: options.clientId,
username: options.username,
content: {
history: [],
status: 'ok'
}
});
return Promise.resolve(historyReply);
}),
restart: jest.fn(() => Promise.resolve(void 0)),
requestExecute: jest.fn(options => {
const msgId = coreutils_2.UUID.uuid4();
executionCount++;
Private.lastMessageProperty.set(thisObject, msgId);
const msg = kernel_1.KernelMessage.createMessage({
channel: 'iopub',
msgType: 'execute_input',
session: thisObject.clientId,
username: thisObject.username,
msgId,
content: {
code: options.code,
execution_count: executionCount
}
});
iopubMessageSignal.emit(msg);
const reply = kernel_1.KernelMessage.createMessage({
channel: 'shell',
msgType: 'execute_reply',
session: thisObject.clientId,
username: thisObject.username,
msgId,
content: {
user_expressions: {},
execution_count: executionCount,
status: 'ok'
}
});
return new exports.MockShellFuture(reply);
})
};
// Add signals.
const iopubMessageSignal = new signaling_1.Signal(thisObject);
const statusChangedSignal = new signaling_1.Signal(thisObject);
const pendingInputSignal = new signaling_1.Signal(thisObject);
thisObject.statusChanged = statusChangedSignal;
thisObject.iopubMessage = iopubMessageSignal;
thisObject.pendingInput = pendingInputSignal;
thisObject.hasPendingInput = false;
return thisObject;
});
/**
* A mock session connection.
*
* @param options Addition session options to use
* @param model A session model to use
*/
exports.SessionConnectionMock = jest.fn((options, kernel) => {
var _a, _b;
const name = (kernel === null || kernel === void 0 ? void 0 : kernel.name) || ((_b = (_a = options.model) === null || _a === void 0 ? void 0 : _a.kernel) === null || _b === void 0 ? void 0 : _b.name) || exports.DEFAULT_NAME;
kernel = kernel || new exports.KernelMock({ model: { name } });
const model = {
id: coreutils_2.UUID.uuid4(),
path: 'foo',
type: 'notebook',
name: 'foo',
...options.model,
kernel: kernel.model
};
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
...options,
model,
...model,
kernel,
serverSettings: serverconnection_1.ServerConnection.makeSettings(options.serverSettings),
dispose: jest.fn(),
changeKernel: jest.fn(partialModel => {
return changeKernel(kernel, partialModel);
}),
shutdown: jest.fn(() => Promise.resolve(void 0)),
setPath: jest.fn(path => {
thisObject.path = path;
propertyChangedSignal.emit('path');
return Promise.resolve();
}),
setName: jest.fn(name => {
thisObject.name = name;
propertyChangedSignal.emit('name');
return Promise.resolve();
}),
setType: jest.fn(type => {
thisObject.type = type;
propertyChangedSignal.emit('type');
return Promise.resolve();
})
};
const disposedSignal = new signaling_1.Signal(thisObject);
const propertyChangedSignal = new signaling_1.Signal(thisObject);
const statusChangedSignal = new signaling_1.Signal(thisObject);
const connectionStatusChangedSignal = new signaling_1.Signal(thisObject);
const kernelChangedSignal = new signaling_1.Signal(thisObject);
const iopubMessageSignal = new signaling_1.Signal(thisObject);
const unhandledMessageSignal = new signaling_1.Signal(thisObject);
const pendingInputSignal = new signaling_1.Signal(thisObject);
kernel.iopubMessage.connect((_, args) => {
iopubMessageSignal.emit(args);
}, thisObject);
kernel.statusChanged.connect((_, args) => {
statusChangedSignal.emit(args);
}, thisObject);
kernel.pendingInput.connect((_, args) => {
pendingInputSignal.emit(args);
}, thisObject);
thisObject.disposed = disposedSignal;
thisObject.connectionStatusChanged = connectionStatusChangedSignal;
thisObject.propertyChanged = propertyChangedSignal;
thisObject.statusChanged = statusChangedSignal;
thisObject.kernelChanged = kernelChangedSignal;
thisObject.iopubMessage = iopubMessageSignal;
thisObject.unhandledMessage = unhandledMessageSignal;
thisObject.pendingInput = pendingInputSignal;
return thisObject;
});
/**
* A mock contents manager.
*/
exports.ContentsManagerMock = jest.fn(() => {
const files = new Map();
const dummy = new contents_1.ContentsManager();
const checkpoints = new Map();
const checkPointContent = new Map();
const baseModel = Private.createFile({ type: 'directory' });
// create the default drive
files.set('', new Map([
['', { ...baseModel, path: '', name: '' }]
]));
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
newUntitled: jest.fn(options => {
const driveName = dummy.driveName((options === null || options === void 0 ? void 0 : options.path) || '');
const localPath = dummy.localPath((options === null || options === void 0 ? void 0 : options.path) || '');
// create the test file without the drive name
const createOptions = { ...options, path: localPath };
const model = Private.createFile(createOptions || {});
// re-add the drive name to the model
const drivePath = driveName ? `${driveName}:${model.path}` : model.path;
const driveModel = {
...model,
path: drivePath
};
files.get(driveName).set(model.path, driveModel);
fileChangedSignal.emit({
type: 'new',
oldValue: null,
newValue: driveModel
});
return Promise.resolve(driveModel);
}),
createCheckpoint: jest.fn(path => {
var _a;
const lastModified = new Date().toISOString();
const data = { id: coreutils_2.UUID.uuid4(), last_modified: lastModified };
checkpoints.set(path, data);
const driveName = dummy.driveName(path);
const localPath = dummy.localPath(path);
checkPointContent.set(path, (_a = files.get(driveName).get(localPath)) === null || _a === void 0 ? void 0 : _a.content);
return Promise.resolve(data);
}),
listCheckpoints: jest.fn(path => {
const p = checkpoints.get(path);
if (p !== undefined) {
return Promise.resolve([p]);
}
return Promise.resolve([]);
}),
deleteCheckpoint: jest.fn(path => {
if (!checkpoints.has(path)) {
return Private.makeResponseError(404);
}
checkpoints.delete(path);
return Promise.resolve();
}),
restoreCheckpoint: jest.fn(path => {
if (!checkpoints.has(path)) {
return Private.makeResponseError(404);
}
const driveName = dummy.driveName(path);
const localPath = dummy.localPath(path);
files.get(driveName).get(localPath).content =
checkPointContent.get(path);
return Promise.resolve();
}),
getSharedModelFactory: jest.fn(() => {
return null;
}),
normalize: jest.fn(path => {
return dummy.normalize(path);
}),
localPath: jest.fn(path => {
return dummy.localPath(path);
}),
resolvePath: jest.fn((root, path) => {
return dummy.resolvePath(root, path);
}),
get: jest.fn((path, options) => {
const driveName = dummy.driveName(path);
const localPath = dummy.localPath(path);
const drive = files.get(driveName);
path = Private.fixSlash(localPath);
if (!drive.has(path)) {
return Private.makeResponseError(404);
}
const model = drive.get(path);
const overrides = {};
if (path == 'random-hash.txt') {
overrides.hash = Math.random().toString();
}
else if (path == 'newer-timestamp-no-hash.txt') {
overrides.hash = undefined;
const tomorrow = new Date();
tomorrow.setDate(new Date().getDate() + 1);
overrides.last_modified = tomorrow.toISOString();
}
if (model.type === 'directory') {
if ((options === null || options === void 0 ? void 0 : options.content) !== false) {
const content = [];
drive.forEach(fileModel => {
const localPath = dummy.localPath(fileModel.path);
if (
// If file path is under this directory, add it to contents array.
coreutils_1.PathExt.dirname(localPath) == model.path &&
// But the directory should exclude itself from the contents array.
fileModel !== model) {
content.push(fileModel);
}
});
return Promise.resolve({ ...model, content });
}
return Promise.resolve(model);
}
if ((options === null || options === void 0 ? void 0 : options.content) != false) {
return Promise.resolve(model);
}
return Promise.resolve({ ...model, content: '', ...overrides });
}),
driveName: jest.fn(path => {
return dummy.driveName(path);
}),
rename: jest.fn((oldPath, newPath) => {
const driveName = dummy.driveName(oldPath);
const drive = files.get(driveName);
let oldLocalPath = dummy.localPath(oldPath);
let newLocalPath = dummy.localPath(newPath);
oldLocalPath = Private.fixSlash(oldLocalPath);
newLocalPath = Private.fixSlash(newLocalPath);
if (!drive.has(oldLocalPath)) {
return Private.makeResponseError(404);
}
const oldValue = drive.get(oldPath);
drive.delete(oldPath);
const name = coreutils_1.PathExt.basename(newLocalPath);
const newValue = { ...oldValue, name, path: newLocalPath };
drive.set(newPath, newValue);
fileChangedSignal.emit({
type: 'rename',
oldValue,
newValue
});
return Promise.resolve(newValue);
}),
delete: jest.fn(path => {
const driveName = dummy.driveName(path);
const localPath = dummy.localPath(path);
const drive = files.get(driveName);
path = Private.fixSlash(localPath);
if (!drive.has(path)) {
return Private.makeResponseError(404);
}
const oldValue = drive.get(path);
drive.delete(path);
fileChangedSignal.emit({
type: 'delete',
oldValue,
newValue: null
});
return Promise.resolve(void 0);
}),
save: jest.fn((path, options) => {
if (path == 'readonly.txt') {
return Private.makeResponseError(403);
}
path = Private.fixSlash(path);
const timeStamp = new Date().toISOString();
const drive = files.get(dummy.driveName(path));
if (drive.has(path)) {
const updates = path == 'frozen-time-and-hash.txt'
? {}
: {
last_modified: timeStamp,
hash: timeStamp
};
drive.set(path, {
...drive.get(path),
...options,
...updates
});
}
else {
drive.set(path, {
path,
name: coreutils_1.PathExt.basename(path),
content: '',
writable: true,
created: timeStamp,
type: 'file',
format: 'text',
mimetype: 'plain/text',
...options,
last_modified: timeStamp,
hash: timeStamp,
hash_algorithm: 'static'
});
}
fileChangedSignal.emit({
type: 'save',
oldValue: null,
newValue: drive.get(path)
});
return Promise.resolve(drive.get(path));
}),
getDownloadUrl: jest.fn(path => {
return dummy.getDownloadUrl(path);
}),
addDrive: jest.fn(drive => {
dummy.addDrive(drive);
files.set(drive.name, new Map([
['', { ...baseModel, path: '', name: '' }]
]));
}),
dispose: jest.fn()
};
const fileChangedSignal = new signaling_1.Signal(thisObject);
thisObject.fileChanged = fileChangedSignal;
return thisObject;
});
/**
* A mock sessions manager.
*/
exports.SessionManagerMock = jest.fn(() => {
let sessions = [];
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
ready: Promise.resolve(void 0),
isReady: true,
startNew: jest.fn(options => {
const session = new exports.SessionConnectionMock({ model: options }, null);
sessions.push(session.model);
runningChangedSignal.emit(sessions);
return Promise.resolve(session);
}),
connectTo: jest.fn(options => {
return new exports.SessionConnectionMock(options, null);
}),
stopIfNeeded: jest.fn(path => {
const length = sessions.length;
sessions = sessions.filter(model => model.path !== path);
if (sessions.length !== length) {
runningChangedSignal.emit(sessions);
}
return Promise.resolve(void 0);
}),
refreshRunning: jest.fn(() => Promise.resolve(void 0)),
running: jest.fn(() => sessions[Symbol.iterator]())
};
const runningChangedSignal = new signaling_1.Signal(thisObject);
thisObject.runningChanged = runningChangedSignal;
return thisObject;
});
/**
* A mock kernel specs manager
*/
exports.KernelSpecManagerMock = jest.fn(() => {
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
specs: { default: exports.DEFAULT_NAME, kernelspecs: exports.KERNELSPECS },
isReady: true,
ready: Promise.resolve(void 0),
refreshSpecs: jest.fn(() => Promise.resolve(void 0))
};
return thisObject;
});
/**
* A mock service manager.
*/
exports.ServiceManagerMock = jest.fn(() => {
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
ready: Promise.resolve(void 0),
isReady: true,
contents: new exports.ContentsManagerMock(),
sessions: new exports.SessionManagerMock(),
kernelspecs: new exports.KernelSpecManagerMock(),
dispose: jest.fn()
};
return thisObject;
});
/**
* A mock kernel shell future.
*/
exports.MockShellFuture = jest.fn((result) => {
const thisObject = {
...jest.requireActual('@jupyterlab/services'),
dispose: jest.fn(),
done: Promise.resolve(result)
};
return thisObject;
});
function changeKernel(kernel, partialModel) {
if (partialModel.id) {
const kernelIdx = exports.KERNEL_MODELS.findIndex(model => {
return model.id === partialModel.id;
});
if (kernelIdx !== -1) {
kernel.model = Private.RUNNING_KERNELS[kernelIdx].model;
kernel.id = partialModel.id;
return Promise.resolve(Private.RUNNING_KERNELS[kernelIdx]);
}
else {
throw new Error(`Unable to change kernel to one with id: ${partialModel.id}`);
}
}
else if (partialModel.name) {
const kernelIdx = exports.KERNEL_MODELS.findIndex(model => {
return model.name === partialModel.name;
});
if (kernelIdx !== -1) {
kernel.model = Private.RUNNING_KERNELS[kernelIdx].model;
kernel.id = partialModel.id;
return Promise.resolve(Private.RUNNING_KERNELS[kernelIdx]);
}
else {
throw new Error(`Unable to change kernel to one with name: ${partialModel.name}`);
}
}
else {
throw new Error(`Unable to change kernel`);
}
}
exports.changeKernel = changeKernel;
/**
* A namespace for module private data.
*/
var Private;
(function (Private) {
function createFile(options) {
options = options || {};
let name = coreutils_2.UUID.uuid4();
switch (options.type) {
case 'directory':
name = `Untitled Folder_${name}`;
break;
case 'notebook':
name = `Untitled_${name}.ipynb`;
break;
default:
name = `untitled_${name}${options.ext || '.txt'}`;
}
const path = coreutils_1.PathExt.join(options.path || '', name);
let content = '';
if (options.type === 'notebook') {
content = JSON.stringify({});
}
const timeStamp = new Date().toISOString();
return {
path,
content,
name,
last_modified: timeStamp,
writable: true,
created: timeStamp,
type: options.type || 'file',
format: 'text',
mimetype: 'plain/text'
};
}
Private.createFile = createFile;
function fixSlash(path) {
if (path.endsWith('/')) {
path = path.slice(0, path.length - 1);
}
return path;
}
Private.fixSlash = fixSlash;
function makeResponseError(status) {
const resp = new Response(void 0, { status });
return Promise.reject(new serverconnection_1.ServerConnection.ResponseError(resp));
}
Private.makeResponseError = makeResponseError;
function cloneKernel(options) {
return new exports.KernelMock({ ...options, clientId: coreutils_2.UUID.uuid4() });
}
Private.cloneKernel = cloneKernel;
// Get the kernel spec for kernel name
function kernelSpecForKernelName(name) {
return exports.KERNELSPECS[name];
}
Private.kernelSpecForKernelName = kernelSpecForKernelName;
// Get the kernel info for kernel name
function getInfo(name) {
return {
protocol_version: '1',
implementation: 'foo',
implementation_version: '1',
language_info: {
version: '1',
name
},
banner: 'hello, world!',
help_links: [],
status: 'ok'
};
}
Private.getInfo = getInfo;
// This list of running kernels simply mirrors the KERNEL_MODELS and KERNELSPECS lists
Private.RUNNING_KERNELS = exports.KERNEL_MODELS.map((model, _) => {
return new exports.KernelMock({ model });
});
Private.lastMessageProperty = new properties_1.AttachedProperty({
name: 'lastMessageId',
create: () => ''
});
})(Private || (Private = {}));
/**
* The user API service manager.
*/
class FakeUserManager extends basemanager_1.BaseManager {
/**
* Create a new user manager.
*/
constructor(options = {}, identity, permissions) {
super(options);
this._isReady = false;
this._userChanged = new signaling_1.Signal(this);
this._connectionFailure = new signaling_1.Signal(this);
// Initialize internal data.
this._ready = new Promise(resolve => {
// Schedule updating the user to the next macro task queue.
setTimeout(() => {
this._identity = identity;
this._permissions = permissions;
this._userChanged.emit({
identity: this._identity,
permissions: this._permissions
});
resolve();
}, 0);
})
.then(() => {
if (this.isDisposed) {
return;
}
this._isReady = true;
})
.catch(_ => undefined);
}
/**
* Test whether the manager is ready.
*/
get isReady() {
return this._isReady;
}
/**
* A promise that fulfills when the manager is ready.
*/
get ready() {
return this._ready;
}
/**
* Get the most recently fetched identity.
*/
get identity() {
return this._identity;
}
/**
* Get the most recently fetched permissions.
*/
get permissions() {
return this._permissions;
}
/**
* A signal emitted when the user changes.
*/
get userChanged() {
return this._userChanged;
}
/**
* A signal emitted when there is a connection failure.
*/
get connectionFailure() {
return this._connectionFailure;
}
/**
* Dispose of the resources used by the manager.
*/
dispose() {
super.dispose();
}
/**
* Force a refresh of the specs from the server.
*
* @returns A promise that resolves when the specs are fetched.
*
* #### Notes
* This is intended to be called only in response to a user action,
* since the manager maintains its internal state.
*/
async refreshUser() {
return Promise.resolve();
}
}
exports.FakeUserManager = FakeUserManager;
//# sourceMappingURL=testutils.js.map