UNPKG

@jupyterlab/services

Version:

Client APIs for the Jupyter services REST APIs

738 lines 26.5 kB
"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