Skip to content

Commit 8906a57

Browse files
authored
Merge pull request #843 from krinsman/step3
Step 3
2 parents 7cf4b33 + 73e3b1a commit 8906a57

File tree

9 files changed

+156
-46
lines changed

9 files changed

+156
-46
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ $ docker-compose up
8484

8585
### Local Installation
8686

87-
The Notebook Viewer requires several binary packages to be installed on your system. The primary ones are `libmemcached-dev libcurl4-openssl-dev pandoc libevent-dev`. Package names may differ on your system, see [salt-states](https://github.com/rgbkrk/salt-states-nbviewer/blob/master/nbviewer/init.sls) for more details.
87+
The Notebook Viewer requires several binary packages to be installed on your system. The primary ones are `libmemcached-dev libcurl4-openssl-dev pandoc libevent-dev libgnutls28-dev`. Package names may differ on your system, see [salt-states](https://github.com/rgbkrk/salt-states-nbviewer/blob/master/nbviewer/init.sls) for more details.
8888

8989
If they are installed, you can install the required Python packages via pip.
9090

nbviewer/app.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from jinja2 import Environment, FileSystemLoader
2727

28-
from traitlets import Unicode, Any, Set, default
28+
from traitlets import Any, Dict, Set, Unicode, default
2929
from traitlets.config import Application
3030

3131
from .handlers import init_handlers
@@ -83,6 +83,9 @@ class NBViewer(Application):
8383

8484
config_file = Unicode('nbviewer_config.py', help="The config file to load").tag(config=True)
8585

86+
# Use this to insert custom configuration of handlers for NBViewer extensions
87+
handler_settings = Dict().tag(config=True)
88+
8689
url_handler = Unicode(default_value="nbviewer.providers.url.handlers.URLHandler", help="The Tornado handler to use for viewing notebooks accessed via URL").tag(config=True)
8790
local_handler = Unicode(default_value="nbviewer.providers.local.handlers.LocalFileHandler", help="The Tornado handler to use for viewing notebooks found on a local filesystem").tag(config=True)
8891
github_blob_handler = Unicode(default_value="nbviewer.providers.github.handlers.GitHubBlobHandler", help="The Tornado handler to use for viewing notebooks stored as blobs on GitHub").tag(config=True)
@@ -243,7 +246,16 @@ def template_paths(self):
243246

244247
def init_tornado_application(self):
245248
# handle handlers
246-
handlers = init_handlers(self.formats, options.providers, self.base_url, options.localfiles)
249+
handler_names = dict(
250+
url_handler=self.url_handler,
251+
github_blob_handler=self.github_blob_handler,
252+
github_tree_handler=self.github_tree_handler,
253+
local_handler=self.local_handler,
254+
gist_handler=self.gist_handler,
255+
user_gists_handler=self.user_gists_handler,
256+
)
257+
handler_kwargs = {'handler_names' : handler_names, 'handler_settings' : self.handler_settings}
258+
handlers = init_handlers(self.formats, options.providers, self.base_url, options.localfiles, **handler_kwargs)
247259

248260
# NBConvert config
249261
self.config.NbconvertApp.fileext = 'html'

nbviewer/handlers.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,31 +75,53 @@ def get_provider_rewrites(self):
7575
# Default handler URL mapping
7676
#-----------------------------------------------------------------------------
7777

78-
def format_handlers(formats, handlers):
79-
return [
78+
def format_handlers(formats, urlspecs, **handler_settings):
79+
"""
80+
Tornado handler URLSpec of form (route, handler_class, initalize_kwargs)
81+
https://www.tornadoweb.org/en/stable/web.html#tornado.web.URLSpec
82+
kwargs passed to initialize are None by default but can be added
83+
https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.initialize
84+
"""
85+
urlspecs = [
8086
(prefix + url, handler, {
8187
"format": format,
8288
"format_prefix": prefix
8389
})
8490
for format in formats
85-
for url, handler in handlers
91+
for url, handler, initialize_kwargs in urlspecs
8692
for prefix in [format_prefix + format]
8793
]
94+
for handler_setting in handler_settings:
95+
if handler_settings[handler_setting]:
96+
# here we modify the URLSpec dict to have the key-value pairs from
97+
# handler_settings in NBViewer.init_tornado_application
98+
for urlspec in urlspecs:
99+
urlspec[2][handler_setting] = handler_settings[handler_setting]
100+
return urlspecs
101+
102+
def init_handlers(formats, providers, base_url, localfiles, **handler_kwargs):
103+
"""
104+
`handler_kwargs` is a dict of dicts: first dict is `handler_names`, which
105+
specifies the handler_classes to load for the providers, the second
106+
is `handler_settings` (see comments in format_handlers)
107+
Only `handler_settings` should get added to the initialize_kwargs in the
108+
handler URLSpecs, which is why we pass only it to `format_handlers`
109+
but both it and `handler_names` to `provider_handlers`
110+
"""
111+
handler_settings = handler_kwargs['handler_settings']
88112

89-
90-
def init_handlers(formats, providers, base_url, localfiles):
91113
pre_providers = [
92-
('/?', IndexHandler),
93-
('/index.html', IndexHandler),
94-
(r'/faq/?', FAQHandler),
95-
(r'/create/?', CreateHandler),
114+
('/?', IndexHandler, {}),
115+
('/index.html', IndexHandler, {}),
116+
(r'/faq/?', FAQHandler, {}),
117+
(r'/create/?', CreateHandler, {}),
96118

97119
# don't let super old browsers request data-uris
98-
(r'.*/data:.*;base64,.*', Custom404),
120+
(r'.*/data:.*;base64,.*', Custom404, {}),
99121
]
100122

101123
post_providers = [
102-
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler)
124+
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler, {})
103125
]
104126

105127
# Add localfile handlers if the option is set
@@ -108,12 +130,12 @@ def init_handlers(formats, providers, base_url, localfiles):
108130
# https://github.com/jupyter/nbviewer/pull/727#discussion_r144448440.
109131
providers.insert(0, 'nbviewer.providers.local')
110132

111-
handlers = provider_handlers(providers)
133+
handlers = provider_handlers(providers, **handler_kwargs)
112134

113135
raw_handlers = (
114136
pre_providers +
115137
handlers +
116-
format_handlers(formats, handlers) +
138+
format_handlers(formats, handlers, **handler_settings) +
117139
post_providers
118140
)
119141

@@ -122,6 +144,6 @@ def init_handlers(formats, providers, base_url, localfiles):
122144
pattern = url_path_join(base_url, handler[0])
123145
new_handler = tuple([pattern] + list(handler[1:]))
124146
new_handlers.append(new_handler)
125-
new_handlers.append((r'.*', Custom404))
147+
new_handlers.append((r'.*', Custom404, {}))
126148

127149
return new_handlers

nbviewer/providers/__init__.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,32 @@
1212
for prov in ['gist', 'github', 'dropbox', 'url']]
1313

1414

15-
def provider_handlers(providers):
15+
def provider_handlers(providers, **handler_kwargs):
1616
"""Load tornado URL handlers from an ordered list of dotted-notation modules
1717
which contain a `default_handlers` function
1818
1919
`default_handlers` should accept a list of handlers and returns an
2020
augmented list of handlers: this allows the addition of, for
2121
example, custom URLs which should be intercepted before being
2222
handed to the basic `url` handler
23+
24+
`handler_kwargs` is a dict of dicts: first dict is `handler_names`, which
25+
specifies the handler_classes to load for the providers, the second
26+
is `handler_settings` (see comments in `format_handlers` in nbviewer/handlers.py)
2327
"""
24-
return _load_provider_feature('default_handlers', providers)
28+
handler_names = handler_kwargs['handler_names']
29+
handler_settings = handler_kwargs['handler_settings']
30+
31+
urlspecs = _load_provider_feature('default_handlers', providers, **handler_names)
32+
for handler_setting in handler_settings:
33+
if handler_settings[handler_setting]:
34+
# here we modify the URLSpec dict to have the key-value pairs from
35+
# handler_settings in NBViewer.init_tornado_application
36+
# kwargs passed to initialize are None by default but can be added
37+
# https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.initialize
38+
for urlspec in urlspecs:
39+
urlspec[2][handler_setting] = handler_settings[handler_setting]
40+
return urlspecs
2541

2642

2743
def provider_uri_rewrites(providers):
@@ -35,17 +51,59 @@ def provider_uri_rewrites(providers):
3551
return _load_provider_feature('uri_rewrites', providers)
3652

3753

38-
def _load_provider_feature(feature, providers):
54+
def _load_provider_feature(feature, providers, **handler_names):
3955
"""Load the named feature from an ordered list of dotted-notation modules
4056
which each implements the feature.
4157
4258
The feature will be passed a list of feature implementations and must
4359
return that list, suitably modified.
60+
61+
`handler_names` is the same as the `handler_names` attribute of the NBViewer class
4462
"""
63+
64+
# Ex: provider = 'nbviewer.providers.url'
65+
# provider.rsplit(',', 1) = ['nbviewer.providers', 'url']
66+
# provider_type = 'url'
67+
provider_types = [provider.rsplit('.', 1)[-1] for provider in providers]
68+
69+
if 'github' in provider_types:
70+
provider_types.append('github_blob')
71+
provider_types.append('github_tree')
72+
provider_types.remove('github')
73+
74+
provider_handlers = {}
75+
76+
# Ex: provider_type = 'url'
77+
for provider_type in provider_types:
78+
# Ex: provider_handler_key = 'url_handler'
79+
provider_handler_key = provider_type + '_handler'
80+
try:
81+
# Ex: handler_names['url_handler']
82+
handler_names[provider_handler_key]
83+
except KeyError as e:
84+
continue
85+
else:
86+
# Ex: provider_handlers['url_handler'] = handler_names['url_handler']
87+
provider_handlers[provider_handler_key] = handler_names[provider_handler_key]
88+
4589
features = []
4690

91+
# Ex: provider = 'nbviewer.providers.url'
4792
for provider in providers:
48-
mod = __import__(provider, fromlist=[feature])
49-
features = getattr(mod, feature)(features)
93+
# Ex: module = __import__('nbviewer.providers.url', fromlist=['default_handlers'])
94+
module = __import__(provider, fromlist=[feature])
95+
# Ex: getattr(module, 'default_handlers') = the `default_handlers` function from
96+
# nbviewer.providers.url (in handlers.py of nbviewer/providers/url)
97+
# so in example, features = nbviewer.providers.url.default_handlers(list_of_already_loaded_handlers, **handler_names)
98+
# => features = list_of_already_loaded_handlers + [URLSpec of chosen URL handler]
99+
features = getattr(module, feature)(features, **handler_names)
100+
return features
101+
102+
def _load_handler_from_location(handler_location):
103+
# Ex: handler_location = 'nbviewer.providers.url.URLHandler'
104+
# module_name = 'nbviewer.providers.url', handler_name = 'URLHandler'
105+
module_name, handler_name = tuple(handler_location.rsplit('.', 1))
50106

51-
return features
107+
module = __import__(module_name, fromlist=[handler_name])
108+
handler = getattr(module, handler_name)
109+
return handler

nbviewer/providers/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ class CurlError(Exception): pass
5757
class BaseHandler(web.RequestHandler):
5858
"""Base Handler class with common utilities"""
5959

60-
def initialize(self, format=None, format_prefix=""):
60+
def initialize(self, format=None, format_prefix="", **handler_settings):
6161
self.format = format or self.default_format
6262
self.format_prefix = format_prefix
6363
self.http_client = httpclient.AsyncHTTPClient()
6464

65+
for handler_setting in handler_settings:
66+
setattr(self, handler_setting, handler_settings[handler_setting])
67+
6568
# Overloaded methods
6669
def redirect(self, url, *args, **kwargs):
6770
purl = urlparse(url)

nbviewer/providers/gist/handlers.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from ..github.handlers import GithubClientMixin
2323

24+
from .. import _load_handler_from_location
2425

2526
PROVIDER_CTX = {
2627
'provider_label': 'Gist',
@@ -207,15 +208,18 @@ def get(self, gist_id, file=''):
207208
self.redirect(self.from_base(new_url))
208209

209210

210-
def default_handlers(handlers=[]):
211+
def default_handlers(handlers=[], **handler_names):
211212
"""Tornado handlers"""
212213

214+
gist_handler = _load_handler_from_location(handler_names['gist_handler'])
215+
user_gists_handler = _load_handler_from_location(handler_names['user_gists_handler'])
216+
213217
return handlers + [
214-
(r'/gist/([^\/]+/)?([0-9]+|[0-9a-f]{20,})', GistHandler),
215-
(r'/gist/([^\/]+/)?([0-9]+|[0-9a-f]{20,})/(?:files/)?(.*)', GistHandler),
216-
(r'/([0-9]+|[0-9a-f]{20,})', GistRedirectHandler),
217-
(r'/([0-9]+|[0-9a-f]{20,})/(.*)', GistRedirectHandler),
218-
(r'/gist/([^\/]+)/?', UserGistsHandler),
218+
(r'/gist/([^\/]+/)?([0-9]+|[0-9a-f]{20,})', gist_handler, {}),
219+
(r'/gist/([^\/]+/)?([0-9]+|[0-9a-f]{20,})/(?:files/)?(.*)', gist_handler, {}),
220+
(r'/([0-9]+|[0-9a-f]{20,})', GistRedirectHandler, {}),
221+
(r'/([0-9]+|[0-9a-f]{20,})/(.*)', GistRedirectHandler, {}),
222+
(r'/gist/([^\/]+)/?', user_gists_handler, {}),
219223
]
220224

221225

nbviewer/providers/github/handlers.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
from .client import AsyncGitHubClient
3535

36+
from .. import _load_handler_from_location
3637

3738
PROVIDER_CTX = {
3839
'provider_label': 'GitHub',
@@ -334,27 +335,31 @@ def get(self, user, repo, ref, path):
334335
self.cache_and_finish(filedata)
335336

336337

337-
def default_handlers(handlers=[]):
338+
def default_handlers(handlers=[], **handler_names):
338339
"""Tornado handlers"""
339340

341+
blob_handler = _load_handler_from_location(handler_names['github_blob_handler'])
342+
tree_handler = _load_handler_from_location(handler_names['github_tree_handler'])
343+
340344
return [
341345
# ideally these URIs should have been caught by an appropriate
342346
# uri_rewrite rather than letting the url provider catch them and then
343347
# fixing it here.
344348
# There are probably links in the wild that depend on these, so keep
345349
# these handlers for backwards compatibility.
346-
(r'/url[s]?/github\.com/(?P<url>.*)', GitHubRedirectHandler),
347-
(r'/url[s]?/raw\.?github\.com/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?P<path>.*)', RawGitHubURLHandler),
348-
(r'/url[s]?/raw\.?githubusercontent\.com/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?P<path>.*)', RawGitHubURLHandler),
350+
(r'/url[s]?/github\.com/(?P<url>.*)', GitHubRedirectHandler, {}),
351+
(r'/url[s]?/raw\.?github\.com/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?P<path>.*)', RawGitHubURLHandler, {}),
352+
(r'/url[s]?/raw\.?githubusercontent\.com/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?P<path>.*)', RawGitHubURLHandler, {}),
349353
] + handlers + [
350-
(r'/github/([^\/]+)', AddSlashHandler),
351-
(r'/github/(?P<user>[^\/]+)/', GitHubUserHandler),
352-
(r'/github/([^\/]+)/([^\/]+)', AddSlashHandler),
353-
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/', GitHubRepoHandler),
354-
(r'/github/([^\/]+)/([^\/]+)/(?:blob|raw)/([^\/]+)/(.*)/', RemoveSlashHandler),
355-
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?:blob|raw)/(?P<ref>[^\/]+)/(?P<path>.*)', GitHubBlobHandler),
356-
(r'/github/([^\/]+)/([^\/]+)/tree/([^\/]+)', AddSlashHandler),
357-
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/tree/(?P<ref>[^\/]+)/(?P<path>.*)', GitHubTreeHandler),
354+
(r'/github/([^\/]+)', AddSlashHandler, {}),
355+
(r'/github/(?P<user>[^\/]+)/', GitHubUserHandler, {}),
356+
(r'/github/([^\/]+)/([^\/]+)', AddSlashHandler, {}),
357+
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/', GitHubRepoHandler, {}),
358+
(r'/github/([^\/]+)/([^\/]+)/(?:blob|raw)/([^\/]+)/(.*)/', RemoveSlashHandler, {}),
359+
(r'/github/([^\/]+)/([^\/]+)/tree/([^\/]+)', AddSlashHandler, {}),
360+
] + [
361+
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/tree/(?P<ref>[^\/]+)/(?P<path>.*)', tree_handler, {}),
362+
(r'/github/(?P<user>[^\/]+)/(?P<repo>[^\/]+)/(?:blob|raw)/(?P<ref>[^\/]+)/(?P<path>.*)', blob_handler, {}),
358363
]
359364

360365

nbviewer/providers/local/handlers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
RenderingHandler,
2525
)
2626

27+
from .. import _load_handler_from_location
2728

2829
class LocalFileHandler(RenderingHandler):
2930
"""Renderer for /localfile
@@ -257,10 +258,12 @@ def show_dir(self, fullpath, path):
257258
return html
258259

259260

260-
def default_handlers(handlers=[]):
261+
def default_handlers(handlers=[], **handler_names):
261262
"""Tornado handlers"""
262263

264+
local_handler = _load_handler_from_location(handler_names['local_handler'])
265+
263266
return handlers + [
264-
(r'/localfile/?(.*)', LocalFileHandler),
267+
(r'/localfile/?(.*)', local_handler, {}),
265268
]
266269

nbviewer/providers/url/handlers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
RenderingHandler,
2727
)
2828

29+
from .. import _load_handler_from_location
2930

3031
class URLHandler(RenderingHandler):
3132
"""Renderer for /url or /urls"""
@@ -89,11 +90,13 @@ def get(self, secure, netloc, url):
8990
format=self.format)
9091

9192

92-
def default_handlers(handlers=[]):
93+
def default_handlers(handlers=[], **handler_names):
9394
"""Tornado handlers"""
9495

96+
url_handler = _load_handler_from_location(handler_names['url_handler'])
97+
9598
return handlers + [
96-
(r'/url(?P<secure>[s]?)/(?P<netloc>[^/]+)/(?P<url>.*)', URLHandler),
99+
(r'/url(?P<secure>[s]?)/(?P<netloc>[^/]+)/(?P<url>.*)', url_handler, {}),
97100
]
98101

99102

0 commit comments

Comments
 (0)