Skip to content

Commit 5742a33

Browse files
authored
Merge branch 'main' into fix-issue-95371
2 parents d7bfc33 + ef63cca commit 5742a33

File tree

25 files changed

+691
-623
lines changed

25 files changed

+691
-623
lines changed

Doc/library/asyncio-task.rst

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,14 +1067,59 @@ Scheduling From Other Threads
10671067
This function is meant to be called from a different OS thread
10681068
than the one where the event loop is running. Example::
10691069

1070-
# Create a coroutine
1071-
coro = asyncio.sleep(1, result=3)
1072-
1073-
# Submit the coroutine to a given loop
1074-
future = asyncio.run_coroutine_threadsafe(coro, loop)
1075-
1076-
# Wait for the result with an optional timeout argument
1077-
assert future.result(timeout) == 3
1070+
def in_thread(loop: asyncio.AbstractEventLoop) -> None:
1071+
# Run some blocking IO
1072+
pathlib.Path("example.txt").write_text("hello world", encoding="utf8")
1073+
1074+
# Create a coroutine
1075+
coro = asyncio.sleep(1, result=3)
1076+
1077+
# Submit the coroutine to a given loop
1078+
future = asyncio.run_coroutine_threadsafe(coro, loop)
1079+
1080+
# Wait for the result with an optional timeout argument
1081+
assert future.result(timeout=2) == 3
1082+
1083+
async def amain() -> None:
1084+
# Get the running loop
1085+
loop = asyncio.get_running_loop()
1086+
1087+
# Run something in a thread
1088+
await asyncio.to_thread(in_thread, loop)
1089+
1090+
It's also possible to run the other way around. Example::
1091+
1092+
@contextlib.contextmanager
1093+
def loop_in_thread() -> Generator[asyncio.AbstractEventLoop]:
1094+
loop_fut = concurrent.futures.Future[asyncio.AbstractEventLoop]()
1095+
stop_event = asyncio.Event()
1096+
1097+
async def main() -> None:
1098+
loop_fut.set_result(asyncio.get_running_loop())
1099+
await stop_event.wait()
1100+
1101+
with concurrent.futures.ThreadPoolExecutor(1) as tpe:
1102+
complete_fut = tpe.submit(asyncio.run, main())
1103+
for fut in concurrent.futures.as_completed((loop_fut, complete_fut)):
1104+
if fut is loop_fut:
1105+
loop = loop_fut.result()
1106+
try:
1107+
yield loop
1108+
finally:
1109+
loop.call_soon_threadsafe(stop_event.set)
1110+
else:
1111+
fut.result()
1112+
1113+
# Create a loop in another thread
1114+
with loop_in_thread() as loop:
1115+
# Create a coroutine
1116+
coro = asyncio.sleep(1, result=3)
1117+
1118+
# Submit the coroutine to a given loop
1119+
future = asyncio.run_coroutine_threadsafe(coro, loop)
1120+
1121+
# Wait for the result with an optional timeout argument
1122+
assert future.result(timeout=2) == 3
10781123

10791124
If an exception is raised in the coroutine, the returned Future
10801125
will be notified. It can also be used to cancel the task in

Doc/library/urllib.request.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,9 @@ The following classes are provided:
411411
:ref:`http-password-mgr` for information on the interface that must be
412412
supported.
413413

414+
.. versionchanged:: 3.14
415+
Added support for HTTP digest authentication algorithm ``SHA-256``.
416+
414417

415418
.. class:: HTTPDigestAuthHandler(password_mgr=None)
416419

Doc/library/zipfile.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ The module defines the following items:
8484
formerly protected :attr:`!_compresslevel`. The older protected name
8585
continues to work as a property for backwards compatibility.
8686

87+
88+
.. method:: _for_archive(archive)
89+
90+
Resolve the date_time, compression attributes, and external attributes
91+
to suitable defaults as used by :meth:`ZipFile.writestr`.
92+
93+
Returns self for chaining.
94+
95+
.. versionadded:: 3.14
96+
97+
8798
.. function:: is_zipfile(filename)
8899

89100
Returns ``True`` if *filename* is a valid ZIP file based on its magic number,

Doc/whatsnew/3.14.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,29 @@ unittest
646646
(Contributed by Jacob Walls in :gh:`80958`.)
647647

648648

649+
urllib
650+
------
651+
652+
* Upgrade HTTP digest authentication algorithm for :mod:`urllib.request` by
653+
supporting SHA-256 digest authentication as specified in :rfc:`7616`.
654+
(Contributed by Calvin Bui in :gh:`128193`.)
655+
656+
649657
uuid
650658
----
651659

652660
* Add support for UUID version 8 via :func:`uuid.uuid8` as specified
653661
in :rfc:`9562`.
654662
(Contributed by Bénédikt Tran in :gh:`89083`.)
655663

664+
zipinfo
665+
-------
666+
667+
* Added :func:`ZipInfo._for_archive <zipfile.ZipInfo._for_archive>`
668+
to resolve suitable defaults for a :class:`~zipfile.ZipInfo` object
669+
as used by :func:`ZipFile.writestr <zipfile.ZipFile.writestr>`.
670+
671+
(Contributed by Bénédikt Tran in :gh:`123424`.)
656672

657673
.. Add improved modules above alphabetically, not here at the end.
658674

InternalDocs/parser.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ an input string as its argument, and yields one of the following results:
5656

5757
Note that "failure" results do not imply that the program is incorrect, nor do
5858
they necessarily mean that the parsing has failed. Since the choice operator is
59-
ordered, a failure very often merely indicates "try the following option". A
59+
ordered, a failure very often merely indicates "try the following option". A
6060
direct implementation of a PEG parser as a recursive descent parser will present
6161
exponential time performance in the worst case, because PEG parsers have
6262
infinite lookahead (this means that they can consider an arbitrary number of
@@ -253,7 +253,7 @@ inside curly-braces, which specifies the return value of the alternative:
253253
If the action is omitted, a default action is generated:
254254

255255
- If there is a single name in the rule, it gets returned.
256-
- If there multiple names in the rule, a collection with all parsed
256+
- If there are multiple names in the rule, a collection with all parsed
257257
expressions gets returned (the type of the collection will be different
258258
in C and Python).
259259

@@ -447,7 +447,7 @@ parser (the one used by the interpreter) just execute:
447447
$ make regen-pegen
448448
```
449449

450-
using the `Makefile` in the main directory. If you are on Windows you can
450+
using the `Makefile` in the main directory. If you are on Windows you can
451451
use the Visual Studio project files to regenerate the parser or to execute:
452452

453453
```dos
@@ -539,7 +539,7 @@ memoization is used.
539539
The C parser used by Python is highly optimized and memoization can be expensive
540540
both in memory and time. Although the memory cost is obvious (the parser needs
541541
memory for storing previous results in the cache) the execution time cost comes
542-
for continuously checking if the given rule has a cache hit or not. In many
542+
from continuously checking if the given rule has a cache hit or not. In many
543543
situations, just parsing it again can be faster. Pegen **disables memoization
544544
by default** except for rules with the special marker `memo` after the rule
545545
name (and type, if present):
@@ -605,7 +605,7 @@ File "<stdin>", line 1
605605
SyntaxError: invalid syntax
606606
```
607607

608-
While soft keywords don't have this limitation if used in a context other the
608+
While soft keywords don't have this limitation if used in a context other than
609609
one where they are defined as keywords:
610610

611611
```pycon

Lib/pathlib/_abc.py

Lines changed: 4 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import posixpath
1717
from errno import EINVAL
1818
from glob import _GlobberBase, _no_recurse_symlinks
19-
from stat import S_ISDIR, S_ISLNK, S_ISREG
2019
from pathlib._os import copyfileobj
2120

2221

@@ -206,21 +205,6 @@ def __str__(self):
206205
passing to system calls."""
207206
raise NotImplementedError
208207

209-
def as_posix(self):
210-
"""Return the string representation of the path with forward (/)
211-
slashes."""
212-
return str(self).replace(self.parser.sep, '/')
213-
214-
@property
215-
def drive(self):
216-
"""The drive prefix (letter or UNC path), if any."""
217-
return self.parser.splitdrive(self.anchor)[0]
218-
219-
@property
220-
def root(self):
221-
"""The root of the path, if any."""
222-
return self.parser.splitdrive(self.anchor)[1]
223-
224208
@property
225209
def anchor(self):
226210
"""The concatenation of the drive and root, or ''."""
@@ -292,51 +276,6 @@ def with_suffix(self, suffix):
292276
else:
293277
return self.with_name(stem + suffix)
294278

295-
def relative_to(self, other, *, walk_up=False):
296-
"""Return the relative path to another path identified by the passed
297-
arguments. If the operation is not possible (because this is not
298-
related to the other path), raise ValueError.
299-
300-
The *walk_up* parameter controls whether `..` may be used to resolve
301-
the path.
302-
"""
303-
if not isinstance(other, PurePathBase):
304-
other = self.with_segments(other)
305-
anchor0, parts0 = _explode_path(self)
306-
anchor1, parts1 = _explode_path(other)
307-
if anchor0 != anchor1:
308-
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
309-
while parts0 and parts1 and parts0[-1] == parts1[-1]:
310-
parts0.pop()
311-
parts1.pop()
312-
for part in parts1:
313-
if not part or part == '.':
314-
pass
315-
elif not walk_up:
316-
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
317-
elif part == '..':
318-
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
319-
else:
320-
parts0.append('..')
321-
return self.with_segments(*reversed(parts0))
322-
323-
def is_relative_to(self, other):
324-
"""Return True if the path is relative to another path or False.
325-
"""
326-
if not isinstance(other, PurePathBase):
327-
other = self.with_segments(other)
328-
anchor0, parts0 = _explode_path(self)
329-
anchor1, parts1 = _explode_path(other)
330-
if anchor0 != anchor1:
331-
return False
332-
while parts0 and parts1 and parts0[-1] == parts1[-1]:
333-
parts0.pop()
334-
parts1.pop()
335-
for part in parts1:
336-
if part and part != '.':
337-
return False
338-
return True
339-
340279
@property
341280
def parts(self):
342281
"""An object providing sequence-like access to the
@@ -388,11 +327,6 @@ def parents(self):
388327
parent = split(path)[0]
389328
return tuple(parents)
390329

391-
def is_absolute(self):
392-
"""True if the path is absolute (has both a root and, if applicable,
393-
a drive)."""
394-
return self.parser.isabs(str(self))
395-
396330
def match(self, path_pattern, *, case_sensitive=None):
397331
"""
398332
Return True if this path matches the given pattern. If the pattern is
@@ -450,55 +384,33 @@ class PathBase(PurePathBase):
450384
"""
451385
__slots__ = ()
452386

453-
def stat(self, *, follow_symlinks=True):
454-
"""
455-
Return the result of the stat() system call on this path, like
456-
os.stat() does.
457-
"""
458-
raise NotImplementedError
459-
460-
# Convenience functions for querying the stat results
461-
462387
def exists(self, *, follow_symlinks=True):
463388
"""
464389
Whether this path exists.
465390
466391
This method normally follows symlinks; to check whether a symlink exists,
467392
add the argument follow_symlinks=False.
468393
"""
469-
try:
470-
self.stat(follow_symlinks=follow_symlinks)
471-
except (OSError, ValueError):
472-
return False
473-
return True
394+
raise NotImplementedError
474395

475396
def is_dir(self, *, follow_symlinks=True):
476397
"""
477398
Whether this path is a directory.
478399
"""
479-
try:
480-
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
481-
except (OSError, ValueError):
482-
return False
400+
raise NotImplementedError
483401

484402
def is_file(self, *, follow_symlinks=True):
485403
"""
486404
Whether this path is a regular file (also True for symlinks pointing
487405
to regular files).
488406
"""
489-
try:
490-
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
491-
except (OSError, ValueError):
492-
return False
407+
raise NotImplementedError
493408

494409
def is_symlink(self):
495410
"""
496411
Whether this path is a symbolic link.
497412
"""
498-
try:
499-
return S_ISLNK(self.stat(follow_symlinks=False).st_mode)
500-
except (OSError, ValueError):
501-
return False
413+
raise NotImplementedError
502414

503415
def open(self, mode='r', buffering=-1, encoding=None,
504416
errors=None, newline=None):

Lib/pathlib/_local.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from errno import *
88
from glob import _StringGlobber, _no_recurse_symlinks
99
from itertools import chain
10-
from stat import S_IMODE, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
10+
from stat import S_IMODE, S_ISDIR, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
1111
from _collections_abc import Sequence
1212

1313
try:
@@ -437,6 +437,11 @@ def _parse_pattern(cls, pattern):
437437
parts.append('')
438438
return parts
439439

440+
def as_posix(self):
441+
"""Return the string representation of the path with forward (/)
442+
slashes."""
443+
return str(self).replace(self.parser.sep, '/')
444+
440445
@property
441446
def _raw_path(self):
442447
paths = self._raw_paths
@@ -725,7 +730,10 @@ def is_dir(self, *, follow_symlinks=True):
725730
"""
726731
if follow_symlinks:
727732
return os.path.isdir(self)
728-
return PathBase.is_dir(self, follow_symlinks=follow_symlinks)
733+
try:
734+
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
735+
except (OSError, ValueError):
736+
return False
729737

730738
def is_file(self, *, follow_symlinks=True):
731739
"""
@@ -734,7 +742,10 @@ def is_file(self, *, follow_symlinks=True):
734742
"""
735743
if follow_symlinks:
736744
return os.path.isfile(self)
737-
return PathBase.is_file(self, follow_symlinks=follow_symlinks)
745+
try:
746+
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
747+
except (OSError, ValueError):
748+
return False
738749

739750
def is_mount(self):
740751
"""

Lib/pathlib/_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,5 @@ class Parser(Protocol):
1515

1616
sep: str
1717
def split(self, path: str) -> tuple[str, str]: ...
18-
def splitdrive(self, path: str) -> tuple[str, str]: ...
1918
def splitext(self, path: str) -> tuple[str, str]: ...
2019
def normcase(self, path: str) -> str: ...
21-
def isabs(self, path: str) -> bool: ...

0 commit comments

Comments
 (0)