From 438cbd857a875efc105b4215b1ae131e67af37e1 Mon Sep 17 00:00:00 2001 From: Dzmitry Plashchynski Date: Thu, 31 Jul 2025 15:06:33 +0300 Subject: [PATCH 1/3] gh-131146: Fix month names in a "standalone form" in calendar module (GH-131147) The calendar module displays month names in some locales using the genitive case. This is grammatically incorrect, as the nominative case should be used when the month is named by itself. To address this issue, this change introduces new lists `standalone_month_name` and `standalone_month_abbr` that contain month names in the nominative case -- or more generally, in the form that should be used to name the month itself, rather than form a date. The module now uses the `%OB` format specifier to get month names in this form where available. --- Doc/library/calendar.rst | 33 +++++++++++++++++ Lib/calendar.py | 21 ++++++++--- Lib/test/test_calendar.py | 36 ++++++++++++++++++- ...-03-17-21-21-06.gh-issue-131146.A5Obgv.rst | 6 ++++ 4 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index b292d828841f2f..fd397547a04437 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -501,6 +501,14 @@ The :mod:`calendar` module exports the following data attributes: >>> list(calendar.month_name) ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] + .. caution:: + + In locales with alternative month names forms, the :data:`!month_name` sequence + may not be suitable when a month name stands by itself and not as part of a date. + For instance, in Greek and in many Slavic and Baltic languages, :data:`!month_name` + will produce the month in genitive case. Use :data:`standalone_month_name` for a form + suitable for standalone use. + .. data:: month_abbr @@ -512,6 +520,31 @@ The :mod:`calendar` module exports the following data attributes: >>> list(calendar.month_abbr) ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + .. caution:: + + In locales with alternative month names forms, the :data:`!month_abbr` sequence + may not be suitable when a month name stands by itself and not as part of a date. + Use :data:`standalone_month_abbr` for a form suitable for standalone use. + + +.. data:: standalone_month_name + + A sequence that represents the months of the year in the current locale + in the standalone form if the locale provides one. Else it is equivalent + to :data:`month_name`. + + .. versionadded:: next + + +.. data:: standalone_month_abbr + + A sequence that represents the abbreviated months of the year in the current + locale in the standalone form if the locale provides one. Else it is + equivalent to :data:`month_abbr`. + + .. versionadded:: next + + .. data:: JANUARY FEBRUARY MARCH diff --git a/Lib/calendar.py b/Lib/calendar.py index 3be1b50500eb07..45bb265a65602c 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -14,8 +14,9 @@ __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", "firstweekday", "isleap", "leapdays", "weekday", "monthrange", "monthcalendar", "prmonth", "month", "prcal", "calendar", - "timegm", "month_name", "month_abbr", "day_name", "day_abbr", - "Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", + "timegm", "month_name", "month_abbr", "standalone_month_name", + "standalone_month_abbr", "day_name", "day_abbr", "Calendar", + "TextCalendar", "HTMLCalendar", "LocaleTextCalendar", "LocaleHTMLCalendar", "weekheader", "Day", "Month", "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", @@ -139,6 +140,16 @@ def __len__(self): month_name = _localized_month('%B') month_abbr = _localized_month('%b') +# On platforms that support the %OB and %Ob specifiers, they are used +# to get the standalone form of the month name. This is required for +# some languages such as Greek, Slavic, and Baltic languages. +try: + standalone_month_name = _localized_month('%OB') + standalone_month_abbr = _localized_month('%Ob') +except ValueError: + standalone_month_name = month_name + standalone_month_abbr = month_abbr + def isleap(year): """Return True for leap years, False for non-leap years.""" @@ -377,7 +388,7 @@ def formatmonthname(self, theyear, themonth, width, withyear=True): """ _validate_month(themonth) - s = month_name[themonth] + s = standalone_month_name[themonth] if withyear: s = "%s %r" % (s, theyear) return s.center(width) @@ -510,9 +521,9 @@ def formatmonthname(self, theyear, themonth, withyear=True): """ _validate_month(themonth) if withyear: - s = '%s %s' % (month_name[themonth], theyear) + s = '%s %s' % (standalone_month_name[themonth], theyear) else: - s = '%s' % month_name[themonth] + s = standalone_month_name[themonth] return '%s' % ( self.cssclass_month_head, s) diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index bc39c86b8cf62d..589a21baf7bd61 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -8,6 +8,7 @@ import io import locale import os +import platform import sys import time @@ -546,7 +547,8 @@ def test_days(self): self.assertEqual(value[::-1], list(reversed(value))) def test_months(self): - for attr in "month_name", "month_abbr": + for attr in ("month_name", "month_abbr", "standalone_month_name", + "standalone_month_abbr"): value = getattr(calendar, attr) self.assertEqual(len(value), 13) self.assertEqual(len(value[:]), 13) @@ -556,6 +558,38 @@ def test_months(self): # verify it "acts like a sequence" in two forms of iteration self.assertEqual(value[::-1], list(reversed(value))) + @support.run_with_locale('LC_ALL', 'pl_PL') + @unittest.skipUnless(sys.platform == 'darwin' or platform.libc_ver()[0] == 'glibc', + "Guaranteed to work with glibc and macOS") + def test_standalone_month_name_and_abbr_pl_locale(self): + expected_standalone_month_names = [ + "", "styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", + "lipiec", "sierpień", "wrzesień", "październik", "listopad", + "grudzień" + ] + expected_standalone_month_abbr = [ + "", "sty", "lut", "mar", "kwi", "maj", "cze", + "lip", "sie", "wrz", "paź", "lis", "gru" + ] + self.assertEqual( + list(calendar.standalone_month_name), + expected_standalone_month_names + ) + self.assertEqual( + list(calendar.standalone_month_abbr), + expected_standalone_month_abbr + ) + + def test_standalone_month_name_and_abbr_C_locale(self): + # Ensure that the standalone month names and abbreviations are + # equal to the regular month names and abbreviations for + # the "C" locale. + with calendar.different_locale("C"): + self.assertListEqual(list(calendar.month_name), + list(calendar.standalone_month_name)) + self.assertListEqual(list(calendar.month_abbr), + list(calendar.standalone_month_abbr)) + def test_locale_text_calendar(self): try: cal = calendar.LocaleTextCalendar(locale='') diff --git a/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst b/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst new file mode 100644 index 00000000000000..6d8bc0959aed52 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-17-21-21-06.gh-issue-131146.A5Obgv.rst @@ -0,0 +1,6 @@ +Fix :class:`calendar.TextCalendar`, :class:`calendar.HTMLCalendar`, +and the :mod:`calendar` CLI to display month names in the nominative +case by adding :data:`calendar.standalone_month_name` and +:data:`calendar.standalone_month_abbr`, which provide month names and +abbreviations in the grammatical form used when a month name stands by +itself, if the locale supports it. From d18f73ae1349ed005fa05ea2d852e1ab51dbc087 Mon Sep 17 00:00:00 2001 From: Xuanteng Huang <44627253+xuantengh@users.noreply.github.com> Date: Thu, 31 Jul 2025 21:22:22 +0800 Subject: [PATCH 2/3] gh-137200: support frame lineno setter with `BRANCH_LEFT` and `BRANCH_RIGHT` events (GH-137229) --- Lib/test/test_monitoring.py | 22 ++++++++++++++++++++++ Objects/frameobject.c | 2 ++ 2 files changed, 24 insertions(+) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index a932ac80117d27..4122f786a9afb0 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -3,6 +3,7 @@ import collections import dis import functools +import inspect import math import operator import sys @@ -1709,6 +1710,27 @@ def func(v=1): ('branch right', 'func', 6, 8), ('branch right', 'func', 2, 10)]) + def test_callback_set_frame_lineno(self): + def func(s: str) -> int: + if s.startswith("t"): + return 1 + else: + return 0 + + def callback(code, from_, to): + # try set frame.f_lineno + frame = inspect.currentframe() + while frame and frame.f_code is not code: + frame = frame.f_back + + self.assertIsNotNone(frame) + frame.f_lineno = frame.f_lineno + 1 # run next instruction + + sys.monitoring.set_local_events(TEST_TOOL, func.__code__, E.BRANCH_LEFT) + sys.monitoring.register_callback(TEST_TOOL, E.BRANCH_LEFT, callback) + + self.assertEqual(func("true"), 1) + class TestBranchConsistency(MonitoringTestBase, unittest.TestCase): diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 97de1e06efe1b2..72c0ab0666e927 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1685,6 +1685,8 @@ frame_lineno_set_impl(PyFrameObject *self, PyObject *value) case PY_MONITORING_EVENT_PY_RESUME: case PY_MONITORING_EVENT_JUMP: case PY_MONITORING_EVENT_BRANCH: + case PY_MONITORING_EVENT_BRANCH_LEFT: + case PY_MONITORING_EVENT_BRANCH_RIGHT: case PY_MONITORING_EVENT_LINE: case PY_MONITORING_EVENT_PY_YIELD: /* Setting f_lineno is allowed for the above events */ From 2a87af062b79d914ce0120f1f1763213c1ebe8c4 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 31 Jul 2025 12:17:27 -0400 Subject: [PATCH 3/3] gh-137282: Fix `TypeError` in tab completion and `dir()` of `concurrent.futures` (GH-137214) Signed-off-by: Henry Schreiner --- Lib/concurrent/futures/__init__.py | 2 +- Lib/test/test___all__.py | 2 ++ .../next/Library/2025-07-31-10-31-56.gh-issue-137282.GOCwIC.rst | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-31-10-31-56.gh-issue-137282.GOCwIC.rst diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index e717222cf98b32..d6ac4b3e0b675f 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -44,7 +44,7 @@ def __dir__(): - return __all__ + ('__author__', '__doc__') + return __all__ + ['__author__', '__doc__'] def __getattr__(name): diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py index f35b1194308262..8ded9f99248372 100644 --- a/Lib/test/test___all__.py +++ b/Lib/test/test___all__.py @@ -72,6 +72,8 @@ def check_all(self, modname): all_set = set(all_list) self.assertCountEqual(all_set, all_list, "in module {}".format(modname)) self.assertEqual(keys, all_set, "in module {}".format(modname)) + # Verify __dir__ is non-empty and doesn't produce an error + self.assertTrue(dir(sys.modules[modname])) def walk_modules(self, basedir, modpath): for fn in sorted(os.listdir(basedir)): diff --git a/Misc/NEWS.d/next/Library/2025-07-31-10-31-56.gh-issue-137282.GOCwIC.rst b/Misc/NEWS.d/next/Library/2025-07-31-10-31-56.gh-issue-137282.GOCwIC.rst new file mode 100644 index 00000000000000..78f169ea029b17 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-31-10-31-56.gh-issue-137282.GOCwIC.rst @@ -0,0 +1 @@ +Fix tab completion and :func:`dir` on :mod:`concurrent.futures`.