diff options
Diffstat (limited to 'babel')
-rw-r--r-- | babel/core.py | 11 | ||||
-rw-r--r-- | babel/dates.py | 32 | ||||
-rw-r--r-- | babel/localtime/__init__.py | 2 | ||||
-rw-r--r-- | babel/localtime/_helpers.py | 1 | ||||
-rw-r--r-- | babel/localtime/_unix.py | 6 | ||||
-rw-r--r-- | babel/localtime/_win32.py | 3 | ||||
-rw-r--r-- | babel/messages/catalog.py | 27 | ||||
-rw-r--r-- | babel/messages/checkers.py | 5 | ||||
-rw-r--r-- | babel/messages/extract.py | 25 | ||||
-rw-r--r-- | babel/messages/frontend.py | 70 | ||||
-rw-r--r-- | babel/messages/jslexer.py | 12 | ||||
-rw-r--r-- | babel/messages/mofile.py | 2 | ||||
-rw-r--r-- | babel/messages/plurals.py | 2 | ||||
-rw-r--r-- | babel/messages/pofile.py | 34 | ||||
-rw-r--r-- | babel/numbers.py | 50 | ||||
-rw-r--r-- | babel/plural.py | 8 | ||||
-rw-r--r-- | babel/support.py | 23 | ||||
-rw-r--r-- | babel/units.py | 1 | ||||
-rw-r--r-- | babel/util.py | 8 |
19 files changed, 176 insertions, 146 deletions
diff --git a/babel/core.py b/babel/core.py index 5041c1b..ce564d7 100644 --- a/babel/core.py +++ b/babel/core.py @@ -46,6 +46,7 @@ if TYPE_CHECKING: _global_data = None _default_plural_rule = PluralRule({}) + def _raise_no_data_error(): raise RuntimeError('The babel data files are not available. ' 'This usually happens because you are using ' @@ -383,10 +384,12 @@ class Locale: for key in ('language', 'territory', 'script', 'variant'): if not hasattr(other, key): return False - return (self.language == getattr(other, 'language')) and \ - (self.territory == getattr(other, 'territory')) and \ - (self.script == getattr(other, 'script')) and \ - (self.variant == getattr(other, 'variant')) + return ( + self.language == getattr(other, 'language') and # noqa: B009 + self.territory == getattr(other, 'territory') and # noqa: B009 + self.script == getattr(other, 'script') and # noqa: B009 + self.variant == getattr(other, 'variant') # noqa: B009 + ) def __ne__(self, other: object) -> bool: return not self.__eq__(other) diff --git a/babel/dates.py b/babel/dates.py index 51bc7ff..e626df5 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -27,9 +27,9 @@ except ModuleNotFoundError: pytz = None import zoneinfo +import datetime from bisect import bisect_right from collections.abc import Iterable -import datetime from babel import localtime from babel.core import Locale, default_locale, get_global @@ -49,7 +49,7 @@ if TYPE_CHECKING: # empty set characters ( U+2205 )." # - https://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names -NO_INHERITANCE_MARKER = u'\u2205\u2205\u2205' +NO_INHERITANCE_MARKER = '\u2205\u2205\u2205' if pytz: @@ -247,13 +247,13 @@ def get_timezone(zone: str | datetime.tzinfo | None = None) -> datetime.tzinfo: if pytz: try: return pytz.timezone(zone) - except pytz.UnknownTimeZoneError as exc: + except pytz.UnknownTimeZoneError as exc: # noqa: F841 pass else: assert zoneinfo try: return zoneinfo.ZoneInfo(zone) - except zoneinfo.ZoneInfoNotFoundError as exc: + except zoneinfo.ZoneInfoNotFoundError as exc: # noqa: F841 pass raise LookupError(f"Unknown timezone {zone}") from exc @@ -558,11 +558,11 @@ def get_timezone_gmt( if return_z and hours == 0 and seconds == 0: return 'Z' elif seconds == 0 and width == 'iso8601_short': - return u'%+03d' % hours + return '%+03d' % hours elif width == 'short' or width == 'iso8601_short': - pattern = u'%+03d%02d' + pattern = '%+03d%02d' elif width == 'iso8601': - pattern = u'%+03d:%02d' + pattern = '%+03d:%02d' else: pattern = locale.zone_formats['gmt'] % '%+03d:%02d' return pattern % (hours, seconds // 60) @@ -1083,10 +1083,10 @@ def format_timedelta( break # This really should not happen if pattern is None: - return u'' + return '' return pattern.replace('{0}', str(value)) - return u'' + return '' def _format_fallback_interval( @@ -1349,8 +1349,7 @@ def parse_date( month_idx = format_str.index('l') day_idx = format_str.index('d') - indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')] - indexes.sort() + indexes = sorted([(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')]) indexes = {item[1]: idx for idx, item in enumerate(indexes)} # FIXME: this currently only supports numbers, but should also support month @@ -1399,8 +1398,7 @@ def parse_time( min_idx = format_str.index('m') sec_idx = format_str.index('s') - indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')] - indexes.sort() + indexes = sorted([(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')]) indexes = {item[1]: idx for idx, item in enumerate(indexes)} # TODO: support time zones @@ -1436,7 +1434,7 @@ class DateTimePattern: return pat def __mod__(self, other: DateTimeFormat) -> str: - if type(other) is not DateTimeFormat: + if not isinstance(other, DateTimeFormat): return NotImplemented return self.format % other @@ -1829,7 +1827,7 @@ def parse_pattern(pattern: str) -> DateTimePattern: :param pattern: the formatting pattern to parse """ - if type(pattern) is DateTimePattern: + if isinstance(pattern, DateTimePattern): return pattern if pattern in _pattern_cache: @@ -1849,7 +1847,7 @@ def parse_pattern(pattern: str) -> DateTimePattern: else: raise NotImplementedError(f"Unknown token type: {tok_type}") - _pattern_cache[pattern] = pat = DateTimePattern(pattern, u''.join(result)) + _pattern_cache[pattern] = pat = DateTimePattern(pattern, ''.join(result)) return pat @@ -1884,7 +1882,7 @@ def tokenize_pattern(pattern: str) -> list[tuple[str, str | tuple[str, int]]]: fieldchar[0] = '' fieldnum[0] = 0 - for idx, char in enumerate(pattern.replace("''", '\0')): + for char in pattern.replace("''", '\0'): if quotebuf is None: if char == "'": # quote started if fieldchar[0]: diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py index 9d227c7..1d65fb2 100644 --- a/babel/localtime/__init__.py +++ b/babel/localtime/__init__.py @@ -9,9 +9,9 @@ :license: BSD, see LICENSE for more details. """ +import datetime import sys import time -import datetime from threading import RLock if sys.platform == 'win32': diff --git a/babel/localtime/_helpers.py b/babel/localtime/_helpers.py index b7238f6..159f9a5 100644 --- a/babel/localtime/_helpers.py +++ b/babel/localtime/_helpers.py @@ -24,6 +24,7 @@ def _get_tzinfo(tzenv: str): return None + def _get_tzinfo_or_raise(tzenv: str): tzinfo = _get_tzinfo(tzenv) if tzinfo is None: diff --git a/babel/localtime/_unix.py b/babel/localtime/_unix.py index 89b461a..eb81beb 100644 --- a/babel/localtime/_unix.py +++ b/babel/localtime/_unix.py @@ -1,14 +1,14 @@ +import datetime import os import re -import datetime - from babel.localtime._helpers import ( + _get_tzinfo, _get_tzinfo_from_file, _get_tzinfo_or_raise, - _get_tzinfo, ) + def _tz_from_env(tzenv: str) -> datetime.tzinfo: if tzenv[0] == ':': tzenv = tzenv[1:] diff --git a/babel/localtime/_win32.py b/babel/localtime/_win32.py index 42f819a..1a52567 100644 --- a/babel/localtime/_win32.py +++ b/babel/localtime/_win32.py @@ -6,9 +6,10 @@ except ImportError: winreg = None import datetime +from typing import Any, Dict, cast + from babel.core import get_global from babel.localtime._helpers import _get_tzinfo_or_raise -from typing import Any, Dict, cast # When building the cldr data on windows this module gets imported. # Because at that point there is no global.dat yet this call will diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 4486bec..dead4aa 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -9,21 +9,20 @@ """ from __future__ import annotations +import datetime import re - from collections import OrderedDict -from collections.abc import Generator, Iterable, Iterator -import datetime +from collections.abc import Iterable, Iterator +from copy import copy from difflib import get_close_matches from email import message_from_string -from copy import copy from typing import TYPE_CHECKING from babel import __version__ as VERSION from babel.core import Locale, UnknownLocaleError from babel.dates import format_datetime from babel.messages.plurals import get_plural -from babel.util import distinct, LOCALTZ, FixedOffsetTimezone, _cmp +from babel.util import LOCALTZ, FixedOffsetTimezone, _cmp, distinct if TYPE_CHECKING: from typing_extensions import TypeAlias @@ -81,7 +80,7 @@ class Message: def __init__( self, id: _MessageID, - string: _MessageID | None = u'', + string: _MessageID | None = '', locations: Iterable[tuple[str, int]] = (), flags: Iterable[str] = (), auto_comments: Iterable[str] = (), @@ -108,7 +107,7 @@ class Message: """ self.id = id if not string and self.pluralizable: - string = (u'', u'') + string = ('', '') self.string = string self.locations = list(distinct(locations)) self.flags = set(flags) @@ -234,13 +233,14 @@ class TranslationError(Exception): translations are encountered.""" -DEFAULT_HEADER = u"""\ +DEFAULT_HEADER = """\ # Translations template for PROJECT. # Copyright (C) YEAR ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. #""" + def parse_separated_header(value: str) -> dict[str, str]: # Adapted from https://peps.python.org/pep-0594/#cgi from email.message import Message @@ -445,7 +445,7 @@ class Catalog: value = self._force_text(value, encoding=self.charset) if name == 'project-id-version': parts = value.split(' ') - self.project = u' '.join(parts[:-1]) + self.project = ' '.join(parts[:-1]) self.version = parts[-1] elif name == 'report-msgid-bugs-to': self.msgid_bugs_address = value @@ -592,7 +592,7 @@ class Catalog: flags = set() if self.fuzzy: flags |= {'fuzzy'} - yield Message(u'', '\n'.join(buf), flags=flags) + yield Message('', '\n'.join(buf), flags=flags) for key in self._messages: yield self._messages[key] @@ -737,7 +737,8 @@ class Catalog: if key in self._messages: del self._messages[key] - def update(self, + def update( + self, template: Catalog, no_fuzzy_matching: bool = False, update_header_comment: bool = False, @@ -832,7 +833,7 @@ class Catalog: if not isinstance(message.string, (list, tuple)): fuzzy = True message.string = tuple( - [message.string] + ([u''] * (len(message.id) - 1)) + [message.string] + ([''] * (len(message.id) - 1)) ) elif len(message.string) != self.num_plurals: fuzzy = True @@ -842,7 +843,7 @@ class Catalog: message.string = message.string[0] message.flags |= oldmsg.flags if fuzzy: - message.flags |= {u'fuzzy'} + message.flags |= {'fuzzy'} self[message.id] = message for message in template: diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py index 00b84e5..38a26e8 100644 --- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -13,8 +13,7 @@ from __future__ import annotations from collections.abc import Callable -from babel.messages.catalog import Catalog, Message, TranslationError, PYTHON_FORMAT - +from babel.messages.catalog import PYTHON_FORMAT, Catalog, Message, TranslationError #: list of format chars that are compatible to each other _string_format_compatibilities = [ @@ -111,7 +110,7 @@ def _validate_format(format: str, alternative: str) -> None: def _check_positional(results: list[tuple[str, str]]) -> bool: positional = None - for name, char in results: + for name, _char in results: if positional is None: positional = name is None else: diff --git a/babel/messages/extract.py b/babel/messages/extract.py index a426510..453742e 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -18,21 +18,29 @@ from __future__ import annotations import ast -from collections.abc import Callable, Collection, Generator, Iterable, Mapping, MutableSequence import io import os import sys +from collections.abc import ( + Callable, + Collection, + Generator, + Iterable, + Mapping, + MutableSequence, +) from os.path import relpath -from tokenize import generate_tokens, COMMENT, NAME, OP, STRING -from typing import Any, TYPE_CHECKING +from textwrap import dedent +from tokenize import COMMENT, NAME, OP, STRING, generate_tokens +from typing import TYPE_CHECKING, Any from babel.util import parse_encoding, parse_future_flags, pathmatch -from textwrap import dedent if TYPE_CHECKING: from typing import IO, Protocol - from typing_extensions import Final, TypeAlias, TypedDict + from _typeshed import SupportsItems, SupportsRead, SupportsReadline + from typing_extensions import Final, TypeAlias, TypedDict class _PyOptions(TypedDict, total=False): encoding: str @@ -82,7 +90,6 @@ DEFAULT_KEYWORDS: dict[str, _Keyword] = { DEFAULT_MAPPING: list[tuple[str, str]] = [('**.py', 'python')] - def _strip_comment_tags(comments: MutableSequence[str], tags: Iterable[str]): """Helper function for `extract` that strips comment tags from strings in a list of comment lines. This functions operates in-place. @@ -652,8 +659,7 @@ def extract_javascript( token = Token('operator', ')', token.lineno) if options.get('parse_template_string') and not funcname and token.type == 'template_string': - for item in parse_template_string(token.value, keywords, comment_tags, options, token.lineno): - yield item + yield from parse_template_string(token.value, keywords, comment_tags, options, token.lineno) elif token.type == 'operator' and token.value == '(': if funcname: @@ -786,8 +792,7 @@ def parse_template_string( if level == 0 and expression_contents: expression_contents = expression_contents[0:-1] fake_file_obj = io.BytesIO(expression_contents.encode()) - for item in extract_javascript(fake_file_obj, keywords, comment_tags, options, lineno): - yield item + yield from extract_javascript(fake_file_obj, keywords, comment_tags, options, lineno) lineno += len(line_re.findall(expression_contents)) expression_contents = '' prev_character = character diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index c7e921d..ab094ec 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -8,6 +8,7 @@ :license: BSD, see LICENSE for more details. """ +import datetime import fnmatch import logging import optparse @@ -18,14 +19,19 @@ import sys import tempfile from collections import OrderedDict from configparser import RawConfigParser -import datetime from io import StringIO +from typing import Iterable -from babel import __version__ as VERSION from babel import Locale, localedata +from babel import __version__ as VERSION from babel.core import UnknownLocaleError -from babel.messages.catalog import Catalog, DEFAULT_HEADER -from babel.messages.extract import DEFAULT_KEYWORDS, DEFAULT_MAPPING, check_and_call_extract_file, extract_from_dir +from babel.messages.catalog import DEFAULT_HEADER, Catalog +from babel.messages.extract import ( + DEFAULT_KEYWORDS, + DEFAULT_MAPPING, + check_and_call_extract_file, + extract_from_dir, +) from babel.messages.mofile import write_mo from babel.messages.pofile import read_po, write_po from babel.util import LOCALTZ @@ -38,15 +44,16 @@ try: distutils_log = log # "distutils.log → (no replacement yet)" try: - from setuptools.errors import OptionError, SetupError, BaseError + from setuptools.errors import BaseError, OptionError, SetupError except ImportError: # Error aliases only added in setuptools 59 (2021-11). OptionError = SetupError = BaseError = Exception except ImportError: from distutils import log as distutils_log from distutils.cmd import Command as _Command - from distutils.errors import DistutilsOptionError as OptionError, DistutilsSetupError as SetupError, DistutilsError as BaseError - + from distutils.errors import DistutilsError as BaseError + from distutils.errors import DistutilsOptionError as OptionError + from distutils.errors import DistutilsSetupError as SetupError def listify_value(arg, split=None): @@ -188,7 +195,7 @@ class compile_catalog(Command): def run(self): n_errors = 0 for domain in self.domain: - for catalog, errors in self._run_domain(domain).items(): + for errors in self._run_domain(domain).values(): n_errors += len(errors) if n_errors: self.log.error('%d errors encountered.', n_errors) @@ -472,6 +479,27 @@ class extract_messages(Command): else: self.directory_filter = None + def _build_callback(self, path: str): + def callback(filename: str, method: str, options: dict): + if method == 'ignore': + return + + # If we explicitly provide a full filepath, just use that. + # Otherwise, path will be the directory path and filename + # is the relative path from that dir to the file. + # So we can join those to get the full filepath. + if os.path.isfile(path): + filepath = path + else: + filepath = os.path.normpath(os.path.join(path, filename)) + + optstr = '' + if options: + opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items()) + optstr = f" ({opt_values})" + self.log.info('extracting messages from %s%s', filepath, optstr) + return callback + def run(self): mappings = self._get_mappings() with open(self.output_file, 'wb') as outfile: @@ -483,25 +511,7 @@ class extract_messages(Command): header_comment=(self.header_comment or DEFAULT_HEADER)) for path, method_map, options_map in mappings: - def callback(filename, method, options): - if method == 'ignore': - return - - # If we explicitly provide a full filepath, just use that. - # Otherwise, path will be the directory path and filename - # is the relative path from that dir to the file. - # So we can join those to get the full filepath. - if os.path.isfile(path): - filepath = path - else: - filepath = os.path.normpath(os.path.join(path, filename)) - - optstr = '' - if options: - opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items()) - optstr = f" ({opt_values})" - self.log.info('extracting messages from %s%s', filepath, optstr) - + callback = self._build_callback(path) if os.path.isfile(path): current_dir = os.getcwd() extracted = check_and_call_extract_file( @@ -842,7 +852,7 @@ class update_catalog(Command): omit_header=self.omit_header, ignore_obsolete=self.ignore_obsolete, include_previous=self.previous, width=self.width) - except: + except Exception: os.remove(tmpname) raise @@ -937,7 +947,7 @@ class CommandLineInterface: identifiers = localedata.locale_identifiers() longest = max(len(identifier) for identifier in identifiers) identifiers.sort() - format = u'%%-%ds %%s' % (longest + 1) + format = '%%-%ds %%s' % (longest + 1) for identifier in identifiers: locale = Locale.parse(identifier) print(format % (identifier, locale.english_name)) @@ -1105,7 +1115,7 @@ def parse_mapping(fileobj, filename=None): return method_map, options_map -def parse_keywords(strings=[]): +def parse_keywords(strings: Iterable[str] = ()): """Parse keywords specifications from the given list of strings. >>> kw = sorted(parse_keywords(['_', 'dgettext:2', 'dngettext:2,3', 'pgettext:1c,2']).items()) diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index 07fffde..0563f62 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ babel.messages.jslexer ~~~~~~~~~~~~~~~~~~~~~~ @@ -11,9 +10,8 @@ """ from __future__ import annotations -from collections import namedtuple -from collections.abc import Generator, Iterator, Sequence import re +from collections.abc import Generator from typing import NamedTuple operators: list[str] = sorted([ @@ -34,11 +32,13 @@ line_join_re = re.compile(r'\\' + line_re.pattern) uni_escape_re = re.compile(r'[a-fA-F0-9]{1,4}') hex_escape_re = re.compile(r'[a-fA-F0-9]{1,2}') + class Token(NamedTuple): type: str value: str lineno: int + _rules: list[tuple[str | None, re.Pattern[str]]] = [ (None, re.compile(r'\s+', re.UNICODE)), (None, re.compile(r'<!--.*')), @@ -102,7 +102,7 @@ def unquote_string(string: str) -> str: add = result.append pos = 0 - while 1: + while True: # scan for the next escape escape_pos = string.find('\\', pos) if escape_pos < 0: @@ -155,7 +155,7 @@ def unquote_string(string: str) -> str: if pos < len(string): add(string[pos:]) - return u''.join(result) + return ''.join(result) def tokenize(source: str, jsx: bool = True, dotted: bool = True, template_string: bool = True, lineno: int = 1) -> Generator[Token, None, None]: @@ -174,7 +174,7 @@ def tokenize(source: str, jsx: bool = True, dotted: bool = True, template_string while pos < end: # handle regular rules first - for token_type, rule in rules: + for token_type, rule in rules: # noqa: B007 match = rule.match(source, pos) if match is not None: break diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py index a96f059..0a432a7 100644 --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -55,7 +55,7 @@ def read_mo(fileobj: SupportsRead[bytes]) -> Catalog: # Now put all messages from the .mo file buffer into the catalog # dictionary - for i in range(0, msgcount): + for _i in range(msgcount): mlen, moff = unpack(ii, buf[origidx:origidx + 8]) mend = moff + mlen tlen, toff = unpack(ii, buf[transidx:transidx + 8]) diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py index 0fdf53b..eb8de47 100644 --- a/babel/messages/plurals.py +++ b/babel/messages/plurals.py @@ -9,9 +9,9 @@ """ from __future__ import annotations -from babel.core import default_locale, Locale from operator import itemgetter +from babel.core import Locale, default_locale # XXX: remove this file, duplication with babel.plural diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index b6d0d6e..aef8cbf 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -14,14 +14,15 @@ import os import re from collections.abc import Iterable from typing import TYPE_CHECKING -from babel.core import Locale +from babel.core import Locale from babel.messages.catalog import Catalog, Message -from babel.util import wraptext, _cmp +from babel.util import _cmp, wraptext if TYPE_CHECKING: - from _typeshed import SupportsWrite from typing import IO, AnyStr + + from _typeshed import SupportsWrite from typing_extensions import Literal @@ -133,7 +134,6 @@ class _NormalizedString: return self.__cmp__(other) != 0 - class PoFileParser: """Support class to read messages from a ``gettext`` PO (portable object) file and add them to a `Catalog` @@ -183,7 +183,7 @@ class PoFileParser: string = ['' for _ in range(self.catalog.num_plurals)] for idx, translation in self.translations: if idx >= self.catalog.num_plurals: - self._invalid_pofile(u"", self.offset, "msg has more translations than num_plurals of catalog") + self._invalid_pofile("", self.offset, "msg has more translations than num_plurals of catalog") continue string[idx] = translation.denormalize() string = tuple(string) @@ -319,8 +319,8 @@ class PoFileParser: # No actual messages found, but there was some info in comments, from which # we'll construct an empty header message if not self.counter and (self.flags or self.user_comments or self.auto_comments): - self.messages.append(_NormalizedString(u'""')) - self.translations.append([0, _NormalizedString(u'""')]) + self.messages.append(_NormalizedString('""')) + self.translations.append([0, _NormalizedString('""')]) self._add_message() def _invalid_pofile(self, line, lineno, msg) -> None: @@ -451,17 +451,17 @@ def normalize(string: str, prefix: str = '', width: int = 76) -> str: buf = [] size = 2 while chunks: - l = len(escape(chunks[-1])) - 2 + prefixlen - if size + l < width: + length = len(escape(chunks[-1])) - 2 + prefixlen + if size + length < width: buf.append(chunks.pop()) - size += l + size += length else: if not buf: # handle long chunks by putting them on a # separate line buf.append(chunks.pop()) break - lines.append(u''.join(buf)) + lines.append(''.join(buf)) else: lines.append(line) else: @@ -474,7 +474,7 @@ def normalize(string: str, prefix: str = '', width: int = 76) -> str: if lines and not lines[-1]: del lines[-1] lines[-1] += '\n' - return u'""\n' + u'\n'.join([(prefix + escape(line)) for line in lines]) + return '""\n' + '\n'.join([(prefix + escape(line)) for line in lines]) def write_po( @@ -585,7 +585,7 @@ def write_po( for line in comment_header.splitlines(): lines += wraptext(line, width=width, subsequent_indent='# ') - comment_header = u'\n'.join(lines) + comment_header = '\n'.join(lines) _write(f"{comment_header}\n") for comment in message.user_comments: @@ -614,11 +614,13 @@ def write_po( locs.append(location) _write_comment(' '.join(locs), prefix=':') if message.flags: - _write('#%s\n' % ', '.join([''] + sorted(message.flags))) + _write(f"#{', '.join(['', *sorted(message.flags)])}\n") if message.previous_id and include_previous: - _write_comment('msgid %s' % _normalize(message.previous_id[0]), - prefix='|') + _write_comment( + f'msgid {_normalize(message.previous_id[0])}', + prefix='|', + ) if len(message.previous_id) > 1: _write_comment('msgid_plural %s' % _normalize( message.previous_id[1] diff --git a/babel/numbers.py b/babel/numbers.py index 5a05f91..399b70b 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -19,11 +19,11 @@ # - https://www.unicode.org/reports/tr35/ (Appendix G.6) from __future__ import annotations +import datetime import decimal import re -from typing import TYPE_CHECKING, Any, overload import warnings -import datetime +from typing import TYPE_CHECKING, Any, overload from babel.core import Locale, default_locale, get_global from babel.localedata import LocaleDataDict @@ -324,7 +324,7 @@ def get_decimal_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: :param locale: the `Locale` object or locale identifier """ - return Locale.parse(locale).number_symbols.get('decimal', u'.') + return Locale.parse(locale).number_symbols.get('decimal', '.') def get_plus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: @@ -335,7 +335,7 @@ def get_plus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: :param locale: the `Locale` object or locale identifier """ - return Locale.parse(locale).number_symbols.get('plusSign', u'+') + return Locale.parse(locale).number_symbols.get('plusSign', '+') def get_minus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: @@ -346,7 +346,7 @@ def get_minus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: :param locale: the `Locale` object or locale identifier """ - return Locale.parse(locale).number_symbols.get('minusSign', u'-') + return Locale.parse(locale).number_symbols.get('minusSign', '-') def get_exponential_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: @@ -357,7 +357,7 @@ def get_exponential_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: :param locale: the `Locale` object or locale identifier """ - return Locale.parse(locale).number_symbols.get('exponential', u'E') + return Locale.parse(locale).number_symbols.get('exponential', 'E') def get_group_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: @@ -368,11 +368,11 @@ def get_group_symbol(locale: Locale | str | None = LC_NUMERIC) -> str: :param locale: the `Locale` object or locale identifier """ - return Locale.parse(locale).number_symbols.get('group', u',') + return Locale.parse(locale).number_symbols.get('group', ',') def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = LC_NUMERIC) -> str: - u"""Return the given number formatted for a specific locale. + """Return the given number formatted for a specific locale. >>> format_number(1099, locale='en_US') # doctest: +SKIP u'1,099' @@ -418,7 +418,7 @@ def format_decimal( decimal_quantization: bool = True, group_separator: bool = True, ) -> str: - u"""Return the given decimal number formatted for a specific locale. + """Return the given decimal number formatted for a specific locale. >>> format_decimal(1.2345, locale='en_US') u'1.234' @@ -473,7 +473,7 @@ def format_compact_decimal( locale: Locale | str | None = LC_NUMERIC, fraction_digits: int = 0, ) -> str: - u"""Return the given decimal number formatted for a specific locale in compact form. + """Return the given decimal number formatted for a specific locale in compact form. >>> format_compact_decimal(12345, format_type="short", locale='en_US') u'12K' @@ -555,7 +555,7 @@ def format_currency( decimal_quantization: bool = True, group_separator: bool = True, ) -> str: - u"""Return formatted currency value. + """Return formatted currency value. >>> format_currency(1099.98, 'USD', locale='en_US') u'$1,099.98' @@ -653,7 +653,7 @@ def format_currency( try: pattern = locale.currency_formats[format_type] except KeyError: - raise UnknownCurrencyFormatError(f"{format_type!r} is not a known currency format type") + raise UnknownCurrencyFormatError(f"{format_type!r} is not a known currency format type") from None return pattern.apply( number, locale, currency=currency, currency_digits=currency_digits, @@ -711,7 +711,7 @@ def format_compact_currency( locale: Locale | str | None = LC_NUMERIC, fraction_digits: int = 0 ) -> str: - u"""Format a number as a currency value in compact form. + """Format a number as a currency value in compact form. >>> format_compact_currency(12345, 'USD', locale='en_US') u'$12K' @@ -870,8 +870,8 @@ def parse_number(string: str, locale: Locale | str | None = LC_NUMERIC) -> int: """ try: return int(string.replace(get_group_symbol(locale), '')) - except ValueError: - raise NumberFormatError(f"{string!r} is not a valid number") + except ValueError as ve: + raise NumberFormatError(f"{string!r} is not a valid number") from ve def parse_decimal(string: str, locale: Locale | str | None = LC_NUMERIC, strict: bool = False) -> decimal.Decimal: @@ -916,7 +916,7 @@ def parse_decimal(string: str, locale: Locale | str | None = LC_NUMERIC, strict: decimal_symbol = get_decimal_symbol(locale) if not strict and ( - group_symbol == u'\xa0' and # if the grouping symbol is U+00A0 NO-BREAK SPACE, + group_symbol == '\xa0' and # if the grouping symbol is U+00A0 NO-BREAK SPACE, group_symbol not in string and # and the string to be parsed does not contain it, ' ' in string # but it does contain a space instead, ): @@ -926,20 +926,20 @@ def parse_decimal(string: str, locale: Locale | str | None = LC_NUMERIC, strict: try: parsed = decimal.Decimal(string.replace(group_symbol, '') .replace(decimal_symbol, '.')) - except decimal.InvalidOperation: - raise NumberFormatError(f"{string!r} is not a valid decimal number") + except decimal.InvalidOperation as exc: + raise NumberFormatError(f"{string!r} is not a valid decimal number") from exc if strict and group_symbol in string: proper = format_decimal(parsed, locale=locale, decimal_quantization=False) if string != proper and string.rstrip('0') != (proper + decimal_symbol): try: parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '') .replace(group_symbol, '.')) - except decimal.InvalidOperation: + except decimal.InvalidOperation as exc: raise NumberFormatError( f"{string!r} is not a properly formatted decimal number. " f"Did you mean {proper!r}?", suggestions=[proper], - ) + ) from exc else: proper_alt = format_decimal(parsed_alt, locale=locale, decimal_quantization=False) if proper_alt == proper: @@ -1095,7 +1095,7 @@ class NumberPattern: scale = 0 if '%' in ''.join(self.prefix + self.suffix): scale = 2 - elif u'‰' in ''.join(self.prefix + self.suffix): + elif '‰' in ''.join(self.prefix + self.suffix): scale = 3 return scale @@ -1222,11 +1222,11 @@ class NumberPattern: number if self.number_pattern != '' else '', self.suffix[is_negative]]) - if u'¤' in retval: - retval = retval.replace(u'¤¤¤', + if '¤' in retval: + retval = retval.replace('¤¤¤', get_currency_name(currency, value, locale)) - retval = retval.replace(u'¤¤', currency.upper()) - retval = retval.replace(u'¤', get_currency_symbol(currency, locale)) + retval = retval.replace('¤¤', currency.upper()) + retval = retval.replace('¤', get_currency_symbol(currency, locale)) # remove single quotes around text, except for doubled single quotes # which are replaced with a single quote diff --git a/babel/plural.py b/babel/plural.py index b4f54c0..26073ff 100644 --- a/babel/plural.py +++ b/babel/plural.py @@ -115,7 +115,7 @@ class PluralRule: rules = rules.items() found = set() self.abstract: list[tuple[str, Any]] = [] - for key, expr in sorted(list(rules)): + for key, expr in sorted(rules): if key not in _plural_tags: raise ValueError(f"unknown tag {key!r}") elif key in found: @@ -325,6 +325,7 @@ def cldr_modulo(a: float, b: float) -> float: class RuleError(Exception): """Raised if a rule is malformed.""" + _VARS = { 'n', # absolute value of the source number. 'i', # integer digits of n. @@ -363,6 +364,7 @@ def tokenize_rule(s: str) -> list[tuple[str, str]]: 'Got unexpected %r' % s[pos]) return result[::-1] + def test_next_token( tokens: list[tuple[str, str]], type_: str, @@ -519,7 +521,7 @@ class _Parser: def _binary_compiler(tmpl): """Compiler factory for the `_Compiler`.""" - return lambda self, l, r: tmpl % (self.compile(l), self.compile(r)) + return lambda self, left, right: tmpl % (self.compile(left), self.compile(right)) def _unary_compiler(tmpl): @@ -627,7 +629,7 @@ class _UnicodeCompiler(_Compiler): compile_mod = _binary_compiler('%s mod %s') def compile_not(self, relation): - return self.compile_relation(negated=True, *relation[1]) + return self.compile_relation(*relation[1], negated=True) def compile_relation(self, method, expr, range_list, negated=False): ranges = [] diff --git a/babel/support.py b/babel/support.py index a8dd230..242b492 100644 --- a/babel/support.py +++ b/babel/support.py @@ -16,23 +16,26 @@ import decimal import gettext import locale import os -import datetime from collections.abc import Iterator from typing import TYPE_CHECKING, Any, Callable from babel.core import Locale - -from babel.dates import (format_date, format_datetime, format_time, - format_timedelta, get_timezone) -from babel.numbers import (format_compact_currency, format_compact_decimal, - format_currency, format_decimal, format_percent, - format_scientific) +from babel.dates import format_date, format_datetime, format_time, format_timedelta +from babel.numbers import ( + format_compact_currency, + format_compact_decimal, + format_currency, + format_decimal, + format_percent, + format_scientific, +) if TYPE_CHECKING: from typing_extensions import Literal from babel.dates import _PredefinedTimeFormat + class Format: """Wrapper class providing the various date and number formatting functions bound to a specific locale and time-zone. @@ -77,6 +80,7 @@ class Format: """Return a date and time formatted according to the given pattern. >>> from datetime import datetime + >>> from babel.dates import get_timezone >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern')) >>> fmt.datetime(datetime(2007, 4, 1, 15, 30)) u'Apr 1, 2007, 11:30:00 AM' @@ -91,6 +95,7 @@ class Format: """Return a time formatted according to the given pattern. >>> from datetime import datetime + >>> from babel.dates import get_timezone >>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern')) >>> fmt.time(datetime(2007, 4, 1, 15, 30)) u'11:30:00 AM' @@ -333,7 +338,7 @@ class LazyProxy: return LazyProxy( self._func, enable_cache=self._is_cache_enabled, - *self._args, + *self._args, # noqa: B026 **self._kwargs ) @@ -342,7 +347,7 @@ class LazyProxy: return LazyProxy( deepcopy(self._func, memo), enable_cache=deepcopy(self._is_cache_enabled, memo), - *deepcopy(self._args, memo), + *deepcopy(self._args, memo), # noqa: B026 **deepcopy(self._kwargs, memo) ) diff --git a/babel/units.py b/babel/units.py index 1180bd1..0c72ee9 100644 --- a/babel/units.py +++ b/babel/units.py @@ -9,6 +9,7 @@ from babel.numbers import LC_NUMERIC, format_decimal if TYPE_CHECKING: from typing_extensions import Literal + class UnknownUnitError(ValueError): def __init__(self, unit: str, locale: Locale) -> None: ValueError.__init__(self, f"{unit} is not a known unit in {locale}") diff --git a/babel/util.py b/babel/util.py index d25ec53..a5403e6 100644 --- a/babel/util.py +++ b/babel/util.py @@ -11,19 +11,20 @@ from __future__ import annotations import codecs import collections +import datetime import os import re import textwrap -from babel import localtime, dates - from collections.abc import Generator, Iterable -import datetime from typing import IO, Any, TypeVar +from babel import dates, localtime + missing = object() _T = TypeVar("_T") + def distinct(iterable: Iterable[_T]) -> Generator[_T, None, None]: """Yield all items in an iterable collection that are distinct. @@ -43,6 +44,7 @@ def distinct(iterable: Iterable[_T]) -> Generator[_T, None, None]: yield item seen.add(item) + # Regexp to match python magic encoding line PYTHON_MAGIC_COMMENT_re = re.compile( br'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)', re.VERBOSE) |