From 5e1cd72d4db7cf076e34bb6d86fd03b683ab43a2 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 10:30:50 +0200 Subject: [PATCH 01/14] Configuring for pure-python --- .coveragerc | 19 ---- .editorconfig | 39 +++++++ .github/workflows/tests.yml | 63 +++++++++++ .gitignore | 41 ++++--- .meta.toml | 20 ++++ .travis.yml | 21 ---- MANIFEST.in | 26 ++--- bootstrap.py | 210 ------------------------------------ setup.cfg | 11 ++ tox.ini | 86 +++++++++++---- 10 files changed, 234 insertions(+), 302 deletions(-) delete mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/workflows/tests.yml create mode 100644 .meta.toml delete mode 100644 .travis.yml delete mode 100644 bootstrap.py diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index b5ae1099..00000000 --- a/.coveragerc +++ /dev/null @@ -1,19 +0,0 @@ -[run] -source = zope.i18n -omit = - */flycheck_*py - -[paths] -source = - src/ - .tox/*/lib/python*/site-packages/ - .tox/pypy*/site-packages/ - -[report] -precision = 2 -exclude_lines = - pragma: no cover - if __name__ == '__main__': - raise NotImplementedError - self.fail - raise AssertionError diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c5508b9f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +# +# EditorConfig Configuration file, for more details see: +# http://EditorConfig.org +# EditorConfig is a convention description, that could be interpreted +# by multiple editors to enforce common coding conventions for specific +# file types + +# top-most EditorConfig file: +# Will ignore other EditorConfig files in Home directory or upper tree level. +root = true + + +[*] # For All Files +# Unix-style newlines with a newline ending every file +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset +charset = utf-8 +# Indent style default +indent_style = space +# Max Line Length - a hard line wrap, should be disabled +max_line_length = off + +[*.{py,cfg,ini}] +# 4 space indentation +indent_size = 4 + +[*.{yml,zpt,pt,dtml,zcml}] +# 2 space indentation +indent_size = 2 + +[{Makefile,.gitmodules}] +# Tab indentation (no size specified, but view as 4 spaces) +indent_style = tab +indent_size = unset +tab_width = unset diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9ea114e7 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,63 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 12 * * 0' # run once a week on Sunday + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + strategy: + # We want to see all failures: + fail-fast: false + matrix: + os: + - ubuntu + config: + # [Python version, tox env] + - ["3.8", "lint"] + - ["2.7", "py27"] + - ["3.5", "py35"] + - ["3.6", "py36"] + - ["3.7", "py37"] + - ["3.8", "py38"] + - ["3.9", "py39"] + - ["pypy2", "pypy"] + - ["pypy3", "pypy3"] + - ["3.8", "docs"] + - ["3.8", "coverage"] + + runs-on: ${{ matrix.os }}-latest + name: ${{ matrix.config[1] }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.config[0] }} + - name: Pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.config[0] }}- + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: tox -e ${{ matrix.config[1] }} + - name: Coverage + if: matrix.config[1] == 'coverage' + run: | + pip install coveralls coverage-python-version + coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index fec8ebec..c724a76f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,31 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +*.dll +*.egg-info/ +*.profraw *.pyc +*.pyo *.so -*.dll -*.mo -__pycache__ -src/*.egg-info - -.installed.cfg -.tox -bin -build -develop-eggs -parts -docs/_build/ .coverage +.coverage.* +.eggs/ +.installed.cfg +.mr.developer.cfg +.tox/ +.vscode/ +__pycache__/ +bin/ +build/ coverage.xml -nosetests.xml -htmlcov/ +develop-eggs/ +develop/ +dist/ +docs/_build +eggs/ +etc/ +lib/ +lib64 +log/ +parts/ +pyvenv.cfg +var/ diff --git a/.meta.toml b/.meta.toml new file mode 100644 index 00000000..4a7e6d4b --- /dev/null +++ b/.meta.toml @@ -0,0 +1,20 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +[meta] +template = "pure-python" +commit-id = "7f5b73fe9d2bcbabafaef5f69dd97408a142d42a" + +[python] +with-appveyor = false +with-windows = false +with-pypy = true +with-future-python = false +with-legacy-python = true +with-docs = true +with-sphinx-doctests = false + +[tox] +use-flake8 = true + +[coverage] +fail-under = 100 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7dccb109..00000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: python -dist: xenial -python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - - pypy - - pypy3 -install: - - pip install -U pip setuptools - - pip install -U coverage coveralls - - 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 -after_success: - - coveralls -notifications: - email: false -cache: pip diff --git a/MANIFEST.in b/MANIFEST.in index e4564d6c..38e9c74f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,25 +1,13 @@ -include *.txt +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python include *.rst -include *.py -include *.ini +include *.txt include buildout.cfg -include .travis.yml include tox.ini -include .coveragerc - -recursive-include src *.txt *.py *.dtd *.xml *.html *.po *.mo *.in *.zcml -recursive-include src *.py -recursive-include src *.dtd -recursive-include src *.xml -recursive-include src *.html -recursive-include src *.po -recursive-include src *.mo -recursive-include src *.in -recursive-include src *.zcml -recursive-include src *.rst -recursive-include src/zope/i18n/tests *.mo -recursive-include docs *.rst recursive-include docs *.py -recursive-include docs *.bat +recursive-include docs *.rst +recursive-include docs *.txt recursive-include docs Makefile + +recursive-include src *.py diff --git a/bootstrap.py b/bootstrap.py deleted file mode 100644 index a4599211..00000000 --- a/bootstrap.py +++ /dev/null @@ -1,210 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 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. -# -############################################################################## -"""Bootstrap a buildout-based project - -Simply run this script in a directory containing a buildout.cfg. -The script accepts buildout command-line options, so you can -use the -c option to specify an alternate configuration file. -""" - -import os -import shutil -import sys -import tempfile - -from optparse import OptionParser - -__version__ = '2015-07-01' -# See zc.buildout's changelog if this version is up to date. - -tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') - -usage = '''\ -[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] - -Bootstraps a buildout-based project. - -Simply run this script in a directory containing a buildout.cfg, using the -Python that you want bin/buildout to use. - -Note that by using --find-links to point to local resources, you can keep -this script from going over the network. -''' - -parser = OptionParser(usage=usage) -parser.add_option("--version", - action="store_true", default=False, - help=("Return bootstrap.py 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")) -parser.add_option("--allow-site-packages", - action="store_true", default=False, - help=("Let bootstrap.py use existing site packages")) -parser.add_option("--buildout-version", - help="Use a specific zc.buildout version") -parser.add_option("--setuptools-version", - help="Use a specific setuptools version") -parser.add_option("--setuptools-to-dir", - help=("Allow for re-use of existing directory of " - "setuptools versions")) - -options, args = parser.parse_args() -if options.version: - print("bootstrap.py version %s" % __version__) - sys.exit(0) - - -###################################################################### -# load/install setuptools - -try: - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - -ez = {} -if os.path.exists('ez_setup.py'): - exec(open('ez_setup.py').read(), ez) -else: - exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) - -if not options.allow_site_packages: - # ez_setup imports site, which adds site packages - # this will remove them from the path to ensure that incompatible versions - # of setuptools are not in the path - import site - # inside a virtualenv, there is no 'getsitepackages'. - # We can't remove these reliably - if hasattr(site, 'getsitepackages'): - for sitepackage_path in site.getsitepackages(): - # Strip all site-packages directories from sys.path that - # are not sys.prefix; this is because on Windows - # sys.prefix is a site-package directory. - if sitepackage_path != sys.prefix: - sys.path[:] = [x for x in sys.path - if sitepackage_path not in x] - -setup_args = dict(to_dir=tmpeggs, download_delay=0) - -if options.setuptools_version is not None: - setup_args['version'] = options.setuptools_version -if options.setuptools_to_dir is not None: - setup_args['to_dir'] = options.setuptools_to_dir - -ez['use_setuptools'](**setup_args) -import setuptools -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 - -setuptools_path = ws.find( - pkg_resources.Requirement.parse('setuptools')).location - -# Fix sys.path here as easy_install.pth added before PYTHONPATH -cmd = [sys.executable, '-c', - 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + - '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]) - -requirement = 'zc.buildout' -version = options.buildout_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): - try: - return not parsed_version.is_prerelease - except AttributeError: - # Older setuptools - 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=[setuptools_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) != 0: - raise Exception( - "Failed to execute command:\n%s" % repr(cmd)[1:-1]) - -###################################################################### -# Import and run buildout - -ws.add_entry(tmpeggs) -ws.require(requirement) -import zc.buildout.buildout - -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/setup.cfg b/setup.cfg index 2a9acf13..264b78c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,13 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python [bdist_wheel] universal = 1 + +[flake8] +doctests = 1 + +[check-manifest] +ignore = + .editorconfig + .meta.toml + docs/_build/html/_sources/* diff --git a/tox.ini b/tox.ini index 757b7d47..fd22c86e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,80 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python [tox] +minversion = 3.18 envlist = - py27,py35,py36,py37,pypy,pypy3,coverage,docs + lint + py27 + py35 + py36 + py37 + py38 + py39 + pypy + pypy3 + docs + coverage [testenv] +usedevelop = true +deps = commands = - zope-testrunner --test-path=src {posargs:-pvc} - sphinx-build -b doctest -d {envdir}/doctrees docs {envdir}/doctest + zope-testrunner --test-path=src {posargs:-vc} extras = test - docs - compile -[testenv:coverage] -usedevelop = true -basepython = - python3.6 -commands = - coverage erase - coverage run -p -m zope.testrunner --test-path=src {posargs:-pvc} - coverage run -p -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest - coverage combine - coverage report --fail-under=100 +[testenv:lint] +basepython = python3 +skip_install = true deps = - coverage -parallel_show_output = true + flake8 + check-manifest + check-python-versions + wheel +commands = + flake8 src setup.py + check-manifest + check-python-versions [testenv:docs] -basepython = - python3.6 +basepython = python3 +skip_install = false +# Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it: +deps = Sphinx < 4 +extras = + docs +commands_pre = commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + +[testenv:coverage] +basepython = python3 +allowlist_externals = + mkdir +deps = + coverage + coverage-python-version +commands = + mkdir -p {toxinidir}/parts/htmlcov + coverage run -m zope.testrunner --test-path=src {posargs:-vc} + coverage html + coverage report -m --fail-under=0 + +[coverage:run] +branch = True +plugins = coverage_python_version +source = zope.i18n + +[coverage:report] +precision = 2 +exclude_lines = + pragma: no cover + pragma: nocover + except ImportError: + raise NotImplementedError + if __name__ == '__main__': + self.fail + raise AssertionError + +[coverage:html] +directory = parts/htmlcov From 4e15ebd90d5b31e6fe5161c5aac14e5d038adc85 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Fri, 3 Sep 2021 10:07:09 +0200 Subject: [PATCH 02/14] autopep8 --- docs/conf.py | 6 +-- setup.py | 7 ++- src/zope/i18n/compile.py | 5 ++- src/zope/i18n/config.py | 3 +- src/zope/i18n/format.py | 43 ++++++++++--------- src/zope/i18n/interfaces/__init__.py | 3 -- src/zope/i18n/interfaces/locales.py | 16 ++++--- src/zope/i18n/locales/__init__.py | 10 +++-- src/zope/i18n/locales/fallbackcollator.py | 1 + src/zope/i18n/locales/inheritance.py | 9 ++-- src/zope/i18n/locales/provider.py | 2 +- .../i18n/locales/tests/test_docstrings.py | 4 +- .../locales/tests/test_fallbackcollator.py | 8 ++-- src/zope/i18n/locales/tests/test_locales.py | 2 + .../i18n/locales/tests/test_xmlfactory.py | 10 ++--- src/zope/i18n/locales/xmlfactory.py | 38 ++++++---------- src/zope/i18n/testmessagecatalog.py | 5 ++- src/zope/i18n/tests/test.py | 1 + src/zope/i18n/tests/test_formats.py | 12 +++--- .../i18n/tests/test_gettextmessagecatalog.py | 1 - src/zope/i18n/tests/test_imessagecatalog.py | 4 +- .../i18n/tests/test_itranslationdomain.py | 5 ++- src/zope/i18n/tests/test_negotiator.py | 16 ++++--- src/zope/i18n/tests/test_plurals.py | 6 +-- .../i18n/tests/test_testmessagecatalog.py | 2 + src/zope/i18n/tests/test_translationdomain.py | 2 +- src/zope/i18n/tests/test_zcml.py | 1 + src/zope/i18n/tests/testi18nawareobject.py | 4 +- src/zope/i18n/zcml.py | 4 +- 29 files changed, 120 insertions(+), 110 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0ed66d43..f6fff3fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) sys.path.append(os.path.abspath('../src')) rqmt = pkg_resources.require('zope.i18n')[0] @@ -183,8 +183,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'zopei18n.tex', u'zope.i18n Documentation', - u'Zope Foundation and Contributors', 'manual'), + ('index', 'zopei18n.tex', u'zope.i18n Documentation', + u'Zope Foundation and Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/setup.py b/setup.py index dc3b83c5..297b18de 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,12 @@ import os from setuptools import setup, find_packages + def read(*rnames): with open(os.path.join(os.path.dirname(__file__), *rnames)) as f: return f.read() + def alltests(): import sys import unittest @@ -39,6 +41,7 @@ def alltests(): suites = list(zope.testrunner.find.find_suites(options)) return unittest.TestSuite(suites) + COMPILE_REQUIRES = [ # python-gettext used to be here, but it's now # a fixed requirement. Keep the extra to avoid @@ -93,7 +96,7 @@ def alltests(): url='https://github.com/zopefoundation/zope.i18n', packages=find_packages('src'), package_dir={'': 'src'}, - namespace_packages=['zope',], + namespace_packages=['zope', ], install_requires=[ 'setuptools', 'python-gettext', @@ -116,4 +119,4 @@ def alltests(): test_suite='__main__.alltests', include_package_data=True, zip_safe=False, - ) +) diff --git a/src/zope/i18n/compile.py b/src/zope/i18n/compile.py index 834bf24e..441523dc 100644 --- a/src/zope/i18n/compile.py +++ b/src/zope/i18n/compile.py @@ -11,12 +11,14 @@ HAS_PYTHON_GETTEXT = True + def _safe_mtime(path): try: return os.path.getmtime(path) except (IOError, OSError): return None + def compile_mo_file(domain, lc_messages_path): """Creates or updates a mo file in the locales folder.""" @@ -44,6 +46,7 @@ def compile_mo_file(domain, lc_messages_path): with open(mofile, 'wb') as fd: fd.write(mo.read()) except PoSyntaxError as err: - logger.warning('Syntax error while compiling %s (%s).', pofile, err.msg) + logger.warning( + 'Syntax error while compiling %s (%s).', pofile, err.msg) except (IOError, OSError) as err: logger.warning('Error while compiling %s (%s).', pofile, err) diff --git a/src/zope/i18n/config.py b/src/zope/i18n/config.py index 1636083a..b0068c58 100644 --- a/src/zope/i18n/config.py +++ b/src/zope/i18n/config.py @@ -45,4 +45,5 @@ def _parse_languages(value): #: A set of languages that `zope.i18n.negotiate` will pass to the #: `zope.i18n.interfaces.INegotiator` utility. If this is None, #: no utility will be used. -ALLOWED_LANGUAGES = _parse_languages(os.environ.get(ALLOWED_LANGUAGES_KEY, None)) +ALLOWED_LANGUAGES = _parse_languages( + os.environ.get(ALLOWED_LANGUAGES_KEY, None)) diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index abc3e50c..7b9ba4a2 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -31,7 +31,7 @@ try: NATIVE_NUMBER_TYPES += (long,) except NameError: - pass # Py3 + pass # Py3 def roundHalfUp(n): @@ -139,10 +139,11 @@ def parse(self, text, pattern=None, asObject=True): if not ampm_entry: raise DateTimeParseError( 'Cannot handle 12-hour format without am/pm marker.') - ampm = self.calendar.pm == results[bin_pattern.index(ampm_entry[0])] + ampm = self.calendar.pm == results[bin_pattern.index( + ampm_entry[0])] if hour == 12: ampm = not ampm - ordered[3] = (hour + 12*ampm)%24 + ordered[3] = (hour + 12*ampm) % 24 # Shortcut for the simple int functions dt_fields_map = {'d': 2, 'H': 3, 'm': 4, 's': 5, 'S': 6} @@ -155,7 +156,7 @@ def parse(self, text, pattern=None, asObject=True): # Handle timezones tzinfo = None - pytz_tzinfo = False # If True, we should use pytz specific syntax + pytz_tzinfo = False # If True, we should use pytz specific syntax tz_entry = _findFormattingCharacterInPattern('z', bin_pattern) if ordered[3:] != [None, None, None, None] and tz_entry: length = tz_entry[0][1] @@ -189,7 +190,7 @@ def parse(self, text, pattern=None, asObject=True): datetime.date.today(), datetime.time(*[e or 0 for e in ordered[3:]]))).timetz() return datetime.time( - *[e or 0 for e in ordered[3:]], **{'tzinfo' :tzinfo} + *[e or 0 for e in ordered[3:]], **{'tzinfo': tzinfo} ) if pytz_tzinfo: @@ -198,7 +199,7 @@ def parse(self, text, pattern=None, asObject=True): )) return datetime.datetime( - *[e or 0 for e in ordered], **{'tzinfo' :tzinfo} + *[e or 0 for e in ordered], **{'tzinfo': tzinfo} ) def format(self, obj, pattern=None): @@ -282,19 +283,19 @@ def parse(self, text, pattern=None): if bin_pattern[sign][GROUPING]: regex += self.symbols['group'] min_size += min_size/3 - regex += ']{%i,100}' %(min_size) + regex += ']{%i,100}' % (min_size) if bin_pattern[sign][FRACTION]: max_precision = len(bin_pattern[sign][FRACTION]) min_precision = bin_pattern[sign][FRACTION].count('0') regex += '['+self.symbols['decimal']+']?' - regex += '[0-9]{%i,%i}' %(min_precision, max_precision) + regex += '[0-9]{%i,%i}' % (min_precision, max_precision) if bin_pattern[sign][EXPONENTIAL] != '': regex += self.symbols['exponential'] min_exp_size = bin_pattern[sign][EXPONENTIAL].count('0') pre_symbols = self.symbols['minusSign'] if bin_pattern[sign][EXPONENTIAL][0] == '+': pre_symbols += self.symbols['plusSign'] - regex += '[%s]?[0-9]{%i,100}' %(pre_symbols, min_exp_size) + regex += '[%s]?[0-9]{%i,100}' % (pre_symbols, min_exp_size) regex += ')' if bin_pattern[sign][PADDING3] is not None: regex += '[' + bin_pattern[sign][PADDING3] + ']+' @@ -353,7 +354,7 @@ def _format_fraction(self, fraction, pattern, rounding=True): fractionLen = len(fraction) rounded = int(fraction) + 1 fraction = ('%0' + str(fractionLen) + 'i') % rounded - if len(fraction) > fractionLen: # rounded fraction >= 1 + if len(fraction) > fractionLen: # rounded fraction >= 1 roundInt = True fraction = fraction[1:] else: @@ -494,7 +495,7 @@ def format(self, obj, pattern=None, rounding=True): if bin_pattern[PADDING2] is not None and pre_padding > 0: if bin_pattern[PADDING1] is not None: text += bin_pattern[PADDING2] - else: # pragma: no cover + else: # pragma: no cover text += bin_pattern[PADDING2] * pre_padding text += number if bin_pattern[PADDING3] is not None and post_padding > 0: @@ -578,7 +579,7 @@ def parseDateTimePattern(pattern, DATETIMECHARS="aGyMdEDFwWhHmsSkKz"): # Some cleaning up if state == IN_QUOTE: - if quote_start == -1: # pragma: no cover + if quote_start == -1: # pragma: no cover # It should not be possible to get into this state. # The only time we set quote_start to -1 we also set the state # to DEFAULT. @@ -605,7 +606,7 @@ def buildDateTimeParseInfo(calendar, pattern): for entry in _findFormattingCharacterInPattern(field, pattern): # The maximum amount of digits should be infinity, but 1000 is # close enough here. - info[entry] = r'([0-9]{%i,1000})' %entry[1] + info[entry] = r'([0-9]{%i,1000})' % entry[1] # year (Number) for entry in _findFormattingCharacterInPattern('y', pattern): @@ -618,12 +619,12 @@ def buildDateTimeParseInfo(calendar, pattern): # am/pm marker (Text) for entry in _findFormattingCharacterInPattern('a', pattern): - info[entry] = r'(%s|%s)' %(calendar.am, calendar.pm) + info[entry] = r'(%s|%s)' % (calendar.am, calendar.pm) # era designator (Text) # TODO: works for gregorian only right now for entry in _findFormattingCharacterInPattern('G', pattern): - info[entry] = r'(%s|%s)' %(calendar.eras[1][1], calendar.eras[2][1]) + info[entry] = r'(%s|%s)' % (calendar.eras[1][1], calendar.eras[2][1]) # time zone (Text) for entry in _findFormattingCharacterInPattern('z', pattern): @@ -676,7 +677,7 @@ def buildDateTimeInfo(dt, calendar, pattern): else: ampm = calendar.am - h = dt.hour%12 + h = dt.hour % 12 if h == 0: h = 12 @@ -693,7 +694,7 @@ def buildDateTimeInfo(dt, calendar, pattern): tz_mins = int(math.fabs(tz_secs % 3600 / 60)) tz_hours = int(math.fabs(tz_secs / 3600)) tz_sign = '-' if tz_secs < 0 else '+' - tz_defaultname = "%s%i%.2i" %(tz_sign, tz_hours, tz_mins) + tz_defaultname = "%s%i%.2i" % (tz_sign, tz_hours, tz_mins) tz_name = tzinfo.tzname(dt) or tz_defaultname tz_fullname = getattr(tzinfo, 'zone', None) or tz_name @@ -705,12 +706,12 @@ def buildDateTimeInfo(dt, calendar, pattern): # Generic Numbers for field, value in (('d', dt.day), ('D', int(dt.strftime('%j'))), ('F', day_of_week_in_month), ('k', dt.hour or 24), - ('K', dt.hour%12), ('h', h), ('H', dt.hour), + ('K', dt.hour % 12), ('h', h), ('H', dt.hour), ('m', dt.minute), ('s', dt.second), ('S', dt.microsecond), ('w', int(dt.strftime('%W'))), ('W', week_in_month)): for entry in _findFormattingCharacterInPattern(field, pattern): - info[entry] = (u"%%.%ii" %entry[1]) %value + info[entry] = (u"%%.%ii" % entry[1]) % value # am/pm marker (Text) for entry in _findFormattingCharacterInPattern('a', pattern): @@ -724,9 +725,9 @@ def buildDateTimeInfo(dt, calendar, pattern): # time zone (Text) for entry in _findFormattingCharacterInPattern('z', pattern): if entry[1] == 1: - info[entry] = u"%s%i%.2i" %(tz_sign, tz_hours, tz_mins) + info[entry] = u"%s%i%.2i" % (tz_sign, tz_hours, tz_mins) elif entry[1] == 2: - info[entry] = u"%s%.2i:%.2i" %(tz_sign, tz_hours, tz_mins) + info[entry] = u"%s%.2i:%.2i" % (tz_sign, tz_hours, tz_mins) elif entry[1] == 3: info[entry] = tz_name else: diff --git a/src/zope/i18n/interfaces/__init__.py b/src/zope/i18n/interfaces/__init__.py index c5a33289..e7171080 100644 --- a/src/zope/i18n/interfaces/__init__.py +++ b/src/zope/i18n/interfaces/__init__.py @@ -203,7 +203,6 @@ class IMessageImportFilter(Interface): Classes implementing this interface should usually be Adaptors, as they adapt the IEditableTranslationService interface.""" - def importMessages(domains, languages, file): """Import all messages that are defined in the specified domains and languages. @@ -254,7 +253,6 @@ class IMessageExportFilter(Interface): Classes implementing this interface should usually be Adaptors, as they adapt the IEditableTranslationService interface.""" - def exportMessages(domains, languages): """Export all messages that are defined in the specified domains and languages. @@ -326,7 +324,6 @@ def format(obj, pattern=None): """Format an object to a string using the pattern as a rule.""" - class INumberFormat(IFormat): r"""Specific number formatting interface. Here are the formatting rules (I modified the rules from ICU a bit, since I think they did not diff --git a/src/zope/i18n/interfaces/locales.py b/src/zope/i18n/interfaces/locales.py index f6017ad5..01c9cbec 100644 --- a/src/zope/i18n/interfaces/locales.py +++ b/src/zope/i18n/interfaces/locales.py @@ -17,7 +17,7 @@ import re from zope.interface import Interface, Attribute from zope.schema import \ - Field, Text, TextLine, Int, Bool, Tuple, List, Dict, Date + Field, Text, TextLine, Int, Bool, Tuple, List, Dict, Date from zope.schema import Choice @@ -188,7 +188,6 @@ class ILocaleTimeZone(Interface): required=True, readonly=True) - names = Dict( title=u"Time Zone Names", description=u"Various names of the timezone.", @@ -230,7 +229,7 @@ class ILocaleFormatLength(Interface): title=u"Format Length Type", description=u"Name of the format length", values=(u"full", u"long", u"medium", u"short") - ) + ) default = TextLine( title=u"Default Format", @@ -491,6 +490,7 @@ class ILocaleCurrency(Interface): symbolChoice = Bool(title=u"Symbol Choice") + class ILocaleNumbers(Interface): """This object contains various data about numbers and currencies.""" @@ -556,7 +556,6 @@ class ILocaleNumbers(Interface): description=u"Name of the format length"), value_type=Field(title=u"ILocaleCurrency object")) - def getFormatter(category, length=None, name=u""): """Get the NumberFormat based on the category, length and name of the format. @@ -577,8 +576,11 @@ def getFormatter(category, length=None, name=u""): def getDefaultCurrency(): """Get the default currency.""" + _orientations = [u"left-to-right", u"right-to-left", u"top-to-bottom", u"bottom-to-top"] + + class ILocaleOrientation(Interface): """Information about the orientation of text.""" @@ -586,13 +588,14 @@ class ILocaleOrientation(Interface): title=u"Orientation of characters", values=_orientations, default=u"left-to-right" - ) + ) lines = Choice( title=u"Orientation of characters", values=_orientations, default=u"top-to-bottom" - ) + ) + class ILocale(Interface): """This class contains all important information about the locale. @@ -699,6 +702,7 @@ def __getitem__(key): object is consulted. """ + class ICollator(Interface): """Provide support for collating text strings diff --git a/src/zope/i18n/locales/__init__.py b/src/zope/i18n/locales/__init__.py index 030c4535..9da515ad 100644 --- a/src/zope/i18n/locales/__init__.py +++ b/src/zope/i18n/locales/__init__.py @@ -29,7 +29,7 @@ from zope.i18n.interfaces.locales import ILocaleDayContext, ILocaleMonthContext from zope.i18n.format import NumberFormat, DateTimeFormat from zope.i18n.locales.inheritance import \ - AttributeInheritance, InheritingDictionary, NoParentException + AttributeInheritance, InheritingDictionary, NoParentException from zope.i18n.locales.provider import LocaleProvider, LoadLocaleError @@ -73,6 +73,7 @@ 'islamic-civil': ('civil-arabic',), 'buddhist': ('thai-buddhist', )} + @implementer(ILocaleIdentity) class LocaleIdentity(object): """Represents a unique identification of the locale @@ -112,7 +113,7 @@ def __init__(self, language=None, script=None, territory=None, variant=None): def __repr__(self): """See zope.i18n.interfaces.ILocaleIdentity """ - return "" %( + return "" % ( self.language, self.script, self.territory, self.variant) @@ -158,6 +159,7 @@ def __eq__(self, other): return ((self.generationDate, self.number) == (other.generationDate, other.number)) + @implementer(ILocaleDisplayNames) class LocaleDisplayNames(AttributeInheritance): """Locale display names with inheritable data. @@ -233,7 +235,6 @@ class LocaleFormatLength(AttributeInheritance): """Specifies one of the format lengths of a specific quantity, like numbers, dates, times and datetimes.""" - def __init__(self, type=None): """Initialize the object.""" self.type = type @@ -652,6 +653,7 @@ class LocaleOrientation(AttributeInheritance): """Implementation of ILocaleOrientation """ + @implementer(ILocale) class Locale(AttributeInheritance): """Implementation of the ILocale interface.""" @@ -690,7 +692,7 @@ def getLocaleID(self): # Notice that 'pieces' is always empty. pieces = [key + '=' + type for (key, type) in ()] assert not pieces - if pieces: # pragma: no cover + if pieces: # pragma: no cover id_string += '@' + ','.join(pieces) return id_string diff --git a/src/zope/i18n/locales/fallbackcollator.py b/src/zope/i18n/locales/fallbackcollator.py index fc76a580..55c9b774 100644 --- a/src/zope/i18n/locales/fallbackcollator.py +++ b/src/zope/i18n/locales/fallbackcollator.py @@ -16,6 +16,7 @@ from unicodedata import normalize + class FallbackCollator: def __init__(self, locale): diff --git a/src/zope/i18n/locales/inheritance.py b/src/zope/i18n/locales/inheritance.py index 912d8c9a..e597b789 100644 --- a/src/zope/i18n/locales/inheritance.py +++ b/src/zope/i18n/locales/inheritance.py @@ -24,11 +24,13 @@ from zope.interface import implementer from zope.i18n.interfaces.locales import \ - ILocaleInheritance, IAttributeInheritance, IDictionaryInheritance + ILocaleInheritance, IAttributeInheritance, IDictionaryInheritance + class NoParentException(AttributeError): pass + @implementer(ILocaleInheritance) class Inheritance(object): """A simple base version of locale inheritance. @@ -37,7 +39,6 @@ class Inheritance(object): 'ILocaleInheritance' implementations. """ - # See zope.i18n.interfaces.locales.ILocaleInheritance __parent__ = None @@ -100,7 +101,6 @@ class AttributeInheritance(Inheritance): True """ - def __setattr__(self, name, value): """See zope.i18n.interfaces.locales.ILocaleInheritance""" # If we have a value that can also inherit data from other locales, we @@ -111,7 +111,6 @@ def __setattr__(self, name, value): value.__name__ = name super(AttributeInheritance, self).__setattr__(name, value) - def __getattr__(self, name): """See zope.i18n.interfaces.locales.ILocaleInheritance""" try: @@ -134,7 +133,6 @@ def __getattr__(self, name): return value - @implementer(IDictionaryInheritance) class InheritingDictionary(Inheritance, dict): """Implementation of a dictionary that can also inherit values. @@ -197,7 +195,6 @@ class InheritingDictionary(Inheritance, dict): `value` is a deprecated synonym for `values` """ - def __setitem__(self, name, value): """See zope.i18n.interfaces.locales.ILocaleInheritance""" if ILocaleInheritance.providedBy(value): diff --git a/src/zope/i18n/locales/provider.py b/src/zope/i18n/locales/provider.py index 3864b7ae..9e7f66b7 100644 --- a/src/zope/i18n/locales/provider.py +++ b/src/zope/i18n/locales/provider.py @@ -20,6 +20,7 @@ from zope.interface import implementer from zope.i18n.interfaces.locales import ILocaleProvider + class LoadLocaleError(Exception): """This error is raised if a locale cannot be loaded.""" @@ -28,7 +29,6 @@ class LoadLocaleError(Exception): class LocaleProvider(object): """A locale provider that gets its data from the XML data.""" - def __init__(self, locale_dir): self._locales = {} self._locale_dir = locale_dir diff --git a/src/zope/i18n/locales/tests/test_docstrings.py b/src/zope/i18n/locales/tests/test_docstrings.py index fa520782..9f75b442 100644 --- a/src/zope/i18n/locales/tests/test_docstrings.py +++ b/src/zope/i18n/locales/tests/test_docstrings.py @@ -20,6 +20,7 @@ from zope.i18n.testing import unicode_checker + class LocaleInheritanceStub(AttributeInheritance): def __init__(self, nextLocale=None): @@ -36,7 +37,8 @@ def test_suite(): DocTestSuite('zope.i18n.locales', checker=unicode_checker), DocTestSuite('zope.i18n.locales.inheritance', checker=unicode_checker), DocTestSuite('zope.i18n.locales.xmlfactory', checker=unicode_checker), - )) + )) + if __name__ == '__main__': unittest.main() diff --git a/src/zope/i18n/locales/tests/test_fallbackcollator.py b/src/zope/i18n/locales/tests/test_fallbackcollator.py index 7e10b50d..e7c84a80 100644 --- a/src/zope/i18n/locales/tests/test_fallbackcollator.py +++ b/src/zope/i18n/locales/tests/test_fallbackcollator.py @@ -17,11 +17,13 @@ from zope.i18n.testing import unicode_checker + def test_suite(): return unittest.TestSuite(( - doctest.DocFileSuite('../fallbackcollator.txt', checker=unicode_checker), - )) + doctest.DocFileSuite('../fallbackcollator.txt', + checker=unicode_checker), + )) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') - diff --git a/src/zope/i18n/locales/tests/test_locales.py b/src/zope/i18n/locales/tests/test_locales.py index 2b100b83..523b1d2a 100644 --- a/src/zope/i18n/locales/tests/test_locales.py +++ b/src/zope/i18n/locales/tests/test_locales.py @@ -24,6 +24,7 @@ import zope.i18n datadir = os.path.join(os.path.dirname(zope.i18n.__file__), 'locales', 'data') + class AbstractTestILocaleProviderMixin(object): """Test the functionality of an implmentation of the ILocaleProvider interface.""" @@ -143,6 +144,7 @@ def test_getLocale(self): self.assertEqual(locale.id.territory, 'GB') self.assertEqual(locale.id.variant, None) + class TestRootLocale(TestCase): """There were some complaints that the root locale does not work correctly, so make sure it does.""" diff --git a/src/zope/i18n/locales/tests/test_xmlfactory.py b/src/zope/i18n/locales/tests/test_xmlfactory.py index 792c96df..6b52dd2b 100644 --- a/src/zope/i18n/locales/tests/test_xmlfactory.py +++ b/src/zope/i18n/locales/tests/test_xmlfactory.py @@ -19,6 +19,7 @@ from zope.i18n.locales.xmlfactory import LocaleFactory import zope.i18n + class LocaleXMLFileTestCase(TestCase): """This test verifies that every locale XML file can be loaded.""" @@ -33,14 +34,14 @@ def runTest(self): # XXX: The tests below are commented out because it's not # necessary for the xml files to have all format definitions. - ## Making sure all number format patterns parse - #for category in (u'decimal', u'scientific', u'percent', u'currency'): + # Making sure all number format patterns parse + # for category in (u'decimal', u'scientific', u'percent', u'currency'): # for length in getattr(locale.numbers, category+'Formats').values(): # for format in length.formats.values(): # self.assert_(parseNumberPattern(format.pattern) is not None) - ## Making sure all datetime patterns parse - #for calendar in locale.dates.calendars.values(): + # Making sure all datetime patterns parse + # for calendar in locale.dates.calendars.values(): # for category in ('date', 'time', 'dateTime'): # for length in getattr(calendar, category+'Formats').values(): # for format in length.formats.values(): @@ -48,7 +49,6 @@ def runTest(self): # parseDateTimePattern(format.pattern) is not None) - def test_suite(): suite = TestSuite() locale_dir = os.path.join(os.path.dirname(zope.i18n.__file__), diff --git a/src/zope/i18n/locales/xmlfactory.py b/src/zope/i18n/locales/xmlfactory.py index 5b2dbf51..5dbcee08 100644 --- a/src/zope/i18n/locales/xmlfactory.py +++ b/src/zope/i18n/locales/xmlfactory.py @@ -41,7 +41,6 @@ def _getText(self, nodelist): rc = rc + node.data return rc - def _extractVersion(self, identity_node): """Extract the Locale's version info based on data from the DOM tree. @@ -81,7 +80,6 @@ def _extractVersion(self, identity_node): return LocaleVersion(number, generationDate, notes) - def _extractIdentity(self): """Extract the Locale's identity object based on info from the DOM tree. @@ -119,7 +117,7 @@ def _extractIdentity(self): # Retrieve the language of the locale nodes = identity.getElementsByTagName('language') if nodes != []: - id.language = nodes[0].getAttribute('type') or None + id.language = nodes[0].getAttribute('type') or None # Retrieve the territory of the locale nodes = identity.getElementsByTagName('territory') if nodes != []: @@ -132,7 +130,6 @@ def _extractIdentity(self): id.version = self._extractVersion(identity) return id - def _extractTypes(self, names_node): """Extract all types from the names_node. @@ -179,7 +176,6 @@ def _extractTypes(self, names_node): types[(type, key)] = self._getText(type_node.childNodes) return types - def _extractDisplayNames(self): """Extract all display names from the DOM tree. @@ -285,7 +281,6 @@ def _extractDisplayNames(self): displayNames.types = types return displayNames - def _extractMonths(self, months_node, calendar): """Extract all month entries from cal_node and store them in calendar. @@ -385,14 +380,15 @@ def _extractMonths(self, months_node, calendar): defaultMonthContext_node = months_node.getElementsByTagName('default') if defaultMonthContext_node: - calendar.defaultMonthContext = defaultMonthContext_node[0].getAttribute('type') + calendar.defaultMonthContext = defaultMonthContext_node[0].getAttribute( + 'type') monthContext_nodes = months_node.getElementsByTagName('monthContext') if not monthContext_nodes: return calendar.monthContexts = InheritingDictionary() - names_node = abbrs_node = None # BBB + names_node = abbrs_node = None # BBB for node in monthContext_nodes: context_type = node.getAttribute('type') @@ -441,7 +437,6 @@ def _extractMonths(self, months_node, calendar): calendar.months[type] = (names.get(type, None), abbrs.get(type, None)) - def _extractDays(self, days_node, calendar): """Extract all day entries from cal_node and store them in calendar. @@ -529,14 +524,15 @@ def _extractDays(self, days_node, calendar): defaultDayContext_node = days_node.getElementsByTagName('default') if defaultDayContext_node: - calendar.defaultDayContext = defaultDayContext_node[0].getAttribute('type') + calendar.defaultDayContext = defaultDayContext_node[0].getAttribute( + 'type') dayContext_nodes = days_node.getElementsByTagName('dayContext') if not dayContext_nodes: return calendar.dayContexts = InheritingDictionary() - names_node = abbrs_node = None # BBB + names_node = abbrs_node = None # BBB for node in dayContext_nodes: context_type = node.getAttribute('type') @@ -584,7 +580,6 @@ def _extractDays(self, days_node, calendar): calendar.days[type] = (names.get(type, None), abbrs.get(type, None)) - def _extractWeek(self, cal_node, calendar): """Extract all week entries from cal_node and store them in calendar. @@ -644,7 +639,6 @@ def _extractWeek(self, cal_node, calendar): time_args = map(int, node.getAttribute('time').split(':')) calendar.week['weekendEnd'] = (day, time(*time_args)) - def _extractEras(self, cal_node, calendar): """Extract all era entries from cal_node and store them in calendar. @@ -703,8 +697,8 @@ def _extractEras(self, cal_node, calendar): calendar.eras = InheritingDictionary() for type in abbrs.keys(): - calendar.eras[type] = (names.get(type, None), abbrs.get(type, None)) - + calendar.eras[type] = (names.get(type, None), + abbrs.get(type, None)) def _extractFormats(self, formats_node, lengthNodeName, formatNodeName): """Extract all format entries from formats_node and return a @@ -772,7 +766,8 @@ def _extractFormats(self, formats_node, lengthNodeName, formatNodeName): format.pattern = self._getText(pattern_node.childNodes) name_nodes = format_node.getElementsByTagName('displayName') if name_nodes: - format.displayName = self._getText(name_nodes[0].childNodes) + format.displayName = self._getText( + name_nodes[0].childNodes) length.formats[format.type] = format lengths[length.type] = length @@ -925,7 +920,6 @@ def _extractCalendars(self, dates_node): return calendars - def _extractTimeZones(self, dates_node): """Extract all timezone information for the locale from the DOM tree. @@ -1009,7 +1003,6 @@ def _extractTimeZones(self, dates_node): return zones - def _extractDates(self): """Extract all date information from the DOM tree""" dates_nodes = self._data.getElementsByTagName('dates') @@ -1025,7 +1018,6 @@ def _extractDates(self): dates.timezones = timezones return dates - def _extractSymbols(self, numbers_node): """Extract all week entries from cal_node and store them in calendar. @@ -1081,7 +1073,6 @@ def _extractSymbols(self, numbers_node): return symbols - def _extractNumberFormats(self, numbers_node, numbers): """Extract all number formats from the numbers_node and save the data in numbers. @@ -1174,7 +1165,6 @@ def _extractNumberFormats(self, numbers_node, numbers): setattr(numbers, defaultName, default) setattr(numbers, formatsName, formats) - def _extractCurrencies(self, numbers_node): """Extract all currency definitions and their information from the Locale's DOM tree. @@ -1232,7 +1222,7 @@ def _extractCurrencies(self, numbers_node): if nodes: currency.symbol = self._getText(nodes[0].childNodes) currency.symbolChoice = \ - nodes[0].getAttribute('choice') == u"true" + nodes[0].getAttribute('choice') == u"true" nodes = curr_node.getElementsByTagName('displayName') if nodes: @@ -1242,7 +1232,6 @@ def _extractCurrencies(self, numbers_node): return currencies - def _extractNumbers(self): """Extract all number information from the DOM tree""" numbers_nodes = self._data.getElementsByTagName('numbers') @@ -1259,7 +1248,6 @@ def _extractNumbers(self): numbers.currencies = currencies return numbers - def _extractDelimiters(self): """Extract all delimiter entries from the DOM tree. @@ -1315,7 +1303,6 @@ def _extractDelimiters(self): return delimiters - def _extractOrientation(self): """Extract orientation information. @@ -1345,7 +1332,6 @@ def _extractOrientation(self): setattr(orientation, name, value) return orientation - def __call__(self): """Create the Locale.""" locale = Locale(self._extractIdentity()) diff --git a/src/zope/i18n/testmessagecatalog.py b/src/zope/i18n/testmessagecatalog.py index 8420c581..a06b15a4 100644 --- a/src/zope/i18n/testmessagecatalog.py +++ b/src/zope/i18n/testmessagecatalog.py @@ -18,6 +18,7 @@ import zope.i18n.interfaces from zope.i18n.translationdomain import TranslationDomain + @interface.implementer(zope.i18n.interfaces.IGlobalMessageCatalog) class TestMessageCatalog(object): @@ -43,13 +44,15 @@ def getIdentifier(self): def reload(self): pass + @interface.implementer(zope.i18n.interfaces.ITranslationDomain) def TestMessageFallbackDomain(domain_id=u""): domain = TranslationDomain(domain_id) domain.addCatalog(TestMessageCatalog(domain_id)) return domain + interface.directlyProvides( TestMessageFallbackDomain, zope.i18n.interfaces.IFallbackTranslationDomainFactory, - ) +) diff --git a/src/zope/i18n/tests/test.py b/src/zope/i18n/tests/test.py index 4fb4f4c9..05e13668 100644 --- a/src/zope/i18n/tests/test.py +++ b/src/zope/i18n/tests/test.py @@ -22,6 +22,7 @@ def test_suite(): options = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + def suite(name): return doctest.DocTestSuite( name, diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py index 83c91292..24791b4f 100644 --- a/src/zope/i18n/tests/test_formats.py +++ b/src/zope/i18n/tests/test_formats.py @@ -34,6 +34,7 @@ class LocaleStub(object): pass + class LocaleCalendarStub(object): type = u"gregorian" @@ -102,13 +103,13 @@ def getDayTypeFromAbbreviation(self, abbr): class _TestCase(TestCase): # Avoid deprecation warnings in Python 3 by making the preferred # method name available for Python 2. - assertRaisesRegex = getattr(TestCase, 'assertRaisesRegex', TestCase.assertRaisesRegexp) + assertRaisesRegex = getattr( + TestCase, 'assertRaisesRegex', TestCase.assertRaisesRegexp) class TestDateTimePatternParser(_TestCase): """Extensive tests for the ICU-based-syntax datetime pattern parser.""" - def testParseSimpleTimePattern(self): self.assertEqual(parseDateTimePattern('HH'), [('H', 2)]) @@ -217,7 +218,8 @@ def testGenericNumbers(self): for char in 'dDFkKhHmsSwW': for length in range(1, 6): self.assertEqual(self.info((char, length)), - '([0-9]{%i,1000})' %length) + '([0-9]{%i,1000})' % length) + def testYear(self): self.assertEqual(self.info(('y', 2)), '([0-9]{2})') self.assertEqual(self.info(('y', 4)), '([0-9]{4})') @@ -674,7 +676,6 @@ def testFormatUnusualFormats(self): "F. EEEE 'im' MMMM, yyyy"), u"2. Freitag im Januar, 2003") - def testFormatGregorianEra(self): self.assertEqual( self.format.format(datetime.date(2017, 12, 17), 'G'), @@ -1329,7 +1330,7 @@ def testFormatThousandSeparatorInteger(self): def testFormatBadThousandSeparator(self): self.assertRaises(ValueError, - self.format.format, 23341, '0,') + self.format.format, 23341, '0,') def testFormatDecimal(self): self.assertEqual(self.format.format(23341.02357, '###0.0#'), @@ -1348,7 +1349,6 @@ def testRounding(self): self.assertEqual(self.format.format(1.9999, '0.000'), '2.000') self.assertEqual(self.format.format(1.9999, '0.0000'), '1.9999') - def testFormatScientificDecimal(self): self.assertEqual(self.format.format(23341.02357, '0.00####E00'), '2.334102E04') diff --git a/src/zope/i18n/tests/test_gettextmessagecatalog.py b/src/zope/i18n/tests/test_gettextmessagecatalog.py index 07a09323..5bd93dfa 100644 --- a/src/zope/i18n/tests/test_gettextmessagecatalog.py +++ b/src/zope/i18n/tests/test_gettextmessagecatalog.py @@ -27,6 +27,5 @@ def _getMessageCatalog(self): catalog = GettextMessageCatalog('en', 'default', self._path) return catalog - def _getUniqueIndentifier(self): return self._path diff --git a/src/zope/i18n/tests/test_imessagecatalog.py b/src/zope/i18n/tests/test_imessagecatalog.py index 803d4f0e..36fb43cf 100644 --- a/src/zope/i18n/tests/test_imessagecatalog.py +++ b/src/zope/i18n/tests/test_imessagecatalog.py @@ -21,7 +21,6 @@ class TestIMessageCatalog(unittest.TestCase): - # This should be overridden by every class that inherits this test def _getMessageCatalog(self): raise NotImplementedError() @@ -29,7 +28,6 @@ def _getMessageCatalog(self): def _getUniqueIndentifier(self): raise NotImplementedError() - def setUp(self): self._catalog = self._getMessageCatalog() @@ -63,4 +61,4 @@ def testGetIdentifier(self): def test_suite(): - return unittest.TestSuite() # Deliberately empty + return unittest.TestSuite() # Deliberately empty diff --git a/src/zope/i18n/tests/test_itranslationdomain.py b/src/zope/i18n/tests/test_itranslationdomain.py index 79c385fd..85e4c6a8 100644 --- a/src/zope/i18n/tests/test_itranslationdomain.py +++ b/src/zope/i18n/tests/test_itranslationdomain.py @@ -28,16 +28,17 @@ text_type = str if bytes is not str else unicode + @implementer(IUserPreferredLanguages) class Environment(object): - def __init__(self, langs=()): self.langs = langs def getPreferredLanguages(self): return self.langs + class TestITranslationDomain(PlacelessSetup): # This should be overwritten by every class that inherits this test @@ -108,4 +109,4 @@ def testNoTargetLanguage(self): def test_suite(): - return unittest.TestSuite() # Deliberately empty + return unittest.TestSuite() # Deliberately empty diff --git a/src/zope/i18n/tests/test_negotiator.py b/src/zope/i18n/tests/test_negotiator.py index f8a0336a..ff855e24 100644 --- a/src/zope/i18n/tests/test_negotiator.py +++ b/src/zope/i18n/tests/test_negotiator.py @@ -20,6 +20,7 @@ from zope.component.testing import PlacelessSetup from zope.interface import implementer + @implementer(IUserPreferredLanguages) class Env(object): @@ -39,12 +40,12 @@ def setUp(self): def test_findLanguages(self): _cases = ( - (('en','de'), ('en','de','fr'), 'en'), - (('en'), ('it','de','fr'), None), - (('pt-br','de'), ('pt_BR','de','fr'), 'pt_BR'), - (('pt-br','en'), ('pt', 'en', 'fr'), 'pt'), - (('pt-br','en-us', 'de'), ('de', 'en', 'fr'), 'en'), - ) + (('en', 'de'), ('en', 'de', 'fr'), 'en'), + (('en'), ('it', 'de', 'fr'), None), + (('pt-br', 'de'), ('pt_BR', 'de', 'fr'), 'pt_BR'), + (('pt-br', 'en'), ('pt', 'en', 'fr'), 'pt'), + (('pt-br', 'en-us', 'de'), ('de', 'en', 'fr'), 'en'), + ) for user_pref_langs, obj_langs, expected in _cases: env = Env(user_pref_langs) @@ -55,7 +56,8 @@ def test_findLanguages(self): def test_suite(): return unittest.TestSuite(( unittest.makeSuite(NegotiatorTest), - )) + )) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/zope/i18n/tests/test_plurals.py b/src/zope/i18n/tests/test_plurals.py index d4f57c75..bc97bf3f 100644 --- a/src/zope/i18n/tests/test_plurals.py +++ b/src/zope/i18n/tests/test_plurals.py @@ -160,18 +160,18 @@ def test_floater(self): self.assertEqual(catalog.getPluralMessage( 'The item is rated 1/5 star.', 'The item is rated %s/5 stars.', 3.5), - 'The item is rated 3.5/5 stars.') + 'The item is rated 3.5/5 stars.') # It's cast either to an int or a float because of the %s in # the translation string. self.assertEqual(catalog.getPluralMessage( 'There is %d chance.', 'There are %f chances.', 1.5), - 'There are 1.500000 chances.') + 'There are 1.500000 chances.') self.assertEqual(catalog.getPluralMessage( 'There is %d chance.', 'There are %f chances.', 3.5), - 'There are 3.500000 chances.') + 'There are 3.500000 chances.') def test_translate_without_defaults(self): domain = self._getTranslationDomain('en') diff --git a/src/zope/i18n/tests/test_testmessagecatalog.py b/src/zope/i18n/tests/test_testmessagecatalog.py index ce5adc65..df426fcc 100644 --- a/src/zope/i18n/tests/test_testmessagecatalog.py +++ b/src/zope/i18n/tests/test_testmessagecatalog.py @@ -15,10 +15,12 @@ import unittest import doctest + def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('../testmessagecatalog.rst') )) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/zope/i18n/tests/test_translationdomain.py b/src/zope/i18n/tests/test_translationdomain.py index f8efbd9b..550ec859 100644 --- a/src/zope/i18n/tests/test_translationdomain.py +++ b/src/zope/i18n/tests/test_translationdomain.py @@ -18,7 +18,7 @@ from zope.i18n.translationdomain import TranslationDomain from zope.i18n.gettextmessagecatalog import GettextMessageCatalog from zope.i18n.tests.test_itranslationdomain import \ - TestITranslationDomain, Environment + TestITranslationDomain, Environment from zope.i18nmessageid import MessageFactory from zope.i18n.interfaces import ITranslationDomain diff --git a/src/zope/i18n/tests/test_zcml.py b/src/zope/i18n/tests/test_zcml.py index 52ee4375..e04362dc 100644 --- a/src/zope/i18n/tests/test_zcml.py +++ b/src/zope/i18n/tests/test_zcml.py @@ -37,6 +37,7 @@ %s """ + class DirectivesTest(PlacelessSetup, unittest.TestCase): # This test suite needs the [zcml] and [compile] extra dependencies diff --git a/src/zope/i18n/tests/testi18nawareobject.py b/src/zope/i18n/tests/testi18nawareobject.py index 21a61f4f..996416bb 100644 --- a/src/zope/i18n/tests/testi18nawareobject.py +++ b/src/zope/i18n/tests/testi18nawareobject.py @@ -54,6 +54,7 @@ def getAvailableLanguages(self): # ############################################################ + class AbstractTestII18nAwareMixin(object): def setUp(self): @@ -73,7 +74,8 @@ def testSetDefaultLanguage(self): self.assertEqual(self.object.getDefaultLanguage(), 'lt') def testGetAvailableLanguages(self): - self.assertEqual(sorted(self.object.getAvailableLanguages()), ['en', 'fr', 'lt']) + self.assertEqual(sorted(self.object.getAvailableLanguages()), [ + 'en', 'fr', 'lt']) class TestI18nAwareObject(AbstractTestII18nAwareMixin, unittest.TestCase): diff --git a/src/zope/i18n/zcml.py b/src/zope/i18n/zcml.py index 4df9ccbe..6934485c 100644 --- a/src/zope/i18n/zcml.py +++ b/src/zope/i18n/zcml.py @@ -45,14 +45,14 @@ class IRegisterTranslationsDirective(Interface): title=u"Directory", description=u"Directory containing the translations", required=True - ) + ) domain = TextLine( title=u"Domain", description=(u"Translation domain to register. If not specified, " u"all domains found in the directory are registered"), required=False - ) + ) def allow_language(lang): From 171ecc86e49776b238e5b36af98efddb892053ab Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Fri, 3 Sep 2021 10:09:45 +0200 Subject: [PATCH 03/14] More aggressive autopep8. With `--ignore W690,E711,E721 --aggressive` --- docs/conf.py | 9 +++--- src/zope/i18n/format.py | 42 ++++++++++++++------------ src/zope/i18n/locales/__init__.py | 13 +++++--- src/zope/i18n/locales/xmlfactory.py | 13 ++++---- src/zope/i18n/tests/test_formats.py | 41 +++++++++++++------------ src/zope/i18n/tests/test_negotiator.py | 10 +++--- src/zope/i18n/translationdomain.py | 2 +- 7 files changed, 70 insertions(+), 60 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f6fff3fd..02f419ea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ sys.path.append(os.path.abspath('../src')) rqmt = pkg_resources.require('zope.i18n')[0] -# -- General configuration ----------------------------------------------------- +# -- General configuration ----------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -77,7 +77,8 @@ # for source files. exclude_trees = ['_build'] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. default_role = 'obj' # If true, '()' will be appended to :func: etc. cross-reference text. @@ -98,7 +99,7 @@ #modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. @@ -172,7 +173,7 @@ htmlhelp_basename = 'zopei18ndoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output -------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index 7b9ba4a2..eaec5d1f 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -143,7 +143,7 @@ def parse(self, text, pattern=None, asObject=True): ampm_entry[0])] if hour == 12: ampm = not ampm - ordered[3] = (hour + 12*ampm) % 24 + ordered[3] = (hour + 12 * ampm) % 24 # Shortcut for the simple int functions dt_fields_map = {'d': 2, 'H': 3, 'm': 4, 's': 5, 'S': 6} @@ -236,7 +236,7 @@ def __init__(self, pattern=None, symbols=()): self.symbols = { u"decimal": u".", u"group": u",", - u"list": u";", + u"list": u";", u"percentSign": u"%", u"nativeZeroDigit": u"0", u"patternDigit": u"#", @@ -282,12 +282,12 @@ def parse(self, text, pattern=None): min_size = bin_pattern[sign][INTEGER].count('0') if bin_pattern[sign][GROUPING]: regex += self.symbols['group'] - min_size += min_size/3 + min_size += min_size / 3 regex += ']{%i,100}' % (min_size) if bin_pattern[sign][FRACTION]: max_precision = len(bin_pattern[sign][FRACTION]) min_precision = bin_pattern[sign][FRACTION].count('0') - regex += '['+self.symbols['decimal']+']?' + regex += '[' + self.symbols['decimal'] + ']?' regex += '[0-9]{%i,%i}' % (min_precision, max_precision) if bin_pattern[sign][EXPONENTIAL] != '': regex += self.symbols['exponential'] @@ -327,13 +327,14 @@ def parse(self, text, pattern=None): num_str = num_str.replace(self.symbols['exponential'], 'E') if self.type: type = self.type - return sign*type(num_str) + return sign * type(num_str) def _format_integer(self, integer, pattern): size = len(integer) min_size = pattern.count('0') if size < min_size: - integer = self.symbols['nativeZeroDigit']*(min_size-size) + integer + integer = self.symbols['nativeZeroDigit'] * \ + (min_size - size) + integer return integer def _format_fraction(self, fraction, pattern, rounding=True): @@ -362,8 +363,8 @@ def _format_fraction(self, fraction, pattern, rounding=True): roundInt = True if precision < min_precision: - fraction += self.symbols['nativeZeroDigit']*(min_precision - - precision) + fraction += self.symbols['nativeZeroDigit'] * (min_precision - + precision) if fraction != '': fraction = self.symbols['decimal'] + fraction return fraction, roundInt @@ -440,16 +441,16 @@ def format(self, obj, pattern=None, rounding=True): # abs() of number smaller 1 if len(obj_int_frac) > 1: res = re.match('(0*)[0-9]*', obj_int_frac[1]).groups()[0] - exponent = self._format_integer(str(len(res)+1), + exponent = self._format_integer(str(len(res) + 1), exp_bin_pattern) - exponent = self.symbols['minusSign']+exponent + exponent = self.symbols['minusSign'] + exponent number = obj_int_frac[1][len(res):] else: # We have exactly 0 exponent = self._format_integer('0', exp_bin_pattern) number = self.symbols['nativeZeroDigit'] else: - exponent = self._format_integer(str(len(obj_int_frac[0])-1), + exponent = self._format_integer(str(len(obj_int_frac[0]) - 1), exp_bin_pattern) number = ''.join(obj_int_frac) @@ -484,13 +485,13 @@ def format(self, obj, pattern=None, rounding=True): if bin_pattern[GROUPING]: integer = self._group(integer, bin_pattern[GROUPING]) pre_padding = len(bin_pattern[INTEGER]) - len(integer) - post_padding = len(bin_pattern[FRACTION]) - len(fraction)+1 + post_padding = len(bin_pattern[FRACTION]) - len(fraction) + 1 number = integer + fraction # Put it all together text = '' if bin_pattern[PADDING1] is not None and pre_padding > 0: - text += bin_pattern[PADDING1]*pre_padding + text += bin_pattern[PADDING1] * pre_padding text += bin_pattern[PREFIX] if bin_pattern[PADDING2] is not None and pre_padding > 0: if bin_pattern[PADDING1] is not None: @@ -502,10 +503,10 @@ def format(self, obj, pattern=None, rounding=True): if bin_pattern[PADDING4] is not None: text += bin_pattern[PADDING3] else: - text += bin_pattern[PADDING3]*post_padding + text += bin_pattern[PADDING3] * post_padding text += bin_pattern[SUFFIX] if bin_pattern[PADDING4] is not None and post_padding > 0: - text += bin_pattern[PADDING4]*post_padding + text += bin_pattern[PADDING4] * post_padding # TODO: Need to make sure unicode is everywhere return text_type(text) @@ -644,9 +645,10 @@ def buildDateTimeParseInfo(calendar, pattern): elif entry[1] == 2: info[entry] = r'([0-9]{2})' elif entry[1] == 3: - info[entry] = r'('+'|'.join(calendar.getMonthAbbreviations())+')' + info[entry] = r'(' + \ + '|'.join(calendar.getMonthAbbreviations()) + ')' else: - info[entry] = r'('+'|'.join(calendar.getMonthNames())+')' + info[entry] = r'(' + '|'.join(calendar.getMonthNames()) + ')' # day in week (Text and Number) for entry in _findFormattingCharacterInPattern('E', pattern): @@ -655,9 +657,9 @@ def buildDateTimeParseInfo(calendar, pattern): elif entry[1] == 2: info[entry] = r'([0-9]{2})' elif entry[1] == 3: - info[entry] = r'('+'|'.join(calendar.getDayAbbreviations())+')' + info[entry] = r'(' + '|'.join(calendar.getDayAbbreviations()) + ')' else: - info[entry] = r'('+'|'.join(calendar.getDayNames())+')' + info[entry] = r'(' + '|'.join(calendar.getDayNames()) + ')' return info @@ -965,7 +967,7 @@ def parseNumberPattern(pattern): last_index = -1 for index, char in enumerate(reversed(integer)): if char == ",": - grouping += (index-last_index-1,) + grouping += (index - last_index - 1,) last_index = index # use last group ad infinitum grouping += (0,) diff --git a/src/zope/i18n/locales/__init__.py b/src/zope/i18n/locales/__init__.py index 9da515ad..82159ff6 100644 --- a/src/zope/i18n/locales/__init__.py +++ b/src/zope/i18n/locales/__init__.py @@ -103,7 +103,8 @@ class LocaleIdentity(object): """ - def __init__(self, language=None, script=None, territory=None, variant=None): + def __init__(self, language=None, script=None, + territory=None, variant=None): """Initialize object.""" self.language = language self.script = script @@ -344,7 +345,8 @@ def __init__(self, type): def getMonthNames(self): """See zope.i18n.interfaces.ILocaleCalendar""" - return [self.months.get(type, (None, None))[0] for type in range(1, 13)] + return [self.months.get(type, (None, None))[0] + for type in range(1, 13)] def getMonthTypeFromName(self, name): """See zope.i18n.interfaces.ILocaleCalendar""" @@ -354,7 +356,8 @@ def getMonthTypeFromName(self, name): def getMonthAbbreviations(self): """See zope.i18n.interfaces.ILocaleCalendar""" - return [self.months.get(type, (None, None))[1] for type in range(1, 13)] + return [self.months.get(type, (None, None))[1] + for type in range(1, 13)] def getMonthTypeFromAbbreviation(self, abbr): """See zope.i18n.interfaces.ILocaleCalendar""" @@ -506,11 +509,11 @@ def getFormatter(self, category, length=None, name=None, cal = self.calendars[calendar] - formats = getattr(cal, category+'Formats') + formats = getattr(cal, category + 'Formats') if length is None: length = getattr( cal, - 'default'+category[0].upper()+category[1:]+'Format', + 'default' + category[0].upper() + category[1:] + 'Format', list(formats.keys())[0]) # 'datetime' is always a bit special; we often do not have a length diff --git a/src/zope/i18n/locales/xmlfactory.py b/src/zope/i18n/locales/xmlfactory.py index 5dbcee08..5710d19a 100644 --- a/src/zope/i18n/locales/xmlfactory.py +++ b/src/zope/i18n/locales/xmlfactory.py @@ -759,7 +759,8 @@ def _extractFormats(self, formats_node, lengthNodeName, formatNodeName): if length_node.getElementsByTagName(formatNodeName): length.formats = InheritingDictionary() - for format_node in length_node.getElementsByTagName(formatNodeName): + for format_node in length_node.getElementsByTagName( + formatNodeName): format = LocaleFormat() format.type = format_node.getAttribute('type') or None pattern_node = format_node.getElementsByTagName('pattern')[0] @@ -909,7 +910,7 @@ def _extractCalendars(self, dates_node): default, formats = self._extractFormats( formats_nodes[0], lengthName, formatName) setattr(calendar, - 'default'+formatName[0].upper()+formatName[1:], + 'default' + formatName[0].upper() + formatName[1:], default) setattr(calendar, formatsName, formats) @@ -1153,10 +1154,10 @@ def _extractNumberFormats(self, numbers_node, numbers): """ for category in ('decimal', 'scientific', 'percent', 'currency'): - formatsName = category+'Formats' - lengthName = category+'FormatLength' - formatName = category+'Format' - defaultName = 'default'+formatName[0].upper()+formatName[1:] + formatsName = category + 'Formats' + lengthName = category + 'FormatLength' + formatName = category + 'Format' + defaultName = 'default' + formatName[0].upper() + formatName[1:] formats_nodes = numbers_node.getElementsByTagName(formatsName) if formats_nodes: diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py index 24791b4f..4ca039ed 100644 --- a/src/zope/i18n/tests/test_formats.py +++ b/src/zope/i18n/tests/test_formats.py @@ -72,7 +72,8 @@ class LocaleCalendarStub(object): week = {'firstDay': 1, 'minDays': 1} def getMonthNames(self): - return [self.months.get(type, (None, None))[0] for type in range(1, 13)] + return [self.months.get(type, (None, None))[0] + for type in range(1, 13)] def getMonthTypeFromName(self, name): for item in self.months.items(): @@ -80,7 +81,8 @@ def getMonthTypeFromName(self, name): return item[0] def getMonthAbbreviations(self): - return [self.months.get(type, (None, None))[1] for type in range(1, 13)] + return [self.months.get(type, (None, None))[1] + for type in range(1, 13)] def getMonthTypeFromAbbreviation(self, abbr): for item in self.months.items(): @@ -230,7 +232,8 @@ def testYear(self): def testAMPMMarker(self): names = ['vorm.', 'nachm.'] for length in range(1, 6): - self.assertEqual(self.info(('a', length)), '('+'|'.join(names)+')') + self.assertEqual(self.info(('a', length)), + '(' + '|'.join(names) + ')') def testEra(self): self.assertEqual(self.info(('G', 1)), '(v. Chr.|n. Chr.)') @@ -250,12 +253,12 @@ def testMonthNames(self): names = [u"Januar", u"Februar", u"Maerz", u"April", u"Mai", u"Juni", u"Juli", u"August", u"September", u"Oktober", u"November", u"Dezember"] - self.assertEqual(self.info(('M', 4)), '('+'|'.join(names)+')') + self.assertEqual(self.info(('M', 4)), '(' + '|'.join(names) + ')') def testMonthAbbr(self): names = ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] - self.assertEqual(self.info(('M', 3)), '('+'|'.join(names)+')') + self.assertEqual(self.info(('M', 3)), '(' + '|'.join(names) + ')') def testWeekdayNumber(self): self.assertEqual(self.info(('E', 1)), '([0-9])') @@ -264,13 +267,13 @@ def testWeekdayNumber(self): def testWeekdayNames(self): names = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'] - self.assertEqual(self.info(('E', 4)), '('+'|'.join(names)+')') - self.assertEqual(self.info(('E', 5)), '('+'|'.join(names)+')') - self.assertEqual(self.info(('E', 10)), '('+'|'.join(names)+')') + self.assertEqual(self.info(('E', 4)), '(' + '|'.join(names) + ')') + self.assertEqual(self.info(('E', 5)), '(' + '|'.join(names) + ')') + self.assertEqual(self.info(('E', 10)), '(' + '|'.join(names) + ')') def testWeekdayAbbr(self): names = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] - self.assertEqual(self.info(('E', 3)), '('+'|'.join(names)+')') + self.assertEqual(self.info(('E', 3)), '(' + '|'.join(names) + ')') class TestDateTimeFormat(_TestCase): @@ -468,10 +471,10 @@ def testFormatAllWeekdays(self): for day in range(1, 8): self.assertEqual( self.format.format( - datetime.datetime(2003, 1, day+5, 21, 48), + datetime.datetime(2003, 1, day + 5, 21, 48), "EEEE, d. MMMM yyyy H:mm' Uhr 'z"), '%s, %i. Januar 2003 21:48 Uhr +000' % ( - self.format.calendar.days[day][0], day+5)) + self.format.calendar.days[day][0], day + 5)) def testFormatTimeZone(self): self.assertEqual( @@ -712,7 +715,7 @@ def testParsePosNegAlternativeIntegerPattern(self): self.assertEqual( parseNumberPattern('###0;#0'), ((None, '', None, '###0', '', '', None, '', None, ()), - (None, '', None, '#0', '', '', None, '', None, ()))) + (None, '', None, '#0', '', '', None, '', None, ()))) def testParsePrefixedIntegerPattern(self): self.assertEqual( @@ -754,7 +757,7 @@ def testParsePosNegAlternativeFractionPattern(self): self.assertEqual( parseNumberPattern('###0.00#;#0.0#'), ((None, '', None, '###0', '00#', '', None, '', None, ()), - (None, '', None, '#0', '0#', '', None, '', None, ()))) + (None, '', None, '#0', '0#', '', None, '', None, ()))) def testParsePosNegFractionPattern(self): self.assertEqual( @@ -1426,27 +1429,27 @@ def testFormatSmallNumbers(self): def testFormatHighPrecisionNumbers(self): self.assertEqual( self.format.format( - 1+1e-7, '(#0.00#####);(-#0.00#####)'), + 1 + 1e-7, '(#0.00#####);(-#0.00#####)'), '(1.0000001)') self.assertEqual( self.format.format( - 1+1e-7, '(#0.00###)'), + 1 + 1e-7, '(#0.00###)'), '(1.00000)') self.assertEqual( self.format.format( - 1+1e-9, '(#0.00#######);(-#0.00#######)'), + 1 + 1e-9, '(#0.00#######);(-#0.00#######)'), '(1.000000001)') self.assertEqual( self.format.format( - 1+1e-9, '(#0.00###)'), + 1 + 1e-9, '(#0.00###)'), '(1.00000)') self.assertEqual( self.format.format( - 1+1e-12, '(#0.00##########);(-#0.00##########)'), + 1 + 1e-12, '(#0.00##########);(-#0.00##########)'), '(1.000000000001)') self.assertEqual( self.format.format( - 1+1e-12, '(#0.00###)'), + 1 + 1e-12, '(#0.00###)'), '(1.00000)') def testNoRounding(self): diff --git a/src/zope/i18n/tests/test_negotiator.py b/src/zope/i18n/tests/test_negotiator.py index ff855e24..ef6a4700 100644 --- a/src/zope/i18n/tests/test_negotiator.py +++ b/src/zope/i18n/tests/test_negotiator.py @@ -40,11 +40,11 @@ def setUp(self): def test_findLanguages(self): _cases = ( - (('en', 'de'), ('en', 'de', 'fr'), 'en'), - (('en'), ('it', 'de', 'fr'), None), - (('pt-br', 'de'), ('pt_BR', 'de', 'fr'), 'pt_BR'), - (('pt-br', 'en'), ('pt', 'en', 'fr'), 'pt'), - (('pt-br', 'en-us', 'de'), ('de', 'en', 'fr'), 'en'), + (('en', 'de'), ('en', 'de', 'fr'), 'en'), + (('en'), ('it', 'de', 'fr'), None), + (('pt-br', 'de'), ('pt_BR', 'de', 'fr'), 'pt_BR'), + (('pt-br', 'en'), ('pt', 'en', 'fr'), 'pt'), + (('pt-br', 'en-us', 'de'), ('de', 'en', 'fr'), 'en'), ) for user_pref_langs, obj_langs, expected in _cases: diff --git a/src/zope/i18n/translationdomain.py b/src/zope/i18n/translationdomain.py index b9287f0d..155e6c24 100644 --- a/src/zope/i18n/translationdomain.py +++ b/src/zope/i18n/translationdomain.py @@ -85,7 +85,7 @@ def translate(self, msgid, mapping=None, context=None, msgid_plural, default_plural, number) def _recursive_translate(self, msgid, mapping, target_language, default, - context, msgid_plural, default_plural, number, + context, msgid_plural, default_plural, number, seen=None): """Recursively translate msg.""" # MessageID attributes override arguments From 55532a6b34e9aafc7cd44044c0fbb762351b147c Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Fri, 3 Sep 2021 10:10:42 +0200 Subject: [PATCH 04/14] autopep8 in full aggressive mode. --- src/zope/i18n/testmessagecatalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zope/i18n/testmessagecatalog.py b/src/zope/i18n/testmessagecatalog.py index a06b15a4..b88b3d71 100644 --- a/src/zope/i18n/testmessagecatalog.py +++ b/src/zope/i18n/testmessagecatalog.py @@ -29,7 +29,7 @@ def __init__(self, domain): def queryMessage(self, msgid, default=None): default = getattr(msgid, 'default', default) - if default != None and default != msgid: + if default is not None and default != msgid: msg = u"%s (%s)" % (msgid, default) else: msg = msgid From 332629aa14660a10d1e37f02d35847f3229f0844 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 10:50:47 +0200 Subject: [PATCH 05/14] Manually fix other lint errors. --- src/zope/i18n/__init__.py | 4 +++- src/zope/i18n/compile.py | 11 ++++++----- src/zope/i18n/interfaces/__init__.py | 6 +++--- src/zope/i18n/locales/__init__.py | 4 +++- src/zope/i18n/locales/tests/test_xmlfactory.py | 13 ++++++++----- src/zope/i18n/locales/xmlfactory.py | 6 ++++-- src/zope/i18n/negotiator.py | 4 ++-- src/zope/i18n/tests/test_zcml.py | 3 ++- 8 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/zope/i18n/__init__.py b/src/zope/i18n/__init__.py index dd525ea8..aed9ded8 100644 --- a/src/zope/i18n/__init__.py +++ b/src/zope/i18n/__init__.py @@ -16,7 +16,9 @@ import re from zope.component import queryUtility -from zope.i18nmessageid import MessageFactory, Message +from zope.i18nmessageid import Message +# I expect that MessageFactory might be there for BBB reasons, but not sure. +from zope.i18nmessageid import MessageFactory # noqa from zope.i18n.config import ALLOWED_LANGUAGES from zope.i18n.interfaces import INegotiator diff --git a/src/zope/i18n/compile.py b/src/zope/i18n/compile.py index 441523dc..81a6a171 100644 --- a/src/zope/i18n/compile.py +++ b/src/zope/i18n/compile.py @@ -36,11 +36,12 @@ def compile_mo_file(domain, lc_messages_path): if po_mtime > mo_mtime: try: - # Msgfmt.getAsFile returns io.BytesIO on Python 3, and cStringIO.StringIO - # on Python 2; sadly StringIO isn't a proper context manager, so we have to - # wrap it with `closing`. Also, Msgfmt doesn't properly close a file - # it opens for reading if you pass the path, but it does if you pass - # the file. + # Msgfmt.getAsFile returns io.BytesIO on Python 3, + # and cStringIO.StringIO on Python 2; + # sadly StringIO isn't a proper context manager, so we have to + # wrap it with `closing`. Also, Msgfmt doesn't properly close a + # file it opens for reading if you pass the path, + # but it does if you pass the file. with open(pofile, 'rb') as pofd: with closing(Msgfmt(pofd, domain).getAsFile()) as mo: with open(mofile, 'wb') as fd: diff --git a/src/zope/i18n/interfaces/__init__.py b/src/zope/i18n/interfaces/__init__.py index e7171080..025c4775 100644 --- a/src/zope/i18n/interfaces/__init__.py +++ b/src/zope/i18n/interfaces/__init__.py @@ -370,9 +370,9 @@ class INumberFormat(IFormat): \u00A4 This is the currency sign. it will be replaced by a currency symbol. If it is present in a pattern, the monetary decimal separator is used instead of the decimal separator. - \u00A4\u00A4 This is the international currency sign. It will be replaced - by an international currency symbol. If it is present in a - pattern, the monetary decimal separator is used instead of + \u00A4\u00A4 This is the international currency sign. It will be + replaced by an international currency symbol. If it is present + in a pattern, the monetary decimal separator is used instead of the decimal separator. X Any other characters can be used in the prefix or suffix ' Used to quote special characters in a prefix or suffix diff --git a/src/zope/i18n/locales/__init__.py b/src/zope/i18n/locales/__init__.py index 82159ff6..12009a68 100644 --- a/src/zope/i18n/locales/__init__.py +++ b/src/zope/i18n/locales/__init__.py @@ -30,7 +30,9 @@ from zope.i18n.format import NumberFormat, DateTimeFormat from zope.i18n.locales.inheritance import \ AttributeInheritance, InheritingDictionary, NoParentException -from zope.i18n.locales.provider import LocaleProvider, LoadLocaleError +# LoadLocaleError is not used, but might be imported from here by others. +from zope.i18n.locales.provider import LoadLocaleError # noqa +from zope.i18n.locales.provider import LocaleProvider # Setup the locale directory diff --git a/src/zope/i18n/locales/tests/test_xmlfactory.py b/src/zope/i18n/locales/tests/test_xmlfactory.py index 6b52dd2b..0dc070e2 100644 --- a/src/zope/i18n/locales/tests/test_xmlfactory.py +++ b/src/zope/i18n/locales/tests/test_xmlfactory.py @@ -42,11 +42,14 @@ def runTest(self): # Making sure all datetime patterns parse # for calendar in locale.dates.calendars.values(): - # for category in ('date', 'time', 'dateTime'): - # for length in getattr(calendar, category+'Formats').values(): - # for format in length.formats.values(): - # self.assert_( - # parseDateTimePattern(format.pattern) is not None) + # for category in ('date', 'time', 'dateTime'): + # for length in getattr( + # calendar, category + 'Formats' + # ).values(): + # for format in length.formats.values(): + # self.assert_( + # parseDateTimePattern(format.pattern) is not None + # ) def test_suite(): diff --git a/src/zope/i18n/locales/xmlfactory.py b/src/zope/i18n/locales/xmlfactory.py index 5710d19a..0520c2df 100644 --- a/src/zope/i18n/locales/xmlfactory.py +++ b/src/zope/i18n/locales/xmlfactory.py @@ -345,7 +345,8 @@ def _extractMonths(self, months_node, calendar): >>> names[7:] [u'August', u'September', u'Oktober', u'November', u'Dezember'] - >>> abbrs = [ctx.months[u"abbreviated"][type] for type in range(1,13)] + >>> abbrs = [ctx.months[u"abbreviated"][type] + ... for type in range(1,13)] >>> abbrs[:6] [u'Jan', u'Feb', u'Mrz', u'Apr', u'Mai', u'Jun'] >>> abbrs[6:] @@ -1312,7 +1313,8 @@ def _extractOrientation(self): >>> xml = u''' ... ... - ... + ... ... ... ''' >>> dom = parseString(xml) diff --git a/src/zope/i18n/negotiator.py b/src/zope/i18n/negotiator.py index 3c5fa17d..3ece2219 100644 --- a/src/zope/i18n/negotiator.py +++ b/src/zope/i18n/negotiator.py @@ -29,8 +29,8 @@ def normalize_langs(langs): # Make a mapping from normalized->original so we keep can match # the normalized lang and return the original string. n_langs = {} - for l in langs: - n_langs[normalize_lang(l)] = l + for lang in langs: + n_langs[normalize_lang(lang)] = lang return n_langs diff --git a/src/zope/i18n/tests/test_zcml.py b/src/zope/i18n/tests/test_zcml.py index e04362dc..0467a404 100644 --- a/src/zope/i18n/tests/test_zcml.py +++ b/src/zope/i18n/tests/test_zcml.py @@ -157,7 +157,8 @@ def testRegisterTranslationsForDomain(self): xmlconfig.string( template % ''' - + ''', self.context) path = os.path.join(os.path.dirname(zope.i18n.tests.__file__), From 74cf43f3dd2d3108548fa0ecc091711ef2ab4eb2 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 11:15:07 +0200 Subject: [PATCH 06/14] Add _compat module to define text_type on Py 2 and 3. The old way worked, but gave a linting error because unicode is not defined. --- src/zope/i18n/__init__.py | 5 +++-- src/zope/i18n/_compat.py | 8 ++++++++ src/zope/i18n/format.py | 3 ++- src/zope/i18n/simpletranslationdomain.py | 4 +--- src/zope/i18n/tests/test_itranslationdomain.py | 3 +-- src/zope/i18n/tests/test_zcml.py | 2 +- src/zope/i18n/translationdomain.py | 3 +-- 7 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 src/zope/i18n/_compat.py diff --git a/src/zope/i18n/__init__.py b/src/zope/i18n/__init__.py index aed9ded8..b737d005 100644 --- a/src/zope/i18n/__init__.py +++ b/src/zope/i18n/__init__.py @@ -17,15 +17,16 @@ from zope.component import queryUtility from zope.i18nmessageid import Message -# I expect that MessageFactory might be there for BBB reasons, but not sure. +# MessageFactory is not used, but it might be here for BBB reasons, +# as it could be imported by other packages. from zope.i18nmessageid import MessageFactory # noqa +from zope.i18n._compat import text_type from zope.i18n.config import ALLOWED_LANGUAGES from zope.i18n.interfaces import INegotiator from zope.i18n.interfaces import ITranslationDomain from zope.i18n.interfaces import IFallbackTranslationDomainFactory -text_type = str if bytes is not str else unicode # Set up regular expressions for finding interpolation variables in text. # NAME_RE must exactly match the expression of the same name in the diff --git a/src/zope/i18n/_compat.py b/src/zope/i18n/_compat.py new file mode 100644 index 00000000..00fb20fe --- /dev/null +++ b/src/zope/i18n/_compat.py @@ -0,0 +1,8 @@ +# This gives a linting error because unicode is not defined on Python 3: +# text_type = str if bytes is not str else unicode +try: + # Python 3 + text_type = unicode +except NameError: + # Python 2 + text_type = str diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index eaec5d1f..05431e03 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -23,10 +23,11 @@ import pytz import pytz.reference +from zope.i18n._compat import text_type from zope.i18n.interfaces import IDateTimeFormat, INumberFormat from zope.interface import implementer -text_type = str if bytes is not str else unicode + NATIVE_NUMBER_TYPES = (int, float) try: NATIVE_NUMBER_TYPES += (long,) diff --git a/src/zope/i18n/simpletranslationdomain.py b/src/zope/i18n/simpletranslationdomain.py index b093db8f..b20385ae 100644 --- a/src/zope/i18n/simpletranslationdomain.py +++ b/src/zope/i18n/simpletranslationdomain.py @@ -15,13 +15,11 @@ """ from zope.interface import implementer from zope.component import getUtility +from zope.i18n._compat import text_type from zope.i18n.interfaces import ITranslationDomain, INegotiator from zope.i18n import interpolate -text_type = str if bytes is not str else unicode - - @implementer(ITranslationDomain) class SimpleTranslationDomain(object): """This is the simplest implementation of the ITranslationDomain I diff --git a/src/zope/i18n/tests/test_itranslationdomain.py b/src/zope/i18n/tests/test_itranslationdomain.py index 85e4c6a8..bea53602 100644 --- a/src/zope/i18n/tests/test_itranslationdomain.py +++ b/src/zope/i18n/tests/test_itranslationdomain.py @@ -22,12 +22,11 @@ from zope.schema import getValidationErrors +from zope.i18n._compat import text_type from zope.i18n.negotiator import negotiator from zope.i18n.interfaces import INegotiator, IUserPreferredLanguages from zope.i18n.interfaces import ITranslationDomain -text_type = str if bytes is not str else unicode - @implementer(IUserPreferredLanguages) class Environment(object): diff --git a/src/zope/i18n/tests/test_zcml.py b/src/zope/i18n/tests/test_zcml.py index 0467a404..cd3d2b4e 100644 --- a/src/zope/i18n/tests/test_zcml.py +++ b/src/zope/i18n/tests/test_zcml.py @@ -25,10 +25,10 @@ from zope.configuration import xmlconfig import zope.i18n.tests +from zope.i18n._compat import text_type from zope.i18n.interfaces import ITranslationDomain from zope.i18n import config -text_type = str if bytes is not str else unicode template = """\ Date: Thu, 2 Sep 2021 11:59:39 +0200 Subject: [PATCH 07/14] Add meta.toml rules for manifest and check-manifest. --- .meta.toml | 26 +++++++++++++++++++++++++- MANIFEST.in | 9 +++++++++ setup.cfg | 9 +++++++++ tox.ini | 2 +- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.meta.toml b/.meta.toml index 4a7e6d4b..63ff7aed 100644 --- a/.meta.toml +++ b/.meta.toml @@ -5,7 +5,6 @@ template = "pure-python" commit-id = "7f5b73fe9d2bcbabafaef5f69dd97408a142d42a" [python] -with-appveyor = false with-windows = false with-pypy = true with-future-python = false @@ -18,3 +17,28 @@ use-flake8 = true [coverage] fail-under = 100 + +[manifest] +additional-rules = [ + "recursive-include docs *.bat", + "recursive-include src *.dtd", + "recursive-include src *.html", + "recursive-include src *.in", + "recursive-include src *.po", + "recursive-include src *.rst", + "recursive-include src *.txt", + "recursive-include src *.xml", + "recursive-include src *.zcml", + ] + +[check-manifest] +ignore-bad-ideas = [ + "src/zope/i18n/tests/de-default.mo", + "src/zope/i18n/tests/en-alt.mo", + "src/zope/i18n/tests/en-default.mo", + "src/zope/i18n/tests/locale/de/LC_MESSAGES/zope-i18n.mo", + "src/zope/i18n/tests/locale/en/LC_MESSAGES/zope-i18n.mo", + "src/zope/i18n/tests/locale2/en/LC_MESSAGES/zope-i18n.mo", + "src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo", + "src/zope/i18n/tests/pl-default.mo", + ] diff --git a/MANIFEST.in b/MANIFEST.in index 38e9c74f..b2b9c967 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,3 +11,12 @@ recursive-include docs *.txt recursive-include docs Makefile recursive-include src *.py +recursive-include docs *.bat +recursive-include src *.dtd +recursive-include src *.html +recursive-include src *.in +recursive-include src *.po +recursive-include src *.rst +recursive-include src *.txt +recursive-include src *.xml +recursive-include src *.zcml diff --git a/setup.cfg b/setup.cfg index 264b78c1..c703c588 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,3 +11,12 @@ ignore = .editorconfig .meta.toml docs/_build/html/_sources/* +ignore-bad-ideas = + src/zope/i18n/tests/de-default.mo + src/zope/i18n/tests/en-alt.mo + src/zope/i18n/tests/en-default.mo + src/zope/i18n/tests/locale/de/LC_MESSAGES/zope-i18n.mo + src/zope/i18n/tests/locale/en/LC_MESSAGES/zope-i18n.mo + src/zope/i18n/tests/locale2/en/LC_MESSAGES/zope-i18n.mo + src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo + src/zope/i18n/tests/pl-default.mo diff --git a/tox.ini b/tox.ini index fd22c86e..cf1d7e5d 100644 --- a/tox.ini +++ b/tox.ini @@ -58,7 +58,7 @@ commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage html - coverage report -m --fail-under=0 + coverage report -m --fail-under=100 [coverage:run] branch = True From 183ab0df50dbbe6e21dad801a3b8650dc7962712 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 12:15:21 +0200 Subject: [PATCH 08/14] Include sphinx doctests. --- .meta.toml | 2 +- setup.cfg | 1 + tox.ini | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.meta.toml b/.meta.toml index 63ff7aed..e8e5c848 100644 --- a/.meta.toml +++ b/.meta.toml @@ -10,7 +10,7 @@ with-pypy = true with-future-python = false with-legacy-python = true with-docs = true -with-sphinx-doctests = false +with-sphinx-doctests = true [tox] use-flake8 = true diff --git a/setup.cfg b/setup.cfg index c703c588..43050e94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,6 +11,7 @@ ignore = .editorconfig .meta.toml docs/_build/html/_sources/* + docs/_build/doctest/* ignore-bad-ideas = src/zope/i18n/tests/de-default.mo src/zope/i18n/tests/en-alt.mo diff --git a/tox.ini b/tox.ini index cf1d7e5d..68e58b02 100644 --- a/tox.ini +++ b/tox.ini @@ -18,10 +18,14 @@ envlist = [testenv] usedevelop = true deps = + # Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it: + Sphinx < 4 commands = zope-testrunner --test-path=src {posargs:-vc} + !py27-!pypy: sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest extras = test + docs [testenv:lint] basepython = python3 @@ -41,11 +45,10 @@ basepython = python3 skip_install = false # Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it: deps = Sphinx < 4 -extras = - docs commands_pre = commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest [testenv:coverage] basepython = python3 @@ -54,9 +57,12 @@ allowlist_externals = deps = coverage coverage-python-version + # Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it: + Sphinx < 4 commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} + coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest coverage html coverage report -m --fail-under=100 From 7205ea65d1734863437c8a7ff2993f532f0ba02e Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 12:45:19 +0200 Subject: [PATCH 09/14] Removed 'name = main' code from tests. This is not called during the tests, and this results in less coverage. --- src/zope/i18n/locales/tests/test_docstrings.py | 4 ---- src/zope/i18n/locales/tests/test_fallbackcollator.py | 4 ---- src/zope/i18n/tests/test.py | 4 ---- src/zope/i18n/tests/test_negotiator.py | 4 ---- src/zope/i18n/tests/test_simpletranslationdomain.py | 4 ---- src/zope/i18n/tests/test_testmessagecatalog.py | 4 ---- 6 files changed, 24 deletions(-) diff --git a/src/zope/i18n/locales/tests/test_docstrings.py b/src/zope/i18n/locales/tests/test_docstrings.py index 9f75b442..faa4e97b 100644 --- a/src/zope/i18n/locales/tests/test_docstrings.py +++ b/src/zope/i18n/locales/tests/test_docstrings.py @@ -38,7 +38,3 @@ def test_suite(): DocTestSuite('zope.i18n.locales.inheritance', checker=unicode_checker), DocTestSuite('zope.i18n.locales.xmlfactory', checker=unicode_checker), )) - - -if __name__ == '__main__': - unittest.main() diff --git a/src/zope/i18n/locales/tests/test_fallbackcollator.py b/src/zope/i18n/locales/tests/test_fallbackcollator.py index e7c84a80..ad78b72f 100644 --- a/src/zope/i18n/locales/tests/test_fallbackcollator.py +++ b/src/zope/i18n/locales/tests/test_fallbackcollator.py @@ -23,7 +23,3 @@ def test_suite(): doctest.DocFileSuite('../fallbackcollator.txt', checker=unicode_checker), )) - - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/src/zope/i18n/tests/test.py b/src/zope/i18n/tests/test.py index 05e13668..62082c38 100644 --- a/src/zope/i18n/tests/test.py +++ b/src/zope/i18n/tests/test.py @@ -35,7 +35,3 @@ def suite(name): suite("zope.i18n.config"), suite("zope.i18n.testing"), ]) - - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/src/zope/i18n/tests/test_negotiator.py b/src/zope/i18n/tests/test_negotiator.py index ef6a4700..9267332e 100644 --- a/src/zope/i18n/tests/test_negotiator.py +++ b/src/zope/i18n/tests/test_negotiator.py @@ -57,7 +57,3 @@ def test_suite(): return unittest.TestSuite(( unittest.makeSuite(NegotiatorTest), )) - - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') diff --git a/src/zope/i18n/tests/test_simpletranslationdomain.py b/src/zope/i18n/tests/test_simpletranslationdomain.py index 7c87c409..2f233d68 100644 --- a/src/zope/i18n/tests/test_simpletranslationdomain.py +++ b/src/zope/i18n/tests/test_simpletranslationdomain.py @@ -42,7 +42,3 @@ def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSimpleTranslationDomain)) return suite - - -if __name__ == '__main__': - unittest.TextTestRunner().run(test_suite()) diff --git a/src/zope/i18n/tests/test_testmessagecatalog.py b/src/zope/i18n/tests/test_testmessagecatalog.py index df426fcc..e5c8e57a 100644 --- a/src/zope/i18n/tests/test_testmessagecatalog.py +++ b/src/zope/i18n/tests/test_testmessagecatalog.py @@ -20,7 +20,3 @@ def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('../testmessagecatalog.rst') )) - - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') From 0ed53e0d825290d41d6bb1e048ebc111272753b2 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 12:36:53 +0200 Subject: [PATCH 10/14] Set minimum coverage at 99 percent. We had 100% test coverage previously, but this dropped to around 99.11 percent when using the new default in tox.ini: ``` [coverage:run] branch = True ``` With branch=False, the coverage would be back at 100 percent, but I don't see a way to configure this in meta.toml, and it may be better to see the actual coverage. We should add more tests to cover the untested branches. For now, we lower our expectations. --- .meta.toml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.meta.toml b/.meta.toml index e8e5c848..877be7c1 100644 --- a/.meta.toml +++ b/.meta.toml @@ -16,7 +16,7 @@ with-sphinx-doctests = true use-flake8 = true [coverage] -fail-under = 100 +fail-under = 99 [manifest] additional-rules = [ diff --git a/tox.ini b/tox.ini index 68e58b02..da82bb13 100644 --- a/tox.ini +++ b/tox.ini @@ -64,7 +64,7 @@ commands = coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest coverage html - coverage report -m --fail-under=100 + coverage report -m --fail-under=99 [coverage:run] branch = True From cd25a3832b303ca93ddfbea38b17e81500acfaa1 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 12:49:37 +0200 Subject: [PATCH 11/14] Add Python 3.8 and 3.9 to classifiers. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 297b18de..6a7c83e0 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,8 @@ def alltests(): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Natural Language :: English', From 72a8f425cc67f5215ef3583f06ea943a95fb798d Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Thu, 2 Sep 2021 18:03:05 +0200 Subject: [PATCH 12/14] Add changelog entry. --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 013bb059..c554ca20 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,8 @@ 4.7.1 (unreleased) ================== -- Nothing changed yet. +- Support and test Python 3.8 and 3.9. + Full supported list is now: 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, PyPy, PyPy3. 4.7.0 (2019-07-10) From a2d344672595e30b4329944342e61a6819705275 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Fri, 3 Sep 2021 08:55:26 +0200 Subject: [PATCH 13/14] Apply suggestions from code review I mixed up Python 2 and 3 in the try/except comment. Co-authored-by: Michael Howitz --- src/zope/i18n/_compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zope/i18n/_compat.py b/src/zope/i18n/_compat.py index 00fb20fe..f6bf93be 100644 --- a/src/zope/i18n/_compat.py +++ b/src/zope/i18n/_compat.py @@ -1,8 +1,8 @@ # This gives a linting error because unicode is not defined on Python 3: # text_type = str if bytes is not str else unicode try: - # Python 3 + # Python 2 text_type = unicode except NameError: - # Python 2 + # Python 3 text_type = str From 51480fc597fa04b2a044b873605cb114efacf9f6 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Fri, 3 Sep 2021 10:52:13 +0200 Subject: [PATCH 14/14] Fixed more linting errors. --- src/zope/i18n/format.py | 3 ++- src/zope/i18n/locales/tests/test_xmlfactory.py | 10 ++++++---- src/zope/i18n/locales/xmlfactory.py | 11 ++++++----- src/zope/i18n/tests/test_formats.py | 9 ++++++--- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index 05431e03..2a2421c5 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -189,7 +189,8 @@ def parse(self, text, pattern=None, asObject=True): return tzinfo.localize( datetime.datetime.combine( datetime.date.today(), - datetime.time(*[e or 0 for e in ordered[3:]]))).timetz() + datetime.time(*[e or 0 for e in ordered[3:]])) + ).timetz() return datetime.time( *[e or 0 for e in ordered[3:]], **{'tzinfo': tzinfo} ) diff --git a/src/zope/i18n/locales/tests/test_xmlfactory.py b/src/zope/i18n/locales/tests/test_xmlfactory.py index 0dc070e2..e11097c6 100644 --- a/src/zope/i18n/locales/tests/test_xmlfactory.py +++ b/src/zope/i18n/locales/tests/test_xmlfactory.py @@ -36,9 +36,11 @@ def runTest(self): # Making sure all number format patterns parse # for category in (u'decimal', u'scientific', u'percent', u'currency'): - # for length in getattr(locale.numbers, category+'Formats').values(): + # for length in getattr( + # locale.numbers, category + 'Formats' + # ).values(): # for format in length.formats.values(): - # self.assert_(parseNumberPattern(format.pattern) is not None) + # self.assertIsNotNone(parseNumberPattern(format.pattern) # Making sure all datetime patterns parse # for calendar in locale.dates.calendars.values(): @@ -47,8 +49,8 @@ def runTest(self): # calendar, category + 'Formats' # ).values(): # for format in length.formats.values(): - # self.assert_( - # parseDateTimePattern(format.pattern) is not None + # self.assertIsNotNone( + # parseDateTimePattern(format.pattern) # ) diff --git a/src/zope/i18n/locales/xmlfactory.py b/src/zope/i18n/locales/xmlfactory.py index 0520c2df..f1ed2195 100644 --- a/src/zope/i18n/locales/xmlfactory.py +++ b/src/zope/i18n/locales/xmlfactory.py @@ -381,8 +381,8 @@ def _extractMonths(self, months_node, calendar): defaultMonthContext_node = months_node.getElementsByTagName('default') if defaultMonthContext_node: - calendar.defaultMonthContext = defaultMonthContext_node[0].getAttribute( - 'type') + calendar.defaultMonthContext = defaultMonthContext_node[ + 0].getAttribute('type') monthContext_nodes = months_node.getElementsByTagName('monthContext') if not monthContext_nodes: @@ -525,8 +525,8 @@ def _extractDays(self, days_node, calendar): defaultDayContext_node = days_node.getElementsByTagName('default') if defaultDayContext_node: - calendar.defaultDayContext = defaultDayContext_node[0].getAttribute( - 'type') + calendar.defaultDayContext = defaultDayContext_node[ + 0].getAttribute('type') dayContext_nodes = days_node.getElementsByTagName('dayContext') if not dayContext_nodes: @@ -904,7 +904,8 @@ def _extractCalendars(self, dates_node): for formatsName, lengthName, formatName in ( ('dateFormats', 'dateFormatLength', 'dateFormat'), ('timeFormats', 'timeFormatLength', 'timeFormat'), - ('dateTimeFormats', 'dateTimeFormatLength', 'dateTimeFormat')): + ('dateTimeFormats', 'dateTimeFormatLength', + 'dateTimeFormat')): formats_nodes = cal_node.getElementsByTagName(formatsName) if formats_nodes: diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py index 4ca039ed..a266de65 100644 --- a/src/zope/i18n/tests/test_formats.py +++ b/src/zope/i18n/tests/test_formats.py @@ -787,7 +787,8 @@ def testParseThousandSeparatorPatterns(self): self.assertEqual( parseNumberPattern('#,##,##0.###;-#,##,##0.###'), ((None, '', None, '#####0', '###', '', None, '', None, (3, 2, 0)), - (None, '-', None, '#####0', '###', '', None, '', None, (3, 2, 0)))) + (None, '-', None, '#####0', '###', '', None, '', None, + (3, 2, 0)))) self.assertEqual( parseNumberPattern('#,##0.##;-#,##0.##'), @@ -812,7 +813,8 @@ def testParseThousandSeparatorPatterns(self): self.assertEqual( parseNumberPattern('##,##,##0.###;-##,##,##0.###'), ((None, '', None, '######0', '###', '', None, '', None, (3, 2, 0)), - (None, '-', None, '######0', '###', '', None, '', None, (3, 2, 0)))) + (None, '-', None, '######0', '###', '', None, '', None, + (3, 2, 0)))) self.assertEqual( parseNumberPattern('##,##0.##;-##,##0.##'), @@ -862,7 +864,8 @@ def testParseThousandSeparatorPatterns(self): self.assertEqual( parseNumberPattern('##,##,##0.00;-##,##,##0.00'), ((None, '', None, '######0', '00', '', None, '', None, (3, 2, 0)), - (None, '-', None, '######0', '00', '', None, '', None, (3, 2, 0)))) + (None, '-', None, '######0', '00', '', None, '', None, + (3, 2, 0)))) self.assertEqual( parseNumberPattern('###0.00;-###0.00'),