Skip to content

Commit 9384106

Browse files
committed
Merge branch 'main' into superopt
* main: pythongh-103479: [Enum] require __new__ to be considered a data type (pythonGH-103495) pythongh-103365: [Enum] STRICT boundary corrections (pythonGH-103494) pythonGH-103488: Use return-offset, not yield-offset. (pythonGH-103502) pythongh-103088: Fix test_venv error message to avoid bytes/str warning (pythonGH-103500) pythonGH-103082: Turn on branch events for FOR_ITER instructions. (python#103507) pythongh-102978: Fix mock.patch function signatures for class and staticmethod decorators (python#103228) pythongh-103462: Ensure SelectorSocketTransport.writelines registers a writer when data is still pending (python#103463) pythongh-95299: Rework test_cppext.py to not invoke setup.py directly (python#103316)
2 parents 626999d + a6f9594 commit 9384106

26 files changed

+758
-484
lines changed

Doc/howto/enum.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -865,17 +865,19 @@ Some rules:
865865
4. When another data type is mixed in, the :attr:`value` attribute is *not the
866866
same* as the enum member itself, although it is equivalent and will compare
867867
equal.
868-
5. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's
868+
5. A ``data type`` is a mixin that defines :meth:`__new__`, or a
869+
:class:`~dataclasses.dataclass`
870+
6. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's
869871
:meth:`__str__` and :meth:`__repr__` respectively; other codes (such as
870872
``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type.
871-
6. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
873+
7. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
872874
and :func:`format` will use the enum's :meth:`__str__` method.
873875

874876
.. note::
875877

876878
Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are
877879
designed to be drop-in replacements for existing constants, their
878-
:meth:`__str__` method has been reset to their data types
880+
:meth:`__str__` method has been reset to their data types'
879881
:meth:`__str__` method.
880882

881883
When to use :meth:`__new__` vs. :meth:`__init__`

Doc/library/enum.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,8 @@ Data Types
696696

697697
.. attribute:: STRICT
698698

699-
Out-of-range values cause a :exc:`ValueError` to be raised::
699+
Out-of-range values cause a :exc:`ValueError` to be raised. This is the
700+
default for :class:`Flag`::
700701

701702
>>> from enum import Flag, STRICT, auto
702703
>>> class StrictFlag(Flag, boundary=STRICT):
@@ -714,7 +715,7 @@ Data Types
714715
.. attribute:: CONFORM
715716

716717
Out-of-range values have invalid values removed, leaving a valid *Flag*
717-
value. This is the default for :class:`Flag`::
718+
value::
718719

719720
>>> from enum import Flag, CONFORM, auto
720721
>>> class ConformFlag(Flag, boundary=CONFORM):

Include/internal/pycore_frame.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ typedef struct _PyInterpreterFrame {
6161
// over, or (in the case of a newly-created frame) a totally invalid value:
6262
_Py_CODEUNIT *prev_instr;
6363
int stacktop; /* Offset of TOS from localsplus */
64-
uint16_t yield_offset;
64+
/* The return_offset determines where a `RETURN` should go in the caller,
65+
* relative to `prev_instr`.
66+
* It is only meaningful to the callee,
67+
* so it needs to be set in any CALL (to a Python function)
68+
* or SEND (to a coroutine or generator).
69+
* If there is no callee, then it is meaningless. */
70+
uint16_t return_offset;
6571
char owner;
6672
/* Locals and stack */
6773
PyObject *localsplus[1];
@@ -121,7 +127,7 @@ _PyFrame_Initialize(
121127
frame->stacktop = code->co_nlocalsplus;
122128
frame->frame_obj = NULL;
123129
frame->prev_instr = _PyCode_CODE(code) - 1;
124-
frame->yield_offset = 0;
130+
frame->return_offset = 0;
125131
frame->owner = FRAME_OWNED_BY_THREAD;
126132

127133
for (int i = null_locals_from; i < code->co_nlocalsplus; i++) {

Lib/asyncio/selector_events.py

+3
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,9 @@ def writelines(self, list_of_data):
11761176
return
11771177
self._buffer.extend([memoryview(data) for data in list_of_data])
11781178
self._write_ready()
1179+
# If the entire buffer couldn't be written, register a write handler
1180+
if self._buffer:
1181+
self._loop._add_writer(self._sock_fd, self._write_ready)
11791182

11801183
def can_write_eof(self):
11811184
return True

Lib/enum.py

+41-29
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ def __set_name__(self, enum_class, member_name):
275275
enum_member.__objclass__ = enum_class
276276
enum_member.__init__(*args)
277277
enum_member._sort_order_ = len(enum_class._member_names_)
278+
279+
if Flag is not None and issubclass(enum_class, Flag):
280+
enum_class._flag_mask_ |= value
281+
if _is_single_bit(value):
282+
enum_class._singles_mask_ |= value
283+
enum_class._all_bits_ = 2 ** ((enum_class._flag_mask_).bit_length()) - 1
284+
278285
# If another member with the same value was already defined, the
279286
# new member becomes an alias to the existing one.
280287
try:
@@ -532,12 +539,8 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
532539
classdict['_use_args_'] = use_args
533540
#
534541
# convert future enum members into temporary _proto_members
535-
# and record integer values in case this will be a Flag
536-
flag_mask = 0
537542
for name in member_names:
538543
value = classdict[name]
539-
if isinstance(value, int):
540-
flag_mask |= value
541544
classdict[name] = _proto_member(value)
542545
#
543546
# house-keeping structures
@@ -554,8 +557,9 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
554557
boundary
555558
or getattr(first_enum, '_boundary_', None)
556559
)
557-
classdict['_flag_mask_'] = flag_mask
558-
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
560+
classdict['_flag_mask_'] = 0
561+
classdict['_singles_mask_'] = 0
562+
classdict['_all_bits_'] = 0
559563
classdict['_inverted_'] = None
560564
try:
561565
exc = None
@@ -644,21 +648,10 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
644648
):
645649
delattr(enum_class, '_boundary_')
646650
delattr(enum_class, '_flag_mask_')
651+
delattr(enum_class, '_singles_mask_')
647652
delattr(enum_class, '_all_bits_')
648653
delattr(enum_class, '_inverted_')
649654
elif Flag is not None and issubclass(enum_class, Flag):
650-
# ensure _all_bits_ is correct and there are no missing flags
651-
single_bit_total = 0
652-
multi_bit_total = 0
653-
for flag in enum_class._member_map_.values():
654-
flag_value = flag._value_
655-
if _is_single_bit(flag_value):
656-
single_bit_total |= flag_value
657-
else:
658-
# multi-bit flags are considered aliases
659-
multi_bit_total |= flag_value
660-
enum_class._flag_mask_ = single_bit_total
661-
#
662655
# set correct __iter__
663656
member_list = [m._value_ for m in enum_class]
664657
if member_list != sorted(member_list):
@@ -974,6 +967,7 @@ def _find_data_repr_(mcls, class_name, bases):
974967

975968
@classmethod
976969
def _find_data_type_(mcls, class_name, bases):
970+
# a datatype has a __new__ method, or a __dataclass_fields__ attribute
977971
data_types = set()
978972
base_chain = set()
979973
for chain in bases:
@@ -986,7 +980,7 @@ def _find_data_type_(mcls, class_name, bases):
986980
if base._member_type_ is not object:
987981
data_types.add(base._member_type_)
988982
break
989-
elif '__new__' in base.__dict__ or '__init__' in base.__dict__:
983+
elif '__new__' in base.__dict__ or '__dataclass_fields__' in base.__dict__:
990984
data_types.add(candidate or base)
991985
break
992986
else:
@@ -1303,8 +1297,8 @@ def _reduce_ex_by_global_name(self, proto):
13031297
class FlagBoundary(StrEnum):
13041298
"""
13051299
control how out of range values are handled
1306-
"strict" -> error is raised
1307-
"conform" -> extra bits are discarded [default for Flag]
1300+
"strict" -> error is raised [default for Flag]
1301+
"conform" -> extra bits are discarded
13081302
"eject" -> lose flag status
13091303
"keep" -> keep flag status and all bits [default for IntFlag]
13101304
"""
@@ -1315,7 +1309,7 @@ class FlagBoundary(StrEnum):
13151309
STRICT, CONFORM, EJECT, KEEP = FlagBoundary
13161310

13171311

1318-
class Flag(Enum, boundary=CONFORM):
1312+
class Flag(Enum, boundary=STRICT):
13191313
"""
13201314
Support for flags
13211315
"""
@@ -1394,6 +1388,7 @@ def _missing_(cls, value):
13941388
# - value must not include any skipped flags (e.g. if bit 2 is not
13951389
# defined, then 0d10 is invalid)
13961390
flag_mask = cls._flag_mask_
1391+
singles_mask = cls._singles_mask_
13971392
all_bits = cls._all_bits_
13981393
neg_value = None
13991394
if (
@@ -1425,7 +1420,8 @@ def _missing_(cls, value):
14251420
value = all_bits + 1 + value
14261421
# get members and unknown
14271422
unknown = value & ~flag_mask
1428-
member_value = value & flag_mask
1423+
aliases = value & ~singles_mask
1424+
member_value = value & singles_mask
14291425
if unknown and cls._boundary_ is not KEEP:
14301426
raise ValueError(
14311427
'%s(%r) --> unknown values %r [%s]'
@@ -1439,11 +1435,25 @@ def _missing_(cls, value):
14391435
pseudo_member = cls._member_type_.__new__(cls, value)
14401436
if not hasattr(pseudo_member, '_value_'):
14411437
pseudo_member._value_ = value
1442-
if member_value:
1443-
pseudo_member._name_ = '|'.join([
1444-
m._name_ for m in cls._iter_member_(member_value)
1445-
])
1446-
if unknown:
1438+
if member_value or aliases:
1439+
members = []
1440+
combined_value = 0
1441+
for m in cls._iter_member_(member_value):
1442+
members.append(m)
1443+
combined_value |= m._value_
1444+
if aliases:
1445+
value = member_value | aliases
1446+
for n, pm in cls._member_map_.items():
1447+
if pm not in members and pm._value_ and pm._value_ & value == pm._value_:
1448+
members.append(pm)
1449+
combined_value |= pm._value_
1450+
unknown = value ^ combined_value
1451+
pseudo_member._name_ = '|'.join([m._name_ for m in members])
1452+
if not combined_value:
1453+
pseudo_member._name_ = None
1454+
elif unknown and cls._boundary_ is STRICT:
1455+
raise ValueError('%r: no members with value %r' % (cls, unknown))
1456+
elif unknown:
14471457
pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown)
14481458
else:
14491459
pseudo_member._name_ = None
@@ -1675,6 +1685,7 @@ def convert_class(cls):
16751685
body['_boundary_'] = boundary or etype._boundary_
16761686
body['_flag_mask_'] = None
16771687
body['_all_bits_'] = None
1688+
body['_singles_mask_'] = None
16781689
body['_inverted_'] = None
16791690
body['__or__'] = Flag.__or__
16801691
body['__xor__'] = Flag.__xor__
@@ -1750,7 +1761,8 @@ def convert_class(cls):
17501761
else:
17511762
multi_bits |= value
17521763
gnv_last_values.append(value)
1753-
enum_class._flag_mask_ = single_bits
1764+
enum_class._flag_mask_ = single_bits | multi_bits
1765+
enum_class._singles_mask_ = single_bits
17541766
enum_class._all_bits_ = 2 ** ((single_bits|multi_bits).bit_length()) - 1
17551767
# set correct __iter__
17561768
member_list = [m._value_ for m in enum_class]

Lib/test/setup_testcppext.py

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# gh-91321: Build a basic C++ test extension to check that the Python C API is
22
# compatible with C++ and does not emit C++ compiler warnings.
3+
import os
34
import sys
45
from test import support
56

@@ -25,14 +26,8 @@
2526

2627
def main():
2728
cppflags = list(CPPFLAGS)
28-
if '-std=c++03' in sys.argv:
29-
sys.argv.remove('-std=c++03')
30-
std = 'c++03'
31-
name = '_testcpp03ext'
32-
else:
33-
# Python currently targets C++11
34-
std = 'c++11'
35-
name = '_testcpp11ext'
29+
std = os.environ["CPYTHON_TEST_CPP_STD"]
30+
name = os.environ["CPYTHON_TEST_EXT_NAME"]
3631

3732
cppflags = [*CPPFLAGS, f'-std={std}']
3833

1.04 MB
Binary file not shown.

Lib/test/test_asyncio/test_selector_events.py

+42
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,48 @@ def test_write_sendmsg_no_data(self):
747747
self.assertFalse(self.sock.sendmsg.called)
748748
self.assertEqual(list_to_buffer([b'data']), transport._buffer)
749749

750+
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
751+
def test_writelines_sendmsg_full(self):
752+
data = memoryview(b'data')
753+
self.sock.sendmsg = mock.Mock()
754+
self.sock.sendmsg.return_value = len(data)
755+
756+
transport = self.socket_transport(sendmsg=True)
757+
transport.writelines([data])
758+
self.assertTrue(self.sock.sendmsg.called)
759+
self.assertFalse(self.loop.writers)
760+
761+
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
762+
def test_writelines_sendmsg_partial(self):
763+
data = memoryview(b'data')
764+
self.sock.sendmsg = mock.Mock()
765+
self.sock.sendmsg.return_value = 2
766+
767+
transport = self.socket_transport(sendmsg=True)
768+
transport.writelines([data])
769+
self.assertTrue(self.sock.sendmsg.called)
770+
self.assertTrue(self.loop.writers)
771+
772+
def test_writelines_send_full(self):
773+
data = memoryview(b'data')
774+
self.sock.send.return_value = len(data)
775+
self.sock.send.fileno.return_value = 7
776+
777+
transport = self.socket_transport()
778+
transport.writelines([data])
779+
self.assertTrue(self.sock.send.called)
780+
self.assertFalse(self.loop.writers)
781+
782+
def test_writelines_send_partial(self):
783+
data = memoryview(b'data')
784+
self.sock.send.return_value = 2
785+
self.sock.send.fileno.return_value = 7
786+
787+
transport = self.socket_transport()
788+
transport.writelines([data])
789+
self.assertTrue(self.sock.send.called)
790+
self.assertTrue(self.loop.writers)
791+
750792
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
751793
def test_write_sendmsg_full(self):
752794
data = memoryview(b'data')

Lib/test/test_cppext.py

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# gh-91321: Build a basic C++ test extension to check that the Python C API is
22
# compatible with C++ and does not emit C++ compiler warnings.
33
import os.path
4+
import shutil
45
import sys
56
import unittest
67
import subprocess
@@ -39,6 +40,10 @@ def check_build(self, std_cpp03, extension_name):
3940
self._check_build(std_cpp03, extension_name)
4041

4142
def _check_build(self, std_cpp03, extension_name):
43+
pkg_dir = 'pkg'
44+
os.mkdir(pkg_dir)
45+
shutil.copy(SETUP_TESTCPPEXT, os.path.join(pkg_dir, "setup.py"))
46+
4247
venv_dir = 'env'
4348
verbose = support.verbose
4449

@@ -59,11 +64,15 @@ def _check_build(self, std_cpp03, extension_name):
5964
python = os.path.join(venv_dir, 'bin', python_exe)
6065

6166
def run_cmd(operation, cmd):
67+
env = os.environ.copy()
68+
env['CPYTHON_TEST_CPP_STD'] = 'c++03' if std_cpp03 else 'c++11'
69+
env['CPYTHON_TEST_EXT_NAME'] = extension_name
6270
if verbose:
6371
print('Run:', ' '.join(cmd))
64-
subprocess.run(cmd, check=True)
72+
subprocess.run(cmd, check=True, env=env)
6573
else:
6674
proc = subprocess.run(cmd,
75+
env=env,
6776
stdout=subprocess.PIPE,
6877
stderr=subprocess.STDOUT,
6978
text=True)
@@ -72,16 +81,16 @@ def run_cmd(operation, cmd):
7281
self.fail(
7382
f"{operation} failed with exit code {proc.returncode}")
7483

75-
# Build the C++ extension
7684
cmd = [python, '-X', 'dev',
77-
SETUP_TESTCPPEXT, 'build_ext', '--verbose']
78-
if std_cpp03:
79-
cmd.append('-std=c++03')
80-
run_cmd('Build', cmd)
85+
'-m', 'pip', 'install',
86+
support.findfile('setuptools-67.6.1-py3-none-any.whl'),
87+
support.findfile('wheel-0.40.0-py3-none-any.whl')]
88+
run_cmd('Install build dependencies', cmd)
8189

82-
# Install the C++ extension
90+
# Build and install the C++ extension
8391
cmd = [python, '-X', 'dev',
84-
SETUP_TESTCPPEXT, 'install']
92+
'-m', 'pip', 'install', '--no-build-isolation',
93+
os.path.abspath(pkg_dir)]
8594
run_cmd('Install', cmd)
8695

8796
# Do a reference run. Until we test that running python

0 commit comments

Comments
 (0)