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

Notebook server info files #4772

Merged
merged 5 commits into from
Jan 14, 2014
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 69 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 @@ -72,6 +74,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 @@ -499,6 +502,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 @@ -594,6 +603,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 @@ -666,7 +689,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 @@ -691,33 +713,56 @@ 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),
'baseurlpath': self.base_project_url,
'notebookdir': os.path.abspath(self.notebook_manager.notebook_dir),
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably keep the same names in JSON, so base_project_url and 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'
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 @@ -732,16 +777,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 discover_running_servers(profile='default'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like this should be defined somewhere other than the notebookapp file, but I'm not sure where. Any idea?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a brief look, but this seemed the most logical place I could see. I'd argue that it's effectively 'list instances of NotebookApp on this system', so it makes sense to define it near the NotebookApp class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to add an ipython notebook list subcommand to show currently running servers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am +1 on having a notebook list subcommand, but I think we should name the function using "list" instead of "discover": discover_running_servers.

"""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.discover_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()