[Checkins] SVN: zope.password/trunk/ Support Py 3.3, add tox setup and manifest.

Stephen Richter cvs-admin at zope.org
Thu Feb 21 19:45:04 UTC 2013


Log message for revision 129588:
  Support Py 3.3, add tox setup and manifest.
  

Changed:
  _U  zope.password/trunk/
  U   zope.password/trunk/CHANGES.txt
  A   zope.password/trunk/MANIFEST.in
  U   zope.password/trunk/bootstrap.py
  U   zope.password/trunk/buildout.cfg
  U   zope.password/trunk/setup.py
  U   zope.password/trunk/src/zope/password/interfaces.py
  U   zope.password/trunk/src/zope/password/legacy.py
  U   zope.password/trunk/src/zope/password/password.py
  U   zope.password/trunk/src/zope/password/testing.py
  U   zope.password/trunk/src/zope/password/tests/test_password.py
  U   zope.password/trunk/src/zope/password/tests/test_zpasswd.py
  U   zope.password/trunk/src/zope/password/zpasswd.py
  A   zope.password/trunk/tox.ini

-=-

Property changes on: zope.password/trunk
___________________________________________________________________
Modified: svn:ignore
   - bin
build
dist
lib
develop-eggs
eggs
parts
.installed.cfg
coverage

   + .tox
bin
build
dist
lib
develop-eggs
eggs
parts
.installed.cfg
coverage


Modified: zope.password/trunk/CHANGES.txt
===================================================================
--- zope.password/trunk/CHANGES.txt	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/CHANGES.txt	2013-02-21 19:45:04 UTC (rev 129588)
@@ -5,13 +5,15 @@
 4.0.0 (unreleased)
 ------------------
 
+- Added support for Python 3.3
+
 - Replaced deprecated ``zope.interface.implements`` usage with equivalent
   ``zope.interface.implementer`` decorator.
 
 - Dropped support for Python 2.4 and 2.5.
 
-- Add a new IMatchingPasswordManager interface with a 'match' method, which
-  returns True if a given password hash was encdoded with the scheme
+- Add a new ``IMatchingPasswordManager`` interface with a 'match' method,
+  which returns True if a given password hash was encdoded with the scheme
   implemented by the specific manager. All managers in this package implement
   this interface.
 

Added: zope.password/trunk/MANIFEST.in
===================================================================
--- zope.password/trunk/MANIFEST.in	                        (rev 0)
+++ zope.password/trunk/MANIFEST.in	2013-02-21 19:45:04 UTC (rev 129588)
@@ -0,0 +1,9 @@
+include *.rst
+include *.txt
+include *.py
+include buildout.cfg
+include tox.ini
+
+recursive-include src *
+
+global-exclude *.pyc

Modified: zope.password/trunk/bootstrap.py
===================================================================
--- zope.password/trunk/bootstrap.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/bootstrap.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -18,33 +18,148 @@
 use the -c option to specify an alternate configuration file.
 """
 
-import os, shutil, sys, tempfile, urllib2
+import os, shutil, sys, tempfile
+from optparse import OptionParser
 
 tmpeggs = tempfile.mkdtemp()
 
-ez = {}
-exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
-                     ).read() in ez
-ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
 
-import pkg_resources
+Bootstraps a buildout-based project.
 
-cmd = 'from setuptools.command.easy_install import main; main()'
-if sys.platform == 'win32':
-    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
 
-ws = pkg_resources.working_set
-assert os.spawnle(
-    os.P_WAIT, sys.executable, sys.executable,
-    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
-    dict(os.environ,
-         PYTHONPATH=
-         ws.find(pkg_resources.Requirement.parse('setuptools')).location
-         ),
-    ) == 0
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
 
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", help="use a specific zc.buildout version")
+
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", "--config-file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+parser.add_option("-f", "--find-links",
+                   help=("Specify a URL to search for buildout releases"))
+
+
+options, args = parser.parse_args()
+
+######################################################################
+# load/install distribute
+
+to_reload = False
+try:
+    import pkg_resources, setuptools
+    if not hasattr(pkg_resources, '_distribute'):
+        to_reload = True
+        raise ImportError
+except ImportError:
+    ez = {}
+
+    try:
+        from urllib.request import urlopen
+    except ImportError:
+        from urllib2 import urlopen
+
+    exec(urlopen('http://python-distribute.org/distribute_setup.py').read(), ez)
+    setup_args = dict(to_dir=tmpeggs, download_delay=0, no_fake=True)
+    ez['use_setuptools'](**setup_args)
+
+    if to_reload:
+        reload(pkg_resources)
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+######################################################################
+# Install buildout
+
+ws  = pkg_resources.working_set
+
+cmd = [sys.executable, '-c',
+       'from setuptools.command.easy_install import main; main()',
+       '-mZqNxd', tmpeggs]
+
+find_links = os.environ.get(
+    'bootstrap-testing-find-links',
+    options.find_links or
+    ('http://downloads.buildout.org/'
+     if options.accept_buildout_test_releases else None)
+    )
+if find_links:
+    cmd.extend(['-f', find_links])
+
+distribute_path = ws.find(
+    pkg_resources.Requirement.parse('distribute')).location
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[distribute_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+import subprocess
+if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=distribute_path)) != 0:
+    raise Exception(
+        "Failed to execute command:\n%s",
+        repr(cmd)[1:-1])
+
+######################################################################
+# Import and run buildout
+
 ws.add_entry(tmpeggs)
-ws.require('zc.buildout')
+ws.require(requirement)
 import zc.buildout.buildout
-zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+
+if not [a for a in args if '=' not in a]:
+    args.append('bootstrap')
+
+# if -c was provided, we push it back into args for buildout' main function
+if options.config_file is not None:
+    args[0:0] = ['-c', options.config_file]
+
+zc.buildout.buildout.main(args)
 shutil.rmtree(tmpeggs)

Modified: zope.password/trunk/buildout.cfg
===================================================================
--- zope.password/trunk/buildout.cfg	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/buildout.cfg	2013-02-21 19:45:04 UTC (rev 129588)
@@ -20,7 +20,6 @@
 interpreter = python
 
 [zpasswd]
-recipe = z3c.recipe.dev:script
+recipe = zc.recipe.egg
 eggs = zope.password
-module = zope.password.zpasswd
-method = main
+scripts = zpasswd

Modified: zope.password/trunk/setup.py
===================================================================
--- zope.password/trunk/setup.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/setup.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -15,6 +15,20 @@
 """
 from setuptools import setup, find_packages
 
+def alltests():
+    import os
+    import sys
+    import unittest
+    # use the zope.testrunner machinery to find all the
+    # test suites we've put under ourselves
+    import zope.testrunner.find
+    import zope.testrunner.options
+    here = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))
+    args = sys.argv[:]
+    defaults = ["--test-path", here]
+    options = zope.testrunner.options.get_options(args, defaults)
+    suites = list(zope.testrunner.find.find_suites(options))
+    return unittest.TestSuite(suites)
 
 setup(name='zope.password',
       version='4.0.0dev',
@@ -37,6 +51,9 @@
           'Programming Language :: Python :: 2',
           'Programming Language :: Python :: 2.6',
           'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.3',
+          'Programming Language :: Python :: Implementation :: CPython',
           'Natural Language :: English',
           'Operating System :: OS Independent',
           'Topic :: Internet :: WWW/HTTP',
@@ -45,7 +62,7 @@
       packages=find_packages('src'),
       package_dir = {'': 'src'},
       extras_require=dict(vocabulary=['zope.schema'],
-                          test=['zope.schema'],
+                          test=['zope.schema', 'zope.testing'],
                           ),
       namespace_packages=['zope'],
       install_requires=['setuptools',
@@ -53,6 +70,16 @@
                         'zope.configuration',
                         'zope.interface',
                         ],
+      tests_require = [
+          'zope.schema',
+          'zope.testing',
+          'zope.testrunner',
+          ],
+      test_suite = '__main__.alltests',
       include_package_data = True,
       zip_safe = False,
+      entry_points="""
+      [console_scripts]
+      zpasswd = zope.password.zpasswd:main
+      """,
       )

Modified: zope.password/trunk/src/zope/password/interfaces.py
===================================================================
--- zope.password/trunk/src/zope/password/interfaces.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/interfaces.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -19,10 +19,14 @@
     """Password manager"""
 
     def encodePassword(password):
-        """Return encoded data for the given password"""
+        """Return encoded data for the given password
 
+        The encoded password is a bytes string.
+        """
+
     def checkPassword(encoded_password, password):
-        """Does the given encoded data coincide with the given password"""
+        """Does the given encoded data coincide with the given password
+        """
 
 class IMatchingPasswordManager(IPasswordManager):
     """Password manager with hash matching support"""
@@ -31,5 +35,5 @@
         """
         Returns True when the given data was encoded with the scheme
         implemented by this password manager.
-        
+
         """

Modified: zope.password/trunk/src/zope/password/legacy.py
===================================================================
--- zope.password/trunk/src/zope/password/legacy.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/legacy.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -14,7 +14,7 @@
 """Legacy password managers, using now-outdated, insecure methods for hashing
 """
 __docformat__ = 'restructuredtext'
-
+import sys
 from codecs import getencoder
 
 try:
@@ -29,16 +29,24 @@
 
 _encoder = getencoder("utf-8")
 
+PY2 = sys.version_info[0] == 2
 
+try:
+    unicode
+except NameError:
+    # Py3: Define unicode.
+    unicode = str
+
+
 if crypt is not None:
     @implementer(IMatchingPasswordManager)
     class CryptPasswordManager(object):
         """Crypt password manager.
-        
-        Implements a UNIX crypt(3) hashing scheme. Note that crypt is 
+
+        Implements a UNIX crypt(3) hashing scheme. Note that crypt is
         considered far inferior to more modern schemes such as SSHA hashing,
         and only uses the first 8 characters of a password.
-        
+
         >>> from zope.interface.verify import verifyObject
 
         >>> manager = CryptPasswordManager()
@@ -58,7 +66,7 @@
         against an 8 character password plus suffix always matches. Our test
         password (including utf-8 encoding) is exactly 8 characters long, and
         thus affixing 'wrong' to it tests as a correct password::
-        
+
         >>> manager.checkPassword(encoded, password + u"wrong")
         True
 
@@ -67,7 +75,7 @@
         >>> manager.checkPassword(encoded, 'completely wrong')
         False
 
-        Using the `openssl passwd` command-line utility to encode ``secret``, 
+        Using the `openssl passwd` command-line utility to encode ``secret``,
         we get ``erz50QD3gv4Dw`` as seeded hash.
 
         Our password manager generates the same value when seeded with the
@@ -88,7 +96,7 @@
         >>> manager.encodePassword(password) != manager.encodePassword(password)
         True
 
-        The manager only claims to implement CRYPT encodings, anything not 
+        The manager only claims to implement CRYPT encodings, anything not
         starting with the string {CRYPT} returns False::
 
         >>> manager.match('{MD5}someotherhash')
@@ -103,10 +111,13 @@
                            "abcdefghijklmnopqrstuvwxyz"
                            "0123456789./")
                 salt = choice(choices) + choice(choices)
-            return '{CRYPT}%s' % crypt(_encoder(password)[0], salt)
+            if PY2:
+                # Py3: Python 2 can only handle ASCII for crypt.
+                password = _encoder(password)[0]
+            return '{CRYPT}%s' % crypt(password, salt)
 
         def checkPassword(self, encoded_password, password):
-            return encoded_password == self.encodePassword(password, 
+            return encoded_password == self.encodePassword(password,
                 encoded_password[7:9])
 
         def match(self, encoded_password):
@@ -139,10 +150,10 @@
     False
 
     Using the password 'PHP & Information Security' should result in the
-    hash ``379693e271cd3bd6``, according to 
+    hash ``379693e271cd3bd6``, according to
     http://phpsec.org/articles/2005/password-hashing.html
 
-    Our password manager generates the same value when seeded with the, so we 
+    Our password manager generates the same value when seeded with the, so we
     can be sure, our output is compatible with MySQL versions before 4.1::
 
     >>> password = 'PHP & Information Security'
@@ -155,7 +166,7 @@
     >>> manager.checkPassword(encoded, password + u"wrong")
     False
 
-    The manager only claims to implement MYSQL encodings, anything not 
+    The manager only claims to implement MYSQL encodings, anything not
     starting with the string {MYSQL} returns False::
 
     >>> manager.match('{MD5}someotherhash')
@@ -165,21 +176,28 @@
 
 
     def encodePassword(self, password):
-        nr = 1345345333L
+        nr = 1345345333
         add = 7
-        nr2 = 0x12345671L
+        nr2 = 0x12345671
         for i in _encoder(password)[0]:
-            if i == ' ' or i == '\t':
+            if PY2:
+                # In Python 2 bytes iterate over single-char strings.
+                i = ord(i)
+            if i == ord(b' ') or i == ord(b'\t'):
                 continue
-            nr ^= (((nr & 63) + add) * ord(i)) + (nr << 8)
+            nr ^= (((nr & 63) + add) * i) + (nr << 8)
             nr2 += (nr2 << 8) ^ nr
-            add += ord(i)
-        r0 = nr & ((1L << 31) - 1L)
-        r1 = nr2 & ((1L << 31) - 1L)
-        return "{MYSQL}%08lx%08lx" % (r0, r1)
+            add += i
+        r0 = nr & ((1 << 31) - 1)
+        r1 = nr2 & ((1 << 31) - 1)
+        return ("{MYSQL}%08lx%08lx" % (r0, r1)).encode()
 
     def checkPassword(self, encoded_password, password):
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
         return encoded_password == self.encodePassword(password)
 
     def match(self, encoded_password):
-        return encoded_password.startswith('{MYSQL}')
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        return encoded_password.startswith(b'{MYSQL}')

Modified: zope.password/trunk/src/zope/password/password.py
===================================================================
--- zope.password/trunk/src/zope/password/password.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/password.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -19,20 +19,20 @@
 from base64 import standard_b64decode
 from base64 import urlsafe_b64decode
 from binascii import a2b_hex
+from hashlib import md5, sha1
 from os import urandom
 from codecs import getencoder
-try:
-    from hashlib import md5, sha1
-except ImportError:
-    # Python 2.4
-    from md5 import new as md5
-    from sha import new as sha1
 
 from zope.interface import implementer
 from zope.password.interfaces import IMatchingPasswordManager
 
 _encoder = getencoder("utf-8")
 
+try:
+    unicode
+except NameError:
+    # Py3: Define unicode.
+    unicode = str
 
 @implementer(IMatchingPasswordManager)
 class PlainTextPasswordManager(object):
@@ -46,8 +46,8 @@
 
     >>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
     >>> encoded = manager.encodePassword(password)
-    >>> encoded
-    u'right \u0410'
+    >>> encoded == password.encode('utf-8')
+    True
     >>> manager.checkPassword(encoded, password)
     True
     >>> manager.checkPassword(encoded, password + u"wrong")
@@ -65,6 +65,8 @@
 
 
     def encodePassword(self, password):
+        if isinstance(password, unicode):
+            password = password.encode('utf-8')
         return password
 
     def checkPassword(self, encoded_password, password):
@@ -138,7 +140,7 @@
     >>> passwd = u'foobar\u2211' # sigma-sign.
     >>> manager.checkPassword(manager.encodePassword(passwd), passwd)
     True
-    >>> manager.checkPassword(unicode(manager.encodePassword(passwd)), passwd)
+    >>> manager.checkPassword(manager.encodePassword(passwd).decode(), passwd)
     True
 
     The manager only claims to implement SSHA encodings, anything not starting
@@ -160,16 +162,20 @@
     def encodePassword(self, password, salt=None):
         if salt is None:
             salt = urandom(4)
+        elif isinstance(salt, unicode):
+            salt = salt.encode('utf-8')
         hash = sha1(_encoder(password)[0])
         hash.update(salt)
-        return '{SSHA}' + standard_b64encode(hash.digest() + salt)
+        return b'{SSHA}' + standard_b64encode(hash.digest() + salt)
 
     def checkPassword(self, encoded_password, password):
         # standard_b64decode() cannot handle unicode input string. We
         # encode to ascii. This is safe as the encoded_password string
         # should not contain non-ascii characters anyway.
-        encoded_password = encoded_password.encode('ascii')[6:]
-        if '_' in encoded_password or '-' in encoded_password:
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        encoded_password = encoded_password[6:]
+        if b'_' in encoded_password or b'-' in encoded_password:
             # Encoded using old urlsafe_b64encode, re-encode
             byte_string = urlsafe_b64decode(encoded_password)
             encoded_password = standard_b64encode(byte_string)
@@ -179,7 +185,9 @@
         return encoded_password == self.encodePassword(password, salt)[6:]
 
     def match(self, encoded_password):
-        return encoded_password.startswith('{SSHA}')
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        return encoded_password.startswith(b'{SSHA}')
 
 
 class SMD5PasswordManager(PlainTextPasswordManager):
@@ -238,7 +246,7 @@
     >>> passwd = u'foobar\u2211' # sigma-sign.
     >>> manager.checkPassword(manager.encodePassword(passwd), passwd)
     True
-    >>> manager.checkPassword(unicode(manager.encodePassword(passwd)), passwd)
+    >>> manager.checkPassword(manager.encodePassword(passwd).decode(), passwd)
     True
 
     The manager only claims to implement SMD5 encodings, anything not starting
@@ -252,21 +260,23 @@
     def encodePassword(self, password, salt=None):
         if salt is None:
             salt = urandom(4)
+        elif isinstance(salt, unicode):
+            salt = salt.encode('utf-8')
         hash = md5(_encoder(password)[0])
         hash.update(salt)
-        return '{SMD5}' + standard_b64encode(hash.digest() + salt)
+        return b'{SMD5}' + standard_b64encode(hash.digest() + salt)
 
     def checkPassword(self, encoded_password, password):
-        # standard_b64decode() cannot handle unicode input string. We
-        # encode to ascii. This is safe as the encoded_password string
-        # should not contain non-ascii characters anyway.
-        encoded_password = encoded_password.encode('ascii')[6:]
-        byte_string = standard_b64decode(encoded_password)
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        byte_string = standard_b64decode(encoded_password[6:])
         salt = byte_string[16:]
-        return encoded_password == self.encodePassword(password, salt)[6:]
+        return encoded_password == self.encodePassword(password, salt)
 
     def match(self, encoded_password):
-        return encoded_password.startswith('{SMD5}')
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        return encoded_password.startswith(b'{SMD5}')
 
 
 class MD5PasswordManager(PlainTextPasswordManager):
@@ -302,7 +312,7 @@
     >>> passwd = u'foobar\u2211' # sigma-sign.
     >>> manager.checkPassword(manager.encodePassword(passwd), passwd)
     True
-    >>> manager.checkPassword(unicode(manager.encodePassword(passwd)), passwd)
+    >>> manager.checkPassword(manager.encodePassword(passwd).decode(), passwd)
     True
 
     A previous version of this manager also created a cosmetic salt, added
@@ -326,18 +336,22 @@
     def encodePassword(self, password, salt=None):
         # The salt argument only exists for backwards compatibility and is
         # ignored on purpose.
-        return '{MD5}%s' % standard_b64encode(
+        return b'{MD5}' + standard_b64encode(
             md5(_encoder(password)[0]).digest())
 
     def checkPassword(self, encoded_password, password):
-        encoded = encoded_password[encoded_password.find('}') + 1:]
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        encoded = encoded_password[encoded_password.find(b'}') + 1:]
         if len(encoded) > 24:
             # Backwards compatible, hexencoded md5 and bogus salt
             encoded = standard_b64encode(a2b_hex(encoded[-32:]))
         return encoded == self.encodePassword(password)[5:]
 
     def match(self, encoded_password):
-        return encoded_password.startswith('{MD5}')
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
+        return encoded_password.startswith(b'{MD5}')
 
 
 class SHA1PasswordManager(PlainTextPasswordManager):
@@ -373,7 +387,7 @@
     >>> passwd = u'foobar\u2211' # sigma-sign.
     >>> manager.checkPassword(manager.encodePassword(passwd), passwd)
     True
-    >>> manager.checkPassword(unicode(manager.encodePassword(passwd)), passwd)
+    >>> manager.checkPassword(manager.encodePassword(passwd).decode(), passwd)
     True
 
     A previous version of this manager also created a cosmetic salt, added
@@ -410,12 +424,14 @@
     def encodePassword(self, password, salt=None):
         # The salt argument only exists for backwards compatibility and is
         # ignored on purpose.
-        return '{SHA}%s' % standard_b64encode(
+        return b'{SHA}' + standard_b64encode(
             sha1(_encoder(password)[0]).digest())
 
     def checkPassword(self, encoded_password, password):
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
         if self.match(encoded_password):
-            encoded = encoded_password[encoded_password.find('}') + 1:]
+            encoded = encoded_password[encoded_password.find(b'}') + 1:]
             if len(encoded) > 28:
                 # Backwards compatible, hexencoded sha1 and bogus salt
                 encoded = standard_b64encode(a2b_hex(encoded[-40:]))
@@ -425,9 +441,11 @@
         return encoded_password == self.encodePassword(password)[5:]
 
     def match(self, encoded_password):
+        if isinstance(encoded_password, unicode):
+            encoded_password = encoded_password.encode('ascii')
         return (
-            encoded_password.startswith('{SHA}') or 
-            encoded_password.startswith('{SHA1}'))
+            encoded_password.startswith(b'{SHA}') or
+            encoded_password.startswith(b'{SHA1}'))
 
 
 # Simple registry

Modified: zope.password/trunk/src/zope/password/testing.py
===================================================================
--- zope.password/trunk/src/zope/password/testing.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/testing.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -35,7 +35,7 @@
 
 def setUpPasswordManagers():
     """Helper function for setting up password manager utilities for tests
-    
+
     >>> from zope.component import getUtility
     >>> setUpPasswordManagers()
 
@@ -81,7 +81,7 @@
 
     >>> CryptPasswordManager is None or 'Crypt' in voc
     True
-    
+
     """
     provideUtility(PlainTextPasswordManager(), IMatchingPasswordManager,
                    'Plain Text')
@@ -90,7 +90,7 @@
     provideUtility(SMD5PasswordManager(), IMatchingPasswordManager, 'SMD5')
     provideUtility(SHA1PasswordManager(), IMatchingPasswordManager, 'SHA1')
     provideUtility(MySQLPasswordManager(), IMatchingPasswordManager, 'MySQL')
-    
+
     if CryptPasswordManager is not None:
         provideUtility(CryptPasswordManager, IMatchingPasswordManager, 'Crypt')
 

Modified: zope.password/trunk/src/zope/password/tests/test_password.py
===================================================================
--- zope.password/trunk/src/zope/password/tests/test_password.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/tests/test_password.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -14,13 +14,23 @@
 """Password Managers Tests
 """
 import doctest
+import re
 import unittest
+from zope.testing import renormalizing
 
+checker = renormalizing.RENormalizing([
+    # Python 3 bytes add a "b".
+    (re.compile("b('.*?')"),
+     r"\1"),
+    (re.compile('b(".*?")'),
+     r"\1"),
+    ])
+
 def test_suite():
     return unittest.TestSuite((
-        doctest.DocTestSuite('zope.password.password'),
-        doctest.DocTestSuite('zope.password.legacy'),
+        doctest.DocTestSuite('zope.password.password', checker=checker),
+        doctest.DocTestSuite('zope.password.legacy', checker=checker),
         doctest.DocTestSuite(
             'zope.password.testing',
-            optionflags=doctest.ELLIPSIS),
+            optionflags=doctest.ELLIPSIS, checker=checker),
         ))

Modified: zope.password/trunk/src/zope/password/tests/test_zpasswd.py
===================================================================
--- zope.password/trunk/src/zope/password/tests/test_zpasswd.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/tests/test_zpasswd.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -17,15 +17,20 @@
 import os
 import sys
 import unittest, doctest
-from StringIO import StringIO
 
+try:
+    from StringIO import StringIO
+except ImportError:
+    # Py3: StringIO moved to io.
+    from io import StringIO
+
 from zope.password import password, zpasswd
 
 class TestBase(unittest.TestCase):
     def setUp(self):
         # Create a minimal site.zcml file
         open('testsite.zcml', 'wb').write(
-            '<configure xmlns="http://namespaces.zope.org/zope"/>\n'
+            b'<configure xmlns="http://namespaces.zope.org/zope"/>\n'
             )
         self.stdout = StringIO()
         self.stderr = StringIO()
@@ -55,7 +60,7 @@
     def check_stdout_content(self, args):
         try:
             options = self.parse_args(args)
-        except SystemExit, e:
+        except SystemExit as e:
             self.assertEqual(e.code, 0)
             self.assert_(self.stdout.getvalue())
             self.failIf(self.stderr.getvalue())
@@ -130,7 +135,8 @@
     def test_principal_information(self):
         options = self.createOptions()
         app = ControlledInputApplication(options,
-            ["id", "title", "login", "1", "passwd", "passwd", "description"])
+            ["id", u"title", u"login", u"1",
+             u"passwd", u"passwd", u"description"])
         app.process()
         self.failUnless(not self.stderr.getvalue())
         self.failUnless(app.all_input_consumed())

Modified: zope.password/trunk/src/zope/password/zpasswd.py
===================================================================
--- zope.password/trunk/src/zope/password/zpasswd.py	2013-02-21 18:33:31 UTC (rev 129587)
+++ zope.password/trunk/src/zope/password/zpasswd.py	2013-02-21 19:45:04 UTC (rev 129588)
@@ -13,6 +13,7 @@
 ##############################################################################
 """Implementation of the zpasswd script.
 """
+from __future__ import print_function
 import optparse
 import os
 import pkg_resources
@@ -27,7 +28,7 @@
         argv = sys.argv
     try:
         options = parse_args(argv)
-    except SystemExit, e:
+    except SystemExit as e:
         if e.code:
             return 2
         else:
@@ -37,14 +38,14 @@
         return app.process()
     except KeyboardInterrupt:
         return 1
-    except SystemExit, e:
+    except SystemExit as e:
         return e.code
 
 class Principal(object):
     """Principal.
 
-    >>> principal = Principal("id", "title", "login", "password")
-    >>> print principal
+    >>> principal = Principal("id", u"title", u"login", b"password")
+    >>> print(principal)
       <principal
         id="id"
         title="title"
@@ -52,9 +53,9 @@
         password="password"
         />
 
-    >>> principal = Principal("id", "title", "login", "password",
-    ...     "description", "SHA1")
-    >>> print principal
+    >>> principal = Principal("id", u"title", u"login", b"password",
+    ...     u"description", "SHA1")
+    >>> print(principal)
       <principal
         id="id"
         title="title"
@@ -80,7 +81,7 @@
             '    id=%s' % quoteattr(self.id),
             '    title=%s' % quoteattr(self.title),
             '    login=%s' % quoteattr(self.login),
-            '    password=%s' % quoteattr(self.password)
+            '    password=%s' % quoteattr(self.password.decode())
             ]
         if self.description:
             lines.append('    description=%s' % quoteattr(self.description))
@@ -157,9 +158,9 @@
         principal = self.get_principal()
 
         if destination is sys.stdout:
-            print self.title
-        print >>destination, principal
-        print
+            print(self.title)
+        print(principal, file=destination)
+        print()
 
         return 0
 
@@ -183,7 +184,7 @@
         while True:
             value = self.read_input_line(prompt).strip()
             if not value and error:
-                print >>sys.stderr, error
+                print(error, file=sys.stderr)
                 continue
             return value
 
@@ -194,7 +195,7 @@
         managers = self.options.managers
 
         for i, (name, manager) in enumerate(managers):
-            print "% i. %s" % (i + 1, name)
+            print("% i. %s" % (i + 1, name))
             if name == 'SSHA':
                 default = i
         print
@@ -210,8 +211,8 @@
                 if index > 0 and index <= len(managers):
                     index -= 1
                     break
-            print >>sys.stderr, "You must select a password manager"
-        print "%s password manager selected" % managers[index][0]
+            print("You must select a password manager", file=sys.stderr)
+        print("%s password manager selected" % managers[index][0])
         return managers[index]
 
     def get_password(self):
@@ -219,15 +220,15 @@
         while True:
             password = self.read_password("Password: ")
             if not password:
-                print >>sys.stderr, "Password may not be empty"
+                print("Password may not be empty", file=sys.stderr)
                 continue
             if password != password.strip() or password.split() != [password]:
-                print >>sys.stderr, "Password may not contain spaces"
+                print("Password may not contain spaces", file=sys.stderr)
                 continue
             break
         again = self.read_password("Verify password: ")
         if again != password:
-            print >>sys.stderr, "Password not verified!"
+            print("Password not verified!", file=sys.stderr)
             sys.exit(1)
         return password
 
@@ -235,7 +236,7 @@
         if self.need_blank_line:
             print
             self.need_blank_line = False
-        print message
+        print(message)
 
 def get_password_managers(config_path=None):
     if not config_path:
@@ -245,7 +246,7 @@
         from zope.component import getUtilitiesFor
         from zope.password.interfaces import IPasswordManager
 
-        print "Loading configuration..."
+        print("Loading configuration...")
         config = xmlconfig.file(config_path)
         managers = []
         for name, manager in getUtilitiesFor(IPasswordManager):

Added: zope.password/trunk/tox.ini
===================================================================
--- zope.password/trunk/tox.ini	                        (rev 0)
+++ zope.password/trunk/tox.ini	2013-02-21 19:45:04 UTC (rev 129588)
@@ -0,0 +1,17 @@
+[tox]
+envlist =
+    py26,py27,py33
+
+[testenv]
+commands =
+    python setup.py test -q
+# without explicit deps, setup.py test will download a bunch of eggs into $PWD
+# (and it seems I can't use zope.dottedname[testing] here, so forget DRY)
+deps =
+    zope.component
+    zope.configuration
+    zope.interface
+    zope.schema
+    zope.testing
+    zope.testrunner
+



More information about the checkins mailing list