Index: test/test_anypy_hashlib.py =================================================================== --- test/test_anypy_hashlib.py (revision 0) +++ test/test_anypy_hashlib.py (revision 4116) @@ -0,0 +1,139 @@ +#! /usr/bin/env python +import unittest +import warnings + +import roundup.anypy.hashlib_ + +class UntestableWarning(Warning): + pass + +# suppress deprecation warnings; -> warnings.filters[0]: +warnings.simplefilter(action='ignore', + category=DeprecationWarning, + append=0) + +try: + import sha +except: + warnings.warn('sha module functions', UntestableWarning) + sha = None + +try: + import md5 +except: + warnings.warn('md5 module functions', UntestableWarning) + md5 = None + +try: + import hashlib +except: + warnings.warn('hashlib module functions', UntestableWarning) + hashlib = None + +# preserve other warning filters set elsewhere: +del warnings.filters[0] + +if not ((sha or md5) and hashlib): + warnings.warn('anypy.hashlib_ continuity', UntestableWarning) + +class TestCase_anypy_hashlib(unittest.TestCase): + """test the hashlib compatibility layer""" + + testdata = ( + ('', + 'da39a3ee5e6b4b0d3255bfef95601890afd80709', + 'd41d8cd98f00b204e9800998ecf8427e'), + ('Strange women lying in ponds distributing swords' + ' is no basis for a system of government.', + 'da9b2b00466b00411038c057681fe67349f92d7d', + 'b71c5178d316ec446c25386f4857d4f9'), + ('Ottos Mops hopst fort', + 'fdf7e6c54cf07108c86edd8d47c90450671c2c81', + 'a3dce74bee59ee92f1038263e5252500'), + ('Dieser Satz kein Verb', + '3030aded8a079b92043a39dc044a35443959dcdd', + '2f20c69d514228011fb0d32e14dd5d80'), + ) + + # the following two are always excecuted: + def test_sha1_expected_anypy(self): + """...anypy.hashlib_.sha1().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(roundup.anypy.hashlib_.sha1(src).hexdigest(), SHA) + + def test_md5_expected_anypy(self): + """...anypy.hashlib_.md5().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(roundup.anypy.hashlib_.md5(src).hexdigest(), MD5) + + # execution depending on availability of modules: + if md5 and hashlib: + def test_md5_continuity(self): + """md5.md5().digest() == hashlib.md5().digest()""" + if md5.md5 is hashlib.md5: + return + else: + for s, i1, i2 in self.testdata: + self.assertEqual(md5.md5(s).digest(), + hashlib.md5().digest()) + + if md5: + def test_md5_expected(self): + """md5.md5().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(md5.md5(src).hexdigest(), MD5) + + def test_md5_new_expected(self): + """md5.new is md5.md5, or at least yields expected results""" + if md5.new is md5.md5: + return + else: + for src, SHA, MD5 in self.testdata: + self.assertEqual(md5.new(src).hexdigest(), MD5) + + if sha and hashlib: + def test_sha1_continuity(self): + """sha.sha().digest() == hashlib.sha1().digest()""" + if sha.sha is hashlib.sha1: + return + else: + for s in self.testdata: + self.assertEqual(sha.sha(s).digest(), + hashlib.sha1().digest()) + + if sha: + def test_sha_expected(self): + """sha.sha().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(sha.sha(src).hexdigest(), SHA) + + # fails for me with Python 2.3; unittest module bug? + def test_sha_new_expected(self): + """sha.new is sha.sha, or at least yields expected results""" + if sha.new is sha.sha: + return + else: + for src, SHA, MD5 in self.testdata: + self.assertEqual(sha.new(src).hexdigest(), SHA) + + if hashlib: + def test_sha1_expected_hashlib(self): + """hashlib.sha1().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(hashlib.sha1(src).hexdigest(), SHA) + + def test_md5_expected_hashlib(self): + """hashlib.md5().hexdigest() yields expected results""" + for src, SHA, MD5 in self.testdata: + self.assertEqual(hashlib.md5(src).hexdigest(), MD5) + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestCase_anypy_hashlib)) + return suite + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + unittest.main(testRunner=runner) + +# vim: ts=8 et sts=4 sw=4 si Index: test/db_test_base.py =================================================================== --- test/db_test_base.py (revision 4178) +++ test/db_test_base.py (working copy) @@ -17,7 +17,9 @@ # # $Id: db_test_base.py,v 1.101 2008-08-19 01:40:59 richard Exp $ -import unittest, os, shutil, errno, imp, sys, time, pprint, sets, base64, os.path +import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set from roundup.hyperdb import String, Password, Link, Multilink, Date, \ Interval, DatabaseError, Boolean, Number, Node @@ -284,7 +286,7 @@ # try a couple of the built-in iterable types to make # sure that we accept them and handle them properly # try a set as input for the multilink - nid = self.db.issue.create(title="spam", nosy=sets.Set(u1)) + nid = self.db.issue.create(title="spam", nosy=set(u1)) if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "nosy"), [u1]) self.assertRaises(TypeError, self.db.issue.set, nid, @@ -294,7 +296,7 @@ if commit: self.db.commit() self.assertEqual(self.db.issue.get(nid, "nosy"), []) # make sure we accept a frozen set - self.db.issue.set(nid, nosy=sets.Set([u1,u2])) + self.db.issue.set(nid, nosy=set([u1,u2])) if commit: self.db.commit() l = [u1,u2]; l.sort() m = self.db.issue.get(nid, "nosy"); m.sort() @@ -487,12 +489,12 @@ others = nodeids[:] others.remove('1') - self.assertEqual(sets.Set(self.db.status.getnodeids()), - sets.Set(nodeids)) - self.assertEqual(sets.Set(self.db.status.getnodeids(retired=True)), - sets.Set(['1'])) - self.assertEqual(sets.Set(self.db.status.getnodeids(retired=False)), - sets.Set(others)) + self.assertEqual(set(self.db.status.getnodeids()), + set(nodeids)) + self.assertEqual(set(self.db.status.getnodeids(retired=True)), + set(['1'])) + self.assertEqual(set(self.db.status.getnodeids(retired=False)), + set(others)) self.assert_(self.db.status.is_retired('1')) @@ -2054,7 +2056,7 @@ self.db.getjournal('a', aid) class RDBMSTest: - ''' tests specific to RDBMS backends ''' + """ tests specific to RDBMS backends """ def test_indexTest(self): self.assertEqual(self.db.sql_index_exists('_issue', '_issue_id_idx'), 1) self.assertEqual(self.db.sql_index_exists('_issue', '_issue_x_idx'), 0) Index: test/test_hyperdbvals.py =================================================================== --- test/test_hyperdbvals.py (revision 4178) +++ test/test_hyperdbvals.py (working copy) @@ -10,7 +10,8 @@ # # $Id: test_hyperdbvals.py,v 1.3 2006-08-18 01:26:19 richard Exp $ -import unittest, os, shutil, errno, sys, difflib, cgi, re, sha +import unittest, os, shutil, errno, sys, difflib, cgi, re +from roundup.anypy.hashlib_ import sha1 from roundup import init, instance, password, hyperdb, date @@ -80,7 +81,7 @@ self.assert_(isinstance(val, password.Password)) val = self._test('password', '{crypt}a string') self.assert_(isinstance(val, password.Password)) - s = sha.sha('a string').hexdigest() + s = sha1('a string').hexdigest() val = self._test('password', '{SHA}'+s) self.assert_(isinstance(val, password.Password)) self.assertEqual(val, 'a string') Index: setup.py =================================================================== --- setup.py (revision 4178) +++ setup.py (working copy) @@ -58,6 +58,7 @@ # template munching packagelist = [ 'roundup', + 'roundup.anypy', 'roundup.cgi', 'roundup.cgi.PageTemplates', 'roundup.cgi.TAL', Index: scripts/import_sf.py =================================================================== --- scripts/import_sf.py (revision 4178) +++ scripts/import_sf.py (working copy) @@ -1,4 +1,4 @@ -''' Import tracker data from Sourceforge.NET +""" Import tracker data from Sourceforge.NET This script needs four steps to work: @@ -19,9 +19,11 @@ roundup-admin -i import /tmp/imported And you're done! -''' +""" -import sys, sets, os, csv, time, urllib2, httplib, mimetypes, urlparse +import sys, os, csv, time, urllib2, httplib, mimetypes, urlparse +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set try: import cElementTree as ElementTree @@ -53,8 +55,8 @@ def fetch_files(xml_file, file_dir): """ Fetch files referenced in the xml_file into the dir file_dir. """ root = ElementTree.parse(xml_file).getroot() - to_fetch = sets.Set() - deleted = sets.Set() + to_fetch = set() + deleted = set() for artifact in root.find('artifacts'): for field in artifact.findall('field'): if field.get('name') == 'artifact_id': @@ -73,7 +75,7 @@ deleted.add((aid, fid)) to_fetch = to_fetch - deleted - got = sets.Set(os.listdir(file_dir)) + got = set(os.listdir(file_dir)) to_fetch = to_fetch - got # load cached urls (sigh) @@ -122,10 +124,10 @@ # parse out the XML artifacts = [] - categories = sets.Set() - users = sets.Set() - add_files = sets.Set() - remove_files = sets.Set() + categories = set() + users = set() + add_files = set() + remove_files = set() for artifact in root.find('artifacts'): d = {} op = {} @@ -254,7 +256,7 @@ else: d['status'] = unread - nosy = sets.Set() + nosy = set() for message in artifact.get('messages', []): authid = users[message['user_name']] if not message['body']: continue @@ -338,7 +340,7 @@ f.close() def convert_message(content, id): - ''' Strip off the useless sf message header crap ''' + """ Strip off the useless sf message header crap """ if content[:14] == 'Logged In: YES': return '\n'.join(content.splitlines()[3:]).strip() return content Index: roundup/admin.py =================================================================== --- roundup/admin.py (revision 4178) +++ roundup/admin.py (working copy) @@ -729,7 +729,7 @@ print _('%(key)s: %(value)s')%locals() def do_display(self, args): - """Usage: display designator[,designator]* + ''"""Usage: display designator[,designator]* Show the property values for the given node(s). This lists the properties and their associated values for the given Index: roundup/hyperdb.py =================================================================== --- roundup/hyperdb.py (revision 4178) +++ roundup/hyperdb.py (working copy) @@ -22,7 +22,8 @@ # standard python modules import os, re, shutil, weakref -from sets import Set +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set # roundup modules import date, password @@ -193,7 +194,7 @@ # definitely in the new list (in case of e.g. # =A,+B, which should replace the old # list with A,B) - set = 1 + do_set = 1 newvalue = [] for item in value: item = item.strip() @@ -206,10 +207,10 @@ if item.startswith('-'): remove = 1 item = item[1:] - set = 0 + do_set = 0 elif item.startswith('+'): item = item[1:] - set = 0 + do_set = 0 # look up the value itemid = convertLinkValue(db, propname, self, item) @@ -228,7 +229,7 @@ # that's it, set the new Multilink property value, # or overwrite it completely - if set: + if do_set: value = newvalue else: value = curvalue @@ -487,7 +488,7 @@ v = self._val if not isinstance(self._val, type([])): v = [self._val] - vals = Set(v) + vals = set(v) vals.intersection_update(val) self._val = [v for v in vals] else: Index: roundup/password.py =================================================================== --- roundup/password.py (revision 4178) +++ roundup/password.py (working copy) @@ -21,26 +21,26 @@ """ __docformat__ = 'restructuredtext' -import sha, md5, re, string, random +import re, string, random +from roundup.anypy.hashlib_ import md5, sha1 try: import crypt -except: +except ImportError: crypt = None - pass class PasswordValueError(ValueError): - ''' The password value is not valid ''' + """ The password value is not valid """ pass def encodePassword(plaintext, scheme, other=None): - '''Encrypt the plaintext password. - ''' + """Encrypt the plaintext password. + """ if plaintext is None: plaintext = "" if scheme == 'SHA': - s = sha.sha(plaintext).hexdigest() + s = sha1(plaintext).hexdigest() elif scheme == 'MD5': - s = md5.md5(plaintext).hexdigest() + s = md5(plaintext).hexdigest() elif scheme == 'crypt' and crypt is not None: if other is not None: salt = other @@ -59,7 +59,7 @@ return ''.join([random.choice(chars) for x in range(length)]) class Password: - '''The class encapsulates a Password property type value in the database. + """The class encapsulates a Password property type value in the database. The encoding of the password is one if None, 'SHA', 'MD5' or 'plaintext'. The encodePassword function is used to actually encode the password from @@ -79,13 +79,13 @@ 1 >>> 'not sekrit' != p 1 - ''' + """ default_scheme = 'SHA' # new encryptions use this scheme pwre = re.compile(r'{(\w+)}(.+)') def __init__(self, plaintext=None, scheme=None, encrypted=None): - '''Call setPassword if plaintext is not None.''' + """Call setPassword if plaintext is not None.""" if scheme is None: scheme = self.default_scheme if plaintext is not None: @@ -98,9 +98,9 @@ self.plaintext = None def unpack(self, encrypted, scheme=None): - '''Set the password info from the scheme: string + """Set the password info from the scheme: string (the inverse of __str__) - ''' + """ m = self.pwre.match(encrypted) if m: self.scheme = m.group(1) @@ -111,7 +111,7 @@ self.setPassword(encrypted, scheme) def setPassword(self, plaintext, scheme=None): - '''Sets encrypts plaintext.''' + """Sets encrypts plaintext.""" if scheme is None: scheme = self.default_scheme self.scheme = scheme @@ -119,7 +119,7 @@ self.plaintext = plaintext def __cmp__(self, other): - '''Compare this password against another password.''' + """Compare this password against another password.""" # check to see if we're comparing instances if isinstance(other, Password): if self.scheme != other.scheme: @@ -133,7 +133,7 @@ self.password)) def __str__(self): - '''Stringify the encrypted password for database storage.''' + """Stringify the encrypted password for database storage.""" if self.password is None: raise ValueError, 'Password not set' return '{%s}%s'%(self.scheme, self.password) Index: roundup/install_util.py =================================================================== --- roundup/install_util.py (revision 4178) +++ roundup/install_util.py (working copy) @@ -21,7 +21,8 @@ """ __docformat__ = 'restructuredtext' -import os, sha, shutil +import os, shutil +from roundup.anypy.hashlib_ import sha1 sgml_file_types = [".xml", ".ent", ".html"] hash_file_types = [".py", ".sh", ".conf", ".cgi"] @@ -59,7 +60,7 @@ del lines[-1] # calculate current digest - digest = sha.new() + digest = sha1() for line in lines: digest.update(line) @@ -74,7 +75,7 @@ def __init__(self, filename): self.filename = filename - self.digest = sha.new() + self.digest = sha1() self.file = open(self.filename, "w") def write(self, data): Index: roundup/anypy/hashlib_.py =================================================================== --- roundup/anypy/hashlib_.py (revision 0) +++ roundup/anypy/hashlib_.py (revision 0) @@ -0,0 +1,11 @@ +""" +anypy.hashlib_: encapsulation of hashlib/md5/sha1/sha +""" + +try: + from hashlib import md5, sha1 # new in Python 2.5 +except ImportError: + from md5 import md5 # deprecated in Python 2.6 + from sha import sha as sha1 # deprecated in Python 2.6 + +# vim: ts=8 sts=4 sw=4 si Index: roundup/anypy/TODO.TXT =================================================================== --- roundup/anypy/TODO.TXT (revision 0) +++ roundup/anypy/TODO.TXT (revision 0) @@ -0,0 +1,26 @@ +Python compatiblity TODO +~~~~~~~~~~~~~~~~~~~~~~~~ + +- the popen2 module is deprecated as of Python 2.6; + the subprocess module is available since Python 2.4, + thus a roundup.anypy.subprocess_ module is needed + +- the MimeWriter module is deprecated as of Python 2.6. The email package is + available since Python 2.2, thus we should manage without a ...email_ + module; however, it has suffered some API changes over the time + (http://docs.python.org/library/email.html#package-history), + so this is not sure. + + Here's an incomplete replacement table: + + MimeWriter usage checked for + -> email usage Python ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~ + MimeWriter.MimeWriter + -> email.Message.Message (2.3) + + MimeWriter.MimeWrite.addheader + -> email.Message.Message.add_header (2.3) + + +# vim: si Index: roundup/anypy/sets_.py =================================================================== --- roundup/anypy/sets_.py (revision 0) +++ roundup/anypy/sets_.py (revision 0) @@ -0,0 +1,30 @@ +""" +anypy.sets_: sets compatibility module + +uses the built-in type 'set' if available, and thus avoids +deprecation warnings. Simple usage: + +Change all + from sets import Set +to + from roundup.anypy.sets_ import set + +and use 'set' instead of 'Set'. +To avoid unnecessary imports, you can: + + try: + set + except NameError: + from roundup.anypy.sets_ import set + +see: +http://docs.python.org/library/sets.html#comparison-to-the-built-in-set-types + +""" + +try: + set = set # built-in since Python 2.4 +except NameError, TypeError: + from sets import Set as set # deprecated as of Python 2.6 + +# vim: ts=8 sts=4 sw=4 si et Index: roundup/anypy/__init__.py =================================================================== --- roundup/anypy/__init__.py (revision 0) +++ roundup/anypy/__init__.py (revision 0) @@ -0,0 +1,7 @@ +""" +roundup.anypy - compatibility layer for any Python 2.3+ +""" +VERSION = '.'.join(map(str, + (0, + 1, # hashlib_, sets_ + ))) Index: roundup/anypy/README.TXT =================================================================== --- roundup/anypy/README.TXT (revision 0) +++ roundup/anypy/README.TXT (revision 0) @@ -0,0 +1,57 @@ +roundup.anypy package - Python version compatibility layer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Roundup currently supports Python 2.3 to 2.6; however, some modules +have been introduced, while others have been deprecated. The modules +in this package provide the functionalities which are used by Roundup + +- adapting the most recent Python usage +- using new built-in functionality +- avoiding deprecation warnings + +Use the modules in this package to preserve Roundup's compatibility. + +sets_: sets compatibility module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since Python 2.4, there is a built-in type 'set'; therefore, the 'sets' +module is deprecated since version 2.6. As far as Roundup is concerned, +the usage is identical; see +http://docs.python.org/library/sets.html#comparison-to-the-built-in-set-types + +Uses the built-in type 'set' if available, and thus avoids +deprecation warnings. Simple usage: + +Change all:: + from sets import Set + +to:: + from roundup.anypy.sets_ import set + +and use 'set' instead of 'Set' (or sets.Set, respectively). +To avoid unnecessary imports, you can:: + + try: + set + except NameError: + from roundup.anypy.sets_ import set + +hashlib_: md5/sha/hashlib compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The md5 and sha modules are deprecated since Python 2.6; the hashlib +module, introduced with Python 2.5, is recommended instead. + +Change all:: + import md5 + md5.md5(), md5.new() + import sha + sha.sha(), sha.new() + +to:: + from roundup.anypy.hashlib_ import md5 + md5() + from roundup.anypy.hashlib_ import sha1 + sha1() + +# vim: si Index: roundup/backends/indexer_common.py =================================================================== --- roundup/backends/indexer_common.py (revision 4178) +++ roundup/backends/indexer_common.py (working copy) @@ -1,5 +1,7 @@ #$Id: indexer_common.py,v 1.11 2008-09-11 19:41:07 schlatterbeck Exp $ -import re, sets +import re +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set from roundup import hyperdb @@ -17,7 +19,7 @@ class Indexer: def __init__(self, db): - self.stopwords = sets.Set(STOPWORDS) + self.stopwords = set(STOPWORDS) for word in db.config[('main', 'indexer_stopwords')]: self.stopwords.add(word) @@ -28,11 +30,11 @@ return self.find(search_terms) def search(self, search_terms, klass, ignore={}): - '''Display search results looking for [search, terms] associated + """Display search results looking for [search, terms] associated with the hyperdb Class "klass". Ignore hits on {class: property}. "dre" is a helper, not an argument. - ''' + """ # do the index lookup hits = self.getHits(search_terms, klass) if not hits: Index: roundup/backends/indexer_rdbms.py =================================================================== --- roundup/backends/indexer_rdbms.py (revision 4178) +++ roundup/backends/indexer_rdbms.py (working copy) @@ -1,9 +1,11 @@ #$Id: indexer_rdbms.py,v 1.18 2008-09-01 00:43:02 richard Exp $ -''' This implements the full-text indexer over two RDBMS tables. The first +""" This implements the full-text indexer over two RDBMS tables. The first is a mapping of words to occurance IDs. The second maps the IDs to (Class, propname, itemid) instances. -''' -import re, sets +""" +import re +# Python 2.3 ... 2.6 compatibility: +from roundup.anypy.sets_ import set from roundup.backends.indexer_common import Indexer as IndexerBase @@ -14,27 +16,27 @@ self.reindex = 0 def close(self): - '''close the indexing database''' + """close the indexing database""" # just nuke the circular reference self.db = None def save_index(self): - '''Save the changes to the index.''' + """Save the changes to the index.""" # not necessary - the RDBMS connection will handle this for us pass def force_reindex(self): - '''Force a reindexing of the database. This essentially + """Force a reindexing of the database. This essentially empties the tables ids and index and sets a flag so - that the databases are reindexed''' + that the databases are reindexed""" self.reindex = 1 def should_reindex(self): - '''returns True if the indexes need to be rebuilt''' + """returns True if the indexes need to be rebuilt""" return self.reindex def add_text(self, identifier, text, mime_type='text/plain'): - ''' "identifier" is (classname, itemid, property) ''' + """ "identifier" is (classname, itemid, property) """ if mime_type != 'text/plain': return @@ -65,7 +67,7 @@ text = unicode(text, "utf-8", "replace").upper() wordlist = [w.encode("utf-8", "replace") for w in re.findall(r'(?u)\b\w{2,25}\b', text)] - words = sets.Set() + words = set() for word in wordlist: if self.is_stopword(word): continue if len(word) > 25: continue @@ -77,10 +79,10 @@ self.db.cursor.executemany(sql, words) def find(self, wordlist): - '''look up all the words in the wordlist. + """look up all the words in the wordlist. If none are found return an empty dictionary * more rules here - ''' + """ if not wordlist: return []