Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-anyio for openSUSE:Factory 
checked in at 2021-11-29 17:28:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-anyio (Old)
 and      /work/SRC/openSUSE:Factory/.python-anyio.new.31177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-anyio"

Mon Nov 29 17:28:40 2021 rev:7 rq:934534 version:3.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-anyio/python-anyio.changes        
2021-10-27 22:21:12.727198859 +0200
+++ /work/SRC/openSUSE:Factory/.python-anyio.new.31177/python-anyio.changes     
2021-12-02 02:22:04.193274130 +0100
@@ -1,0 +2,14 @@
+Mon Nov 29 12:01:51 UTC 2021 - Dirk M??ller <dmuel...@suse.com>
+
+- update to 3.4.0:
+  * Added context propagation to/from worker threads in 
``to_thread.run_sync()``,
+  ``from_thread.run()`` and ``from_thread.run_sync()``
+  * Fixed race condition in ``Lock`` and ``Semaphore`` classes when a task 
waiting on ``acquire()``
+  is cancelled while another task is waiting to acquire the same primitive
+  * Fixed async context manager's ``__aexit__()`` method not being called in
+  ``BlockingPortal.wrap_async_context_manager()`` if the host task is cancelled
+  * Fixed worker threads being marked as being event loop threads in sniffio
+  * Fixed task parent ID not getting set to the correct value on asyncio
+  * Enabled the test suite to run without IPv6 support, trio or pytest plugin 
autoloading
+
+-------------------------------------------------------------------

Old:
----
  anyio-3.3.4.tar.gz

New:
----
  anyio-3.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-anyio.spec ++++++
--- /var/tmp/diff_new_pack.NqEphl/_old  2021-12-02 02:22:04.661272497 +0100
+++ /var/tmp/diff_new_pack.NqEphl/_new  2021-12-02 02:22:04.665272483 +0100
@@ -19,12 +19,13 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-anyio
-Version:        3.3.4
+Version:        3.4.0
 Release:        0
 Summary:        High level compatibility layer for asynchronous event loop 
implementations
 License:        MIT
 URL:            https://github.com/agronholm/anyio
 Source:         
https://files.pythonhosted.org/packages/source/a/anyio/anyio-%{version}.tar.gz
+BuildRequires:  %{python_module contextlib2 if %python-base < 3.7}
 BuildRequires:  %{python_module dataclasses if %python-base < 3.7}
 BuildRequires:  %{python_module idna >= 2.8}
 BuildRequires:  %{python_module setuptools_scm}
@@ -49,6 +50,7 @@
 Requires:       python-typing_extensions
 %endif
 %if 0%{?python_version_nodots} < 37
+Requires:       python-contextvars
 Requires:       python-dataclasses
 %endif
 Suggests:       python-trio >= 0.16

++++++ anyio-3.3.4.tar.gz -> anyio-3.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/.github/workflows/codeqa-test.yml 
new/anyio-3.4.0/.github/workflows/codeqa-test.yml
--- old/anyio-3.3.4/.github/workflows/codeqa-test.yml   2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/.github/workflows/codeqa-test.yml   2021-11-23 
00:57:30.000000000 +0100
@@ -66,6 +66,8 @@
       run: pip install .[test,trio] coveralls
     - name: Test with pytest
       run: coverage run -m pytest
+      env:
+        PYTEST_DISABLE_PLUGIN_AUTOLOAD: 1
     - name: Upload Coverage
       run: coveralls --service=github
       env:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/PKG-INFO new/anyio-3.4.0/PKG-INFO
--- old/anyio-3.3.4/PKG-INFO    2021-10-16 12:45:40.520680400 +0200
+++ new/anyio-3.4.0/PKG-INFO    2021-11-23 00:57:44.761705900 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: anyio
-Version: 3.3.4
+Version: 3.4.0
 Summary: High level compatibility layer for multiple asynchronous event loop 
implementations
 Home-page: UNKNOWN
 Author: Alex Gr??nholm
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/docs/subprocesses.rst 
new/anyio-3.4.0/docs/subprocesses.rst
--- old/anyio-3.3.4/docs/subprocesses.rst       2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/docs/subprocesses.rst       2021-11-23 00:57:30.000000000 
+0100
@@ -108,7 +108,7 @@
 * If a cancellable call is cancelled during execution on the worker process, 
the worker process
   will be killed
 * The worker process imports the parent's ``__main__`` module, so guarding for 
any import time side
-  effects using ``if __name__ == '__main__':`` is required to avoid inifinite 
recursion
+  effects using ``if __name__ == '__main__':`` is required to avoid infinite 
recursion
 * ``sys.stdin`` and ``sys.stdout``, ``sys.stderr`` are redirected to 
``/dev/null`` so :func:`print`
   and :func:`input` won't work
 * Worker processes terminate after 5 minutes of inactivity, or when the event 
loop is finished
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/docs/tasks.rst 
new/anyio-3.4.0/docs/tasks.rst
--- old/anyio-3.3.4/docs/tasks.rst      2021-10-16 12:45:31.000000000 +0200
+++ new/anyio-3.4.0/docs/tasks.rst      2021-11-23 00:57:30.000000000 +0100
@@ -90,3 +90,17 @@
 exception, :exc:`~ExceptionGroup` is raised which contains both exception 
objects.
 Unfortunately this complicates any code that wishes to catch a specific 
exception because it could
 be wrapped in an :exc:`~ExceptionGroup`.
+
+Context propagation
+-------------------
+
+Whenever a new task is spawned, `context`_ will be copied to the new task. It 
is important to note
+*which* content will be copied to the newly spawned task. It is not the 
context of the task group's
+host task that will be copied, but the context of the task that calls
+:meth:`TaskGroup.start() <.abc.TaskGroup.start>` or
+:meth:`TaskGroup.start_soon() <.abc.TaskGroup.start_soon>`.
+
+.. note:: Context propagation **does not work** on asyncio when using Python 
3.6, as asyncio
+    support for this only landed in v3.7.
+
+.. _context: https://docs.python.org/3/library/contextvars.html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/docs/threads.rst 
new/anyio-3.4.0/docs/threads.rst
--- old/anyio-3.3.4/docs/threads.rst    2021-10-16 12:45:31.000000000 +0200
+++ new/anyio-3.4.0/docs/threads.rst    2021-11-23 00:57:30.000000000 +0100
@@ -177,3 +177,15 @@
 
 .. note:: You cannot use wrapped async context managers in synchronous 
callbacks inside the event
           loop thread.
+
+Context propagation
+-------------------
+
+When running functions in worker threads, the current context is copied to the 
worker thread.
+Therefore any context variables available on the task will also be available 
to the code running
+on the thread. As always with context variables, any changes made to them will 
not propagate back
+to the calling asynchronous task.
+
+When calling asynchronous code from worker threads, context is again copied to 
the task that calls
+the target function in the event loop thread. Note, however, that this **does 
not work** on asyncio
+when running on Python 3.6.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/docs/versionhistory.rst 
new/anyio-3.4.0/docs/versionhistory.rst
--- old/anyio-3.3.4/docs/versionhistory.rst     2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/docs/versionhistory.rst     2021-11-23 00:57:30.000000000 
+0100
@@ -3,6 +3,24 @@
 
 This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
 
+**3.4.0**
+
+- Added context propagation to/from worker threads in ``to_thread.run_sync()``,
+  ``from_thread.run()`` and ``from_thread.run_sync()``
+  (`#363 <https://github.com/agronholm/anyio/issues/363>`_; partially based on 
a PR by Sebasti??n
+  Ram??rez)
+
+  **NOTE**: Requires Python 3.7 to work properly on asyncio!
+- Fixed race condition in ``Lock`` and ``Semaphore`` classes when a task 
waiting on ``acquire()``
+  is cancelled while another task is waiting to acquire the same primitive
+  (`#387 <https://github.com/agronholm/anyio/issues/387>`_)
+- Fixed async context manager's ``__aexit__()`` method not being called in
+  ``BlockingPortal.wrap_async_context_manager()`` if the host task is cancelled
+  (`#381 <https://github.com/agronholm/anyio/issues/381>`_; PR by Jonathan 
Slenders)
+- Fixed worker threads being marked as being event loop threads in sniffio
+- Fixed task parent ID not getting set to the correct value on asyncio
+- Enabled the test suite to run without IPv6 support, trio or pytest plugin 
autoloading
+
 **3.3.4**
 
 - Fixed ``BrokenResourceError`` instead of ``EndOfStream`` being raised in 
``TLSStream`` when the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/pyproject.toml 
new/anyio-3.4.0/pyproject.toml
--- old/anyio-3.3.4/pyproject.toml      2021-10-16 12:45:31.000000000 +0200
+++ new/anyio-3.4.0/pyproject.toml      2021-11-23 00:57:30.000000000 +0100
@@ -32,7 +32,7 @@
 disallow_subclassing_any = false
 
 [tool.pytest.ini_options]
-addopts = "-rsx --tb=short --strict-config --strict-markers"
+addopts = "-rsx --tb=short --strict-config --strict-markers -p anyio -p 
no:asyncio"
 testpaths = ["tests"]
 # Ignore resource warnings due to a CPython/Windows bug 
(https://bugs.python.org/issue44428)
 filterwarnings = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/setup.cfg new/anyio-3.4.0/setup.cfg
--- old/anyio-3.3.4/setup.cfg   2021-10-16 12:45:40.520680400 +0200
+++ new/anyio-3.4.0/setup.cfg   2021-11-23 00:57:44.761705900 +0100
@@ -28,6 +28,7 @@
 python_requires = >= 3.6.2
 zip_safe = False
 install_requires = 
+       contextvars; python_version < '3.7'
        dataclasses; python_version < '3.7'
        idna >= 2.8
        sniffio >= 1.1
@@ -42,6 +43,7 @@
 [options.extras_require]
 test = 
        mock >= 4; python_version < '3.8'
+       contextlib2; python_version < '3.7'
        coverage[toml] >= 4.5
        hypothesis >= 4.0
        pytest >= 6.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/_backends/_asyncio.py 
new/anyio-3.4.0/src/anyio/_backends/_asyncio.py
--- old/anyio-3.3.4/src/anyio/_backends/_asyncio.py     2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/src/anyio/_backends/_asyncio.py     2021-11-23 
00:57:30.000000000 +0100
@@ -7,6 +7,7 @@
 from asyncio.base_events import _run_until_complete_cb  # type: ignore
 from collections import OrderedDict, deque
 from concurrent.futures import Future
+from contextvars import Context, copy_context
 from dataclasses import dataclass
 from functools import partial, wraps
 from inspect import (
@@ -22,6 +23,8 @@
     Mapping, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast)
 from weakref import WeakKeyDictionary
 
+import sniffio
+
 from .. import CapacityLimiterStatistics, EventStatistics, TaskInfo, abc
 from .._core._compat import DeprecatedAsyncContextManager, DeprecatedAwaitable
 from .._core._eventloop import claim_worker_thread, threadlocals
@@ -515,8 +518,9 @@
 
 
 class _AsyncioTaskStatus(abc.TaskStatus):
-    def __init__(self, future: asyncio.Future):
+    def __init__(self, future: asyncio.Future, parent_id: int):
         self._future = future
+        self._parent_id = parent_id
 
     def started(self, value: object = None) -> None:
         try:
@@ -524,6 +528,9 @@
         except asyncio.InvalidStateError:
             raise RuntimeError("called 'started' twice on the same task 
status") from None
 
+        task = cast(asyncio.Task, current_task())
+        _task_states[task].parent_id = self._parent_id
+
 
 class TaskGroup(abc.TaskGroup):
     def __init__(self) -> None:
@@ -653,7 +660,11 @@
 
         kwargs = {}
         if task_status_future:
-            kwargs['task_status'] = _AsyncioTaskStatus(task_status_future)
+            parent_id = id(current_task())
+            kwargs['task_status'] = _AsyncioTaskStatus(task_status_future,
+                                                       
id(self.cancel_scope._host_task))
+        else:
+            parent_id = id(self.cancel_scope._host_task)
 
         coro = func(*args, **kwargs)
         if not asyncio.iscoroutine(coro):
@@ -668,7 +679,7 @@
             task.add_done_callback(task_done)
 
         # Make the spawned task inherit the task group's cancel scope
-        _task_states[task] = TaskState(parent_id=id(current_task()), name=name,
+        _task_states[task] = TaskState(parent_id=parent_id, name=name,
                                        cancel_scope=self.cancel_scope)
         self.cancel_scope._tasks.add(task)
         return task
@@ -710,7 +721,7 @@
         self.workers = workers
         self.idle_workers = idle_workers
         self.loop = root_task._loop
-        self.queue: Queue[Union[Tuple[Callable, tuple, asyncio.Future], None]] 
= Queue(2)
+        self.queue: Queue[Union[Tuple[Context, Callable, tuple, 
asyncio.Future], None]] = Queue(2)
         self.idle_since = current_time()
         self.stopping = False
 
@@ -735,12 +746,12 @@
                     # Shutdown command received
                     return
 
-                func, args, future = item
+                context, func, args, future = item
                 if not future.cancelled():
                     result = None
                     exception: Optional[BaseException] = None
                     try:
-                        result = func(*args)
+                        result = context.run(func, *args)
                     except BaseException as exc:
                         exception = exc
 
@@ -801,7 +812,9 @@
                     
expired_worker.root_task.remove_done_callback(expired_worker.stop)
                     expired_worker.stop()
 
-            worker.queue.put_nowait((func, args, future))
+            context = copy_context()
+            context.run(sniffio.current_async_library_cvar.set, None)
+            worker.queue.put_nowait((context, func, args, future))
             return await future
 
 
@@ -818,7 +831,11 @@
 
     f: concurrent.futures.Future[T_Retval] = Future()
     loop = loop or threadlocals.loop
-    loop.call_soon_threadsafe(wrapper)
+    if sys.version_info < (3, 7):
+        loop.call_soon_threadsafe(copy_context().run, wrapper)
+    else:
+        loop.call_soon_threadsafe(wrapper)
+
     return f.result()
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/_backends/_trio.py 
new/anyio-3.4.0/src/anyio/_backends/_trio.py
--- old/anyio-3.3.4/src/anyio/_backends/_trio.py        2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/src/anyio/_backends/_trio.py        2021-11-23 
00:57:30.000000000 +0100
@@ -2,6 +2,7 @@
 import math
 import socket
 from concurrent.futures import Future
+from contextvars import copy_context
 from dataclasses import dataclass
 from functools import partial
 from io import IOBase
@@ -11,6 +12,7 @@
     Any, Awaitable, Callable, Collection, ContextManager, Coroutine, Deque, 
Dict, Generic, List,
     Mapping, NoReturn, Optional, Sequence, Set, Tuple, Type, TypeVar, Union)
 
+import sniffio
 import trio.from_thread
 from outcome import Error, Outcome, Value
 from trio.socket import SocketType as TrioSocketType
@@ -37,6 +39,10 @@
 else:
     from trio.lowlevel import wait_readable, wait_writable
 
+try:
+    from trio.lowlevel import open_process as trio_open_process
+except ImportError:
+    from trio import open_process as trio_open_process
 
 T_Retval = TypeVar('T_Retval')
 T_SockAddr = TypeVar('T_SockAddr', str, IPSockAddrType)
@@ -167,10 +173,36 @@
         with claim_worker_thread('trio'):
             return func(*args)
 
-    return await run_sync(wrapper, cancellable=cancellable, limiter=limiter)
+    # TODO: remove explicit context copying when trio 0.20 is the minimum 
requirement
+    context = copy_context()
+    context.run(sniffio.current_async_library_cvar.set, None)
+    return await run_sync(context.run, wrapper, cancellable=cancellable, 
limiter=limiter)
+
+
+# TODO: remove this workaround when trio 0.20 is the minimum requirement
+def run_async_from_thread(fn: Callable[..., Awaitable[T_Retval]], *args: Any) 
-> T_Retval:
+    async def wrapper() -> Optional[T_Retval]:
+        retval: T_Retval
+
+        async def inner() -> None:
+            nonlocal retval
+            __tracebackhide__ = True
+            retval = await fn(*args)
+
+        async with trio.open_nursery() as n:
+            context.run(n.start_soon, inner)
+
+        __tracebackhide__ = True
+        return retval
+
+    context = copy_context()
+    context.run(sniffio.current_async_library_cvar.set, 'trio')
+    return trio.from_thread.run(wrapper)
+
 
-run_async_from_thread = trio.from_thread.run
-run_sync_from_thread = trio.from_thread.run_sync
+def run_sync_from_thread(fn: Callable[..., T_Retval], *args: Any) -> T_Retval:
+    # TODO: remove explicit context copying when trio 0.20 is the minimum 
requirement
+    return trio.from_thread.run_sync(copy_context().run, fn, *args)
 
 
 class BlockingPortal(abc.BlockingPortal):
@@ -183,9 +215,11 @@
 
     def _spawn_task_from_thread(self, func: Callable, args: tuple, kwargs: 
Dict[str, Any],
                                 name: object, future: Future) -> None:
+        context = copy_context()
+        context.run(sniffio.current_async_library_cvar.set, 'trio')
         return trio.from_thread.run_sync(
-            partial(self._task_group.start_soon, name=name), self._call_func, 
func, args, kwargs,
-            future, trio_token=self._token)
+            context.run, partial(self._task_group.start_soon, name=name), 
self._call_func, func,
+            args, kwargs, future, trio_token=self._token)
 
 
 #
@@ -283,7 +317,7 @@
                        stdin: int, stdout: int, stderr: int,
                        cwd: Union[str, bytes, PathLike, None] = None,
                        env: Optional[Mapping[str, str]] = None) -> Process:
-    process = await trio.open_process(command, stdin=stdin, stdout=stdout, 
stderr=stderr,
+    process = await trio_open_process(command, stdin=stdin, stdout=stdout, 
stderr=stderr,
                                       shell=shell, cwd=cwd, env=env)
     stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None
     stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout 
else None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/_core/_eventloop.py 
new/anyio-3.4.0/src/anyio/_core/_eventloop.py
--- old/anyio-3.3.4/src/anyio/_core/_eventloop.py       2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/src/anyio/_core/_eventloop.py       2021-11-23 
00:57:30.000000000 +0100
@@ -123,11 +123,9 @@
 def claim_worker_thread(backend: str) -> Generator[Any, None, None]:
     module = sys.modules['anyio._backends._' + backend]
     threadlocals.current_async_module = module
-    token = sniffio.current_async_library_cvar.set(backend)
     try:
         yield
     finally:
-        sniffio.current_async_library_cvar.reset(token)
         del threadlocals.current_async_module
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/_core/_exceptions.py 
new/anyio-3.4.0/src/anyio/_core/_exceptions.py
--- old/anyio-3.3.4/src/anyio/_core/_exceptions.py      2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/src/anyio/_core/_exceptions.py      2021-11-23 
00:57:30.000000000 +0100
@@ -4,7 +4,7 @@
 
 class BrokenResourceError(Exception):
     """
-    Raised when trying to use a resource that has been rendered unusuable due 
to external causes
+    Raised when trying to use a resource that has been rendered unusable due 
to external causes
     (e.g. a send stream whose peer has disconnected).
     """
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/_core/_synchronization.py 
new/anyio-3.4.0/src/anyio/_core/_synchronization.py
--- old/anyio-3.3.4/src/anyio/_core/_synchronization.py 2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/src/anyio/_core/_synchronization.py 2021-11-23 
00:57:30.000000000 +0100
@@ -127,6 +127,8 @@
             except BaseException:
                 if not event.is_set():
                     self._waiters.remove(token)
+                elif self._owner_task == task:
+                    self.release()
 
                 raise
 
@@ -302,6 +304,8 @@
             except BaseException:
                 if not event.is_set():
                     self._waiters.remove(event)
+                else:
+                    self.release()
 
                 raise
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio/from_thread.py 
new/anyio-3.4.0/src/anyio/from_thread.py
--- old/anyio-3.3.4/src/anyio/from_thread.py    2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/src/anyio/from_thread.py    2021-11-23 00:57:30.000000000 
+0100
@@ -70,7 +70,7 @@
     _exit_future: Future
     _exit_event: Event
     _exit_exc_info: Tuple[Optional[Type[BaseException]], 
Optional[BaseException],
-                          Optional[TracebackType]]
+                          Optional[TracebackType]] = (None, None, None)
 
     def __init__(self, async_cm: AsyncContextManager[T_co], portal: 
'BlockingPortal'):
         self._async_cm = async_cm
@@ -86,8 +86,18 @@
         else:
             self._enter_future.set_result(value)
 
-        await self._exit_event.wait()
-        return await self._async_cm.__aexit__(*self._exit_exc_info)
+        try:
+            # Wait for the sync context manager to exit.
+            # This next statement can raise `get_cancelled_exc_class()` if
+            # something went wrong in a task group in this async context
+            # manager.
+            await self._exit_event.wait()
+        finally:
+            # In case of cancellation, it could be that we end up here before
+            # `_BlockingAsyncContextManager.__exit__` is called, and an
+            # `_exit_exc_info` has been set.
+            result = await self._async_cm.__aexit__(*self._exit_exc_info)
+            return result
 
     def __enter__(self) -> T_co:
         self._enter_future = Future()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio.egg-info/PKG-INFO 
new/anyio-3.4.0/src/anyio.egg-info/PKG-INFO
--- old/anyio-3.3.4/src/anyio.egg-info/PKG-INFO 2021-10-16 12:45:40.000000000 
+0200
+++ new/anyio-3.4.0/src/anyio.egg-info/PKG-INFO 2021-11-23 00:57:44.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: anyio
-Version: 3.3.4
+Version: 3.4.0
 Summary: High level compatibility layer for multiple asynchronous event loop 
implementations
 Home-page: UNKNOWN
 Author: Alex Gr??nholm
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/src/anyio.egg-info/requires.txt 
new/anyio-3.4.0/src/anyio.egg-info/requires.txt
--- old/anyio-3.3.4/src/anyio.egg-info/requires.txt     2021-10-16 
12:45:40.000000000 +0200
+++ new/anyio-3.4.0/src/anyio.egg-info/requires.txt     2021-11-23 
00:57:44.000000000 +0100
@@ -2,6 +2,7 @@
 sniffio>=1.1
 
 [:python_version < "3.7"]
+contextvars
 dataclasses
 
 [:python_version < "3.8"]
@@ -18,6 +19,9 @@
 pytest-mock>=3.6.1
 trustme
 
+[test:python_version < "3.7"]
+contextlib2
+
 [test:python_version < "3.7" and (platform_python_implementation == "CPython" 
and platform_system != "Windows")]
 uvloop<0.15
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/conftest.py 
new/anyio-3.4.0/tests/conftest.py
--- old/anyio-3.3.4/tests/conftest.py   2021-10-16 12:45:31.000000000 +0200
+++ new/anyio-3.4.0/tests/conftest.py   2021-11-23 00:57:30.000000000 +0100
@@ -22,7 +22,7 @@
     else:
         uvloop_policy = uvloop.EventLoopPolicy()
 
-pytest_plugins = ['pytester']
+pytest_plugins = ['pytester', 'pytest_mock']
 
 
 @pytest.fixture(params=[
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_from_thread.py 
new/anyio-3.4.0/tests/test_from_thread.py
--- old/anyio-3.3.4/tests/test_from_thread.py   2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/tests/test_from_thread.py   2021-11-23 00:57:30.000000000 
+0100
@@ -3,22 +3,29 @@
 import time
 from concurrent.futures import CancelledError
 from contextlib import suppress
-from typing import Any, Dict, List, NoReturn, Optional
+from contextvars import ContextVar
+from typing import Any, AsyncGenerator, Dict, List, NoReturn, Optional
 
 import pytest
 from _pytest.logging import LogCaptureFixture
 
 from anyio import (
-    Event, from_thread, get_cancelled_exc_class, get_current_task, run, sleep, 
to_thread,
-    wait_all_tasks_blocked)
+    Event, create_task_group, from_thread, get_cancelled_exc_class, 
get_current_task, run, sleep,
+    to_thread, wait_all_tasks_blocked)
 from anyio.abc import TaskStatus
 from anyio.from_thread import BlockingPortal, start_blocking_portal
+from anyio.lowlevel import checkpoint
 
 if sys.version_info >= (3, 8):
     from typing import Literal
 else:
     from typing_extensions import Literal
 
+if sys.version_info >= (3, 7):
+    from contextlib import asynccontextmanager
+else:
+    from contextlib2 import asynccontextmanager
+
 
 pytestmark = pytest.mark.anyio
 
@@ -117,6 +124,22 @@
         exc = pytest.raises(RuntimeError, from_thread.run, foo)
         exc.match('This function can only be run from an AnyIO worker thread')
 
+    async def test_contextvar_propagation(self, anyio_backend_name: str) -> 
None:
+        if anyio_backend_name == 'asyncio' and sys.version_info < (3, 7):
+            pytest.skip('Asyncio does not propagate context before Python 3.7')
+
+        var = ContextVar('var', default=1)
+
+        async def async_func() -> int:
+            await checkpoint()
+            return var.get()
+
+        def worker() -> int:
+            var.set(6)
+            return from_thread.run(async_func)
+
+        assert await to_thread.run_sync(worker) == 6
+
 
 class TestRunSyncFromThread:
     def test_run_sync_from_unclaimed_thread(self) -> None:
@@ -126,6 +149,15 @@
         exc = pytest.raises(RuntimeError, from_thread.run_sync, foo)
         exc.match('This function can only be run from an AnyIO worker thread')
 
+    async def test_contextvar_propagation(self) -> None:
+        var = ContextVar('var', default=1)
+
+        def worker() -> int:
+            var.set(6)
+            return from_thread.run_sync(var.get)
+
+        assert await to_thread.run_sync(worker) == 6
+
 
 class TestBlockingPortal:
     class AsyncCM:
@@ -318,6 +350,23 @@
                 assert cm == 'test'
                 raise Exception('should be ignored')
 
+    def test_async_context_manager_exception_in_task_group(
+            self, anyio_backend_name: str, anyio_backend_options: Dict[str, 
Any]) -> None:
+        """Regression test for #381."""
+        async def failing_func() -> None:
+            0 / 0
+
+        @asynccontextmanager
+        async def run_in_context() -> AsyncGenerator[None, None]:
+            async with create_task_group() as tg:
+                tg.start_soon(failing_func)
+                yield
+
+        with start_blocking_portal(anyio_backend_name, anyio_backend_options) 
as portal:
+            with pytest.raises(ZeroDivisionError):
+                with portal.wrap_async_context_manager(run_in_context()):
+                    pass
+
     def test_start_no_value(self, anyio_backend_name: str,
                             anyio_backend_options: Dict[str, Any]) -> None:
         def taskfunc(*, task_status: TaskStatus) -> None:
@@ -378,6 +427,35 @@
                 taskfunc, name='testname')  # type: ignore[arg-type]
             assert start_value == 'testname'
 
+    def test_contextvar_propagation_sync(self, anyio_backend_name: str,
+                                         anyio_backend_options: Dict[str, 
Any]) -> None:
+        if anyio_backend_name == 'asyncio' and sys.version_info < (3, 7):
+            pytest.skip('Asyncio does not propagate context before Python 3.7')
+
+        var = ContextVar('var', default=1)
+        var.set(6)
+        with start_blocking_portal(anyio_backend_name, anyio_backend_options) 
as portal:
+            propagated_value = portal.call(var.get)
+
+        assert propagated_value == 6
+
+    def test_contextvar_propagation_async(self, anyio_backend_name: str,
+                                          anyio_backend_options: Dict[str, 
Any]) -> None:
+        if anyio_backend_name == 'asyncio' and sys.version_info < (3, 7):
+            pytest.skip('Asyncio does not propagate context before Python 3.7')
+
+        var = ContextVar('var', default=1)
+        var.set(6)
+
+        async def get_var() -> int:
+            await checkpoint()
+            return var.get()
+
+        with start_blocking_portal(anyio_backend_name, anyio_backend_options) 
as portal:
+            propagated_value = portal.call(get_var)
+
+        assert propagated_value == 6
+
     @pytest.mark.parametrize('anyio_backend', ['asyncio'])
     async def test_asyncio_run_sync_called(self, caplog: LogCaptureFixture) -> 
None:
         """Regression test for #357."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_pytest_plugin.py 
new/anyio-3.4.0/tests/test_pytest_plugin.py
--- old/anyio-3.3.4/tests/test_pytest_plugin.py 2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/tests/test_pytest_plugin.py 2021-11-23 00:57:30.000000000 
+0100
@@ -6,6 +6,8 @@
 pytestmark = pytest.mark.filterwarnings(
     'ignore:The TerminalReporter.writer attribute is 
deprecated:pytest.PytestDeprecationWarning:')
 
+pytest_args = '-v', '-p', 'anyio', '-p', 'no:asyncio'
+
 
 def test_plugin(testdir: Testdir) -> None:
     testdir.makeconftest(
@@ -61,7 +63,7 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest(*pytest_args)
     result.assert_outcomes(passed=3 * len(get_all_backends()), 
skipped=len(get_all_backends()))
 
 
@@ -134,7 +136,7 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest(*pytest_args)
     result.assert_outcomes(passed=2, failed=1, errors=2)
 
 
@@ -171,7 +173,7 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest_subprocess(*pytest_args)
     result.assert_outcomes(passed=len(get_all_backends()))
 
 
@@ -198,7 +200,7 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest_subprocess(*pytest_args)
     result.assert_outcomes(passed=len(get_all_backends()))
 
 
@@ -229,7 +231,7 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest(*pytest_args)
     result.assert_outcomes(passed=len(get_all_backends()) + 1, 
xfailed=len(get_all_backends()))
 
 
@@ -268,5 +270,5 @@
         """
     )
 
-    result = testdir.runpytest('-v', '-p', 'no:asyncio')
+    result = testdir.runpytest(*pytest_args)
     result.assert_outcomes(passed=2 * len(get_all_backends()), xfailed=2 * 
len(get_all_backends()))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_sockets.py 
new/anyio-3.4.0/tests/test_sockets.py
--- old/anyio-3.3.4/tests/test_sockets.py       2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/tests/test_sockets.py       2021-11-23 00:57:30.000000000 
+0100
@@ -38,6 +38,20 @@
 
 pytestmark = pytest.mark.anyio
 
+# If a socket can bind to ::1, the current environment has IPv6 properly 
configured
+has_ipv6 = False
+if socket.has_ipv6:
+    s = socket.socket(AddressFamily.AF_INET6)
+    try:
+        s.bind(('::1', 0))
+    except OSError:
+        pass
+    else:
+        has_ipv6 = True
+    finally:
+        s.close()
+        del s
+
 
 @pytest.fixture
 def fake_localhost_dns(monkeypatch: MonkeyPatch) -> None:
@@ -53,7 +67,7 @@
 @pytest.fixture(params=[
     pytest.param(AddressFamily.AF_INET, id='ipv4'),
     pytest.param(AddressFamily.AF_INET6, id='ipv6',
-                 marks=[pytest.mark.skipif(not socket.has_ipv6, reason='no 
IPv6 support')])
+                 marks=[pytest.mark.skipif(not has_ipv6, reason='no IPv6 
support')])
 ])
 def family(request: SubRequest) -> AnyIPAddressFamily:
     return request.param
@@ -194,7 +208,7 @@
             raw_socket = stream.extra(SocketAttribute.raw_socket)
             assert raw_socket.getsockopt(socket.IPPROTO_TCP, 
socket.TCP_NODELAY) != 0
 
-    @pytest.mark.skipif(not socket.has_ipv6, reason='IPv6 is not available')
+    @pytest.mark.skipif(not has_ipv6, reason='IPv6 is not available')
     @pytest.mark.parametrize('local_addr, expected_client_addr', [
         pytest.param('', '::1', id='dualstack'),
         pytest.param('127.0.0.1', '127.0.0.1', id='ipv4'),
@@ -227,7 +241,7 @@
     @pytest.mark.parametrize('target, exception_class', [
         pytest.param(
             'localhost', ExceptionGroup, id='multi',
-            marks=[pytest.mark.skipif(not socket.has_ipv6, reason='IPv6 is not 
available')]
+            marks=[pytest.mark.skipif(not has_ipv6, reason='IPv6 is not 
available')]
         ),
         pytest.param('127.0.0.1', ConnectionRefusedError, id='single')
     ])
@@ -441,9 +455,9 @@
     @pytest.mark.parametrize('family', [
         pytest.param(AddressFamily.AF_INET, id='ipv4'),
         pytest.param(AddressFamily.AF_INET6, id='ipv6',
-                     marks=[pytest.mark.skipif(not socket.has_ipv6, reason='no 
IPv6 support')]),
+                     marks=[pytest.mark.skipif(not has_ipv6, reason='no IPv6 
support')]),
         pytest.param(socket.AF_UNSPEC, id='both',
-                     marks=[pytest.mark.skipif(not socket.has_ipv6, reason='no 
IPv6 support')])
+                     marks=[pytest.mark.skipif(not has_ipv6, reason='no IPv6 
support')])
     ])
     async def test_accept(self, family: AnyIPAddressFamily) -> None:
         async with await create_tcp_listener(local_host='localhost', 
family=family) as multi:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_synchronization.py 
new/anyio-3.4.0/tests/test_synchronization.py
--- old/anyio-3.3.4/tests/test_synchronization.py       2021-10-16 
12:45:31.000000000 +0200
+++ new/anyio-3.4.0/tests/test_synchronization.py       2021-11-23 
00:57:30.000000000 +0100
@@ -5,7 +5,7 @@
 from anyio import (
     CancelScope, Condition, Event, Lock, Semaphore, WouldBlock, 
create_task_group, to_thread,
     wait_all_tasks_blocked)
-from anyio.abc import CapacityLimiter
+from anyio.abc import CapacityLimiter, TaskStatus
 
 pytestmark = pytest.mark.anyio
 
@@ -65,23 +65,34 @@
             assert lock.locked()
             tg.start_soon(try_lock)
 
-    async def test_cancel(self) -> None:
-        task_started = got_lock = False
-
-        async def task() -> None:
-            nonlocal task_started, got_lock
-            task_started = True
+    @pytest.mark.parametrize('release_first', [
+        pytest.param(False, id='releaselast'),
+        pytest.param(True, id='releasefirst')
+    ])
+    async def test_cancel_during_acquire(self, release_first: bool) -> None:
+        acquired = False
+
+        async def task(*, task_status: TaskStatus) -> None:
+            nonlocal acquired
+            task_status.started()
             async with lock:
-                got_lock = True
+                acquired = True
 
         lock = Lock()
         async with create_task_group() as tg:
-            async with lock:
-                tg.start_soon(task)
-                tg.cancel_scope.cancel()
+            await lock.acquire()
+            await tg.start(task)
+            tg.cancel_scope.cancel()
+            with CancelScope(shield=True):
+                if release_first:
+                    lock.release()
+                    await wait_all_tasks_blocked()
+                else:
+                    await wait_all_tasks_blocked()
+                    lock.release()
 
-        assert task_started
-        assert not got_lock
+        assert not acquired
+        assert not lock.locked()
 
     async def test_statistics(self) -> None:
         async def waiter() -> None:
@@ -282,24 +293,34 @@
         assert semaphore.value == 0
         pytest.raises(WouldBlock, semaphore.acquire_nowait)
 
-    async def test_acquire_cancel(self) -> None:
-        local_scope = acquired = None
-
-        async def task() -> None:
-            nonlocal local_scope, acquired
-            with CancelScope() as local_scope:
-                async with semaphore:
-                    acquired = True
+    @pytest.mark.parametrize('release_first', [
+        pytest.param(False, id='releaselast'),
+        pytest.param(True, id='releasefirst')
+    ])
+    async def test_cancel_during_acquire(self, release_first: bool) -> None:
+        acquired = False
+
+        async def task(*, task_status: TaskStatus) -> None:
+            nonlocal acquired
+            task_status.started()
+            async with semaphore:
+                acquired = True
 
         semaphore = Semaphore(1)
         async with create_task_group() as tg:
-            async with semaphore:
-                tg.start_soon(task)
-                await wait_all_tasks_blocked()
-                assert local_scope is not None
-                local_scope.cancel()
+            await semaphore.acquire()
+            await tg.start(task)
+            tg.cancel_scope.cancel()
+            with CancelScope(shield=True):
+                if release_first:
+                    semaphore.release()
+                    await wait_all_tasks_blocked()
+                else:
+                    await wait_all_tasks_blocked()
+                    semaphore.release()
 
         assert not acquired
+        assert semaphore.value == 1
 
     @pytest.mark.parametrize('max_value', [2, None])
     async def test_max_value(self, max_value: Optional[int]) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_taskgroups.py 
new/anyio-3.4.0/tests/test_taskgroups.py
--- old/anyio-3.3.4/tests/test_taskgroups.py    2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/tests/test_taskgroups.py    2021-11-23 00:57:30.000000000 
+0100
@@ -2,15 +2,15 @@
 import re
 import sys
 import time
-from typing import Any, AsyncGenerator, Coroutine, Dict, Generator, NoReturn, 
Set
+from typing import Any, AsyncGenerator, Coroutine, Dict, Generator, NoReturn, 
Optional, Set
 
 import pytest
-import trio
 
 import anyio
 from anyio import (
     CancelScope, ExceptionGroup, create_task_group, 
current_effective_deadline, current_time,
-    fail_after, get_cancelled_exc_class, move_on_after, sleep, 
wait_all_tasks_blocked)
+    fail_after, get_cancelled_exc_class, get_current_task, move_on_after, 
sleep,
+    wait_all_tasks_blocked)
 from anyio.abc import TaskGroup, TaskStatus
 from anyio.lowlevel import checkpoint
 
@@ -54,7 +54,7 @@
 
 @pytest.mark.parametrize('module', [
     pytest.param(asyncio, id='asyncio'),
-    pytest.param(trio, id='trio')
+    pytest.param(pytest.importorskip('trio'), id='trio')
 ])
 def test_run_natively(module: Any) -> None:
     async def testfunc() -> None:
@@ -1004,3 +1004,45 @@
             await wait_all_tasks_blocked()
             assert isinstance(task, asyncio.Task)
             task.cancel('blah')
+
+
+async def test_start_soon_parent_id() -> None:
+    root_task_id = get_current_task().id
+    parent_id: Optional[int] = None
+
+    async def subtask() -> None:
+        nonlocal parent_id
+        parent_id = get_current_task().parent_id
+
+    async def starter_task() -> None:
+        tg.start_soon(subtask)
+
+    async with anyio.create_task_group() as tg:
+        tg.start_soon(starter_task)
+
+    assert parent_id == root_task_id
+
+
+async def test_start_parent_id() -> None:
+    root_task_id = get_current_task().id
+    starter_task_id: Optional[int] = None
+    initial_parent_id: Optional[int] = None
+    permanent_parent_id: Optional[int] = None
+
+    async def subtask(*, task_status: TaskStatus) -> None:
+        nonlocal initial_parent_id, permanent_parent_id
+        initial_parent_id = get_current_task().parent_id
+        task_status.started()
+        permanent_parent_id = get_current_task().parent_id
+
+    async def starter_task() -> None:
+        nonlocal starter_task_id
+        starter_task_id = get_current_task().id
+        await tg.start(subtask)
+
+    async with anyio.create_task_group() as tg:
+        tg.start_soon(starter_task)
+
+    assert initial_parent_id != permanent_parent_id
+    assert initial_parent_id == starter_task_id
+    assert permanent_parent_id == root_task_id
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-3.3.4/tests/test_to_thread.py 
new/anyio-3.4.0/tests/test_to_thread.py
--- old/anyio-3.3.4/tests/test_to_thread.py     2021-10-16 12:45:31.000000000 
+0200
+++ new/anyio-3.4.0/tests/test_to_thread.py     2021-11-23 00:57:30.000000000 
+0100
@@ -3,10 +3,12 @@
 import threading
 import time
 from concurrent.futures import Future
+from contextvars import ContextVar
 from functools import partial
 from typing import Any, List, NoReturn, Optional
 
 import pytest
+import sniffio
 
 import anyio.to_thread
 from anyio import (
@@ -132,6 +134,17 @@
     assert future.result(1)
 
 
+async def test_contextvar_propagation() -> None:
+    var = ContextVar('var', default=1)
+    var.set(6)
+    assert await to_thread.run_sync(var.get) == 6
+
+
+async def test_asynclib_detection() -> None:
+    with pytest.raises(sniffio.AsyncLibraryNotFoundError):
+        await to_thread.run_sync(sniffio.current_async_library)
+
+
 @pytest.mark.parametrize('anyio_backend', ['asyncio'])
 async def test_asyncio_cancel_native_task() -> None:
     task: "Optional[asyncio.Task[None]]" = None

Reply via email to