Skip to content

Commit 5a6bd3f

Browse files
authored
Merge branch 'main' into pythongh-100479-add-makepath
2 parents e75dedc + 4307fea commit 5a6bd3f

29 files changed

+272
-1812
lines changed

.gitattributes

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ Lib/test/test_importlib/resources/data01/* noeol
3232
Lib/test/test_importlib/resources/namespacedata01/* noeol
3333
Lib/test/xmltestdata/* noeol
3434

35+
# Shell scripts should have LF even on Windows because of Cygwin
36+
Lib/venv/scripts/common/activate text eol=lf
37+
3538
# CRLF files
3639
[attr]dos text eol=crlf
3740

.github/CODEOWNERS

+1-5
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@ Python/traceback.c @iritkatriel
6161
/Tools/build/parse_html5_entities.py @ezio-melotti
6262

6363
# Import (including importlib).
64-
# Ignoring importlib.h so as to not get flagged on
65-
# all pull requests that change the emitted
66-
# bytecode.
67-
**/*import*.c @brettcannon @encukou @ericsnowcurrently @ncoghlan @warsaw
68-
**/*import*.py @brettcannon @encukou @ericsnowcurrently @ncoghlan @warsaw
64+
**/*import* @brettcannon @encukou @ericsnowcurrently @ncoghlan @warsaw
6965
**/*importlib/resources/* @jaraco @warsaw @FFY00
7066
**/importlib/metadata/* @jaraco @warsaw
7167

Doc/library/csv.rst

+20-2
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ The :mod:`csv` module defines the following constants:
327327

328328
Instructs :class:`writer` objects to quote all non-numeric fields.
329329

330-
Instructs the reader to convert all non-quoted fields to type *float*.
330+
Instructs :class:`reader` objects to convert all non-quoted fields to type *float*.
331331

332332

333333
.. data:: QUOTE_NONE
@@ -337,7 +337,25 @@ The :mod:`csv` module defines the following constants:
337337
character. If *escapechar* is not set, the writer will raise :exc:`Error` if
338338
any characters that require escaping are encountered.
339339

340-
Instructs :class:`reader` to perform no special processing of quote characters.
340+
Instructs :class:`reader` objects to perform no special processing of quote characters.
341+
342+
.. data:: QUOTE_NOTNULL
343+
344+
Instructs :class:`writer` objects to quote all fields which are not
345+
``None``. This is similar to :data:`QUOTE_ALL`, except that if a
346+
field value is ``None`` an empty (unquoted) string is written.
347+
348+
Instructs :class:`reader` objects to interpret an empty (unquoted) field as None and
349+
to otherwise behave as :data:`QUOTE_ALL`.
350+
351+
.. data:: QUOTE_STRINGS
352+
353+
Instructs :class:`writer` objects to always place quotes around fields
354+
which are strings. This is similar to :data:`QUOTE_NONNUMERIC`, except that if a
355+
field value is ``None`` an empty (unquoted) string is written.
356+
357+
Instructs :class:`reader` objects to interpret an empty (unquoted) string as ``None`` and
358+
to otherwise behave as :data:`QUOTE_NONNUMERIC`.
341359

342360
The :mod:`csv` module defines the following exception:
343361

Doc/whatsnew/3.12.rst

+7
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ asyncio
253253
* :func:`asyncio.wait` now accepts generators yielding tasks.
254254
(Contributed by Kumar Aditya in :gh:`78530`.)
255255

256+
csv
257+
---
258+
259+
* Add :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` flags to
260+
provide finer grained control of ``None`` and empty strings by
261+
:class:`~csv.reader` and :class:`~csv.writer` objects.
262+
256263
inspect
257264
-------
258265

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/csv.py

+2
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
unregister_dialect, get_dialect, list_dialects, \
1010
field_size_limit, \
1111
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
12+
QUOTE_STRINGS, QUOTE_NOTNULL, \
1213
__doc__
1314
from _csv import Dialect as _Dialect
1415

1516
from io import StringIO
1617

1718
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
19+
"QUOTE_STRINGS", "QUOTE_NOTNULL",
1820
"Error", "Dialect", "__doc__", "excel", "excel_tab",
1921
"field_size_limit", "reader", "writer",
2022
"register_dialect", "get_dialect", "list_dialects", "Sniffer",

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

Lib/test/test_csv.py

+4
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ def test_write_quoting(self):
187187
quoting = csv.QUOTE_ALL)
188188
self._write_test(['a\nb',1], '"a\nb","1"',
189189
quoting = csv.QUOTE_ALL)
190+
self._write_test(['a','',None,1], '"a","",,1',
191+
quoting = csv.QUOTE_STRINGS)
192+
self._write_test(['a','',None,1], '"a","",,"1"',
193+
quoting = csv.QUOTE_NOTNULL)
190194

191195
def test_write_escape(self):
192196
self._write_test(['a',1,'p,q'], 'a,1,"p,q"',

Lib/test/test_monitoring.py

+71
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,77 @@ def func3():
10111011
('line', 'func3', 6)])
10121012

10131013

1014+
def line_from_offset(code, offset):
1015+
for start, end, line in code.co_lines():
1016+
if start <= offset < end:
1017+
return line - code.co_firstlineno
1018+
return -1
1019+
1020+
class JumpRecorder:
1021+
1022+
event_type = E.JUMP
1023+
name = "jump"
1024+
1025+
def __init__(self, events):
1026+
self.events = events
1027+
1028+
def __call__(self, code, from_, to):
1029+
from_line = line_from_offset(code, from_)
1030+
to_line = line_from_offset(code, to)
1031+
self.events.append((self.name, code.co_name, from_line, to_line))
1032+
1033+
1034+
class BranchRecorder(JumpRecorder):
1035+
1036+
event_type = E.BRANCH
1037+
name = "branch"
1038+
1039+
1040+
JUMP_AND_BRANCH_RECORDERS = JumpRecorder, BranchRecorder
1041+
JUMP_BRANCH_AND_LINE_RECORDERS = JumpRecorder, BranchRecorder, LineRecorder
1042+
1043+
class TestBranchAndJumpEvents(CheckEvents):
1044+
maxDiff = None
1045+
1046+
def test_loop(self):
1047+
1048+
def func():
1049+
x = 1
1050+
for a in range(2):
1051+
if a:
1052+
x = 4
1053+
else:
1054+
x = 6
1055+
1056+
self.check_events(func, recorders = JUMP_AND_BRANCH_RECORDERS, expected = [
1057+
('branch', 'func', 2, 2),
1058+
('branch', 'func', 3, 6),
1059+
('jump', 'func', 6, 2),
1060+
('branch', 'func', 2, 2),
1061+
('branch', 'func', 3, 4),
1062+
('jump', 'func', 4, 2),
1063+
('branch', 'func', 2, 2)])
1064+
1065+
1066+
self.check_events(func, recorders = JUMP_BRANCH_AND_LINE_RECORDERS, expected = [
1067+
('line', 'check_events', 10),
1068+
('line', 'func', 1),
1069+
('line', 'func', 2),
1070+
('branch', 'func', 2, 2),
1071+
('line', 'func', 3),
1072+
('branch', 'func', 3, 6),
1073+
('line', 'func', 6),
1074+
('jump', 'func', 6, 2),
1075+
('branch', 'func', 2, 2),
1076+
('line', 'func', 3),
1077+
('branch', 'func', 3, 4),
1078+
('line', 'func', 4),
1079+
('jump', 'func', 4, 2),
1080+
('branch', 'func', 2, 2),
1081+
('line', 'func', 2),
1082+
('line', 'check_events', 11)])
1083+
1084+
10141085
class TestSetGetEvents(MonitoringTestBase, unittest.TestCase):
10151086

10161087
def test_global(self):

Lib/test/test_unittest/testmock/testhelpers.py

+18
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,24 @@ def __getattr__(self, attribute):
952952
self.assertFalse(hasattr(autospec, '__name__'))
953953

954954

955+
def test_autospec_signature_staticmethod(self):
956+
class Foo:
957+
@staticmethod
958+
def static_method(a, b=10, *, c): pass
959+
960+
mock = create_autospec(Foo.__dict__['static_method'])
961+
self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock))
962+
963+
964+
def test_autospec_signature_classmethod(self):
965+
class Foo:
966+
@classmethod
967+
def class_method(cls, a, b=10, *, c): pass
968+
969+
mock = create_autospec(Foo.__dict__['class_method'])
970+
self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock))
971+
972+
955973
def test_spec_inspect_signature(self):
956974

957975
def myfunc(x, y): pass

Lib/test/test_unittest/testmock/testpatch.py

+30
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,36 @@ def test_autospec_classmethod(self):
996996
method.assert_called_once_with()
997997

998998

999+
def test_autospec_staticmethod_signature(self):
1000+
# Patched methods which are decorated with @staticmethod should have the same signature
1001+
class Foo:
1002+
@staticmethod
1003+
def static_method(a, b=10, *, c): pass
1004+
1005+
Foo.static_method(1, 2, c=3)
1006+
1007+
with patch.object(Foo, 'static_method', autospec=True) as method:
1008+
method(1, 2, c=3)
1009+
self.assertRaises(TypeError, method)
1010+
self.assertRaises(TypeError, method, 1)
1011+
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
1012+
1013+
1014+
def test_autospec_classmethod_signature(self):
1015+
# Patched methods which are decorated with @classmethod should have the same signature
1016+
class Foo:
1017+
@classmethod
1018+
def class_method(cls, a, b=10, *, c): pass
1019+
1020+
Foo.class_method(1, 2, c=3)
1021+
1022+
with patch.object(Foo, 'class_method', autospec=True) as method:
1023+
method(1, 2, c=3)
1024+
self.assertRaises(TypeError, method)
1025+
self.assertRaises(TypeError, method, 1)
1026+
self.assertRaises(TypeError, method, 1, 2, 3, c=4)
1027+
1028+
9991029
def test_autospec_with_new(self):
10001030
patcher = patch('%s.function' % __name__, new=3, autospec=True)
10011031
self.assertRaises(TypeError, patcher.start)

0 commit comments

Comments
 (0)