-
-
Notifications
You must be signed in to change notification settings - Fork 106
/
__init__.py
168 lines (136 loc) · 5.52 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import re, json, copy
from tornado import web
from tornado.httputil import url_concat
from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPError
from traitlets import Unicode, Bool
from traitlets.config import Configurable
from jupyter_server.utils import url_path_join, url_escape
from jupyter_server.base.handlers import APIHandler
from ._version import __version__
link_regex = re.compile(r'<([^>]*)>;\s*rel="([\w]*)\"')
class GitHubConfig(Configurable):
"""
Allows configuration of access to the GitHub api
"""
allow_client_side_access_token = Bool(
False,
help=(
"If True the access token specified in the JupyterLab settings "
"will take precedence. If False the token specified in JupyterLab "
"will be ignored. Storing your access token in the client can "
"present a security risk so be careful if enabling this setting."
)
).tag(config=True)
api_url = Unicode(
'https://api.github.com',
help="The url for the GitHub api"
).tag(config=True)
access_token = Unicode(
'',
help="A personal access token for GitHub."
).tag(config=True)
validate_cert = Bool(
True,
help=(
"Whether to validate the servers' SSL certificate on requests "
"made to the GitHub api. In general this is a bad idea so only "
"disable SSL validation if you know what you are doing!"
)
).tag(config=True)
class GitHubHandler(APIHandler):
"""
A proxy for the GitHub API v3.
The purpose of this proxy is to provide authentication to the API requests
which allows for a higher rate limit. Without this, the rate limit on
unauthenticated calls is so limited as to be practically useless.
"""
client = AsyncHTTPClient()
@web.authenticated
async def get(self, path):
"""
Proxy API requests to GitHub, adding authentication parameter(s) if
they have been set.
"""
# Get access to the notebook config object
c = GitHubConfig(config=self.config)
try:
query = self.request.query_arguments
params = {key: query[key][0].decode() for key in query}
api_path = url_path_join(c.api_url, url_escape(path))
params['per_page'] = 100
access_token = params.pop('access_token', None)
if access_token and c.allow_client_side_access_token == True:
token = access_token
elif access_token and c.allow_client_side_access_token == False:
msg = (
"Client side (JupyterLab) access tokens have been "
"disabled for security reasons.\nPlease remove your "
"access token from JupyterLab and instead add it to "
"your notebook configuration file:\n"
"c.GitHubConfig.access_token = '<TOKEN>'\n"
)
raise HTTPError(403, msg)
elif c.access_token != '':
# Preferentially use the config access_token if set
token = c.access_token
else:
token = ''
api_path = url_concat(api_path, params)
request = HTTPRequest(
api_path,
validate_cert=c.validate_cert,
user_agent='JupyterLab GitHub',
headers={"Authorization": "token {}".format(token)}
)
response = await self.client.fetch(request)
data = json.loads(response.body.decode('utf-8'))
# Check if we need to paginate results.
# If so, get pages until all the results
# are loaded into the data buffer.
next_page_path = self._maybe_get_next_page_path(response)
while next_page_path:
request = copy.copy(request)
request.url = next_page_path
response = await self.client.fetch(request)
next_page_path = self._maybe_get_next_page_path(response)
data.extend(json.loads(response.body.decode('utf-8')))
# Send the results back.
self.finish(json.dumps(data))
except HTTPError as err:
self.set_status(err.code)
message = err.response.body if err.response else str(err.code)
self.finish(message)
def _maybe_get_next_page_path(self, response):
# If there is a 'Link' header in the response, we
# need to paginate.
link_headers = response.headers.get_list('Link')
next_page_path = None
if link_headers:
links = {}
matched = link_regex.findall(link_headers[0])
for match in matched:
links[match[1]] = match[0]
next_page_path = links.get('next', None)
return next_page_path
def _jupyter_labextension_paths():
return [
{
"src": "labextension",
"dest": "@jupyterlab/github",
}
]
def _jupyter_server_extension_paths():
return [{
'module': 'jupyterlab_github'
}]
def load_jupyter_server_extension(nb_server_app):
"""
Called when the extension is loaded.
Args:
nb_server_app (NotebookWebApplication): handle to the Notebook webserver instance.
"""
web_app = nb_server_app.web_app
base_url = web_app.settings['base_url']
endpoint = url_path_join(base_url, 'github')
handlers = [(endpoint + "(.*)", GitHubHandler)]
web_app.add_handlers('.*$', handlers)