Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-110481-inter-thread-queue
Browse files Browse the repository at this point in the history
  • Loading branch information
colesbury committed Feb 9, 2024
2 parents c631856 + 31633f4 commit 8311e18
Show file tree
Hide file tree
Showing 33 changed files with 946 additions and 239 deletions.
5 changes: 3 additions & 2 deletions Doc/c-api/code.rst
Expand Up @@ -22,12 +22,13 @@ bound into a function.
.. c:var:: PyTypeObject PyCode_Type
This is an instance of :c:type:`PyTypeObject` representing the Python
:class:`code` type.
:ref:`code object <code-objects>`.


.. c:function:: int PyCode_Check(PyObject *co)
Return true if *co* is a :class:`code` object. This function always succeeds.
Return true if *co* is a :ref:`code object <code-objects>`.
This function always succeeds.
.. c:function:: int PyCode_GetNumFree(PyCodeObject *co)
Expand Down
17 changes: 15 additions & 2 deletions Doc/library/enum.rst
Expand Up @@ -286,6 +286,19 @@ Data Types
appropriate value will be chosen for you. See :class:`auto` for the
details.

.. attribute:: Enum._name_

Name of the member.

.. attribute:: Enum._value_

Value of the member, can be set in :meth:`~object.__new__`.

.. attribute:: Enum._order_

No longer used, kept for backward compatibility.
(class attribute, removed during class creation).

.. attribute:: Enum._ignore_

``_ignore_`` is only used during creation and is removed from the
Expand Down Expand Up @@ -823,8 +836,8 @@ Supported ``_sunder_`` names
- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a
:class:`str`, that will not be transformed into members, and will be removed
from the final class
- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is
consistent (class attribute, removed during class creation)
- :attr:`~Enum._order_` -- no longer used, kept for backward
compatibility (class attribute, removed during class creation)
- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for
an enum member; may be overridden

Expand Down
1 change: 0 additions & 1 deletion Doc/tools/.nitignore
Expand Up @@ -31,7 +31,6 @@ Doc/library/email.compat32-message.rst
Doc/library/email.errors.rst
Doc/library/email.parser.rst
Doc/library/email.policy.rst
Doc/library/enum.rst
Doc/library/exceptions.rst
Doc/library/faulthandler.rst
Doc/library/fcntl.rst
Expand Down
17 changes: 17 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -101,6 +101,17 @@ Improved Error Messages
variables. See also :ref:`using-on-controlling-color`.
(Contributed by Pablo Galindo Salgado in :gh:`112730`.)

* When an incorrect keyword argument is passed to a function, the error message
now potentially suggests the correct keyword argument.
(Contributed by Pablo Galindo Salgado and Shantanu Jain in :gh:`107944`.)

>>> "better error messages!".split(max_split=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
"better error messages!".split(max_split=1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
TypeError: split() got an unexpected keyword argument 'max_split'. Did you mean 'maxsplit'?

Other Language Changes
======================

Expand Down Expand Up @@ -1328,6 +1339,12 @@ Build Changes
:ref:`limited C API <limited-c-api>`.
(Contributed by Victor Stinner in :gh:`85283`.)

* ``wasm32-wasi`` is now a tier 2 platform.
(Contributed by Brett Cannon in :gh:`115192`.)

* ``wasm32-emscripten`` is no longer a supported platform.
(Contributed by Brett Cannon in :gh:`115192`.)


C API Changes
=============
Expand Down
24 changes: 12 additions & 12 deletions Include/internal/pycore_symtable.h
Expand Up @@ -109,18 +109,18 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name);

/* Flags for def-use information */

#define DEF_GLOBAL 1 /* global stmt */
#define DEF_LOCAL 2 /* assignment in code block */
#define DEF_PARAM 2<<1 /* formal parameter */
#define DEF_NONLOCAL 2<<2 /* nonlocal stmt */
#define USE 2<<3 /* name is used */
#define DEF_FREE 2<<4 /* name used but not defined in nested block */
#define DEF_FREE_CLASS 2<<5 /* free variable from class's method */
#define DEF_IMPORT 2<<6 /* assignment occurred via import */
#define DEF_ANNOT 2<<7 /* this name is annotated */
#define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */
#define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */
#define DEF_COMP_CELL 2<<10 /* this name is a cell in an inlined comprehension */
#define DEF_GLOBAL 1 /* global stmt */
#define DEF_LOCAL 2 /* assignment in code block */
#define DEF_PARAM (2<<1) /* formal parameter */
#define DEF_NONLOCAL (2<<2) /* nonlocal stmt */
#define USE (2<<3) /* name is used */
#define DEF_FREE (2<<4) /* name used but not defined in nested block */
#define DEF_FREE_CLASS (2<<5) /* free variable from class's method */
#define DEF_IMPORT (2<<6) /* assignment occurred via import */
#define DEF_ANNOT (2<<7) /* this name is annotated */
#define DEF_COMP_ITER (2<<8) /* this name is a comprehension iteration variable */
#define DEF_TYPE_PARAM (2<<9) /* this name is a type parameter */
#define DEF_COMP_CELL (2<<10) /* this name is a cell in an inlined comprehension */

#define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT)

Expand Down
2 changes: 1 addition & 1 deletion Lib/pickletools.py
Expand Up @@ -1253,7 +1253,7 @@ def __init__(self, name, code, arg,
stack_before=[],
stack_after=[pyint],
proto=2,
doc="""Long integer using found-byte length.
doc="""Long integer using four-byte length.
A more efficient encoding of a Python long; the long4 encoding
says it all."""),
Expand Down
32 changes: 29 additions & 3 deletions Lib/test/test_call.py
Expand Up @@ -155,7 +155,7 @@ def test_varargs16_kw(self):
min, 0, default=1, key=2, foo=3)

def test_varargs17_kw(self):
msg = r"'foo' is an invalid keyword argument for print\(\)$"
msg = r"print\(\) got an unexpected keyword argument 'foo'$"
self.assertRaisesRegex(TypeError, msg,
print, 0, sep=1, end=2, file=3, flush=4, foo=5)

Expand Down Expand Up @@ -928,7 +928,7 @@ def check_suggestion_includes(self, message):
self.assertIn(f"Did you mean '{message}'?", str(cm.exception))

@contextlib.contextmanager
def check_suggestion_not_pressent(self):
def check_suggestion_not_present(self):
with self.assertRaises(TypeError) as cm:
yield
self.assertNotIn("Did you mean", str(cm.exception))
Expand All @@ -946,7 +946,7 @@ def foo(blech=None, /, aaa=None, *args, late1=None):

for keyword, suggestion in cases:
with self.subTest(keyword):
ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_pressent()
ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_present()
with ctx:
foo(**{keyword:None})

Expand Down Expand Up @@ -987,6 +987,32 @@ def case_change_over_substitution(BLuch=None, Luch = None, fluch = None):
with self.check_suggestion_includes(suggestion):
func(bluch=None)

def test_unexpected_keyword_suggestion_via_getargs(self):
with self.check_suggestion_includes("maxsplit"):
"foo".split(maxsplt=1)

self.assertRaisesRegex(
TypeError, r"split\(\) got an unexpected keyword argument 'blech'$",
"foo".split, blech=1
)
with self.check_suggestion_not_present():
"foo".split(blech=1)
with self.check_suggestion_not_present():
"foo".split(more_noise=1, maxsplt=1)

# Also test the vgetargskeywords path
with self.check_suggestion_includes("name"):
ImportError(namez="oops")

self.assertRaisesRegex(
TypeError, r"ImportError\(\) got an unexpected keyword argument 'blech'$",
ImportError, blech=1
)
with self.check_suggestion_not_present():
ImportError(blech=1)
with self.check_suggestion_not_present():
ImportError(blech=1, namez="oops")

@cpython_only
class TestRecursion(unittest.TestCase):

Expand Down
26 changes: 13 additions & 13 deletions Lib/test/test_capi/test_getargs.py
Expand Up @@ -667,15 +667,15 @@ def test_invalid_keyword(self):
try:
getargs_keywords((1,2),3,arg5=10,arg666=666)
except TypeError as err:
self.assertEqual(str(err), "'arg666' is an invalid keyword argument for this function")
self.assertEqual(str(err), "this function got an unexpected keyword argument 'arg666'")
else:
self.fail('TypeError should have been raised')

def test_surrogate_keyword(self):
try:
getargs_keywords((1,2), 3, (4,(5,6)), (7,8,9), **{'\uDC80': 10})
except TypeError as err:
self.assertEqual(str(err), "'\udc80' is an invalid keyword argument for this function")
self.assertEqual(str(err), "this function got an unexpected keyword argument '\udc80'")
else:
self.fail('TypeError should have been raised')

Expand Down Expand Up @@ -742,12 +742,12 @@ def test_too_many_args(self):
def test_invalid_keyword(self):
# extraneous keyword arg
with self.assertRaisesRegex(TypeError,
"'monster' is an invalid keyword argument for this function"):
"this function got an unexpected keyword argument 'monster'"):
getargs_keyword_only(1, 2, monster=666)

def test_surrogate_keyword(self):
with self.assertRaisesRegex(TypeError,
"'\udc80' is an invalid keyword argument for this function"):
"this function got an unexpected keyword argument '\udc80'"):
getargs_keyword_only(1, 2, **{'\uDC80': 10})

def test_weird_str_subclass(self):
Expand All @@ -761,7 +761,7 @@ def __hash__(self):
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3})
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
"this function got an unexpected keyword argument"):
getargs_keyword_only(1, 2, **{BadStr("monster"): 666})

def test_weird_str_subclass2(self):
Expand All @@ -774,7 +774,7 @@ def __hash__(self):
"invalid keyword argument for this function"):
getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3})
with self.assertRaisesRegex(TypeError,
"invalid keyword argument for this function"):
"this function got an unexpected keyword argument"):
getargs_keyword_only(1, 2, **{BadStr("monster"): 666})


Expand Down Expand Up @@ -807,7 +807,7 @@ def test_required_args(self):

def test_empty_keyword(self):
with self.assertRaisesRegex(TypeError,
"'' is an invalid keyword argument for this function"):
"this function got an unexpected keyword argument ''"):
self.getargs(1, 2, **{'': 666})


Expand Down Expand Up @@ -1204,7 +1204,7 @@ def test_basic(self):
"function missing required argument 'a'"):
parse((), {}, 'O', ['a'])
with self.assertRaisesRegex(TypeError,
"'b' is an invalid keyword argument"):
"this function got an unexpected keyword argument 'b'"):
parse((), {'b': 1}, '|O', ['a'])
with self.assertRaisesRegex(TypeError,
fr"argument for function given by name \('a'\) "
Expand Down Expand Up @@ -1278,10 +1278,10 @@ def test_nonascii_keywords(self):
fr"and position \(1\)"):
parse((1,), {name: 2}, 'O|O', [name, 'b'])
with self.assertRaisesRegex(TypeError,
f"'{name}' is an invalid keyword argument"):
f"this function got an unexpected keyword argument '{name}'"):
parse((), {name: 1}, '|O', ['b'])
with self.assertRaisesRegex(TypeError,
"'b' is an invalid keyword argument"):
"this function got an unexpected keyword argument 'b'"):
parse((), {'b': 1}, '|O', [name])

invalid = name.encode() + (name.encode()[:-1] or b'\x80')
Expand All @@ -1301,17 +1301,17 @@ def test_nonascii_keywords(self):
for name2 in ('b', 'ë', 'ĉ', 'Ɐ', '𐀁'):
with self.subTest(name2=name2):
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
f"this function got an unexpected keyword argument '{name2}'"):
parse((), {name2: 1}, '|O', [name])

name2 = name.encode().decode('latin1')
if name2 != name:
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
f"this function got an unexpected keyword argument '{name2}'"):
parse((), {name2: 1}, '|O', [name])
name3 = name + '3'
with self.assertRaisesRegex(TypeError,
f"'{name2}' is an invalid keyword argument"):
f"this function got an unexpected keyword argument '{name2}'"):
parse((), {name2: 1, name3: 2}, '|OO', [name, name3])

def test_nested_tuple(self):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_exceptions.py
Expand Up @@ -1917,7 +1917,7 @@ def test_attributes(self):
self.assertEqual(exc.name, 'somename')
self.assertEqual(exc.path, 'somepath')

msg = "'invalid' is an invalid keyword argument for ImportError"
msg = r"ImportError\(\) got an unexpected keyword argument 'invalid'"
with self.assertRaisesRegex(TypeError, msg):
ImportError('test', invalid='keyword')

Expand Down
52 changes: 52 additions & 0 deletions Lib/test/test_io.py
Expand Up @@ -2497,6 +2497,28 @@ def test_interleaved_read_write(self):
f.flush()
self.assertEqual(raw.getvalue(), b'a2c')

def test_read1_after_write(self):
with self.BytesIO(b'abcdef') as raw:
with self.tp(raw, 3) as f:
f.write(b"1")
self.assertEqual(f.read1(1), b'b')
f.flush()
self.assertEqual(raw.getvalue(), b'1bcdef')
with self.BytesIO(b'abcdef') as raw:
with self.tp(raw, 3) as f:
f.write(b"1")
self.assertEqual(f.read1(), b'bcd')
f.flush()
self.assertEqual(raw.getvalue(), b'1bcdef')
with self.BytesIO(b'abcdef') as raw:
with self.tp(raw, 3) as f:
f.write(b"1")
# XXX: read(100) returns different numbers of bytes
# in Python and C implementations.
self.assertEqual(f.read1(100)[:3], b'bcd')
f.flush()
self.assertEqual(raw.getvalue(), b'1bcdef')

def test_interleaved_readline_write(self):
with self.BytesIO(b'ab\ncdef\ng\n') as raw:
with self.tp(raw) as f:
Expand All @@ -2509,6 +2531,36 @@ def test_interleaved_readline_write(self):
f.flush()
self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n')

def test_xxx(self):
with self.BytesIO(b'abcdefgh') as raw:
with self.tp(raw) as f:
f.write(b'123')
self.assertEqual(f.read(), b'defgh')
f.write(b'456')
f.flush()
self.assertEqual(raw.getvalue(), b'123defgh456')
with self.BytesIO(b'abcdefgh') as raw:
with self.tp(raw) as f:
f.write(b'123')
self.assertEqual(f.read(3), b'def')
f.write(b'456')
f.flush()
self.assertEqual(raw.getvalue(), b'123def456')
with self.BytesIO(b'abcdefgh') as raw:
with self.tp(raw) as f:
f.write(b'123')
self.assertEqual(f.read1(), b'defgh')
f.write(b'456')
f.flush()
self.assertEqual(raw.getvalue(), b'123defgh456')
with self.BytesIO(b'abcdefgh') as raw:
with self.tp(raw) as f:
f.write(b'123')
self.assertEqual(f.read1(3), b'def')
f.write(b'456')
f.flush()
self.assertEqual(raw.getvalue(), b'123def456')

# You can't construct a BufferedRandom over a non-seekable stream.
test_unseekable = None

Expand Down
@@ -0,0 +1,2 @@
Promote WASI to a tier 2 platform and drop Emscripten from tier 3 in
configure.ac.
@@ -0,0 +1 @@
Avoid vendoring ``vcruntime140_threads.dll`` when building with Visual Studio 2022 version 17.8.
@@ -0,0 +1 @@
Improve error message for function calls with bad keyword arguments via getargs
@@ -0,0 +1 @@
Adapt :class:`set` and :class:`frozenset` methods to Argument Clinic.
@@ -0,0 +1 @@
:meth:`io.BufferedRandom.read1` now flushes the underlying write buffer.
10 changes: 10 additions & 0 deletions Modules/_io/bufferedio.c
Expand Up @@ -1050,6 +1050,16 @@ _io__Buffered_read1_impl(buffered *self, Py_ssize_t n)
Py_DECREF(res);
return NULL;
}
/* Flush the write buffer if necessary */
if (self->writable) {
PyObject *r = buffered_flush_and_rewind_unlocked(self);
if (r == NULL) {
LEAVE_BUFFERED(self)
Py_DECREF(res);
return NULL;
}
Py_DECREF(r);
}
_bufferedreader_reset_buf(self);
r = _bufferedreader_raw_read(self, PyBytes_AS_STRING(res), n);
LEAVE_BUFFERED(self)
Expand Down

0 comments on commit 8311e18

Please sign in to comment.