forked from pymc-devs/pymc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
googlecode.py
425 lines (358 loc) · 14.5 KB
/
googlecode.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# Copyright 2007 Google Inc. All Rights Reserved.
#
# Licensed under the terms of the Apache Software License 2.0:
# http://www.apache.org/licenses/LICENSE-2.0
#
# Questions, comments, feature requests and patches are most welcome.
# Please direct all of these to the Google Code users group:
# http://groups.google.com/group/google-code-hosting
'''distutils command class for uploading to Google Code
Add this command to your setup.py script for automatic uploading of
source and Windows binary distributions. For example:
try:
from googlecode_distutils_upload import upload
except ImportError:
class upload(distutils.core.Command):
user_options = []
def __init__(self, *args, **kwargs):
sys.stderr.write("""\
error: Install this module in site-packages to upload:
http://support.googlecode.com/svn/trunk/scripts/googlecode_distutils_upload.py
""")
sys.exit(3)
setup(...,
cmdclass={'upload': upload},
)
'''
import distutils
import distutils.command.bdist_wininst
import os
import sys
SOURCE_LABELS = ['Type-Source']
WINDOWS_LABELS = ['OpSys-Windows', 'Type-Installer']
class upload2google(distutils.core.Command):
description = 'upload source or Windows distribution to Google Code'
user_options = [('src', None,
'upload source distribution'),
('windows', None,
'upload Windows distribution'),
('dist-dir=', 'd',
'directory to find distribution archive in'
' [default: dist]'),
('config-dir=', None,
'read svn auth data from DIR'
' ("none" means not to use svn auth data)'),
('user=', 'u',
'Google Code username'),
]
boolean_options = ['src', 'windows']
def initialize_options(self):
self.src = False
self.windows = False
self.dist_dir = None
self.config_dir = None
self.user = None
def finalize_options(self):
# Validate src and windows options.
if (not self.src and not self.windows) or (self.src and self.windows):
sys.stderr.write('error: Use exactly one of --src or --windows\n')
sys.exit(2)
# Get dist-dir default from sdist or bdist_wininst.
if self.src:
self.set_undefined_options('sdist', ('dist_dir', 'dist_dir'))
else:
self.set_undefined_options('bdist_wininst', ('dist_dir', 'dist_dir'))
# Do nothing for config-dir and user; upload_find_auth does the
# right thing when they're None.
def run(self):
name = self.distribution.get_name()
version = self.distribution.get_version()
if self.src:
# TODO(epg): sdist is more flexible with formats...
fn = os.path.join(self.dist_dir, self.distribution.get_fullname())
if sys.platform == 'win32':
fn += '.zip'
else:
fn += '.tar.gz'
summary = ' '.join([name, version, 'source distribution'])
labels = SOURCE_LABELS
else:
# Get filename from bdist_wininst.
bd = distutils.command.bdist_wininst.bdist_wininst(self.distribution)
bd.initialize_options()
bd.dist_dir = self.dist_dir
bd.finalize_options()
fn = bd.get_installer_filename(self.distribution.get_fullname())
summary = ' '.join([name, version, 'for Windows'])
labels = WINDOWS_LABELS
(status, reason,
file_url) = upload_find_auth(fn, name, summary,
labels, self.config_dir,
self.user)
if file_url is None:
sys.stderr.write('error: %s (%d)\n' % (reason, status))
sys.exit(2)
sys.stdout.write('Uploaded %s\n' % (file_url,))
### googlecode_upload.py ###
# Copyright 2006, 2007 Google Inc. All Rights Reserved.
# Author: [email protected] (David Anderson)
#
# Script for uploading files to a Google Code project.
#
# This is intended to be both a useful script for people who want to
# streamline project uploads and a reference implementation for
# uploading files to Google Code projects.
#
# To upload a file to Google Code, you need to provide a path to the
# file on your local machine, a small summary of what the file is, a
# project name, and a valid account that is a member or owner of that
# project. You can optionally provide a list of labels that apply to
# the file. The file will be uploaded under the same name that it has
# in your local filesystem (that is, the "basename" or last path
# component). Run the script with '--help' to get the exact syntax
# and available options.
#
# Note that the upload script requests that you enter your
# googlecode.com password. This is NOT your Gmail account password!
# This is the password you use on googlecode.com for committing to
# Subversion and uploading files. You can find your password by going
# to http://code.google.com/hosting/settings when logged in with your
# Gmail account. If you have already committed to your project's
# Subversion repository, the script will automatically retrieve your
# credentials from there (unless disabled, see the output of '--help'
# for details).
#
# If you are looking at this script as a reference for implementing
# your own Google Code file uploader, then you should take a look at
# the upload() function, which is the meat of the uploader. You
# basically need to build a multipart/form-data POST request with the
# right fields and send it to https://PROJECT.googlecode.com/files .
# Authenticate the request using HTTP Basic authentication, as is
# shown below.
#
# Licensed under the terms of the Apache Software License 2.0:
# http://www.apache.org/licenses/LICENSE-2.0
#
# Questions, comments, feature requests and patches are most welcome.
# Please direct all of these to the Google Code users group:
# http://groups.google.com/group/google-code-hosting
"""Google Code file uploader script.
"""
__author__ = '[email protected] (David Anderson)'
import httplib
import os.path
import optparse
import getpass
import base64
import sys
def get_svn_config_dir():
"""Return user's Subversion configuration directory."""
try:
from win32com.shell.shell import SHGetFolderPath
import win32com.shell.shellcon
except ImportError:
# If we can't import the win32api, just use ~; this is right on unix, and
# returns not entirely unreasonable results on Windows.
return os.path.expanduser('~/.subversion')
# We're on Windows with win32api; use APPDATA.
return os.path.join(SHGetFolderPath(0, win32com.shell.shellcon.CSIDL_APPDATA,
0, 0).encode('utf-8'),
'Subversion')
def get_svn_auth(project_name, config_dir):
"""Return (username, password) for project_name in config_dir."""
# Default to returning nothing.
result = (None, None)
try:
from svn.core import SVN_AUTH_CRED_SIMPLE, svn_config_read_auth_data
from svn.core import SubversionException
except ImportError:
return result
realm = ('<https://%s.googlecode.com:443> Google Code Subversion Repository'
% project_name)
# auth may be none even if no exception is raised, e.g. if config_dir does
# not exist, or exists but has no entry for realm.
try:
auth = svn_config_read_auth_data(SVN_AUTH_CRED_SIMPLE, realm, config_dir)
except SubversionException:
auth = None
if auth is not None:
try:
result = (auth['username'], auth['password'])
except KeyError:
# Missing the keys, so return nothing.
pass
return result
def upload(file, project_name, user_name, password, summary, labels=None):
"""Upload a file to a Google Code project's file server.
Args:
file: The local path to the file.
project_name: The name of your project on Google Code.
user_name: Your Google account name.
password: The googlecode.com password for your account.
Note that this is NOT your global Google Account password!
summary: A small description for the file.
labels: an optional list of label strings with which to tag the file.
Returns: a tuple:
http_status: 201 if the upload succeeded, something else if an
error occured.
http_reason: The human-readable string associated with http_status
file_url: If the upload succeeded, the URL of the file on Google
Code, None otherwise.
"""
# The login is the user part of [email protected]. If the login provided
# is in the full user@domain form, strip it down.
if '@' in user_name:
user_name = user_name[:user_name.index('@')]
form_fields = [('summary', summary)]
if labels is not None:
form_fields.extend([('label', l.strip()) for l in labels])
content_type, body = encode_upload_request(form_fields, file)
upload_host = '%s.googlecode.com' % project_name
upload_uri = '/files'
auth_token = base64.b64encode('%s:%s'% (user_name, password))
headers = {
'Authorization': 'Basic %s' % auth_token,
'User-Agent': 'Googlecode.com uploader v0.9.4',
'Content-Type': content_type,
}
server = httplib.HTTPSConnection(upload_host)
server.request('POST', upload_uri, body, headers)
resp = server.getresponse()
server.close()
if resp.status == 201:
location = resp.getheader('Location', None)
else:
location = None
return resp.status, resp.reason, location
def encode_upload_request(fields, file_path):
"""Encode the given fields and file into a multipart form body.
fields is a sequence of (name, value) pairs. file is the path of
the file to upload. The file will be uploaded to Google Code with
the same file name.
Returns: (content_type, body) ready for httplib.HTTP instance
"""
BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
CRLF = '\r\n'
body = []
# Add the metadata about the upload first
for key, value in fields:
body.extend(
['--' + BOUNDARY,
'Content-Disposition: form-data; name="%s"' % key,
'',
value,
])
# Now add the file itself
file_name = os.path.basename(file_path)
f = open(file_path, 'rb')
file_content = f.read()
f.close()
body.extend(
['--' + BOUNDARY,
'Content-Disposition: form-data; name="filename"; filename="%s"'
% file_name,
# The upload server determines the mime-type, no need to set it.
'Content-Type: application/octet-stream',
'',
file_content,
])
# Finalize the form body
body.extend(['--' + BOUNDARY + '--', ''])
return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
def upload_find_auth(file_path, project_name, summary, labels=None,
config_dir=None, user_name=None, tries=3):
"""Find credentials and upload a file to a Google Code project's file server.
file_path, project_name, summary, and labels are passed as-is to upload.
If config_dir is None, try get_svn_config_dir(); if it is 'none', skip
trying the Subversion configuration entirely. If user_name is not None, use
it for the first attempt; prompt for subsequent attempts.
Args:
file_path: The local path to the file.
project_name: The name of your project on Google Code.
summary: A small description for the file.
labels: an optional list of label strings with which to tag the file.
config_dir: Path to Subversion configuration directory, 'none', or None.
user_name: Your Google account name.
tries: How many attempts to make.
"""
if config_dir != 'none':
# Try to load username/password from svn config for first try.
if config_dir is None:
config_dir = get_svn_config_dir()
(svn_username, password) = get_svn_auth(project_name, config_dir)
if user_name is None:
# If username was not supplied by caller, use svn config.
user_name = svn_username
else:
# Just initialize password for the first try.
password = None
while tries > 0:
if user_name is None:
# Read username if not specified or loaded from svn config, or on
# subsequent tries.
sys.stdout.write('Please enter your googlecode.com username: ')
sys.stdout.flush()
user_name = sys.stdin.readline().rstrip()
if password is None:
# Read password if not loaded from svn config, or on subsequent tries.
print 'Please enter your googlecode.com password.'
print '** Note that this is NOT your Gmail account password! **'
print 'It is the password you use to access Subversion repositories,'
print 'and can be found here: http://code.google.com/hosting/settings'
password = getpass.getpass()
status, reason, url = upload(file_path, project_name, user_name, password,
summary, labels)
# Returns 403 Forbidden instead of 401 Unauthorized for bad
# credentials as of 2007-07-17.
if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
# Rest for another try.
user_name = password = None
tries = tries - 1
else:
# We're done.
break
return status, reason, url
#def main():
# parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
# '-p PROJECT [options] FILE')
# parser.add_option('--config-dir', dest='config_dir', metavar='DIR',
# help='read svn auth data from DIR'
# ' ("none" means not to use svn auth data)')
# parser.add_option('-s', '--summary', dest='summary',
# help='Short description of the file')
# parser.add_option('-p', '--project', dest='project',
# help='Google Code project name')
# parser.add_option('-u', '--user', dest='user',
# help='Your Google Code username')
# parser.add_option('-l', '--labels', dest='labels',
# help='An optional list of labels to attach to the file')
#
# options, args = parser.parse_args()
#
# if not options.summary:
# parser.error('File summary is missing.')
# elif not options.project:
# parser.error('Project name is missing.')
# elif len(args) < 1:
# parser.error('File to upload not provided.')
# elif len(args) > 1:
# parser.error('Only one file may be specified.')
#
# file_path = args[0]
#
# if options.labels:
# labels = options.labels.split(',')
# else:
# labels = None
#
# status, reason, url = upload_find_auth(file_path, options.project,
# options.summary, labels,
# options.config_dir, options.user)
# if url:
# print 'The file was uploaded successfully.'
# print 'URL: %s' % url
# return 0
# else:
# print 'An error occurred. Your file was not uploaded.'
# print 'Google Code upload server said: %s (%s)' % (reason, status)
# return 1