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 2024-03-14 17:42:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-anyio (Old) and /work/SRC/openSUSE:Factory/.python-anyio.new.1905 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-anyio" Thu Mar 14 17:42:27 2024 rev:20 rq:1157062 version:4.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-anyio/python-anyio.changes 2024-01-21 23:07:44.088146221 +0100 +++ /work/SRC/openSUSE:Factory/.python-anyio.new.1905/python-anyio.changes 2024-03-14 17:42:35.319941817 +0100 @@ -1,0 +2,14 @@ +Mon Mar 11 23:36:15 UTC 2024 - Steve Kowalik <[email protected]> + +- Update to 4.3.0: + * Added support for the Python 3.12 ``walk_up`` keyword argument in + ``anyio.Path.relative_to()`` + * Fixed passing ``total_tokens`` to ``anyio.CapacityLimiter()`` as a + keyword argument not working on the ``trio`` backend + * Fixed ``Process.aclose()`` not performing the minimum level of + necessary cleanup when cancelled + * Fixed ``Process.stdin.aclose()``, ``Process.stdout.aclose()``, and + ``Process.stderr.aclose()`` +- Add exceptiongroup to {Build,}Requires. + +------------------------------------------------------------------- Old: ---- anyio-4.2.0.tar.gz New: ---- anyio-4.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-anyio.spec ++++++ --- /var/tmp/diff_new_pack.AJJVke/_old 2024-03-14 17:42:36.027967776 +0100 +++ /var/tmp/diff_new_pack.AJJVke/_new 2024-03-14 17:42:36.031967923 +0100 @@ -18,14 +18,14 @@ %{?sle15_python_module_pythons} Name: python-anyio -Version: 4.2.0 +Version: 4.3.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 base >= 3.8} +BuildRequires: %{python_module exceptiongroup} BuildRequires: %{python_module idna >= 2.8} BuildRequires: %{python_module pip} BuildRequires: %{python_module psutil >= 5.9} @@ -37,7 +37,6 @@ BuildRequires: python-rpm-macros >= 20210127.3a18043 # SECTION test requirements BuildRequires: %{python_module hypothesis >= 4.0} -BuildRequires: %{python_module mock >= 4.0 if %python-base < 3.8} BuildRequires: %{python_module pytest >= 7.0} BuildRequires: %{python_module pytest-mock >= 3.6.1} BuildRequires: %{python_module trio >= 0.23} @@ -46,10 +45,9 @@ BuildRequires: fdupes Requires: python-idna >= 2.8 Requires: python-sniffio >= 1.1 -Requires: (python-typing_extensions if python-base < 3.11) -%if 0%{?python_version_nodots} < 37 -Requires: python-contextvars -Requires: python-dataclasses +%if 0%{?python_version_nodots} < 311 +Requires: python-exceptiongroup +Requires: python-typing_extensions %endif Suggests: python-trio >= 0.23 BuildArch: noarch @@ -95,5 +93,5 @@ %doc README.rst %license LICENSE %{python_sitelib}/anyio -%{python_sitelib}/anyio-%{version}*-info +%{python_sitelib}/anyio-%{version}.dist-info ++++++ anyio-4.2.0.tar.gz -> anyio-4.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/.github/workflows/publish.yml new/anyio-4.3.0/.github/workflows/publish.yml --- old/anyio-4.2.0/.github/workflows/publish.yml 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/.github/workflows/publish.yml 2024-02-19 09:35:33.000000000 +0100 @@ -24,7 +24,7 @@ - name: Create packages run: python -m build - name: Archive packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: dist path: dist @@ -38,7 +38,7 @@ id-token: write steps: - name: Retrieve packages - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Upload packages uses: pypa/gh-action-pypi-publish@release/v1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/.github/workflows/test.yml new/anyio-4.3.0/.github/workflows/test.yml --- old/anyio-4.2.0/.github/workflows/test.yml 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/.github/workflows/test.yml 2024-02-19 09:35:33.000000000 +0100 @@ -18,7 +18,7 @@ - uses: actions/checkout@v4 - name: Get changed files by category id: changed-files - uses: tj-actions/changed-files@v37 + uses: tj-actions/changed-files@v41 with: files_yaml: | workflow: @@ -66,11 +66,11 @@ - os: macos-latest python-version: "3.8" - os: macos-latest - python-version: "3.11" + python-version: "3.12" - os: windows-latest python-version: "3.8" - os: windows-latest - python-version: "3.11" + python-version: "3.12" runs-on: ${{ matrix.os }} needs: changed-files if: | @@ -92,12 +92,12 @@ - name: Install dependencies run: pip install -e .[test] - name: Test with pytest - run: | - coverage run -m pytest -v - coverage xml + run: coverage run -m pytest -v timeout-minutes: 5 env: PYTEST_DISABLE_PLUGIN_AUTOLOAD: 1 + - name: Generate coverage report + run: coverage xml - name: Upload Coverage uses: coverallsapp/github-action@v2 with: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/.pre-commit-config.yaml new/anyio-4.3.0/.pre-commit-config.yaml --- old/anyio-4.2.0/.pre-commit-config.yaml 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/.pre-commit-config.yaml 2024-02-19 09:35:33.000000000 +0100 @@ -16,14 +16,14 @@ - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.2.1 hooks: - id: ruff args: [--fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + rev: v1.8.0 hooks: - id: mypy additional_dependencies: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/PKG-INFO new/anyio-4.3.0/PKG-INFO --- old/anyio-4.2.0/PKG-INFO 2023-12-16 18:06:20.394697200 +0100 +++ new/anyio-4.3.0/PKG-INFO 2024-02-19 09:35:45.028421400 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: anyio -Version: 4.2.0 +Version: 4.3.0 Summary: High level compatibility layer for multiple asynchronous event loop implementations Author-email: Alex Grönholm <[email protected]> License: MIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/docs/typedattrs.rst new/anyio-4.3.0/docs/typedattrs.rst --- old/anyio-4.2.0/docs/typedattrs.rst 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/docs/typedattrs.rst 2024-02-19 09:35:33.000000000 +0100 @@ -34,21 +34,24 @@ By convention, typed attributes are stored together in a container class with other attributes of the same category:: - from anyio import TypedAttribute, TypedAttributeSet + from anyio import TypedAttributeSet, typed_attribute - class MyTypedAttribute: - string_valued_attribute = TypedAttribute[str]() - some_float_attribute = TypedAttribute[float]() + class MyTypedAttribute(TypedAttributeSet): + string_valued_attribute: str = typed_attribute() + some_float_attribute: float = typed_attribute() To provide values for these attributes, implement the :meth:`~.TypedAttributeProvider.extra_attributes` property in your class:: + from collections.abc import Callable, Mapping + from anyio import TypedAttributeProvider class MyAttributeProvider(TypedAttributeProvider): - def extra_attributes(): + @property + def extra_attributes() -> Mapping[Any, Callable[[], Any]]: return { MyTypedAttribute.string_valued_attribute: lambda: 'my attribute value', MyTypedAttribute.some_float_attribute: lambda: 6.492 @@ -58,7 +61,8 @@ attributes in the return value:: class AnotherAttributeProvider(MyAttributeProvider): - def extra_attributes(): + @property + def extra_attributes() -> Mapping[Any, Callable[[], Any]]: return { **super().extra_attributes, MyTypedAttribute.string_valued_attribute: lambda: 'overridden attribute value' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/docs/versionhistory.rst new/anyio-4.3.0/docs/versionhistory.rst --- old/anyio-4.2.0/docs/versionhistory.rst 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/docs/versionhistory.rst 2024-02-19 09:35:33.000000000 +0100 @@ -3,6 +3,27 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_. +**4.3.0** + +- Added support for the Python 3.12 ``walk_up`` keyword argument in + ``anyio.Path.relative_to()`` (PR by Colin Taylor) +- Fixed passing ``total_tokens`` to ``anyio.CapacityLimiter()`` as a keyword argument + not working on the ``trio`` backend + (`#515 <https://github.com/agronholm/anyio/issues/515>`_) +- Fixed ``Process.aclose()`` not performing the minimum level of necessary cleanup when + cancelled. Previously: + + - Cancellation of ``Process.aclose()`` could leak an orphan process + - Cancellation of ``run_process()`` could very briefly leak an orphan process. + - Cancellation of ``Process.aclose()`` or ``run_process()`` on Trio could leave + standard streams unclosed + + (PR by Ganden Schaffner) +- Fixed ``Process.stdin.aclose()``, ``Process.stdout.aclose()``, and + ``Process.stderr.aclose()`` not including a checkpoint on asyncio (PR by Ganden + Schaffner) +- Fixed documentation on how to provide your own typed attributes + **4.2.0** - Add support for ``byte``-based paths in ``connect_unix``, ``create_unix_listeners``, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio/_backends/_asyncio.py new/anyio-4.3.0/src/anyio/_backends/_asyncio.py --- old/anyio-4.2.0/src/anyio/_backends/_asyncio.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/src/anyio/_backends/_asyncio.py 2024-02-19 09:35:33.000000000 +0100 @@ -918,6 +918,7 @@ async def aclose(self) -> None: self._stream.feed_eof() + await AsyncIOBackend.checkpoint() @dataclass(eq=False) @@ -930,6 +931,7 @@ async def aclose(self) -> None: self._stream.close() + await AsyncIOBackend.checkpoint() @dataclass(eq=False) @@ -940,14 +942,22 @@ _stderr: StreamReaderWrapper | None async def aclose(self) -> None: - if self._stdin: - await self._stdin.aclose() - if self._stdout: - await self._stdout.aclose() - if self._stderr: - await self._stderr.aclose() + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() - await self.wait() + raise async def wait(self) -> int: return await self._process.wait() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio/_backends/_trio.py new/anyio-4.3.0/src/anyio/_backends/_trio.py --- old/anyio-4.2.0/src/anyio/_backends/_trio.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/src/anyio/_backends/_trio.py 2024-02-19 09:35:33.000000000 +0100 @@ -283,14 +283,21 @@ _stderr: abc.ByteReceiveStream | None async def aclose(self) -> None: - if self._stdin: - await self._stdin.aclose() - if self._stdout: - await self._stdout.aclose() - if self._stderr: - await self._stderr.aclose() - - await self.wait() + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() + raise async def wait(self) -> int: return await self._process.wait() @@ -631,14 +638,24 @@ class CapacityLimiter(BaseCapacityLimiter): def __new__( - cls, *args: Any, original: trio.CapacityLimiter | None = None + cls, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, ) -> CapacityLimiter: return object.__new__(cls) def __init__( - self, *args: Any, original: trio.CapacityLimiter | None = None + self, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, ) -> None: - self.__original = original or trio.CapacityLimiter(*args) + if original is not None: + self.__original = original + else: + assert total_tokens is not None + self.__original = trio.CapacityLimiter(total_tokens) async def __aenter__(self) -> None: return await self.__original.__aenter__() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio/_core/_fileio.py new/anyio-4.3.0/src/anyio/_core/_fileio.py --- old/anyio-4.2.0/src/anyio/_core/_fileio.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/src/anyio/_core/_fileio.py 2024-02-19 09:35:33.000000000 +0100 @@ -514,8 +514,17 @@ ) -> str: return await to_thread.run_sync(self._path.read_text, encoding, errors) - def relative_to(self, *other: str | PathLike[str]) -> Path: - return Path(self._path.relative_to(*other)) + if sys.version_info >= (3, 12): + + def relative_to( + self, *other: str | PathLike[str], walk_up: bool = False + ) -> Path: + return Path(self._path.relative_to(*other, walk_up=walk_up)) + + else: + + def relative_to(self, *other: str | PathLike[str]) -> Path: + return Path(self._path.relative_to(*other)) async def readlink(self) -> Path: target = await to_thread.run_sync(os.readlink, self._path) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio/_core/_subprocesses.py new/anyio-4.3.0/src/anyio/_core/_subprocesses.py --- old/anyio-4.2.0/src/anyio/_core/_subprocesses.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/src/anyio/_core/_subprocesses.py 2024-02-19 09:35:33.000000000 +0100 @@ -65,20 +65,18 @@ start_new_session=start_new_session, ) as process: stream_contents: list[bytes | None] = [None, None] - try: - async with create_task_group() as tg: - if process.stdout: - tg.start_soon(drain_stream, process.stdout, 0) - if process.stderr: - tg.start_soon(drain_stream, process.stderr, 1) - if process.stdin and input: - await process.stdin.send(input) - await process.stdin.aclose() + async with create_task_group() as tg: + if process.stdout: + tg.start_soon(drain_stream, process.stdout, 0) - await process.wait() - except BaseException: - process.kill() - raise + if process.stderr: + tg.start_soon(drain_stream, process.stderr, 1) + + if process.stdin and input: + await process.stdin.send(input) + await process.stdin.aclose() + + await process.wait() output, errors = stream_contents if check and process.returncode != 0: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio/_core/_synchronization.py new/anyio-4.3.0/src/anyio/_core/_synchronization.py --- old/anyio-4.2.0/src/anyio/_core/_synchronization.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/src/anyio/_core/_synchronization.py 2024-02-19 09:35:33.000000000 +0100 @@ -623,6 +623,8 @@ :param action: the action to guard against (visible in the :exc:`BusyResourceError` when triggered, e.g. "Another task is already {action} this resource") + + .. versionadded:: 4.1 """ __slots__ = "action", "_guarded" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/src/anyio.egg-info/PKG-INFO new/anyio-4.3.0/src/anyio.egg-info/PKG-INFO --- old/anyio-4.2.0/src/anyio.egg-info/PKG-INFO 2023-12-16 18:06:20.000000000 +0100 +++ new/anyio-4.3.0/src/anyio.egg-info/PKG-INFO 2024-02-19 09:35:44.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: anyio -Version: 4.2.0 +Version: 4.3.0 Summary: High level compatibility layer for multiple asynchronous event loop implementations Author-email: Alex Grönholm <[email protected]> License: MIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/streams/test_tls.py new/anyio-4.3.0/tests/streams/test_tls.py --- old/anyio-4.2.0/tests/streams/test_tls.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/streams/test_tls.py 2024-02-19 09:35:33.000000000 +0100 @@ -2,7 +2,6 @@ import socket import ssl -import sys from contextlib import ExitStack from threading import Thread from typing import ContextManager, NoReturn @@ -26,10 +25,6 @@ from anyio.streams.tls import TLSAttribute, TLSListener, TLSStream pytestmark = pytest.mark.anyio -skip_on_broken_openssl = pytest.mark.skipif( - (ssl.OPENSSL_VERSION_INFO[0] > 1 and sys.version_info < (3, 8)), - reason="Python 3.7 does not work with OpenSSL versions higher than 1.X", -) class TestTLSStream: @@ -193,7 +188,6 @@ pytest.param(False, False, id="neither_standard"), ], ) - @skip_on_broken_openssl async def test_ragged_eofs( self, server_context: ssl.SSLContext, @@ -250,7 +244,6 @@ else: assert server_exc is None - @skip_on_broken_openssl async def test_ragged_eof_on_receive( self, server_context: ssl.SSLContext, client_context: ssl.SSLContext ) -> None: @@ -350,10 +343,7 @@ ca.configure_trust(client_context) if force_tlsv12: expected_pattern = r"send_eof\(\) requires at least TLSv1.3" - if hasattr(ssl, "TLSVersion"): - client_context.maximum_version = ssl.TLSVersion.TLSv1_2 - else: # Python 3.6 - client_context.options |= ssl.OP_NO_TLSv1_3 + client_context.maximum_version = ssl.TLSVersion.TLSv1_2 else: expected_pattern = ( r"send_eof\(\) has not yet been implemented for TLS streams" @@ -397,7 +387,6 @@ class TestTLSListener: - @skip_on_broken_openssl async def test_handshake_fail( self, server_context: ssl.SSLContext, caplog: pytest.LogCaptureFixture ) -> None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_fileio.py new/anyio-4.3.0/tests/test_fileio.py --- old/anyio-4.2.0/tests/test_fileio.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_fileio.py 2024-02-19 09:35:33.000000000 +0100 @@ -68,10 +68,15 @@ def populated_tmpdir(self, tmp_path: pathlib.Path) -> pathlib.Path: tmp_path.joinpath("testfile").touch() tmp_path.joinpath("testfile2").touch() + subdir = tmp_path / "subdir" - subdir.mkdir() - subdir.joinpath("dummyfile1.txt").touch() - subdir.joinpath("dummyfile2.txt").touch() + sibdir = tmp_path / "sibdir" + + for subpath in (subdir, sibdir): + subpath.mkdir() + subpath.joinpath("dummyfile1.txt").touch() + subpath.joinpath("dummyfile2.txt").touch() + return tmp_path async def test_properties(self) -> None: @@ -297,19 +302,29 @@ all_paths = [] async for path in Path(populated_tmpdir).glob("**/*.txt"): assert isinstance(path, Path) - all_paths.append(path.name) + all_paths.append(path.relative_to(populated_tmpdir)) all_paths.sort() - assert all_paths == ["dummyfile1.txt", "dummyfile2.txt"] + assert all_paths == [ + Path("sibdir") / "dummyfile1.txt", + Path("sibdir") / "dummyfile2.txt", + Path("subdir") / "dummyfile1.txt", + Path("subdir") / "dummyfile2.txt", + ] async def test_rglob(self, populated_tmpdir: pathlib.Path) -> None: all_paths = [] async for path in Path(populated_tmpdir).rglob("*.txt"): assert isinstance(path, Path) - all_paths.append(path.name) + all_paths.append(path.relative_to(populated_tmpdir)) all_paths.sort() - assert all_paths == ["dummyfile1.txt", "dummyfile2.txt"] + assert all_paths == [ + Path("sibdir") / "dummyfile1.txt", + Path("sibdir") / "dummyfile2.txt", + Path("subdir") / "dummyfile1.txt", + Path("subdir") / "dummyfile2.txt", + ] async def test_iterdir(self, populated_tmpdir: pathlib.Path) -> None: all_paths = [] @@ -318,7 +333,7 @@ all_paths.append(path.name) all_paths.sort() - assert all_paths == ["subdir", "testfile", "testfile2"] + assert all_paths == ["sibdir", "subdir", "testfile", "testfile2"] def test_joinpath(self) -> None: path = Path("/foo").joinpath("bar") @@ -421,10 +436,29 @@ path.write_text("some text åäö", encoding="utf-8") assert await Path(path).read_text(encoding="utf-8") == "some text åäö" - async def test_relative_to(self, tmp_path: pathlib.Path) -> None: + async def test_relative_to_subpath(self, tmp_path: pathlib.Path) -> None: path = tmp_path / "subdir" assert path.relative_to(tmp_path) == Path("subdir") + @pytest.mark.skipif( + sys.version_info < (3, 12), + reason="Path.relative_to(walk_up=<bool>) is only available on Python 3.12+", + ) + async def test_relative_to_sibling( + self, + populated_tmpdir: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + subdir = Path(populated_tmpdir / "subdir") + sibdir = Path(populated_tmpdir / "sibdir") + + with pytest.raises(ValueError): + subdir.relative_to(sibdir, walk_up=False) + + monkeypatch.chdir(sibdir) + relpath = subdir.relative_to(sibdir, walk_up=True) / "dummyfile1.txt" + assert os.access(relpath, os.R_OK) + async def test_rename(self, tmp_path: pathlib.Path) -> None: path = tmp_path / "somefile" path.touch() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_from_thread.py new/anyio-4.3.0/tests/test_from_thread.py --- old/anyio-4.2.0/tests/test_from_thread.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_from_thread.py 2024-02-19 09:35:33.000000000 +0100 @@ -220,9 +220,6 @@ 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: @@ -572,9 +569,6 @@ 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: @@ -585,9 +579,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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_sockets.py new/anyio-4.3.0/tests/test_sockets.py --- old/anyio-4.2.0/tests/test_sockets.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_sockets.py 2024-02-19 09:35:33.000000000 +0100 @@ -7,6 +7,7 @@ import platform import socket import sys +import tempfile import threading import time from contextlib import suppress @@ -14,7 +15,7 @@ from socket import AddressFamily from ssl import SSLContext, SSLError from threading import Thread -from typing import Any, Iterable, Iterator, NoReturn, TypeVar, cast +from typing import Any, Generator, Iterable, Iterator, NoReturn, TypeVar, cast import psutil import pytest @@ -511,6 +512,7 @@ assert not caplog.text [email protected] class TestTCPListener: async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None: async with await create_tcp_listener( @@ -707,8 +709,11 @@ ) class TestUNIXStream: @pytest.fixture - def socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("socket") + def socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "socket" @pytest.fixture(params=[False, True], ids=["str", "path"]) def socket_path_or_str(self, request: SubRequest, socket_path: Path) -> Path | str: @@ -1026,8 +1031,11 @@ ) class TestUNIXListener: @pytest.fixture - def socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("socket") + def socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "socket" @pytest.fixture(params=[False, True], ids=["str", "path"]) def socket_path_or_str(self, request: SubRequest, socket_path: Path) -> Path | str: @@ -1140,37 +1148,40 @@ client_addresses: list[str | IPSockAddrType] = [] listeners: list[Listener] = [await create_tcp_listener(local_host="localhost")] - if sys.platform != "win32": - socket_path = tmp_path_factory.mktemp("unix").joinpath("socket") - listeners.append(await create_unix_listener(socket_path)) - - expected_addresses: list[str | IPSockAddrType] = [] - async with MultiListener(listeners) as multi_listener: - async with create_task_group() as tg: - tg.start_soon(multi_listener.serve, handle) - for listener in multi_listener.listeners: - event = Event() - local_address = listener.extra(SocketAttribute.local_address) - if ( - sys.platform != "win32" - and listener.extra(SocketAttribute.family) - == socket.AddressFamily.AF_UNIX - ): - assert isinstance(local_address, str) - stream: SocketStream = await connect_unix(local_address) - else: - assert isinstance(local_address, tuple) - stream = await connect_tcp(*local_address) + with tempfile.TemporaryDirectory() as path: + if sys.platform != "win32": + listeners.append(await create_unix_listener(Path(path) / "socket")) - expected_addresses.append(stream.extra(SocketAttribute.local_address)) - await event.wait() - await stream.aclose() + expected_addresses: list[str | IPSockAddrType] = [] + async with MultiListener(listeners) as multi_listener: + async with create_task_group() as tg: + tg.start_soon(multi_listener.serve, handle) + for listener in multi_listener.listeners: + event = Event() + local_address = listener.extra(SocketAttribute.local_address) + if ( + sys.platform != "win32" + and listener.extra(SocketAttribute.family) + == socket.AddressFamily.AF_UNIX + ): + assert isinstance(local_address, str) + stream: SocketStream = await connect_unix(local_address) + else: + assert isinstance(local_address, tuple) + stream = await connect_tcp(*local_address) - tg.cancel_scope.cancel() + expected_addresses.append( + stream.extra(SocketAttribute.local_address) + ) + await event.wait() + await stream.aclose() + + tg.cancel_scope.cancel() - assert client_addresses == expected_addresses + assert client_addresses == expected_addresses [email protected] @pytest.mark.usefixtures("check_asyncio_bug") class TestUDPSocket: async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None: @@ -1296,6 +1307,7 @@ assert local_address[1] > 0 [email protected] @pytest.mark.usefixtures("check_asyncio_bug") class TestConnectedUDPSocket: async def test_extra_attributes(self, family: AnyIPAddressFamily) -> None: @@ -1423,16 +1435,22 @@ ) class TestUNIXDatagramSocket: @pytest.fixture - def socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("socket") + def socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "socket" @pytest.fixture(params=[False, True], ids=["str", "path"]) def socket_path_or_str(self, request: SubRequest, socket_path: Path) -> Path | str: return socket_path if request.param else str(socket_path) @pytest.fixture - def peer_socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("peer_socket") + def peer_socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "peer_socket" async def test_extra_attributes(self, socket_path: Path) -> None: async with await create_unix_datagram_socket(local_path=socket_path) as unix_dg: @@ -1545,16 +1563,22 @@ ) class TestConnectedUNIXDatagramSocket: @pytest.fixture - def socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("socket") + def socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "socket" @pytest.fixture(params=[False, True], ids=["str", "path"]) def socket_path_or_str(self, request: SubRequest, socket_path: Path) -> Path | str: return socket_path if request.param else str(socket_path) @pytest.fixture - def peer_socket_path(self, tmp_path_factory: TempPathFactory) -> Path: - return tmp_path_factory.mktemp("unix").joinpath("peer_socket") + def peer_socket_path(self) -> Generator[Path, None, None]: + # Use stdlib tempdir generation + # Fixes `OSError: AF_UNIX path too long` from pytest generated temp_path + with tempfile.TemporaryDirectory() as path: + yield Path(path) / "peer_socket" @pytest.fixture(params=[False, True], ids=["peer_str", "peer_path"]) def peer_socket_path_or_str( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_subprocesses.py new/anyio-4.3.0/tests/test_subprocesses.py --- old/anyio-4.2.0/tests/test_subprocesses.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_subprocesses.py 2024-02-19 09:35:33.000000000 +0100 @@ -8,23 +8,14 @@ from textwrap import dedent import pytest +from _pytest.fixtures import FixtureRequest -from anyio import open_process, run_process +from anyio import CancelScope, ClosedResourceError, open_process, run_process from anyio.streams.buffered import BufferedByteReceiveStream pytestmark = pytest.mark.anyio [email protected](autouse=True) -def check_compatibility(anyio_backend_name: str) -> None: - if anyio_backend_name == "asyncio": - if platform.system() == "Windows" and sys.version_info < (3, 8): - pytest.skip( - "Python < 3.8 uses SelectorEventLoop by default and it does not " - "support subprocesses" - ) - - @pytest.mark.parametrize( "shell, command", [ @@ -176,3 +167,61 @@ out, err = capfd.readouterr() assert out == "stdout-text" + os.linesep assert err == "stderr-text" + os.linesep + + +async def test_process_aexit_cancellation_doesnt_orphan_process() -> None: + """ + Regression test for #669. + + Ensures that open_process.__aexit__() doesn't leave behind an orphan process when + cancelled. + + """ + with CancelScope() as scope: + async with await open_process( + [sys.executable, "-c", "import time; time.sleep(1)"] + ) as process: + scope.cancel() + + assert process.returncode is not None + assert process.returncode != 0 + + +async def test_process_aexit_cancellation_closes_standard_streams( + request: FixtureRequest, + anyio_backend_name: str, +) -> None: + """ + Regression test for #669. + + Ensures that open_process.__aexit__() closes standard streams when cancelled. Also + ensures that process.std{in.send,{out,err}.receive}() raise ClosedResourceError on a + closed stream. + + """ + if anyio_backend_name == "asyncio": + # Avoid pytest.xfail here due to https://github.com/pytest-dev/pytest/issues/9027 + request.node.add_marker( + pytest.mark.xfail(reason="#671 needs to be resolved first") + ) + + with CancelScope() as scope: + async with await open_process( + [sys.executable, "-c", "import time; time.sleep(1)"] + ) as process: + scope.cancel() + + assert process.stdin is not None + + with pytest.raises(ClosedResourceError): + await process.stdin.send(b"foo") + + assert process.stdout is not None + + with pytest.raises(ClosedResourceError): + await process.stdout.receive(1) + + assert process.stderr is not None + + with pytest.raises(ClosedResourceError): + await process.stderr.receive(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_synchronization.py new/anyio-4.3.0/tests/test_synchronization.py --- old/anyio-4.2.0/tests/test_synchronization.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_synchronization.py 2024-02-19 09:35:33.000000000 +0100 @@ -689,3 +689,8 @@ backend=anyio_backend_name, backend_options=anyio_backend_options, ) + + async def test_total_tokens_as_kwarg(self) -> None: + # Regression test for #515 + limiter = CapacityLimiter(total_tokens=1) + assert limiter.total_tokens == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_taskgroups.py new/anyio-4.3.0/tests/test_taskgroups.py --- old/anyio-4.2.0/tests/test_taskgroups.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_taskgroups.py 2024-02-19 09:35:33.000000000 +0100 @@ -185,6 +185,9 @@ assert not finished [email protected]( + sys.version_info < (3, 9), reason="Requires a way to detect cancellation source" +) @pytest.mark.parametrize("anyio_backend", ["asyncio"]) async def test_start_native_host_cancelled() -> None: started = finished = False @@ -199,9 +202,6 @@ async with create_task_group() as tg: await tg.start(taskfunc) - if sys.version_info < (3, 9): - pytest.xfail("Requires a way to detect cancellation source") - task = asyncio.get_running_loop().create_task(start_another()) await wait_all_tasks_blocked() task.cancel() @@ -530,7 +530,7 @@ reason="There is currently no way to tell if cancellation happened due to timeout " "explicitly if the deadline has been exceeded" ) -async def test_fail_after_scope_camcelled_before_timeout() -> None: +async def test_fail_after_scope_cancelled_before_timeout() -> None: with fail_after(0.1) as scope: scope.cancel() time.sleep(0.11) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/anyio-4.2.0/tests/test_to_process.py new/anyio-4.3.0/tests/test_to_process.py --- old/anyio-4.2.0/tests/test_to_process.py 2023-12-16 18:06:12.000000000 +0100 +++ new/anyio-4.3.0/tests/test_to_process.py 2024-02-19 09:35:33.000000000 +0100 @@ -1,7 +1,6 @@ from __future__ import annotations import os -import platform import sys import time from functools import partial @@ -21,16 +20,6 @@ pytestmark = pytest.mark.anyio [email protected](autouse=True) -def check_compatibility(anyio_backend_name: str) -> None: - if anyio_backend_name == "asyncio": - if platform.system() == "Windows" and sys.version_info < (3, 8): - pytest.skip( - "Python < 3.8 uses SelectorEventLoop by default and it does not " - "support subprocesses" - ) - - async def test_run_sync_in_process_pool() -> None: """ Test that the function runs in a different process, and the same process in both
