Skip to content

Commit

Permalink
Pull in main
Browse files Browse the repository at this point in the history
  • Loading branch information
erlend-aasland committed Jan 3, 2025
2 parents 35e1dac + 4ed36d6 commit a91d36b
Show file tree
Hide file tree
Showing 28 changed files with 264 additions and 151 deletions.
4 changes: 1 addition & 3 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@

# Create table of contents entries for domain objects (e.g. functions, classes,
# attributes, etc.). Default is True.
toc_object_entries = True
# Hide parents to tidy up long entries in sidebar
toc_object_entries_show_parents = 'hide'
toc_object_entries = False

# Ignore any .rst files in the includes/ directory;
# they're embedded in pages but not rendered as individual pages.
Expand Down
6 changes: 4 additions & 2 deletions Doc/howto/free-threading-extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ Most of the C API is thread-safe, but there are some exceptions.

* **Struct Fields**: Accessing fields in Python C API objects or structs
directly is not thread-safe if the field may be concurrently modified.
* **Macros**: Accessor macros like :c:macro:`PyList_GET_ITEM` and
:c:macro:`PyList_SET_ITEM` do not perform any error checking or locking.
* **Macros**: Accessor macros like :c:macro:`PyList_GET_ITEM`,
:c:macro:`PyList_SET_ITEM`, and macros like
:c:macro:`PySequence_Fast_GET_SIZE` that use the object returned by
:c:func:`PySequence_Fast` do not perform any error checking or locking.
These macros are not thread-safe if the container object may be modified
concurrently.
* **Borrowed References**: C API functions that return
Expand Down
27 changes: 26 additions & 1 deletion Doc/library/calendar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,32 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is

:class:`TextCalendar` instances have the following methods:


.. method:: formatday(theday, weekday, width)

Return a string representing a single day formatted with the given *width*.
If *theday* is ``0``, return a string of spaces of
the specified width, representing an empty day. The *weekday* parameter
is unused.

.. method:: formatweek(theweek, w=0)

Return a single week in a string with no newline. If *w* is provided, it
specifies the width of the date columns, which are centered. Depends
on the first weekday as specified in the constructor or set by the
:meth:`setfirstweekday` method.

.. method:: formatweekday(weekday, width)

Return a string representing the name of a single weekday formatted to
the specified *width*. The *weekday* parameter is an integer representing
the day of the week, where ``0`` is Monday and ``6`` is Sunday.

.. method:: formatweekheader(width)

Return a string containing the header row of weekday names, formatted
with the given *width* for each column. The names depend on the locale
settings and are padded to the specified width.

.. method:: formatmonth(theyear, themonth, w=0, l=0)

Expand All @@ -154,6 +173,12 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is
on the first weekday as specified in the constructor or set by the
:meth:`setfirstweekday` method.

.. method:: formatmonthname(theyear, themonth, width=0, withyear=True)

Return a string representing the month's name centered within the
specified *width*. If *withyear* is ``True``, include the year in the
output. The *theyear* and *themonth* parameters specify the year
and month for the name to be formatted respectively.

.. method:: prmonth(theyear, themonth, w=0, l=0)

Expand Down Expand Up @@ -445,7 +470,7 @@ The :mod:`calendar` module exports the following data attributes:

A sequence that represents the months of the year in the current locale. This
follows normal convention of January being month number 1, so it has a length of
13 and ``month_name[0]`` is the empty string.
13 and ``month_name[0]`` is the empty string.

>>> import calendar
>>> list(calendar.month_name)
Expand Down
7 changes: 7 additions & 0 deletions Doc/library/pyexpat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,13 @@ The ``errors`` module has the following attributes:
has been breached.


.. data:: XML_ERROR_NOT_STARTED

The parser was tried to be stopped or suspended before it started.

.. versionadded:: next


.. rubric:: Footnotes

.. [1] The encoding string included in XML output should conform to the
Expand Down
1 change: 0 additions & 1 deletion Doc/tools/extensions/pyspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,5 @@ def setup(app):
app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod)
app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod)
app.add_directive('miscnews', MiscNews)
app.add_css_file('sidebar-wrap.css')
app.connect('env-check-consistency', patch_pairindextypes)
return {'version': '1.0', 'parallel_read_safe': True}
6 changes: 0 additions & 6 deletions Doc/tools/static/sidebar-wrap.css

This file was deleted.

10 changes: 7 additions & 3 deletions Lib/asyncio/selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,13 @@ def _accept_connection(
logger.debug("%r got a new connection from %r: %r",
server, addr, conn)
conn.setblocking(False)
except (BlockingIOError, InterruptedError, ConnectionAbortedError):
# Early exit because the socket accept buffer is empty.
return None
except ConnectionAbortedError:
# Discard connections that were aborted before accept().
continue
except (BlockingIOError, InterruptedError):
# Early exit because of a signal or
# the socket accept buffer is empty.
return
except OSError as exc:
# There's nowhere to send the error, so just log it.
if exc.errno in (errno.EMFILE, errno.ENFILE,
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_asyncio/test_selector_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,31 @@ def test_accept_connection_multiple(self):
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog)

def test_accept_connection_skip_connectionabortederror(self):
sock = mock.Mock()

def mock_sock_accept():
# mock accept(2) returning -ECONNABORTED every-other
# time that it's called. This applies most to OpenBSD
# whose sockets generate this errno more reproducibly than
# Linux and other OS.
if sock.accept.call_count % 2 == 0:
raise ConnectionAbortedError
return (mock.Mock(), mock.Mock())

sock.accept.side_effect = mock_sock_accept
backlog = 100
# test that _accept_connection's loop calls sock.accept
# all 100 times, continuing past ConnectionAbortedError
# instead of unnecessarily returning early
mock_obj = mock.patch.object
with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
self.loop._accept_connection(
mock.Mock(), sock, backlog=backlog)
# as in test_accept_connection_multiple avoid task pending
# warnings by using asyncio.sleep(0)
self.loop.run_until_complete(asyncio.sleep(0))
self.assertEqual(sock.accept.call_count, backlog)

class SelectorTransportTests(test_utils.TestCase):

Expand Down
123 changes: 64 additions & 59 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Python test set -- built-in functions

import ast
import asyncio
import builtins
import collections
import contextlib
Expand Down Expand Up @@ -31,7 +30,8 @@
from types import AsyncGeneratorType, FunctionType, CellType
from operator import neg
from test import support
from test.support import (cpython_only, swap_attr, maybe_get_event_loop_policy)
from test.support import cpython_only, swap_attr
from test.support import async_yield, run_yielding_async_fn
from test.support.import_helper import import_module
from test.support.os_helper import (EnvironmentVarGuard, TESTFN, unlink)
from test.support.script_helper import assert_python_ok
Expand Down Expand Up @@ -414,10 +414,6 @@ def test_compile_top_level_await_no_coro(self):
msg=f"source={source} mode={mode}")


@unittest.skipIf(
support.is_emscripten or support.is_wasi,
"socket.accept is broken"
)
def test_compile_top_level_await(self):
"""Test whether code with top level await can be compiled.
Expand All @@ -432,30 +428,42 @@ async def arange(n):
for i in range(n):
yield i

class Lock:
async def __aenter__(self):
return self

async def __aexit__(self, *exc_info):
pass

async def sleep(delay, result=None):
assert delay == 0
await async_yield(None)
return result

modes = ('single', 'exec')
optimizations = (-1, 0, 1, 2)
code_samples = [
'''a = await asyncio.sleep(0, result=1)''',
'''a = await sleep(0, result=1)''',
'''async for i in arange(1):
a = 1''',
'''async with asyncio.Lock() as l:
'''async with Lock() as l:
a = 1''',
'''a = [x async for x in arange(2)][1]''',
'''a = 1 in {x async for x in arange(2)}''',
'''a = {x:1 async for x in arange(1)}[0]''',
'''a = [x async for x in arange(2) async for x in arange(2)][1]''',
'''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await asyncio.sleep(0, x) async for x in arange(2)][1]''',
'''a = [await sleep(0, x) async for x in arange(2)][1]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await asyncio.sleep(0); a = 1''',
'''assert not await sleep(0); a = 1''',
'''assert [x async for x in arange(1)]; a = 1''',
'''assert {x async for x in arange(1)}; a = 1''',
'''assert {x: x async for x in arange(1)}; a = 1''',
'''
if (a := 1) and __debug__:
async with asyncio.Lock() as l:
async with Lock() as l:
pass
''',
'''
Expand All @@ -464,43 +472,45 @@ async def arange(n):
pass
''',
]
policy = maybe_get_event_loop_policy()
try:
for mode, code_sample, optimize in product(modes, code_samples, optimizations):
with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode, optimize=optimize)

co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
optimize=optimize)

self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
msg=f"source={source} mode={mode}")

# test we can create and advance a function type
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
async_f = FunctionType(co, globals_)
asyncio.run(async_f())
self.assertEqual(globals_['a'], 1)

# test we can await-eval,
globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
asyncio.run(eval(co, globals_))
self.assertEqual(globals_['a'], 1)
finally:
asyncio._set_event_loop_policy(policy)
for mode, code_sample, optimize in product(modes, code_samples, optimizations):
with self.subTest(mode=mode, code_sample=code_sample, optimize=optimize):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode, optimize=optimize)

co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
optimize=optimize)

self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
msg=f"source={source} mode={mode}")

# test we can create and advance a function type
globals_ = {'Lock': Lock, 'a': 0, 'arange': arange, 'sleep': sleep}
run_yielding_async_fn(FunctionType(co, globals_))
self.assertEqual(globals_['a'], 1)

# test we can await-eval,
globals_ = {'Lock': Lock, 'a': 0, 'arange': arange, 'sleep': sleep}
run_yielding_async_fn(lambda: eval(co, globals_))
self.assertEqual(globals_['a'], 1)

def test_compile_top_level_await_invalid_cases(self):
# helper function just to check we can run top=level async-for
async def arange(n):
for i in range(n):
yield i

class Lock:
async def __aenter__(self):
return self

async def __aexit__(self, *exc_info):
pass

modes = ('single', 'exec')
code_samples = [
'''def f(): await arange(10)\n''',
Expand All @@ -511,27 +521,22 @@ async def arange(n):
a = 1
''',
'''def f():
async with asyncio.Lock() as l:
async with Lock() as l:
a = 1
'''
]
policy = maybe_get_event_loop_policy()
try:
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode)

with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
finally:
asyncio._set_event_loop_policy(policy)
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
compile(source, '?', mode)

with self.assertRaises(
SyntaxError, msg=f"source={source} mode={mode}"):
co = compile(source,
'?',
mode,
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)

def test_compile_async_generator(self):
"""
Expand All @@ -542,7 +547,7 @@ def test_compile_async_generator(self):
code = dedent("""async def ticker():
for i in range(10):
yield i
await asyncio.sleep(0)""")
await sleep(0)""")

co = compile(code, '?', 'exec', flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
glob = {}
Expand Down
Loading

0 comments on commit a91d36b

Please sign in to comment.