summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/anyio/.dist-info/METADATA44
-rw-r--r--contrib/python/anyio/anyio/__init__.py2
-rw-r--r--contrib/python/anyio/anyio/_backends/_asyncio.py282
-rw-r--r--contrib/python/anyio/anyio/_backends/_trio.py12
-rw-r--r--contrib/python/anyio/anyio/_core/_asyncio_selector_thread.py150
-rw-r--r--contrib/python/anyio/anyio/_core/_exceptions.py2
-rw-r--r--contrib/python/anyio/anyio/_core/_fileio.py4
-rw-r--r--contrib/python/anyio/anyio/_core/_sockets.py93
-rw-r--r--contrib/python/anyio/anyio/_core/_synchronization.py13
-rw-r--r--contrib/python/anyio/anyio/abc/__init__.py12
-rw-r--r--contrib/python/anyio/anyio/abc/_eventloop.py6
-rw-r--r--contrib/python/anyio/anyio/abc/_tasks.py6
-rw-r--r--contrib/python/anyio/ya.make4
-rw-r--r--contrib/python/fonttools/.dist-info/METADATA9
-rw-r--r--contrib/python/fonttools/fontTools/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/designspaceLib/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/feaLib/builder.py39
-rw-r--r--contrib/python/fonttools/ya.make2
18 files changed, 503 insertions, 181 deletions
diff --git a/contrib/python/anyio/.dist-info/METADATA b/contrib/python/anyio/.dist-info/METADATA
index 10d7aafc777..5d0f7c91301 100644
--- a/contrib/python/anyio/.dist-info/METADATA
+++ b/contrib/python/anyio/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: anyio
-Version: 4.6.2.post1
+Version: 4.7.0
Summary: High level compatibility layer for multiple asynchronous event loop implementations
Author-email: Alex Grönholm <[email protected]>
License: MIT
@@ -23,28 +23,28 @@ Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Description-Content-Type: text/x-rst
License-File: LICENSE
-Requires-Dist: idna >=2.8
-Requires-Dist: sniffio >=1.1
-Requires-Dist: exceptiongroup >=1.0.2 ; python_version < "3.11"
-Requires-Dist: typing-extensions >=4.1 ; python_version < "3.11"
-Provides-Extra: doc
-Requires-Dist: packaging ; extra == 'doc'
-Requires-Dist: Sphinx ~=7.4 ; extra == 'doc'
-Requires-Dist: sphinx-rtd-theme ; extra == 'doc'
-Requires-Dist: sphinx-autodoc-typehints >=1.2.0 ; extra == 'doc'
-Provides-Extra: test
-Requires-Dist: anyio[trio] ; extra == 'test'
-Requires-Dist: coverage[toml] >=7 ; extra == 'test'
-Requires-Dist: exceptiongroup >=1.2.0 ; extra == 'test'
-Requires-Dist: hypothesis >=4.0 ; extra == 'test'
-Requires-Dist: psutil >=5.9 ; extra == 'test'
-Requires-Dist: pytest >=7.0 ; extra == 'test'
-Requires-Dist: pytest-mock >=3.6.1 ; extra == 'test'
-Requires-Dist: trustme ; extra == 'test'
-Requires-Dist: uvloop >=0.21.0b1 ; (platform_python_implementation == "CPython" and platform_system != "Windows") and extra == 'test'
-Requires-Dist: truststore >=0.9.1 ; (python_version >= "3.10") and extra == 'test'
+Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
+Requires-Dist: idna>=2.8
+Requires-Dist: sniffio>=1.1
+Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
Provides-Extra: trio
-Requires-Dist: trio >=0.26.1 ; extra == 'trio'
+Requires-Dist: trio>=0.26.1; extra == "trio"
+Provides-Extra: test
+Requires-Dist: anyio[trio]; extra == "test"
+Requires-Dist: coverage[toml]>=7; extra == "test"
+Requires-Dist: exceptiongroup>=1.2.0; extra == "test"
+Requires-Dist: hypothesis>=4.0; extra == "test"
+Requires-Dist: psutil>=5.9; extra == "test"
+Requires-Dist: pytest>=7.0; extra == "test"
+Requires-Dist: pytest-mock>=3.6.1; extra == "test"
+Requires-Dist: trustme; extra == "test"
+Requires-Dist: truststore>=0.9.1; python_version >= "3.10" and extra == "test"
+Requires-Dist: uvloop>=0.21; (platform_python_implementation == "CPython" and platform_system != "Windows") and extra == "test"
+Provides-Extra: doc
+Requires-Dist: packaging; extra == "doc"
+Requires-Dist: Sphinx~=7.4; extra == "doc"
+Requires-Dist: sphinx_rtd_theme; extra == "doc"
+Requires-Dist: sphinx-autodoc-typehints>=1.2.0; extra == "doc"
.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
:target: https://github.com/agronholm/anyio/actions/workflows/test.yml
diff --git a/contrib/python/anyio/anyio/__init__.py b/contrib/python/anyio/anyio/__init__.py
index fd9fe06bcfc..0738e595830 100644
--- a/contrib/python/anyio/anyio/__init__.py
+++ b/contrib/python/anyio/anyio/__init__.py
@@ -34,8 +34,10 @@ from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_
from ._core._sockets import create_unix_listener as create_unix_listener
from ._core._sockets import getaddrinfo as getaddrinfo
from ._core._sockets import getnameinfo as getnameinfo
+from ._core._sockets import wait_readable as wait_readable
from ._core._sockets import wait_socket_readable as wait_socket_readable
from ._core._sockets import wait_socket_writable as wait_socket_writable
+from ._core._sockets import wait_writable as wait_writable
from ._core._streams import create_memory_object_stream as create_memory_object_stream
from ._core._subprocesses import open_process as open_process
from ._core._subprocesses import run_process as run_process
diff --git a/contrib/python/anyio/anyio/_backends/_asyncio.py b/contrib/python/anyio/anyio/_backends/_asyncio.py
index 0a69e7ac610..0b7479d2649 100644
--- a/contrib/python/anyio/anyio/_backends/_asyncio.py
+++ b/contrib/python/anyio/anyio/_backends/_asyncio.py
@@ -28,6 +28,8 @@ from collections.abc import (
Collection,
Coroutine,
Iterable,
+ Iterator,
+ MutableMapping,
Sequence,
)
from concurrent.futures import Future
@@ -50,6 +52,7 @@ from threading import Thread
from types import TracebackType
from typing import (
IO,
+ TYPE_CHECKING,
Any,
Optional,
TypeVar,
@@ -99,6 +102,11 @@ from ..abc._eventloop import StrOrBytesPath
from ..lowlevel import RunVar
from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
+if TYPE_CHECKING:
+ from _typeshed import FileDescriptorLike
+else:
+ FileDescriptorLike = object
+
if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
@@ -347,8 +355,12 @@ _run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary
def _task_started(task: asyncio.Task) -> bool:
"""Return ``True`` if the task has been started and has not finished."""
+ # The task coro should never be None here, as we never add finished tasks to the
+ # task list
+ coro = task.get_coro()
+ assert coro is not None
try:
- return getcoroutinestate(task.get_coro()) in (CORO_RUNNING, CORO_SUSPENDED)
+ return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED)
except AttributeError:
# task coro is async_genenerator_asend https://bugs.python.org/issue37771
raise Exception(f"Cannot determine if task {task} has started or not") from None
@@ -360,11 +372,22 @@ def _task_started(task: asyncio.Task) -> bool:
def is_anyio_cancellation(exc: CancelledError) -> bool:
- return (
- bool(exc.args)
- and isinstance(exc.args[0], str)
- and exc.args[0].startswith("Cancelled by cancel scope ")
- )
+ # Sometimes third party frameworks catch a CancelledError and raise a new one, so as
+ # a workaround we have to look at the previous ones in __context__ too for a
+ # matching cancel message
+ while True:
+ if (
+ exc.args
+ and isinstance(exc.args[0], str)
+ and exc.args[0].startswith("Cancelled by cancel scope ")
+ ):
+ return True
+
+ if isinstance(exc.__context__, CancelledError):
+ exc = exc.__context__
+ continue
+
+ return False
class CancelScope(BaseCancelScope):
@@ -385,8 +408,10 @@ class CancelScope(BaseCancelScope):
self._cancel_handle: asyncio.Handle | None = None
self._tasks: set[asyncio.Task] = set()
self._host_task: asyncio.Task | None = None
- self._cancel_calls: int = 0
- self._cancelling: int | None = None
+ if sys.version_info >= (3, 11):
+ self._pending_uncancellations: int | None = 0
+ else:
+ self._pending_uncancellations = None
def __enter__(self) -> CancelScope:
if self._active:
@@ -405,13 +430,13 @@ class CancelScope(BaseCancelScope):
self._parent_scope = task_state.cancel_scope
task_state.cancel_scope = self
if self._parent_scope is not None:
+ # If using an eager task factory, the parent scope may not even contain
+ # the host task
self._parent_scope._child_scopes.add(self)
- self._parent_scope._tasks.remove(host_task)
+ self._parent_scope._tasks.discard(host_task)
self._timeout()
self._active = True
- if sys.version_info >= (3, 11):
- self._cancelling = self._host_task.cancelling()
# Start cancelling the host task if the scope was cancelled before entering
if self._cancel_called:
@@ -456,30 +481,41 @@ class CancelScope(BaseCancelScope):
host_task_state.cancel_scope = self._parent_scope
- # Undo all cancellations done by this scope
- if self._cancelling is not None:
- while self._cancel_calls:
- self._cancel_calls -= 1
- if self._host_task.uncancel() <= self._cancelling:
- break
+ # Restart the cancellation effort in the closest visible, cancelled parent
+ # scope if necessary
+ self._restart_cancellation_in_parent()
# We only swallow the exception iff it was an AnyIO CancelledError, either
# directly as exc_val or inside an exception group and there are no cancelled
# parent cancel scopes visible to us here
- not_swallowed_exceptions = 0
- swallow_exception = False
- if exc_val is not None:
- for exc in iterate_exceptions(exc_val):
- if self._cancel_called and isinstance(exc, CancelledError):
- if not (swallow_exception := self._uncancel(exc)):
- not_swallowed_exceptions += 1
- else:
- not_swallowed_exceptions += 1
+ if self._cancel_called and not self._parent_cancellation_is_visible_to_us:
+ # For each level-cancel() call made on the host task, call uncancel()
+ while self._pending_uncancellations:
+ self._host_task.uncancel()
+ self._pending_uncancellations -= 1
- # Restart the cancellation effort in the closest visible, cancelled parent
- # scope if necessary
- self._restart_cancellation_in_parent()
- return swallow_exception and not not_swallowed_exceptions
+ # Update cancelled_caught and check for exceptions we must not swallow
+ cannot_swallow_exc_val = False
+ if exc_val is not None:
+ for exc in iterate_exceptions(exc_val):
+ if isinstance(exc, CancelledError) and is_anyio_cancellation(
+ exc
+ ):
+ self._cancelled_caught = True
+ else:
+ cannot_swallow_exc_val = True
+
+ return self._cancelled_caught and not cannot_swallow_exc_val
+ else:
+ if self._pending_uncancellations:
+ assert self._parent_scope is not None
+ assert self._parent_scope._pending_uncancellations is not None
+ self._parent_scope._pending_uncancellations += (
+ self._pending_uncancellations
+ )
+ self._pending_uncancellations = 0
+
+ return False
finally:
self._host_task = None
del exc_val
@@ -506,31 +542,6 @@ class CancelScope(BaseCancelScope):
and self._parent_scope._effectively_cancelled
)
- def _uncancel(self, cancelled_exc: CancelledError) -> bool:
- if self._host_task is None:
- self._cancel_calls = 0
- return True
-
- while True:
- if is_anyio_cancellation(cancelled_exc):
- # Only swallow the cancellation exception if it's an AnyIO cancel
- # exception and there are no other cancel scopes down the line pending
- # cancellation
- self._cancelled_caught = (
- self._effectively_cancelled
- and not self._parent_cancellation_is_visible_to_us
- )
- return self._cancelled_caught
-
- # Sometimes third party frameworks catch a CancelledError and raise a new
- # one, so as a workaround we have to look at the previous ones in
- # __context__ too for a matching cancel message
- if isinstance(cancelled_exc.__context__, CancelledError):
- cancelled_exc = cancelled_exc.__context__
- continue
-
- return False
-
def _timeout(self) -> None:
if self._deadline != math.inf:
loop = get_running_loop()
@@ -562,8 +573,11 @@ class CancelScope(BaseCancelScope):
waiter = task._fut_waiter # type: ignore[attr-defined]
if not isinstance(waiter, asyncio.Future) or not waiter.done():
task.cancel(f"Cancelled by cancel scope {id(origin):x}")
- if task is origin._host_task:
- origin._cancel_calls += 1
+ if (
+ task is origin._host_task
+ and origin._pending_uncancellations is not None
+ ):
+ origin._pending_uncancellations += 1
# Deliver cancellation to child scopes that aren't shielded or running their own
# cancellation callbacks
@@ -663,7 +677,45 @@ class TaskState:
self.cancel_scope = cancel_scope
-_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary()
+class TaskStateStore(MutableMapping["Awaitable[Any] | asyncio.Task", TaskState]):
+ def __init__(self) -> None:
+ self._task_states = WeakKeyDictionary[asyncio.Task, TaskState]()
+ self._preliminary_task_states: dict[Awaitable[Any], TaskState] = {}
+
+ def __getitem__(self, key: Awaitable[Any] | asyncio.Task, /) -> TaskState:
+ assert isinstance(key, asyncio.Task)
+ try:
+ return self._task_states[key]
+ except KeyError:
+ if coro := key.get_coro():
+ if state := self._preliminary_task_states.get(coro):
+ return state
+
+ raise KeyError(key)
+
+ def __setitem__(
+ self, key: asyncio.Task | Awaitable[Any], value: TaskState, /
+ ) -> None:
+ if isinstance(key, asyncio.Task):
+ self._task_states[key] = value
+ else:
+ self._preliminary_task_states[key] = value
+
+ def __delitem__(self, key: asyncio.Task | Awaitable[Any], /) -> None:
+ if isinstance(key, asyncio.Task):
+ del self._task_states[key]
+ else:
+ del self._preliminary_task_states[key]
+
+ def __len__(self) -> int:
+ return len(self._task_states) + len(self._preliminary_task_states)
+
+ def __iter__(self) -> Iterator[Awaitable[Any] | asyncio.Task]:
+ yield from self._task_states
+ yield from self._preliminary_task_states
+
+
+_task_states = TaskStateStore()
#
@@ -783,7 +835,7 @@ class TaskGroup(abc.TaskGroup):
task_status_future: asyncio.Future | None = None,
) -> asyncio.Task:
def task_done(_task: asyncio.Task) -> None:
- task_state = _task_states[_task]
+ # task_state = _task_states[_task]
assert task_state.cancel_scope is not None
assert _task in task_state.cancel_scope._tasks
task_state.cancel_scope._tasks.remove(_task)
@@ -840,16 +892,26 @@ class TaskGroup(abc.TaskGroup):
f"the return value ({coro!r}) is not a coroutine object"
)
- name = get_callable_name(func) if name is None else str(name)
- task = create_task(coro, name=name)
- task.add_done_callback(task_done)
-
# Make the spawned task inherit the task group's cancel scope
- _task_states[task] = TaskState(
+ _task_states[coro] = task_state = TaskState(
parent_id=parent_id, cancel_scope=self.cancel_scope
)
+ name = get_callable_name(func) if name is None else str(name)
+ try:
+ task = create_task(coro, name=name)
+ finally:
+ del _task_states[coro]
+
+ _task_states[task] = task_state
self.cancel_scope._tasks.add(task)
self._tasks.add(task)
+
+ if task.done():
+ # This can happen with eager task factories
+ task_done(task)
+ else:
+ task.add_done_callback(task_done)
+
return task
def start_soon(
@@ -1718,8 +1780,8 @@ class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSock
return
-_read_events: RunVar[dict[Any, asyncio.Event]] = RunVar("read_events")
-_write_events: RunVar[dict[Any, asyncio.Event]] = RunVar("write_events")
+_read_events: RunVar[dict[int, asyncio.Event]] = RunVar("read_events")
+_write_events: RunVar[dict[int, asyncio.Event]] = RunVar("write_events")
#
@@ -2082,7 +2144,9 @@ class AsyncIOTaskInfo(TaskInfo):
else:
parent_id = task_state.parent_id
- super().__init__(id(task), parent_id, task.get_name(), task.get_coro())
+ coro = task.get_coro()
+ assert coro is not None, "created TaskInfo from a completed Task"
+ super().__init__(id(task), parent_id, task.get_name(), coro)
self._task = weakref.ref(task)
def has_pending_cancellation(self) -> bool:
@@ -2090,12 +2154,11 @@ class AsyncIOTaskInfo(TaskInfo):
# If the task isn't around anymore, it won't have a pending cancellation
return False
- if sys.version_info >= (3, 11):
- if task.cancelling():
- return True
+ if task._must_cancel: # type: ignore[attr-defined]
+ return True
elif (
- isinstance(task._fut_waiter, asyncio.Future)
- and task._fut_waiter.cancelled()
+ isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined]
+ and task._fut_waiter.cancelled() # type: ignore[attr-defined]
):
return True
@@ -2335,10 +2398,11 @@ class AsyncIOBackend(AsyncBackend):
@classmethod
def current_effective_deadline(cls) -> float:
+ if (task := current_task()) is None:
+ return math.inf
+
try:
- cancel_scope = _task_states[
- current_task() # type: ignore[index]
- ].cancel_scope
+ cancel_scope = _task_states[task].cancel_scope
except KeyError:
return math.inf
@@ -2671,7 +2735,7 @@ class AsyncIOBackend(AsyncBackend):
return await get_running_loop().getnameinfo(sockaddr, flags)
@classmethod
- async def wait_socket_readable(cls, sock: socket.socket) -> None:
+ async def wait_readable(cls, obj: FileDescriptorLike) -> None:
await cls.checkpoint()
try:
read_events = _read_events.get()
@@ -2679,26 +2743,34 @@ class AsyncIOBackend(AsyncBackend):
read_events = {}
_read_events.set(read_events)
- if read_events.get(sock):
- raise BusyResourceError("reading from") from None
+ if not isinstance(obj, int):
+ obj = obj.fileno()
+
+ if read_events.get(obj):
+ raise BusyResourceError("reading from")
loop = get_running_loop()
- event = read_events[sock] = asyncio.Event()
- loop.add_reader(sock, event.set)
+ event = asyncio.Event()
+ try:
+ loop.add_reader(obj, event.set)
+ except NotImplementedError:
+ from anyio._core._asyncio_selector_thread import get_selector
+
+ selector = get_selector()
+ selector.add_reader(obj, event.set)
+ remove_reader = selector.remove_reader
+ else:
+ remove_reader = loop.remove_reader
+
+ read_events[obj] = event
try:
await event.wait()
finally:
- if read_events.pop(sock, None) is not None:
- loop.remove_reader(sock)
- readable = True
- else:
- readable = False
-
- if not readable:
- raise ClosedResourceError
+ remove_reader(obj)
+ del read_events[obj]
@classmethod
- async def wait_socket_writable(cls, sock: socket.socket) -> None:
+ async def wait_writable(cls, obj: FileDescriptorLike) -> None:
await cls.checkpoint()
try:
write_events = _write_events.get()
@@ -2706,23 +2778,31 @@ class AsyncIOBackend(AsyncBackend):
write_events = {}
_write_events.set(write_events)
- if write_events.get(sock):
- raise BusyResourceError("writing to") from None
+ if not isinstance(obj, int):
+ obj = obj.fileno()
+
+ if write_events.get(obj):
+ raise BusyResourceError("writing to")
loop = get_running_loop()
- event = write_events[sock] = asyncio.Event()
- loop.add_writer(sock.fileno(), event.set)
+ event = asyncio.Event()
+ try:
+ loop.add_writer(obj, event.set)
+ except NotImplementedError:
+ from anyio._core._asyncio_selector_thread import get_selector
+
+ selector = get_selector()
+ selector.add_writer(obj, event.set)
+ remove_writer = selector.remove_writer
+ else:
+ remove_writer = loop.remove_writer
+
+ write_events[obj] = event
try:
await event.wait()
finally:
- if write_events.pop(sock, None) is not None:
- loop.remove_writer(sock)
- writable = True
- else:
- writable = False
-
- if not writable:
- raise ClosedResourceError
+ del write_events[obj]
+ remove_writer(obj)
@classmethod
def current_default_thread_limiter(cls) -> CapacityLimiter:
diff --git a/contrib/python/anyio/anyio/_backends/_trio.py b/contrib/python/anyio/anyio/_backends/_trio.py
index 24dcd744461..70a0a605781 100644
--- a/contrib/python/anyio/anyio/_backends/_trio.py
+++ b/contrib/python/anyio/anyio/_backends/_trio.py
@@ -28,6 +28,7 @@ from socket import AddressFamily, SocketKind
from types import TracebackType
from typing import (
IO,
+ TYPE_CHECKING,
Any,
Generic,
NoReturn,
@@ -80,6 +81,9 @@ from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType
from ..abc._eventloop import AsyncBackend, StrOrBytesPath
from ..streams.memory import MemoryObjectSendStream
+if TYPE_CHECKING:
+ from _typeshed import HasFileno
+
if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
@@ -1260,18 +1264,18 @@ class TrioBackend(AsyncBackend):
return await trio.socket.getnameinfo(sockaddr, flags)
@classmethod
- async def wait_socket_readable(cls, sock: socket.socket) -> None:
+ async def wait_readable(cls, obj: HasFileno | int) -> None:
try:
- await wait_readable(sock)
+ await wait_readable(obj)
except trio.ClosedResourceError as exc:
raise ClosedResourceError().with_traceback(exc.__traceback__) from None
except trio.BusyResourceError:
raise BusyResourceError("reading from") from None
@classmethod
- async def wait_socket_writable(cls, sock: socket.socket) -> None:
+ async def wait_writable(cls, obj: HasFileno | int) -> None:
try:
- await wait_writable(sock)
+ await wait_writable(obj)
except trio.ClosedResourceError as exc:
raise ClosedResourceError().with_traceback(exc.__traceback__) from None
except trio.BusyResourceError:
diff --git a/contrib/python/anyio/anyio/_core/_asyncio_selector_thread.py b/contrib/python/anyio/anyio/_core/_asyncio_selector_thread.py
new file mode 100644
index 00000000000..d98c3040721
--- /dev/null
+++ b/contrib/python/anyio/anyio/_core/_asyncio_selector_thread.py
@@ -0,0 +1,150 @@
+from __future__ import annotations
+
+import asyncio
+import socket
+import threading
+from collections.abc import Callable
+from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ from _typeshed import FileDescriptorLike
+
+_selector_lock = threading.Lock()
+_selector: Selector | None = None
+
+
+class Selector:
+ def __init__(self) -> None:
+ self._thread = threading.Thread(target=self.run, name="AnyIO socket selector")
+ self._selector = DefaultSelector()
+ self._send, self._receive = socket.socketpair()
+ self._send.setblocking(False)
+ self._receive.setblocking(False)
+ self._selector.register(self._receive, EVENT_READ)
+ self._closed = False
+
+ def start(self) -> None:
+ self._thread.start()
+ threading._register_atexit(self._stop) # type: ignore[attr-defined]
+
+ def _stop(self) -> None:
+ global _selector
+ self._closed = True
+ self._notify_self()
+ self._send.close()
+ self._thread.join()
+ self._selector.unregister(self._receive)
+ self._receive.close()
+ self._selector.close()
+ _selector = None
+ assert (
+ not self._selector.get_map()
+ ), "selector still has registered file descriptors after shutdown"
+
+ def _notify_self(self) -> None:
+ try:
+ self._send.send(b"\x00")
+ except BlockingIOError:
+ pass
+
+ def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
+ loop = asyncio.get_running_loop()
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)})
+ else:
+ if EVENT_READ in key.data:
+ raise ValueError(
+ "this file descriptor is already registered for reading"
+ )
+
+ key.data[EVENT_READ] = loop, callback
+ self._selector.modify(fd, key.events | EVENT_READ, key.data)
+
+ self._notify_self()
+
+ def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
+ loop = asyncio.get_running_loop()
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)})
+ else:
+ if EVENT_WRITE in key.data:
+ raise ValueError(
+ "this file descriptor is already registered for writing"
+ )
+
+ key.data[EVENT_WRITE] = loop, callback
+ self._selector.modify(fd, key.events | EVENT_WRITE, key.data)
+
+ self._notify_self()
+
+ def remove_reader(self, fd: FileDescriptorLike) -> bool:
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ return False
+
+ if new_events := key.events ^ EVENT_READ:
+ del key.data[EVENT_READ]
+ self._selector.modify(fd, new_events, key.data)
+ else:
+ self._selector.unregister(fd)
+
+ return True
+
+ def remove_writer(self, fd: FileDescriptorLike) -> bool:
+ try:
+ key = self._selector.get_key(fd)
+ except KeyError:
+ return False
+
+ if new_events := key.events ^ EVENT_WRITE:
+ del key.data[EVENT_WRITE]
+ self._selector.modify(fd, new_events, key.data)
+ else:
+ self._selector.unregister(fd)
+
+ return True
+
+ def run(self) -> None:
+ while not self._closed:
+ for key, events in self._selector.select():
+ if key.fileobj is self._receive:
+ try:
+ while self._receive.recv(4096):
+ pass
+ except BlockingIOError:
+ pass
+
+ continue
+
+ if events & EVENT_READ:
+ loop, callback = key.data[EVENT_READ]
+ self.remove_reader(key.fd)
+ try:
+ loop.call_soon_threadsafe(callback)
+ except RuntimeError:
+ pass # the loop was already closed
+
+ if events & EVENT_WRITE:
+ loop, callback = key.data[EVENT_WRITE]
+ self.remove_writer(key.fd)
+ try:
+ loop.call_soon_threadsafe(callback)
+ except RuntimeError:
+ pass # the loop was already closed
+
+
+def get_selector() -> Selector:
+ global _selector
+
+ with _selector_lock:
+ if _selector is None:
+ _selector = Selector()
+ _selector.start()
+
+ return _selector
diff --git a/contrib/python/anyio/anyio/_core/_exceptions.py b/contrib/python/anyio/anyio/_core/_exceptions.py
index 6e3f8ccc675..97ea3130414 100644
--- a/contrib/python/anyio/anyio/_core/_exceptions.py
+++ b/contrib/python/anyio/anyio/_core/_exceptions.py
@@ -16,7 +16,7 @@ class BrokenResourceError(Exception):
class BrokenWorkerProcess(Exception):
"""
- Raised by :func:`run_sync_in_process` if the worker process terminates abruptly or
+ Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or
otherwise misbehaves.
"""
diff --git a/contrib/python/anyio/anyio/_core/_fileio.py b/contrib/python/anyio/anyio/_core/_fileio.py
index 53d3288c295..ef2930e480e 100644
--- a/contrib/python/anyio/anyio/_core/_fileio.py
+++ b/contrib/python/anyio/anyio/_core/_fileio.py
@@ -92,10 +92,10 @@ class AsyncFile(AsyncResource, Generic[AnyStr]):
async def readlines(self) -> list[AnyStr]:
return await to_thread.run_sync(self._fp.readlines)
- async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> bytes:
+ async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
return await to_thread.run_sync(self._fp.readinto, b)
- async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> bytes:
+ async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
return await to_thread.run_sync(self._fp.readinto1, b)
@overload
diff --git a/contrib/python/anyio/anyio/_core/_sockets.py b/contrib/python/anyio/anyio/_core/_sockets.py
index 6070c647fd9..a822d060d72 100644
--- a/contrib/python/anyio/anyio/_core/_sockets.py
+++ b/contrib/python/anyio/anyio/_core/_sockets.py
@@ -10,7 +10,7 @@ from collections.abc import Awaitable
from ipaddress import IPv6Address, ip_address
from os import PathLike, chmod
from socket import AddressFamily, SocketKind
-from typing import Any, Literal, cast, overload
+from typing import TYPE_CHECKING, Any, Literal, cast, overload
from .. import to_thread
from ..abc import (
@@ -31,9 +31,19 @@ from ._resources import aclose_forcefully
from ._synchronization import Event
from ._tasks import create_task_group, move_on_after
+if TYPE_CHECKING:
+ from _typeshed import FileDescriptorLike
+else:
+ FileDescriptorLike = object
+
if sys.version_info < (3, 11):
from exceptiongroup import ExceptionGroup
+if sys.version_info < (3, 13):
+ from typing_extensions import deprecated
+else:
+ from warnings import deprecated
+
IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515
AnyIPAddressFamily = Literal[
@@ -186,6 +196,14 @@ async def connect_tcp(
try:
addr_obj = ip_address(remote_host)
except ValueError:
+ addr_obj = None
+
+ if addr_obj is not None:
+ if isinstance(addr_obj, IPv6Address):
+ target_addrs = [(socket.AF_INET6, addr_obj.compressed)]
+ else:
+ target_addrs = [(socket.AF_INET, addr_obj.compressed)]
+ else:
# getaddrinfo() will raise an exception if name resolution fails
gai_res = await getaddrinfo(
target_host, remote_port, family=family, type=socket.SOCK_STREAM
@@ -194,7 +212,7 @@ async def connect_tcp(
# Organize the list so that the first address is an IPv6 address (if available)
# and the second one is an IPv4 addresses. The rest can be in whatever order.
v6_found = v4_found = False
- target_addrs: list[tuple[socket.AddressFamily, str]] = []
+ target_addrs = []
for af, *rest, sa in gai_res:
if af == socket.AF_INET6 and not v6_found:
v6_found = True
@@ -204,11 +222,6 @@ async def connect_tcp(
target_addrs.insert(1, (af, sa[0]))
else:
target_addrs.append((af, sa[0]))
- else:
- if isinstance(addr_obj, IPv6Address):
- target_addrs = [(socket.AF_INET6, addr_obj.compressed)]
- else:
- target_addrs = [(socket.AF_INET, addr_obj.compressed)]
oserrors: list[OSError] = []
async with create_task_group() as tg:
@@ -588,12 +601,13 @@ def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str
return get_async_backend().getnameinfo(sockaddr, flags)
+@deprecated("This function is deprecated; use `wait_readable` instead")
def wait_socket_readable(sock: socket.socket) -> Awaitable[None]:
"""
- Wait until the given socket has data to be read.
+ .. deprecated:: 4.7.0
+ Use :func:`wait_readable` instead.
- This does **NOT** work on Windows when using the asyncio backend with a proactor
- event loop (default on py3.8+).
+ Wait until the given socket has data to be read.
.. warning:: Only use this on raw sockets that have not been wrapped by any higher
level constructs like socket streams!
@@ -605,11 +619,15 @@ def wait_socket_readable(sock: socket.socket) -> Awaitable[None]:
to become readable
"""
- return get_async_backend().wait_socket_readable(sock)
+ return get_async_backend().wait_readable(sock.fileno())
+@deprecated("This function is deprecated; use `wait_writable` instead")
def wait_socket_writable(sock: socket.socket) -> Awaitable[None]:
"""
+ .. deprecated:: 4.7.0
+ Use :func:`wait_writable` instead.
+
Wait until the given socket can be written to.
This does **NOT** work on Windows when using the asyncio backend with a proactor
@@ -625,7 +643,58 @@ def wait_socket_writable(sock: socket.socket) -> Awaitable[None]:
to become writable
"""
- return get_async_backend().wait_socket_writable(sock)
+ return get_async_backend().wait_writable(sock.fileno())
+
+
+def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]:
+ """
+ Wait until the given object has data to be read.
+
+ On Unix systems, ``obj`` must either be an integer file descriptor, or else an
+ object with a ``.fileno()`` method which returns an integer file descriptor. Any
+ kind of file descriptor can be passed, though the exact semantics will depend on
+ your kernel. For example, this probably won't do anything useful for on-disk files.
+
+ On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an
+ object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File
+ descriptors aren't supported, and neither are handles that refer to anything besides
+ a ``SOCKET``.
+
+ On backends where this functionality is not natively provided (asyncio
+ ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread
+ which is set to shut down when the interpreter shuts down.
+
+ .. warning:: Don't use this on raw sockets that have been wrapped by any higher
+ level constructs like socket streams!
+
+ :param obj: an object with a ``.fileno()`` method or an integer handle
+ :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
+ object to become readable
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the object
+ to become readable
+
+ """
+ return get_async_backend().wait_readable(obj)
+
+
+def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]:
+ """
+ Wait until the given object can be written to.
+
+ :param obj: an object with a ``.fileno()`` method or an integer handle
+ :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
+ object to become writable
+ :raises ~anyio.BusyResourceError: if another task is already waiting for the object
+ to become writable
+
+ .. seealso:: See the documentation of :func:`wait_readable` for the definition of
+ ``obj`` and notes on backend compatibility.
+
+ .. warning:: Don't use this on raw sockets that have been wrapped by any higher
+ level constructs like socket streams!
+
+ """
+ return get_async_backend().wait_writable(obj)
#
diff --git a/contrib/python/anyio/anyio/_core/_synchronization.py b/contrib/python/anyio/anyio/_core/_synchronization.py
index 023ab73370d..7878ba66682 100644
--- a/contrib/python/anyio/anyio/_core/_synchronization.py
+++ b/contrib/python/anyio/anyio/_core/_synchronization.py
@@ -109,6 +109,7 @@ class Event:
class EventAdapter(Event):
_internal_event: Event | None = None
+ _is_set: bool = False
def __new__(cls) -> EventAdapter:
return object.__new__(cls)
@@ -117,14 +118,22 @@ class EventAdapter(Event):
def _event(self) -> Event:
if self._internal_event is None:
self._internal_event = get_async_backend().create_event()
+ if self._is_set:
+ self._internal_event.set()
return self._internal_event
def set(self) -> None:
- self._event.set()
+ if self._internal_event is None:
+ self._is_set = True
+ else:
+ self._event.set()
def is_set(self) -> bool:
- return self._internal_event is not None and self._internal_event.is_set()
+ if self._internal_event is None:
+ return self._is_set
+
+ return self._internal_event.is_set()
async def wait(self) -> None:
await self._event.wait()
diff --git a/contrib/python/anyio/anyio/abc/__init__.py b/contrib/python/anyio/anyio/abc/__init__.py
index 1ca0fcf746e..3d3b61cc9a0 100644
--- a/contrib/python/anyio/anyio/abc/__init__.py
+++ b/contrib/python/anyio/anyio/abc/__init__.py
@@ -1,7 +1,5 @@
from __future__ import annotations
-from typing import Any
-
from ._eventloop import AsyncBackend as AsyncBackend
from ._resources import AsyncResource as AsyncResource
from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket
@@ -50,8 +48,8 @@ from .._core._tasks import CancelScope as CancelScope
from ..from_thread import BlockingPortal as BlockingPortal
# Re-export imports so they look like they live directly in this package
-key: str
-value: Any
-for key, value in list(locals().items()):
- if getattr(value, "__module__", "").startswith("anyio.abc."):
- value.__module__ = __name__
+for __value in list(locals().values()):
+ if getattr(__value, "__module__", "").startswith("anyio.abc."):
+ __value.__module__ = __name__
+
+del __value
diff --git a/contrib/python/anyio/anyio/abc/_eventloop.py b/contrib/python/anyio/anyio/abc/_eventloop.py
index 93d0e9d25b4..2bfdf286357 100644
--- a/contrib/python/anyio/anyio/abc/_eventloop.py
+++ b/contrib/python/anyio/anyio/abc/_eventloop.py
@@ -28,6 +28,8 @@ else:
from typing_extensions import TypeAlias
if TYPE_CHECKING:
+ from _typeshed import HasFileno
+
from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore
from .._core._tasks import CancelScope
from .._core._testing import TaskInfo
@@ -333,12 +335,12 @@ class AsyncBackend(metaclass=ABCMeta):
@classmethod
@abstractmethod
- async def wait_socket_readable(cls, sock: socket) -> None:
+ async def wait_readable(cls, obj: HasFileno | int) -> None:
pass
@classmethod
@abstractmethod
- async def wait_socket_writable(cls, sock: socket) -> None:
+ async def wait_writable(cls, obj: HasFileno | int) -> None:
pass
@classmethod
diff --git a/contrib/python/anyio/anyio/abc/_tasks.py b/contrib/python/anyio/anyio/abc/_tasks.py
index 88aecf38334..f6e5c40c7ff 100644
--- a/contrib/python/anyio/anyio/abc/_tasks.py
+++ b/contrib/python/anyio/anyio/abc/_tasks.py
@@ -40,6 +40,12 @@ class TaskGroup(metaclass=ABCMeta):
:ivar cancel_scope: the cancel scope inherited by all child tasks
:vartype cancel_scope: CancelScope
+
+ .. note:: On asyncio, support for eager task factories is considered to be
+ **experimental**. In particular, they don't follow the usual semantics of new
+ tasks being scheduled on the next iteration of the event loop, and may thus
+ cause unexpected behavior in code that wasn't written with such semantics in
+ mind.
"""
cancel_scope: CancelScope
diff --git a/contrib/python/anyio/ya.make b/contrib/python/anyio/ya.make
index aadbb5b2978..956fc4c8415 100644
--- a/contrib/python/anyio/ya.make
+++ b/contrib/python/anyio/ya.make
@@ -2,13 +2,14 @@
PY3_LIBRARY()
-VERSION(4.6.2.post1)
+VERSION(4.7.0)
LICENSE(MIT)
PEERDIR(
contrib/python/idna
contrib/python/sniffio
+ contrib/python/typing-extensions
)
NO_LINT()
@@ -25,6 +26,7 @@ PY_SRCS(
anyio/_backends/_asyncio.py
anyio/_backends/_trio.py
anyio/_core/__init__.py
+ anyio/_core/_asyncio_selector_thread.py
anyio/_core/_eventloop.py
anyio/_core/_exceptions.py
anyio/_core/_fileio.py
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA
index 9460888006d..5a177ba30c9 100644
--- a/contrib/python/fonttools/.dist-info/METADATA
+++ b/contrib/python/fonttools/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fonttools
-Version: 4.55.1
+Version: 4.55.2
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -377,6 +377,13 @@ Have fun!
Changelog
~~~~~~~~~
+4.55.2 (released 2024-12-05)
+----------------------------
+
+- [Docs] update Sphinx config (#3712)
+- [designspaceLib] Allow axisOrdering to be set to zero (#3715)
+- [feaLib] Don’t modify variable anchors in place (#3717)
+
4.55.1 (released 2024-12-02)
----------------------------
diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py
index 6aa5f3ad593..2fe3602d325 100644
--- a/contrib/python/fonttools/fontTools/__init__.py
+++ b/contrib/python/fonttools/fontTools/__init__.py
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.55.1"
+version = __version__ = "4.55.2"
__all__ = ["version", "log", "configLogger"]
diff --git a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
index 0a1e782f57c..661f3405da1 100644
--- a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
+++ b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
@@ -1596,7 +1596,7 @@ class BaseDocWriter(object):
mapElement.attrib["input"] = self.intOrFloat(inputValue)
mapElement.attrib["output"] = self.intOrFloat(outputValue)
axisElement.append(mapElement)
- if axisObject.axisOrdering or axisObject.axisLabels:
+ if axisObject.axisOrdering is not None or axisObject.axisLabels:
labelsElement = ET.Element("labels")
if axisObject.axisOrdering is not None:
labelsElement.attrib["ordering"] = str(axisObject.axisOrdering)
diff --git a/contrib/python/fonttools/fontTools/feaLib/builder.py b/contrib/python/fonttools/fontTools/feaLib/builder.py
index bda855e1e94..81aa8c2e263 100644
--- a/contrib/python/fonttools/fontTools/feaLib/builder.py
+++ b/contrib/python/fonttools/fontTools/feaLib/builder.py
@@ -1658,38 +1658,31 @@ class Builder(object):
return default, device
+ def makeAnchorPos(self, varscalar, deviceTable, location):
+ device = None
+ if not isinstance(varscalar, VariableScalar):
+ if deviceTable is not None:
+ device = otl.buildDevice(dict(deviceTable))
+ return varscalar, device
+ default, device = self.makeVariablePos(location, varscalar)
+ if device is not None and deviceTable is not None:
+ raise FeatureLibError(
+ "Can't define a device coordinate and variable scalar", location
+ )
+ return default, device
+
def makeOpenTypeAnchor(self, location, anchor):
"""ast.Anchor --> otTables.Anchor"""
if anchor is None:
return None
- variable = False
deviceX, deviceY = None, None
if anchor.xDeviceTable is not None:
deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
if anchor.yDeviceTable is not None:
deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
- for dim in ("x", "y"):
- varscalar = getattr(anchor, dim)
- if not isinstance(varscalar, VariableScalar):
- continue
- if getattr(anchor, dim + "DeviceTable") is not None:
- raise FeatureLibError(
- "Can't define a device coordinate and variable scalar", location
- )
- default, device = self.makeVariablePos(location, varscalar)
- setattr(anchor, dim, default)
- if device is not None:
- if dim == "x":
- deviceX = device
- else:
- deviceY = device
- variable = True
-
- otlanchor = otl.buildAnchor(
- anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY
- )
- if variable:
- otlanchor.Format = 3
+ x, deviceX = self.makeAnchorPos(anchor.x, anchor.xDeviceTable, location)
+ y, deviceY = self.makeAnchorPos(anchor.y, anchor.yDeviceTable, location)
+ otlanchor = otl.buildAnchor(x, y, anchor.contourpoint, deviceX, deviceY)
return otlanchor
_VALUEREC_ATTRS = {
diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make
index ce8febfcdd8..d01a0e414f3 100644
--- a/contrib/python/fonttools/ya.make
+++ b/contrib/python/fonttools/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(4.55.1)
+VERSION(4.55.2)
LICENSE(MIT)