From 87db9734ec89bfd86ea612770e2cd139d2792789 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 20 Apr 2017 12:46:24 -0500 Subject: [PATCH] Port to Python 3 --- .coveragerc | 6 + .gitignore | 2 + .travis.yml | 16 ++- CHANGES.txt => CHANGES.rst | 3 +- MANIFEST.in | 1 + README.txt => README.rst | 0 setup.cfg | 2 + setup.py | 35 ++++-- src/zope/app/pagetemplate/interfaces.py | 3 +- src/zope/app/pagetemplate/metaconfigure.py | 3 - src/zope/app/pagetemplate/namedtemplate.py | 2 - src/zope/app/pagetemplate/simpleviewclass.py | 1 - src/zope/app/pagetemplate/talesapi.py | 16 +-- src/zope/app/pagetemplate/tests/test_bwc.py | 39 +++++++ .../app/pagetemplate/tests/test_talesapi.py | 109 +++++++----------- .../app/pagetemplate/tests/test_urlquote.py | 41 +++---- src/zope/app/pagetemplate/urlquote.py | 62 +++++----- .../app/pagetemplate/viewpagetemplatefile.py | 1 - tox.ini | 9 ++ 19 files changed, 200 insertions(+), 151 deletions(-) create mode 100644 .coveragerc rename CHANGES.txt => CHANGES.rst (98%) rename README.txt => README.rst (100%) create mode 100644 setup.cfg create mode 100644 src/zope/app/pagetemplate/tests/test_bwc.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..af40312 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = src + +[report] +exclude_lines = + pragma: no cover diff --git a/.gitignore b/.gitignore index 5149542..f25457c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ build/ dist/ *.egg-info/ .tox/ +.coverage +htmlcov diff --git a/.travis.yml b/.travis.yml index 02dca11..78c8af2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,21 @@ language: python +sudo: false python: - 2.7 + - pypy-5.4.1 + - 3.4 + - 3.5 + - 3.6 install: - - pip install . + - pip install -U pip setuptools + - pip install -U coverage coveralls + - pip install -U -e .[test] script: - - python setup.py test -q + - coverage run -m zope.testrunner --test-path=src --auto-color --auto-progress notifications: email: false +after_success: + - coveralls +cache: pip +before_cache: + - rm -f $HOME/.cache/pip/log/debug.log diff --git a/CHANGES.txt b/CHANGES.rst similarity index 98% rename from CHANGES.txt rename to CHANGES.rst index a0e9cfa..f8a5932 100644 --- a/CHANGES.txt +++ b/CHANGES.rst @@ -2,9 +2,10 @@ Changes ======= -3.11.3 (unreleased) +4.0.0 (unreleased) ------------------- +- Add support for Python 3 and PyPy. - Do not explicitly require ``zope.security [untrustedpython]``. Older ``zope.pagetemplate`` versions require it, newer ones do not. diff --git a/MANIFEST.in b/MANIFEST.in index 9b76be2..ba087d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.py include *.txt +include *.rst include buildout.cfg recursive-include src *.pt recursive-include src *.zcml diff --git a/README.txt b/README.rst similarity index 100% rename from README.txt rename to README.rst diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 376f51e..100a2a9 100644 --- a/setup.py +++ b/setup.py @@ -20,29 +20,41 @@ import os.path def read(*rnames): - return open(os.path.join(os.path.dirname(__file__), *rnames)).read() + with open(os.path.join(os.path.dirname(__file__), *rnames)) as f: + return f.read() -version = '3.11.3dev' +version = '4.0.0.dev0' setup(name='zope.app.pagetemplate', version=version, - url='http://pypi.python.org/pypi/zope.app.pagetemplate', + url='http://github.com/zopefoundation/zope.app.pagetemplate', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', description='PageTemplate integration for Zope 3', long_description=( - read('README.txt') + read('README.rst') + '\n\n.. contents::\n\n' + - read('CHANGES.txt') + read('CHANGES.rst') ), license='ZPL 2.1', - classifiers=['Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Zope Public License', - 'Programming Language :: Python', - 'Framework :: Zope3', - ], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Zope Public License', + 'Programming Language :: Python', + 'Programming Language :: Python:: 2.7', + 'Programming Language :: Python:: 3.4', + 'Programming Language :: Python:: 3.5', + 'Programming Language :: Python:: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Topic :: Internet :: WWW/HTTP', + 'Framework :: Zope3' + ], packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope', 'zope.app'], @@ -67,6 +79,7 @@ def read(*rnames): 'zope.component [hook,test]', 'zope.container', 'zope.publisher', + 'zope.testrunner', ], }, zip_safe=False, diff --git a/src/zope/app/pagetemplate/interfaces.py b/src/zope/app/pagetemplate/interfaces.py index 0d7ec86..a97acd1 100644 --- a/src/zope/app/pagetemplate/interfaces.py +++ b/src/zope/app/pagetemplate/interfaces.py @@ -12,9 +12,8 @@ # ############################################################################## """Interfaces for apis to make available to TALES - -$Id$ """ + __docformat__ = 'restructuredtext' import zope.interface diff --git a/src/zope/app/pagetemplate/metaconfigure.py b/src/zope/app/pagetemplate/metaconfigure.py index 39a66e5..118ab79 100644 --- a/src/zope/app/pagetemplate/metaconfigure.py +++ b/src/zope/app/pagetemplate/metaconfigure.py @@ -13,8 +13,6 @@ ############################################################################## """ZCML configuration directives for configuring the default zope: namespace in TALES. - -$Id$ """ __docformat__ = 'restructuredtext' @@ -23,4 +21,3 @@ from zope.browserpage.metadirectives import IExpressionTypeDirective from zope.browserpage.metaconfigure import expressiontype from zope.browserpage.metaconfigure import registerType - diff --git a/src/zope/app/pagetemplate/namedtemplate.py b/src/zope/app/pagetemplate/namedtemplate.py index aeef941..15eb4ec 100644 --- a/src/zope/app/pagetemplate/namedtemplate.py +++ b/src/zope/app/pagetemplate/namedtemplate.py @@ -12,8 +12,6 @@ # ############################################################################## """ - -$Id$ """ # BBB diff --git a/src/zope/app/pagetemplate/simpleviewclass.py b/src/zope/app/pagetemplate/simpleviewclass.py index 69615b9..292b87a 100644 --- a/src/zope/app/pagetemplate/simpleviewclass.py +++ b/src/zope/app/pagetemplate/simpleviewclass.py @@ -13,7 +13,6 @@ ############################################################################## """Simple View Class -$Id$ """ __docformat__ = 'restructuredtext' diff --git a/src/zope/app/pagetemplate/talesapi.py b/src/zope/app/pagetemplate/talesapi.py index 240e57b..7cbb713 100644 --- a/src/zope/app/pagetemplate/talesapi.py +++ b/src/zope/app/pagetemplate/talesapi.py @@ -15,9 +15,10 @@ $Id$ """ + __docformat__ = 'restructuredtext' -from zope.interface import implements +from zope.interface import implementer from zope.size.interfaces import ISized from zope.security.interfaces import Unauthorized from zope.tales.interfaces import ITALESFunctionNamespace @@ -26,43 +27,44 @@ from zope.dublincore.interfaces import IZopeDublinCore from zope.traversing.api import getName +@implementer(IDCTimes, + IDCDescriptiveProperties, + ITALESFunctionNamespace) class ZopeTalesAPI(object): - implements(IDCTimes, IDCDescriptiveProperties, ITALESFunctionNamespace) - def __init__(self, context): self.context = context def setEngine(self, engine): self._engine = engine + @property def title(self): a = IZopeDublinCore(self.context, None) if a is None: raise AttributeError('title') return a.title - title = property(title) + @property def description(self): a = IZopeDublinCore(self.context, None) if a is None: raise AttributeError('description') return a.description - description = property(description) + @property def created(self): a = IZopeDublinCore(self.context, None) if a is None: raise AttributeError('created') return a.created - created = property(created) + @property def modified(self): a = IZopeDublinCore(self.context, None) if a is None: raise AttributeError('modified') return a.modified - modified = property(modified) def name(self): return getName(self.context) diff --git a/src/zope/app/pagetemplate/tests/test_bwc.py b/src/zope/app/pagetemplate/tests/test_bwc.py new file mode 100644 index 0000000..847c707 --- /dev/null +++ b/src/zope/app/pagetemplate/tests/test_bwc.py @@ -0,0 +1,39 @@ +############################################################################## +# +# Copyright (c) 2017 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Tests for deprecated/backwards-compatibility imports. +""" + + +import unittest + +class TestBWC(unittest.TestCase): + + def test_viewpagetemplatefile(self): + from zope.app.pagetemplate import viewpagetemplatefile + + def test_i18n(self): + from zope.app.pagetemplate import i18n + self.assertIsNotNone(i18n.ZopeMessageFactory) + + def test_metaconfigure(self): + from zope.app.pagetemplate import metaconfigure + self.assertIsNotNone(metaconfigure.clear) + + def test_simpleviewclass(self): + from zope.app.pagetemplate import simpleviewclass + self.assertIsNotNone(simpleviewclass.simple) + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/src/zope/app/pagetemplate/tests/test_talesapi.py b/src/zope/app/pagetemplate/tests/test_talesapi.py index d4d24eb..2b7cf43 100644 --- a/src/zope/app/pagetemplate/tests/test_talesapi.py +++ b/src/zope/app/pagetemplate/tests/test_talesapi.py @@ -12,24 +12,24 @@ # ############################################################################## """Tales API Tests - -$Id$ """ + + from datetime import datetime -from doctest import DocTestSuite -from zope.interface import implements +import unittest + +from zope.interface import implementer from zope.size.interfaces import ISized from zope.traversing.interfaces import IPhysicallyLocatable from zope.dublincore.interfaces import IZopeDublinCore from zope.app.pagetemplate.talesapi import ZopeTalesAPI +@implementer(IZopeDublinCore, # not really, but who's checking. ;) + IPhysicallyLocatable, # not really + ISized) class TestObject(object): - implements(IZopeDublinCore, # not really, but who's checking. ;) - IPhysicallyLocatable, # not really - ISized) - description = u"This object stores some number of apples" title = u"apple cart" created = datetime(2000, 10, 1, 23, 11, 00) @@ -44,63 +44,42 @@ def sizeForDisplay(self): def getName(self): return u'apples' -testObject = TestObject() - -def title(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.title - u'apple cart' - """ - -def description(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.description - u'This object stores some number of apples' - """ - -def name(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.name() - u'apples' - """ - -def title_or_name(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.title_or_name() - u'apple cart' - - >>> testObject = TestObject() - >>> testObject.title = u"" - >>> api = ZopeTalesAPI(testObject) - >>> api.title_or_name() - u'apples' - """ - -def size(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.size() - u'5 apples' - """ - -def modified(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.modified - datetime.datetime(2003, 1, 2, 3, 4, 5) - """ - -def created(): - """ - >>> api = ZopeTalesAPI(testObject) - >>> api.created - datetime.datetime(2000, 10, 1, 23, 11) - """ + +class TestAPI(unittest.TestCase): + + def test_title(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject.title, api.title) + + def test_description(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject.description, api.description) + + def test_name(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject().getName(), api.name()) + + def test_title_or_name(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject.title, api.title_or_name()) + + testObject2 = TestObject() + testObject2.title = u"" + api = ZopeTalesAPI(testObject2) + self.assertEqual(u'apples', api.title_or_name()) + + def test_size(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject().sizeForDisplay(), api.size()) + + def test_modified(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject.modified, api.modified) + + def test_created(self): + api = ZopeTalesAPI(TestObject()) + self.assertEqual(TestObject.created, api.created) def test_suite(): - return DocTestSuite() + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/src/zope/app/pagetemplate/tests/test_urlquote.py b/src/zope/app/pagetemplate/tests/test_urlquote.py index 9640fa2..d2f478b 100644 --- a/src/zope/app/pagetemplate/tests/test_urlquote.py +++ b/src/zope/app/pagetemplate/tests/test_urlquote.py @@ -17,7 +17,6 @@ something (and don't really scramble stuff). We are relying on the python urllib to be functional to avoid test duplication. -$Id$ """ import unittest @@ -31,35 +30,25 @@ class TestObject(object): def __str__(self): return "www.google.de" -def quote_simple(): - """ - >>> q = URLQuote(u"www.google.de") - >>> q.quote() - 'www.google.de' - >>> q.unquote() - u'www.google.de' - >>> q.quote_plus() - 'www.google.de' - >>> q.unquote_plus() - u'www.google.de' - """ +class TestQuote(unittest.TestCase): -def quote_cast_needed(): - """ - >>> q = URLQuote(TestObject()) - >>> q.quote() - 'www.google.de' - >>> q.unquote() - u'www.google.de' - >>> q.quote_plus() - 'www.google.de' - >>> q.unquote_plus() - u'www.google.de' - """ + def test_quote_simple(self): + q = URLQuote(u"www.google.de") + self.assertEqual(u'www.google.de', q.quote()) + self.assertEqual(u'www.google.de', q.unquote()) + self.assertEqual(u'www.google.de', q.quote_plus()) + self.assertEqual(u'www.google.de', q.unquote_plus()) + + def test_quote_cast_needed(self): + q = URLQuote(TestObject()) + self.assertEqual(u'www.google.de', q.quote()) + self.assertEqual(u'www.google.de', q.unquote()) + self.assertEqual(u'www.google.de', q.quote_plus()) + self.assertEqual(u'www.google.de', q.unquote_plus()) def test_suite(): return unittest.TestSuite(( - DocTestSuite(), + unittest.makeSuite(TestQuote), DocTestSuite('zope.app.pagetemplate.urlquote'), )) diff --git a/src/zope/app/pagetemplate/urlquote.py b/src/zope/app/pagetemplate/urlquote.py index 434c8cd..f63f675 100644 --- a/src/zope/app/pagetemplate/urlquote.py +++ b/src/zope/app/pagetemplate/urlquote.py @@ -12,15 +12,27 @@ # ############################################################################## """URL quoting for ZPT - -$Id$ """ + __docformat__ = 'restructuredtext' -import urllib -from zope.interface import implements +import six +from six.moves import urllib_parse as urllib + +from zope.interface import implementer from zope.traversing.interfaces import IPathAdapter +from zope.app.pagetemplate.interfaces import IURLQuote + +def _safe_as_text(s): + if isinstance(s, six.text_type): + return s + try: + return six.text_type(s, 'utf-8') + except UnicodeDecodeError: + return s + +@implementer(IPathAdapter, IURLQuote) class URLQuote(object): r"""An adapter for URL quoting. @@ -30,7 +42,7 @@ class URLQuote(object): >>> quoter = URLQuote(u'Roki\u0161kis') >>> quoter.quote() 'Roki%C5%A1kis' - + >>> quoter.quote_plus() 'Roki%C5%A1kis' @@ -38,30 +50,29 @@ class URLQuote(object): UTF-8, and tries to convert it to unicode. >>> quoter = URLQuote('Roki%C5%A1kis') - >>> quoter.unquote() - u'Roki\u0161kis' - - >>> quoter.unquote_plus() - u'Roki\u0161kis' + >>> isinstance(quoter.unquote(), six.text_type) + True + + >>> isinstance(quoter.unquote_plus(), six.text_type) + True If the unquoted string can't be converted to unicode, the unquoted - string is returned. + string is returned. On Python 2, this will be a byte str, but under Python 3, + the returned string is always going to be unicode. >>> quoter = URLQuote('S%F6derk%F6ping') - >>> quoter.unquote() - 'S\xf6derk\xf6ping' + >>> isinstance(quoter.unquote(), str) + True - >>> quoter.unquote_plus() - 'S\xf6derk\xf6ping' + >>> isinstance(quoter.unquote_plus(), str) + True """ - __used_for__ = basestring - implements(IPathAdapter) def __init__(self, context): - if not isinstance(context, basestring): + if not isinstance(context, six.string_types): context = str(context) - elif isinstance(context, unicode): + elif six.PY2 and isinstance(context, six.text_type): context = context.encode('utf-8') self.context = context @@ -75,17 +86,8 @@ def quote_plus(self): def unquote(self): """Return the object's URL unquote representation.""" - unquoted = urllib.unquote(self.context) - try: - return unicode(unquoted, 'utf-8') - except UnicodeDecodeError: - return unquoted + return _safe_as_text(urllib.unquote(self.context)) def unquote_plus(self): """Return the object's URL unquote_plus representation.""" - unquoted = urllib.unquote_plus(self.context) - try: - return unicode(unquoted, 'utf-8') - except UnicodeDecodeError: - return unquoted - + return _safe_as_text(urllib.unquote_plus(self.context)) diff --git a/src/zope/app/pagetemplate/viewpagetemplatefile.py b/src/zope/app/pagetemplate/viewpagetemplatefile.py index d6a2872..3191eb1 100644 --- a/src/zope/app/pagetemplate/viewpagetemplatefile.py +++ b/src/zope/app/pagetemplate/viewpagetemplatefile.py @@ -13,7 +13,6 @@ ############################################################################## """File-based page templates that can be used as methods on views. -$Id$ """ __docformat__ = 'restructuredtext' diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..695f359 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = + py27, pypy, py34, py35, py36 + +[testenv] +commands = + zope-testrunner --test-path=src +deps = + .[test]