Skip to content

Commit 040ce61

Browse files
committed
Made choice of (provider) handlers customizable via 'nbviewer_config.py'.
1 parent 7cf4b33 commit 040ce61

File tree

8 files changed

+149
-45
lines changed

8 files changed

+149
-45
lines changed

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: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,31 +75,49 @@ 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+
urlspecs = [
8080
(prefix + url, handler, {
8181
"format": format,
8282
"format_prefix": prefix
8383
})
8484
for format in formats
85-
for url, handler in handlers
85+
# Tornado handler URLSpec of form (route, handler_class, initalize_kwargs)
86+
# https://www.tornadoweb.org/en/stable/web.html#tornado.web.URLSpec
87+
# kwargs passed to initialize are None by default but can be added
88+
# https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.initialize
89+
for url, handler, initialize_kwargs in urlspecs
8690
for prefix in [format_prefix + format]
8791
]
92+
for handler_setting in handler_settings:
93+
if handler_settings[handler_setting]:
94+
# here we modify the URLSpec dict to have the key-value pairs from
95+
# handler_settings in NBViewer.init_tornado_application
96+
for urlspec in urlspecs:
97+
urlspec[2][handler_setting] = handler_settings[handler_setting]
98+
return urlspecs
99+
100+
def init_handlers(formats, providers, base_url, localfiles, **handler_kwargs):
101+
# `handler_kwargs` is a dict of dicts: first dict is `handler_names`, which
102+
# specifies the handler_classes to load for the providers, the second
103+
# is `handler_settings` (see comments in format_handlers)
104+
# Only `handler_settings` should get added to the initialize_kwargs in the
105+
# handler URLSpecs, which is why we pass only it to `format_handlers`
106+
# but both it and `handler_names` to `provider_handlers`
107+
handler_settings = handler_kwargs['handler_settings']
88108

89-
90-
def init_handlers(formats, providers, base_url, localfiles):
91109
pre_providers = [
92-
('/?', IndexHandler),
93-
('/index.html', IndexHandler),
94-
(r'/faq/?', FAQHandler),
95-
(r'/create/?', CreateHandler),
110+
('/?', IndexHandler, {}),
111+
('/index.html', IndexHandler, {}),
112+
(r'/faq/?', FAQHandler, {}),
113+
(r'/create/?', CreateHandler, {}),
96114

97115
# don't let super old browsers request data-uris
98-
(r'.*/data:.*;base64,.*', Custom404),
116+
(r'.*/data:.*;base64,.*', Custom404, {}),
99117
]
100118

101119
post_providers = [
102-
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler)
120+
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler, {})
103121
]
104122

105123
# Add localfile handlers if the option is set
@@ -108,12 +126,12 @@ def init_handlers(formats, providers, base_url, localfiles):
108126
# https://github.com/jupyter/nbviewer/pull/727#discussion_r144448440.
109127
providers.insert(0, 'nbviewer.providers.local')
110128

111-
handlers = provider_handlers(providers)
129+
handlers = provider_handlers(providers, **handler_kwargs)
112130

113131
raw_handlers = (
114132
pre_providers +
115133
handlers +
116-
format_handlers(formats, handlers) +
134+
format_handlers(formats, handlers, **handler_settings) +
117135
post_providers
118136
)
119137

@@ -122,6 +140,6 @@ def init_handlers(formats, providers, base_url, localfiles):
122140
pattern = url_path_join(base_url, handler[0])
123141
new_handler = tuple([pattern] + list(handler[1:]))
124142
new_handlers.append(new_handler)
125-
new_handlers.append((r'.*', Custom404))
143+
new_handlers.append((r'.*', Custom404, {}))
126144

127145
return new_handlers

nbviewer/providers/__init__.py

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
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
@@ -21,7 +21,22 @@ def provider_handlers(providers):
2121
example, custom URLs which should be intercepted before being
2222
handed to the basic `url` handler
2323
"""
24-
return _load_provider_feature('default_handlers', providers)
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)
27+
handler_names = handler_kwargs['handler_names']
28+
handler_settings = handler_kwargs['handler_settings']
29+
30+
urlspecs = _load_provider_feature('default_handlers', providers, **handler_names)
31+
for handler_setting in handler_settings:
32+
if handler_settings[handler_setting]:
33+
# here we modify the URLSpec dict to have the key-value pairs from
34+
# handler_settings in NBViewer.init_tornado_application
35+
# kwargs passed to initialize are None by default but can be added
36+
# https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.initialize
37+
for urlspec in urlspecs:
38+
urlspec[2][handler_setting] = handler_settings[handler_setting]
39+
return urlspecs
2540

2641

2742
def provider_uri_rewrites(providers):
@@ -35,17 +50,58 @@ def provider_uri_rewrites(providers):
3550
return _load_provider_feature('uri_rewrites', providers)
3651

3752

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

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

51-
return features
105+
module = __import__(module_name, fromlist=[handler_name])
106+
handler = getattr(module, handler_name)
107+
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)