Skip to content

Commit

Permalink
Merge pull request ipython#4772 from takluyver/nbserver-files
Browse files Browse the repository at this point in the history
Notebook server info files
  • Loading branch information
minrk committed Jan 14, 2014
2 parents 6c9fd5a + 92e4706 commit ecedcf1
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 10 deletions.
107 changes: 97 additions & 10 deletions IPython/html/notebookapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

# stdlib
import errno
import io
import json
import logging
import os
import random
Expand Down Expand Up @@ -73,6 +75,7 @@

from IPython.config.application import catch_config_error, boolean_flag
from IPython.core.application import BaseIPythonApplication
from IPython.core.profiledir import ProfileDir
from IPython.consoleapp import IPythonConsoleApp
from IPython.kernel import swallow_argv
from IPython.kernel.zmq.session import default_secure
Expand Down Expand Up @@ -214,6 +217,27 @@ def init_handlers(self, settings):
return new_handlers


class NbserverListApp(BaseIPythonApplication):

description="List currently running notebook servers in this profile."

flags = dict(
json=({'NbserverListApp': {'json': True}},
"Produce machine-readable JSON output."),
)

json = Bool(False, config=True,
help="If True, each line of output will be a JSON object with the "
"details from the server info file.")

def start(self):
if not self.json:
print("Currently running servers:")
for serverinfo in list_running_servers(self.profile):
if self.json:
print(json.dumps(serverinfo))
else:
print(serverinfo['url'], "::", serverinfo['notebook_dir'])

#-----------------------------------------------------------------------------
# Aliases and Flags
Expand Down Expand Up @@ -286,6 +310,10 @@ class NotebookApp(BaseIPythonApplication):
FileNotebookManager]
flags = Dict(flags)
aliases = Dict(aliases)

subcommands = dict(
list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
)

kernel_argv = List(Unicode)

Expand Down Expand Up @@ -502,6 +530,12 @@ def _mathjax_url_changed(self, name, old, new):
"sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
)

info_file = Unicode()

def _info_file_default(self):
info_file = "nbserver-%s.json"%os.getpid()
return os.path.join(self.profile_dir.security_dir, info_file)

def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)

Expand Down Expand Up @@ -597,6 +631,20 @@ def init_webapp(self):
'no available port could be found.')
self.exit(1)

@property
def display_url(self):
ip = self.ip if self.ip else '[all ip addresses on your system]'
return self._url(ip)

@property
def connection_url(self):
ip = self.ip if self.ip else localhost()
return self._url(ip)

def _url(self, ip):
proto = 'https' if self.certfile else 'http'
return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)

def init_signal(self):
if not sys.platform.startswith('win'):
signal.signal(signal.SIGINT, self._handle_sigint)
Expand Down Expand Up @@ -669,7 +717,6 @@ def init_components(self):
elif status == 'unclean':
self.log.warn("components submodule unclean, you may see 404s on static/components")
self.log.warn("run `setup.py submodule` or `git submodule update` to update")


@catch_config_error
def initialize(self, argv=None):
Expand All @@ -694,33 +741,59 @@ def notebook_info(self):
"Return the current working directory and the server url information"
info = self.notebook_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The IPython Notebook is running at: %s" % self._url
return info + "The IPython Notebook is running at: %s" % self.display_url

def server_info(self):
"""Return a JSONable dict of information about this server."""
return {'url': self.connection_url,
'hostname': self.ip if self.ip else 'localhost',
'port': self.port,
'secure': bool(self.certfile),
'base_project_url': self.base_project_url,
'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
}

def write_server_info_file(self):
"""Write the result of server_info() to the JSON file info_file."""
with open(self.info_file, 'w') as f:
json.dump(self.server_info(), f, indent=2)

def remove_server_info_file(self):
"""Remove the nbserver-<pid>.json file created for this server.
Ignores the error raised when the file has already been removed.
"""
try:
os.unlink(self.info_file)
except OSError as e:
if e.errno != errno.ENOENT:
raise

def start(self):
""" Start the IPython Notebook server app, after initialization
This method takes no arguments so all configuration and initialization
must be done prior to calling this method."""
ip = self.ip if self.ip else '[all ip addresses on your system]'
proto = 'https' if self.certfile else 'http'
if self.subapp is not None:
return self.subapp.start()

info = self.log.info
self._url = "%s://%s:%i%s" % (proto, ip, self.port,
self.base_project_url)
for line in self.notebook_info().split("\n"):
info(line)
info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")

self.write_server_info_file()

if self.open_browser or self.file_to_run:
ip = self.ip or localhost()
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warn('No web browser found: %s.' % e)
browser = None

nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
f = self.file_to_run
if f:
nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
if f.startswith(nbdir):
f = f[len(nbdir):]
else:
Expand All @@ -735,16 +808,30 @@ def start(self):
else:
url = url_path_join('tree', f)
if browser:
b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
self.port, self.base_project_url, url), new=2)
b = lambda : browser.open("%s%s" % (self.connection_url, url),
new=2)
threading.Thread(target=b).start()
try:
ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
info("Interrupted...")
finally:
self.cleanup_kernels()
self.remove_server_info_file()


def list_running_servers(profile='default'):
"""Iterate over the server info files of running notebook servers.
Given a profile name, find nbserver-* files in the security directory of
that profile, and yield dicts of their information, each one pertaining to
a currently running notebook server instance.
"""
pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
for file in os.listdir(pd.security_dir):
if file.startswith('nbserver-'):
with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
yield json.load(f)

#-----------------------------------------------------------------------------
# Main entry point
Expand Down
16 changes: 16 additions & 0 deletions IPython/html/tests/test_notebookapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import nose.tools as nt

import IPython.testing.tools as tt
from IPython.html import notebookapp

#-----------------------------------------------------------------------------
# Test functions
Expand All @@ -23,3 +24,18 @@ def test_help_output():
"""ipython notebook --help-all works"""
tt.help_all_output_test('notebook')

def test_server_info_file():
nbapp = notebookapp.NotebookApp(profile='nbserver_file_test')
def get_servers():
return list(notebookapp.list_running_servers(profile='nbserver_file_test'))
nbapp.initialize(argv=[])
nbapp.write_server_info_file()
servers = get_servers()
nt.assert_equal(len(servers), 1)
nt.assert_equal(servers[0]['port'], nbapp.port)
nt.assert_equal(servers[0]['url'], nbapp.connection_url)
nbapp.remove_server_info_file()
nt.assert_equal(get_servers(), [])

# The ENOENT error should be silenced.
nbapp.remove_server_info_file()

0 comments on commit ecedcf1

Please sign in to comment.