Skip to content

Commit 51317e6

Browse files
authored
Merge pull request #844 from krinsman/step4
Step 4
2 parents 07b0e30 + 37a2198 commit 51317e6

File tree

5 files changed

+107
-85
lines changed

5 files changed

+107
-85
lines changed

nbviewer/providers/base.py

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
pycurl = None
5151
class CurlError(Exception): pass
5252

53-
date_fmt = "%a, %d %b %Y %H:%M:%S UTC"
5453
format_prefix = "/format/"
5554

5655

@@ -61,6 +60,7 @@ def initialize(self, format=None, format_prefix="", **handler_settings):
6160
self.format = format or self.default_format
6261
self.format_prefix = format_prefix
6362
self.http_client = httpclient.AsyncHTTPClient()
63+
self.date_fmt = "%a, %d %b %Y %H:%M:%S UTC"
6464

6565
for handler_setting in handler_settings:
6666
setattr(self, handler_setting, handler_settings[handler_setting])
@@ -259,10 +259,19 @@ def get_template(self, name):
259259
"""Return the jinja template object for a given name"""
260260
return self.settings['jinja2_env'].get_template(name)
261261

262-
def render_template(self, name, **ns):
263-
ns.update(self.template_namespace)
262+
def render_template(self, name, **namespace):
263+
namespace.update(self.template_namespace)
264264
template = self.get_template(name)
265-
return template.render(**ns)
265+
return template.render(**namespace)
266+
267+
# Wrappers to facilitate custom rendering in subclasses without having to rewrite entire GET methods
268+
# This would seem to mostly involve creating different template namespaces to enable custom logic in
269+
# extended templates, but there might be other possibilities
270+
def render_status_code_template(self, status_code, **namespace):
271+
return self.render_template('%d.html' % status_code, **namespace)
272+
273+
def render_error_template(self, **namespace):
274+
return self.render_template('error.html', **namespace)
266275

267276
@property
268277
def template_namespace(self):
@@ -415,7 +424,7 @@ def write_error(self, status_code, **kwargs):
415424
status_message = reason
416425

417426
# build template namespace
418-
ns = dict(
427+
namespace = dict(
419428
status_code=status_code,
420429
status_message=status_message,
421430
message=message,
@@ -424,9 +433,9 @@ def write_error(self, status_code, **kwargs):
424433

425434
# render the template
426435
try:
427-
html = self.render_template('%d.html' % status_code, **ns)
436+
html = self.render_status_code_template(status_code, **namespace)
428437
except Exception as e:
429-
html = self.render_template('error.html', **ns)
438+
html = self.render_error_template(**namespace)
430439
self.set_header('Content-Type', 'text/html')
431440
self.write(html)
432441

@@ -621,12 +630,26 @@ def filter_formats(self, nb, raw):
621630
except Exception as err:
622631
app_log.info("failed to test %s: %s", self.request.uri, name)
623632

633+
# Wrappers to facilitate custom rendering in subclasses without having to rewrite entire GET methods
634+
# This would seem to mostly involve creating different template namespaces to enable custom logic in
635+
# extended templates, but there might be other possibilities
636+
def render_notebook_template(self, body, nb, download_url, json_notebook, **namespace):
637+
return self.render_template(
638+
"formats/%s.html" % self.format,
639+
body=body,
640+
nb=nb,
641+
download_url=download_url,
642+
format=self.format,
643+
default_format=self.default_format,
644+
format_prefix=self.format_prefix,
645+
formats=dict(self.filter_formats(nb, json_notebook)),
646+
format_base=self.request.uri.replace(self.format_prefix, "").replace(self.base_url, '/'),
647+
date=datetime.utcnow().strftime(self.date_fmt),
648+
**namespace)
649+
624650
@gen.coroutine
625-
def finish_notebook(self, json_notebook, download_url, provider_url=None,
626-
provider_icon=None, provider_label=None, msg=None,
627-
breadcrumbs=None, public=False, format=None, request=None,
628-
title=None, executor_url=None, executor_label=None,
629-
executor_icon=None):
651+
def finish_notebook(self, json_notebook, download_url, msg=None,
652+
public=False, **namespace):
630653
"""Renders a notebook from its JSON body.
631654
632655
Parameters
@@ -679,7 +702,7 @@ def finish_notebook(self, json_notebook, download_url, provider_url=None,
679702
app_log.info("Rendering %d B notebook from %s", len(json_notebook), download_url)
680703
render_time = self.statsd.timer('rendering.nbrender.time').start()
681704
nbhtml, config = yield self.pool.submit(render_notebook,
682-
self.formats[format], nb, download_url,
705+
self.formats[self.format], nb, download_url,
683706
config=self.config,
684707
)
685708
render_time.stop()
@@ -696,30 +719,16 @@ def finish_notebook(self, json_notebook, download_url, provider_url=None,
696719
app_log.debug("Finished render of %s", download_url)
697720

698721
html_time = self.statsd.timer('rendering.html.time').start()
699-
html = self.render_template(
700-
"formats/%s.html" % format,
722+
html = self.render_notebook_template(
701723
body=nbhtml,
702724
nb=nb,
703725
download_url=download_url,
704-
provider_url=provider_url,
705-
provider_label=provider_label,
706-
provider_icon=provider_icon,
707-
executor_url=executor_url,
708-
executor_label=executor_label,
709-
executor_icon=executor_icon,
710-
format=self.format,
711-
default_format=self.default_format,
712-
format_prefix=format_prefix,
713-
formats=dict(self.filter_formats(nb, json_notebook)),
714-
format_base=self.request.uri.replace(self.format_prefix, "").replace(self.base_url, '/'),
715-
date=datetime.utcnow().strftime(date_fmt),
716-
breadcrumbs=breadcrumbs,
717-
title=title,
718-
**config)
726+
json_notebook=json_notebook,
727+
**namespace)
719728
html_time.stop()
720729

721-
if 'content_type' in self.formats[format]:
722-
self.set_header('Content-Type', self.formats[format]['content_type'])
730+
if 'content_type' in self.formats[self.format]:
731+
self.set_header('Content-Type', self.formats[self.format]['content_type'])
723732
yield self.cache_and_finish(html)
724733

725734
# Index notebook

nbviewer/providers/gist/handlers.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,18 @@
2323

2424
from .. import _load_handler_from_location
2525

26-
PROVIDER_CTX = {
27-
'provider_label': 'Gist',
28-
'provider_icon': 'github-square',
29-
'executor_label': 'Binder',
30-
'executor_icon': 'icon-binder',
31-
}
32-
33-
34-
BINDER_TMPL = '{binder_base_url}/gist/{user}/{gist_id}/master'
35-
BINDER_PATH_TMPL = BINDER_TMPL+'?filepath={path}'
36-
3726

3827
class GistClientMixin(GithubClientMixin):
28+
PROVIDER_CTX = {
29+
'provider_label': 'Gist',
30+
'provider_icon': 'github-square',
31+
'executor_label': 'Binder',
32+
'executor_icon': 'icon-binder',
33+
}
34+
35+
BINDER_TMPL = '{binder_base_url}/gist/{user}/{gist_id}/master'
36+
BINDER_PATH_TMPL = BINDER_TMPL+'?filepath={path}'
37+
3938
def client_error_message(self, exc, url, body, msg=None):
4039
if exc.code == 403 and 'too big' in body.lower():
4140
return 400, "GitHub will not serve raw gists larger than 10MB"
@@ -50,9 +49,16 @@ class UserGistsHandler(GistClientMixin, BaseHandler):
5049
5150
.ipynb file extension is required for listing (not for rendering).
5251
"""
52+
def render_usergists_template(self, entries, user, provider_url, prev_url,
53+
next_url, **namespace):
54+
return self.render_template("usergists.html", entries=entries, user=user,
55+
provider_url=provider_url, prev_url=prev_url,
56+
next_url=next_url, **self.PROVIDER_CTX,
57+
**namespace)
58+
5359
@cached
5460
@gen.coroutine
55-
def get(self, user):
61+
def get(self, user, **namespace):
5662
page = self.get_argument("page", None)
5763
params = {}
5864
if page:
@@ -74,8 +80,8 @@ def get(self, user):
7480
description=gist['description'] or '',
7581
))
7682
provider_url = u"https://gist.github.com/{user}".format(user=user)
77-
html = self.render_template("usergists.html",
78-
entries=entries, user=user, provider_url=provider_url, prev_url=prev_url, next_url=next_url, **PROVIDER_CTX
83+
html = self.render_usergists_template(entries=entries, user=user, provider_url=provider_url,
84+
prev_url=prev_url, next_url=next_url, **namespace
7985
)
8086
yield self.cache_and_finish(html)
8187

@@ -125,7 +131,7 @@ def get(self, user, gist_id, filename=''):
125131
content = file['content']
126132

127133
# Enable a binder navbar icon if a binder base URL is configured
128-
executor_url = BINDER_PATH_TMPL.format(
134+
executor_url = self.BINDER_PATH_TMPL.format(
129135
binder_base_url=self.binder_base_url,
130136
user=user.rstrip('/'),
131137
gist_id=gist_id,
@@ -140,9 +146,7 @@ def get(self, user, gist_id, filename=''):
140146
executor_url=executor_url,
141147
msg="gist: %s" % gist_id,
142148
public=gist['public'],
143-
format=self.format,
144-
request=self.request,
145-
**PROVIDER_CTX
149+
**self.PROVIDER_CTX
146150
)
147151
else:
148152
self.set_header('Content-Type', file.get('type') or 'text/plain')
@@ -178,7 +182,7 @@ def get(self, user, gist_id, filename=''):
178182
entries.extend(others)
179183

180184
# Enable a binder navbar icon if a binder base URL is configured
181-
executor_url = BINDER_TMPL.format(
185+
executor_url = self.BINDER_TMPL.format(
182186
binder_base_url=self.binder_base_url,
183187
user=user.rstrip('/'),
184188
gist_id=gist_id
@@ -192,7 +196,7 @@ def get(self, user, gist_id, filename=''):
192196
user=user.rstrip('/'),
193197
provider_url=gist['html_url'],
194198
executor_url=executor_url,
195-
**PROVIDER_CTX
199+
**self.PROVIDER_CTX
196200
)
197201
yield self.cache_and_finish(html)
198202

nbviewer/providers/github/handlers.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,21 @@
3535

3636
from .. import _load_handler_from_location
3737

38-
PROVIDER_CTX = {
39-
'provider_label': 'GitHub',
40-
'provider_icon': 'github',
41-
'executor_label': 'Binder',
42-
'executor_icon': 'icon-binder',
43-
}
44-
45-
46-
BINDER_TMPL = '{binder_base_url}/gh/{org}/{repo}/{ref}'
47-
BINDER_PATH_TMPL = BINDER_TMPL+'?filepath={path}'
48-
4938

5039
def _github_url():
5140
return os.environ.get('GITHUB_URL') if os.environ.get('GITHUB_URL', '') else "https://github.com/"
5241

5342
class GithubClientMixin(object):
43+
PROVIDER_CTX = {
44+
'provider_label': 'GitHub',
45+
'provider_icon': 'github',
46+
'executor_label': 'Binder',
47+
'executor_icon': 'icon-binder',
48+
}
49+
50+
BINDER_TMPL = '{binder_base_url}/gh/{org}/{repo}/{ref}'
51+
BINDER_PATH_TMPL = BINDER_TMPL+'?filepath={path}'
52+
5453
@property
5554
def github_client(self):
5655
"""Create an upgraded github API client from the HTTP client"""
@@ -112,7 +111,7 @@ def get(self, user):
112111
html = self.render_template("userview.html",
113112
entries=entries, provider_url=provider_url,
114113
next_url=next_url, prev_url=prev_url,
115-
**PROVIDER_CTX
114+
**self.PROVIDER_CTX
116115
)
117116
yield self.cache_and_finish(html)
118117

@@ -127,9 +126,17 @@ def get(self, user, repo):
127126

128127
class GitHubTreeHandler(GithubClientMixin, BaseHandler):
129128
"""list files in a github repo (like github tree)"""
129+
def render_treelist_template(self, entries, breadcrumbs, provider_url, user, repo, ref, path,
130+
branches, tags, executor_url, **namespace):
131+
return self.render_template("treelist.html", entries=entries, breadcrumbs=breadcrumbs,
132+
provider_url=provider_url, user=user, repo=repo, ref=ref,
133+
path=path, branches=branches, tags=tags, tree_type="github",
134+
tree_label="repositories", executor_url=executor_url,
135+
**self.PROVIDER_CTX, **namespace)
136+
130137
@cached
131138
@gen.coroutine
132-
def get(self, user, repo, ref, path):
139+
def get(self, user, repo, ref, path, **namespace):
133140
if not self.request.uri.endswith('/'):
134141
self.redirect(self.request.uri + '/')
135142
return
@@ -215,20 +222,17 @@ def get(self, user, repo, ref, path):
215222
entries.extend(others)
216223

217224
# Enable a binder navbar icon if a binder base URL is configured
218-
executor_url = BINDER_TMPL.format(
225+
executor_url = self.BINDER_TMPL.format(
219226
binder_base_url=self.binder_base_url,
220227
org=user,
221228
repo=repo,
222229
ref=ref,
223230
) if self.binder_base_url else None
224231

225-
html = self.render_template("treelist.html",
232+
html = self.render_treelist_template(
226233
entries=entries, breadcrumbs=breadcrumbs, provider_url=provider_url,
227-
user=user, repo=repo, ref=ref, path=path,
228-
branches=branches, tags=tags, tree_type="github",
229-
tree_label="repositories",
230-
executor_url=executor_url,
231-
**PROVIDER_CTX
234+
user=user, repo=repo, ref=ref, path=path, branches=branches, tags=tags,
235+
executor_url=executor_url, **namespace
232236
)
233237
yield self.cache_and_finish(html)
234238

@@ -302,7 +306,7 @@ def get(self, user, repo, ref, path):
302306
breadcrumbs.extend(self.breadcrumbs(dir_path, base_url))
303307

304308
# Enable a binder navbar icon if a binder base URL is configured
305-
executor_url = BINDER_PATH_TMPL.format(
309+
executor_url = self.BINDER_PATH_TMPL.format(
306310
binder_base_url=self.binder_base_url,
307311
org=user,
308312
repo=repo,
@@ -325,9 +329,7 @@ def get(self, user, repo, ref, path):
325329
breadcrumbs=breadcrumbs,
326330
msg="file from GitHub: %s" % raw_url,
327331
public=True,
328-
format=self.format,
329-
request=self.request,
330-
**PROVIDER_CTX
332+
**self.PROVIDER_CTX
331333
)
332334
else:
333335
mime, enc = mimetypes.guess_type(path)

nbviewer/providers/local/handlers.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,20 @@ def get(self, path):
183183
download_url='?download',
184184
msg="file from localfile: %s" % path,
185185
public=False,
186-
format=self.format,
187-
request=self.request,
188186
breadcrumbs=self.breadcrumbs(path),
189187
title=os.path.basename(path))
190188

191-
def show_dir(self, fullpath, path):
189+
# Make available to increase modularity for subclassing
190+
# E.g. so subclasses can implement templates with custom logic
191+
# without having to copy-paste the entire show_dir method
192+
def render_dirview_template(self, entries, breadcrumbs, title, **namespace):
193+
194+
return self.render_template('dirview.html',
195+
entries=entries, breadcrumbs=breadcrumbs,
196+
title=title, **namespace)
197+
198+
199+
def show_dir(self, fullpath, path, **namespace):
192200
"""Render the directory view template for a given filesystem path.
193201
194202
Parameters
@@ -251,10 +259,10 @@ def show_dir(self, fullpath, path):
251259
entries.extend(dirs)
252260
entries.extend(ipynbs)
253261

254-
html = self.render_template('dirview.html',
255-
entries=entries,
256-
breadcrumbs=self.breadcrumbs(path),
257-
title=url_path_join(path, '/'))
262+
html = self.render_dirview_template(entries=entries,
263+
breadcrumbs=self.breadcrumbs(path),
264+
title=url_path_join(path, '/'),
265+
**namespace)
258266
return html
259267

260268

nbviewer/providers/url/handlers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ def get(self, secure, netloc, url):
8686
yield self.finish_notebook(nbjson, download_url=remote_url,
8787
msg="file from url: %s" % remote_url,
8888
public=public,
89-
request=self.request,
90-
format=self.format)
89+
request=self.request)
9190

9291

9392
def default_handlers(handlers=[], **handler_names):

0 commit comments

Comments
 (0)