Skip to content
This repository was archived by the owner on Jun 18, 2020. It is now read-only.

Commit 1174ddc

Browse files
author
Jason Grout
committed
Add server and profile options to the notebook() command
The server now takes either 'twistd' or 'flask' as options. The profile option saves profile data to a user-specified file.
1 parent b2b89d0 commit 1174ddc

File tree

2 files changed

+184
-52
lines changed

2 files changed

+184
-52
lines changed

sagenb/notebook/notebook_object.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ class NotebookObject:
164164
now, so if the machines are separate the server machine must
165165
NSF export ``/tmp``.
166166
167+
- ``server`` -- string ("twistd" (default) or "flask"). The server
168+
to use to server content.
169+
170+
- ``profile`` -- True, False, or file prefix (default: False - no profiling),
171+
If True, profiling is saved to a randomly-named file like `sagenb-*-profile*.stats`
172+
in the $DOT_SAGE directory. If a string, that string is used as a
173+
prefix for the pstats data file.
174+
167175
- ``ulimit`` -- string (initial default: None -- leave as is),
168176
if given and ``server_pool`` is also given, the worksheet
169177
processes are run with these constraints. See the ulimit

sagenb/notebook/run_notebook.py

Lines changed: 176 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,76 @@
3636
template_file = os.path.join(conf_path, 'cert.cfg')
3737

3838
FLASK_NOTEBOOK_CONFIG = """
39+
####################################################################
40+
# WARNING -- Do not edit this file! It is autogenerated each time
41+
# the notebook(...) command is executed.
42+
####################################################################
43+
44+
import sagenb.notebook.misc
45+
sagenb.notebook.misc.DIR = %(cwd)r #We should really get rid of this!
46+
47+
import signal, sys, random
48+
def save_notebook(notebook):
49+
print "Quitting all running worksheets..."
50+
notebook.quit()
51+
print "Saving notebook..."
52+
notebook.save()
53+
print "Notebook cleanly saved."
54+
55+
#########
56+
# Flask #
57+
#########
58+
import os, sys, random
59+
flask_dir = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sagenb', 'flask_version')
60+
sys.path.append(flask_dir)
61+
import base as flask_base
62+
opts={}
63+
startup_token = '{0:x}'.format(random.randint(0, 2**128))
64+
if %(login)s:
65+
opts['startup_token'] = startup_token
66+
flask_app = flask_base.create_app(%(notebook_opts)s, **opts)
67+
sys.path.remove(flask_dir)
68+
69+
if %(secure)s:
70+
from OpenSSL import SSL
71+
ssl_context = SSL.Context(SSL.SSLv23_METHOD)
72+
ssl_context.use_privatekey_file(%(pkey)r)
73+
ssl_context.use_certificate_file(%(cert)r)
74+
else:
75+
ssl_context = None
76+
77+
import logging
78+
logger=logging.getLogger('werkzeug')
79+
#logger.setLevel(logging.WARNING)
80+
#logger.setLevel(logging.INFO) # to see page requests
81+
logger.setLevel(logging.DEBUG)
82+
logger.addHandler(logging.StreamHandler())
83+
84+
if %(secure)s:
85+
# Monkey-patch werkzeug so that it works with pyOpenSSL and Python 2.7
86+
# otherwise, we constantly get TypeError: shutdown() takes exactly 0 arguments (1 given)
87+
88+
# Monkey patching idiom: http://mail.python.org/pipermail/python-dev/2008-January/076194.html
89+
def monkeypatch_method(cls):
90+
def decorator(func):
91+
setattr(cls, func.__name__, func)
92+
return func
93+
return decorator
94+
from werkzeug import serving
95+
96+
@monkeypatch_method(serving.BaseWSGIServer)
97+
def shutdown_request(self, request):
98+
request.shutdown()
99+
100+
%(open_page)s
101+
try:
102+
flask_app.run(host=%(host)r, port=%(port)s, threaded=True,
103+
ssl_context=ssl_context, debug=False)
104+
finally:
105+
save_notebook(flask_base.notebook)
106+
"""
107+
108+
TWISTD_NOTEBOOK_CONFIG = """
39109
####################################################################
40110
# WARNING -- Do not edit this file! It is autogenerated each time
41111
# the notebook(...) command is executed.
@@ -50,17 +120,10 @@
50120
51121
from twisted.internet import reactor
52122
53-
# Now set things up and start the notebook
54-
import sagenb.notebook.notebook
55-
sagenb.notebook.notebook.JSMATH=True
56-
import sagenb.notebook.notebook as notebook
57-
import sagenb.notebook.worksheet as worksheet
58-
59-
import sagenb.notebook.misc as misc
60-
61-
misc.DIR = %(cwd)r #We should really get rid of this!
123+
import sagenb.notebook.misc
124+
sagenb.notebook.misc.DIR = %(cwd)r #We should really get rid of this!
62125
63-
import signal, sys, random
126+
import signal
64127
def save_notebook(notebook):
65128
from twisted.internet.error import ReactorNotRunning
66129
print "Quitting all running worksheets..."
@@ -84,7 +147,7 @@ def my_sigint(x, n):
84147
#########
85148
# Flask #
86149
#########
87-
import os
150+
import os, sys, random
88151
flask_dir = os.path.join(os.environ['SAGE_ROOT'], 'devel', 'sagenb', 'flask_version')
89152
sys.path.append(flask_dir)
90153
import base as flask_base
@@ -215,16 +278,16 @@ def notebook_setup(self=None):
215278
'--outfile %s' % (template_file, private_pem, public_pem)]
216279
print cmd[0]
217280
subprocess.call(cmd, shell=True)
218-
281+
219282
# Set permissions on private cert
220283
os.chmod(private_pem, 0600)
221284

222285
print "Successfully configured notebook."
223286

224-
def notebook_twisted(self,
287+
def notebook_run(self,
225288
directory = None,
226289
port = 8080,
227-
interface = 'localhost',
290+
interface = 'localhost',
228291
port_tries = 50,
229292
secure = False,
230293
reset = False,
@@ -241,7 +304,10 @@ def notebook_twisted(self,
241304
start_path = "",
242305
fork = False,
243306
quiet = False,
244-
307+
308+
server = "twistd",
309+
profile = False,
310+
245311
subnets = None,
246312
require_login = None,
247313
open_viewer = None,
@@ -285,10 +351,9 @@ def notebook_twisted(self,
285351
# if none use defaults
286352

287353
nb = notebook.load_notebook(directory)
288-
354+
289355
directory = nb._dir
290-
conf = os.path.join(directory, 'twistedconf.tac')
291-
356+
292357
if not quiet:
293358
print "The notebook files are stored in:", nb._dir
294359

@@ -314,7 +379,7 @@ def notebook_twisted(self,
314379

315380
if not nb.user_manager().user_exists('admin'):
316381
reset = True
317-
382+
318383
if reset:
319384
passwd = get_admin_passwd()
320385
if reset:
@@ -345,9 +410,52 @@ def notebook_twisted(self,
345410
nb.save()
346411
del nb
347412

348-
def run(port):
413+
def run_flask():
414+
"""Run a flask (werkzeug) webserver."""
415+
# TODO: Check to see if server is running already (PID file?)
416+
conf = os.path.join(directory, 'run_flask')
417+
418+
notebook_opts = '"%s",interface="%s",port=%s,secure=%s' % (
419+
os.path.abspath(directory), interface, port, secure)
420+
421+
if automatic_login:
422+
start_path = "'/?startup_token=%s' % startup_token"
423+
if interface:
424+
hostname = interface
425+
else:
426+
hostname = 'localhost'
427+
open_page = "from sagenb.misc.misc import open_page; open_page('%s', %s, %s, %s)" % (hostname, port, secure, start_path)
428+
else:
429+
open_page = ''
430+
431+
config = open(conf, 'w')
432+
433+
config.write(FLASK_NOTEBOOK_CONFIG%{'notebook_opts': notebook_opts,
434+
'cwd':cwd,
435+
'open_page': open_page, 'login': automatic_login,
436+
'secure': secure, 'pkey': private_pem, 'cert': public_pem,
437+
'host': interface, 'port': port})
438+
439+
config.close()
440+
441+
if profile:
442+
import random
443+
if isinstance(profile, basestring):
444+
profilefile = profile+'%s.stats'%random.random()
445+
else:
446+
profilefile = 'sagenb-flask-profile-%s.stats'%random.random()
447+
profilecmd = '-m cProfile -o %s'%profilefile
448+
else:
449+
profilecmd=''
450+
cmd = 'python %s %s' % (profilecmd, conf)
451+
return cmd
452+
# end of inner function run_flask
453+
454+
def run_twistd():
455+
"""Run a twistd webserver."""
349456
# Is a server already running? Check if a Twistd PID exists in
350457
# the given directory.
458+
conf = os.path.join(directory, 'twistedconf.tac')
351459
pidfile = os.path.join(directory, 'twistd.pid')
352460
if platformType != 'win32':
353461
from twisted.scripts._twistd_unix import checkPID
@@ -358,6 +466,7 @@ def run(port):
358466

359467
if str(e).startswith('Another twistd server is running,'):
360468
print 'Another Sage Notebook server is running, PID %d.' % pid
469+
361470
old_interface, old_port, old_secure = get_old_settings(conf)
362471
if automatic_login and old_port:
363472
old_interface = old_interface or 'localhost'
@@ -367,20 +476,12 @@ def run(port):
367476

368477
from sagenb.misc.misc import open_page as browse_to
369478
browse_to(old_interface, old_port, old_secure, '/')
370-
return
479+
return None
371480
print '\nPlease either stop the old server or run the new server in a different directory.'
372-
return
481+
return None
373482

374483
## Create the config file
375484
if secure:
376-
if (not os.path.exists(private_pem) or
377-
not os.path.exists(public_pem)):
378-
print "In order to use an SECURE encrypted notebook, you must first run notebook.setup()."
379-
print "Now running notebook.setup()"
380-
notebook_setup()
381-
if (not os.path.exists(private_pem) or
382-
not os.path.exists(public_pem)):
383-
print "Failed to setup notebook. Please try notebook.setup() again manually."
384485
strport = '%s:%s:interface=%s:privateKey=%s:certKey=%s'%(
385486
protocol, port, interface, private_pem, public_pem)
386487
else:
@@ -401,35 +502,27 @@ def run(port):
401502

402503
config = open(conf, 'w')
403504

404-
config.write(FLASK_NOTEBOOK_CONFIG%{'notebook_opts': notebook_opts,
505+
config.write(TWISTD_NOTEBOOK_CONFIG%{'notebook_opts': notebook_opts,
405506
'cwd':cwd, 'strport': strport,
406507
'open_page': open_page, 'login': automatic_login})
407508

408509

409-
config.close()
510+
config.close()
410511

411512
## Start up twisted
412-
cmd = 'twistd --pidfile="%s" -ny "%s"' % (pidfile, conf)
413-
if not quiet:
414-
print_open_msg('localhost' if not interface else interface,
415-
port, secure=secure)
416-
if secure and not quiet:
417-
print "There is an admin account. If you do not remember the password,"
418-
print "quit the notebook and type notebook(reset=True)."
419-
420-
if fork:
421-
import pexpect
422-
return pexpect.spawn(cmd)
513+
if profile:
514+
import random
515+
if isinstance(profile, basestring):
516+
profilefile = profile+'%s.stats'%random.random()
517+
else:
518+
profilefile = 'sagenb-twistd-profile-%s.stats'%random.random()
519+
profilecmd = '--profile=%s --profiler=cprofile --savestats'%profilefile
423520
else:
424-
e = os.system(cmd)
425-
426-
os.chdir(cwd)
427-
if e == 256:
428-
raise socket.error
521+
profilecmd=''
522+
cmd = 'twistd %s --pidfile="%s" -ny "%s"' % (profilecmd, pidfile, conf)
523+
return cmd
524+
# end of inner function run_twistd
429525

430-
return True
431-
# end of inner function run
432-
433526
if interface != 'localhost' and not secure:
434527
print "*" * 70
435528
print "WARNING: Insecure notebook server listening on external interface."
@@ -440,7 +533,38 @@ def run(port):
440533
port = find_next_available_port(interface, port, port_tries)
441534
if automatic_login:
442535
"Automatic login isn't fully implemented. You have to manually open your web browser to the above URL."
443-
return run(port)
536+
if secure:
537+
if (not os.path.exists(private_pem) or
538+
not os.path.exists(public_pem)):
539+
print "In order to use an SECURE encrypted notebook, you must first run notebook.setup()."
540+
print "Now running notebook.setup()"
541+
notebook_setup()
542+
if (not os.path.exists(private_pem) or
543+
not os.path.exists(public_pem)):
544+
print "Failed to setup notebook. Please try notebook.setup() again manually."
545+
if server=="flask":
546+
cmd = run_flask()
547+
elif server=="twistd":
548+
cmd = run_twistd()
549+
if cmd is None:
550+
return
551+
552+
if not quiet:
553+
print_open_msg('localhost' if not interface else interface,
554+
port, secure=secure)
555+
if secure and not quiet:
556+
print "There is an admin account. If you do not remember the password,"
557+
print "quit the notebook and type notebook(reset=True)."
558+
print "Executing", cmd
559+
if fork:
560+
import pexpect
561+
return pexpect.spawn(cmd)
562+
else:
563+
e = os.system(cmd)
564+
565+
os.chdir(cwd)
566+
if e == 256:
567+
raise socket.error
444568

445569
def get_admin_passwd():
446570
print "\n" * 2

0 commit comments

Comments
 (0)