Skip to content

Commit 1f43bc2

Browse files
[3.12] gh-110519: Improve deprecation warning in the gettext module (GH-110520) (GH-110563)
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(). (cherry picked from commit 326c6c4) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 53122bc commit 1f43bc2

File tree

3 files changed

+144
-51
lines changed

3 files changed

+144
-51
lines changed

Lib/gettext.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
# find this format documented anywhere.
4747

4848

49+
import operator
4950
import os
5051
import re
5152
import sys
@@ -166,14 +167,21 @@ def _parse(tokens, priority=-1):
166167

167168
def _as_int(n):
168169
try:
169-
i = round(n)
170+
round(n)
170171
except TypeError:
171172
raise TypeError('Plural value must be an integer, got %s' %
172173
(n.__class__.__name__,)) from None
174+
173175
import warnings
176+
frame = sys._getframe(1)
177+
stacklevel = 2
178+
while frame.f_back is not None and frame.f_globals.get('__name__') == __name__:
179+
stacklevel += 1
180+
frame = frame.f_back
174181
warnings.warn('Plural value must be an integer, got %s' %
175182
(n.__class__.__name__,),
176-
DeprecationWarning, 4)
183+
DeprecationWarning,
184+
stacklevel)
177185
return n
178186

179187

@@ -200,7 +208,7 @@ def c2py(plural):
200208
elif c == ')':
201209
depth -= 1
202210

203-
ns = {'_as_int': _as_int}
211+
ns = {'_as_int': _as_int, '__name__': __name__}
204212
exec('''if True:
205213
def func(n):
206214
if not isinstance(n, int):

Lib/test/test_gettext.py

+130-48
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import base64
33
import gettext
44
import unittest
5+
from functools import partial
56

67
from test import support
78
from test.support import os_helper
@@ -122,8 +123,9 @@ def reset_gettext():
122123

123124

124125
class GettextBaseTest(unittest.TestCase):
125-
def setUp(self):
126-
self.addCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
126+
@classmethod
127+
def setUpClass(cls):
128+
cls.addClassCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
127129
if not os.path.isdir(LOCALEDIR):
128130
os.makedirs(LOCALEDIR)
129131
with open(MOFILE, 'wb') as fp:
@@ -136,6 +138,8 @@ def setUp(self):
136138
fp.write(base64.decodebytes(UMO_DATA))
137139
with open(MMOFILE, 'wb') as fp:
138140
fp.write(base64.decodebytes(MMO_DATA))
141+
142+
def setUp(self):
139143
self.env = self.enterContext(os_helper.EnvironmentVarGuard())
140144
self.env['LANGUAGE'] = 'xx'
141145
reset_gettext()
@@ -316,59 +320,137 @@ def test_multiline_strings(self):
316320
trggrkg zrffntr pngnybt yvoenel.''')
317321

318322

319-
class PluralFormsTestCase(GettextBaseTest):
323+
class PluralFormsTests:
324+
325+
def _test_plural_forms(self, ngettext, gettext,
326+
singular, plural, tsingular, tplural,
327+
numbers_only=True):
328+
x = ngettext(singular, plural, 1)
329+
self.assertEqual(x, tsingular)
330+
x = ngettext(singular, plural, 2)
331+
self.assertEqual(x, tplural)
332+
x = gettext(singular)
333+
self.assertEqual(x, tsingular)
334+
335+
if numbers_only:
336+
lineno = self._test_plural_forms.__code__.co_firstlineno + 9
337+
with self.assertWarns(DeprecationWarning) as cm:
338+
x = ngettext(singular, plural, 1.0)
339+
self.assertEqual(cm.filename, __file__)
340+
self.assertEqual(cm.lineno, lineno + 4)
341+
self.assertEqual(x, tsingular)
342+
with self.assertWarns(DeprecationWarning) as cm:
343+
x = ngettext(singular, plural, 1.1)
344+
self.assertEqual(cm.filename, __file__)
345+
self.assertEqual(cm.lineno, lineno + 9)
346+
self.assertEqual(x, tplural)
347+
with self.assertRaises(TypeError):
348+
ngettext(singular, plural, None)
349+
else:
350+
x = ngettext(singular, plural, None)
351+
self.assertEqual(x, tplural)
352+
353+
def test_plural_forms(self):
354+
self._test_plural_forms(
355+
self.ngettext, self.gettext,
356+
'There is %s file', 'There are %s files',
357+
'Hay %s fichero', 'Hay %s ficheros')
358+
self._test_plural_forms(
359+
self.ngettext, self.gettext,
360+
'%d file deleted', '%d files deleted',
361+
'%d file deleted', '%d files deleted')
362+
363+
def test_plural_context_forms(self):
364+
ngettext = partial(self.npgettext, 'With context')
365+
gettext = partial(self.pgettext, 'With context')
366+
self._test_plural_forms(
367+
ngettext, gettext,
368+
'There is %s file', 'There are %s files',
369+
'Hay %s fichero (context)', 'Hay %s ficheros (context)')
370+
self._test_plural_forms(
371+
ngettext, gettext,
372+
'%d file deleted', '%d files deleted',
373+
'%d file deleted', '%d files deleted')
374+
375+
def test_plural_wrong_context_forms(self):
376+
self._test_plural_forms(
377+
partial(self.npgettext, 'Unknown context'),
378+
partial(self.pgettext, 'Unknown context'),
379+
'There is %s file', 'There are %s files',
380+
'There is %s file', 'There are %s files')
381+
382+
383+
class GNUTranslationsPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
320384
def setUp(self):
321385
GettextBaseTest.setUp(self)
322-
self.localedir = os.curdir
323386
# Set up the bindings
324-
gettext.bindtextdomain('gettext', self.localedir)
387+
gettext.bindtextdomain('gettext', os.curdir)
325388
gettext.textdomain('gettext')
326-
self.mofile = MOFILE
327389

328-
def test_plural_forms1(self):
329-
eq = self.assertEqual
330-
x = gettext.ngettext('There is %s file', 'There are %s files', 1)
331-
eq(x, 'Hay %s fichero')
332-
x = gettext.ngettext('There is %s file', 'There are %s files', 2)
333-
eq(x, 'Hay %s ficheros')
334-
x = gettext.gettext('There is %s file')
335-
eq(x, 'Hay %s fichero')
336-
337-
def test_plural_context_forms1(self):
338-
eq = self.assertEqual
339-
x = gettext.npgettext('With context',
340-
'There is %s file', 'There are %s files', 1)
341-
eq(x, 'Hay %s fichero (context)')
342-
x = gettext.npgettext('With context',
343-
'There is %s file', 'There are %s files', 2)
344-
eq(x, 'Hay %s ficheros (context)')
345-
x = gettext.pgettext('With context', 'There is %s file')
346-
eq(x, 'Hay %s fichero (context)')
347-
348-
def test_plural_forms2(self):
349-
eq = self.assertEqual
350-
with open(self.mofile, 'rb') as fp:
351-
t = gettext.GNUTranslations(fp)
352-
x = t.ngettext('There is %s file', 'There are %s files', 1)
353-
eq(x, 'Hay %s fichero')
354-
x = t.ngettext('There is %s file', 'There are %s files', 2)
355-
eq(x, 'Hay %s ficheros')
356-
x = t.gettext('There is %s file')
357-
eq(x, 'Hay %s fichero')
358-
359-
def test_plural_context_forms2(self):
360-
eq = self.assertEqual
361-
with open(self.mofile, 'rb') as fp:
390+
self.gettext = gettext.gettext
391+
self.ngettext = gettext.ngettext
392+
self.pgettext = gettext.pgettext
393+
self.npgettext = gettext.npgettext
394+
395+
396+
class GNUTranslationsWithDomainPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
397+
def setUp(self):
398+
GettextBaseTest.setUp(self)
399+
# Set up the bindings
400+
gettext.bindtextdomain('gettext', os.curdir)
401+
402+
self.gettext = partial(gettext.dgettext, 'gettext')
403+
self.ngettext = partial(gettext.dngettext, 'gettext')
404+
self.pgettext = partial(gettext.dpgettext, 'gettext')
405+
self.npgettext = partial(gettext.dnpgettext, 'gettext')
406+
407+
def test_plural_forms_wrong_domain(self):
408+
self._test_plural_forms(
409+
partial(gettext.dngettext, 'unknown'),
410+
partial(gettext.dgettext, 'unknown'),
411+
'There is %s file', 'There are %s files',
412+
'There is %s file', 'There are %s files',
413+
numbers_only=False)
414+
415+
def test_plural_context_forms_wrong_domain(self):
416+
self._test_plural_forms(
417+
partial(gettext.dnpgettext, 'unknown', 'With context'),
418+
partial(gettext.dpgettext, 'unknown', 'With context'),
419+
'There is %s file', 'There are %s files',
420+
'There is %s file', 'There are %s files',
421+
numbers_only=False)
422+
423+
424+
class GNUTranslationsClassPluralFormsTestCase(PluralFormsTests, GettextBaseTest):
425+
def setUp(self):
426+
GettextBaseTest.setUp(self)
427+
with open(MOFILE, 'rb') as fp:
362428
t = gettext.GNUTranslations(fp)
363-
x = t.npgettext('With context',
364-
'There is %s file', 'There are %s files', 1)
365-
eq(x, 'Hay %s fichero (context)')
366-
x = t.npgettext('With context',
367-
'There is %s file', 'There are %s files', 2)
368-
eq(x, 'Hay %s ficheros (context)')
369-
x = t.pgettext('With context', 'There is %s file')
370-
eq(x, 'Hay %s fichero (context)')
371429

430+
self.gettext = t.gettext
431+
self.ngettext = t.ngettext
432+
self.pgettext = t.pgettext
433+
self.npgettext = t.npgettext
434+
435+
def test_plural_forms_null_translations(self):
436+
t = gettext.NullTranslations()
437+
self._test_plural_forms(
438+
t.ngettext, t.gettext,
439+
'There is %s file', 'There are %s files',
440+
'There is %s file', 'There are %s files',
441+
numbers_only=False)
442+
443+
def test_plural_context_forms_null_translations(self):
444+
t = gettext.NullTranslations()
445+
self._test_plural_forms(
446+
partial(t.npgettext, 'With context'),
447+
partial(t.pgettext, 'With context'),
448+
'There is %s file', 'There are %s files',
449+
'There is %s file', 'There are %s files',
450+
numbers_only=False)
451+
452+
453+
class PluralFormsInternalTestCase:
372454
# Examples from http://www.gnu.org/software/gettext/manual/gettext.html
373455

374456
def test_ja(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Deprecation warning about non-integer number in :mod:`gettext` now alwais
2+
refers to the line in the user code where gettext function or method is
3+
used. Previously it could refer to a line in ``gettext`` code.

0 commit comments

Comments
 (0)