Merge lp:~brian.curtin/ubuntuone-windows-installer/patching into lp:ubuntuone-windows-installer

Proposed by Brian Curtin
Status: Merged
Approved by: Brian Curtin
Approved revision: 174
Merged at revision: 171
Proposed branch: lp:~brian.curtin/ubuntuone-windows-installer/patching
Merge into: lp:ubuntuone-windows-installer
Prerequisite: lp:~brian.curtin/ubuntuone-windows-installer/no-u1to-deps
Diff against target: 756 lines (+698/-7)
4 files modified
scripts/devsetup/buildout.cfg (+33/-7)
scripts/devsetup/patches/keyring/pykeyring-delete-password.diff (+517/-0)
scripts/devsetup/patches/twisted/5726.diff (+91/-0)
scripts/devsetup/patches/twisted/6462.diff (+57/-0)
To merge this branch: bzr merge lp:~brian.curtin/ubuntuone-windows-installer/patching
Reviewer Review Type Date Requested Status
Mike McCracken (community) Approve
dobey (community) Approve
Review via email: mp+162647@code.launchpad.net

Commit message

- Use collective.recipe.patch to more consistently setup buildout environments with dependencies we need to patch.

Description of the change

Using the collective.recipe.patch buildout recipe, we can take the unmodified versions of our dependencies and patch them when the buildout environment is setup. This change applies two patches to Twisted and one to keyring until versions are released which include these changes.

Note that the default operation of using the included patch.py library does not work for these diffs for whatever reason. It was easier to just set the patch-binary manually to be the "patch" application found on the path. On Windows, this requires that you have some sort of patch library installed, probably via gnuwin32 utils.

I have an outstanding pull request to collective.recipe.patch due to how it uses the close_fds argument to subprocess.Popen, which is incompatible on Windows. If the pull request is not accepted relatively soon, I will probably have to upload a fork to PyPI.

To post a comment you must log in.
Revision history for this message
Mike McCracken (mikemc) wrote :

when testing this, I get this:

Download error on http://keyring-python.org/: [Errno 8] nodename nor servname provided, or not known -- Some packages may not be found!
Download error on http://home.python-keyring.org/: [Errno 8] nodename nor servname provided, or not known -- Some packages may not be found!
Download error on http://home.python-keyring.org/: [Errno 8] nodename nor servname provided, or not known -- Some packages may not be found!
Download error on http://home.python-keyring.org/: [Errno 8] nodename nor servname provided, or not known -- Some packages may not be found!
Getting distribution for 'keyring==0.7'.

not sure where that URL is coming from, but it definitely doesn't exist.
do I have an out of date setuptools or distribute or something?

review: Needs Information
Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Mike McCracken (mikemc) wrote :

Sorry for the delay, this worked for me yesterday

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'scripts/devsetup/buildout.cfg'
2--- scripts/devsetup/buildout.cfg 2013-05-06 19:02:26 +0000
3+++ scripts/devsetup/buildout.cfg 2013-05-06 19:02:26 +0000
4@@ -26,8 +26,6 @@
5 find-links =
6 https://github.com/ghtdak/qtreactor/tarball/master#egg=qt4reactor
7 http://launchpad.net/python-distutils-extra/trunk/2.31/+download/python-distutils-extra-2.31.tar.gz#egg=python-distutils-extra
8-# Mandel's patched keyring, already built
9- http://www.briancurtin.com/canonical/deps/keyring-0.7-py2.7.egg#egg=keyring
10 # Pycrypto already built using python -c "import setuptools; execfile('setup.py')" bdist_egg
11 http://www.briancurtin.com/canonical/deps/pycrypto-2.6-py2.7-win32.egg#egg=pycrypto
12 # Py2exe already build using python -c "import setuptools; execfile('setup.py')" bdist_egg
13@@ -38,9 +36,6 @@
14 # http://sourceforge.net/tracker/index.php?func=detail&aid=3036368&group_id=115265&atid=692940
15 http://www.briancurtin.com/canonical/deps/comtypes-0.6.2.zip#egg=comtypes
16 http://launchpad.net/configglue/trunk/1.0/+download/configglue-1.0.tar.gz#egg=configglue
17-# This Twisted build includes alecu's patch which has not yet been accepted
18-# http://www.twistedmatrix.com/trac/ticket/5726
19- http://www.briancurtin.com/canonical/deps/Twisted-12.3.0-py2.7-win32.egg#egg=twisted
20 http://people.canonical.com/~dobey/pyflakes-0.5.0-2ubuntu2.bo2.tar.gz#egg=pyflakes
21 http://u1.to/~mikemc/g/pyobjc_core-2.5.0b1-py2.7-macosx-10.6-x86_64.egg#egg=pyobjc-core-2.5.0b1
22 http://u1.to/~mikemc/7/pyobjc_framework_Cocoa-2.5.0b1-py2.7-macosx-10.6-x86_64.egg#egg=pyobjc-framework-Cocoa-2.5.0b1
23@@ -78,10 +73,10 @@
24 recipe = z3c.recipe.scripts
25 eggs =
26 ${scripty:eggs}
27- twisted
28+ ${patched-twisted:egg}
29+ ${patched-keyring:egg}
30 qt4reactor
31 python-distutils-extra
32- keyring
33 pycrypto
34 pyOpenSSL
35 pil
36@@ -110,6 +105,37 @@
37 ${buildout:parts-directory}/ubuntuone-dev-tools
38 ${buildout:parts-directory}/dirspec
39
40+[twisted]
41+recipe = z3c.recipe.scripts
42+eggs =
43+ twisted
44+versions = versions
45+interpreter = python
46+
47+[keyring]
48+recipe = z3c.recipe.scripts
49+eggs =
50+ keyring
51+versions = versions
52+interpreter = python
53+
54+[patched-twisted]
55+recipe = collective.recipe.patch
56+patch-binary = patch
57+egg = ${twisted:eggs}
58+# Patches for http://twistedmatrix.com/trac/ticket/6462
59+# and http://twistedmatrix.com/trac/ticket/5726
60+patches = patches/twisted/6462.diff
61+ patches/twisted/5726.diff
62+
63+[patched-keyring]
64+recipe = collective.recipe.patch
65+patch-binary = patch
66+egg = ${keyring:eggs}
67+# Patch generated between 0.7 and mandel's branch
68+# https://bitbucket.org/mandel/pykeyring-delete-password
69+patches = patches/keyring/pykeyring-delete-password.diff
70+
71 # Install various scripts. These are all separate sections because the
72 # initialization and arguments values apply to every entry point you
73 # declare in a section, and these four are incompatible. Only pyflakes
74
75=== added directory 'scripts/devsetup/patches'
76=== added directory 'scripts/devsetup/patches/keyring'
77=== added file 'scripts/devsetup/patches/keyring/pykeyring-delete-password.diff'
78--- scripts/devsetup/patches/keyring/pykeyring-delete-password.diff 1970-01-01 00:00:00 +0000
79+++ scripts/devsetup/patches/keyring/pykeyring-delete-password.diff 2013-05-06 19:02:26 +0000
80@@ -0,0 +1,517 @@
81+--- keyring/__init__.py 2013-04-16 10:04:36.000000000 -0500
82++++ keyring/__init__.py 2013-04-12 09:26:11.848796300 -0500
83+@@ -6,5 +6,6 @@
84+ import logging
85+ logger = logging.getLogger('keyring')
86+
87+-from keyring.core import set_keyring, get_keyring, set_password, get_password
88++from core import (set_keyring, get_keyring, set_password, get_password,
89++ delete_password)
90+ from keyring.getpassbackend import get_password as get_pass_get_password
91+--- keyring/backend.py 2013-04-16 10:04:36.000000000 -0500
92++++ keyring/backend.py 2013-04-12 09:26:11.877792300 -0500
93+@@ -8,7 +8,6 @@
94+ import os
95+ import sys
96+ import ConfigParser
97+-import base64
98+
99+ from keyring.util.escape import escape as escape_for_ini
100+ from keyring.util import properties
101+@@ -36,10 +35,17 @@
102+ _BLOCK_SIZE = 32
103+ _PADDING = '0'
104+
105++
106+ class PasswordSetError(Exception):
107+ """Raised when the password can't be set.
108+ """
109+
110++
111++class PasswordDeleteError(Exception):
112++ """Raised when the password can't be deleted.
113++ """
114++
115++
116+ class KeyringBackend(object):
117+ """The abstract base class of the keyring, every backend must implement
118+ this interface.
119+@@ -67,6 +73,13 @@
120+ """
121+ raise PasswordSetError("reason")
122+
123++ @abstractmethod
124++ def delete_password(self, service, username):
125++ """Delete the password for the username of the service.
126++ """
127++ raise PasswordDeleteError("reason")
128++
129++
130+ class _ExtensionKeyring(KeyringBackend):
131+ """_ExtensionKeyring is a adaptor class for the platform related keyring
132+ backends.
133+@@ -112,7 +125,16 @@
134+ try:
135+ self.keyring_impl.password_set(service, username, password)
136+ except OSError, e:
137+- raise PasswordSetError(e.message)
138++ raise PasswordSetError(e)
139++
140++ def delete_password(self, service, username):
141++ """Override the delete_password in KeyringBackend.
142++ """
143++ try:
144++ self.keyring_impl.password_delete(service, username)
145++ except OSError, e:
146++ raise PasswordDeleteError(e)
147++
148+
149+ class OSXKeychain(_ExtensionKeyring):
150+ """Mac OS X Keychain"""
151+@@ -127,6 +149,7 @@
152+ """
153+ return sys.platform == 'darwin'
154+
155++
156+ class GnomeKeyring(KeyringBackend):
157+ """Gnome Keyring"""
158+
159+@@ -174,8 +197,22 @@
160+ # The user pressed "Cancel" when prompted to unlock their keyring.
161+ raise PasswordSetError("cancelled by user")
162+
163++ def delete_password(self, service, username):
164++ """Delete the password for the username of the service.
165++ """
166++ try:
167++ items = gnomekeyring.find_network_password_sync(username, service)
168++ for current in items:
169++ gnomekeyring.item_delete_sync(current['keyring'],
170++ current['item_id'])
171++ except gnomekeyring.NoMatchError:
172++ raise PasswordDeleteError("can't found the password")
173++ except gnomekeyring.CancelledError:
174++ raise PasswordDeleteError("cancelled by user")
175++
176+ kwallet = None
177+
178++
179+ def open_kwallet(kwallet_module=None, qt_module=None):
180+
181+ # If we specified the kwallet_module and/or qt_module, surely we won't need
182+@@ -224,7 +261,7 @@
183+ """KDE KWallet"""
184+
185+ def supported(self):
186+- if kwallet_support and os.environ.has_key('KDE_SESSION_UID'):
187++ if kwallet_support and 'KDE_SESSION_UID' in os.environ:
188+ return 1
189+ elif kwallet_support:
190+ return 0
191+@@ -249,7 +286,16 @@
192+ """Set password for the username of the service
193+ """
194+ wallet = open_kwallet()
195+- wallet.writePassword(username+'@'+service, password)
196++ wallet.writePassword(username + '@' + service, password)
197++
198++ def delete_password(self, service, username):
199++ """Delete the password for the username of the service.
200++ """
201++ key = username + '@' + service
202++ if kwallet.keyDoesNotExist(kwallet.walletName(), 'Python', key):
203++ raise PasswordDeleteError("can't found the password")
204++ kwallet.removeEntry(key)
205++
206+
207+ class BasicFileKeyring(KeyringBackend):
208+ """BasicFileKeyring is a file-based implementation of keyring.
209+@@ -297,11 +343,11 @@
210+
211+ # fetch the password
212+ try:
213+- password_base64 = config.get(service, username).encode()
214++ password_base64 = config.get(service, username)
215+ # decode with base64
216+- password_encrypted = base64.decodestring(password_base64)
217++ password_encrypted = password_base64.decode("base64")
218+ # decrypted the password
219+- password = self.decrypt(password_encrypted).decode('utf-8')
220++ password = self.decrypt(password_encrypted)
221+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
222+ password = None
223+ return password
224+@@ -313,21 +359,35 @@
225+ username = escape_for_ini(username)
226+
227+ # encrypt the password
228+- password_encrypted = self.encrypt(password.encode('utf-8'))
229++ password_encrypted = self.encrypt(password)
230+ # load the password from the disk
231+ config = ConfigParser.RawConfigParser()
232+ if os.path.exists(self.file_path):
233+ config.read(self.file_path)
234+
235+ # encode with base64
236+- password_base64 = base64.encodestring(password_encrypted).decode()
237++ password_base64 = password_encrypted.encode("base64")
238+ # write the modification
239+ if not config.has_section(service):
240+ config.add_section(service)
241+ config.set(service, username, password_base64)
242+- config_file = open(self.file_path,'w')
243++ config_file = open(self.file_path, 'w')
244+ config.write(config_file)
245+
246++ def delete_password(self, service, username):
247++ """Delete the password for the username of the service.
248++ """
249++ service = escape_for_ini(service)
250++ config = ConfigParser.RawConfigParser()
251++ if os.path.exists(self.file_path):
252++ config.read(self.file_path)
253++ if not config.remove_section(service):
254++ raise PasswordDeleteError("can't found the password")
255++ # update the file
256++ config_file = open(self.file_path, 'w')
257++ config.write(config_file)
258++
259++
260+ class UncryptedFileKeyring(BasicFileKeyring):
261+ """Uncrypted File Keyring"""
262+
263+@@ -348,6 +408,7 @@
264+ """
265+ return 0
266+
267++
268+ class CryptedFileKeyring(BasicFileKeyring):
269+ """PyCrypto File Keyring"""
270+
271+@@ -377,7 +438,8 @@
272+ password = None
273+ while 1:
274+ if not password:
275+- password = self._getpass("Please set a password for your new keyring")
276++ password = self._getpass("Please set a password"
277++ " for your new keyring")
278+ password2 = self._getpass('Password (again): ')
279+ if password != password2:
280+ sys.stderr.write("Error: Your passwords didn't match\n")
281+@@ -404,7 +466,7 @@
282+ config.add_section(_KEYRING_SETTING)
283+ config.set(_KEYRING_SETTING, _CRYPTED_PASSWORD, self.crypted_password)
284+
285+- config_file = open(self.file_path,'w')
286++ config_file = open(self.file_path, 'w')
287+ config.write(config_file)
288+
289+ if config_file:
290+@@ -519,7 +581,9 @@
291+ def __init__(self):
292+ super(WinVaultKeyring, self).__init__()
293+ try:
294+- import pywintypes, win32cred
295++ import pywintypes
296++ import win32cred
297++
298+ self.win32cred = win32cred
299+ self.pywintypes = pywintypes
300+ except ImportError:
301+@@ -558,7 +622,7 @@
302+ TargetName=target,
303+ )
304+ except self.pywintypes.error, e:
305+- if e.winerror == 1168 and e.funcname == 'CredRead': # not found
306++ if e[:2] == (1168, 'CredRead'): # not found
307+ return None
308+ raise
309+ return res
310+@@ -583,6 +647,8 @@
311+ self.win32cred.CredWrite(credential, 0)
312+
313+ def delete_password(self, service, username):
314++ """Delete the password for the username of the service.
315++ """
316+ compound = self._compound_name(username, service)
317+ for target in service, compound:
318+ existing_pw = self._get_password(target)
319+@@ -590,11 +656,13 @@
320+ self._delete_password(target)
321+
322+ def _delete_password(self, target):
323++ """Call win32 api to delete the password."""
324+ self.win32cred.CredDelete(
325+ Type=self.win32cred.CRED_TYPE_GENERIC,
326+ TargetName=target,
327+ )
328+
329++
330+ class Win32CryptoRegistry(KeyringBackend):
331+ """Win32CryptoRegistry is a keyring which use Windows CryptAPI to encrypt
332+ the user's passwords and store them under registry keys
333+@@ -633,33 +701,47 @@
334+ hkey = OpenKey(HKEY_CURRENT_USER, key)
335+ password_base64 = QueryValueEx(hkey, username)[0]
336+ # decode with base64
337+- password_encrypted = base64.encodestring(password_base64)
338++ password_encrypted = password_base64.decode("base64")
339+ # decrypted the password
340+ password = self.crypt_handler.decrypt(password_encrypted)
341+ except EnvironmentError:
342+ password = None
343+ return password
344+
345+-
346+ def set_password(self, service, username, password):
347+ """Write the password to the registry
348+ """
349+ # encrypt the password
350+ password_encrypted = self.crypt_handler.encrypt(password)
351+ # encode with base64
352+- password_base64 = base64.encodestring(password_encrypted)
353++ password_base64 = password_encrypted.encode("base64")
354+
355+ # store the password
356+ from _winreg import HKEY_CURRENT_USER, CreateKey, SetValueEx, REG_SZ
357+ hkey = CreateKey(HKEY_CURRENT_USER, r'Software\%s\Keyring' % service)
358+ SetValueEx(hkey, username, 0, REG_SZ, password_base64)
359+
360++ def delete_password(self, service, username):
361++ """Delete the password for the username of the service.
362++ """
363++ from _winreg import (KEY_ALL_ACCESS, HKEY_CURRENT_USER, DeleteValue,
364++ OpenKey)
365++ try:
366++ key = r'Software\%s\Keyring' % service
367++ hkey = OpenKey(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS)
368++ DeleteValue(hkey, username)
369++ except WindowsError, e:
370++ raise PasswordDeleteError(e)
371++
372++
373+ def select_windows_backend():
374+ if os.name != 'nt':
375+ return None
376+ major, minor, build, platform, text = sys.getwindowsversion()
377+ try:
378+- import pywintypes, win32cred
379++ import pywintypes
380++ import win32cred
381++
382+ if (major, minor) >= (5, 1):
383+ # recommend for windows xp+
384+ return 'cred'
385+@@ -683,14 +765,14 @@
386+
387+ _all_keyring = None
388+
389++
390+ def get_all_keyring():
391+ """Return the list of all keyrings in the lib
392+ """
393+ global _all_keyring
394+ if _all_keyring is None:
395+- _all_keyring = [ OSXKeychain(), GnomeKeyring(), KDEKWallet(),
396+- CryptedFileKeyring(), UncryptedFileKeyring(),
397+- Win32CryptoKeyring(), Win32CryptoRegistry(),
398+- WinVaultKeyring()]
399++ _all_keyring = [OSXKeychain(), GnomeKeyring(), KDEKWallet(),
400++ CryptedFileKeyring(), UncryptedFileKeyring(),
401++ Win32CryptoKeyring(), Win32CryptoRegistry(),
402++ WinVaultKeyring()]
403+ return _all_keyring
404+-
405+--- keyring/cli.py 2013-04-16 10:04:36.000000000 -0500
406++++ keyring/cli.py 2013-04-12 09:26:11.906770400 -0500
407+@@ -11,7 +11,8 @@
408+
409+ class CommandLineTool(object):
410+ def __init__(self):
411+- self.parser = OptionParser(usage="%prog [get|set] SERVICE USERNAME")
412++ self.parser = OptionParser(
413++ usage="%prog [get|set|del] SERVICE USERNAME")
414+ self.parser.add_option("-p", "--keyring-path",
415+ dest="keyring_path", default=None,
416+ help="Path to the keyring backend")
417+@@ -45,7 +46,6 @@
418+ # So, we play on the safe side, and catch everything.
419+ self.parser.error("Unable to load specified keyring: %s" % e)
420+
421+-
422+ if kind == 'get':
423+ password = keyring.get_password(service, username)
424+ if password is None:
425+@@ -60,8 +60,14 @@
426+ keyring.set_password(service, username, password)
427+ return 0
428+
429++ elif kid == 'del':
430++ password = input_password("Deleting password for '%s' in '%s': " %
431++ (username, service))
432++ keyring.delete_password(service, username)
433++ return 0
434++
435+ else:
436+- self.parser.error("You can only 'get' or 'set' a password.")
437++ self.parser.error("You can only 'get', 'del' or 'set' a password.")
438+ pass
439+
440+ def input_password(self, prompt):
441+@@ -72,7 +78,6 @@
442+
443+ return getpass.getpass(prompt)
444+
445+-
446+ def output_password(self, password):
447+ """Output the password to the user.
448+
449+@@ -85,7 +90,6 @@
450+ def main(argv=None):
451+ """Main command line interface."""
452+
453+-
454+ if argv is None:
455+ argv = sys.argv[1:]
456+
457+--- keyring/core.py 2013-04-16 10:04:36.000000000 -0500
458++++ keyring/core.py 2013-04-12 09:26:11.937796900 -0500
459+@@ -4,16 +4,14 @@
460+ Created by Kang Zhang on 2009-07-09
461+ """
462+ import os
463+-try:
464+- import configparser as config_parser
465+-except ImportError:
466+- import ConfigParser as config_parser
467++import ConfigParser
468+ import imp
469+ import sys
470+
471+ from keyring import logger
472+ from keyring import backend
473+
474++
475+ def set_keyring(keyring):
476+ """Set current keyring backend.
477+ """
478+@@ -23,21 +21,31 @@
479+ else:
480+ raise TypeError("The keyring must be a subclass of KeyringBackend")
481+
482++
483+ def get_keyring():
484+ """Get current keyring backend.
485+ """
486+ return _keyring_backend
487+
488++
489+ def get_password(service_name, username):
490+- """Get password from the specified service
491++ """Get password from the specified service.
492+ """
493+ return _keyring_backend.get_password(service_name, username)
494+
495++
496+ def set_password(service_name, username, password):
497+- """Set password for the user in the specified service
498++ """Set password for the user in the specified service.
499+ """
500+ _keyring_backend.set_password(service_name, username, password)
501+
502++
503++def delete_password(service_name, username):
504++ """Delete the password for the user in the specified service.
505++ """
506++ _keyring_backend.delete_password(service_name, username)
507++
508++
509+ def init_backend():
510+ """Load a keyring from a config file or for the default platform.
511+
512+@@ -52,7 +60,7 @@
513+
514+ keyrings = backend.get_all_keyring()
515+ # rank according to the supported result
516+- keyrings.sort(key = lambda x: -x.supported())
517++ keyrings.sort(lambda x, y: y.supported() - x.supported())
518+ # get the most recommended one
519+ keyring = keyrings[0]
520+
521+@@ -95,10 +103,10 @@
522+ # avoid import the imported modules
523+ module = sys.modules[keyring_name[:keyring_name.rfind('.')]]
524+ except KeyError:
525+- module = load_module(keyring_name, sys.path+[keyring_path])
526++ module = load_module(keyring_name, sys.path + [keyring_path])
527+
528+ keyring_class = keyring_name.split('.')[-1].strip()
529+- keyring_temp = getattr(module, keyring_class)()
530++ exec "keyring_temp = module." + keyring_class + "() " in locals()
531+
532+ return keyring_temp
533+
534+@@ -123,7 +131,7 @@
535+ break
536+
537+ if os.path.exists(keyring_cfg):
538+- config = config_parser.RawConfigParser()
539++ config = ConfigParser.RawConfigParser()
540+ config.read(keyring_cfg)
541+ # load the keyring-path option
542+ try:
543+@@ -131,7 +139,7 @@
544+ keyring_path = config.get("backend", "keyring-path").strip()
545+ else:
546+ keyring_path = None
547+- except config_parser.NoOptionError:
548++ except ConfigParser.NoOptionError:
549+ keyring_path = None
550+
551+ # load the keyring class name, and then load this keyring
552+@@ -139,10 +147,10 @@
553+ if config.has_section("backend"):
554+ keyring_name = config.get("backend", "default-keyring").strip()
555+ else:
556+- raise config_parser.NoOptionError('backend', 'default-keyring')
557++ raise ConfigParser.NoOptionError('backend', 'default-keyring')
558+
559+ keyring = load_keyring(keyring_path, keyring_name)
560+- except (config_parser.NoOptionError, ImportError):
561++ except (ConfigParser.NoOptionError, ImportError):
562+ logger.warning("Keyring config file contains incorrect values.\n" +
563+ "Config file: %s" % keyring_cfg)
564+
565+--- keyring/getpassbackend.py 2013-04-16 10:04:36.000000000 -0500
566++++ keyring/getpassbackend.py 2013-04-12 09:26:11.962790900 -0500
567+@@ -4,10 +4,10 @@
568+
569+ import keyring.core
570+
571++
572+ def get_password(prompt='Password: ', stream=None,
573+ service_name='Python',
574+ username=None):
575+ if username is None:
576+ username = getpass.getuser()
577+ return keyring.core.get_password(service_name, username)
578+-
579+--- keyring/http.py 2013-04-16 10:04:36.000000000 -0500
580++++ keyring/http.py 2013-04-12 09:26:11.989799600 -0500
581+@@ -7,7 +7,7 @@
582+ handlers = [urllib2.HTTPBasicAuthHandler(PasswordMgr())]
583+ urllib2.install_opener(handlers)
584+ urllib2.urlopen(...)
585+-
586++
587+ This will prompt for a password if one is required and isn't already
588+ in the keyring. Then, it adds it to the keyring for subsequent use.
589+ """
590+@@ -15,6 +15,7 @@
591+ import keyring
592+ import getpass
593+
594++
595+ class PasswordMgr(object):
596+ def get_username(self, realm, authuri):
597+ return getpass.getuser()
598
599=== added directory 'scripts/devsetup/patches/twisted'
600=== added file 'scripts/devsetup/patches/twisted/5726.diff'
601--- scripts/devsetup/patches/twisted/5726.diff 1970-01-01 00:00:00 +0000
602+++ scripts/devsetup/patches/twisted/5726.diff 2013-05-06 19:02:26 +0000
603@@ -0,0 +1,91 @@
604+=== modified file 'twisted/internet/_dumbwin32proc.py'
605+--- twisted/internet/_dumbwin32proc.py 2011-10-02 01:01:47 +0000
606++++ twisted/internet/_dumbwin32proc.py 2012-07-16 21:39:49 +0000
607+@@ -172,8 +172,9 @@
608+ cmdline = quoteArguments(args)
609+ # TODO: error detection here. See #2787 and #4184.
610+ def doCreate():
611++ flags = win32con.CREATE_NO_WINDOW
612+ self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
613+- command, cmdline, None, None, 1, 0, env, path, StartupInfo)
614++ command, cmdline, None, None, 1, flags, env, path, StartupInfo)
615+ try:
616+ try:
617+ doCreate()
618+
619+=== modified file 'twisted/test/test_process.py'
620+--- twisted/test/test_process.py 2012-07-08 18:39:59 +0000
621++++ twisted/test/test_process.py 2012-07-19 05:27:43 +0000
622+@@ -27,7 +27,7 @@
623+ from twisted.python.log import msg
624+ from twisted.internet import reactor, protocol, error, interfaces, defer
625+ from twisted.trial import unittest
626+-from twisted.python import util, runtime, procutils
627++from twisted.python import filepath, util, runtime, procutils
628+ from twisted.python.compat import set
629+
630+
631+@@ -2350,6 +2350,48 @@
632+
633+
634+
635++class Win32CreateProcessFlagsTest(unittest.TestCase):
636++ """
637++ Check the flags passed to CreateProcess.
638++ """
639++
640++ @defer.inlineCallbacks
641++ def test_flags(self):
642++ """
643++ Verify that the flags passed to win32process.CreateProcess() prevent a
644++ new console window from being created. See bug #5726 for a script to
645++ test this interactively.
646++ """
647++ from twisted.internet import _dumbwin32proc
648++ flags = []
649++ real_CreateProcess = _dumbwin32proc.win32process.CreateProcess
650++
651++ def fake_createprocess(_appName, _commandLine, _processAttributes,
652++ _threadAttributes, _bInheritHandles, creationFlags,
653++ _newEnvironment, _currentDirectory, startupinfo):
654++ """Store the creationFlags for later comparing."""
655++ flags.append(creationFlags)
656++ return real_CreateProcess(_appName, _commandLine,
657++ _processAttributes, _threadAttributes,
658++ _bInheritHandles, creationFlags, _newEnvironment,
659++ _currentDirectory, startupinfo)
660++
661++ self.patch(_dumbwin32proc.win32process, "CreateProcess",
662++ fake_createprocess)
663++ exe = sys.executable
664++ scriptPath = filepath.FilePath(__file__).sibling("process_cmdline.py")
665++
666++ d = defer.Deferred()
667++ processProto = TrivialProcessProtocol(d)
668++ comspec = str(os.environ["COMSPEC"])
669++ cmd = [comspec, "/c", exe, scriptPath.path]
670++ _dumbwin32proc.Process(reactor, processProto, None, cmd, {}, None)
671++ yield d
672++ self.assertEqual(flags,
673++ [_dumbwin32proc.win32process.CREATE_NO_WINDOW])
674++
675++
676++
677+ class UtilTestCase(unittest.TestCase):
678+ """
679+ Tests for process-related helper functions (currently only
680+@@ -2553,6 +2595,7 @@
681+ Win32ProcessTestCase.skip = skipMessage
682+ TestTwoProcessesNonPosix.skip = skipMessage
683+ Dumbwin32procPidTest.skip = skipMessage
684++ Win32CreateProcessFlagsTest.skip = skipMessage
685+ Win32UnicodeEnvironmentTest.skip = skipMessage
686+
687+ if not interfaces.IReactorProcess(reactor, None):
688+
689+=== added file 'twisted/topfiles/5726.bugfix'
690+--- twisted/topfiles/5726.bugfix 1970-01-01 00:00:00 +0000
691++++ twisted/topfiles/5726.bugfix 2012-07-16 22:43:20 +0000
692+@@ -0,0 +1,1 @@
693++spawnProcess no longer opens an unwanted console on Windows
694+
695
696=== added file 'scripts/devsetup/patches/twisted/6462.diff'
697--- scripts/devsetup/patches/twisted/6462.diff 1970-01-01 00:00:00 +0000
698+++ scripts/devsetup/patches/twisted/6462.diff 2013-05-06 19:02:26 +0000
699@@ -0,0 +1,57 @@
700+Index: twisted/internet/_dumbwin32proc.py
701+===================================================================
702+--- twisted/internet/_dumbwin32proc.py (revision 38219)
703++++ twisted/internet/_dumbwin32proc.py (working copy)
704+@@ -7,6 +7,7 @@
705+ """
706+
707+ import os
708++import sys
709+
710+ # Win32 imports
711+ import win32api
712+@@ -184,8 +185,9 @@
713+ 'all must be unicode',):
714+ raise
715+ newenv = {}
716++ encoding = sys.getfilesystemencoding()
717+ for key, value in env.items():
718+- newenv[unicode(key)] = unicode(value)
719++ newenv[key.decode(encoding)] = value.decode(encoding)
720+ env = newenv
721+ doCreate()
722+ except pywintypes.error, pwte:
723+Index: twisted/test/test_process.py
724+===================================================================
725+--- twisted/test/test_process.py (revision 38219)
726++++ twisted/test/test_process.py (working copy)
727+@@ -2293,6 +2293,7 @@
728+ """
729+ goodKey = u'UNICODE'
730+ goodValue = u'UNICODE'
731++ encodedValue = u'jalape\xf1o'.encode("mbcs")
732+
733+ def test_encodableUnicodeEnvironment(self):
734+ """
735+@@ -2311,8 +2312,21 @@
736+ self.goodValue.encode('ascii'))
737+ return p.getResult().addCallback(gotEnvironment)
738+
739++ def test_decodableUnicodeEnvironment(self):
740++ """
741++ Test C{os.environ} (inherited by every subprocess on Windows) that
742++ contains an MBCS encoded byte string value."""
743++ os.environ[self.goodKey] = self.encodedValue
744++ self.addCleanup(operator.delitem, os.environ, self.goodKey)
745+
746++ p = GetEnvironmentDictionary.run(reactor, [], {})
747++ def gotEnvironment(environ):
748++ self.assertEqual(
749++ environ[self.goodKey.encode('ascii')],
750++ self.encodedValue)
751++ return p.getResult().addCallback(gotEnvironment)
752+
753++
754+ class Dumbwin32procPidTest(unittest.TestCase):
755+ """
756+ Simple test for the pid attribute of Process on win32.

Subscribers

People subscribed via source and target branches