##// END OF EJS Templates
fix typo in gh_api.get_pulls_list
MinRK -
Show More
@@ -1,276 +1,276 b''
1 """Functions for Github API requests."""
1 """Functions for Github API requests."""
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 import re
10 import re
11 import sys
11 import sys
12
12
13 import requests
13 import requests
14 import getpass
14 import getpass
15 import json
15 import json
16
16
17 try:
17 try:
18 import requests_cache
18 import requests_cache
19 except ImportError:
19 except ImportError:
20 print("no cache")
20 print("no cache")
21 else:
21 else:
22 requests_cache.install_cache("gh_api")
22 requests_cache.install_cache("gh_api")
23
23
24 # Keyring stores passwords by a 'username', but we're not storing a username and
24 # Keyring stores passwords by a 'username', but we're not storing a username and
25 # password
25 # password
26 fake_username = 'ipython_tools'
26 fake_username = 'ipython_tools'
27
27
28 class Obj(dict):
28 class Obj(dict):
29 """Dictionary with attribute access to names."""
29 """Dictionary with attribute access to names."""
30 def __getattr__(self, name):
30 def __getattr__(self, name):
31 try:
31 try:
32 return self[name]
32 return self[name]
33 except KeyError:
33 except KeyError:
34 raise AttributeError(name)
34 raise AttributeError(name)
35
35
36 def __setattr__(self, name, val):
36 def __setattr__(self, name, val):
37 self[name] = val
37 self[name] = val
38
38
39 token = None
39 token = None
40 def get_auth_token():
40 def get_auth_token():
41 global token
41 global token
42
42
43 if token is not None:
43 if token is not None:
44 return token
44 return token
45
45
46 import keyring
46 import keyring
47 token = keyring.get_password('github', fake_username)
47 token = keyring.get_password('github', fake_username)
48 if token is not None:
48 if token is not None:
49 return token
49 return token
50
50
51 print("Please enter your github username and password. These are not "
51 print("Please enter your github username and password. These are not "
52 "stored, only used to get an oAuth token. You can revoke this at "
52 "stored, only used to get an oAuth token. You can revoke this at "
53 "any time on Github.")
53 "any time on Github.")
54 user = input("Username: ")
54 user = input("Username: ")
55 pw = getpass.getpass("Password: ")
55 pw = getpass.getpass("Password: ")
56
56
57 auth_request = {
57 auth_request = {
58 "scopes": [
58 "scopes": [
59 "public_repo",
59 "public_repo",
60 "gist"
60 "gist"
61 ],
61 ],
62 "note": "IPython tools",
62 "note": "IPython tools",
63 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
63 "note_url": "https://github.com/ipython/ipython/tree/master/tools",
64 }
64 }
65 response = requests.post('https://api.github.com/authorizations',
65 response = requests.post('https://api.github.com/authorizations',
66 auth=(user, pw), data=json.dumps(auth_request))
66 auth=(user, pw), data=json.dumps(auth_request))
67 response.raise_for_status()
67 response.raise_for_status()
68 token = json.loads(response.text)['token']
68 token = json.loads(response.text)['token']
69 keyring.set_password('github', fake_username, token)
69 keyring.set_password('github', fake_username, token)
70 return token
70 return token
71
71
72 def make_auth_header():
72 def make_auth_header():
73 return {'Authorization': 'token ' + get_auth_token()}
73 return {'Authorization': 'token ' + get_auth_token()}
74
74
75 def post_issue_comment(project, num, body):
75 def post_issue_comment(project, num, body):
76 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
76 url = 'https://api.github.com/repos/{project}/issues/{num}/comments'.format(project=project, num=num)
77 payload = json.dumps({'body': body})
77 payload = json.dumps({'body': body})
78 requests.post(url, data=payload, headers=make_auth_header())
78 requests.post(url, data=payload, headers=make_auth_header())
79
79
80 def post_gist(content, description='', filename='file', auth=False):
80 def post_gist(content, description='', filename='file', auth=False):
81 """Post some text to a Gist, and return the URL."""
81 """Post some text to a Gist, and return the URL."""
82 post_data = json.dumps({
82 post_data = json.dumps({
83 "description": description,
83 "description": description,
84 "public": True,
84 "public": True,
85 "files": {
85 "files": {
86 filename: {
86 filename: {
87 "content": content
87 "content": content
88 }
88 }
89 }
89 }
90 }).encode('utf-8')
90 }).encode('utf-8')
91
91
92 headers = make_auth_header() if auth else {}
92 headers = make_auth_header() if auth else {}
93 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
93 response = requests.post("https://api.github.com/gists", data=post_data, headers=headers)
94 response.raise_for_status()
94 response.raise_for_status()
95 response_data = json.loads(response.text)
95 response_data = json.loads(response.text)
96 return response_data['html_url']
96 return response_data['html_url']
97
97
98 def get_pull_request(project, num, auth=False):
98 def get_pull_request(project, num, auth=False):
99 """get pull request info by number
99 """get pull request info by number
100 """
100 """
101 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
101 url = "https://api.github.com/repos/{project}/pulls/{num}".format(project=project, num=num)
102 if auth:
102 if auth:
103 header = make_auth_header()
103 header = make_auth_header()
104 else:
104 else:
105 header = None
105 header = None
106 response = requests.get(url, headers=header)
106 response = requests.get(url, headers=header)
107 response.raise_for_status()
107 response.raise_for_status()
108 return json.loads(response.text, object_hook=Obj)
108 return json.loads(response.text, object_hook=Obj)
109
109
110 def get_pull_request_files(project, num, auth=False):
110 def get_pull_request_files(project, num, auth=False):
111 """get list of files in a pull request"""
111 """get list of files in a pull request"""
112 url = "https://api.github.com/repos/{project}/pulls/{num}/files".format(project=project, num=num)
112 url = "https://api.github.com/repos/{project}/pulls/{num}/files".format(project=project, num=num)
113 if auth:
113 if auth:
114 header = make_auth_header()
114 header = make_auth_header()
115 else:
115 else:
116 header = None
116 header = None
117 return get_paged_request(url, headers=header)
117 return get_paged_request(url, headers=header)
118
118
119 element_pat = re.compile(r'<(.+?)>')
119 element_pat = re.compile(r'<(.+?)>')
120 rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
120 rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')
121
121
122 def get_paged_request(url, headers=None, **params):
122 def get_paged_request(url, headers=None, **params):
123 """get a full list, handling APIv3's paging"""
123 """get a full list, handling APIv3's paging"""
124 results = []
124 results = []
125 params.setdefault("per_page", 100)
125 params.setdefault("per_page", 100)
126 while True:
126 while True:
127 print("fetching %s with %s" % (url, params), file=sys.stderr)
127 print("fetching %s with %s" % (url, params), file=sys.stderr)
128 response = requests.get(url, headers=headers, params=params)
128 response = requests.get(url, headers=headers, params=params)
129 response.raise_for_status()
129 response.raise_for_status()
130 results.extend(response.json())
130 results.extend(response.json())
131 if 'next' in response.links:
131 if 'next' in response.links:
132 url = response.links['next']['url']
132 url = response.links['next']['url']
133 else:
133 else:
134 break
134 break
135 return results
135 return results
136
136
137 def get_pulls_list(project, auth=False, **params):
137 def get_pulls_list(project, auth=False, **params):
138 """get pull request list"""
138 """get pull request list"""
139 params.setdefault("state", "closed")
139 params.setdefault("state", "closed")
140 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
140 url = "https://api.github.com/repos/{project}/pulls".format(project=project)
141 if auth:
141 if auth:
142 headers = make_auth_header()
142 headers = make_auth_header()
143 else:
143 else:
144 headers = None
144 headers = None
145 pages = get_paged_request(url, headers=headers, params=params)
145 pages = get_paged_request(url, headers=headers, **params)
146 return pages
146 return pages
147
147
148 def get_issues_list(project, auth=False, **params):
148 def get_issues_list(project, auth=False, **params):
149 """get issues list"""
149 """get issues list"""
150 params.setdefault("state", "closed")
150 params.setdefault("state", "closed")
151 url = "https://api.github.com/repos/{project}/issues".format(project=project)
151 url = "https://api.github.com/repos/{project}/issues".format(project=project)
152 if auth:
152 if auth:
153 headers = make_auth_header()
153 headers = make_auth_header()
154 else:
154 else:
155 headers = None
155 headers = None
156 pages = get_paged_request(url, headers=headers, **params)
156 pages = get_paged_request(url, headers=headers, **params)
157 return pages
157 return pages
158
158
159 def get_milestones(project, auth=False, **params):
159 def get_milestones(project, auth=False, **params):
160 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
160 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
161 if auth:
161 if auth:
162 headers = make_auth_header()
162 headers = make_auth_header()
163 else:
163 else:
164 headers = None
164 headers = None
165 milestones = get_paged_request(url, headers=headers, **params)
165 milestones = get_paged_request(url, headers=headers, **params)
166 return milestones
166 return milestones
167
167
168 def get_milestone_id(project, milestone, auth=False, **params):
168 def get_milestone_id(project, milestone, auth=False, **params):
169 milestones = get_milestones(project, auth=auth, **params)
169 milestones = get_milestones(project, auth=auth, **params)
170 for mstone in milestones:
170 for mstone in milestones:
171 if mstone['title'] == milestone:
171 if mstone['title'] == milestone:
172 return mstone['number']
172 return mstone['number']
173 else:
173 else:
174 raise ValueError("milestone %s not found" % milestone)
174 raise ValueError("milestone %s not found" % milestone)
175
175
176 def is_pull_request(issue):
176 def is_pull_request(issue):
177 """Return True if the given issue is a pull request."""
177 """Return True if the given issue is a pull request."""
178 return bool(issue.get('pull_request', {}).get('html_url', None))
178 return bool(issue.get('pull_request', {}).get('html_url', None))
179
179
180 # encode_multipart_formdata is from urllib3.filepost
180 # encode_multipart_formdata is from urllib3.filepost
181 # The only change is to iter_fields, to enforce S3's required key ordering
181 # The only change is to iter_fields, to enforce S3's required key ordering
182
182
183 def iter_fields(fields):
183 def iter_fields(fields):
184 fields = fields.copy()
184 fields = fields.copy()
185 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
185 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
186 'Policy', 'Signature', 'Content-Type', 'file'):
186 'Policy', 'Signature', 'Content-Type', 'file'):
187 yield (key, fields.pop(key))
187 yield (key, fields.pop(key))
188 for (k,v) in fields.items():
188 for (k,v) in fields.items():
189 yield k,v
189 yield k,v
190
190
191 def encode_multipart_formdata(fields, boundary=None):
191 def encode_multipart_formdata(fields, boundary=None):
192 """
192 """
193 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
193 Encode a dictionary of ``fields`` using the multipart/form-data mime format.
194
194
195 :param fields:
195 :param fields:
196 Dictionary of fields or list of (key, value) field tuples. The key is
196 Dictionary of fields or list of (key, value) field tuples. The key is
197 treated as the field name, and the value as the body of the form-data
197 treated as the field name, and the value as the body of the form-data
198 bytes. If the value is a tuple of two elements, then the first element
198 bytes. If the value is a tuple of two elements, then the first element
199 is treated as the filename of the form-data section.
199 is treated as the filename of the form-data section.
200
200
201 Field names and filenames must be unicode.
201 Field names and filenames must be unicode.
202
202
203 :param boundary:
203 :param boundary:
204 If not specified, then a random boundary will be generated using
204 If not specified, then a random boundary will be generated using
205 :func:`mimetools.choose_boundary`.
205 :func:`mimetools.choose_boundary`.
206 """
206 """
207 # copy requests imports in here:
207 # copy requests imports in here:
208 from io import BytesIO
208 from io import BytesIO
209 from requests.packages.urllib3.filepost import (
209 from requests.packages.urllib3.filepost import (
210 choose_boundary, six, writer, b, get_content_type
210 choose_boundary, six, writer, b, get_content_type
211 )
211 )
212 body = BytesIO()
212 body = BytesIO()
213 if boundary is None:
213 if boundary is None:
214 boundary = choose_boundary()
214 boundary = choose_boundary()
215
215
216 for fieldname, value in iter_fields(fields):
216 for fieldname, value in iter_fields(fields):
217 body.write(b('--%s\r\n' % (boundary)))
217 body.write(b('--%s\r\n' % (boundary)))
218
218
219 if isinstance(value, tuple):
219 if isinstance(value, tuple):
220 filename, data = value
220 filename, data = value
221 writer(body).write('Content-Disposition: form-data; name="%s"; '
221 writer(body).write('Content-Disposition: form-data; name="%s"; '
222 'filename="%s"\r\n' % (fieldname, filename))
222 'filename="%s"\r\n' % (fieldname, filename))
223 body.write(b('Content-Type: %s\r\n\r\n' %
223 body.write(b('Content-Type: %s\r\n\r\n' %
224 (get_content_type(filename))))
224 (get_content_type(filename))))
225 else:
225 else:
226 data = value
226 data = value
227 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
227 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
228 % (fieldname))
228 % (fieldname))
229 body.write(b'Content-Type: text/plain\r\n\r\n')
229 body.write(b'Content-Type: text/plain\r\n\r\n')
230
230
231 if isinstance(data, int):
231 if isinstance(data, int):
232 data = str(data) # Backwards compatibility
232 data = str(data) # Backwards compatibility
233 if isinstance(data, six.text_type):
233 if isinstance(data, six.text_type):
234 writer(body).write(data)
234 writer(body).write(data)
235 else:
235 else:
236 body.write(data)
236 body.write(data)
237
237
238 body.write(b'\r\n')
238 body.write(b'\r\n')
239
239
240 body.write(b('--%s--\r\n' % (boundary)))
240 body.write(b('--%s--\r\n' % (boundary)))
241
241
242 content_type = b('multipart/form-data; boundary=%s' % boundary)
242 content_type = b('multipart/form-data; boundary=%s' % boundary)
243
243
244 return body.getvalue(), content_type
244 return body.getvalue(), content_type
245
245
246
246
247 def post_download(project, filename, name=None, description=""):
247 def post_download(project, filename, name=None, description=""):
248 """Upload a file to the GitHub downloads area"""
248 """Upload a file to the GitHub downloads area"""
249 if name is None:
249 if name is None:
250 name = os.path.basename(filename)
250 name = os.path.basename(filename)
251 with open(filename, 'rb') as f:
251 with open(filename, 'rb') as f:
252 filedata = f.read()
252 filedata = f.read()
253
253
254 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
254 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
255
255
256 payload = json.dumps(dict(name=name, size=len(filedata),
256 payload = json.dumps(dict(name=name, size=len(filedata),
257 description=description))
257 description=description))
258 response = requests.post(url, data=payload, headers=make_auth_header())
258 response = requests.post(url, data=payload, headers=make_auth_header())
259 response.raise_for_status()
259 response.raise_for_status()
260 reply = json.loads(response.content)
260 reply = json.loads(response.content)
261 s3_url = reply['s3_url']
261 s3_url = reply['s3_url']
262
262
263 fields = dict(
263 fields = dict(
264 key=reply['path'],
264 key=reply['path'],
265 acl=reply['acl'],
265 acl=reply['acl'],
266 success_action_status=201,
266 success_action_status=201,
267 Filename=reply['name'],
267 Filename=reply['name'],
268 AWSAccessKeyId=reply['accesskeyid'],
268 AWSAccessKeyId=reply['accesskeyid'],
269 Policy=reply['policy'],
269 Policy=reply['policy'],
270 Signature=reply['signature'],
270 Signature=reply['signature'],
271 file=(reply['name'], filedata),
271 file=(reply['name'], filedata),
272 )
272 )
273 fields['Content-Type'] = reply['mime_type']
273 fields['Content-Type'] = reply['mime_type']
274 data, content_type = encode_multipart_formdata(fields)
274 data, content_type = encode_multipart_formdata(fields)
275 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
275 s3r = requests.post(s3_url, data=data, headers={'Content-Type': content_type})
276 return s3r
276 return s3r
General Comments 0
You need to be logged in to leave comments. Login now