From cc9f097936226845a89d879d49f38894dd032d03 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 11 Sep 2017 11:28:00 -0500 Subject: Fix non-ASCII supplement info under Python 2 and drop Py3.3. Fixes #1. Fix the coverage environment using zope.testrunner (nose no longer works, that same namespace path issue). Also run the doctests on all supported versions. This requires dropping Python 3.3 because sphinx needs 3.4+. --- .coveragerc | 7 +- .travis.yml | 6 +- CHANGES.rst | 8 +- setup.py | 4 +- src/zope/exceptions/exceptionformatter.py | 42 +++-- src/zope/exceptions/log.py | 8 +- .../exceptions/tests/test_exceptionformatter.py | 189 ++++++++++++--------- src/zope/exceptions/tests/test_log.py | 31 +++- tox.ini | 15 +- 9 files changed, 191 insertions(+), 119 deletions(-) diff --git a/.coveragerc b/.coveragerc index af40312..3b86679 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,11 @@ [run] -source = src +source = zope.exceptions [report] +precision = 2 exclude_lines = pragma: no cover + if __name__ == '__main__': + raise NotImplementedError + self.fail + raise AssertionError diff --git a/.travis.yml b/.travis.yml index 19a4c83..830860d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,17 @@ language: python sudo: false python: - 2.7 - - 3.3 - 3.4 - 3.5 - 3.6 - - pypy-5.4.1 + - pypy install: - pip install -U pip setuptools - pip install -U coverage coveralls - - pip install -U -e .[test] + - pip install -U -e .[test,docs] script: - coverage run -m zope.testrunner --test-path=src + - coverage run -a -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest notifications: email: false cache: pip diff --git a/CHANGES.rst b/CHANGES.rst index d919f5a..5d60d0f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,12 @@ - Add support for Python 3.6. +- Drop support for Python 3.3. + +- Fix handling of unicode supplemental traceback information on + Python 2. Now such values are always encoded to UTF-8; previously + the results were undefined and depended on system encodings and the + values themselves. See `issue 1 `_. 4.1.0 (2017-04-12) ================== @@ -76,7 +82,7 @@ 4.0.1 (2012-08-20) ================== -- Fixed optional dependency code for `zope.security` to work under Python 3.3. +- Fixed optional dependency code for `'zope.security`` to work under Python 3.3. 4.0.0.1 (2012-05-16) diff --git a/setup.py b/setup.py index bc2e2d0..bdfdafa 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,6 @@ setup(name='zope.exceptions', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -72,7 +71,7 @@ setup(name='zope.exceptions', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3', ], - url='http://cheeseshop.python.org/pypi/zope.exceptions', + url='https://github.com/zopefoundation/zope.exceptions', license='ZPL 2.1', packages=find_packages('src'), package_dir={'': 'src'}, @@ -89,7 +88,6 @@ setup(name='zope.exceptions', zip_safe=False, extras_require={ 'docs': ['Sphinx', 'repoze.sphinx.autointerface'], - 'testing': ['nose', 'coverage'], 'test': tests_require, }, ) diff --git a/src/zope/exceptions/exceptionformatter.py b/src/zope/exceptions/exceptionformatter.py index fabea9f..47e0f41 100644 --- a/src/zope/exceptions/exceptionformatter.py +++ b/src/zope/exceptions/exceptionformatter.py @@ -46,7 +46,12 @@ class TextExceptionFormatter(object): return limit def formatSupplementLine(self, line): - return ' - %s' % line + result = ' - %s' % line + if not isinstance(result, str): + # Must be an Python 2, and must be a unicode `line` + # and we upconverted the result to a unicode + result = result.encode('utf-8') + return result def formatSourceURL(self, url): return [self.formatSupplementLine(url)] @@ -110,13 +115,13 @@ class TextExceptionFormatter(object): co = f.f_code filename = co.co_filename name = co.co_name - locals = f.f_locals # XXX shadowing normal builtins deliberately? - globals = f.f_globals # XXX shadowing normal builtins deliberately? + f_locals = f.f_locals + f_globals = f.f_globals if self.with_filenames: s = ' File "%s", line %d' % (filename, lineno) else: - modname = globals.get('__name__', filename) + modname = f_globals.get('__name__', filename) s = ' Module %s, line %d' % (modname, lineno) s = s + ', in %s' % name @@ -130,13 +135,13 @@ class TextExceptionFormatter(object): result.append(" " + self.escape(line.strip())) # Output a traceback supplement, if any. - if '__traceback_supplement__' in locals: + if '__traceback_supplement__' in f_locals: # Use the supplement defined in the function. - tbs = locals['__traceback_supplement__'] - elif '__traceback_supplement__' in globals: + tbs = f_locals['__traceback_supplement__'] + elif '__traceback_supplement__' in f_globals: # Use the supplement defined in the module. # This is used by Scripts (Python). - tbs = globals['__traceback_supplement__'] + tbs = f_globals['__traceback_supplement__'] else: tbs = None if tbs is not None: @@ -151,7 +156,7 @@ class TextExceptionFormatter(object): # else just swallow the exception. try: - tbi = locals.get('__traceback_info__', None) + tbi = f_locals.get('__traceback_info__', None) if tbi is not None: result.append(self.formatTracebackInfo(tbi)) except: #pragma: no cover @@ -240,13 +245,23 @@ class HTMLExceptionFormatter(TextExceptionFormatter): line_sep = '
\r\n' def escape(self, s): + if not isinstance(s, str): + try: + s = str(s) + except UnicodeError: + if hasattr(s, 'encode'): + # We probably got a unicode string on + # Python 2. + s = s.encode('utf-8') + else: # pragma: no cover + raise return escape(s, quote=False) def getPrefix(self): return '

Traceback (most recent call last):

\r\n