Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposing session information to kernel (fix a commit error in #44) #46

Merged
merged 13 commits into from
Jun 2, 2020
99 changes: 99 additions & 0 deletions examples/sessions.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Session Manager"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ipylab import JupyterFrontEnd\n",
"app = JupyterFrontEnd()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## show all sessions from the global `SessionManager` instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"app.sessions.list_sessions()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Show current session"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"app.sessions.current_session"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Force update current session\n",
"current_session should be updated automatically, you can force a call if you really want to"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"app.sessions.get_current_session()\n",
"app.sessions.current_session"
TK-21st marked this conversation as resolved.
Show resolved Hide resolved
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
10 changes: 9 additions & 1 deletion ipylab/jupyterfrontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .commands import CommandRegistry
from .shell import Shell
from .sessions import SessionManager


@register
Expand All @@ -23,9 +24,16 @@ class JupyterFrontEnd(Widget):
version = Unicode(read_only=True).tag(sync=True)
shell = Instance(Shell).tag(sync=True, **widget_serialization)
commands = Instance(CommandRegistry).tag(sync=True, **widget_serialization)
sessions = Instance(SessionManager).tag(sync=True, **widget_serialization)
jtpio marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, *args, **kwargs):
super().__init__(*args, shell=Shell(), commands=CommandRegistry(), **kwargs)
super().__init__(
*args,
shell=Shell(),
commands=CommandRegistry(),
sessions=SessionManager(),
**kwargs
)
self._ready_event = asyncio.Event()
self._on_ready_callbacks = CallbackDispatcher()
self.on_msg(self._on_frontend_msg)
Expand Down
32 changes: 32 additions & 0 deletions ipylab/sessions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Expose current and all sessions to python kernel
"""

from ipywidgets import Widget, register
from traitlets import List, Unicode, Dict

from ._frontend import module_name, module_version


def _noop():
pass

TK-21st marked this conversation as resolved.
Show resolved Hide resolved

@register
class SessionManager(Widget):
"""Expose JupyterFrontEnd.serviceManager.sessions"""

_model_name = Unicode("SessionManagerModel").tag(sync=True)
_model_module = Unicode(module_name).tag(sync=True)
_model_module_version = Unicode(module_version).tag(sync=True)

# keeps track of alist of sessions
TK-21st marked this conversation as resolved.
Show resolved Hide resolved
current_session = Dict(read_only=True).tag(sync=True)
sessions = List([], read_only=True).tag(sync=True)

def get_current_session(self):
"""Force a call to update current session"""
self.send({"func": "get_current"})

def list_sessions(self):
TK-21st marked this conversation as resolved.
Show resolved Hide resolved
"""List all running sessions managed in the manager"""
return self.sessions
2 changes: 2 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const extension: JupyterFrontEndPlugin<void> = {
widgetExports.ShellModel.shell = shell;
widgetExports.CommandRegistryModel.commands = app.commands;
widgetExports.CommandPaletteModel.palette = palette;
widgetExports.SessionManagerModel.sessions = app.serviceManager.sessions;
widgetExports.SessionManagerModel.shell = shell;

registry.registerWidget({
name: MODULE_NAME,
Expand Down
2 changes: 2 additions & 0 deletions src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../css/widget.css';

import { CommandRegistryModel } from './widgets/commands';
import { CommandPaletteModel } from './widgets/palette';
import { SessionManagerModel } from './widgets/sessions';
import { JupyterFrontEndModel } from './widgets/frontend';
import { PanelModel } from './widgets/panel';
import { ShellModel } from './widgets/shell';
Expand All @@ -21,4 +22,5 @@ export {
SplitPanelModel,
SplitPanelView,
TitleModel,
SessionManagerModel,
};
123 changes: 123 additions & 0 deletions src/widgets/sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SessionManager exposes `JupyterLab.serviceManager.sessions` to user python kernel

import { SessionManager } from '@jupyterlab/services';
import { ISerializers, WidgetModel } from '@jupyter-widgets/base';
import { toArray } from '@lumino/algorithm';
import { MODULE_NAME, MODULE_VERSION } from '../version';
import { Session } from '@jupyterlab/services';
import { ILabShell } from '@jupyterlab/application';

/**
* The model for a Session Manager
*/
export class SessionManagerModel extends WidgetModel {
/**
* The default attributes.
*/
defaults(): any {
return {
...super.defaults(),
_model_name: SessionManagerModel.model_name,
_model_module: SessionManagerModel.model_module,
_model_module_version: SessionManagerModel.model_module_version,
current_session: null,
sessions: [],
};
}

/**
* Initialize a CommandRegistryModel instance.
TK-21st marked this conversation as resolved.
Show resolved Hide resolved
*
* @param attributes The base attributes.
* @param options The initialization options.
*/
initialize(attributes: any, options: any): void {
const { sessions, shell } = SessionManagerModel;
this._sessions = sessions;
this._shell = shell;
sessions.runningChanged.connect(this._sendSessions, this);
shell.currentChanged.connect(this._currentChanged, this);

super.initialize(attributes, options);
this.on('msg:custom', this._onMessage.bind(this));
this._shell.activeChanged.connect(this._currentChanged, this);
this._sendSessions();
this._sendCurrent();
}

/**
* Handle a custom message from the backend.
*
* @param msg The message to handle.
*/
private _onMessage(msg: any): void {
switch (msg.func) {
case 'get_current':
this._sendCurrent();
break;
default:
break;
}
}

/**
* get sessionContext from a given widget instance
*
* @param widget widget tracked by app.shell._track (FocusTracker)
*/
private _getSessionContext(widget: any): Session.IModel | {} {
return widget?.sessionContext?.session?.model ?? {};
}

/**
* Handle focus change in JLab
*
* NOTE: currentChange fires on two situations that we are concerned about here:
* 1. when user focuses on a widget in browser, which the `change.newValue` will
* be the current Widget
* 2. when user executes a code in console/notebook, where the `changed.newValue` will be null since
* we lost focus due to execution.
* To solve this problem, we interrogate `this._tracker.currentWidget` directly.
* We also added a simple fencing to reduce the number of Comm sync calls between Python/JS
*/
private _currentChanged(): void {
this._current_session = this._getSessionContext(this._shell.currentWidget);
this.set('current_session', this._current_session);
this.set('sessions', toArray(this._sessions.running()));
this.save_changes();
}

/**
* Send the list of commands to the backend.
TK-21st marked this conversation as resolved.
Show resolved Hide resolved
*/
private _sendSessions(): void {
this.set('sessions', toArray(this._sessions.running()));
this.save_changes();
}

/**
* send current session to backend
*/
private _sendCurrent(): void {
this._current_session = this._getSessionContext(this._shell.currentWidget);
this.set('current_session', this._current_session);
this.save_changes();
}

static serializers: ISerializers = {
...WidgetModel.serializers,
};

static model_name = 'SessionManagerModel';
static model_module = MODULE_NAME;
static model_module_version = MODULE_VERSION;
static view_name: string = null;
static view_module: string = null;
static view_module_version = MODULE_VERSION;

private _current_session: Session.IModel | {};
private _sessions: SessionManager;
static sessions: SessionManager;
private _shell: ILabShell;
static shell: ILabShell;
}