##// END OF EJS Templates
Drop support for deceased Github API v2
Thomas Kluyver -
Show More
@@ -1,215 +1,201
1 """Functions for Github authorisation."""
1 """Functions for Github authorisation."""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 try:
4 try:
5 input = raw_input
5 input = raw_input
6 except NameError:
6 except NameError:
7 pass
7 pass
8
8
9 import os
9 import os
10
10
11 import requests
11 import requests
12 import getpass
12 import getpass
13 import json
13 import json
14
14
15 # Keyring stores passwords by a 'username', but we're not storing a username and
15 # Keyring stores passwords by a 'username', but we're not storing a username and
16 # password
16 # password
17 fake_username = 'ipython_tools'
17 fake_username = 'ipython_tools'
18
18
19 class Obj(dict):
19 class Obj(dict):
20 """Dictionary with attribute access to names."""
20 """Dictionary with attribute access to names."""
21 def __getattr__(self, name):
21 def __getattr__(self, name):
22 try:
22 try:
23 return self[name]
23 return self[name]
24 except KeyError:
24 except KeyError:
25 raise AttributeError(name)
25 raise AttributeError(name)
26
26
27 def __setattr__(self, name, val):
27 def __setattr__(self, name, val):
28 self[name] = val
28 self[name] = val
29
29
30 token = None
30 token = None
31 def get_auth_token():
31 def get_auth_token():
32 global token
32 global token
33
33
34 if token is not None:
34 if token is not None:
35 return token
35 return token
36
36
37 import keyring
37 import keyring
38 token = keyring.get_password('github', fake_username)
38 token = keyring.get_password('github', fake_username)
39 if token is not None:
39 if token is not None:
40 return token
40 return token
41
41
42 print("Please enter your github username and password. These are not "
42 print("Please enter your github username and password. These are not "
43 "stored, only used to get an oAuth token. You can revoke this at "
43 "stored, only used to get an oAuth token. You can revoke this at "
44 "any time on Github.")
44 "any time on Github.")
45 user = input("Username: ")
45 user = input("Username: ")
46 pw = getpass.getpass("Password: ")
46 pw = getpass.getpass("Password: ")
47
47
48 auth_request = {
48 auth_request = {
49 "scopes": [
49 "scopes": [
50 "public_repo",
50 "public_repo",
51 "gist"
51 "gist"
52 ],
52 ],
53 "note": "IPython tools",
53 "note": "IPython tools",
54 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
54 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
55 }
55 }
56 response = requests.post('https://api.github.com/authorizations',
56 response = requests.post('https://api.github.com/authorizations',
57 auth=(user, pw), data=json.dumps(auth_request))
57 auth=(user, pw), data=json.dumps(auth_request))
58 response.raise_for_status()
58 response.raise_for_status()
59 token = json.loads(response.text)['token']
59 token = json.loads(response.text)['token']
60 keyring.set_password('github', fake_username, token)
60 keyring.set_password('github', fake_username, token)
61 return token
61 return token
62
62
63 def make_auth_header():
63 def make_auth_header():
64 return {'Authorization': 'token ' + get_auth_token()}
64 return {'Authorization': 'token ' + get_auth_token()}
65
65
66 def post_issue_comment(project, num, body):
66 def post_issue_comment(project, num, body):
67 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
67 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
68 payload = json.dumps({'body': body})
68 payload = json.dumps({'body': body})
69 r = requests.post(url, data=payload, headers=make_auth_header())
69 r = requests.post(url, data=payload, headers=make_auth_header())
70
70
71 def post_gist(content, description='', filename='file', auth=False):
71 def post_gist(content, description='', filename='file', auth=False):
72 """Post some text to a Gist, and return the URL."""
72 """Post some text to a Gist, and return the URL."""
73 post_data = json.dumps({
73 post_data = json.dumps({
74 "description": description,
74 "description": description,
75 "public": True,
75 "public": True,
76 "files": {
76 "files": {
77 filename: {
77 filename: {
78 "content": content
78 "content": content
79 }
79 }
80 }
80 }
81 }).encode('utf-8')
81 }).encode('utf-8')
82
82
83 headers = make_auth_header() if auth else {}
83 headers = make_auth_header() if auth else {}
84 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
84 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
85 response.raise_for_status()
85 response.raise_for_status()
86 response_data = json.loads(response.text)
86 response_data = json.loads(response.text)
87 return response_data['html_url']
87 return response_data['html_url']
88
88
89 def get_pull_request(project, num, github_api=3):
89 def get_pull_request(project, num):
90 """get pull request info by number
90 """get pull request info by number
91
92 github_api : version of github api to use
93 """
91 """
94 if github_api==2 :
95 url = "http://github.com/api/v2/json/pulls/{project}/{num}".format(project=project, num=num)
96 elif github_api == 3:
97 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
92 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
98 response = requests.get(url)
93 response = requests.get(url)
99 response.raise_for_status()
94 response.raise_for_status()
100 if github_api == 2 :
101 return json.loads(response.text)['pull']
102 return json.loads(response.text, object_hook=Obj)
95 return json.loads(response.text, object_hook=Obj)
103
96
104 def get_pulls_list(project, github_api=3):
97 def get_pulls_list(project):
105 """get pull request list
98 """get pull request list
106
107 github_api : version of github api to use
108 """
99 """
109 if github_api == 3 :
110 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
100 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
111 else :
112 url = "http://github.com/api/v2/json/pulls/{project}".format(project=project)
113 response = requests.get(url)
101 response = requests.get(url)
114 response.raise_for_status()
102 response.raise_for_status()
115 if github_api == 2 :
116 return json.loads(response.text)['pulls']
117 return json.loads(response.text)
103 return json.loads(response.text)
118
104
119 # encode_multipart_formdata is from urllib3.filepost
105 # encode_multipart_formdata is from urllib3.filepost
120 # The only change is to iter_fields, to enforce S3's required key ordering
106 # The only change is to iter_fields, to enforce S3's required key ordering
121
107
122 def iter_fields(fields):
108 def iter_fields(fields):
123 fields = fields.copy()
109 fields = fields.copy()
124 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
110 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
125 'Policy', 'Signature', 'Content-Type', 'file'):
111 'Policy', 'Signature', 'Content-Type', 'file'):
126 yield (key, fields.pop(key))
112 yield (key, fields.pop(key))
127 for (k,v) in fields.items():
113 for (k,v) in fields.items():
128 yield k,v
114 yield k,v
129
115
130 def encode_multipart_formdata(fields, boundary=None):
116 def encode_multipart_formdata(fields, boundary=None):
131 """
117 """
132 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
118 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
133
119
134 :param fields:
120 :param fields:
135 Dictionary of fields or list of (key, value) field tuples. The key is
121 Dictionary of fields or list of (key, value) field tuples. The key is
136 treated as the field name, and the value as the body of the form-data
122 treated as the field name, and the value as the body of the form-data
137 bytes. If the value is a tuple of two elements, then the first element
123 bytes. If the value is a tuple of two elements, then the first element
138 is treated as the filename of the form-data section.
124 is treated as the filename of the form-data section.
139
125
140 Field names and filenames must be unicode.
126 Field names and filenames must be unicode.
141
127
142 :param boundary:
128 :param boundary:
143 If not specified, then a random boundary will be generated using
129 If not specified, then a random boundary will be generated using
144 :func:`mimetools.choose_boundary`.
130 :func:`mimetools.choose_boundary`.
145 """
131 """
146 # copy requests imports in here:
132 # copy requests imports in here:
147 from io import BytesIO
133 from io import BytesIO
148 from requests.packages.urllib3.filepost import (
134 from requests.packages.urllib3.filepost import (
149 choose_boundary, six, writer, b, get_content_type
135 choose_boundary, six, writer, b, get_content_type
150 )
136 )
151 body = BytesIO()
137 body = BytesIO()
152 if boundary is None:
138 if boundary is None:
153 boundary = choose_boundary()
139 boundary = choose_boundary()
154
140
155 for fieldname, value in iter_fields(fields):
141 for fieldname, value in iter_fields(fields):
156 body.write(b('--%s\r\n' % (boundary)))
142 body.write(b('--%s\r\n' % (boundary)))
157
143
158 if isinstance(value, tuple):
144 if isinstance(value, tuple):
159 filename, data = value
145 filename, data = value
160 writer(body).write('Content-Disposition: form-data; name="%s"; '
146 writer(body).write('Content-Disposition: form-data; name="%s"; '
161 'filename="%s"\r\n' % (fieldname, filename))
147 'filename="%s"\r\n' % (fieldname, filename))
162 body.write(b('Content-Type: %s\r\n\r\n' %
148 body.write(b('Content-Type: %s\r\n\r\n' %
163 (get_content_type(filename))))
149 (get_content_type(filename))))
164 else:
150 else:
165 data = value
151 data = value
166 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
152 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
167 % (fieldname))
153 % (fieldname))
168 body.write(b'Content-Type: text/plain\r\n\r\n')
154 body.write(b'Content-Type: text/plain\r\n\r\n')
169
155
170 if isinstance(data, int):
156 if isinstance(data, int):
171 data = str(data) # Backwards compatibility
157 data = str(data) # Backwards compatibility
172 if isinstance(data, six.text_type):
158 if isinstance(data, six.text_type):
173 writer(body).write(data)
159 writer(body).write(data)
174 else:
160 else:
175 body.write(data)
161 body.write(data)
176
162
177 body.write(b'\r\n')
163 body.write(b'\r\n')
178
164
179 body.write(b('--%s--\r\n' % (boundary)))
165 body.write(b('--%s--\r\n' % (boundary)))
180
166
181 content_type = b('multipart/form-data; boundary=%s' % boundary)
167 content_type = b('multipart/form-data; boundary=%s' % boundary)
182
168
183 return body.getvalue(), content_type
169 return body.getvalue(), content_type
184
170
185
171
186 def post_download(project, filename, name=None, description=""):
172 def post_download(project, filename, name=None, description=""):
187 """Upload a file to the GitHub downloads area"""
173 """Upload a file to the GitHub downloads area"""
188 if name is None:
174 if name is None:
189 name = os.path.basename(filename)
175 name = os.path.basename(filename)
190 with open(filename, 'rb') as f:
176 with open(filename, 'rb') as f:
191 filedata = f.read()
177 filedata = f.read()
192
178
193 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
179 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
194
180
195 payload = json.dumps(dict(name=name, size=len(filedata),
181 payload = json.dumps(dict(name=name, size=len(filedata),
196 description=description))
182 description=description))
197 response = requests.post(url, data=payload, headers=make_auth_header())
183 response = requests.post(url, data=payload, headers=make_auth_header())
198 response.raise_for_status()
184 response.raise_for_status()
199 reply = json.loads(response.content)
185 reply = json.loads(response.content)
200 s3_url = reply['s3_url']
186 s3_url = reply['s3_url']
201
187
202 fields = dict(
188 fields = dict(
203 key=reply['path'],
189 key=reply['path'],
204 acl=reply['acl'],
190 acl=reply['acl'],
205 success_action_status=201,
191 success_action_status=201,
206 Filename=reply['name'],
192 Filename=reply['name'],
207 AWSAccessKeyId=reply['accesskeyid'],
193 AWSAccessKeyId=reply['accesskeyid'],
208 Policy=reply['policy'],
194 Policy=reply['policy'],
209 Signature=reply['signature'],
195 Signature=reply['signature'],
210 file=(reply['name'], filedata),
196 file=(reply['name'], filedata),
211 )
197 )
212 fields['Content-Type'] = reply['mime_type']
198 fields['Content-Type'] = reply['mime_type']
213 data, content_type = encode_multipart_formdata(fields)
199 data, content_type = encode_multipart_formdata(fields)
214 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
200 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
215 return s3r
201 return s3r
General Comments 0
You need to be logged in to leave comments. Login now