From a220e4c259d4c2320037dff8a08aff0a3e97b286 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 20 Feb 2013 14:11:39 +0000 Subject: [PATCH] Added support for Python 3.3. --- CHANGES.txt | 2 + MANIFEST.in | 9 + bootstrap.py | 155 +++++++++++++++--- buildout.cfg | 7 - setup.py | 53 ++++-- src/zope/dublincore/annotatableadapter.py | 9 +- src/zope/dublincore/creatorannotator.py | 6 + src/zope/dublincore/dcterms.py | 3 +- src/zope/dublincore/property.py | 4 +- src/zope/dublincore/testing.py | 16 +- .../tests/test_partialannotatable.py | 4 +- src/zope/dublincore/tests/test_property.py | 14 +- .../dublincore/tests/test_zopedublincore.py | 6 +- src/zope/dublincore/tests/timeannotators.txt | 4 +- src/zope/dublincore/xmlmetadata.py | 12 +- src/zope/dublincore/zopedublincore.py | 16 +- tox.ini | 23 +++ 17 files changed, 274 insertions(+), 69 deletions(-) create mode 100644 MANIFEST.in create mode 100644 tox.ini diff --git a/CHANGES.txt b/CHANGES.txt index 94d0d42..9989927 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,8 @@ Changes 4.0.0 (unreleased) ================== +- Added support for Python 3.3. + - Replaced deprecated ``zope.component.adapts`` usage with equivalent ``zope.component.adapter`` decorator. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f720ab1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include *.rst +include *.txt +include bootstrap.py +include buildout.cfg +include tox.ini + +recursive-include src * + +global-exclude *.pyc diff --git a/bootstrap.py b/bootstrap.py index 5f9e98e..ec3757a 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -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) diff --git a/buildout.cfg b/buildout.cfg index ea8836d..54deac8 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,14 +1,7 @@ [buildout] -extends = - http://download.zope.org/zopetoolkit/index/1.1.2/zopeapp-versions.cfg - http://download.zope.org/zopetoolkit/index/1.1.2/ztk-versions.cfg -versions = versions develop = . parts = test -[versions] -zope.dublincore = - [test] recipe = zc.recipe.testrunner eggs = zope.dublincore [test] diff --git a/setup.py b/setup.py index c3cf5c9..2c5bb49 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,20 @@ def read(*path): return open(os.path.join(*path)).read() + '\n\n' -version = '4.0.0dev' +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) long_description = ( '.. contents::\n\n' + @@ -40,7 +53,7 @@ def read(*path): setup( name="zope.dublincore", - version=version, + version='4.0.0dev', url='http://pypi.python.org/pypi/zope.dublincore', license='ZPL 2.1', description='Zope Dublin Core implementation', @@ -51,12 +64,15 @@ def read(*path): 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', + 'Natural Language :: English', + 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Natural Language :: English', - 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development', ], @@ -67,19 +83,28 @@ def read(*path): include_package_data=True, extras_require=dict( test=['zope.testing >= 3.8', + 'zope.testrunner', 'zope.annotation', 'zope.configuration', ] ), - install_requires = ['setuptools', - 'pytz', - 'zope.component[zcml]', - 'zope.datetime', - 'zope.interface', - 'zope.lifecycleevent', - 'zope.location', - 'zope.schema', - 'zope.security[zcml]>=3.8', - ], + install_requires = [ + 'pytz', + 'setuptools', + 'six', + 'zope.component[zcml]', + 'zope.datetime', + 'zope.interface', + 'zope.lifecycleevent', + 'zope.location', + 'zope.schema', + 'zope.security[zcml]>=3.8', + ], + tests_require = [ + 'zope.testing', + 'zope.testrunner', + 'zope.annotation', + 'zope.configuration'], + test_suite = '__main__.alltests', zip_safe = False ) diff --git a/src/zope/dublincore/annotatableadapter.py b/src/zope/dublincore/annotatableadapter.py index bfe796b..0d9896d 100644 --- a/src/zope/dublincore/annotatableadapter.py +++ b/src/zope/dublincore/annotatableadapter.py @@ -26,9 +26,16 @@ from zope.dublincore.zopedublincore import DateProperty from zope.dublincore.zopedublincore import ScalarProperty from zope.dublincore.zopedublincore import ZopeDublinCore +import six DCkey = "zope.app.dublincore.ZopeDublinCore" +try: + unicode +except NameError: + # Py3: Make unicode available. + unicode = str + @implementer(IWriteZopeDublinCore) @adapter(IAnnotatable) @@ -109,7 +116,7 @@ def __init__(self, context): # can't use super() since this isn't a globally available class ZDCAnnotatableAdapter.__init__(self, context) - for dcname, attrname in fieldmap.iteritems(): + for dcname, attrname in six.iteritems(fieldmap): oldprop = ZopeDublinCore.__dict__.get(dcname) if oldprop is None: raise ValueError("%r is not a valid DC field" % dcname) diff --git a/src/zope/dublincore/creatorannotator.py b/src/zope/dublincore/creatorannotator.py index d70823f..6211dc3 100644 --- a/src/zope/dublincore/creatorannotator.py +++ b/src/zope/dublincore/creatorannotator.py @@ -19,6 +19,12 @@ from zope.security.management import queryInteraction from zope.security.proxy import removeSecurityProxy +try: + unicode +except NameError: + # Py3: Make unicode available. + unicode = str + def CreatorAnnotator(object, event=None): """Update Dublin-Core creator property""" if event is None: diff --git a/src/zope/dublincore/dcterms.py b/src/zope/dublincore/dcterms.py index 712cf6f..8cf6f9d 100644 --- a/src/zope/dublincore/dcterms.py +++ b/src/zope/dublincore/dcterms.py @@ -16,6 +16,7 @@ __docformat__ = 'restructuredtext' from zope.dublincore import dcsv +import six # useful namespace URIs DC_NS = "http://purl.org/dc/elements/1.1/" @@ -183,7 +184,7 @@ def check_rfc3066(value): } element_to_name = {} -for name, (qname, attrs) in name_to_element.iteritems(): +for name, (qname, attrs) in six.iteritems(name_to_element): prefix, localname = qname.split(":") elem_name = _prefix_to_ns[prefix], localname element_to_name[elem_name] = name diff --git a/src/zope/dublincore/property.py b/src/zope/dublincore/property.py index b1fcf28..82bee9b 100644 --- a/src/zope/dublincore/property.py +++ b/src/zope/dublincore/property.py @@ -61,7 +61,7 @@ def __set__(self, inst, value): else: value = (value,) field.validate(value) - if field.readonly and inst.__dict__.has_key(name): + if field.readonly and name in inst.__dict__: raise ValueError(name, 'field is readonly') setattr(inst, name, value) @@ -98,7 +98,7 @@ def __set__(self, inst, value): if isinstance(field, schema.Tuple): value = tuple(value) field.validate(value) - if field.readonly and inst.__dict__.has_key(name): + if field.readonly and name in inst.__dict__: raise ValueError(name, 'field is readonly') setattr(inst, name, value) diff --git a/src/zope/dublincore/testing.py b/src/zope/dublincore/testing.py index 60a2d12..9987474 100644 --- a/src/zope/dublincore/testing.py +++ b/src/zope/dublincore/testing.py @@ -14,11 +14,21 @@ """Testing support """ __docformat__ = 'restructuredtext' - +import re from zope import component +from zope.testing import renormalizing + +from .annotatableadapter import ZDCAnnotatableAdapter +from .interfaces import IWriteZopeDublinCore + +checker = renormalizing.RENormalizing([ + # Python 3 unicode removed the "u". + (re.compile("u('.*?')"), + r"\1"), + (re.compile('u(".*?")'), + r"\1"), + ]) -from annotatableadapter import ZDCAnnotatableAdapter -from interfaces import IWriteZopeDublinCore def setUpDublinCore(): component.provideAdapter(ZDCAnnotatableAdapter, diff --git a/src/zope/dublincore/tests/test_partialannotatable.py b/src/zope/dublincore/tests/test_partialannotatable.py index 240ab20..ca1154a 100644 --- a/src/zope/dublincore/tests/test_partialannotatable.py +++ b/src/zope/dublincore/tests/test_partialannotatable.py @@ -8,10 +8,12 @@ from zope.component.testing import tearDown from zope.annotation.attribute import AttributeAnnotations +from zope.dublincore import testing + def setUp(test): zope.component.testing.setUp(test) zope.component.provideAdapter(AttributeAnnotations) def test_suite(): return doctest.DocFileSuite( - "partial.txt", setUp=setUp, tearDown=tearDown) + "partial.txt", setUp=setUp, tearDown=tearDown, checker=testing.checker) diff --git a/src/zope/dublincore/tests/test_property.py b/src/zope/dublincore/tests/test_property.py index 705ab7f..359eb2a 100644 --- a/src/zope/dublincore/tests/test_property.py +++ b/src/zope/dublincore/tests/test_property.py @@ -34,12 +34,10 @@ def tearDown(test): def test_suite(): - return unittest.TestSuite( - ( - doctest.DocFileSuite( - '../property.txt', - setUp=setUp, - tearDown=tearDown, - optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, - ), + return unittest.TestSuite(( + doctest.DocFileSuite( + '../property.txt', + setUp=setUp, tearDown=tearDown, checker=testing.checker, + optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, + ), )) diff --git a/src/zope/dublincore/tests/test_zopedublincore.py b/src/zope/dublincore/tests/test_zopedublincore.py index 7f46a6f..7add44f 100644 --- a/src/zope/dublincore/tests/test_zopedublincore.py +++ b/src/zope/dublincore/tests/test_zopedublincore.py @@ -71,7 +71,7 @@ def testScalars(self): self.assertEqual(getattr(dc, pname), u'blah blah') self.assertEqual(getattr(dc, mname)(), u'blah blah') - self.assertRaises(Exception, setattr, dc, pname, 'foo') + self.assertRaises(Exception, setattr, dc, pname, b'foo') setattr(dc, pname, u'foo') self.assertEqual(getattr(dc, pname), u'foo') self.assertEqual(getattr(dc, mname)(), u'foo') @@ -113,8 +113,8 @@ def testSequences(self): self.assertEqual(v, [u'bar', u'baz', u'baz\u1111', u'foo']) - self.assertRaises(Exception, setattr, dc, pname, 'foo') - self.assertRaises(Exception, setattr, dc, pname, ['foo']) + self.assertRaises(Exception, setattr, dc, pname, b'foo') + self.assertRaises(Exception, setattr, dc, pname, [b'foo']) setattr(dc, pname, [u'high', u'low', u'spam', u'eggs', u'ham', ]) diff --git a/src/zope/dublincore/tests/timeannotators.txt b/src/zope/dublincore/tests/timeannotators.txt index e691ac5..9932488 100644 --- a/src/zope/dublincore/tests/timeannotators.txt +++ b/src/zope/dublincore/tests/timeannotators.txt @@ -90,7 +90,7 @@ It is registered for the ``ObjectModifiedEvent``: Only ``modified`` gets set: ->>> print content.created +>>> print(content.created) None >>> content.modified datetime.datetime(, tzinfo=) @@ -108,7 +108,7 @@ The modified annotator can also be registered for (object, event): ``modified`` gets set, this way, too: ->>> print content.created +>>> print(content.created) None >>> content.modified datetime.datetime(, tzinfo=) diff --git a/src/zope/dublincore/xmlmetadata.py b/src/zope/dublincore/xmlmetadata.py index 6a943db..f1675b3 100644 --- a/src/zope/dublincore/xmlmetadata.py +++ b/src/zope/dublincore/xmlmetadata.py @@ -18,11 +18,15 @@ import xml.sax import xml.sax.handler -from cStringIO import StringIO from xml.sax.saxutils import escape, quoteattr from zope.dublincore import dcterms +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + XSI_TYPE = (dcterms.XSI_NS, "type") @@ -44,7 +48,8 @@ def __init__(self, mapping=None): self._mapping.update(mapping) self._counter = 0 - def encode(self, (uri, localname)): + def encode(self, xxx_todo_changeme): + (uri, localname) = xxx_todo_changeme if not uri: return localname if uri not in self._mapping: @@ -68,8 +73,7 @@ def getPrefixMappings(self): def dumpString(mapping): sio = StringIO() nsmap = NamespaceTracker(DEFAULT_NAMESPACE_PREFIXES) - items = mapping.items() - items.sort() + items = sorted(mapping.items()) prev = None for name, values in items: name, type = dcterms.splitEncoding(name) diff --git a/src/zope/dublincore/zopedublincore.py b/src/zope/dublincore/zopedublincore.py index e16521d..e0a5f9f 100644 --- a/src/zope/dublincore/zopedublincore.py +++ b/src/zope/dublincore/zopedublincore.py @@ -20,6 +20,13 @@ from zope.interface import implementer from zope.datetime import parseDatetimetz from zope.dublincore.interfaces import IZopeDublinCore +import six + +try: + unicode +except NameError: + # Py3: Make unicode available. + unicode = str class SimpleProperty(object): @@ -67,7 +74,10 @@ def __set__(self, inst, value): if not isinstance(value, datetime): raise TypeError("Element must be %s", datetime) - value = unicode(value.isoformat('T'), 'ascii') + value = value.isoformat('T') + # Py3: Python 2 support, where isformat returned bytes. + if isinstance(value, bytes): + value = value.decode('ascii') super(DateProperty, self).__set__(inst, value) @@ -328,13 +338,13 @@ def _set_qualified(self, name, qvalue): data[qualification] = data.get(qualification, ()) + (value, ) self._changed() - for qualification, values in data.iteritems(): + for qualification, values in six.iteritems(data): qname = qualification and (name + '.' + qualification) or name dict[qname] = values def _get_qualified(self, name): result = [] - for aname, avalue in self._mapping.iteritems(): + for aname, avalue in six.iteritems(self._mapping): if aname == name: qualification = u'' diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..40969d8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,23 @@ +[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 = + pytz + six + zope.annotation + zope.component[zcml] + zope.configuration + zope.datetime + zope.interface + zope.lifecycleevent + zope.location + zope.schema + zope.security[zcml] + zope.testing + zope.testrunner