Skip to content

Commit

Permalink
Merge branch 'main' into 3.14-calendar-colour
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Dec 30, 2024
2 parents ad2921c + 6dbace3 commit e31133f
Show file tree
Hide file tree
Showing 22 changed files with 667 additions and 578 deletions.
61 changes: 53 additions & 8 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1067,14 +1067,59 @@ Scheduling From Other Threads
This function is meant to be called from a different OS thread
than the one where the event loop is running. Example::

# Create a coroutine
coro = asyncio.sleep(1, result=3)

# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)

# Wait for the result with an optional timeout argument
assert future.result(timeout) == 3
def in_thread(loop: asyncio.AbstractEventLoop) -> None:
# Run some blocking IO
pathlib.Path("example.txt").write_text("hello world", encoding="utf8")

# Create a coroutine
coro = asyncio.sleep(1, result=3)

# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)

# Wait for the result with an optional timeout argument
assert future.result(timeout=2) == 3

async def amain() -> None:
# Get the running loop
loop = asyncio.get_running_loop()

# Run something in a thread
await asyncio.to_thread(in_thread, loop)

It's also possible to run the other way around. Example::

@contextlib.contextmanager
def loop_in_thread() -> Generator[asyncio.AbstractEventLoop]:
loop_fut = concurrent.futures.Future[asyncio.AbstractEventLoop]()
stop_event = asyncio.Event()

async def main() -> None:
loop_fut.set_result(asyncio.get_running_loop())
await stop_event.wait()

with concurrent.futures.ThreadPoolExecutor(1) as tpe:
complete_fut = tpe.submit(asyncio.run, main())
for fut in concurrent.futures.as_completed((loop_fut, complete_fut)):
if fut is loop_fut:
loop = loop_fut.result()
try:
yield loop
finally:
loop.call_soon_threadsafe(stop_event.set)
else:
fut.result()

# Create a loop in another thread
with loop_in_thread() as loop:
# Create a coroutine
coro = asyncio.sleep(1, result=3)

# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)

# Wait for the result with an optional timeout argument
assert future.result(timeout=2) == 3

If an exception is raised in the coroutine, the returned Future
will be notified. It can also be used to cancel the task in
Expand Down
11 changes: 11 additions & 0 deletions Doc/library/zipfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ The module defines the following items:
formerly protected :attr:`!_compresslevel`. The older protected name
continues to work as a property for backwards compatibility.


.. method:: _for_archive(archive)

Resolve the date_time, compression attributes, and external attributes
to suitable defaults as used by :meth:`ZipFile.writestr`.

Returns self for chaining.

.. versionadded:: 3.14


.. function:: is_zipfile(filename)

Returns ``True`` if *filename* is a valid ZIP file based on its magic number,
Expand Down
12 changes: 10 additions & 2 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -627,8 +627,8 @@ sys
sys.monitoring
--------------

Two new events are added: :monitoring-event:`BRANCH_LEFT` and
:monitoring-event:`BRANCH_RIGHT`. The ``BRANCH`` event is deprecated.
* Two new events are added: :monitoring-event:`BRANCH_LEFT` and
:monitoring-event:`BRANCH_RIGHT`. The ``BRANCH`` event is deprecated.

tkinter
-------
Expand Down Expand Up @@ -674,6 +674,14 @@ uuid
in :rfc:`9562`.
(Contributed by Bénédikt Tran in :gh:`89083`.)

zipinfo
-------

* Added :func:`ZipInfo._for_archive <zipfile.ZipInfo._for_archive>`
to resolve suitable defaults for a :class:`~zipfile.ZipInfo` object
as used by :func:`ZipFile.writestr <zipfile.ZipFile.writestr>`.

(Contributed by Bénédikt Tran in :gh:`123424`.)

.. Add improved modules above alphabetically, not here at the end.
Expand Down
27 changes: 9 additions & 18 deletions Lib/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ def copy(x):

cls = type(x)

copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
if cls in _copy_atomic_types:
return x
if cls in _copy_builtin_containers:
return cls.copy(x)


if issubclass(cls, type):
# treat it as a regular class:
return _copy_immutable(x)
return x

copier = getattr(cls, "__copy__", None)
if copier is not None:
Expand All @@ -98,23 +100,12 @@ def copy(x):
return _reconstruct(x, None, *rv)


_copy_dispatch = d = {}

def _copy_immutable(x):
return x
for t in (types.NoneType, int, float, bool, complex, str, tuple,
_copy_atomic_types = {types.NoneType, int, float, bool, complex, str, tuple,
bytes, frozenset, type, range, slice, property,
types.BuiltinFunctionType, types.EllipsisType,
types.NotImplementedType, types.FunctionType, types.CodeType,
weakref.ref, super):
d[t] = _copy_immutable

d[list] = list.copy
d[dict] = dict.copy
d[set] = set.copy
d[bytearray] = bytearray.copy

del d, t
weakref.ref, super}
_copy_builtin_containers = {list, dict, set, bytearray}

def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects.
Expand Down
96 changes: 4 additions & 92 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import posixpath
from errno import EINVAL
from glob import _GlobberBase, _no_recurse_symlinks
from stat import S_ISDIR, S_ISLNK, S_ISREG
from pathlib._os import copyfileobj


Expand Down Expand Up @@ -206,21 +205,6 @@ def __str__(self):
passing to system calls."""
raise NotImplementedError

def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
return str(self).replace(self.parser.sep, '/')

@property
def drive(self):
"""The drive prefix (letter or UNC path), if any."""
return self.parser.splitdrive(self.anchor)[0]

@property
def root(self):
"""The root of the path, if any."""
return self.parser.splitdrive(self.anchor)[1]

@property
def anchor(self):
"""The concatenation of the drive and root, or ''."""
Expand Down Expand Up @@ -292,51 +276,6 @@ def with_suffix(self, suffix):
else:
return self.with_name(stem + suffix)

def relative_to(self, other, *, walk_up=False):
"""Return the relative path to another path identified by the passed
arguments. If the operation is not possible (because this is not
related to the other path), raise ValueError.
The *walk_up* parameter controls whether `..` may be used to resolve
the path.
"""
if not isinstance(other, PurePathBase):
other = self.with_segments(other)
anchor0, parts0 = _explode_path(self)
anchor1, parts1 = _explode_path(other)
if anchor0 != anchor1:
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
while parts0 and parts1 and parts0[-1] == parts1[-1]:
parts0.pop()
parts1.pop()
for part in parts1:
if not part or part == '.':
pass
elif not walk_up:
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
elif part == '..':
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
parts0.append('..')
return self.with_segments(*reversed(parts0))

def is_relative_to(self, other):
"""Return True if the path is relative to another path or False.
"""
if not isinstance(other, PurePathBase):
other = self.with_segments(other)
anchor0, parts0 = _explode_path(self)
anchor1, parts1 = _explode_path(other)
if anchor0 != anchor1:
return False
while parts0 and parts1 and parts0[-1] == parts1[-1]:
parts0.pop()
parts1.pop()
for part in parts1:
if part and part != '.':
return False
return True

@property
def parts(self):
"""An object providing sequence-like access to the
Expand Down Expand Up @@ -388,11 +327,6 @@ def parents(self):
parent = split(path)[0]
return tuple(parents)

def is_absolute(self):
"""True if the path is absolute (has both a root and, if applicable,
a drive)."""
return self.parser.isabs(str(self))

def match(self, path_pattern, *, case_sensitive=None):
"""
Return True if this path matches the given pattern. If the pattern is
Expand Down Expand Up @@ -450,55 +384,33 @@ class PathBase(PurePathBase):
"""
__slots__ = ()

def stat(self, *, follow_symlinks=True):
"""
Return the result of the stat() system call on this path, like
os.stat() does.
"""
raise NotImplementedError

# Convenience functions for querying the stat results

def exists(self, *, follow_symlinks=True):
"""
Whether this path exists.
This method normally follows symlinks; to check whether a symlink exists,
add the argument follow_symlinks=False.
"""
try:
self.stat(follow_symlinks=follow_symlinks)
except (OSError, ValueError):
return False
return True
raise NotImplementedError

def is_dir(self, *, follow_symlinks=True):
"""
Whether this path is a directory.
"""
try:
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
except (OSError, ValueError):
return False
raise NotImplementedError

def is_file(self, *, follow_symlinks=True):
"""
Whether this path is a regular file (also True for symlinks pointing
to regular files).
"""
try:
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
except (OSError, ValueError):
return False
raise NotImplementedError

def is_symlink(self):
"""
Whether this path is a symbolic link.
"""
try:
return S_ISLNK(self.stat(follow_symlinks=False).st_mode)
except (OSError, ValueError):
return False
raise NotImplementedError

def open(self, mode='r', buffering=-1, encoding=None,
errors=None, newline=None):
Expand Down
17 changes: 14 additions & 3 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from errno import *
from glob import _StringGlobber, _no_recurse_symlinks
from itertools import chain
from stat import S_IMODE, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from stat import S_IMODE, S_ISDIR, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from _collections_abc import Sequence

try:
Expand Down Expand Up @@ -437,6 +437,11 @@ def _parse_pattern(cls, pattern):
parts.append('')
return parts

def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
return str(self).replace(self.parser.sep, '/')

@property
def _raw_path(self):
paths = self._raw_paths
Expand Down Expand Up @@ -725,7 +730,10 @@ def is_dir(self, *, follow_symlinks=True):
"""
if follow_symlinks:
return os.path.isdir(self)
return PathBase.is_dir(self, follow_symlinks=follow_symlinks)
try:
return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode)
except (OSError, ValueError):
return False

def is_file(self, *, follow_symlinks=True):
"""
Expand All @@ -734,7 +742,10 @@ def is_file(self, *, follow_symlinks=True):
"""
if follow_symlinks:
return os.path.isfile(self)
return PathBase.is_file(self, follow_symlinks=follow_symlinks)
try:
return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode)
except (OSError, ValueError):
return False

def is_mount(self):
"""
Expand Down
2 changes: 0 additions & 2 deletions Lib/pathlib/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ class Parser(Protocol):

sep: str
def split(self, path: str) -> tuple[str, str]: ...
def splitdrive(self, path: str) -> tuple[str, str]: ...
def splitext(self, path: str) -> tuple[str, str]: ...
def normcase(self, path: str) -> str: ...
def isabs(self, path: str) -> bool: ...
8 changes: 8 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2969,3 +2969,11 @@ def run_yielding_async_fn(async_fn, /, *args, **kwargs):
return e.value
finally:
coro.close()


def is_libssl_fips_mode():
try:
from _hashlib import get_fips_mode # ask _hashopenssl.c
except ImportError:
return False # more of a maybe, unless we add this to the _ssl module.
return get_fips_mode() != 0
Loading

0 comments on commit e31133f

Please sign in to comment.