23
23
24
24
from .. import _load_handler_from_location
25
25
26
-
27
26
class GistClientMixin (GithubClientMixin ):
27
+
28
+ # PROVIDER_CTX is a dictionary whose entries are passed as keyword arguments
29
+ # to the render_template method of the GistHandler. The following describe
30
+ # the information contained in each of these keyword arguments:
31
+ # provider_label: str
32
+ # Text to to apply to the navbar icon linking to the provider
33
+ # provider_icon: str
34
+ # CSS classname to apply to the navbar icon linking to the provider
35
+ # executor_label: str, optional
36
+ # Text to apply to the navbar icon linking to the execution service
37
+ # executor_icon: str, optional
38
+ # CSS classname to apply to the navbar icon linking to the execution service
28
39
PROVIDER_CTX = {
29
40
'provider_label' : 'Gist' ,
30
41
'provider_icon' : 'github-square' ,
31
42
'executor_label' : 'Binder' ,
32
43
'executor_icon' : 'icon-binder' ,
33
44
}
34
-
45
+
35
46
BINDER_TMPL = '{binder_base_url}/gist/{user}/{gist_id}/master'
36
47
BINDER_PATH_TMPL = BINDER_TMPL + '?filepath={path}'
37
-
48
+
38
49
def client_error_message (self , exc , url , body , msg = None ):
39
50
if exc .code == 403 and 'too big' in body .lower ():
40
51
return 400 , "GitHub will not serve raw gists larger than 10MB"
@@ -50,12 +61,18 @@ class UserGistsHandler(GistClientMixin, BaseHandler):
50
61
.ipynb file extension is required for listing (not for rendering).
51
62
"""
52
63
def render_usergists_template (self , entries , user , provider_url , prev_url ,
53
- next_url , ** namespace ):
64
+ next_url , ** namespace ):
65
+ """
66
+ provider_url: str
67
+ URL to the notebook document upstream at the provider (e.g., GitHub)
68
+ executor_url: str, optional (kwarg passed into `namespace`)
69
+ URL to execute the notebook document (e.g., Binder)
70
+ """
54
71
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
-
72
+ provider_url = provider_url , prev_url = prev_url ,
73
+ next_url = next_url , ** self .PROVIDER_CTX ,
74
+ ** namespace )
75
+
59
76
@cached
60
77
@gen .coroutine
61
78
def get (self , user , ** namespace ):
@@ -88,14 +105,17 @@ def get(self, user, **namespace):
88
105
89
106
class GistHandler (GistClientMixin , RenderingHandler ):
90
107
"""render a gist notebook, or list files if a multifile gist"""
91
- @ cached
108
+
92
109
@gen .coroutine
93
- def get (self , user , gist_id , filename = '' ):
110
+ def parse_gist (self , user , gist_id , filename = '' ):
111
+
94
112
with self .catch_client_error ():
95
113
response = yield self .github_client .get_gist (gist_id )
96
114
97
115
gist = json .loads (response_text (response ))
116
+
98
117
gist_id = gist ['id' ]
118
+
99
119
if user is None :
100
120
# redirect to /gist/user/gist_id if no user given
101
121
owner_dict = gist .get ('owner' , {})
@@ -111,94 +131,151 @@ def get(self, user, gist_id, filename=''):
111
131
return
112
132
113
133
files = gist ['files' ]
134
+
114
135
many_files_gist = (len (files ) > 1 )
115
136
116
- if not many_files_gist and not filename :
117
- filename = list ( files . keys ())[ 0 ]
137
+ # user and gist_id get modified
138
+ return user , gist_id , gist , files , many_files_gist
118
139
119
- if filename and filename in files :
120
- file = files [filename ]
121
- if (file ['type' ] or '' ).startswith ('image/' ):
122
- app_log .debug ("Fetching raw image (%s) %s/%s: %s" , file ['type' ], gist_id , filename , file ['raw_url' ])
123
- response = yield self .fetch (file ['raw_url' ])
124
- # use raw bytes for images:
125
- content = response .body
126
- elif file ['truncated' ]:
127
- app_log .debug ("Gist %s/%s truncated, fetching %s" , gist_id , filename , file ['raw_url' ])
128
- response = yield self .fetch (file ['raw_url' ])
129
- content = response_text (response , encoding = 'utf-8' )
140
+ # Analogous to GitHubTreeHandler
141
+ @gen .coroutine
142
+ def tree_get (self , user , gist_id , gist , files ):
143
+ """
144
+ user, gist_id, gist, and files are (most) of the values returned by parse_gist
145
+ """
146
+ entries = []
147
+ ipynbs = []
148
+ others = []
149
+
150
+ for file in files .values ():
151
+ e = {}
152
+ e ['name' ] = file ['filename' ]
153
+ if file ['filename' ].endswith ('.ipynb' ):
154
+ e ['url' ] = quote ('/%s/%s' % (gist_id , file ['filename' ]))
155
+ e ['class' ] = 'fa-book'
156
+ ipynbs .append (e )
130
157
else :
131
- content = file ['content' ]
132
-
133
- # Enable a binder navbar icon if a binder base URL is configured
134
- executor_url = self .BINDER_PATH_TMPL .format (
135
- binder_base_url = self .binder_base_url ,
136
- user = user .rstrip ('/' ),
137
- gist_id = gist_id ,
138
- path = quote (filename )
139
- ) if self .binder_base_url else None
140
-
141
- if not many_files_gist or filename .endswith ('.ipynb' ):
142
- yield self .finish_notebook (
143
- content ,
144
- file ['raw_url' ],
145
- provider_url = gist ['html_url' ],
146
- executor_url = executor_url ,
147
- msg = "gist: %s" % gist_id ,
148
- public = gist ['public' ],
149
- ** self .PROVIDER_CTX
158
+ provider_url = u"https://gist.github.com/{user}/{gist_id}#file-{clean_name}" .format (
159
+ user = user ,
160
+ gist_id = gist_id ,
161
+ clean_name = clean_filename (file ['filename' ]),
150
162
)
151
- else :
152
- self .set_header ('Content-Type' , file .get ('type' ) or 'text/plain' )
153
- # cannot redirect because of X-Frame-Content
154
- self .finish (content )
155
- return
163
+ e ['url' ] = provider_url
164
+ e ['class' ] = 'fa-share'
165
+ others .append (e )
166
+
167
+ entries .extend (ipynbs )
168
+ entries .extend (others )
169
+
170
+ # Enable a binder navbar icon if a binder base URL is configured
171
+ executor_url = self .BINDER_TMPL .format (
172
+ binder_base_url = self .binder_base_url ,
173
+ user = user .rstrip ('/' ),
174
+ gist_id = gist_id
175
+ ) if self .binder_base_url else None
176
+
177
+ # provider_url:
178
+ # URL to the notebook document upstream at the provider (e.g., GitHub)
179
+ # executor_url: str, optional
180
+ # URL to execute the notebook document (e.g., Binder)
181
+ html = self .render_template (
182
+ 'treelist.html' ,
183
+ entries = entries ,
184
+ tree_type = 'gist' ,
185
+ tree_label = 'gists' ,
186
+ user = user .rstrip ('/' ),
187
+ provider_url = gist ['html_url' ],
188
+ executor_url = executor_url ,
189
+ ** self .PROVIDER_CTX
190
+ )
191
+ yield self .cache_and_finish (html )
156
192
157
- elif filename :
158
- raise web .HTTPError (404 , "No such file in gist: %s (%s)" , filename , list (files .keys ()))
193
+ # Analogous to GitHubBlobHandler
194
+ @gen .coroutine
195
+ def file_get (self , user , gist_id , filename , gist , many_files_gist , file ):
196
+ content = yield self .get_notebook_data (gist_id , filename , many_files_gist , file )
197
+
198
+ if not content :
199
+ return
200
+
201
+ yield self .deliver_notebook (user , gist_id , filename , gist , file , content )
202
+
203
+ # Only called by file_get
204
+ @gen .coroutine
205
+ def get_notebook_data (self , gist_id , filename , many_files_gist , file ):
206
+ """
207
+ gist_id, filename, many_files_gist, file are all passed to file_get
208
+ """
209
+ if (file ['type' ] or '' ).startswith ('image/' ):
210
+ app_log .debug ("Fetching raw image (%s) %s/%s: %s" , file ['type' ], gist_id , filename , file ['raw_url' ])
211
+ response = yield self .fetch (file ['raw_url' ])
212
+ # use raw bytes for images:
213
+ content = response .body
214
+ elif file ['truncated' ]:
215
+ app_log .debug ("Gist %s/%s truncated, fetching %s" , gist_id , filename , file ['raw_url' ])
216
+ response = yield self .fetch (file ['raw_url' ])
217
+ content = response_text (response , encoding = 'utf-8' )
159
218
else :
160
- entries = []
161
- ipynbs = []
162
- others = []
163
-
164
- for file in files .values ():
165
- e = {}
166
- e ['name' ] = file ['filename' ]
167
- if file ['filename' ].endswith ('.ipynb' ):
168
- e ['url' ] = quote ('/%s/%s' % (gist_id , file ['filename' ]))
169
- e ['class' ] = 'fa-book'
170
- ipynbs .append (e )
171
- else :
172
- provider_url = u"https://gist.github.com/{user}/{gist_id}#file-{clean_name}" .format (
173
- user = user ,
174
- gist_id = gist_id ,
175
- clean_name = clean_filename (file ['filename' ]),
176
- )
177
- e ['url' ] = provider_url
178
- e ['class' ] = 'fa-share'
179
- others .append (e )
180
-
181
- entries .extend (ipynbs )
182
- entries .extend (others )
183
-
184
- # Enable a binder navbar icon if a binder base URL is configured
185
- executor_url = self .BINDER_TMPL .format (
186
- binder_base_url = self .binder_base_url ,
187
- user = user .rstrip ('/' ),
188
- gist_id = gist_id
189
- ) if self .binder_base_url else None
190
-
191
- html = self .render_template (
192
- 'treelist.html' ,
193
- entries = entries ,
194
- tree_type = 'gist' ,
195
- tree_label = 'gists' ,
196
- user = user .rstrip ('/' ),
197
- provider_url = gist ['html_url' ],
198
- executor_url = executor_url ,
199
- ** self .PROVIDER_CTX
200
- )
201
- yield self .cache_and_finish (html )
219
+ content = file ['content' ]
220
+
221
+ if many_files_gist and not filename .endswith ('.ipynb' ):
222
+ self .set_header ('Content-Type' , file .get ('type' ) or 'text/plain' )
223
+ # cannot redirect because of X-Frame-Content
224
+ self .finish (content )
225
+ return
226
+
227
+ else :
228
+ return content
229
+
230
+ # Only called by file_get
231
+ @gen .coroutine
232
+ def deliver_notebook (self , user , gist_id , filename , gist , file , content ):
233
+ """
234
+ user, gist_id, filename, gist, file, are the same values as those
235
+ passed into file_get, whereas content is returned from
236
+ get_notebook_data using user, gist_id, filename, gist, and file.
237
+ """
238
+ # Enable a binder navbar icon if a binder base URL is configured
239
+ executor_url = self .BINDER_PATH_TMPL .format (
240
+ binder_base_url = self .binder_base_url ,
241
+ user = user .rstrip ('/' ),
242
+ gist_id = gist_id ,
243
+ path = quote (filename )
244
+ ) if self .binder_base_url else None
245
+
246
+ # provider_url: str, optional
247
+ # URL to the notebook document upstream at the provider (e.g., GitHub)
248
+ yield self .finish_notebook (
249
+ content ,
250
+ file ['raw_url' ],
251
+ msg = "gist: %s" % gist_id ,
252
+ public = gist ['public' ],
253
+ provider_url = gist ['html_url' ],
254
+ executor_url = executor_url ,
255
+ ** self .PROVIDER_CTX )
256
+
257
+ @cached
258
+ @gen .coroutine
259
+ def get (self , user , gist_id , filename = '' ):
260
+ """
261
+ Encompasses both the case of a single file gist, handled by
262
+ `file_get`, as well as a many-file gist, handled by `tree_get`.
263
+ """
264
+ user , gist_id , gist , files , many_files_gist = yield self .parse_gist (user , gist_id , filename )
265
+
266
+ if many_files_gist and not filename :
267
+ yield self .tree_get (user , gist_id , gist , files )
268
+
269
+ else :
270
+ if not many_files_gist and not filename :
271
+ filename = list (files .keys ())[0 ]
272
+
273
+ if filename not in files :
274
+ raise web .HTTPError (404 , "No such file in gist: %s (%s)" , filename , list (files .keys ()))
275
+
276
+ file = files [filename ]
277
+
278
+ yield self .file_get (user , gist_id , filename , gist , many_files_gist , file )
202
279
203
280
204
281
class GistRedirectHandler (BaseHandler ):
0 commit comments