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