aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/_pydecimal.py14
-rw-r--r--Lib/fractions.py10
-rw-r--r--Lib/test/test_asyncio/test_base_events.py88
-rw-r--r--Lib/test/test_dbm.py3
-rw-r--r--Lib/test/test_decimal.py13
-rw-r--r--Lib/test/test_fractions.py13
-rw-r--r--Lib/test/test_sqlite3/test_dbapi.py26
-rw-r--r--Lib/test/test_typing.py31
-rw-r--r--Lib/test/typinganndata/fwdref_module.py6
-rw-r--r--Lib/typing.py20
10 files changed, 174 insertions, 50 deletions
diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index 781b38ec26b..9b8e42a2342 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -6122,7 +6122,11 @@ _parse_format_specifier_regex = re.compile(r"""\A
(?P<zeropad>0)?
(?P<minimumwidth>\d+)?
(?P<thousands_sep>[,_])?
-(?:\.(?P<precision>\d+))?
+(?:\.
+ (?=[\d,_]) # lookahead for digit or separator
+ (?P<precision>\d+)?
+ (?P<frac_separators>[,_])?
+)?
(?P<type>[eEfFgGn%])?
\z
""", re.VERBOSE|re.DOTALL)
@@ -6215,6 +6219,9 @@ def _parse_format_specifier(format_spec, _localeconv=None):
format_dict['grouping'] = [3, 0]
format_dict['decimal_point'] = '.'
+ if format_dict['frac_separators'] is None:
+ format_dict['frac_separators'] = ''
+
return format_dict
def _format_align(sign, body, spec):
@@ -6334,6 +6341,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
sign = _format_sign(is_negative, spec)
+ frac_sep = spec['frac_separators']
+ if fracpart and frac_sep:
+ fracpart = frac_sep.join(fracpart[pos:pos + 3]
+ for pos in range(0, len(fracpart), 3))
+
if fracpart or spec['alt']:
fracpart = spec['decimal_point'] + fracpart
diff --git a/Lib/fractions.py b/Lib/fractions.py
index a8c67068522..c1b12e7a1c0 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -170,7 +170,11 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?P<zeropad>0(?=[0-9]))?
(?P<minimumwidth>[0-9]+)?
(?P<thousands_sep>[,_])?
- (?:\.(?P<precision>[0-9]+))?
+ (?:\.
+ (?=[,_0-9]) # lookahead for digit or separator
+ (?P<precision>[0-9]+)?
+ (?P<frac_separators>[,_])?
+ )?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
@@ -499,6 +503,7 @@ class Fraction(numbers.Rational):
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"]
precision = int(match["precision"] or "6")
+ frac_sep = match["frac_separators"] or ""
presentation_type = match["presentation_type"]
trim_zeros = presentation_type in "gG" and not alternate_form
trim_point = not alternate_form
@@ -555,6 +560,9 @@ class Fraction(numbers.Rational):
if trim_zeros:
frac_part = frac_part.rstrip("0")
separator = "" if trim_point and not frac_part else "."
+ if frac_sep:
+ frac_part = frac_sep.join(frac_part[pos:pos + 3]
+ for pos in range(0, len(frac_part), 3))
trailing = separator + frac_part + suffix
# Do zero padding if required.
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
index 12179eb0c9e..22ae0ef3581 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -150,6 +150,29 @@ class BaseEventTests(test_utils.TestCase):
socket.SOCK_STREAM,
socket.IPPROTO_TCP))
+ def test_interleave_addrinfos(self):
+ self.maxDiff = None
+ SIX_A = (socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1))
+ SIX_B = (socket.AF_INET6, 0, 0, '', ('2001:db8::2', 2))
+ SIX_C = (socket.AF_INET6, 0, 0, '', ('2001:db8::3', 3))
+ SIX_D = (socket.AF_INET6, 0, 0, '', ('2001:db8::4', 4))
+ FOUR_A = (socket.AF_INET, 0, 0, '', ('192.0.2.1', 5))
+ FOUR_B = (socket.AF_INET, 0, 0, '', ('192.0.2.2', 6))
+ FOUR_C = (socket.AF_INET, 0, 0, '', ('192.0.2.3', 7))
+ FOUR_D = (socket.AF_INET, 0, 0, '', ('192.0.2.4', 8))
+
+ addrinfos = [SIX_A, SIX_B, SIX_C, FOUR_A, FOUR_B, FOUR_C, FOUR_D, SIX_D]
+ expected = [SIX_A, FOUR_A, SIX_B, FOUR_B, SIX_C, FOUR_C, SIX_D, FOUR_D]
+
+ self.assertEqual(expected, base_events._interleave_addrinfos(addrinfos))
+
+ expected_fafc_2 = [SIX_A, SIX_B, FOUR_A, SIX_C, FOUR_B, SIX_D, FOUR_C, FOUR_D]
+ self.assertEqual(
+ expected_fafc_2,
+ base_events._interleave_addrinfos(addrinfos, first_address_family_count=2),
+ )
+
+
class BaseEventLoopTests(test_utils.TestCase):
@@ -1053,6 +1076,71 @@ class BaseEventLoopTests(test_utils.TestCase):
test_utils.run_briefly(self.loop)
self.assertTrue(status['finalized'])
+ @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'no IPv6 support')
+ @patch_socket
+ def test_create_connection_happy_eyeballs(self, m_socket):
+
+ class MyProto(asyncio.Protocol):
+ pass
+
+ async def getaddrinfo(*args, **kw):
+ return [(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
+ (socket.AF_INET, 0, 0, '', ('192.0.2.1', 5))]
+
+ async def sock_connect(sock, address):
+ if address[0] == '2001:db8::1':
+ await asyncio.sleep(1)
+ sock.connect(address)
+
+ loop = asyncio.new_event_loop()
+ loop._add_writer = mock.Mock()
+ loop._add_writer = mock.Mock()
+ loop._add_reader = mock.Mock()
+ loop.getaddrinfo = getaddrinfo
+ loop.sock_connect = sock_connect
+
+ coro = loop.create_connection(MyProto, 'example.com', 80, happy_eyeballs_delay=0.3)
+ transport, protocol = loop.run_until_complete(coro)
+ try:
+ sock = transport._sock
+ sock.connect.assert_called_with(('192.0.2.1', 5))
+ finally:
+ transport.close()
+ test_utils.run_briefly(loop) # allow transport to close
+ loop.close()
+
+ @patch_socket
+ def test_create_connection_happy_eyeballs_ipv4_only(self, m_socket):
+
+ class MyProto(asyncio.Protocol):
+ pass
+
+ async def getaddrinfo(*args, **kw):
+ return [(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
+ (socket.AF_INET, 0, 0, '', ('192.0.2.2', 6))]
+
+ async def sock_connect(sock, address):
+ if address[0] == '192.0.2.1':
+ await asyncio.sleep(1)
+ sock.connect(address)
+
+ loop = asyncio.new_event_loop()
+ loop._add_writer = mock.Mock()
+ loop._add_writer = mock.Mock()
+ loop._add_reader = mock.Mock()
+ loop.getaddrinfo = getaddrinfo
+ loop.sock_connect = sock_connect
+
+ coro = loop.create_connection(MyProto, 'example.com', 80, happy_eyeballs_delay=0.3)
+ transport, protocol = loop.run_until_complete(coro)
+ try:
+ sock = transport._sock
+ sock.connect.assert_called_with(('192.0.2.2', 6))
+ finally:
+ transport.close()
+ test_utils.run_briefly(loop) # allow transport to close
+ loop.close()
+
class MyProto(asyncio.Protocol):
done = None
diff --git a/Lib/test/test_dbm.py b/Lib/test/test_dbm.py
index 7e8d78b8940..ae9faabd536 100644
--- a/Lib/test/test_dbm.py
+++ b/Lib/test/test_dbm.py
@@ -274,7 +274,8 @@ class WhichDBTestCase(unittest.TestCase):
@unittest.skipUnless(ndbm, reason='Test requires ndbm')
def test_whichdb_ndbm(self):
# Issue 17198: check that ndbm which is referenced in whichdb is defined
- with open(_fname + '.db', 'wb'): pass
+ with open(_fname + '.db', 'wb') as f:
+ f.write(b'spam')
_bytes_fname = os.fsencode(_fname)
fnames = [_fname, os_helper.FakePath(_fname),
_bytes_fname, os_helper.FakePath(_bytes_fname)]
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index ef64b878805..08a8f4c3b36 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -1089,6 +1089,15 @@ class FormatTest:
('07_', '1234.56', '1_234.56'),
('_', '1.23456789', '1.23456789'),
('_%', '123.456789', '12_345.6789%'),
+ # and now for something completely different...
+ ('.,', '1.23456789', '1.234,567,89'),
+ ('._', '1.23456789', '1.234_567_89'),
+ ('.6_f', '12345.23456789', '12345.234_568'),
+ (',._%', '123.456789', '12,345.678_9%'),
+ (',._e', '123456', '1.234_56e+5'),
+ (',.4_e', '123456', '1.234_6e+5'),
+ (',.3_e', '123456', '1.235e+5'),
+ (',._E', '123456', '1.234_56E+5'),
# negative zero: default behavior
('.1f', '-0', '-0.0'),
@@ -1162,6 +1171,10 @@ class FormatTest:
# bytes format argument
self.assertRaises(TypeError, Decimal(1).__format__, b'-020')
+ # precision or fractional part separator should follow after dot
+ self.assertRaises(ValueError, format, Decimal(1), '.f')
+ self.assertRaises(ValueError, format, Decimal(1), '._6f')
+
def test_negative_zero_format_directed_rounding(self):
with self.decimal.localcontext() as ctx:
ctx.rounding = ROUND_CEILING
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 1875a2f529c..cf42b86358d 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -1322,6 +1322,8 @@ class FractionTest(unittest.TestCase):
# Thousands separators
(F('1234567.123456'), ',.5e', '1.23457e+06'),
(F('123.123456'), '012_.2e', '0_001.23e+02'),
+ # Thousands separators for fractional part (or for integral too)
+ (F('1234567.123456'), '.5_e', '1.234_57e+06'),
# z flag is legal, but never makes a difference to the output
(F(-1, 7**100), 'z.6e', '-3.091690e-85'),
]
@@ -1447,6 +1449,12 @@ class FractionTest(unittest.TestCase):
(F('1234567'), ',.2f', '1,234,567.00'),
(F('12345678'), ',.2f', '12,345,678.00'),
(F('12345678'), ',f', '12,345,678.000000'),
+ # Thousands separators for fractional part (or for integral too)
+ (F('123456.789123123'), '._f', '123456.789_123'),
+ (F('123456.789123123'), '.7_f', '123456.789_123_1'),
+ (F('123456.789123123'), '.9_f', '123456.789_123_123'),
+ (F('123456.789123123'), '.,f', '123456.789,123'),
+ (F('123456.789123123'), '_.,f', '123_456.789,123'),
# Underscore as thousands separator
(F(2, 3), '_.2f', '0.67'),
(F(2, 3), '_.7f', '0.6666667'),
@@ -1620,6 +1628,11 @@ class FractionTest(unittest.TestCase):
'.f',
'.g',
'.%',
+ # Thousands separators before precision
+ '._6e',
+ '._6f',
+ '._6g',
+ '._6%',
# Z instead of z for negative zero suppression
'Z.2f'
# z flag not supported for general formatting
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
index 291e0356253..3602726437d 100644
--- a/Lib/test/test_sqlite3/test_dbapi.py
+++ b/Lib/test/test_sqlite3/test_dbapi.py
@@ -31,8 +31,7 @@ import urllib.parse
import warnings
from test.support import (
- SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess,
- is_apple, is_emscripten, is_wasi
+ SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess
)
from test.support import gc_collect
from test.support import threading_helper, import_helper
@@ -641,14 +640,21 @@ class OpenTests(unittest.TestCase):
self.assertTrue(os.path.exists(path))
cx.execute(self._sql)
+ def get_undecodable_path(self):
+ path = TESTFN_UNDECODABLE
+ if not path:
+ self.skipTest("only works if there are undecodable paths")
+ try:
+ open(path, 'wb').close()
+ except OSError:
+ self.skipTest(f"can't create file with undecodable path {path!r}")
+ unlink(path)
+ return path
+
@unittest.skipIf(sys.platform == "win32", "skipped on Windows")
- @unittest.skipIf(is_apple, "skipped on Apple platforms")
- @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
- @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
def test_open_with_undecodable_path(self):
- path = TESTFN_UNDECODABLE
+ path = self.get_undecodable_path()
self.addCleanup(unlink, path)
- self.assertFalse(os.path.exists(path))
with contextlib.closing(sqlite.connect(path)) as cx:
self.assertTrue(os.path.exists(path))
cx.execute(self._sql)
@@ -688,14 +694,10 @@ class OpenTests(unittest.TestCase):
cx.execute(self._sql)
@unittest.skipIf(sys.platform == "win32", "skipped on Windows")
- @unittest.skipIf(is_apple, "skipped on Apple platforms")
- @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI")
- @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths")
def test_open_undecodable_uri(self):
- path = TESTFN_UNDECODABLE
+ path = self.get_undecodable_path()
self.addCleanup(unlink, path)
uri = "file:" + urllib.parse.quote(path)
- self.assertFalse(os.path.exists(path))
with contextlib.closing(sqlite.connect(uri, uri=True)) as cx:
self.assertTrue(os.path.exists(path))
cx.execute(self._sql)
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index bef6773ad6c..b1615bbff38 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -6309,31 +6309,6 @@ class NoTypeCheckTests(BaseTestCase):
class InternalsTests(BaseTestCase):
- def test_deprecation_for_no_type_params_passed_to__evaluate(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- (
- "Failing to pass a value to the 'type_params' parameter "
- "of 'typing._eval_type' is deprecated"
- )
- ) as cm:
- self.assertEqual(typing._eval_type(list["int"], globals(), {}), list[int])
-
- self.assertEqual(cm.filename, __file__)
-
- f = ForwardRef("int")
-
- with self.assertWarnsRegex(
- DeprecationWarning,
- (
- "Failing to pass a value to the 'type_params' parameter "
- "of 'typing.ForwardRef._evaluate' is deprecated"
- )
- ) as cm:
- self.assertIs(f._evaluate(globals(), {}, recursive_guard=frozenset()), int)
-
- self.assertEqual(cm.filename, __file__)
-
def test_collect_parameters(self):
typing = import_helper.import_fresh_module("typing")
with self.assertWarnsRegex(
@@ -7351,6 +7326,12 @@ class EvaluateForwardRefTests(BaseTestCase):
list[EqualToForwardRef('A')],
)
+ def test_with_module(self):
+ from test.typinganndata import fwdref_module
+
+ typing.evaluate_forward_ref(
+ fwdref_module.fw,)
+
class CollectionsAbcTests(BaseTestCase):
diff --git a/Lib/test/typinganndata/fwdref_module.py b/Lib/test/typinganndata/fwdref_module.py
new file mode 100644
index 00000000000..7347a7a4245
--- /dev/null
+++ b/Lib/test/typinganndata/fwdref_module.py
@@ -0,0 +1,6 @@
+from typing import ForwardRef
+
+MyList = list[int]
+MyDict = dict[str, 'MyList']
+
+fw = ForwardRef('MyDict', module=__name__)
diff --git a/Lib/typing.py b/Lib/typing.py
index 27105838a0a..f1455c273d3 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -437,20 +437,14 @@ class _Sentinel:
return '<sentinel>'
-_sentinel = _Sentinel()
-
-
-def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
- format=None, owner=None):
+def _eval_type(t, globalns, localns, type_params, *, recursive_guard=frozenset(),
+ format=None, owner=None, parent_fwdref=None):
"""Evaluate all forward references in the given type t.
For use of globalns and localns see the docstring for get_type_hints().
recursive_guard is used to prevent infinite recursion with a recursive
ForwardRef.
"""
- if type_params is _sentinel:
- _deprecation_warning_for_no_type_params_passed("typing._eval_type")
- type_params = ()
if isinstance(t, _lazy_annotationlib.ForwardRef):
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
# from the module, and it will probably pick better than the globals we have here.
@@ -462,7 +456,7 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
if isinstance(t, (_GenericAlias, GenericAlias, Union)):
if isinstance(t, GenericAlias):
args = tuple(
- _make_forward_ref(arg) if isinstance(arg, str) else arg
+ _make_forward_ref(arg, parent_fwdref=parent_fwdref) if isinstance(arg, str) else arg
for arg in t.__args__
)
else:
@@ -942,7 +936,12 @@ def TypeIs(self, parameters):
return _GenericAlias(self, (item,))
-def _make_forward_ref(code, **kwargs):
+def _make_forward_ref(code, *, parent_fwdref=None, **kwargs):
+ if parent_fwdref is not None:
+ if parent_fwdref.__forward_module__ is not None:
+ kwargs['module'] = parent_fwdref.__forward_module__
+ if parent_fwdref.__owner__ is not None:
+ kwargs['owner'] = parent_fwdref.__owner__
forward_ref = _lazy_annotationlib.ForwardRef(code, **kwargs)
# For compatibility, eagerly compile the forwardref's code.
forward_ref.__forward_code__
@@ -1007,6 +1006,7 @@ def evaluate_forward_ref(
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
format=format,
owner=owner,
+ parent_fwdref=forward_ref,
)