]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-110519: Improve deprecation warning in the gettext module (#110520)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 9 Oct 2023 13:45:22 +0000 (16:45 +0300)
committerGitHub <noreply@github.com>
Mon, 9 Oct 2023 13:45:22 +0000 (15:45 +0200)
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().

Lib/gettext.py
Lib/test/test_gettext.py
Misc/NEWS.d/next/Library/2023-10-08-18-15-02.gh-issue-110519.RDGe8-.rst [new file with mode: 0644]

index b72b15f82d43552162cc322ca69df57f14451f1b..e84765bfdf064981b9cd598af319ba8a55857c10 100644 (file)
@@ -46,6 +46,7 @@ internationalized, to the local language and cultural habits.
 #   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):
index 2650ce501c309d759f24b85b76e3b8acc211749a..dd33b9b88f6768771a7b3da9be056b137ed6911c 100644 (file)
@@ -2,6 +2,7 @@ import os
 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 @@ class GettextBaseTest(unittest.TestCase):
             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 @@ fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
 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')
-
-    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:
-            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:
+        self.gettext = gettext.gettext
+        self.ngettext = gettext.ngettext
+        self.pgettext = gettext.pgettext
+        self.npgettext = gettext.npgettext
+
+
+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.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):
diff --git a/Misc/NEWS.d/next/Library/2023-10-08-18-15-02.gh-issue-110519.RDGe8-.rst b/Misc/NEWS.d/next/Library/2023-10-08-18-15-02.gh-issue-110519.RDGe8-.rst
new file mode 100644 (file)
index 0000000..8ff9167
--- /dev/null
@@ -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.