Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
97 changes: 52 additions & 45 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,53 +57,60 @@ def literal_eval(node_or_string):
Caution: A complex expression can overflow the C stack and cause a crash.
"""
if isinstance(node_or_string, str):
node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval')
if isinstance(node_or_string, Expression):
node_or_string = parse(node_or_string.lstrip(" \t"), mode='eval').body
elif isinstance(node_or_string, Expression):
node_or_string = node_or_string.body
def _raise_malformed_node(node):
msg = "malformed node or string"
if lno := getattr(node, 'lineno', None):
msg += f' on line {lno}'
raise ValueError(msg + f': {node!r}')
def _convert_num(node):
if not isinstance(node, Constant) or type(node.value) not in (int, float, complex):
_raise_malformed_node(node)
return _convert_literal(node_or_string)


def _convert_literal(node):
"""
Used by `literal_eval` to convert an AST node into a value.
"""
if isinstance(node, Constant):
return node.value
def _convert_signed_num(node):
if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
operand = _convert_num(node.operand)
if isinstance(node.op, UAdd):
return + operand
else:
return - operand
return _convert_num(node)
def _convert(node):
if isinstance(node, Constant):
return node.value
elif isinstance(node, Tuple):
return tuple(map(_convert, node.elts))
elif isinstance(node, List):
return list(map(_convert, node.elts))
elif isinstance(node, Set):
return set(map(_convert, node.elts))
elif (isinstance(node, Call) and isinstance(node.func, Name) and
node.func.id == 'set' and node.args == node.keywords == []):
return set()
elif isinstance(node, Dict):
if len(node.keys) != len(node.values):
_raise_malformed_node(node)
return dict(zip(map(_convert, node.keys),
map(_convert, node.values)))
elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
left = _convert_signed_num(node.left)
right = _convert_num(node.right)
if isinstance(left, (int, float)) and isinstance(right, complex):
if isinstance(node.op, Add):
return left + right
else:
return left - right
return _convert_signed_num(node)
return _convert(node_or_string)
if isinstance(node, Dict) and len(node.keys) == len(node.values):
return dict(zip(
map(_convert_literal, node.keys),
map(_convert_literal, node.values),
))
if isinstance(node, Tuple):
return tuple(map(_convert_literal, node.elts))
if isinstance(node, List):
return list(map(_convert_literal, node.elts))
if isinstance(node, Set):
return set(map(_convert_literal, node.elts))
if (
isinstance(node, Call) and isinstance(node.func, Name)
and node.func.id == 'set' and node.args == node.keywords == []
):
return set()
if (
isinstance(node, UnaryOp)
and isinstance(node.op, (UAdd, USub))
and isinstance(node.operand, Constant)
and type(operand := node.operand.value) in (int, float, complex)
):
if isinstance(node.op, UAdd):
return + operand
else:
return - operand
if (
isinstance(node, BinOp)
and isinstance(node.op, (Add, Sub))
and isinstance(node.left, (Constant, UnaryOp))
and isinstance(node.right, Constant)
and type(left := _convert_literal(node.left)) in (int, float)
and type(right := _convert_literal(node.right)) is complex
):
if isinstance(node.op, Add):
return left + right
else:
return left - right
msg = "malformed node or string"
if lno := getattr(node, 'lineno', None):
msg += f' on line {lno}'
raise ValueError(msg + f': {node!r}')


def dump(
Expand Down
21 changes: 16 additions & 5 deletions Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 '<tr><th colspan="7" class="%s">%s</th></tr>' % (
self.cssclass_month_head, s)

Expand Down
7 changes: 6 additions & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,12 @@ def has_no_debug_ranges():
return not bool(config['code_debug_ranges'])

def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
return unittest.skipIf(has_no_debug_ranges(), reason)
try:
skip = has_no_debug_ranges()
except unittest.SkipTest as e:
skip = True
reason = e.args[0] if e.args else reason
return unittest.skipIf(skip, reason)


MS_WINDOWS = (sys.platform == 'win32')
Expand Down
36 changes: 35 additions & 1 deletion Lib/test/test_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io
import locale
import os
import platform
import sys
import time

Expand Down Expand Up @@ -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)
Expand All @@ -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='')
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import collections
import dis
import functools
import inspect
import math
import operator
import sys
Expand Down Expand Up @@ -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):

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`ast.literal_eval` is 10-20% faster for small inputs.
2 changes: 2 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading