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