gh-110519: Improve deprecation warning in the gettext module (#110520)

Deprecation warning about non-integer numbers in gettext now always refers
to the line in the user code where gettext function or method is used.
Previously, it could refer to a line in gettext code.

Also, increase test coverage for NullTranslations and domain-aware functions
like dngettext().
This commit is contained in:
Serhiy Storchaka 2023-10-09 16:45:22 +03:00 committed by GitHub
parent 7bd560ce8d
commit 326c6c4e07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 48 deletions

View file

@ -46,6 +46,7 @@
# find this format documented anywhere.
import operator
import os
import re
import sys
@ -166,14 +167,21 @@ def _parse(tokens, priority=-1):
def _as_int(n):
try:
i = round(n)
round(n)
except TypeError:
raise TypeError('Plural value must be an integer, got %s' %
(n.__class__.__name__,)) from None
import warnings
frame = sys._getframe(1)
stacklevel = 2
while frame.f_back is not None and frame.f_globals.get('__name__') == __name__:
stacklevel += 1
frame = frame.f_back
warnings.warn('Plural value must be an integer, got %s' %
(n.__class__.__name__,),
DeprecationWarning, 4)
DeprecationWarning,
stacklevel)
return n
@ -200,7 +208,7 @@ def c2py(plural):
elif c == ')':
depth -= 1
ns = {'_as_int': _as_int}
ns = {'_as_int': _as_int, '__name__': __name__}
exec('''if True:
def func(n):
if not isinstance(n, int):

View file

@ -2,6 +2,7 @@
import base64
import gettext
import unittest
from functools import partial
from test import support
from test.support import os_helper
@ -122,8 +123,9 @@ def reset_gettext():
class GettextBaseTest(unittest.TestCase):
def setUp(self):
self.addCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
@classmethod
def setUpClass(cls):
cls.addClassCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
if not os.path.isdir(LOCALEDIR):
os.makedirs(LOCALEDIR)
with open(MOFILE, 'wb') as fp:
@ -136,6 +138,8 @@ def setUp(self):
fp.write(base64.decodebytes(UMO_DATA))
with open(MMOFILE, 'wb') as fp:
fp.write(base64.decodebytes(MMO_DATA))
def setUp(self):
self.env = self.enterContext(os_helper.EnvironmentVarGuard())
self.env['LANGUAGE'] = 'xx'
reset_gettext()
@ -316,59 +320,137 @@ def test_multiline_strings(self):
trggrkg zrffntr pngnybt yvoenel.''')
class PluralFormsTestCase(GettextBaseTest):
class PluralFormsTests:
def _test_plural_forms(self, ngettext, gettext,
singular, plural, tsingular, tplural,
numbers_only=True):
x = ngettext(singular, plural, 1)
self.assertEqual(x, tsingular)
x = ngettext(singular, plural, 2)
self.assertEqual(x, tplural)
x = gettext(singular)
self.assertEqual(x, tsingular)
if numbers_only:
lineno = self._test_plural_forms.__code__.co_firstlineno + 9
with self.assertWarns(DeprecationWarning) as cm:
x = ngettext(singular, plural, 1.0)
self.assertEqual(cm.filename, __file__)
self.assertEqual(cm.lineno, lineno + 4)
self.assertEqual(x, tsingular)
with self.assertWarns(DeprecationWarning) as cm:
x = ngettext(singular, plural, 1.1)
self.assertEqual(cm.filename, __file__)
self.assertEqual(cm.lineno, lineno + 9)
self.assertEqual(x, tplural)
with self.assertRaises(TypeError):
ngettext(singular, plural, None)
else:
x = ngettext(singular, plural, None)
self.assertEqual(x, tplural)
def test_plural_forms(self):
self._test_plural_forms(
self.ngettext, self.gettext,
'There is %s file', 'There are %s files',
'Hay %s fichero', 'Hay %s ficheros')
self._test_plural_forms(
self.ngettext, self.gettext,
'%d file deleted', '%d files deleted',
'%d file deleted', '%d files deleted')
def test_plural_context_forms(self):
ngettext = partial(self.npgettext, 'With context')
gettext = partial(self.pgettext, 'With context')
self._test_plural_forms(
ngettext, gettext,
'There is %s file', 'There are %s files',
'Hay %s fichero (context)', 'Hay %s ficheros (context)')
self._test_plural_forms(
ngettext, gettext,
'%d file deleted', '%d files deleted',
'%d file deleted', '%d files deleted')
def test_plural_wrong_context_forms(self):
self._test_plural_forms(
partial(self.npgettext, 'Unknown context'),
partial(self.pgettext, 'Unknown context'),
'There is %s file', 'There are %s files',
'There is %s file', 'There are %s files')
class GNUTranslationsPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
def setUp(self):
GettextBaseTest.setUp(self)
self.localedir = os.curdir
# Set up the bindings
gettext.bindtextdomain('gettext', self.localedir)
gettext.bindtextdomain('gettext', os.curdir)
gettext.textdomain('gettext')
self.mofile = MOFILE
def test_plural_forms1(self):
eq = self.assertEqual
x = gettext.ngettext('There is %s file', 'There are %s files', 1)
eq(x, 'Hay %s fichero')
x = gettext.ngettext('There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros')
x = gettext.gettext('There is %s file')
eq(x, 'Hay %s fichero')
self.gettext = gettext.gettext
self.ngettext = gettext.ngettext
self.pgettext = gettext.pgettext
self.npgettext = gettext.npgettext
def test_plural_context_forms1(self):
eq = self.assertEqual
x = gettext.npgettext('With context',
'There is %s file', 'There are %s files', 1)
eq(x, 'Hay %s fichero (context)')
x = gettext.npgettext('With context',
'There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros (context)')
x = gettext.pgettext('With context', 'There is %s file')
eq(x, 'Hay %s fichero (context)')
def test_plural_forms2(self):
eq = self.assertEqual
with open(self.mofile, 'rb') as fp:
class GNUTranslationsWithDomainPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
def setUp(self):
GettextBaseTest.setUp(self)
# Set up the bindings
gettext.bindtextdomain('gettext', os.curdir)
self.gettext = partial(gettext.dgettext, 'gettext')
self.ngettext = partial(gettext.dngettext, 'gettext')
self.pgettext = partial(gettext.dpgettext, 'gettext')
self.npgettext = partial(gettext.dnpgettext, 'gettext')
def test_plural_forms_wrong_domain(self):
self._test_plural_forms(
partial(gettext.dngettext, 'unknown'),
partial(gettext.dgettext, 'unknown'),
'There is %s file', 'There are %s files',
'There is %s file', 'There are %s files',
numbers_only=False)
def test_plural_context_forms_wrong_domain(self):
self._test_plural_forms(
partial(gettext.dnpgettext, 'unknown', 'With context'),
partial(gettext.dpgettext, 'unknown', 'With context'),
'There is %s file', 'There are %s files',
'There is %s file', 'There are %s files',
numbers_only=False)
class GNUTranslationsClassPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
def setUp(self):
GettextBaseTest.setUp(self)
with open(MOFILE, 'rb') as fp:
t = gettext.GNUTranslations(fp)
x = t.ngettext('There is %s file', 'There are %s files', 1)
eq(x, 'Hay %s fichero')
x = t.ngettext('There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros')
x = t.gettext('There is %s file')
eq(x, 'Hay %s fichero')
def test_plural_context_forms2(self):
eq = self.assertEqual
with open(self.mofile, 'rb') as fp:
t = gettext.GNUTranslations(fp)
x = t.npgettext('With context',
'There is %s file', 'There are %s files', 1)
eq(x, 'Hay %s fichero (context)')
x = t.npgettext('With context',
'There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros (context)')
x = t.pgettext('With context', 'There is %s file')
eq(x, 'Hay %s fichero (context)')
self.gettext = t.gettext
self.ngettext = t.ngettext
self.pgettext = t.pgettext
self.npgettext = t.npgettext
def test_plural_forms_null_translations(self):
t = gettext.NullTranslations()
self._test_plural_forms(
t.ngettext, t.gettext,
'There is %s file', 'There are %s files',
'There is %s file', 'There are %s files',
numbers_only=False)
def test_plural_context_forms_null_translations(self):
t = gettext.NullTranslations()
self._test_plural_forms(
partial(t.npgettext, 'With context'),
partial(t.pgettext, 'With context'),
'There is %s file', 'There are %s files',
'There is %s file', 'There are %s files',
numbers_only=False)
class PluralFormsInternalTestCase:
# Examples from http://www.gnu.org/software/gettext/manual/gettext.html
def test_ja(self):

View file

@ -0,0 +1,3 @@
Deprecation warning about non-integer number in :mod:`gettext` now alwais
refers to the line in the user code where gettext function or method is
used. Previously it could refer to a line in ``gettext`` code.