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