##// END OF EJS Templates
Allow checking for backports via milestone
Skipper Seabold -
Show More
@@ -1,143 +1,159 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """
2 """
3 Backport pull requests to a particular branch.
3 Backport pull requests to a particular branch.
4
4
5 Usage: backport_pr.py branch [PR]
5 Usage: backport_pr.py branch [PR]
6
6
7 e.g.:
7 e.g.:
8
8
9 python tools/backport_pr.py 0.13.1 123
9 python tools/backport_pr.py 0.13.1 123
10
10
11 to backport PR #123 onto branch 0.13.1
11 to backport PR #123 onto branch 0.13.1
12
12
13 or
13 or
14
14
15 python tools/backport_pr.py 1.x
15 python tools/backport_pr.py 1.x
16
16
17 to see what PRs are marked for backport that have yet to be applied.
17 to see what PRs are marked for backport that have yet to be applied.
18
18
19 """
19 """
20
20
21 from __future__ import print_function
21 from __future__ import print_function
22
22
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26
26
27 from subprocess import Popen, PIPE, check_call, check_output
27 from subprocess import Popen, PIPE, check_call, check_output
28 from urllib import urlopen
28 from urllib import urlopen
29
29
30 from gh_api import (
30 from gh_api import (
31 get_issues_list,
31 get_issues_list,
32 get_pull_request,
32 get_pull_request,
33 get_pull_request_files,
33 get_pull_request_files,
34 is_pull_request,
34 is_pull_request,
35 get_milestone_id,
35 )
36 )
36
37
37 def find_rejects(root='.'):
38 def find_rejects(root='.'):
38 for dirname, dirs, files in os.walk(root):
39 for dirname, dirs, files in os.walk(root):
39 for fname in files:
40 for fname in files:
40 if fname.endswith('.rej'):
41 if fname.endswith('.rej'):
41 yield os.path.join(dirname, fname)
42 yield os.path.join(dirname, fname)
42
43
43 def get_current_branch():
44 def get_current_branch():
44 branches = check_output(['git', 'branch'])
45 branches = check_output(['git', 'branch'])
45 for branch in branches.splitlines():
46 for branch in branches.splitlines():
46 if branch.startswith('*'):
47 if branch.startswith('*'):
47 return branch[1:].strip()
48 return branch[1:].strip()
48
49
49 def backport_pr(branch, num, project='ipython/ipython'):
50 def backport_pr(branch, num, project='ipython/ipython'):
50 current_branch = get_current_branch()
51 current_branch = get_current_branch()
51 if branch != current_branch:
52 if branch != current_branch:
52 check_call(['git', 'checkout', branch])
53 check_call(['git', 'checkout', branch])
53 check_call(['git', 'pull'])
54 check_call(['git', 'pull'])
54 pr = get_pull_request(project, num, auth=True)
55 pr = get_pull_request(project, num, auth=True)
55 files = get_pull_request_files(project, num, auth=True)
56 files = get_pull_request_files(project, num, auth=True)
56 patch_url = pr['patch_url']
57 patch_url = pr['patch_url']
57 title = pr['title']
58 title = pr['title']
58 description = pr['body']
59 description = pr['body']
59 fname = "PR%i.patch" % num
60 fname = "PR%i.patch" % num
60 if os.path.exists(fname):
61 if os.path.exists(fname):
61 print("using patch from {fname}".format(**locals()))
62 print("using patch from {fname}".format(**locals()))
62 with open(fname) as f:
63 with open(fname) as f:
63 patch = f.read()
64 patch = f.read()
64 else:
65 else:
65 req = urlopen(patch_url)
66 req = urlopen(patch_url)
66 patch = req.read()
67 patch = req.read()
67
68
68 msg = "Backport PR #%i: %s" % (num, title) + '\n\n' + description
69 msg = "Backport PR #%i: %s" % (num, title) + '\n\n' + description
69 check = Popen(['git', 'apply', '--check', '--verbose'], stdin=PIPE)
70 check = Popen(['git', 'apply', '--check', '--verbose'], stdin=PIPE)
70 a,b = check.communicate(patch)
71 a,b = check.communicate(patch)
71
72
72 if check.returncode:
73 if check.returncode:
73 print("patch did not apply, saving to {fname}".format(**locals()))
74 print("patch did not apply, saving to {fname}".format(**locals()))
74 print("edit {fname} until `cat {fname} | git apply --check` succeeds".format(**locals()))
75 print("edit {fname} until `cat {fname} | git apply --check` succeeds".format(**locals()))
75 print("then run tools/backport_pr.py {num} again".format(**locals()))
76 print("then run tools/backport_pr.py {num} again".format(**locals()))
76 if not os.path.exists(fname):
77 if not os.path.exists(fname):
77 with open(fname, 'wb') as f:
78 with open(fname, 'wb') as f:
78 f.write(patch)
79 f.write(patch)
79 return 1
80 return 1
80
81
81 p = Popen(['git', 'apply'], stdin=PIPE)
82 p = Popen(['git', 'apply'], stdin=PIPE)
82 a,b = p.communicate(patch)
83 a,b = p.communicate(patch)
83
84
84 filenames = [ f['filename'] for f in files ]
85 filenames = [ f['filename'] for f in files ]
85
86
86 check_call(['git', 'add'] + filenames)
87 check_call(['git', 'add'] + filenames)
87
88
88 check_call(['git', 'commit', '-m', msg])
89 check_call(['git', 'commit', '-m', msg])
89
90
90 print("PR #%i applied, with msg:" % num)
91 print("PR #%i applied, with msg:" % num)
91 print()
92 print()
92 print(msg)
93 print(msg)
93 print()
94 print()
94
95
95 if branch != current_branch:
96 if branch != current_branch:
96 check_call(['git', 'checkout', current_branch])
97 check_call(['git', 'checkout', current_branch])
97
98
98 return 0
99 return 0
99
100
100 backport_re = re.compile(r"[Bb]ackport.*?(\d+)")
101 backport_re = re.compile(r"[Bb]ackport.*?(\d+)")
101
102
102 def already_backported(branch, since_tag=None):
103 def already_backported(branch, since_tag=None):
103 """return set of PRs that have been backported already"""
104 """return set of PRs that have been backported already"""
104 if since_tag is None:
105 if since_tag is None:
105 since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip()
106 since_tag = check_output(['git','describe', branch, '--abbrev=0']).decode('utf8').strip()
106 cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline']
107 cmd = ['git', 'log', '%s..%s' % (since_tag, branch), '--oneline']
107 lines = check_output(cmd).decode('utf8')
108 lines = check_output(cmd).decode('utf8')
108 return set(int(num) for num in backport_re.findall(lines))
109 return set(int(num) for num in backport_re.findall(lines))
109
110
110 def should_backport(labels):
111 def should_backport(labels=None, milestone=None):
111 """return set of PRs marked for backport"""
112 """return set of PRs marked for backport"""
112 issues = get_issues_list("ipython/ipython",
113 if labels is None and milestone is None:
113 labels=labels,
114 raise ValueError("Specify one of labels or milestone.")
114 state='closed',
115 elif labels is not None and milestone is not None:
115 auth=True,
116 raise ValueError("Specify only one of labels or milestone.")
116 )
117 if labels is not None:
118 issues = get_issues_list("ipython/ipython",
119 labels=labels,
120 state='closed',
121 auth=True,
122 )
123 else:
124 milestone_id = get_milestone_id("ipython/ipython", milestone,
125 auth=True)
126 issues = get_issues_list("ipython/ipython",
127 milestone=milestone_id,
128 state='closed',
129 auth=True,
130 )
131
117 should_backport = set()
132 should_backport = set()
118 for issue in issues:
133 for issue in issues:
119 if not is_pull_request(issue):
134 if not is_pull_request(issue):
120 continue
135 continue
121 pr = get_pull_request("ipython/ipython", issue['number'], auth=True)
136 pr = get_pull_request("ipython/ipython", issue['number'],
137 auth=True)
122 if not pr['merged']:
138 if not pr['merged']:
123 print ("Marked PR closed without merge: %i" % pr['number'])
139 print ("Marked PR closed without merge: %i" % pr['number'])
124 continue
140 continue
125 should_backport.add(pr['number'])
141 should_backport.add(pr['number'])
126 return should_backport
142 return should_backport
127
143
128 if __name__ == '__main__':
144 if __name__ == '__main__':
129
145
130 if len(sys.argv) < 2:
146 if len(sys.argv) < 2:
131 print(__doc__)
147 print(__doc__)
132 sys.exit(1)
148 sys.exit(1)
133
149
134 if len(sys.argv) < 3:
150 if len(sys.argv) < 3:
135 branch = sys.argv[1]
151 branch = sys.argv[1]
136 already = already_backported(branch)
152 already = already_backported(branch)
137 should = should_backport("backport-1.2")
153 should = should_backport("backport-1.2")
138 print ("The following PRs should be backported:")
154 print ("The following PRs should be backported:")
139 for pr in should.difference(already):
155 for pr in should.difference(already):
140 print (pr)
156 print (pr)
141 sys.exit(0)
157 sys.exit(0)
142
158
143 sys.exit(backport_pr(sys.argv[1], int(sys.argv[2])))
159 sys.exit(backport_pr(sys.argv[1], int(sys.argv[2])))
@@ -1,259 +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=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):
160 url = "https://api.github.com/repos/{project}/milestones".format(project=project)
161 if auth:
162 headers = make_auth_header()
163 else:
164 headers = None
165 pages = get_paged_request(url, headers=headers, **params)
166 return pages
167
168 def get_milestone_id(project, milestone, auth=False, **params):
169 pages = get_milestones(project, auth=auth, **params)
170 for page in pages:
171 if page['title'] == milestone:
172 return page['number']
173 else:
174 raise ValueError("milestone %s not found" % milestone)
175
159 def is_pull_request(issue):
176 def is_pull_request(issue):
160 """Return True if the given issue is a pull request."""
177 """Return True if the given issue is a pull request."""
161 return bool(issue.get('pull_request', {}).get('html_url', None))
178 return bool(issue.get('pull_request', {}).get('html_url', None))
162
179
163 # encode_multipart_formdata is from urllib3.filepost
180 # encode_multipart_formdata is from urllib3.filepost
164 # 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
165
182
166 def iter_fields(fields):
183 def iter_fields(fields):
167 fields = fields.copy()
184 fields = fields.copy()
168 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
185 for key in ('key', 'acl', 'Filename', 'success_action_status', 'AWSAccessKeyId',
169 'Policy', 'Signature', 'Content-Type', 'file'):
186 'Policy', 'Signature', 'Content-Type', 'file'):
170 yield (key, fields.pop(key))
187 yield (key, fields.pop(key))
171 for (k,v) in fields.items():
188 for (k,v) in fields.items():
172 yield k,v
189 yield k,v
173
190
174 def encode_multipart_formdata(fields, boundary=None):
191 def encode_multipart_formdata(fields, boundary=None):
175 """
192 """
176 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.
177
194
178 :param fields:
195 :param fields:
179 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
180 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
181 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
182 is treated as the filename of the form-data section.
199 is treated as the filename of the form-data section.
183
200
184 Field names and filenames must be unicode.
201 Field names and filenames must be unicode.
185
202
186 :param boundary:
203 :param boundary:
187 If not specified, then a random boundary will be generated using
204 If not specified, then a random boundary will be generated using
188 :func:`mimetools.choose_boundary`.
205 :func:`mimetools.choose_boundary`.
189 """
206 """
190 # copy requests imports in here:
207 # copy requests imports in here:
191 from io import BytesIO
208 from io import BytesIO
192 from requests.packages.urllib3.filepost import (
209 from requests.packages.urllib3.filepost import (
193 choose_boundary, six, writer, b, get_content_type
210 choose_boundary, six, writer, b, get_content_type
194 )
211 )
195 body = BytesIO()
212 body = BytesIO()
196 if boundary is None:
213 if boundary is None:
197 boundary = choose_boundary()
214 boundary = choose_boundary()
198
215
199 for fieldname, value in iter_fields(fields):
216 for fieldname, value in iter_fields(fields):
200 body.write(b('--%s\r\n' % (boundary)))
217 body.write(b('--%s\r\n' % (boundary)))
201
218
202 if isinstance(value, tuple):
219 if isinstance(value, tuple):
203 filename, data = value
220 filename, data = value
204 writer(body).write('Content-Disposition: form-data; name="%s"; '
221 writer(body).write('Content-Disposition: form-data; name="%s"; '
205 'filename="%s"\r\n' % (fieldname, filename))
222 'filename="%s"\r\n' % (fieldname, filename))
206 body.write(b('Content-Type: %s\r\n\r\n' %
223 body.write(b('Content-Type: %s\r\n\r\n' %
207 (get_content_type(filename))))
224 (get_content_type(filename))))
208 else:
225 else:
209 data = value
226 data = value
210 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
227 writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
211 % (fieldname))
228 % (fieldname))
212 body.write(b'Content-Type: text/plain\r\n\r\n')
229 body.write(b'Content-Type: text/plain\r\n\r\n')
213
230
214 if isinstance(data, int):
231 if isinstance(data, int):
215 data = str(data) # Backwards compatibility
232 data = str(data) # Backwards compatibility
216 if isinstance(data, six.text_type):
233 if isinstance(data, six.text_type):
217 writer(body).write(data)
234 writer(body).write(data)
218 else:
235 else:
219 body.write(data)
236 body.write(data)
220
237
221 body.write(b'\r\n')
238 body.write(b'\r\n')
222
239
223 body.write(b('--%s--\r\n' % (boundary)))
240 body.write(b('--%s--\r\n' % (boundary)))
224
241
225 content_type = b('multipart/form-data; boundary=%s' % boundary)
242 content_type = b('multipart/form-data; boundary=%s' % boundary)
226
243
227 return body.getvalue(), content_type
244 return body.getvalue(), content_type
228
245
229
246
230 def post_download(project, filename, name=None, description=""):
247 def post_download(project, filename, name=None, description=""):
231 """Upload a file to the GitHub downloads area"""
248 """Upload a file to the GitHub downloads area"""
232 if name is None:
249 if name is None:
233 name = os.path.basename(filename)
250 name = os.path.basename(filename)
234 with open(filename, 'rb') as f:
251 with open(filename, 'rb') as f:
235 filedata = f.read()
252 filedata = f.read()
236
253
237 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
254 url = "https://api.github.com/repos/{project}/downloads".format(project=project)
238
255
239 payload = json.dumps(dict(name=name, size=len(filedata),
256 payload = json.dumps(dict(name=name, size=len(filedata),
240 description=description))
257 description=description))
241 response = requests.post(url, data=payload, headers=make_auth_header())
258 response = requests.post(url, data=payload, headers=make_auth_header())
242 response.raise_for_status()
259 response.raise_for_status()
243 reply = json.loads(response.content)
260 reply = json.loads(response.content)
244 s3_url = reply['s3_url']
261 s3_url = reply['s3_url']
245
262
246 fields = dict(
263 fields = dict(
247 key=reply['path'],
264 key=reply['path'],
248 acl=reply['acl'],
265 acl=reply['acl'],
249 success_action_status=201,
266 success_action_status=201,
250 Filename=reply['name'],
267 Filename=reply['name'],
251 AWSAccessKeyId=reply['accesskeyid'],
268 AWSAccessKeyId=reply['accesskeyid'],
252 Policy=reply['policy'],
269 Policy=reply['policy'],
253 Signature=reply['signature'],
270 Signature=reply['signature'],
254 file=(reply['name'], filedata),
271 file=(reply['name'], filedata),
255 )
272 )
256 fields['Content-Type'] = reply['mime_type']
273 fields['Content-Type'] = reply['mime_type']
257 data, content_type = encode_multipart_formdata(fields)
274 data, content_type = encode_multipart_formdata(fields)
258 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})
259 return s3r
276 return s3r
General Comments 0
You need to be logged in to leave comments. Login now