summaryrefslogtreecommitdiff
path: root/babel
diff options
context:
space:
mode:
Diffstat (limited to 'babel')
-rw-r--r--babel/core.py11
-rw-r--r--babel/dates.py32
-rw-r--r--babel/localtime/__init__.py2
-rw-r--r--babel/localtime/_helpers.py1
-rw-r--r--babel/localtime/_unix.py6
-rw-r--r--babel/localtime/_win32.py3
-rw-r--r--babel/messages/catalog.py27
-rw-r--r--babel/messages/checkers.py5
-rw-r--r--babel/messages/extract.py25
-rw-r--r--babel/messages/frontend.py70
-rw-r--r--babel/messages/jslexer.py12
-rw-r--r--babel/messages/mofile.py2
-rw-r--r--babel/messages/plurals.py2
-rw-r--r--babel/messages/pofile.py34
-rw-r--r--babel/numbers.py50
-rw-r--r--babel/plural.py8
-rw-r--r--babel/support.py23
-rw-r--r--babel/units.py1
-rw-r--r--babel/util.py8
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)