From 38dfc8dc87f869d4837cbe8338db53ae9e293f8f Mon Sep 17 00:00:00 2001 From: Mario Frasca Date: Mon, 27 May 2019 12:35:28 -0500 Subject: [PATCH 01/50] attempt partial sorting at least see issue #606. if one object has anything that doesn't compare to `int`, bring it to the top, and correctly sort the rest. --- babel/messages/pofile.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index bbcf7f76c..93b0697c6 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -582,11 +582,13 @@ def _write_message(message, prefix=''): if not no_location: locs = [] - # Attempt to sort the locations. If we can't do that, for instance - # because there are mixed integers and Nones or whatnot (see issue #606) - # then give up, but also don't just crash. + # sort locations by filename and lineno. + # if there's no as lineno, use `-1`. + # if no sorting possible, leave unsorted. + # (see issue #606) try: - locations = sorted(message.locations) + locations = sorted(message.locations, + key=lambda x: (x[0], isinstance(x[1], int) and x[1] or -1)) except TypeError: # e.g. "TypeError: unorderable types: NoneType() < int()" locations = message.locations From 2abad80c9f2fc70bde71f53ab8270ca950100595 Mon Sep 17 00:00:00 2001 From: Romuald Brunet Date: Thu, 18 Jul 2019 14:44:10 +0200 Subject: [PATCH 02/50] Add year along dates in changelog --- CHANGES | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 3462117fc..ec265c557 100644 --- a/CHANGES +++ b/CHANGES @@ -188,7 +188,7 @@ Internal improvements Version 2.3.4 ------------- -(Bugfix release, released on April 22th) +(Bugfix release, released on April 22th 2016) Bugfixes ~~~~~~~~ @@ -199,7 +199,7 @@ Bugfixes Version 2.3.3 ------------- -(Bugfix release, released on April 12th) +(Bugfix release, released on April 12th 2016) Bugfixes ~~~~~~~~ @@ -209,7 +209,7 @@ Bugfixes Version 2.3.2 ------------- -(Bugfix release, released on April 9th) +(Bugfix release, released on April 9th 2016) Bugfixes ~~~~~~~~ @@ -219,12 +219,12 @@ Bugfixes Version 2.3.1 ------------- -(Bugfix release because of deployment problems, released on April 8th) +(Bugfix release because of deployment problems, released on April 8th 2016) Version 2.3 ----------- -(Feature release, released on April 8th) +(Feature release, released on April 8th 2016) Internal improvements ~~~~~~~~~~~~~~~~~~~~~ From 9680427a75d3c267df111e2db585b26925a336bf Mon Sep 17 00:00:00 2001 From: "Steve (Gadget) Barnes" Date: Fri, 18 Oct 2019 17:07:24 +0100 Subject: [PATCH 03/50] Add install of pytz before import_cldr closes #670 --- docs/installation.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ce778b04c..c1b7ab9fe 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -79,15 +79,16 @@ Get the git checkout in a new virtualenv and run in development mode:: New python executable in venv/bin/python Installing distribute............done. $ . venv/bin/activate + $ pip install pytz $ python setup.py import_cldr $ pip install --editable . ... Finished processing dependencies for Babel -Make sure to not forget about the ``import_cldr`` step because otherwise -you will be missing the locale data. This custom command will download -the most appropriate CLDR release from the official website and convert it -for Babel. +Make sure to not forget about the ``pip install pytz`` and ``import_cldr`` steps +because otherwise you will be missing the locale data. +The custom setup command will download the most appropriate CLDR release from the +official website and convert it for Babel but will not work without ``pytz``. This will pull also in the dependencies and activate the git head as the current version inside the virtualenv. Then all you have to do is run From 472a3174a7abc314345a914dde6fa2031018d4c4 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 10:13:37 +0200 Subject: [PATCH 04/50] Upgrade freezegun to fix CI failures --- .ci/appveyor.yml | 2 +- .travis.yml | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/appveyor.yml b/.ci/appveyor.yml index eb9ba8656..91758f415 100644 --- a/.ci/appveyor.yml +++ b/.ci/appveyor.yml @@ -27,7 +27,7 @@ install: - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" # Build data files - - "pip install --upgrade pytest==4.3.1 pytest-cov==2.6.1 codecov freezegun==0.3.11" + - "pip install --upgrade pytest==4.3.1 pytest-cov==2.6.1 codecov freezegun==0.3.12" - "pip install --editable ." - "python setup.py import_cldr" diff --git a/.travis.yml b/.travis.yml index 174913952..9564d4c65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ matrix: install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.11 + - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 - pip install --editable . script: diff --git a/tox.ini b/tox.ini index b3f8041f4..eccffea94 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ deps = pytest==4.3.1 pytest-cov==2.6.1 cdecimal: m3-cdecimal - freezegun==0.3.11 + freezegun==0.3.12 whitelist_externals = make commands = make clean-cldr test passenv = PYTHON_TEST_FLAGS From 521fd3a3aa7d5605f5b5a10e9c50d85c7aa7af61 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 10:46:45 +0200 Subject: [PATCH 05/50] Test on released Python 3.8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9564d4c65..9650f674c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ matrix: - os: linux python: 3.7 - os: linux - python: 3.8-dev + python: 3.8 install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh From 49c68d335f8443ba5fee0a5201dd8c2d033e59ff Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 09:37:50 +0200 Subject: [PATCH 06/50] Download CLDR 36.0 --- scripts/download_import_cldr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py index f118c6900..434b04f80 100755 --- a/scripts/download_import_cldr.py +++ b/scripts/download_import_cldr.py @@ -13,9 +13,9 @@ from urllib import urlretrieve -URL = 'https://unicode.org/Public/cldr/35.1/core.zip' -FILENAME = 'cldr-core-35.1.zip' -FILESUM = 'e2ede8cb8f9c29157e281ee9e696ce540a72c598841bed595a406b710eea87b0' +URL = 'http://unicode.org/Public/cldr/36/core.zip' +FILENAME = 'cldr-core-36.zip' +FILESUM = '07279e56c1f4266d140b907ef3ec379dce0a99542303a9628562ac5fe460ba43' BLKSIZE = 131072 From fab99b8924ad37e9f04798f464f37e2359ebafeb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 09:58:03 +0200 Subject: [PATCH 07/50] CLDR import: assume files without revision tags to be new --- scripts/import_cldr.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 4188055a6..8993b68e4 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -77,8 +77,10 @@ def error(message, *args): def need_conversion(dst_filename, data_dict, source_filename): with open(source_filename, 'rb') as f: blob = f.read(4096) - version = int(re.search(b'version number="\\$Revision: (\\d+)', - blob).group(1)) + version_match = re.search(b'version number="\\$Revision: (\\d+)', blob) + if not version_match: # CLDR 36.0 was shipped without proper revision numbers + return True + version = int(version_match.group(1)) data_dict['_version'] = version if not os.path.isfile(dst_filename): From d4a7c266ba119ee8564a14575a60948f723a15cb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 10:02:04 +0200 Subject: [PATCH 08/50] Correct format_unit test based on new Welsh data --- babel/units.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/babel/units.py b/babel/units.py index e58bf81c2..89c491365 100644 --- a/babel/units.py +++ b/babel/units.py @@ -88,12 +88,12 @@ def format_unit(value, measurement_unit, length='long', format=None, locale=LC_N >>> format_unit(1, 'length-meter', locale='ro_RO') u'1 metru' - >>> format_unit(0, 'length-picometer', locale='cy') - u'0 picometr' - >>> format_unit(2, 'length-picometer', locale='cy') - u'2 bicometr' - >>> format_unit(3, 'length-picometer', locale='cy') - u'3 phicometr' + >>> format_unit(0, 'length-mile', locale='cy') + u'0 mi' + >>> format_unit(1, 'length-mile', locale='cy') + u'1 filltir' + >>> format_unit(3, 'length-mile', locale='cy') + u'3 milltir' >>> format_unit(15, 'length-horse', locale='fi') Traceback (most recent call last): From df9c01c6480ef20523418079b6774612e47b8c63 Mon Sep 17 00:00:00 2001 From: He Chen Date: Mon, 19 Aug 2019 16:06:41 -0400 Subject: [PATCH 09/50] fix small decimal with disabled decimal_quantization --- babel/numbers.py | 2 +- tests/test_numbers.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/babel/numbers.py b/babel/numbers.py index 6888c9cb4..cf819fc9a 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -1063,7 +1063,7 @@ def _format_int(self, value, min, max, locale): def _quantize_value(self, value, locale, frac_prec): quantum = get_decimal_quantum(frac_prec[1]) rounded = value.quantize(quantum) - a, sep, b = str(rounded).partition(".") + a, sep, b = "{:f}".format(rounded).partition(".") number = (self._format_int(a, self.int_prec[0], self.int_prec[1], locale) + self._format_frac(b or '0', locale, frac_prec)) diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 6e26fe900..a980a66ad 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -690,3 +690,7 @@ def test_parse_decimal_nbsp_heuristics(): n = decimal.Decimal("12345.123") assert numbers.parse_decimal("12 345.123", locale="fi") == n assert numbers.parse_decimal(numbers.format_decimal(n, locale="fi"), locale="fi") == n + + +def test_very_small_decimal_no_quantization(): + assert numbers.format_decimal(decimal.Decimal('1E-7'), locale='en', decimal_quantization=False) == '0.0000001' From c42bc9e907d638b368d07a326daa841e4a0bd691 Mon Sep 17 00:00:00 2001 From: sebleblanc Date: Tue, 3 Dec 2019 04:45:42 +0000 Subject: [PATCH 10/50] Hardcode "ignore" method The "ignore" method used to force the opening of the file. Some editors (emacs) create symbolic links to use as synchronization locks. Those links have an extension that matches the opened file, but the links themselves do not point to an existing file, thus causing Babel to attempt to open a file that does not exist. This fix skips opening of a file altogether when using the method "ignore" in the mapping file. --- babel/messages/extract.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/babel/messages/extract.py b/babel/messages/extract.py index db429b2ea..e7d7ad70f 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -236,9 +236,12 @@ def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS, :returns: list of tuples of the form ``(lineno, message, comments, context)`` :rtype: list[tuple[int, str|tuple[str], list[str], str|None] """ + if method == 'ignore': + return [] + with open(filename, 'rb') as fileobj: - return list(extract(method, fileobj, keywords, comment_tags, options, - strip_comment_tags)) + return list(extract(method, fileobj, keywords, comment_tags, + options, strip_comment_tags)) def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(), From dc012d4961c8a58c761252053a9954ccdbf4afd2 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 14:21:49 +0200 Subject: [PATCH 11/50] Distill changelog from git log --- CHANGES | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGES b/CHANGES index ec265c557..4441f68e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,27 @@ Babel Changelog =============== +Version 2.8.0 +------------- + +Improvements +~~~~~~~~~~~~ + +* CLDR: Upgrade to CLDR 36.0 - Aarni Koskela (#679) +* Messages: Don't even open files with the "ignore" extraction method - @sebleblanc (#678) + +Bugfixes +~~~~~~~~ + +* Numbers: Fix formatting very small decimals when quantization is disabled - Lev Lybin, @miluChen (#662) +* Messages: Attempt to sort all messages – Mario Frasca (#651, #606) + +Docs +~~~~ + +* Add years to changelog - Romuald Brunet +* Note that installation requires pytz - Steve (Gadget) Barnes + Version 2.7.0 ------------- From eb1b24f49760eadb13dee0d505b25f93befa5c97 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 14:22:11 +0200 Subject: [PATCH 12/50] Update authors file --- AUTHORS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index 6374cd650..31a5cd1be 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,6 +45,11 @@ Babel is written and maintained by the Babel team and various contributors: - Leonardo Pistone - Jun Omae - Hyunjun Kim +- sebleblanc +- He Chen +- Steve (Gadget) Barnes +- Romuald Brunet +- Mario Frasca - BT-sschmid - Alberto Mardegan - mondeja From 692ad094c8fdf1895b7f56a071f38f8e463a2c62 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 31 Dec 2019 14:23:25 +0200 Subject: [PATCH 13/50] Bump version --- babel/__init__.py | 2 +- docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/babel/__init__.py b/babel/__init__.py index 1132e6f37..c10a8bd1f 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -21,4 +21,4 @@ negotiate_locale, parse_locale, get_locale_identifier -__version__ = '2.7.0' +__version__ = '2.8.0' diff --git a/docs/conf.py b/docs/conf.py index 6eed08fe6..83c5bf206 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '2.7' +version = '2.8' # The full version, including alpha/beta/rc tags. -release = '2.7.0' +release = '2.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 4fa749b918810b52a63b312d82e4003e24db0406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Mon, 6 Jan 2020 00:37:39 +0100 Subject: [PATCH 14/50] Replace usage of parser.suite with ast.parse Replaced usage of the long-superseded "parser.suite" module in the mako.util package for parsing the python magic encoding comment with the "ast.parse" function introduced many years ago in Python 2.5, as "parser.suite" is emitting deprecation warnings in Python 3.9. Fixes https://github.com/sqlalchemy/mako/issues/310 See also https://github.com/sqlalchemy/mako/commit/2dae7d2c3da73653e6de329dc15c55056a0b9ab6 --- babel/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/babel/util.py b/babel/util.py index 73a90516f..c371badbd 100644 --- a/babel/util.py +++ b/babel/util.py @@ -68,8 +68,8 @@ def parse_encoding(fp): m = PYTHON_MAGIC_COMMENT_re.match(line1) if not m: try: - import parser - parser.suite(line1.decode('latin-1')) + import ast + ast.parse(line1.decode('latin-1')) except (ImportError, SyntaxError, UnicodeEncodeError): # Either it's a real syntax error, in which case the source is # not valid python source, or line2 is a continuation of line1, From f4f6653e6aa053724d2c6dc0ee71dcb928013352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Tue, 28 Jan 2020 02:46:25 +0100 Subject: [PATCH 15/50] Introduce invariant that _invalid_pofile() takes unicode line. This makes debugging and reasoning about the code easier; otherwise it is surprising that sometimes `line` is a unicode and sometimes not. So far, when it was not, it could either be only `""` or `'Algo esta mal'`; thus this commit makes those two u"" strings. In all other cases, it was guaranteed that it's unicode, because all code paths leading to `_invalid_pofile()` went through if not isinstance(line, text_type): line = line.decode(self.catalog.charset) before. --- babel/messages/pofile.py | 3 ++- tests/messages/test_pofile.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index 93b0697c6..f6771bedf 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -178,7 +178,7 @@ def _add_message(self): string = ['' for _ in range(self.catalog.num_plurals)] for idx, translation in self.translations: if idx >= self.catalog.num_plurals: - self._invalid_pofile("", self.offset, "msg has more translations than num_plurals of catalog") + self._invalid_pofile(u"", self.offset, "msg has more translations than num_plurals of catalog") continue string[idx] = translation.denormalize() string = tuple(string) @@ -319,6 +319,7 @@ def parse(self, fileobj): self._add_message() def _invalid_pofile(self, line, lineno, msg): + assert isinstance(line, text_type) if self.abort_invalid: raise PoFileError(msg, self.catalog, line, lineno) print("WARNING:", msg) diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index e77fa6e02..214ddf5d5 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -480,7 +480,7 @@ def test_abort_invalid_po_file(self): def test_invalid_pofile_with_abort_flag(self): parser = pofile.PoFileParser(None, abort_invalid=True) lineno = 10 - line = 'Algo esta mal' + line = u'Algo esta mal' msg = 'invalid file' with self.assertRaises(pofile.PoFileError) as e: parser._invalid_pofile(line, lineno, msg) From da7f31143847659b6b74d802618b03438aceb350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Tue, 28 Jan 2020 00:37:22 +0100 Subject: [PATCH 16/50] Fix unicode printing error on Python 2 without TTY. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now, on Python 2.7, `python setup.py test | cat` crashed in the test runner with ====================================================================== ERROR: test_abort_invalid_po_file (tests.messages.test_pofile.ReadPoTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "src/babel/tests/messages/test_pofile.py", line 458, in test_abort_invalid_po_file output = pofile.read_po(buf, locale='fr', abort_invalid=False) File "src/babel/babel/messages/pofile.py", line 377, in read_po parser.parse(fileobj) File "src/babel/babel/messages/pofile.py", line 310, in parse self._process_message_line(lineno, line) File "src/babel/babel/messages/pofile.py", line 210, in _process_message_line self._process_keyword_line(lineno, line, obsolete) File "src/babel/babel/messages/pofile.py", line 222, in _process_keyword_line self._invalid_pofile(line, lineno, "Start of line didn't match any expected keyword.") File "src/babel/babel/messages/pofile.py", line 325, in _invalid_pofile print(u"WARNING: Problem on line {0}: {1}".format(lineno + 1, line)) UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 84: ordinal not in range(128) The test suite would show this when printing the `à` in the test pofile contents Pour toute question, veuillez communiquer avec Fulano à nadie@blah.com But this bug is not confined to the test suite only. Any call to `read_po()` with invalid .po file could trigger it in non-test code when `sys.stdout.encoding` is `None`, which is the default for Python 2 when `sys.stdout.isatty()` is false (as induced e.g. by `| cat`). The fix is to `repr()` the line when printing the WARNING. --- babel/messages/pofile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index f6771bedf..b86dd4052 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -323,7 +323,10 @@ def _invalid_pofile(self, line, lineno, msg): if self.abort_invalid: raise PoFileError(msg, self.catalog, line, lineno) print("WARNING:", msg) - print(u"WARNING: Problem on line {0}: {1}".format(lineno + 1, line)) + # `line` is guaranteed to be unicode so u"{}"-interpolating would always + # succeed, but on Python < 2 if not in a TTY, `sys.stdout.encoding` + # is `None`, unicode may not be printable so we `repr()` to ASCII. + print(u"WARNING: Problem on line {0}: {1}".format(lineno + 1, repr(line))) def read_po(fileobj, locale=None, domain=None, ignore_obsolete=False, charset=None, abort_invalid=False): From 0cfa69e087a24364ba788ff9d862949b65f0ff12 Mon Sep 17 00:00:00 2001 From: CyanNani123 Date: Mon, 13 Jan 2020 23:13:01 +0100 Subject: [PATCH 17/50] catalog.rst: Add __iter__ to Catalog documentation The declaration of __iter__ under the special-members directive makes it visible in the documentation. The docstring describing __iter__ already exists. Closes https://github.com/python-babel/babel/issues/128 --- docs/api/messages/catalog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/messages/catalog.rst b/docs/api/messages/catalog.rst index 8a905bcd9..8cb6375e3 100644 --- a/docs/api/messages/catalog.rst +++ b/docs/api/messages/catalog.rst @@ -12,6 +12,7 @@ Catalogs .. autoclass:: Catalog :members: + :special-members: __iter__ Messages -------- From 4ce6f13412705949f6d74579f15cad15cf00fed7 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Tue, 25 Feb 2020 17:05:43 -0500 Subject: [PATCH 18/50] Update license.rst The documentation refers to the license as the license for _flask_ instead of Babel. --- docs/license.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/license.rst b/docs/license.rst index a619b5746..7c93ab426 100644 --- a/docs/license.rst +++ b/docs/license.rst @@ -19,7 +19,7 @@ Authors General License Definitions --------------------------- -The following section contains the full license texts for Flask and the +The following section contains the full license texts for Babel and the documentation. - "AUTHORS" hereby refers to all the authors listed in the From 167b71421f113e2210e4deefef5020402492e5be Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 5 May 2020 09:58:01 +0200 Subject: [PATCH 19/50] stop using deprecated ElementTree methods "getchildren()" and "getiterator()" Both methods were removed in Python 3.9 as mentioned in the release notes: > Methods getchildren() and getiterator() of classes ElementTree and Element in > the ElementTree module have been removed. They were deprecated in Python 3.2. > Use iter(x) or list(x) instead of x.getchildren() and x.iter() or > list(x.iter()) instead of x.getiterator(). --- scripts/import_cldr.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 8993b68e4..2ed3af91e 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -598,7 +598,7 @@ def parse_calendar_months(data, calendar): for width in ctxt.findall('monthWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.getiterator(): + for elem in width.iter(): if elem.tag == 'month': _import_type_text(widths, elem, int(elem.attrib['type'])) elif elem.tag == 'alias': @@ -616,7 +616,7 @@ def parse_calendar_days(data, calendar): for width in ctxt.findall('dayWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.getiterator(): + for elem in width.iter(): if elem.tag == 'day': _import_type_text(widths, elem, weekdays[elem.attrib['type']]) elif elem.tag == 'alias': @@ -634,7 +634,7 @@ def parse_calendar_quarters(data, calendar): for width in ctxt.findall('quarterWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.getiterator(): + for elem in width.iter(): if elem.tag == 'quarter': _import_type_text(widths, elem, int(elem.attrib['type'])) elif elem.tag == 'alias': @@ -649,7 +649,7 @@ def parse_calendar_eras(data, calendar): for width in calendar.findall('eras/*'): width_type = NAME_MAP[width.tag] widths = eras.setdefault(width_type, {}) - for elem in width.getiterator(): + for elem in width.iter(): if elem.tag == 'era': _import_type_text(widths, elem, type=int(elem.attrib.get('type'))) elif elem.tag == 'alias': @@ -676,7 +676,7 @@ def parse_calendar_periods(data, calendar): def parse_calendar_date_formats(data, calendar): date_formats = data.setdefault('date_formats', {}) for format in calendar.findall('dateFormats'): - for elem in format.getiterator(): + for elem in format.iter(): if elem.tag == 'dateFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, date_formats): @@ -696,7 +696,7 @@ def parse_calendar_date_formats(data, calendar): def parse_calendar_time_formats(data, calendar): time_formats = data.setdefault('time_formats', {}) for format in calendar.findall('timeFormats'): - for elem in format.getiterator(): + for elem in format.iter(): if elem.tag == 'timeFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, time_formats): @@ -717,7 +717,7 @@ def parse_calendar_datetime_skeletons(data, calendar): datetime_formats = data.setdefault('datetime_formats', {}) datetime_skeletons = data.setdefault('datetime_skeletons', {}) for format in calendar.findall('dateTimeFormats'): - for elem in format.getiterator(): + for elem in format.iter(): if elem.tag == 'dateTimeFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, datetime_formats): @@ -880,7 +880,7 @@ def parse_interval_formats(data, tree): interval_formats[None] = elem.text elif elem.tag == "intervalFormatItem": skel_data = interval_formats.setdefault(elem.attrib["id"], {}) - for item_sub in elem.getchildren(): + for item_sub in elem: if item_sub.tag == "greatestDifference": skel_data[item_sub.attrib["id"]] = split_interval_pattern(item_sub.text) else: @@ -903,7 +903,7 @@ def parse_currency_formats(data, tree): type = '%s:%s' % (type, curr_length_type) if _should_skip_elem(elem, type, currency_formats): continue - for child in elem.getiterator(): + for child in elem.iter(): if child.tag == 'alias': currency_formats[type] = Alias( _translate_alias(['currency_formats', elem.attrib['type']], From 7bdaa28a55e8d8228d5434effa4b1473ab7b3669 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 5 May 2020 08:05:56 +0000 Subject: [PATCH 20/50] fix tests when using Python 3.9a6 In Python 3.9a6 integer values for future flags were changed to prevent collision with compiler flags. We need to retrieve these at runtime so the test suite works with Python <= 3.8 as well as Python 3.9. --- tests/test_util.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index a6a4450cf..b9343aaab 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -11,6 +11,7 @@ # individuals. For the exact contribution history, see the revision # history and logs, available at http://babel.edgewall.org/log/. +import __future__ import unittest import pytest @@ -20,6 +21,12 @@ from babel.util import parse_future_flags +class _FF: + division = __future__.division.compiler_flag + print_function = __future__.print_function.compiler_flag + with_statement = __future__.with_statement.compiler_flag + unicode_literals = __future__.unicode_literals.compiler_flag + def test_distinct(): assert list(util.distinct([1, 2, 1, 3, 4, 4])) == [1, 2, 3, 4] assert list(util.distinct('foobar')) == ['f', 'o', 'b', 'a', 'r'] @@ -70,25 +77,25 @@ def test_parse_encoding_non_ascii(): from __future__ import print_function, division, with_statement, unicode_literals -''', 0x10000 | 0x2000 | 0x8000 | 0x20000), +''', _FF.print_function | _FF.division | _FF.with_statement | _FF.unicode_literals), (''' from __future__ import print_function, division print('hello') -''', 0x10000 | 0x2000), +''', _FF.print_function | _FF.division), (''' from __future__ import print_function, division, unknown,,,,, print 'hello' -''', 0x10000 | 0x2000), +''', _FF.print_function | _FF.division), (''' from __future__ import ( print_function, division) -''', 0x10000 | 0x2000), +''', _FF.print_function | _FF.division), (''' from __future__ import \\ print_function, \\ division -''', 0x10000 | 0x2000), +''', _FF.print_function | _FF.division), ]) def test_parse_future(source, result): fp = BytesIO(source.encode('latin-1')) From 2a826bb1ad9996a1c1e2d3e86a493d01d7f12c09 Mon Sep 17 00:00:00 2001 From: Felix Schwarz Date: Tue, 5 May 2020 21:32:52 +0000 Subject: [PATCH 21/50] simplify iteration code in "import_cldr.py" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As Miro Hrončok pointed out we don't need ".iter()" in the script. --- scripts/import_cldr.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 2ed3af91e..7ea6481a2 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -598,7 +598,7 @@ def parse_calendar_months(data, calendar): for width in ctxt.findall('monthWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.iter(): + for elem in width: if elem.tag == 'month': _import_type_text(widths, elem, int(elem.attrib['type'])) elif elem.tag == 'alias': @@ -616,7 +616,7 @@ def parse_calendar_days(data, calendar): for width in ctxt.findall('dayWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.iter(): + for elem in width: if elem.tag == 'day': _import_type_text(widths, elem, weekdays[elem.attrib['type']]) elif elem.tag == 'alias': @@ -634,7 +634,7 @@ def parse_calendar_quarters(data, calendar): for width in ctxt.findall('quarterWidth'): width_type = width.attrib['type'] widths = ctxts.setdefault(width_type, {}) - for elem in width.iter(): + for elem in width: if elem.tag == 'quarter': _import_type_text(widths, elem, int(elem.attrib['type'])) elif elem.tag == 'alias': @@ -649,7 +649,7 @@ def parse_calendar_eras(data, calendar): for width in calendar.findall('eras/*'): width_type = NAME_MAP[width.tag] widths = eras.setdefault(width_type, {}) - for elem in width.iter(): + for elem in width: if elem.tag == 'era': _import_type_text(widths, elem, type=int(elem.attrib.get('type'))) elif elem.tag == 'alias': @@ -676,7 +676,7 @@ def parse_calendar_periods(data, calendar): def parse_calendar_date_formats(data, calendar): date_formats = data.setdefault('date_formats', {}) for format in calendar.findall('dateFormats'): - for elem in format.iter(): + for elem in format: if elem.tag == 'dateFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, date_formats): @@ -696,7 +696,7 @@ def parse_calendar_date_formats(data, calendar): def parse_calendar_time_formats(data, calendar): time_formats = data.setdefault('time_formats', {}) for format in calendar.findall('timeFormats'): - for elem in format.iter(): + for elem in format: if elem.tag == 'timeFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, time_formats): @@ -717,7 +717,7 @@ def parse_calendar_datetime_skeletons(data, calendar): datetime_formats = data.setdefault('datetime_formats', {}) datetime_skeletons = data.setdefault('datetime_skeletons', {}) for format in calendar.findall('dateTimeFormats'): - for elem in format.iter(): + for elem in format: if elem.tag == 'dateTimeFormatLength': type = elem.attrib.get('type') if _should_skip_elem(elem, type, datetime_formats): From e7e4265d9a037ac38bba99f8513fb9e48a1081ba Mon Sep 17 00:00:00 2001 From: Brad Martin Date: Sun, 10 May 2020 12:02:16 -0600 Subject: [PATCH 22/50] docs/numbers.rst : update parse_number comments (#708) * docs/numbers.rst : test format of revised comments * docs/numbers.rst : test final doc changes * docs/numbers.rst : refine format/language * docs/numbers.rst : refine language/format * docs/numbers.rst : refine language/format * docs/numbers.rst : experiment with spacing --- docs/numbers.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/numbers.rst b/docs/numbers.rst index df834eaf8..058d79e18 100644 --- a/docs/numbers.rst +++ b/docs/numbers.rst @@ -160,4 +160,21 @@ Examples: ... NumberFormatError: '2,109,998' is not a valid decimal number -.. note:: Number parsing is not properly implemented yet +Note: as of version 2.8.0, the ``parse_number`` function has limited +functionality. It can remove group symbols of certain locales from numeric +strings, but may behave unexpectedly until its logic handles more encoding +issues and other special cases. + +Examples: + +.. code-block:: pycon + + >>> parse_number('1,099', locale='en_US') + 1099 + >>> parse_number('1.099.024', locale='de') + 1099024 + >>> parse_number('123' + u'\xa0' + '4567', locale='ru') + 1234567 + >>> parse_number('123 4567', locale='ru') + ... + NumberFormatError: '123 4567' is not a valid number From e0e6aa614546855ccb76637b8d910382b6e94dba Mon Sep 17 00:00:00 2001 From: Abdullah Javed Nesar Date: Tue, 22 Sep 2020 17:44:27 +0530 Subject: [PATCH 23/50] Added group_separator feature in number formatting (#726) --- babel/numbers.py | 58 ++++++++++++++++++++++++++++++------------- tests/test_numbers.py | 30 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/babel/numbers.py b/babel/numbers.py index cf819fc9a..bbc5e2129 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -373,7 +373,7 @@ def get_decimal_quantum(precision): def format_decimal( - number, format=None, locale=LC_NUMERIC, decimal_quantization=True): + number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True): u"""Return the given decimal number formatted for a specific locale. >>> format_decimal(1.2345, locale='en_US') @@ -401,19 +401,25 @@ def format_decimal( u'1.235' >>> format_decimal(1.2346, locale='en_US', decimal_quantization=False) u'1.2346' + >>> format_decimal(12345.67, locale='fr_CA', group_separator=False) + u'12345,67' + >>> format_decimal(12345.67, locale='en_US', group_separator=True) + u'12,345.67' :param number: the number to format :param format: :param locale: the `Locale` object or locale identifier :param decimal_quantization: Truncate and round high-precision numbers to the format pattern. Defaults to `True`. + :param group_separator: Boolean to switch group separator on/off in a locale's + number format. """ locale = Locale.parse(locale) if not format: format = locale.decimal_formats.get(format) pattern = parse_pattern(format) return pattern.apply( - number, locale, decimal_quantization=decimal_quantization) + number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator) class UnknownCurrencyFormatError(KeyError): @@ -422,7 +428,7 @@ class UnknownCurrencyFormatError(KeyError): def format_currency( number, currency, format=None, locale=LC_NUMERIC, currency_digits=True, - format_type='standard', decimal_quantization=True): + format_type='standard', decimal_quantization=True, group_separator=True): u"""Return formatted currency value. >>> format_currency(1099.98, 'USD', locale='en_US') @@ -472,6 +478,12 @@ def format_currency( ... UnknownCurrencyFormatError: "'unknown' is not a known currency format type" + >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False) + u'$101299.98' + + >>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True) + u'$101,299.98' + You can also pass format_type='name' to use long display names. The order of the number and currency name, along with the correct localized plural form of the currency name, is chosen according to locale: @@ -500,12 +512,14 @@ def format_currency( :param format_type: the currency format type to use :param decimal_quantization: Truncate and round high-precision numbers to the format pattern. Defaults to `True`. + :param group_separator: Boolean to switch group separator on/off in a locale's + number format. """ if format_type == 'name': return _format_currency_long_name(number, currency, format=format, locale=locale, currency_digits=currency_digits, - decimal_quantization=decimal_quantization) + decimal_quantization=decimal_quantization, group_separator=group_separator) locale = Locale.parse(locale) if format: pattern = parse_pattern(format) @@ -518,12 +532,12 @@ def format_currency( return pattern.apply( number, locale, currency=currency, currency_digits=currency_digits, - decimal_quantization=decimal_quantization) + decimal_quantization=decimal_quantization, group_separator=group_separator) def _format_currency_long_name( number, currency, format=None, locale=LC_NUMERIC, currency_digits=True, - format_type='standard', decimal_quantization=True): + format_type='standard', decimal_quantization=True, group_separator=True): # Algorithm described here: # https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies locale = Locale.parse(locale) @@ -552,13 +566,13 @@ def _format_currency_long_name( number_part = pattern.apply( number, locale, currency=currency, currency_digits=currency_digits, - decimal_quantization=decimal_quantization) + decimal_quantization=decimal_quantization, group_separator=group_separator) return unit_pattern.format(number_part, display_name) def format_percent( - number, format=None, locale=LC_NUMERIC, decimal_quantization=True): + number, format=None, locale=LC_NUMERIC, decimal_quantization=True, group_separator=True): """Return formatted percent value for a specific locale. >>> format_percent(0.34, locale='en_US') @@ -582,18 +596,26 @@ def format_percent( >>> format_percent(23.9876, locale='en_US', decimal_quantization=False) u'2,398.76%' + >>> format_percent(229291.1234, locale='pt_BR', group_separator=False) + u'22929112%' + + >>> format_percent(229291.1234, locale='pt_BR', group_separator=True) + u'22.929.112%' + :param number: the percent number to format :param format: :param locale: the `Locale` object or locale identifier :param decimal_quantization: Truncate and round high-precision numbers to the format pattern. Defaults to `True`. + :param group_separator: Boolean to switch group separator on/off in a locale's + number format. """ locale = Locale.parse(locale) if not format: format = locale.percent_formats.get(format) pattern = parse_pattern(format) return pattern.apply( - number, locale, decimal_quantization=decimal_quantization) + number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator) def format_scientific( @@ -913,6 +935,7 @@ def apply( currency_digits=True, decimal_quantization=True, force_frac=None, + group_separator=True, ): """Renders into a string a number following the defined pattern. @@ -952,8 +975,8 @@ def apply( if self.exp_prec: value, exp, exp_sign = self.scientific_notation_elements(value, locale) - # Adjust the precision of the fractionnal part and force it to the - # currency's if neccessary. + # Adjust the precision of the fractional part and force it to the + # currency's if necessary. if force_frac: # TODO (3.x?): Remove this parameter warnings.warn('The force_frac parameter to NumberPattern.apply() is deprecated.', DeprecationWarning) @@ -975,7 +998,7 @@ def apply( # Render scientific notation. if self.exp_prec: number = ''.join([ - self._quantize_value(value, locale, frac_prec), + self._quantize_value(value, locale, frac_prec, group_separator), get_exponential_symbol(locale), exp_sign, self._format_int( @@ -993,7 +1016,7 @@ def apply( # A normal number pattern. else: - number = self._quantize_value(value, locale, frac_prec) + number = self._quantize_value(value, locale, frac_prec, group_separator) retval = ''.join([ self.prefix[is_negative], @@ -1060,13 +1083,14 @@ def _format_int(self, value, min, max, locale): gsize = self.grouping[1] return value + ret - def _quantize_value(self, value, locale, frac_prec): + def _quantize_value(self, value, locale, frac_prec, group_separator): quantum = get_decimal_quantum(frac_prec[1]) rounded = value.quantize(quantum) a, sep, b = "{:f}".format(rounded).partition(".") - number = (self._format_int(a, self.int_prec[0], - self.int_prec[1], locale) + - self._format_frac(b or '0', locale, frac_prec)) + integer_part = a + if group_separator: + integer_part = self._format_int(a, self.int_prec[0], self.int_prec[1], locale) + number = integer_part + self._format_frac(b or '0', locale, frac_prec) return number def _format_frac(self, value, locale, force_frac=None): diff --git a/tests/test_numbers.py b/tests/test_numbers.py index a980a66ad..3db5f3307 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -153,6 +153,36 @@ def test_formatting_of_very_small_decimals(self): fmt = numbers.format_decimal(number, format="@@@", locale='en_US') self.assertEqual('0.000000700', fmt) + def test_group_separator(self): + self.assertEqual('29567.12', numbers.format_decimal(29567.12, + locale='en_US', group_separator=False)) + self.assertEqual('29567,12', numbers.format_decimal(29567.12, + locale='fr_CA', group_separator=False)) + self.assertEqual('29567,12', numbers.format_decimal(29567.12, + locale='pt_BR', group_separator=False)) + self.assertEqual(u'$1099.98', numbers.format_currency(1099.98, 'USD', + locale='en_US', group_separator=False)) + self.assertEqual(u'101299,98\xa0€', numbers.format_currency(101299.98, 'EUR', + locale='fr_CA', group_separator=False)) + self.assertEqual('101299.98 euros', numbers.format_currency(101299.98, 'EUR', + locale='en_US', group_separator=False, format_type='name')) + self.assertEqual(u'25123412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=False)) + + self.assertEqual(u'29,567.12', numbers.format_decimal(29567.12, + locale='en_US', group_separator=True)) + self.assertEqual(u'29\u202f567,12', numbers.format_decimal(29567.12, + locale='fr_CA', group_separator=True)) + self.assertEqual(u'29.567,12', numbers.format_decimal(29567.12, + locale='pt_BR', group_separator=True)) + self.assertEqual(u'$1,099.98', numbers.format_currency(1099.98, 'USD', + locale='en_US', group_separator=True)) + self.assertEqual(u'101\u202f299,98\xa0\u20ac', numbers.format_currency(101299.98, 'EUR', + locale='fr_CA', group_separator=True)) + self.assertEqual(u'101,299.98 euros', numbers.format_currency(101299.98, 'EUR', + locale='en_US', group_separator=True, + format_type='name')) + self.assertEqual(u'25\xa0123\xa0412\xa0%', numbers.format_percent(251234.1234, locale='sv_SE', group_separator=True)) + class NumberParsingTestCase(unittest.TestCase): From 044416707f9effbe77582779a09ae9e37a0f6ffd Mon Sep 17 00:00:00 2001 From: Nikiforov Konstantin Date: Wed, 30 Sep 2020 20:17:16 +0500 Subject: [PATCH 24/50] LazyProxy: Handle AttributeError in specified func (#724) Fixes #723 Co-authored-by: Aarni Koskela --- babel/support.py | 12 ++++++++++-- tests/test_support.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/babel/support.py b/babel/support.py index efe41d562..47f812d8a 100644 --- a/babel/support.py +++ b/babel/support.py @@ -165,7 +165,7 @@ class LazyProxy(object): Hello, universe! Hello, world! """ - __slots__ = ['_func', '_args', '_kwargs', '_value', '_is_cache_enabled'] + __slots__ = ['_func', '_args', '_kwargs', '_value', '_is_cache_enabled', '_attribute_error'] def __init__(self, func, *args, **kwargs): is_cache_enabled = kwargs.pop('enable_cache', True) @@ -175,11 +175,17 @@ def __init__(self, func, *args, **kwargs): object.__setattr__(self, '_kwargs', kwargs) object.__setattr__(self, '_is_cache_enabled', is_cache_enabled) object.__setattr__(self, '_value', None) + object.__setattr__(self, '_attribute_error', None) @property def value(self): if self._value is None: - value = self._func(*self._args, **self._kwargs) + try: + value = self._func(*self._args, **self._kwargs) + except AttributeError as error: + object.__setattr__(self, '_attribute_error', error) + raise + if not self._is_cache_enabled: return value object.__setattr__(self, '_value', value) @@ -249,6 +255,8 @@ def __delattr__(self, name): delattr(self.value, name) def __getattr__(self, name): + if self._attribute_error is not None: + raise self._attribute_error return getattr(self.value, name) def __setattr__(self, name, value): diff --git a/tests/test_support.py b/tests/test_support.py index b4dd823cd..1b74ae8bc 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -279,6 +279,17 @@ def first(xs): self.assertEqual(2, proxy.value) self.assertEqual(1, proxy_deepcopy.value) + def test_handle_attribute_error(self): + + def raise_attribute_error(): + raise AttributeError('message') + + proxy = support.LazyProxy(raise_attribute_error) + with pytest.raises(AttributeError) as exception: + proxy.value + + self.assertEqual('message', str(exception.value)) + def test_format_date(): fmt = support.Format('en_US') From 3e6d9a70e11041ad6737630281098866462ed1e3 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 1 Oct 2020 15:54:08 +0300 Subject: [PATCH 25/50] Switch downloader to CLDR 37 --- scripts/download_import_cldr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py index 434b04f80..531a04c62 100755 --- a/scripts/download_import_cldr.py +++ b/scripts/download_import_cldr.py @@ -13,9 +13,9 @@ from urllib import urlretrieve -URL = 'http://unicode.org/Public/cldr/36/core.zip' -FILENAME = 'cldr-core-36.zip' -FILESUM = '07279e56c1f4266d140b907ef3ec379dce0a99542303a9628562ac5fe460ba43' +URL = 'http://unicode.org/Public/cldr/37/core.zip' +FILENAME = 'cldr-core-37.zip' +FILESUM = 'ba93f5ba256a61a6f8253397c6c4b1a9b9e77531f013cc7ffa7977b5f7e4da57' BLKSIZE = 131072 From 462444f35756551f83809dab7f7424a907033a9e Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 1 Oct 2020 16:27:02 +0300 Subject: [PATCH 26/50] Adapt things to new compound pattern format --- babel/units.py | 9 ++++++--- scripts/import_cldr.py | 20 +++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/babel/units.py b/babel/units.py index 89c491365..07637358c 100644 --- a/babel/units.py +++ b/babel/units.py @@ -75,8 +75,10 @@ def format_unit(value, measurement_unit, length='long', format=None, locale=LC_N u'12 metri' >>> format_unit(15.5, 'length-mile', locale='fi_FI') u'15,5 mailia' - >>> format_unit(1200, 'pressure-inch-hg', locale='nb') - u'1\\xa0200 tommer kvikks\\xf8lv' + >>> format_unit(1200, 'pressure-millimeter-ofhg', locale='nb') + u'1\\xa0200 millimeter kvikks\\xf8lv' + >>> format_unit(270, 'ton', locale='en') + u'270 tons' Number formats may be overridden with the ``format`` parameter. @@ -271,6 +273,7 @@ def format_compound_unit( else: # Bare denominator formatted_denominator = format_decimal(denominator_value, format=format, locale=locale) - per_pattern = locale._data["compound_unit_patterns"].get("per", {}).get(length, "{0}/{1}") + # TODO: this doesn't support "compound_variations" (or "prefix"), and will fall back to the "x/y" representation + per_pattern = locale._data["compound_unit_patterns"].get("per", {}).get(length, {}).get("compound", "{0}/{1}") return per_pattern.format(formatted_numerator, formatted_denominator) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 7ea6481a2..aab2888e1 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -853,9 +853,23 @@ def parse_unit_patterns(data, tree): for unit in elem.findall('compoundUnit'): unit_type = unit.attrib['type'] - compound_patterns.setdefault(unit_type, {})[unit_length_type] = ( - _text(unit.find('compoundUnitPattern')) - ) + compound_unit_info = {} + compound_variations = {} + for child in unit.getchildren(): + if child.tag == "unitPrefixPattern": + compound_unit_info['prefix'] = _text(child) + elif child.tag == "compoundUnitPattern": + compound_variations[None] = _text(child) + elif child.tag == "compoundUnitPattern1": + compound_variations[child.attrib.get('count')] = _text(child) + if compound_variations: + compound_variation_values = set(compound_variations.values()) + if len(compound_variation_values) == 1: + # shortcut: if all compound variations are the same, only store one + compound_unit_info['compound'] = next(iter(compound_variation_values)) + else: + compound_unit_info['compound_variations'] = compound_variations + compound_patterns.setdefault(unit_type, {})[unit_length_type] = compound_unit_info def parse_date_fields(data, tree): From 31abefa1009ee8f176a7fcd9311935ea806bc1a0 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 2 Oct 2020 10:56:37 +0300 Subject: [PATCH 27/50] Correct default timedelta format to 'long' Augments 9327e0824a1bbed538e73d42b971988f8214b490 --- babel/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel/support.py b/babel/support.py index 47f812d8a..8d905ed64 100644 --- a/babel/support.py +++ b/babel/support.py @@ -79,7 +79,7 @@ def time(self, time=None, format='medium'): return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale) def timedelta(self, delta, granularity='second', threshold=.85, - format='medium', add_direction=False): + format='long', add_direction=False): """Return a time delta according to the rules of the given locale. >>> from datetime import timedelta From acf1caeed35c180ce7c14f822a23ab297931feab Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 2 Oct 2020 10:56:59 +0300 Subject: [PATCH 28/50] Skip deprecated l*gettext functions on Python 3.8+ --- tests/test_support.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_support.py b/tests/test_support.py index 1b74ae8bc..966cb4e62 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -17,6 +17,7 @@ import tempfile import unittest import pytest +import sys from datetime import date, datetime, timedelta from babel import support @@ -26,6 +27,7 @@ get_arg_spec = (inspect.getargspec if PY2 else inspect.getfullargspec) +SKIP_LGETTEXT = sys.version_info >= (3, 8) @pytest.mark.usefixtures("os_environ") class TranslationsTestCase(unittest.TestCase): @@ -76,6 +78,7 @@ def test_upgettext(self): self.assertEqualTypeToo(u'VohCTX', self.translations.upgettext('foo', 'foo')) + @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") def test_lpgettext(self): self.assertEqualTypeToo(b'Voh', self.translations.lgettext('foo')) self.assertEqualTypeToo(b'VohCTX', self.translations.lpgettext('foo', @@ -105,6 +108,7 @@ def test_unpgettext(self): self.translations.unpgettext('foo', 'foo1', 'foos1', 2)) + @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") def test_lnpgettext(self): self.assertEqualTypeToo(b'Voh1', self.translations.lngettext('foo1', 'foos1', 1)) @@ -129,6 +133,7 @@ def test_dupgettext(self): self.assertEqualTypeToo( u'VohCTXD', self.translations.dupgettext('messages1', 'foo', 'foo')) + @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") def test_ldpgettext(self): self.assertEqualTypeToo( b'VohD', self.translations.ldgettext('messages1', 'foo')) @@ -159,6 +164,7 @@ def test_dunpgettext(self): u'VohsCTXD1', self.translations.dunpgettext('messages1', 'foo', 'foo1', 'foos1', 2)) + @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") def test_ldnpgettext(self): self.assertEqualTypeToo( b'VohD1', self.translations.ldngettext('messages1', 'foo1', 'foos1', 1)) @@ -197,7 +203,11 @@ def setUp(self): self.null_translations = support.NullTranslations(fp=fp) def method_names(self): - return [name for name in dir(self.translations) if 'gettext' in name] + names = [name for name in dir(self.translations) if 'gettext' in name] + if SKIP_LGETTEXT: + # Remove deprecated l*gettext functions + names = [name for name in names if not name.startswith('l')] + return names def test_same_methods(self): for name in self.method_names(): From 2b615f7ea7d3c1383e26d1cea402f43895750843 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 2 Oct 2020 12:29:48 +0300 Subject: [PATCH 29/50] Ignore lack of coverage on lines that e.g. raise warnings --- .coveragerc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..a3d8ae65e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_lines = + NotImplemented + pragma: no cover + warnings.warn \ No newline at end of file From 78c42824a61427aa73ebfa6ad5e35da16030671d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 2 Oct 2020 12:35:56 +0300 Subject: [PATCH 30/50] Use 'if not' instead of 'if ... is False' for no_fuzzy_matching Matches the behavior elsewhere in the same function Fixes #698 --- babel/messages/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 2fcb461a8..ed9e9a062 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -807,7 +807,7 @@ def _merge(message, oldkey, newkey): if key in messages: _merge(message, key, key) else: - if no_fuzzy_matching is False: + if not no_fuzzy_matching: # do some fuzzy matching with difflib if isinstance(key, tuple): matchkey = key[0] # just the msgid, no context From 77a3d2f2a4b897c5a9d09a2cbe7159bf7ef37f0c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Nov 2020 12:37:36 -0800 Subject: [PATCH 31/50] Remove deprecated 'sudo: false' from Travis configuraiton https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9650f674c..0aebbf982 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ dist: xenial language: python -# Use travis docker infrastructure for greater speed -sudo: false - cache: directories: - cldr From 53153419972aef6e5c06c6cc1c58896de6b2a281 Mon Sep 17 00:00:00 2001 From: Alessio Bogon Date: Tue, 10 Nov 2020 10:34:57 +0100 Subject: [PATCH 32/50] Handle ZoneInfo objects in get_timezone_location, get_timezone_name (#741) Fixes #740 --- .travis.yml | 2 +- babel/dates.py | 26 +++++---- tests/test_dates.py | 129 ++++++++++++++++++++++++++++++-------------- tox.ini | 1 + 4 files changed, 108 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0aebbf982..e7401cc4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ matrix: install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 + - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 'backports.zoneinfo;python_version>="3.6" and python_version<"3.9"' - pip install --editable . script: diff --git a/babel/dates.py b/babel/dates.py index f1bd66faf..1775029fa 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -76,6 +76,21 @@ def _get_dt_and_tzinfo(dt_or_tzinfo): return dt, tzinfo +def _get_tz_name(dt_or_tzinfo): + """ + Get the timezone name out of a time, datetime, or tzinfo object. + + :rtype: str + """ + dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) + if hasattr(tzinfo, 'zone'): # pytz object + return tzinfo.zone + elif hasattr(tzinfo, 'key') and tzinfo.key is not None: # ZoneInfo object + return tzinfo.key + else: + return tzinfo.tzname(dt or datetime.utcnow()) + + def _get_datetime(instant): """ Get a datetime out of an "instant" (date, time, datetime, number). @@ -500,13 +515,9 @@ def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME, return_city=False): :return: the localized timezone name using location format """ - dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) locale = Locale.parse(locale) - if hasattr(tzinfo, 'zone'): - zone = tzinfo.zone - else: - zone = tzinfo.tzname(dt or datetime.utcnow()) + zone = _get_tz_name(dt_or_tzinfo) # Get the canonical time-zone code zone = get_global('zone_aliases').get(zone, zone) @@ -619,10 +630,7 @@ def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False, dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo) locale = Locale.parse(locale) - if hasattr(tzinfo, 'zone'): - zone = tzinfo.zone - else: - zone = tzinfo.tzname(dt) + zone = _get_tz_name(dt_or_tzinfo) if zone_variant is None: if dt is None: diff --git a/tests/test_dates.py b/tests/test_dates.py index 5be0d16a1..423f737de 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -24,6 +24,23 @@ from babel.util import FixedOffsetTimezone +@pytest.fixture(params=["pytz.timezone", "zoneinfo.ZoneInfo"]) +def timezone_getter(request): + if request.param == "pytz.timezone": + return timezone + elif request.param == "zoneinfo.ZoneInfo": + try: + import zoneinfo + except ImportError: + try: + from backports import zoneinfo + except ImportError: + pytest.skip("zoneinfo not available") + return zoneinfo.ZoneInfo + else: + raise NotImplementedError + + class DateTimeFormatTestCase(unittest.TestCase): def test_quarter_format(self): @@ -583,8 +600,8 @@ def test_get_timezone_gmt(): assert dates.get_timezone_gmt(dt, 'long', locale='fr_FR') == u'UTC-07:00' -def test_get_timezone_location(): - tz = timezone('America/St_Johns') +def test_get_timezone_location(timezone_getter): + tz = timezone_getter('America/St_Johns') assert (dates.get_timezone_location(tz, locale='de_DE') == u"Kanada (St. John\u2019s) Zeit") assert (dates.get_timezone_location(tz, locale='en') == @@ -592,51 +609,83 @@ def test_get_timezone_location(): assert (dates.get_timezone_location(tz, locale='en', return_city=True) == u'St. John’s') - tz = timezone('America/Mexico_City') + tz = timezone_getter('America/Mexico_City') assert (dates.get_timezone_location(tz, locale='de_DE') == u'Mexiko (Mexiko-Stadt) Zeit') - tz = timezone('Europe/Berlin') + tz = timezone_getter('Europe/Berlin') assert (dates.get_timezone_location(tz, locale='de_DE') == u'Deutschland (Berlin) Zeit') -def test_get_timezone_name(): - dt = time(15, 30, tzinfo=timezone('America/Los_Angeles')) - assert (dates.get_timezone_name(dt, locale='en_US') == - u'Pacific Standard Time') - assert (dates.get_timezone_name(dt, locale='en_US', return_zone=True) == - u'America/Los_Angeles') - assert dates.get_timezone_name(dt, width='short', locale='en_US') == u'PST' - - tz = timezone('America/Los_Angeles') - assert dates.get_timezone_name(tz, locale='en_US') == u'Pacific Time' - assert dates.get_timezone_name(tz, 'short', locale='en_US') == u'PT' - - tz = timezone('Europe/Berlin') - assert (dates.get_timezone_name(tz, locale='de_DE') == - u'Mitteleurop\xe4ische Zeit') - assert (dates.get_timezone_name(tz, locale='pt_BR') == - u'Hor\xe1rio da Europa Central') - - tz = timezone('America/St_Johns') - assert dates.get_timezone_name(tz, locale='de_DE') == u'Neufundland-Zeit' - - tz = timezone('America/Los_Angeles') - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='generic') == u'PT' - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='standard') == u'PST' - assert dates.get_timezone_name(tz, locale='en', width='short', - zone_variant='daylight') == u'PDT' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='generic') == u'Pacific Time' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='standard') == u'Pacific Standard Time' - assert dates.get_timezone_name(tz, locale='en', width='long', - zone_variant='daylight') == u'Pacific Daylight Time' - - localnow = datetime.utcnow().replace(tzinfo=timezone('UTC')).astimezone(dates.LOCALTZ) +@pytest.mark.parametrize( + "tzname, params, expected", + [ + ("America/Los_Angeles", {"locale": "en_US"}, u"Pacific Time"), + ("America/Los_Angeles", {"width": "short", "locale": "en_US"}, u"PT"), + ("Europe/Berlin", {"locale": "de_DE"}, u"Mitteleurop\xe4ische Zeit"), + ("Europe/Berlin", {"locale": "pt_BR"}, u"Hor\xe1rio da Europa Central"), + ("America/St_Johns", {"locale": "de_DE"}, u"Neufundland-Zeit"), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "generic"}, + u"PT", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "standard"}, + u"PST", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "short", "zone_variant": "daylight"}, + u"PDT", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "generic"}, + u"Pacific Time", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "standard"}, + u"Pacific Standard Time", + ), + ( + "America/Los_Angeles", + {"locale": "en", "width": "long", "zone_variant": "daylight"}, + u"Pacific Daylight Time", + ), + ("Europe/Berlin", {"locale": "en_US"}, u"Central European Time"), + ], +) +def test_get_timezone_name_tzinfo(timezone_getter, tzname, params, expected): + tz = timezone_getter(tzname) + assert dates.get_timezone_name(tz, **params) == expected + + +@pytest.mark.parametrize("timezone_getter", ["pytz.timezone"], indirect=True) +@pytest.mark.parametrize( + "tzname, params, expected", + [ + ("America/Los_Angeles", {"locale": "en_US"}, u"Pacific Standard Time"), + ( + "America/Los_Angeles", + {"locale": "en_US", "return_zone": True}, + u"America/Los_Angeles", + ), + ("America/Los_Angeles", {"width": "short", "locale": "en_US"}, u"PST"), + ], +) +def test_get_timezone_name_time_pytz(timezone_getter, tzname, params, expected): + """pytz (by design) can't determine if the time is in DST or not, + so it will always return Standard time""" + dt = time(15, 30, tzinfo=timezone_getter(tzname)) + assert dates.get_timezone_name(dt, **params) == expected + + +def test_get_timezone_name_misc(timezone_getter): + localnow = datetime.utcnow().replace(tzinfo=timezone_getter('UTC')).astimezone(dates.LOCALTZ) assert (dates.get_timezone_name(None, locale='en_US') == dates.get_timezone_name(localnow, locale='en_US')) diff --git a/tox.ini b/tox.ini index eccffea94..c069c5942 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ deps = pytest-cov==2.6.1 cdecimal: m3-cdecimal freezegun==0.3.12 + backports.zoneinfo;python_version>"3.6" and python_version<"3.9" whitelist_externals = make commands = make clean-cldr test passenv = PYTHON_TEST_FLAGS From 6e29f11234a046fca4716b8804f8cd62c7b51166 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 10:35:35 +0200 Subject: [PATCH 33/50] Py.test 6 support (#752) * Support Py.test 6+ * Run CI on Py.test 6 on new Pythons --- .travis.yml | 18 +++++++++++++++++- conftest.py | 3 +++ tox.ini | 5 +++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e7401cc4f..7f7331be5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,33 +11,49 @@ matrix: include: - os: linux python: 2.7 + env: + - PYTEST_VERSION=4.3.1 - os: linux python: 2.7 env: - CDECIMAL=m3-cdecimal + - PYTEST_VERSION=4.3.1 - os: linux dist: trusty python: pypy + env: + - PYTEST_VERSION=4.3.1 - os: linux dist: trusty python: pypy3 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.4 + env: + - PYTEST_VERSION=4.3.1 - os: linux python: 3.5 env: - PYTHON_TEST_FLAGS=-bb + - PYTEST_VERSION=6.1.2 - os: linux python: 3.6 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.7 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.8 + env: + - PYTEST_VERSION=6.1.2 install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 'backports.zoneinfo;python_version>="3.6" and python_version<"3.9"' + - pip install --upgrade $CDECIMAL pytest==$PYTEST_VERSION pytest-cov freezegun==0.3.12 'backports.zoneinfo;python_version>="3.6" and python_version<"3.9"' - pip install --editable . script: diff --git a/conftest.py b/conftest.py index 32bd1362a..bd9f2d32d 100644 --- a/conftest.py +++ b/conftest.py @@ -8,4 +8,7 @@ def pytest_collect_file(path, parent): if babel_path.common(path) == babel_path: if path.ext == ".py": + # TODO: remove check when dropping support for old Pytest + if hasattr(DoctestModule, "from_parent"): + return DoctestModule.from_parent(parent, fspath=path) return DoctestModule(path, parent) diff --git a/tox.ini b/tox.ini index c069c5942..27faf5bbb 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,9 @@ envlist = py27, pypy, py34, py35, py36, py37, pypy3, py27-cdecimal [testenv] deps = - pytest==4.3.1 - pytest-cov==2.6.1 + pytest==4.3.1;python_version<"3.5" + pytest==6.1.2;python_version>="3.5" + pytest-cov cdecimal: m3-cdecimal freezegun==0.3.12 backports.zoneinfo;python_version>"3.6" and python_version<"3.9" From 99cc2c6fba08939dc8693ac0ae53c6046cc92459 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 10:35:35 +0200 Subject: [PATCH 34/50] Py.test 6 support (#752) * Support Py.test 6+ * Run CI on Py.test 6 on new Pythons Cherry-pick of commit 6e29f11 --- .travis.yml | 18 +++++++++++++++++- conftest.py | 3 +++ tox.ini | 5 +++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9650f674c..495aa2721 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,33 +14,49 @@ matrix: include: - os: linux python: 2.7 + env: + - PYTEST_VERSION=4.3.1 - os: linux python: 2.7 env: - CDECIMAL=m3-cdecimal + - PYTEST_VERSION=4.3.1 - os: linux dist: trusty python: pypy + env: + - PYTEST_VERSION=4.3.1 - os: linux dist: trusty python: pypy3 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.4 + env: + - PYTEST_VERSION=4.3.1 - os: linux python: 3.5 env: - PYTHON_TEST_FLAGS=-bb + - PYTEST_VERSION=6.1.2 - os: linux python: 3.6 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.7 + env: + - PYTEST_VERSION=6.1.2 - os: linux python: 3.8 + env: + - PYTEST_VERSION=6.1.2 install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==4.3.1 pytest-cov==2.6.1 freezegun==0.3.12 + - pip install --upgrade $CDECIMAL pytest==$PYTEST_VERSION pytest-cov freezegun==0.3.12 - pip install --editable . script: diff --git a/conftest.py b/conftest.py index 32bd1362a..bd9f2d32d 100644 --- a/conftest.py +++ b/conftest.py @@ -8,4 +8,7 @@ def pytest_collect_file(path, parent): if babel_path.common(path) == babel_path: if path.ext == ".py": + # TODO: remove check when dropping support for old Pytest + if hasattr(DoctestModule, "from_parent"): + return DoctestModule.from_parent(parent, fspath=path) return DoctestModule(path, parent) diff --git a/tox.ini b/tox.ini index eccffea94..cda03a292 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,9 @@ envlist = py27, pypy, py34, py35, py36, py37, pypy3, py27-cdecimal [testenv] deps = - pytest==4.3.1 - pytest-cov==2.6.1 + pytest==4.3.1;python_version<"3.5" + pytest==6.1.2;python_version>="3.5" + pytest-cov cdecimal: m3-cdecimal freezegun==0.3.12 whitelist_externals = make From d77fe5d3275f5cf9774fa6c9bf097b466ddb6473 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 11:27:00 +0200 Subject: [PATCH 35/50] Distill changelog --- CHANGES | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 4441f68e7..c694ec8c7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,16 @@ Babel Changelog =============== +Version 2.8.1 +------------- + +This is solely a patch release to make running tests on Py.test 6+ possible. + +Bugfixes +~~~~~~~~ + +* Support Py.test 6 - Aarni Koskela (#747, #750, #752) + Version 2.8.0 ------------- From bd836a4ac51958c083ff13ea02f8c18b7c0f4e2d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 11:27:58 +0200 Subject: [PATCH 36/50] Bump copyright year from 2019 to 2020 --- LICENSE | 2 +- babel/__init__.py | 2 +- babel/core.py | 2 +- babel/dates.py | 2 +- babel/lists.py | 2 +- babel/localedata.py | 2 +- babel/localtime/__init__.py | 2 +- babel/messages/__init__.py | 2 +- babel/messages/catalog.py | 2 +- babel/messages/checkers.py | 2 +- babel/messages/extract.py | 2 +- babel/messages/frontend.py | 2 +- babel/messages/jslexer.py | 2 +- babel/messages/mofile.py | 2 +- babel/messages/plurals.py | 2 +- babel/messages/pofile.py | 2 +- babel/numbers.py | 2 +- babel/plural.py | 2 +- babel/support.py | 2 +- babel/util.py | 2 +- docs/conf.py | 2 +- scripts/dump_data.py | 2 +- scripts/dump_global.py | 2 +- scripts/import_cldr.py | 2 +- tests/messages/test_catalog.py | 2 +- tests/messages/test_checkers.py | 2 +- tests/messages/test_extract.py | 2 +- tests/messages/test_frontend.py | 2 +- tests/messages/test_mofile.py | 2 +- tests/messages/test_plurals.py | 2 +- tests/messages/test_pofile.py | 2 +- tests/test_core.py | 2 +- tests/test_dates.py | 2 +- tests/test_localedata.py | 2 +- tests/test_numbers.py | 2 +- tests/test_plural.py | 2 +- tests/test_support.py | 2 +- tests/test_util.py | 2 +- 38 files changed, 38 insertions(+), 38 deletions(-) diff --git a/LICENSE b/LICENSE index 10722cc18..7e2f12e46 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2019 by the Babel Team, see AUTHORS for more information. +Copyright (c) 2013-2020 by the Babel Team, see AUTHORS for more information. All rights reserved. diff --git a/babel/__init__.py b/babel/__init__.py index c10a8bd1f..12674e051 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -13,7 +13,7 @@ access to various locale display names, localized number and date formatting, etc. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/core.py b/babel/core.py index a80807a61..a0c35b4ea 100644 --- a/babel/core.py +++ b/babel/core.py @@ -5,7 +5,7 @@ Core locale representation and locale data access. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/dates.py b/babel/dates.py index f1bd66faf..dea365a7f 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -12,7 +12,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/lists.py b/babel/lists.py index ab5a24c40..e0bd7ed7f 100644 --- a/babel/lists.py +++ b/babel/lists.py @@ -11,7 +11,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2015-2019 by the Babel Team. + :copyright: (c) 2015-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localedata.py b/babel/localedata.py index e012abbf2..f4771d1fd 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -8,7 +8,7 @@ :note: The `Locale` class, which uses this module under the hood, provides a more convenient interface for accessing the locale data. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py index aefd8a3e7..8c9203a7b 100644 --- a/babel/localtime/__init__.py +++ b/babel/localtime/__init__.py @@ -6,7 +6,7 @@ Babel specific fork of tzlocal to determine the local timezone of the system. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/__init__.py b/babel/messages/__init__.py index 5b69675f3..77c79621b 100644 --- a/babel/messages/__init__.py +++ b/babel/messages/__init__.py @@ -5,7 +5,7 @@ Support for ``gettext`` message catalogs. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 2fcb461a8..b9961086c 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -5,7 +5,7 @@ Data structures for message catalogs. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py index 8c1effaf5..29add9fce 100644 --- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -7,7 +7,7 @@ :since: version 0.9 - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/extract.py b/babel/messages/extract.py index e7d7ad70f..8f53fa298 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -13,7 +13,7 @@ The main entry points into the extraction functionality are the functions `extract_from_dir` and `extract_from_file`. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index 475605549..0b65a7c6b 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -5,7 +5,7 @@ Frontends for the message extraction functionality. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import print_function diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index ace0b47e0..f56c80d98 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -6,7 +6,7 @@ A simple JavaScript 1.5 lexer which is used for the JavaScript extractor. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ from collections import namedtuple diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py index dfd923d23..1ab3e4e9c 100644 --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -5,7 +5,7 @@ Writing of files in the ``gettext`` MO (machine object) format. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py index 81234580d..4755cfcf9 100644 --- a/babel/messages/plurals.py +++ b/babel/messages/plurals.py @@ -5,7 +5,7 @@ Plural form definitions. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index 93b0697c6..aff886d8e 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -6,7 +6,7 @@ Reading and writing of files in the ``gettext`` PO (portable object) format. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/numbers.py b/babel/numbers.py index cf819fc9a..e246baed7 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -12,7 +12,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ # TODO: diff --git a/babel/plural.py b/babel/plural.py index 1e2b2734b..3c9a3e614 100644 --- a/babel/plural.py +++ b/babel/plural.py @@ -5,7 +5,7 @@ CLDR Plural support. See UTS #35. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ import re diff --git a/babel/support.py b/babel/support.py index efe41d562..a8709e46d 100644 --- a/babel/support.py +++ b/babel/support.py @@ -8,7 +8,7 @@ .. note: the code in this module is not used by Babel itself - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/util.py b/babel/util.py index 73a90516f..35039c41f 100644 --- a/babel/util.py +++ b/babel/util.py @@ -5,7 +5,7 @@ Various utility classes and functions. - :copyright: (c) 2013-2019 by the Babel Team. + :copyright: (c) 2013-2020 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/docs/conf.py b/docs/conf.py index 83c5bf206..41d1049b8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,7 @@ # General information about the project. project = u'Babel' -copyright = u'2019, The Babel Team' +copyright = u'2020, The Babel Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/scripts/dump_data.py b/scripts/dump_data.py index e452248d7..57439b98f 100755 --- a/scripts/dump_data.py +++ b/scripts/dump_data.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/dump_global.py b/scripts/dump_global.py index 8db55f2dc..a27b31617 100755 --- a/scripts/dump_global.py +++ b/scripts/dump_global.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 8993b68e4..f0558c239 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index f31dca310..2d9e31d00 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_checkers.py b/tests/messages/test_checkers.py index ec845001e..bf813bc05 100644 --- a/tests/messages/test_checkers.py +++ b/tests/messages/test_checkers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py index 2f41ddc2c..38979cd30 100644 --- a/tests/messages/test_extract.py +++ b/tests/messages/test_extract.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_frontend.py b/tests/messages/test_frontend.py index ad3ea0df3..38165b354 100644 --- a/tests/messages/test_frontend.py +++ b/tests/messages/test_frontend.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_mofile.py b/tests/messages/test_mofile.py index b1851f297..395b9fb1a 100644 --- a/tests/messages/test_mofile.py +++ b/tests/messages/test_mofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_plurals.py b/tests/messages/test_plurals.py index bdca8f6a8..62c770870 100644 --- a/tests/messages/test_plurals.py +++ b/tests/messages/test_plurals.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index e77fa6e02..c8125f9d6 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_core.py b/tests/test_core.py index c146aae9c..53b9d1875 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_dates.py b/tests/test_dates.py index 5be0d16a1..16fda9cbc 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_localedata.py b/tests/test_localedata.py index dbacba0d5..83cd66994 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_numbers.py b/tests/test_numbers.py index a980a66ad..bda7bffba 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_plural.py b/tests/test_plural.py index c54d07a57..1a9fbe507 100644 --- a/tests/test_plural.py +++ b/tests/test_plural.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_support.py b/tests/test_support.py index b4dd823cd..f9a552d16 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_util.py b/tests/test_util.py index a6a4450cf..5b897aa7d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2019 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which From efa0d6d3d7f632ff398705d4f767d953226c16fd Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 11:28:06 +0200 Subject: [PATCH 37/50] Bump version to 2.8.1 --- babel/__init__.py | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/babel/__init__.py b/babel/__init__.py index 12674e051..cbcb4fb2f 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -21,4 +21,4 @@ negotiate_locale, parse_locale, get_locale_identifier -__version__ = '2.8.0' +__version__ = '2.8.1' diff --git a/docs/conf.py b/docs/conf.py index 41d1049b8..7b5584a1a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ # The short X.Y version. version = '2.8' # The full version, including alpha/beta/rc tags. -release = '2.8.0' +release = '2.8.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From cef9189ce68c35f5790bf9ebb3ca13719bded696 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 14:42:18 +0200 Subject: [PATCH 38/50] Distill changelog --- AUTHORS | 9 ++++++++- CHANGES | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 31a5cd1be..9cf8f4e7d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,12 +25,13 @@ Babel is written and maintained by the Babel team and various contributors: - Sachin Paliwal - Alex Willmer - Daniel Neuhäuser +- Miro Hrončok - Cédric Krier - Luke Plant - Jennifer Wang - Lukas Balaga - sudheesh001 -- Miro Hrončok +- Niklas Hambüchen - Changaco - Xavier Fernandez - KO. Mattsson @@ -45,6 +46,12 @@ Babel is written and maintained by the Babel team and various contributors: - Leonardo Pistone - Jun Omae - Hyunjun Kim +- Alessio Bogon +- Nikiforov Konstantin +- Abdullah Javed Nesar +- Brad Martin +- Tyler Kennedy +- CyanNani123 - sebleblanc - He Chen - Steve (Gadget) Barnes diff --git a/CHANGES b/CHANGES index c694ec8c7..fbc73801f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,41 @@ Babel Changelog =============== +Version 2.9.0 +------------- + +Upcoming version support changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* This version, Babel 2.9, is the last version of Babel to support Python 2.7, Python 3.4, and Python 3.5. + +Improvements +~~~~~~~~~~~~ + +* CLDR: Use CLDR 37 – Aarni Koskela (#734) +* Dates: Handle ZoneInfo objects in get_timezone_location, get_timezone_name - Alessio Bogon (#741) +* Numbers: Add group_separator feature in number formatting - Abdullah Javed Nesar (#726) + +Bugfixes +~~~~~~~~ + +* Dates: Correct default Format().timedelta format to 'long' to mute deprecation warnings – Aarni Koskela +* Import: Simplify iteration code in "import_cldr.py" – Felix Schwarz +* Import: Stop using deprecated ElementTree methods "getchildren()" and "getiterator()" – Felix Schwarz +* Messages: Fix unicode printing error on Python 2 without TTY. – Niklas Hambüchen +* Messages: Introduce invariant that _invalid_pofile() takes unicode line. – Niklas Hambüchen +* Tests: fix tests when using Python 3.9 – Felix Schwarz +* Tests: Remove deprecated 'sudo: false' from Travis configuration – Jon Dufresne +* Tests: Support Py.test 6.x – Aarni Koskela +* Utilities: LazyProxy: Handle AttributeError in specified func – Nikiforov Konstantin (#724) +* Utilities: Replace usage of parser.suite with ast.parse – Miro Hrončok + +Documentation +~~~~~~~~~~~~~ + +* Update parse_number comments – Brad Martin (#708) +* Add __iter__ to Catalog documentation – @CyanNani123 + Version 2.8.1 ------------- From 04090802f404ceca98844279750a1db0b0a4636d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 15:14:52 +0200 Subject: [PATCH 39/50] Fix deprecated .getchildren() call Augments 167b71421f113e2210e4deefef5020402492e5be --- scripts/import_cldr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 2977c6e40..8e5eebb95 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -855,7 +855,7 @@ def parse_unit_patterns(data, tree): unit_type = unit.attrib['type'] compound_unit_info = {} compound_variations = {} - for child in unit.getchildren(): + for child in unit: if child.tag == "unitPrefixPattern": compound_unit_info['prefix'] = _text(child) elif child.tag == "compoundUnitPattern": From 49e20930995a9feab35c918abe0cae2554e7e408 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 14:42:51 +0200 Subject: [PATCH 40/50] Travis: Test on Python 3.9 --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7f7331be5..1bebe7f87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,10 @@ matrix: python: 3.8 env: - PYTEST_VERSION=6.1.2 - + - os: linux + python: 3.9 + env: + - PYTEST_VERSION=6.1.2 install: - bash .ci/deps.${TRAVIS_OS_NAME}.sh - pip install --upgrade pip From 9f6ea69f49fad04ece0611b856e0debfac2ae805 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 14:43:28 +0200 Subject: [PATCH 41/50] Bump version to 2.9.0 --- babel/__init__.py | 2 +- docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/babel/__init__.py b/babel/__init__.py index cbcb4fb2f..ecb18b546 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -21,4 +21,4 @@ negotiate_locale, parse_locale, get_locale_identifier -__version__ = '2.8.1' +__version__ = '2.9.0' diff --git a/docs/conf.py b/docs/conf.py index 7b5584a1a..1c21c67cc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '2.8' +version = '2.9' # The full version, including alpha/beta/rc tags. -release = '2.8.1' +release = '2.9.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 9a9d3c60ec05b346e87f2241b7ce976ed1a6bf0b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 14:11:38 +0200 Subject: [PATCH 42/50] Use Freezegun in test_format_current_moment The earlier patch resulted in `unsupported operand type(s) for -: 'datetime' and 'datetime'` on Pypy3. --- tests/test_dates.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test_dates.py b/tests/test_dates.py index 48ed05c49..8e693d34c 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -15,6 +15,7 @@ from datetime import date, datetime, time, timedelta import unittest +import freezegun import pytest import pytz from pytz import timezone @@ -809,19 +810,10 @@ def test_zh_TW_format(): assert dates.format_time(datetime(2016, 4, 8, 12, 34, 56), locale='zh_TW') == u'\u4e0b\u534812:34:56' -def test_format_current_moment(monkeypatch): - import datetime as datetime_module +def test_format_current_moment(): frozen_instant = datetime.utcnow() - - class frozen_datetime(datetime): - - @classmethod - def utcnow(cls): - return frozen_instant - - # Freeze time! Well, some of it anyway. - monkeypatch.setattr(datetime_module, "datetime", frozen_datetime) - assert dates.format_datetime(locale="en_US") == dates.format_datetime(frozen_instant, locale="en_US") + with freezegun.freeze_time(time_to_freeze=frozen_instant): + assert dates.format_datetime(locale="en_US") == dates.format_datetime(frozen_instant, locale="en_US") @pytest.mark.all_locales From 613dc1700f91c3d40b081948c0dd6023d8ece057 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 10 Nov 2020 14:54:31 +0200 Subject: [PATCH 43/50] Make the import warnings about unsupported number systems less verbose --- scripts/import_cldr.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 8e5eebb95..32e6abcb4 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -392,6 +392,7 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): ])) data['locale_id'] = locale_id + data['unsupported_number_systems'] = set() if locale_id in plural_rules: data['plural_form'] = plural_rules[locale_id] @@ -432,6 +433,13 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): parse_character_order(data, tree) parse_measurement_systems(data, tree) + unsupported_number_systems_string = ', '.join(sorted(data.pop('unsupported_number_systems'))) + if unsupported_number_systems_string: + log('%s: unsupported number systems were ignored: %s' % ( + locale_id, + unsupported_number_systems_string, + )) + write_datafile(data_filename, data, dump_json=dump_json) @@ -440,21 +448,14 @@ def _should_skip_number_elem(data, elem): Figure out whether the numbering-containing element `elem` is in a currently non-supported (i.e. currently non-Latin) numbering system. - If it is, a warning is raised. - - :param data: The root data element, for formatting the warning. + :param data: The root data element, for stashing the warning. :param elem: Element with `numberSystem` key :return: Boolean """ number_system = elem.get('numberSystem', 'latn') if number_system != 'latn': - log('%s: Unsupported number system "%s" in <%s numberSystem="%s">' % ( - data['locale_id'], - number_system, - elem.tag, - number_system, - )) + data['unsupported_number_systems'].add(number_system) return True return False From 156b7fb9f377ccf58c71cf01dc69fb10c7b69314 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 15:24:16 +0200 Subject: [PATCH 44/50] Quiesce CLDR download progress bar if requested (or not a TTY) --- scripts/download_import_cldr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py index 531a04c62..805772a16 100755 --- a/scripts/download_import_cldr.py +++ b/scripts/download_import_cldr.py @@ -75,12 +75,13 @@ def main(): cldr_path = os.path.join(repo, 'cldr', os.path.splitext(FILENAME)[0]) zip_path = os.path.join(cldr_dl_path, FILENAME) changed = False + show_progress = (False if os.environ.get("BABEL_CLDR_NO_DOWNLOAD_PROGRESS") else sys.stdout.isatty()) while not is_good_file(zip_path): log('Downloading \'%s\'', FILENAME) if os.path.isfile(zip_path): os.remove(zip_path) - urlretrieve(URL, zip_path, reporthook) + urlretrieve(URL, zip_path, (reporthook if show_progress else None)) changed = True print() common_path = os.path.join(cldr_path, 'common') From d1bbc08e845d03d8e1f0dfa0e04983d755f39cb5 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 11 Nov 2020 15:59:41 +0200 Subject: [PATCH 45/50] import_cldr: use logging; add -q option --- scripts/import_cldr.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 32e6abcb4..0dbd39369 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -17,6 +17,7 @@ import os import re import sys +import logging try: from xml.etree import cElementTree as ElementTree @@ -62,16 +63,7 @@ def _text(elem): 'timeFormats': 'time_formats' } - -def log(message, *args): - if args: - message = message % args - sys.stderr.write(message + '\r\n') - sys.stderr.flush() - - -def error(message, *args): - log('ERROR: %s' % message, *args) +log = logging.getLogger("import_cldr") def need_conversion(dst_filename, data_dict, source_filename): @@ -182,10 +174,19 @@ def main(): '-j', '--json', dest='dump_json', action='store_true', default=False, help='also export debugging JSON dumps of locale data' ) + parser.add_option( + '-q', '--quiet', dest='quiet', action='store_true', default=bool(os.environ.get('BABEL_CLDR_QUIET')), + help='quiesce info/warning messages', + ) options, args = parser.parse_args() if len(args) != 1: parser.error('incorrect number of arguments') + + logging.basicConfig( + level=(logging.ERROR if options.quiet else logging.INFO), + ) + return process_data( srcdir=args[0], destdir=BABEL_PACKAGE_ROOT, @@ -383,8 +384,10 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): territory = '001' # world regions = territory_containment.get(territory, []) - log('Processing %s (Language = %s; Territory = %s)', - filename, language, territory) + log.info( + 'Processing %s (Language = %s; Territory = %s)', + filename, language, territory, + ) locale_id = '_'.join(filter(None, [ language, @@ -435,7 +438,7 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): unsupported_number_systems_string = ', '.join(sorted(data.pop('unsupported_number_systems'))) if unsupported_number_systems_string: - log('%s: unsupported number systems were ignored: %s' % ( + log.warning('%s: unsupported number systems were ignored: %s' % ( locale_id, unsupported_number_systems_string, )) @@ -687,7 +690,7 @@ def parse_calendar_date_formats(data, calendar): text_type(elem.findtext('dateFormat/pattern')) ) except ValueError as e: - error(e) + log.error(e) elif elem.tag == 'alias': date_formats = Alias(_translate_alias( ['date_formats'], elem.attrib['path']) @@ -707,7 +710,7 @@ def parse_calendar_time_formats(data, calendar): text_type(elem.findtext('timeFormat/pattern')) ) except ValueError as e: - error(e) + log.error(e) elif elem.tag == 'alias': time_formats = Alias(_translate_alias( ['time_formats'], elem.attrib['path']) @@ -726,7 +729,7 @@ def parse_calendar_datetime_skeletons(data, calendar): try: datetime_formats[type] = text_type(elem.findtext('dateTimeFormat/pattern')) except ValueError as e: - error(e) + log.error(e) elif elem.tag == 'alias': datetime_formats = Alias(_translate_alias( ['datetime_formats'], elem.attrib['path']) From 58de8342f865df88697a4a166191e880e3c84d82 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 10 Nov 2020 16:19:04 +0200 Subject: [PATCH 46/50] Replace Travis + Appveyor with GitHub Actions (WIP) --- .ci/appveyor.yml | 38 -------------------- .ci/deploy.linux.sh | 4 --- .ci/deploy.osx.sh | 4 --- .ci/deps.linux.sh | 4 --- .ci/deps.osx.sh | 11 ------ .ci/run_with_env.cmd | 47 ------------------------ .github/workflows/test.yml | 38 ++++++++++++++++++++ .travis.yml | 74 -------------------------------------- setup.py | 8 ++--- tox.ini | 29 ++++++++------- 10 files changed, 58 insertions(+), 199 deletions(-) delete mode 100644 .ci/appveyor.yml delete mode 100644 .ci/deploy.linux.sh delete mode 100644 .ci/deploy.osx.sh delete mode 100644 .ci/deps.linux.sh delete mode 100644 .ci/deps.osx.sh delete mode 100644 .ci/run_with_env.cmd create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.ci/appveyor.yml b/.ci/appveyor.yml deleted file mode 100644 index 91758f415..000000000 --- a/.ci/appveyor.yml +++ /dev/null @@ -1,38 +0,0 @@ -# From https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor.yml - -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script intepreter - # See: https://stackoverflow.com/a/13751649 - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\.ci\\run_with_env.cmd" - - matrix: - - PYTHON: "C:\\Python35" - PYTHON_VERSION: "3.5.x" - PYTHON_ARCH: "32" - - - PYTHON: "C:\\Python35-x64" - PYTHON_VERSION: "3.5.x" - PYTHON_ARCH: "64" - -branches: # Only build official branches, PRs are built anyway. - only: - - master - - /release.*/ - -install: - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - # Check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - # Build data files - - "pip install --upgrade pytest==4.3.1 pytest-cov==2.6.1 codecov freezegun==0.3.12" - - "pip install --editable ." - - "python setup.py import_cldr" - -build: false # Not a C# project, build stuff at the test step instead. - -test_script: - - "%CMD_IN_ENV% python -m pytest --cov=babel" - - "codecov" diff --git a/.ci/deploy.linux.sh b/.ci/deploy.linux.sh deleted file mode 100644 index 4d59382d7..000000000 --- a/.ci/deploy.linux.sh +++ /dev/null @@ -1,4 +0,0 @@ -set -x -set -e - -bash <(curl -s https://codecov.io/bash) diff --git a/.ci/deploy.osx.sh b/.ci/deploy.osx.sh deleted file mode 100644 index c44550eff..000000000 --- a/.ci/deploy.osx.sh +++ /dev/null @@ -1,4 +0,0 @@ -set -x -set -e - -echo "Due to a bug in codecov, coverage cannot be deployed for Mac builds." diff --git a/.ci/deps.linux.sh b/.ci/deps.linux.sh deleted file mode 100644 index 13cc9e1ef..000000000 --- a/.ci/deps.linux.sh +++ /dev/null @@ -1,4 +0,0 @@ -set -x -set -e - -echo "No dependencies to install for linux." diff --git a/.ci/deps.osx.sh b/.ci/deps.osx.sh deleted file mode 100644 index b52a84f6d..000000000 --- a/.ci/deps.osx.sh +++ /dev/null @@ -1,11 +0,0 @@ -set -e -set -x - -# Install packages with brew -brew update >/dev/null -brew outdated pyenv || brew upgrade --quiet pyenv - -# Install required python version for this build -pyenv install -ks $PYTHON_VERSION -pyenv global $PYTHON_VERSION -python --version diff --git a/.ci/run_with_env.cmd b/.ci/run_with_env.cmd deleted file mode 100644 index 0f5b8e097..000000000 --- a/.ci/run_with_env.cmd +++ /dev/null @@ -1,47 +0,0 @@ -:: To build extensions for 64 bit Python 3, we need to configure environment -:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) -:: -:: To build extensions for 64 bit Python 2, we need to configure environment -:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) -:: -:: 32 bit builds do not require specific environment configurations. -:: -:: Note: this script needs to be run with the /E:ON and /V:ON flags for the -:: cmd interpreter, at least for (SDK v7.0) -:: -:: More details at: -:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows -:: https://stackoverflow.com/a/13751649 -:: -:: Author: Olivier Grisel -:: License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/ -@ECHO OFF - -SET COMMAND_TO_RUN=%* -SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows - -SET MAJOR_PYTHON_VERSION="%PYTHON_VERSION:~0,1%" -IF %MAJOR_PYTHON_VERSION% == "2" ( - SET WINDOWS_SDK_VERSION="v7.0" -) ELSE IF %MAJOR_PYTHON_VERSION% == "3" ( - SET WINDOWS_SDK_VERSION="v7.1" -) ELSE ( - ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" - EXIT 1 -) - -IF "%PYTHON_ARCH%"=="64" ( - ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture - SET DISTUTILS_USE_SDK=1 - SET MSSdk=1 - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 -) ELSE ( - ECHO Using default MSVC build environment for 32 bit architecture - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 -) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..e9c411862 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: Test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04, windows-2019, macos-10.15] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + exclude: + - os: windows-2019 + python-version: pypy3 + # TODO: Remove this; see: + # https://github.com/actions/setup-python/issues/151 + # https://github.com/tox-dev/tox/issues/1704 + # https://foss.heptapod.net/pypy/pypy/-/issues/3331 + env: + BABEL_CLDR_NO_DOWNLOAD_PROGRESS: "1" + BABEL_CLDR_QUIET: "1" + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + python -m pip install tox tox-gh-actions==2.1.0 + - name: Run test via Tox + run: tox --skip-missing-interpreters + - uses: codecov/codecov-action@v1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1bebe7f87..000000000 --- a/.travis.yml +++ /dev/null @@ -1,74 +0,0 @@ -dist: xenial -language: python - -cache: - directories: - - cldr - - "$HOME/.cache/pip" - - "$HOME/.pyenv" - -matrix: - include: - - os: linux - python: 2.7 - env: - - PYTEST_VERSION=4.3.1 - - os: linux - python: 2.7 - env: - - CDECIMAL=m3-cdecimal - - PYTEST_VERSION=4.3.1 - - os: linux - dist: trusty - python: pypy - env: - - PYTEST_VERSION=4.3.1 - - os: linux - dist: trusty - python: pypy3 - env: - - PYTEST_VERSION=6.1.2 - - os: linux - python: 3.4 - env: - - PYTEST_VERSION=4.3.1 - - os: linux - python: 3.5 - env: - - PYTHON_TEST_FLAGS=-bb - - PYTEST_VERSION=6.1.2 - - os: linux - python: 3.6 - env: - - PYTEST_VERSION=6.1.2 - - os: linux - python: 3.7 - env: - - PYTEST_VERSION=6.1.2 - - os: linux - python: 3.8 - env: - - PYTEST_VERSION=6.1.2 - - os: linux - python: 3.9 - env: - - PYTEST_VERSION=6.1.2 -install: - - bash .ci/deps.${TRAVIS_OS_NAME}.sh - - pip install --upgrade pip - - pip install --upgrade $CDECIMAL pytest==$PYTEST_VERSION pytest-cov freezegun==0.3.12 'backports.zoneinfo;python_version>="3.6" and python_version<"3.9"' - - pip install --editable . - -script: - - make test-cov - - bash .ci/deploy.${TRAVIS_OS_NAME}.sh - -notifications: - email: false - irc: - channels: - - "chat.freenode.net#pocoo" - on_success: change - on_failure: always - use_notice: true - skip_join: true diff --git a/setup.py b/setup.py index 0032a3a05..adf3bb5d9 100755 --- a/setup.py +++ b/setup.py @@ -44,18 +44,16 @@ def run(self): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - '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', 'Topic :: Software Development :: Libraries :: Python Modules', ], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.6', packages=['babel', 'babel.messages', 'babel.localtime'], include_package_data=True, install_requires=[ diff --git a/tox.ini b/tox.ini index 27faf5bbb..14b450ff8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,25 @@ [tox] -envlist = py27, pypy, py34, py35, py36, py37, pypy3, py27-cdecimal +envlist = + py{36,37,38,39} + pypy3 [testenv] deps = - pytest==4.3.1;python_version<"3.5" - pytest==6.1.2;python_version>="3.5" + pytest pytest-cov - cdecimal: m3-cdecimal freezegun==0.3.12 - backports.zoneinfo;python_version>"3.6" and python_version<"3.9" + backports.zoneinfo;python_version<"3.9" + tzdata;sys_platform == 'win32' whitelist_externals = make -commands = make clean-cldr test -passenv = PYTHON_TEST_FLAGS +commands = make clean-cldr test-cov +passenv = + BABEL_* + PYTHON_* -[pep8] -ignore = E501,E731,W503 - -[flake8] -ignore = E501,E731,W503 +[gh-actions] +python = + pypy3: pypy3 + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 From 3a700b5b8b53606fd98ef8294a56f9510f7290f8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 28 Apr 2021 10:33:40 +0300 Subject: [PATCH 47/50] Run locale identifiers through `os.path.basename()` --- babel/localedata.py | 2 ++ tests/test_localedata.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/babel/localedata.py b/babel/localedata.py index f4771d1fd..11085490a 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -47,6 +47,7 @@ def exists(name): """ if not name or not isinstance(name, string_types): return False + name = os.path.basename(name) if name in _cache: return True file_found = os.path.exists(os.path.join(_dirname, '%s.dat' % name)) @@ -102,6 +103,7 @@ def load(name, merge_inherited=True): :raise `IOError`: if no locale data file is found for the given locale identifer, or one of the locales it inherits from """ + name = os.path.basename(name) _cache_lock.acquire() try: data = _cache.get(name) diff --git a/tests/test_localedata.py b/tests/test_localedata.py index 83cd66994..9cb4282e4 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -11,11 +11,17 @@ # individuals. For the exact contribution history, see the revision # history and logs, available at http://babel.edgewall.org/log/. +import os +import pickle +import sys +import tempfile import unittest import random from operator import methodcaller -from babel import localedata +import pytest + +from babel import localedata, Locale, UnknownLocaleError class MergeResolveTestCase(unittest.TestCase): @@ -131,3 +137,25 @@ def listdir_spy(*args): localedata.locale_identifiers.cache = None assert localedata.locale_identifiers() assert len(listdir_calls) == 2 + + +def test_locale_name_cleanup(): + """ + Test that locale identifiers are cleaned up to avoid directory traversal. + """ + no_exist_name = os.path.join(tempfile.gettempdir(), "babel%d.dat" % random.randint(1, 99999)) + with open(no_exist_name, "wb") as f: + pickle.dump({}, f) + + try: + name = os.path.splitext(os.path.relpath(no_exist_name, localedata._dirname))[0] + except ValueError: + if sys.platform == "win32": + pytest.skip("unable to form relpath") + raise + + assert not localedata.exists(name) + with pytest.raises(IOError): + localedata.load(name) + with pytest.raises(UnknownLocaleError): + Locale(name) From 5caf717ceca4bd235552362b4fbff88983c75d8c Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 28 Apr 2021 11:47:42 +0300 Subject: [PATCH 48/50] Disallow special filenames on Windows --- babel/localedata.py | 24 +++++++++++++++++++++--- tests/test_localedata.py | 9 +++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/babel/localedata.py b/babel/localedata.py index 11085490a..782b7afa0 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -13,6 +13,8 @@ """ import os +import re +import sys import threading from itertools import chain @@ -22,6 +24,7 @@ _cache = {} _cache_lock = threading.RLock() _dirname = os.path.join(os.path.dirname(__file__), 'locale-data') +_windows_reserved_name_re = re.compile("^(con|prn|aux|nul|com[0-9]|lpt[0-9])$", re.I) def normalize_locale(name): @@ -38,6 +41,22 @@ def normalize_locale(name): return locale_id +def resolve_locale_filename(name): + """ + Resolve a locale identifier to a `.dat` path on disk. + """ + + # Clean up any possible relative paths. + name = os.path.basename(name) + + # Ensure we're not left with one of the Windows reserved names. + if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]): + raise ValueError("Name %s is invalid on Windows" % name) + + # Build the path. + return os.path.join(_dirname, '%s.dat' % name) + + def exists(name): """Check whether locale data is available for the given locale. @@ -47,10 +66,9 @@ def exists(name): """ if not name or not isinstance(name, string_types): return False - name = os.path.basename(name) if name in _cache: return True - file_found = os.path.exists(os.path.join(_dirname, '%s.dat' % name)) + file_found = os.path.exists(resolve_locale_filename(name)) return True if file_found else bool(normalize_locale(name)) @@ -121,7 +139,7 @@ def load(name, merge_inherited=True): else: parent = '_'.join(parts[:-1]) data = load(parent).copy() - filename = os.path.join(_dirname, '%s.dat' % name) + filename = resolve_locale_filename(name) with open(filename, 'rb') as fileobj: if name != 'root' and merge_inherited: merge(data, pickle.load(fileobj)) diff --git a/tests/test_localedata.py b/tests/test_localedata.py index 9cb4282e4..c852c1b69 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -159,3 +159,12 @@ def test_locale_name_cleanup(): localedata.load(name) with pytest.raises(UnknownLocaleError): Locale(name) + + +@pytest.mark.skipif(sys.platform != "win32", reason="windows-only test") +def test_reserved_locale_names(): + for name in ("con", "aux", "nul", "prn", "com8", "lpt5"): + with pytest.raises(ValueError): + localedata.load(name) + with pytest.raises(ValueError): + Locale(name) From 60b33e083801109277cb068105251e76d0b7c14e Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 28 Apr 2021 22:04:48 +0300 Subject: [PATCH 49/50] Become 2.9.1 * Update copyright year * Update changelog --- CHANGES | 9 +++++++++ LICENSE | 2 +- babel/__init__.py | 4 ++-- babel/core.py | 2 +- babel/dates.py | 2 +- babel/lists.py | 2 +- babel/localedata.py | 2 +- babel/localtime/__init__.py | 2 +- babel/messages/__init__.py | 2 +- babel/messages/catalog.py | 2 +- babel/messages/checkers.py | 2 +- babel/messages/extract.py | 2 +- babel/messages/frontend.py | 2 +- babel/messages/jslexer.py | 2 +- babel/messages/mofile.py | 2 +- babel/messages/plurals.py | 2 +- babel/messages/pofile.py | 2 +- babel/numbers.py | 2 +- babel/plural.py | 2 +- babel/support.py | 2 +- babel/util.py | 2 +- docs/conf.py | 4 ++-- scripts/dump_data.py | 2 +- scripts/dump_global.py | 2 +- scripts/import_cldr.py | 2 +- tests/messages/test_catalog.py | 2 +- tests/messages/test_checkers.py | 2 +- tests/messages/test_extract.py | 2 +- tests/messages/test_frontend.py | 2 +- tests/messages/test_mofile.py | 2 +- tests/messages/test_plurals.py | 2 +- tests/messages/test_pofile.py | 2 +- tests/test_core.py | 2 +- tests/test_dates.py | 2 +- tests/test_localedata.py | 2 +- tests/test_numbers.py | 2 +- tests/test_plural.py | 2 +- tests/test_support.py | 2 +- tests/test_util.py | 2 +- 39 files changed, 49 insertions(+), 40 deletions(-) diff --git a/CHANGES b/CHANGES index fbc73801f..e3c54bfc8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,15 @@ Babel Changelog =============== +Version 2.9.1 +------------- + +Bugfixes +~~~~~~~~ + +* The internal locale-data loading functions now validate the name of the locale file to be loaded and only + allow files within Babel's data directory. Thank you to Chris Lyne of Tenable, Inc. for discovering the issue! + Version 2.9.0 ------------- diff --git a/LICENSE b/LICENSE index 7e2f12e46..693e1a187 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2020 by the Babel Team, see AUTHORS for more information. +Copyright (c) 2013-2021 by the Babel Team, see AUTHORS for more information. All rights reserved. diff --git a/babel/__init__.py b/babel/__init__.py index ecb18b546..3e20e4bd1 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -13,7 +13,7 @@ access to various locale display names, localized number and date formatting, etc. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ @@ -21,4 +21,4 @@ negotiate_locale, parse_locale, get_locale_identifier -__version__ = '2.9.0' +__version__ = '2.9.1' diff --git a/babel/core.py b/babel/core.py index a0c35b4ea..a323a7295 100644 --- a/babel/core.py +++ b/babel/core.py @@ -5,7 +5,7 @@ Core locale representation and locale data access. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/dates.py b/babel/dates.py index d62d00ba4..75e8f3501 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -12,7 +12,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/lists.py b/babel/lists.py index e0bd7ed7f..8368b27a6 100644 --- a/babel/lists.py +++ b/babel/lists.py @@ -11,7 +11,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2015-2020 by the Babel Team. + :copyright: (c) 2015-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localedata.py b/babel/localedata.py index 782b7afa0..438afb643 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -8,7 +8,7 @@ :note: The `Locale` class, which uses this module under the hood, provides a more convenient interface for accessing the locale data. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py index 8c9203a7b..bd3954951 100644 --- a/babel/localtime/__init__.py +++ b/babel/localtime/__init__.py @@ -6,7 +6,7 @@ Babel specific fork of tzlocal to determine the local timezone of the system. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/__init__.py b/babel/messages/__init__.py index 77c79621b..7d2587f63 100644 --- a/babel/messages/__init__.py +++ b/babel/messages/__init__.py @@ -5,7 +5,7 @@ Support for ``gettext`` message catalogs. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index af1b6573c..a19a3e6d8 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -5,7 +5,7 @@ Data structures for message catalogs. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py index 29add9fce..cba911d72 100644 --- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -7,7 +7,7 @@ :since: version 0.9 - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/extract.py b/babel/messages/extract.py index 8f53fa298..64497762c 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -13,7 +13,7 @@ The main entry points into the extraction functionality are the functions `extract_from_dir` and `extract_from_file`. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index 0b65a7c6b..c5eb1dea9 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -5,7 +5,7 @@ Frontends for the message extraction functionality. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import print_function diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index f56c80d98..c57b1213f 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -6,7 +6,7 @@ A simple JavaScript 1.5 lexer which is used for the JavaScript extractor. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ from collections import namedtuple diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py index 1ab3e4e9c..8d3cfc905 100644 --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -5,7 +5,7 @@ Writing of files in the ``gettext`` MO (machine object) format. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py index 4755cfcf9..91ba9e1b1 100644 --- a/babel/messages/plurals.py +++ b/babel/messages/plurals.py @@ -5,7 +5,7 @@ Plural form definitions. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index b8cb46976..be33b831d 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -6,7 +6,7 @@ Reading and writing of files in the ``gettext`` PO (portable object) format. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/numbers.py b/babel/numbers.py index 0303f0542..0fcc07e15 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -12,7 +12,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ # TODO: diff --git a/babel/plural.py b/babel/plural.py index 3c9a3e614..e705e9b8d 100644 --- a/babel/plural.py +++ b/babel/plural.py @@ -5,7 +5,7 @@ CLDR Plural support. See UTS #35. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ import re diff --git a/babel/support.py b/babel/support.py index d6fc56cfb..4be9ed37f 100644 --- a/babel/support.py +++ b/babel/support.py @@ -8,7 +8,7 @@ .. note: the code in this module is not used by Babel itself - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/util.py b/babel/util.py index a5d40c5de..a8fbac1d9 100644 --- a/babel/util.py +++ b/babel/util.py @@ -5,7 +5,7 @@ Various utility classes and functions. - :copyright: (c) 2013-2020 by the Babel Team. + :copyright: (c) 2013-2021 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/docs/conf.py b/docs/conf.py index 1c21c67cc..962792fbd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,7 @@ # General information about the project. project = u'Babel' -copyright = u'2020, The Babel Team' +copyright = u'2021, The Babel Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -53,7 +53,7 @@ # The short X.Y version. version = '2.9' # The full version, including alpha/beta/rc tags. -release = '2.9.0' +release = '2.9.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/scripts/dump_data.py b/scripts/dump_data.py index 57439b98f..ac295b2d7 100755 --- a/scripts/dump_data.py +++ b/scripts/dump_data.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/dump_global.py b/scripts/dump_global.py index a27b31617..c9e1d3008 100755 --- a/scripts/dump_global.py +++ b/scripts/dump_global.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 0dbd39369..7876d5208 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index 2d9e31d00..661999648 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_checkers.py b/tests/messages/test_checkers.py index bf813bc05..49abb51b0 100644 --- a/tests/messages/test_checkers.py +++ b/tests/messages/test_checkers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py index 38979cd30..ac7f0a642 100644 --- a/tests/messages/test_extract.py +++ b/tests/messages/test_extract.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_frontend.py b/tests/messages/test_frontend.py index 38165b354..70580215e 100644 --- a/tests/messages/test_frontend.py +++ b/tests/messages/test_frontend.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_mofile.py b/tests/messages/test_mofile.py index 395b9fb1a..fb672a80c 100644 --- a/tests/messages/test_mofile.py +++ b/tests/messages/test_mofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_plurals.py b/tests/messages/test_plurals.py index 62c770870..5e490f374 100644 --- a/tests/messages/test_plurals.py +++ b/tests/messages/test_plurals.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index 2db7f6715..be1172a88 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_core.py b/tests/test_core.py index 53b9d1875..558322e00 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_dates.py b/tests/test_dates.py index 8e693d34c..44efa7fbc 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_localedata.py b/tests/test_localedata.py index c852c1b69..735678f80 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 739fd1fb7..11e61d37d 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_plural.py b/tests/test_plural.py index 1a9fbe507..bea8115ce 100644 --- a/tests/test_plural.py +++ b/tests/test_plural.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_support.py b/tests/test_support.py index 456425e3e..a683591dc 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_util.py b/tests/test_util.py index dbd6378a0..b29278e00 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2020 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which From a99fa2474c808b51ebdabea18db871e389751559 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 28 Apr 2021 22:26:16 +0300 Subject: [PATCH 50/50] Use 2.9.0's setup.py for 2.9.1 --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index adf3bb5d9..0032a3a05 100755 --- a/setup.py +++ b/setup.py @@ -44,16 +44,18 @@ def run(self): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + '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', 'Topic :: Software Development :: Libraries :: Python Modules', ], - python_requires='>=3.6', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', packages=['babel', 'babel.messages', 'babel.localtime'], include_package_data=True, install_requires=[