Merge pull request #3088 from minrk/nbsettings

cleanup IPython handler settings
pull/37/head
Brian E. Granger 13 years ago
commit 0d6676f340

@ -32,9 +32,15 @@ from tornado.escape import url_escape
from tornado import web
from tornado import websocket
try:
from tornado.log import app_log
except ImportError:
app_log = logging.getLogger()
from zmq.eventloop import ioloop
from zmq.utils import jsonapi
from IPython.config import Application
from IPython.external.decorator import decorator
from IPython.kernel.zmq.session import Session
from IPython.lib.security import passwd_check
@ -96,14 +102,14 @@ if tornado.version_info <= (2,1,1):
websocket.WebSocketHandler._execute = _execute
del _execute
#-----------------------------------------------------------------------------
# Decorator for disabling read-only handlers
#-----------------------------------------------------------------------------
@decorator
def not_if_readonly(f, self, *args, **kwargs):
if self.application.read_only:
if self.settings.get('read_only', False):
raise web.HTTPError(403, "Notebook server is read-only")
else:
return f(self, *args, **kwargs)
@ -120,13 +126,13 @@ def authenticate_unless_readonly(f, self, *args, **kwargs):
def auth_f(self, *args, **kwargs):
return f(self, *args, **kwargs)
if self.application.read_only:
if self.settings.get('read_only', False):
return f(self, *args, **kwargs)
else:
return auth_f(self, *args, **kwargs)
def urljoin(*pieces):
"""Join componenet of url into a relative url
"""Join components of url into a relative url
Use to prevent double slash when joining subpath
"""
@ -147,18 +153,30 @@ class RequestHandler(web.RequestHandler):
class AuthenticatedHandler(RequestHandler):
"""A RequestHandler with an authenticated user."""
def clear_login_cookie(self):
self.clear_cookie(self.cookie_name)
def get_current_user(self):
user_id = self.get_secure_cookie(self.settings['cookie_name'])
user_id = self.get_secure_cookie(self.cookie_name)
# For now the user_id should not return empty, but it could eventually
if user_id == '':
user_id = 'anonymous'
if user_id is None:
# prevent extra Invalid cookie sig warnings:
self.clear_cookie(self.settings['cookie_name'])
if not self.application.password and not self.application.read_only:
self.clear_login_cookie()
if not self.read_only and not self.login_available:
user_id = 'anonymous'
return user_id
@property
def cookie_name(self):
return self.settings.get('cookie_name', '')
@property
def password(self):
"""our password"""
return self.settings.get('password', '')
@property
def logged_in(self):
"""Is a user currently logged in?
@ -175,20 +193,43 @@ class AuthenticatedHandler(RequestHandler):
whether the user is already logged in or not.
"""
return bool(self.application.password)
return bool(self.settings.get('password', ''))
@property
def read_only(self):
"""Is the notebook read-only?
"""
return self.application.read_only
return self.settings.get('read_only', False)
class IPythonHandler(AuthenticatedHandler):
"""IPython-specific extensions to authenticated handling
Mostly property shortcuts to IPython-specific settings.
"""
@property
def config(self):
return self.settings.get('config', None)
@property
def log(self):
"""use the IPython log by default, falling back on tornado's logger"""
if Application.initialized():
return Application.instance().log
else:
return app_log
@property
def use_less(self):
"""Use less instead of css in templates"""
return self.application.use_less
return self.settings.get('use_less', False)
#---------------------------------------------------------------
# URLs
#---------------------------------------------------------------
@property
def ws_url(self):
"""websocket url matching the current request
@ -197,13 +238,69 @@ class AuthenticatedHandler(RequestHandler):
ws[s]://host[:port]
"""
proto = self.request.protocol.replace('http', 'ws')
host = self.application.ipython_app.websocket_host # default to config value
host = self.settings.get('websocket_host', '')
# default to config value
if host == '':
host = self.request.host # get from request
return "%s://%s" % (proto, host)
@property
def mathjax_url(self):
return self.settings.get('mathjax_url', '')
@property
def base_project_url(self):
return self.settings.get('base_project_url', '/')
@property
def base_kernel_url(self):
return self.settings.get('base_kernel_url', '/')
#---------------------------------------------------------------
# Manager objects
#---------------------------------------------------------------
@property
def kernel_manager(self):
return self.settings['kernel_manager']
@property
def notebook_manager(self):
return self.settings['notebook_manager']
@property
def cluster_manager(self):
return self.settings['cluster_manager']
@property
def project(self):
return self.notebook_manager.notebook_dir
#---------------------------------------------------------------
# template rendering
#---------------------------------------------------------------
def get_template(self, name):
"""Return the jinja template object for a given name"""
return self.settings['jinja2_env'].get_template(name)
def render_template(self, name, **ns):
ns.update(self.template_namespace)
template = self.get_template(name)
return template.render(**ns)
@property
def template_namespace(self):
return dict(
base_project_url=self.base_project_url,
base_kernel_url=self.base_kernel_url,
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
use_less=self.use_less,
)
class AuthenticatedFileHandler(AuthenticatedHandler, web.StaticFileHandler):
class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
"""static files should only be accessible when logged in"""
@authenticate_unless_readonly
@ -211,125 +308,89 @@ class AuthenticatedFileHandler(AuthenticatedHandler, web.StaticFileHandler):
return web.StaticFileHandler.get(self, path)
class ProjectDashboardHandler(AuthenticatedHandler):
class ProjectDashboardHandler(IPythonHandler):
@authenticate_unless_readonly
def get(self):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
template = self.application.jinja2_env.get_template('projectdashboard.html')
self.write( template.render(
project=project,
project_component=project.split('/'),
base_project_url=self.application.ipython_app.base_project_url,
base_kernel_url=self.application.ipython_app.base_kernel_url,
read_only=self.read_only,
logged_in=self.logged_in,
use_less=self.use_less,
login_available=self.login_available))
self.write(self.render_template('projectdashboard.html',
project=self.project,
project_component=self.project.split('/'),
))
class LoginHandler(AuthenticatedHandler):
class LoginHandler(IPythonHandler):
def _render(self, message=None):
template = self.application.jinja2_env.get_template('login.html')
self.write( template.render(
next=url_escape(self.get_argument('next', default=self.application.ipython_app.base_project_url)),
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
base_project_url=self.application.ipython_app.base_project_url,
message=message
def _render(self, message=None):
self.write(self.render_template('login.html',
next=url_escape(self.get_argument('next', default=self.base_project_url)),
message=message,
))
def get(self):
if self.current_user:
self.redirect(self.get_argument('next', default=self.application.ipython_app.base_project_url))
self.redirect(self.get_argument('next', default=self.base_project_url))
else:
self._render()
def post(self):
pwd = self.get_argument('password', default=u'')
if self.application.password:
if passwd_check(self.application.password, pwd):
self.set_secure_cookie(self.settings['cookie_name'], str(uuid.uuid4()))
if self.login_available:
if passwd_check(self.password, pwd):
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
else:
self._render(message={'error': 'Invalid password'})
return
self.redirect(self.get_argument('next', default=self.application.ipython_app.base_project_url))
self.redirect(self.get_argument('next', default=self.base_project_url))
class LogoutHandler(AuthenticatedHandler):
class LogoutHandler(IPythonHandler):
def get(self):
self.clear_cookie(self.settings['cookie_name'])
self.clear_login_cookie()
if self.login_available:
message = {'info': 'Successfully logged out.'}
else:
message = {'warning': 'Cannot log out. Notebook authentication '
'is disabled.'}
template = self.application.jinja2_env.get_template('logout.html')
self.write( template.render(
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
base_project_url=self.application.ipython_app.base_project_url,
self.write(self.render_template('logout.html',
message=message))
class NewHandler(AuthenticatedHandler):
class NewHandler(IPythonHandler):
@web.authenticated
def get(self):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
notebook_id = nbm.new_notebook()
self.redirect('/'+urljoin(self.application.ipython_app.base_project_url, notebook_id))
notebook_id = self.notebook_manager.new_notebook()
self.redirect('/' + urljoin(self.base_project_url, notebook_id))
class NamedNotebookHandler(AuthenticatedHandler):
class NamedNotebookHandler(IPythonHandler):
@authenticate_unless_readonly
def get(self, notebook_id):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
nbm = self.notebook_manager
if not nbm.notebook_exists(notebook_id):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
template = self.application.jinja2_env.get_template('notebook.html')
self.write( template.render(
project=project,
self.write(self.render_template('notebook.html',
project=self.project,
notebook_id=notebook_id,
base_project_url=self.application.ipython_app.base_project_url,
base_kernel_url=self.application.ipython_app.base_kernel_url,
kill_kernel=False,
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
mathjax_url=self.application.ipython_app.mathjax_url,
use_less=self.use_less
mathjax_url=self.mathjax_url,
)
)
class PrintNotebookHandler(AuthenticatedHandler):
class PrintNotebookHandler(IPythonHandler):
@authenticate_unless_readonly
def get(self, notebook_id):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
if not nbm.notebook_exists(notebook_id):
if not self.notebook_manager.notebook_exists(notebook_id):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
template = self.application.jinja2_env.get_template('printnotebook.html')
self.write( template.render(
project=project,
self.write( self.render_template('printnotebook.html',
project=self.project,
notebook_id=notebook_id,
base_project_url=self.application.ipython_app.base_project_url,
base_kernel_url=self.application.ipython_app.base_kernel_url,
kill_kernel=False,
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
mathjax_url=self.application.ipython_app.mathjax_url,
mathjax_url=self.mathjax_url,
))
#-----------------------------------------------------------------------------
@ -337,17 +398,17 @@ class PrintNotebookHandler(AuthenticatedHandler):
#-----------------------------------------------------------------------------
class MainKernelHandler(AuthenticatedHandler):
class MainKernelHandler(IPythonHandler):
@web.authenticated
def get(self):
km = self.application.kernel_manager
km = self.kernel_manager
self.finish(jsonapi.dumps(km.list_kernel_ids()))
@web.authenticated
def post(self):
km = self.application.kernel_manager
nbm = self.application.notebook_manager
km = self.kernel_manager
nbm = self.notebook_manager
notebook_id = self.get_argument('notebook', default=None)
kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
@ -355,23 +416,23 @@ class MainKernelHandler(AuthenticatedHandler):
self.finish(jsonapi.dumps(data))
class KernelHandler(AuthenticatedHandler):
class KernelHandler(IPythonHandler):
SUPPORTED_METHODS = ('DELETE')
@web.authenticated
def delete(self, kernel_id):
km = self.application.kernel_manager
km = self.kernel_manager
km.shutdown_kernel(kernel_id)
self.set_status(204)
self.finish()
class KernelActionHandler(AuthenticatedHandler):
class KernelActionHandler(IPythonHandler):
@web.authenticated
def post(self, kernel_id, action):
km = self.application.kernel_manager
km = self.kernel_manager
if action == 'interrupt':
km.interrupt_kernel(kernel_id)
self.set_status(204)
@ -384,6 +445,10 @@ class KernelActionHandler(AuthenticatedHandler):
class ZMQStreamHandler(websocket.WebSocketHandler):
def clear_cookie(self, *args, **kwargs):
"""meaningless for websockets"""
pass
def _reserialize_reply(self, msg_list):
"""Reserialize a reply message using JSON.
@ -413,7 +478,7 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
try:
msg = self._reserialize_reply(msg_list)
except Exception:
self.application.log.critical("Malformed message: %r" % msg_list, exc_info=True)
self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
else:
self.write_message(msg)
@ -426,26 +491,14 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
return True
class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
def open(self, kernel_id):
self.kernel_id = kernel_id.decode('ascii')
try:
cfg = self.application.config
except AttributeError:
# protect from the case where this is run from something other than
# the notebook app:
cfg = None
self.session = Session(config=cfg)
self.session = Session(config=self.config)
self.save_on_message = self.on_message
self.on_message = self.on_first_message
def get_current_user(self):
user_id = self.get_secure_cookie(self.settings['cookie_name'])
if user_id == '' or (user_id is None and not self.application.password):
user_id = 'anonymous'
return user_id
def _inject_cookie_message(self, msg):
"""Inject the first message, which is the document cookie,
for authentication."""
@ -456,12 +509,12 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
try:
self.request._cookies = Cookie.SimpleCookie(msg)
except:
logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
def on_first_message(self, msg):
self._inject_cookie_message(msg)
if self.get_current_user() is None:
logging.warn("Couldn't authenticate WebSocket connection")
self.log.warn("Couldn't authenticate WebSocket connection")
raise web.HTTPError(403)
self.on_message = self.save_on_message
@ -477,7 +530,7 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
except web.HTTPError:
self.close()
return
km = self.application.kernel_manager
km = self.kernel_manager
kernel_id = self.kernel_id
km.add_restart_callback(kernel_id, self.on_kernel_restarted)
km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
@ -502,18 +555,18 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
self.write_message(jsonapi.dumps(msg, default=date_default))
def on_kernel_restarted(self):
logging.warn("kernel %s restarted", self.kernel_id)
self.log.warn("kernel %s restarted", self.kernel_id)
self._send_status_message('restarting')
def on_restart_failed(self):
logging.error("kernel %s restarted failed!", self.kernel_id)
self.log.error("kernel %s restarted failed!", self.kernel_id)
self._send_status_message('dead')
def on_close(self):
# This method can be called twice, once by self.kernel_died and once
# from the WebSocket close event. If the WebSocket connection is
# closed before the ZMQ streams are setup, they could be None.
km = self.application.kernel_manager
km = self.kernel_manager
if self.kernel_id in km:
km.remove_restart_callback(
self.kernel_id, self.on_kernel_restarted,
@ -527,6 +580,10 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
class ShellHandler(AuthenticatedZMQStreamHandler):
@property
def max_msg_size(self):
return self.settings.get('max_msg_size', 65535)
def initialize(self, *args, **kwargs):
self.shell_stream = None
@ -537,8 +594,7 @@ class ShellHandler(AuthenticatedZMQStreamHandler):
except web.HTTPError:
self.close()
return
km = self.application.kernel_manager
self.max_msg_size = km.max_msg_size
km = self.kernel_manager
kernel_id = self.kernel_id
try:
self.shell_stream = km.connect_shell(kernel_id)
@ -566,26 +622,26 @@ class ShellHandler(AuthenticatedZMQStreamHandler):
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookRedirectHandler(AuthenticatedHandler):
class NotebookRedirectHandler(IPythonHandler):
@authenticate_unless_readonly
def get(self, notebook_name):
app = self.application
# strip trailing .ipynb:
notebook_name = os.path.splitext(notebook_name)[0]
notebook_id = app.notebook_manager.rev_mapping.get(notebook_name, '')
notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
if notebook_id:
url = self.settings.get('base_project_url', '/') + notebook_id
return self.redirect(url)
else:
raise HTTPError(404)
class NotebookRootHandler(AuthenticatedHandler):
class NotebookRootHandler(IPythonHandler):
@authenticate_unless_readonly
def get(self):
nbm = self.application.notebook_manager
km = self.application.kernel_manager
nbm = self.notebook_manager
km = self.kernel_manager
files = nbm.list_notebooks()
for f in files :
f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
@ -593,7 +649,7 @@ class NotebookRootHandler(AuthenticatedHandler):
@web.authenticated
def post(self):
nbm = self.application.notebook_manager
nbm = self.notebook_manager
body = self.request.body.strip()
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
@ -605,13 +661,13 @@ class NotebookRootHandler(AuthenticatedHandler):
self.finish(jsonapi.dumps(notebook_id))
class NotebookHandler(AuthenticatedHandler):
class NotebookHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
@authenticate_unless_readonly
def get(self, notebook_id):
nbm = self.application.notebook_manager
nbm = self.notebook_manager
format = self.get_argument('format', default='json')
last_mod, name, data = nbm.get_notebook(notebook_id, format)
@ -626,7 +682,7 @@ class NotebookHandler(AuthenticatedHandler):
@web.authenticated
def put(self, notebook_id):
nbm = self.application.notebook_manager
nbm = self.notebook_manager
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
@ -635,20 +691,17 @@ class NotebookHandler(AuthenticatedHandler):
@web.authenticated
def delete(self, notebook_id):
nbm = self.application.notebook_manager
nbm.delete_notebook(notebook_id)
self.notebook_manager.delete_notebook(notebook_id)
self.set_status(204)
self.finish()
class NotebookCopyHandler(AuthenticatedHandler):
class NotebookCopyHandler(IPythonHandler):
@web.authenticated
def get(self, notebook_id):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
notebook_id = nbm.copy_notebook(notebook_id)
self.redirect('/'+urljoin(self.application.ipython_app.base_project_url, notebook_id))
notebook_id = self.notebook_manager.copy_notebook(notebook_id)
self.redirect('/'+urljoin(self.base_project_url, notebook_id))
#-----------------------------------------------------------------------------
@ -656,33 +709,31 @@ class NotebookCopyHandler(AuthenticatedHandler):
#-----------------------------------------------------------------------------
class MainClusterHandler(AuthenticatedHandler):
class MainClusterHandler(IPythonHandler):
@web.authenticated
def get(self):
cm = self.application.cluster_manager
self.finish(jsonapi.dumps(cm.list_profiles()))
self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
class ClusterProfileHandler(AuthenticatedHandler):
class ClusterProfileHandler(IPythonHandler):
@web.authenticated
def get(self, profile):
cm = self.application.cluster_manager
self.finish(jsonapi.dumps(cm.profile_info(profile)))
self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
class ClusterActionHandler(AuthenticatedHandler):
class ClusterActionHandler(IPythonHandler):
@web.authenticated
def post(self, profile, action):
cm = self.application.cluster_manager
cm = self.cluster_manager
if action == 'start':
n = self.get_argument('n',default=None)
if n is None:
data = cm.start_cluster(profile)
else:
data = cm.start_cluster(profile,int(n))
data = cm.start_cluster(profile, int(n))
if action == 'stop':
data = cm.stop_cluster(profile)
self.finish(jsonapi.dumps(data))
@ -693,7 +744,7 @@ class ClusterActionHandler(AuthenticatedHandler):
#-----------------------------------------------------------------------------
class RSTHandler(AuthenticatedHandler):
class RSTHandler(IPythonHandler):
@web.authenticated
def post(self):
@ -841,7 +892,7 @@ class FileFindHandler(web.StaticFileHandler):
try:
abs_path = filefind(path, roots)
except IOError:
logging.error("Could not find static file %r", path)
app_log.error("Could not find static file %r", path)
return None
# end subclass override
@ -854,7 +905,7 @@ class FileFindHandler(web.StaticFileHandler):
hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
f.close()
except Exception:
logging.error("Could not open static file %r", path)
app_log.error("Could not open static file %r", path)
hashes[abs_path] = None
hsh = hashes.get(abs_path)
if hsh:

@ -29,18 +29,13 @@ from IPython.utils.traitlets import (
class MappingKernelManager(MultiKernelManager):
"""A KernelManager that handles notebok mapping and HTTP error handling"""
"""A KernelManager that handles notebook mapping and HTTP error handling"""
def _kernel_manager_class_default(self):
return "IPython.kernel.ioloop.IOLoopKernelManager"
kernel_argv = List(Unicode)
max_msg_size = Integer(65536, config=True, help="""
The max raw message size accepted from the browser
over a WebSocket connection.
""")
_notebook_mapping = Dict()
#-------------------------------------------------------------------------

@ -178,16 +178,33 @@ class NotebookWebApplication(web.Application):
# Note that the URLs these patterns check against are escaped,
# and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
template_path = os.path.join(os.path.dirname(__file__), "templates")
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
# basics
base_project_url=base_project_url,
base_kernel_url=ipython_app.base_kernel_url,
template_path=template_path,
static_path=ipython_app.static_file_path,
static_handler_class = FileFindHandler,
static_url_prefix = url_path_join(base_project_url,'/static/'),
# authentication
cookie_secret=os.urandom(1024),
login_url=url_path_join(base_project_url,'/login'),
cookie_name='username-%s' % uuid.uuid4(),
base_project_url = base_project_url,
read_only=ipython_app.read_only,
password=ipython_app.password,
# managers
kernel_manager=kernel_manager,
notebook_manager=notebook_manager,
cluster_manager=cluster_manager,
# IPython stuff
max_msg_size=ipython_app.max_msg_size,
config=ipython_app.config,
use_less=ipython_app.use_less,
jinja2_env=Environment(loader=FileSystemLoader(template_path)),
)
# allow custom overrides for the tornado web app.
@ -197,21 +214,11 @@ class NotebookWebApplication(web.Application):
new_handlers = []
for handler in handlers:
pattern = url_path_join(base_project_url, handler[0])
new_handler = tuple([pattern]+list(handler[1:]))
new_handlers.append( new_handler )
new_handler = tuple([pattern] + list(handler[1:]))
new_handlers.append(new_handler)
super(NotebookWebApplication, self).__init__(new_handlers, **settings)
self.kernel_manager = kernel_manager
self.notebook_manager = notebook_manager
self.cluster_manager = cluster_manager
self.ipython_app = ipython_app
self.read_only = self.ipython_app.read_only
self.config = self.ipython_app.config
self.use_less = self.ipython_app.use_less
self.log = log
self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
#-----------------------------------------------------------------------------
@ -301,10 +308,17 @@ class NotebookApp(BaseIPythonApplication):
kernel_argv = List(Unicode)
log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
default_value=logging.INFO,
config=True,
help="Set the log level by value or name.")
max_msg_size = Integer(65536, config=True, help="""
The max raw message size accepted from the browser
over a WebSocket connection.
""")
def _log_level_default(self):
return logging.INFO
def _log_format_default(self):
"""override default log format to include time"""
return u"%(asctime)s.%(msecs).03d [%(name)s] %(message)s"
# create requested profiles by default, if they don't exist:
auto_create = Bool(True)
@ -517,6 +531,14 @@ class NotebookApp(BaseIPythonApplication):
# self.log is a child of. The logging module dipatches log messages to a log
# and all of its ancenstors until propagate is set to False.
self.log.propagate = False
# set the date format
formatter = logging.Formatter(self.log_format, datefmt="%Y-%m-%d %H:%M:%S")
self.log.handlers[0].setFormatter(formatter)
# hook up tornado 3's loggers to our app handlers
for name in ('access', 'application', 'general'):
logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
@ -669,7 +691,7 @@ class NotebookApp(BaseIPythonApplication):
return mgr_info +"The IPython Notebook is running at: %s" % self._url
def start(self):
""" Start the IPython Notebok server app, after initialization
""" 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."""

Loading…
Cancel
Save