diff --git a/.coveragerc-cython.toml b/.coveragerc-cython.toml index 586dc937786..91336d47dd9 100644 --- a/.coveragerc-cython.toml +++ b/.coveragerc-cython.toml @@ -8,6 +8,9 @@ omit = [ ] [report] +partial_also = [ + 'if not TYPE_CHECKING', +] exclude_also = [ 'if TYPE_CHECKING', 'assert False', diff --git a/.coveragerc.toml b/.coveragerc.toml index e1a2dc7b42c..409061e3514 100644 --- a/.coveragerc.toml +++ b/.coveragerc.toml @@ -14,6 +14,9 @@ omit = [ ] [report] +partial_also = [ + 'if not TYPE_CHECKING', +] exclude_also = [ 'if TYPE_CHECKING', 'assert False', diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 9924239f018..ee07408e980 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -268,6 +268,59 @@ jobs: report_type: test_results token: ${{ secrets.CODECOV_TOKEN }} + test-mobile: + permissions: + contents: read # to fetch code (actions/checkout) + + name: Test (${{ matrix.config.platform }}, ${{ matrix.pyver }}, ${{ matrix.config.os }}) + runs-on: ${{ matrix.config.os }} + needs: gen_llhttp + strategy: + matrix: + pyver: ["cp313", "cp314"] + config: + - os: ubuntu-latest + platform: android + archs: x86_64 + - os: macos-14 + platform: ios + archs: arm64_iphonesimulator + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: true + - name: Setup Python ${{ matrix.pyver }} + id: python-install + # important: do not use system python + env: + UV_PYTHON_PREFERENCE: only-managed + uses: astral-sh/setup-uv@v8.1.0 + with: + python-version: ${{ matrix.pyver }} + activate-environment: true + enable-cache: true + - name: Install build tooling and cython + run: | + uv pip install -U pip wheel setuptools build twine -r requirements/cython.in -c requirements/cython.txt + - name: Restore llhttp generated files + uses: actions/download-artifact@v8 + with: + name: llhttp + path: vendor/llhttp/build/ + - name: Cythonize + run: | + make cythonize + - name: Build wheels and test + uses: pypa/cibuildwheel@v3.4.1 + env: + CIBW_BUILD: ${{ matrix.pyver }}-* + CIBW_PLATFORM: ${{ matrix.config.platform }} + CIBW_ARCHS: ${{ matrix.config.archs }} + CIBW_TEST_REQUIRES: -r requirements/test-mobile.txt + CIBW_TEST_SOURCES: setup.cfg README.rst tests + CIBW_TEST_COMMAND: python -m pytest + autobahn: permissions: contents: read # to fetch code (actions/checkout) @@ -451,6 +504,7 @@ jobs: needs: - lint - test + - test-mobile - autobahn runs-on: ubuntu-latest @@ -518,7 +572,7 @@ jobs: permissions: contents: read # to fetch code (actions/checkout) - name: Build wheels on ${{ matrix.os }} ${{ matrix.qemu }} ${{ matrix.musl }} + name: Build wheels on ${{ matrix.os }} ${{ matrix.qemu }} ${{ matrix.musl }} ${{ matrix.platform }} runs-on: ${{ matrix.os }} needs: pre-deploy strategy: @@ -526,6 +580,7 @@ jobs: os: ["ubuntu-latest", "windows-latest", "windows-11-arm", "macos-latest", "ubuntu-24.04-arm"] qemu: [''] musl: [""] + platform: [""] include: # Split ubuntu/musl jobs for the sake of speed-up - os: ubuntu-latest @@ -561,6 +616,10 @@ jobs: musl: musllinux - os: ubuntu-24.04-arm musl: musllinux + - os: ubuntu-latest + platform: android + - os: macos-14 + platform: ios steps: - name: Checkout uses: actions/checkout@v6 @@ -615,14 +674,19 @@ jobs: # for those QEMU matrix cells. extras: uv env: + CIBW_PLATFORM: ${{ matrix.platform || 'auto' }} CIBW_SKIP: pp* ${{ matrix.musl == 'musllinux' && '*manylinux*' || '*musllinux*' }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + CIBW_ARCHS_IOS: arm64_iphoneos arm64_iphonesimulator x86_64_iphonesimulator + CIBW_ARCHS_ANDROID: arm64_v8a x86_64 - name: Upload wheels uses: actions/upload-artifact@v7 with: name: >- dist-${{ matrix.os }}-${{ matrix.musl }}-${{ - matrix.qemu + matrix.platform + && matrix.platform + || matrix.qemu && matrix.qemu || 'native' }} diff --git a/CHANGES/11750.packaging.rst b/CHANGES/11750.packaging.rst new file mode 100644 index 00000000000..2af5e11cf45 --- /dev/null +++ b/CHANGES/11750.packaging.rst @@ -0,0 +1 @@ +Added wheels for Android and iOS platforms -- by :user:`timrid`. diff --git a/CHANGES/12722.bugfix.rst b/CHANGES/12722.bugfix.rst new file mode 100644 index 00000000000..af8b103f566 --- /dev/null +++ b/CHANGES/12722.bugfix.rst @@ -0,0 +1 @@ +Fixed :py:meth:`~aiohttp.FormData.add_field` accepting invalid bytes in ``name`` and ``filename`` -- by :user:`Dreamsorcerer`. diff --git a/CHANGES/12727.bugfix.rst b/CHANGES/12727.bugfix.rst new file mode 100644 index 00000000000..d74b5eae2d3 --- /dev/null +++ b/CHANGES/12727.bugfix.rst @@ -0,0 +1 @@ +Fixed websocket upgrade occurring when header contained a value like `notupgrade` -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/formdata.py b/aiohttp/formdata.py index 50dae68244f..80e3d39ec15 100644 --- a/aiohttp/formdata.py +++ b/aiohttp/formdata.py @@ -8,6 +8,7 @@ from . import hdrs, multipart, payload from .helpers import guess_filename +from .http_writer import _safe_header from .payload import Payload __all__ = ("FormData",) @@ -56,12 +57,14 @@ def add_field( if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)): self._is_multipart = True + _safe_header(name) type_options: MultiDict[str] = MultiDict({"name": name}) if filename is not None and not isinstance(filename, str): raise TypeError("filename must be an instance of str. Got: %s" % filename) if filename is None and isinstance(value, io.IOBase): filename = guess_filename(value, name) if filename is not None: + _safe_header(filename) type_options["filename"] = filename self._is_multipart = True @@ -71,11 +74,7 @@ def add_field( raise TypeError( "content_type must be an instance of str. Got: %s" % content_type ) - if "\r" in content_type or "\n" in content_type: - raise ValueError( - "Newline or carriage return detected in headers. " - "Potential header injection attack." - ) + _safe_header(content_type) headers[hdrs.CONTENT_TYPE] = content_type self._is_multipart = True diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 2fd3fef78de..8cef43de753 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -606,6 +606,9 @@ def make_mocked_request( raw_hdrs = () chunked = "chunked" in headers.get(hdrs.TRANSFER_ENCODING, "").lower() + upgrade = headers.get(hdrs.CONNECTION, "").lower() == "upgrade" and bool( + headers.get(hdrs.UPGRADE) + ) message = RawRequestMessage( method, @@ -615,7 +618,7 @@ def make_mocked_request( raw_hdrs, closing, None, - False, + upgrade, chunked, URL(path), ) diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index 9ec478e46f5..4f423950832 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -279,7 +279,7 @@ def _handshake( ) ) - if "upgrade" not in headers.get(hdrs.CONNECTION, "").lower(): + if not request._message.upgrade: raise HTTPBadRequest( text=f"No CONNECTION upgrade hdr: {headers.get(hdrs.CONNECTION)}" ) diff --git a/pyproject.toml b/pyproject.toml index 06f26bc6995..0c27cc88bb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,10 +48,10 @@ dynamic = [ [project.optional-dependencies] speedups = [ - "aiodns >= 3.3.0", - "Brotli >= 1.2; platform_python_implementation == 'CPython'", + "aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios'", + "Brotli >= 1.2; platform_python_implementation == 'CPython' and sys_platform != 'android' and sys_platform != 'ios'", "brotlicffi >= 1.2; platform_python_implementation != 'CPython'", - "backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14'", + "backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' and sys_platform != 'android' and sys_platform != 'ios'", ] [[project.maintainers]] @@ -170,6 +170,10 @@ test-command = "" # don't build PyPy wheels, install from source instead skip = "pp*" +[tool.cibuildwheel.ios] +# iOS currently does not support build[uv] +build-frontend = "build" + [tool.codespell] skip = '.git,*.pdf,*.svg,Makefile,CONTRIBUTORS.txt,venvs,_build' ignore-words-list = 'te,assertIn' diff --git a/requirements/base-ft.txt b/requirements/base-ft.txt index b138168161f..95b2c278df2 100644 --- a/requirements/base-ft.txt +++ b/requirements/base-ft.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/base-ft.txt --strip-extras requirements/base-ft.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.6.2 # via -r requirements/runtime-deps.in @@ -12,9 +12,9 @@ aiosignal==1.4.0 # via -r requirements/runtime-deps.in async-timeout==5.0.1 ; python_version < "3.11" # via -r requirements/runtime-deps.in -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in cffi==2.0.0 # via pycares diff --git a/requirements/base.txt b/requirements/base.txt index 7b3e4a6b51b..4f8536ea416 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.6.2 # via -r requirements/runtime-deps.in @@ -12,9 +12,9 @@ aiosignal==1.4.0 # via -r requirements/runtime-deps.in async-timeout==5.0.1 ; python_version < "3.11" # via -r requirements/runtime-deps.in -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in cffi==2.0.0 # via pycares diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 317b891ba58..2102be4c3de 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -2,9 +2,9 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/constraints.txt --strip-extras requirements/constraints.in +# pip-compile --output-file=requirements/constraints.txt --strip-extras --unsafe-package=aiohttp requirements/constraints.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via # -r requirements/lint.in # -r requirements/runtime-deps.in @@ -12,8 +12,6 @@ aiohappyeyeballs==2.6.2 # via # -r requirements/runtime-deps.in # aiohttp -aiohttp==3.13.5 - # via pytest-aiohttp aiohttp-theme==0.1.7 # via -r requirements/doc.in aiosignal==1.4.0 @@ -45,7 +43,7 @@ blockbuster==1.5.26 # via # -r requirements/lint.in # -r requirements/test-common.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in build==1.5.0 # via pip-tools @@ -92,7 +90,7 @@ forbiddenfruit==0.1.4 freezegun==1.5.5 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in frozenlist==1.8.0 # via # -r requirements/runtime-deps.in @@ -159,6 +157,8 @@ packaging==26.2 # wheel pathspec==1.1.1 # via mypy +pip==26.1.1 + # via pip-tools pip-tools==7.5.3 # via -r requirements/dev.in pkgconfig==1.6.0 @@ -181,7 +181,7 @@ propcache==0.5.2 proxy-py==2.4.10 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in pycares==5.0.1 # via aiodns pycparser==3.0 @@ -204,7 +204,7 @@ pyproject-hooks==1.2.0 pytest==9.0.3 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in # pytest-aiohttp # pytest-asyncio # pytest-codspeed @@ -223,13 +223,13 @@ pytest-codspeed==5.0.3 # -r requirements/lint.in # -r requirements/test-common.in pytest-cov==7.1.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-mock==3.15.1 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in pytest-timeout==2.4.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-xdist==3.8.0 # via -r requirements/test-common.in python-dateutil==2.9.0.post0 @@ -251,8 +251,10 @@ requests==2.34.2 # sphinxcontrib-spelling rich==15.0.0 # via pytest-codspeed +setuptools==82.0.1 + # via pip-tools setuptools-git==1.2 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in six==1.17.0 # via python-dateutil slotscheck==0.19.1 @@ -329,7 +331,7 @@ valkey==6.1.1 virtualenv==21.4.1 # via pre-commit wait-for-it==2.3.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in wheel==0.47.0 # via pip-tools yarl==1.24.2 @@ -342,7 +344,4 @@ zlib-ng==1.0.0 # -r requirements/test-common.in # The following packages are considered to be unsafe in a requirements file: -pip==26.1.1 - # via pip-tools -setuptools==82.0.1 - # via pip-tools +# aiohttp diff --git a/requirements/dev.txt b/requirements/dev.txt index a746c370577..54af9df4084 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,9 +2,9 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/dev.txt --strip-extras requirements/dev.in +# pip-compile --output-file=requirements/dev.txt --strip-extras --unsafe-package=aiohttp requirements/dev.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via # -r requirements/lint.in # -r requirements/runtime-deps.in @@ -12,8 +12,6 @@ aiohappyeyeballs==2.6.2 # via # -r requirements/runtime-deps.in # aiohttp -aiohttp==3.13.5 - # via pytest-aiohttp aiohttp-theme==0.1.7 # via -r requirements/doc.in aiosignal==1.4.0 @@ -37,7 +35,7 @@ babel==2.18.0 # via sphinx backports-asyncio-runner==1.2.0 # via pytest-asyncio -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via # -r requirements/lint.in # -r requirements/runtime-deps.in @@ -45,7 +43,7 @@ blockbuster==1.5.26 # via # -r requirements/lint.in # -r requirements/test-common.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in build==1.5.0 # via pip-tools @@ -90,7 +88,7 @@ forbiddenfruit==0.1.4 freezegun==1.5.5 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in frozenlist==1.8.0 # via # -r requirements/runtime-deps.in @@ -156,6 +154,8 @@ packaging==26.2 # wheel pathspec==1.1.1 # via mypy +pip==26.1.1 + # via pip-tools pip-tools==7.5.3 # via -r requirements/dev.in pkgconfig==1.6.0 @@ -178,7 +178,7 @@ propcache==0.5.2 proxy-py==2.4.10 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in pycares==5.0.1 # via aiodns pycparser==3.0 @@ -199,7 +199,7 @@ pyproject-hooks==1.2.0 pytest==9.0.3 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in # pytest-aiohttp # pytest-asyncio # pytest-codspeed @@ -218,13 +218,13 @@ pytest-codspeed==5.0.3 # -r requirements/lint.in # -r requirements/test-common.in pytest-cov==7.1.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-mock==3.15.1 # via # -r requirements/lint.in - # -r requirements/test-common.in + # -r requirements/test-common-base.in pytest-timeout==2.4.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-xdist==3.8.0 # via -r requirements/test-common.in python-dateutil==2.9.0.post0 @@ -244,8 +244,10 @@ requests==2.34.2 # via sphinx rich==15.0.0 # via pytest-codspeed +setuptools==82.0.1 + # via pip-tools setuptools-git==1.2 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in six==1.17.0 # via python-dateutil slotscheck==0.19.1 @@ -319,7 +321,7 @@ valkey==6.1.1 virtualenv==21.4.1 # via pre-commit wait-for-it==2.3.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in wheel==0.47.0 # via pip-tools yarl==1.24.2 @@ -332,7 +334,4 @@ zlib-ng==1.0.0 # -r requirements/test-common.in # The following packages are considered to be unsafe in a requirements file: -pip==26.1.1 - # via pip-tools -setuptools==82.0.1 - # via pip-tools +# aiohttp diff --git a/requirements/lint.txt b/requirements/lint.txt index b24f04640e6..afe50abb5d0 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/lint.txt --strip-extras requirements/lint.in +# pip-compile --unsafe-package=aiohttp --output-file=requirements/lint.txt --strip-extras requirements/lint.in # aiodns==4.0.4 # via -r requirements/lint.in diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index 3e3c4d05313..d70fc5a9dbc 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,11 +1,11 @@ # Extracted from `pyproject.toml` via `make sync-direct-runtime-deps` -aiodns >= 3.3.0 +aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios' aiohappyeyeballs >= 2.5.0 aiosignal >= 1.4.0 async-timeout >= 4.0, < 6.0 ; python_version < '3.11' -backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' -Brotli >= 1.2; platform_python_implementation == 'CPython' +backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' and sys_platform != 'android' and sys_platform != 'ios' +Brotli >= 1.2; platform_python_implementation == 'CPython' and sys_platform != 'android' and sys_platform != 'ios' brotlicffi >= 1.2; platform_python_implementation != 'CPython' frozenlist >= 1.1.1 multidict >=4.5, < 7.0 diff --git a/requirements/runtime-deps.txt b/requirements/runtime-deps.txt index 34093be76e4..b553d1fe24b 100644 --- a/requirements/runtime-deps.txt +++ b/requirements/runtime-deps.txt @@ -2,9 +2,9 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --resolver=backtracking --strip-extras requirements/runtime-deps.in +# pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.6.2 # via -r requirements/runtime-deps.in @@ -12,9 +12,9 @@ aiosignal==1.4.0 # via -r requirements/runtime-deps.in async-timeout==5.0.1 ; python_version < "3.11" # via -r requirements/runtime-deps.in -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in cffi==2.0.0 # via pycares diff --git a/requirements/test-common-base.in b/requirements/test-common-base.in new file mode 100644 index 00000000000..07ad1eb6105 --- /dev/null +++ b/requirements/test-common-base.in @@ -0,0 +1,10 @@ +freezegun +pkgconfig +proxy.py >= 2.4.4rc5 +pytest +pytest-aiohttp +pytest-cov +pytest-mock +pytest-timeout +setuptools-git +wait-for-it diff --git a/requirements/test-common-base.txt b/requirements/test-common-base.txt new file mode 100644 index 00000000000..14a15382b14 --- /dev/null +++ b/requirements/test-common-base.txt @@ -0,0 +1,93 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements/test-common-base.txt --strip-extras --unsafe-package=aiohttp requirements/test-common-base.in +# +aiohappyeyeballs==2.6.2 + # via aiohttp +aiosignal==1.4.0 + # via aiohttp +async-timeout==5.0.1 + # via aiohttp +attrs==26.1.0 + # via aiohttp +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +click==8.3.1 + # via wait-for-it +coverage==7.13.4 + # via pytest-cov +exceptiongroup==1.3.1 + # via pytest +freezegun==1.5.5 + # via -r requirements/test-common-base.in +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +idna==3.15 + # via yarl +iniconfig==2.3.0 + # via pytest +multidict==6.7.1 + # via + # aiohttp + # yarl +packaging==26.0 + # via pytest +pkgconfig==1.6.0 + # via -r requirements/test-common-base.in +pluggy==1.6.0 + # via + # pytest + # pytest-cov +propcache==0.5.2 + # via + # aiohttp + # yarl +proxy-py==2.4.10 + # via -r requirements/test-common-base.in +pygments==2.19.2 + # via pytest +pytest==9.0.2 + # via + # -r requirements/test-common-base.in + # pytest-aiohttp + # pytest-asyncio + # pytest-cov + # pytest-mock + # pytest-timeout +pytest-aiohttp==1.1.0 + # via -r requirements/test-common-base.in +pytest-asyncio==1.3.0 + # via pytest-aiohttp +pytest-cov==7.0.0 + # via -r requirements/test-common-base.in +pytest-mock==3.15.1 + # via -r requirements/test-common-base.in +pytest-timeout==2.4.0 + # via -r requirements/test-common-base.in +python-dateutil==2.9.0.post0 + # via freezegun +setuptools-git==1.2 + # via -r requirements/test-common-base.in +six==1.17.0 + # via python-dateutil +tomli==2.4.0 + # via + # coverage + # pytest +typing-extensions==4.15.0 + # via + # aiosignal + # exceptiongroup + # multidict + # pytest-asyncio +wait-for-it==2.3.0 + # via -r requirements/test-common-base.in +yarl==1.24.2 + # via aiohttp + +# The following packages are considered to be unsafe in a requirements file: +# aiohttp diff --git a/requirements/test-common.in b/requirements/test-common.in index 9b3c5839b1f..d3aa5d156b0 100644 --- a/requirements/test-common.in +++ b/requirements/test-common.in @@ -1,19 +1,11 @@ +-r test-common-base.in + blockbuster coverage -freezegun isal; python_version < "3.14" and implementation_name == "cpython" # no wheel for 3.14, no PyPy wheel for 1.8.0+ mypy; implementation_name == "cpython" -pkgconfig -proxy.py >= 2.4.4rc5 -pytest -pytest-aiohttp -pytest-cov -pytest-mock -pytest-timeout pytest-xdist pytest_codspeed python-on-whales -setuptools-git -trustme; platform_machine != "i686" # no 32-bit wheels -wait-for-it +trustme; platform_machine != "i686" # no 32-bit wheels zlib_ng diff --git a/requirements/test-common.txt b/requirements/test-common.txt index bac85c47eae..dd9b06cad13 100644 --- a/requirements/test-common.txt +++ b/requirements/test-common.txt @@ -2,12 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/test-common.txt --strip-extras requirements/test-common.in +# pip-compile --output-file=requirements/test-common.txt --strip-extras --unsafe-package=aiohttp requirements/test-common.in # aiohappyeyeballs==2.6.2 # via aiohttp -aiohttp==3.13.5 - # via pytest-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 @@ -39,7 +37,7 @@ execnet==2.1.2 forbiddenfruit==0.1.4 # via blockbuster freezegun==1.5.5 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in frozenlist==1.8.0 # via # aiohttp @@ -71,7 +69,7 @@ packaging==26.2 pathspec==1.1.1 # via mypy pkgconfig==1.6.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pluggy==1.6.0 # via # pytest @@ -81,7 +79,7 @@ propcache==0.5.2 # aiohttp # yarl proxy-py==2.4.10 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pycparser==3.0 # via cffi pydantic==2.13.4 @@ -94,7 +92,7 @@ pygments==2.20.0 # rich pytest==9.0.3 # via - # -r requirements/test-common.in + # -r requirements/test-common-base.in # pytest-aiohttp # pytest-asyncio # pytest-codspeed @@ -109,11 +107,11 @@ pytest-asyncio==1.4.0 pytest-codspeed==5.0.3 # via -r requirements/test-common.in pytest-cov==7.1.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-mock==3.15.1 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-timeout==2.4.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-xdist==3.8.0 # via -r requirements/test-common.in python-dateutil==2.9.0.post0 @@ -123,7 +121,7 @@ python-on-whales==0.81.0 rich==15.0.0 # via pytest-codspeed setuptools-git==1.2 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in six==1.17.0 # via python-dateutil tomli==2.4.1 @@ -148,8 +146,11 @@ typing-extensions==4.15.0 typing-inspection==0.4.2 # via pydantic wait-for-it==2.3.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in yarl==1.24.2 # via aiohttp zlib-ng==1.0.0 # via -r requirements/test-common.in + +# The following packages are considered to be unsafe in a requirements file: +# aiohttp diff --git a/requirements/test-ft.txt b/requirements/test-ft.txt index 142d75bf348..557b3f41763 100644 --- a/requirements/test-ft.txt +++ b/requirements/test-ft.txt @@ -2,16 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/test-ft.txt --strip-extras requirements/test-ft.in +# pip-compile --output-file=requirements/test-ft.txt --strip-extras --unsafe-package=aiohttp requirements/test-ft.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.6.2 # via # -r requirements/runtime-deps.in # aiohttp -aiohttp==3.13.5 - # via pytest-aiohttp aiosignal==1.4.0 # via # -r requirements/runtime-deps.in @@ -28,11 +26,11 @@ attrs==26.1.0 # via aiohttp backports-asyncio-runner==1.2.0 # via pytest-asyncio -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in blockbuster==1.5.26 # via -r requirements/test-common.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in cffi==2.0.0 # via @@ -53,7 +51,7 @@ execnet==2.1.2 forbiddenfruit==0.1.4 # via blockbuster freezegun==1.5.5 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in frozenlist==1.8.0 # via # -r requirements/runtime-deps.in @@ -91,7 +89,7 @@ packaging==26.2 pathspec==1.1.1 # via mypy pkgconfig==1.6.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pluggy==1.6.0 # via # pytest @@ -102,7 +100,7 @@ propcache==0.5.2 # aiohttp # yarl proxy-py==2.4.10 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pycares==5.0.1 # via aiodns pycparser==3.0 @@ -117,7 +115,7 @@ pygments==2.20.0 # rich pytest==9.0.3 # via - # -r requirements/test-common.in + # -r requirements/test-common-base.in # pytest-aiohttp # pytest-asyncio # pytest-codspeed @@ -132,11 +130,11 @@ pytest-asyncio==1.4.0 pytest-codspeed==5.0.3 # via -r requirements/test-common.in pytest-cov==7.1.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-mock==3.15.1 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-timeout==2.4.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-xdist==3.8.0 # via -r requirements/test-common.in python-dateutil==2.9.0.post0 @@ -146,7 +144,7 @@ python-on-whales==0.81.0 rich==15.0.0 # via pytest-codspeed setuptools-git==1.2 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in six==1.17.0 # via python-dateutil tomli==2.4.1 @@ -172,10 +170,13 @@ typing-extensions==4.15.0 ; python_version < "3.13" typing-inspection==0.4.2 # via pydantic wait-for-it==2.3.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp zlib-ng==1.0.0 # via -r requirements/test-common.in + +# The following packages are considered to be unsafe in a requirements file: +# aiohttp diff --git a/requirements/test-mobile.in b/requirements/test-mobile.in new file mode 100644 index 00000000000..72238fd941e --- /dev/null +++ b/requirements/test-mobile.in @@ -0,0 +1,8 @@ +-r base-ft.in +-r test-common-base.in + +# pip-compile does not support environment markers for transitive dependencies. So +# some packages that are transitive dependencies have to be explicitly excluded here: +backports-asyncio-runner; python_version < "3.11" # transitive dependency of "pytest-asyncio"; not installable on Python >= 3.11 using pip (uv works though) +cffi; sys_platform != 'android' and sys_platform != 'ios' # transitive dependency of "pycares" +pycares; sys_platform != 'android' and sys_platform != 'ios' # transitive dependency of "aiodns" diff --git a/requirements/test-mobile.txt b/requirements/test-mobile.txt new file mode 100644 index 00000000000..062f05549c8 --- /dev/null +++ b/requirements/test-mobile.txt @@ -0,0 +1,127 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements/test-mobile.txt --strip-extras --unsafe-package=aiohttp requirements/test-mobile.in +# +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" + # via -r requirements/runtime-deps.in +aiohappyeyeballs==2.6.2 + # via + # -r requirements/runtime-deps.in + # aiohttp +aiosignal==1.4.0 + # via + # -r requirements/runtime-deps.in + # aiohttp +async-timeout==5.0.1 ; python_version < "3.11" + # via + # -r requirements/runtime-deps.in + # aiohttp +attrs==26.1.0 + # via aiohttp +backports-asyncio-runner==1.2.0 ; python_version < "3.11" + # via + # -r requirements/test-mobile.in + # pytest-asyncio +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" + # via -r requirements/runtime-deps.in +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" + # via -r requirements/runtime-deps.in +cffi==2.0.0 ; sys_platform != "android" and sys_platform != "ios" + # via + # -r requirements/test-mobile.in + # pycares +click==8.4.0 + # via wait-for-it +coverage==7.14.0 + # via pytest-cov +exceptiongroup==1.3.1 + # via pytest +freezegun==1.5.5 + # via -r requirements/test-common-base.in +frozenlist==1.8.0 + # via + # -r requirements/runtime-deps.in + # aiohttp + # aiosignal +gunicorn==26.0.0 + # via -r requirements/base-ft.in +idna==3.15 + # via yarl +iniconfig==2.3.0 + # via pytest +multidict==6.7.1 + # via + # -r requirements/runtime-deps.in + # aiohttp + # yarl +packaging==26.2 + # via + # gunicorn + # pytest +pkgconfig==1.6.0 + # via -r requirements/test-common-base.in +pluggy==1.6.0 + # via + # pytest + # pytest-cov +propcache==0.5.2 + # via + # -r requirements/runtime-deps.in + # aiohttp + # yarl +proxy-py==2.4.10 + # via -r requirements/test-common-base.in +pycares==5.0.1 ; sys_platform != "android" and sys_platform != "ios" + # via + # -r requirements/test-mobile.in + # aiodns +pycparser==3.0 + # via cffi +pygments==2.20.0 + # via pytest +pytest==9.0.3 + # via + # -r requirements/test-common-base.in + # pytest-aiohttp + # pytest-asyncio + # pytest-cov + # pytest-mock + # pytest-timeout +pytest-aiohttp==1.1.0 + # via -r requirements/test-common-base.in +pytest-asyncio==1.4.0a2 + # via pytest-aiohttp +pytest-cov==7.1.0 + # via -r requirements/test-common-base.in +pytest-mock==3.15.1 + # via -r requirements/test-common-base.in +pytest-timeout==2.4.0 + # via -r requirements/test-common-base.in +python-dateutil==2.9.0.post0 + # via freezegun +setuptools-git==1.2 + # via -r requirements/test-common-base.in +six==1.17.0 + # via python-dateutil +tomli==2.4.1 + # via + # coverage + # pytest +typing-extensions==4.15.0 ; python_version < "3.13" + # via + # -r requirements/runtime-deps.in + # aiosignal + # exceptiongroup + # multidict + # pytest-asyncio +wait-for-it==2.3.0 + # via -r requirements/test-common-base.in +yarl==1.24.2 + # via + # -r requirements/runtime-deps.in + # aiohttp + +# The following packages are considered to be unsafe in a requirements file: +# aiohttp diff --git a/requirements/test.txt b/requirements/test.txt index 60d5accb62f..ab1dca17cdb 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -2,16 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/test.txt --strip-extras requirements/test.in +# pip-compile --output-file=requirements/test.txt --strip-extras --unsafe-package=aiohttp requirements/test.in # -aiodns==4.0.4 +aiodns==4.0.4 ; sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in aiohappyeyeballs==2.6.2 # via # -r requirements/runtime-deps.in # aiohttp -aiohttp==3.13.5 - # via pytest-aiohttp aiosignal==1.4.0 # via # -r requirements/runtime-deps.in @@ -28,11 +26,11 @@ attrs==26.1.0 # via aiohttp backports-asyncio-runner==1.2.0 # via pytest-asyncio -backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" +backports-zstd==1.3.0 ; platform_python_implementation == "CPython" and python_version < "3.14" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in blockbuster==1.5.26 # via -r requirements/test-common.in -brotli==1.2.0 ; platform_python_implementation == "CPython" +brotli==1.2.0 ; platform_python_implementation == "CPython" and sys_platform != "android" and sys_platform != "ios" # via -r requirements/runtime-deps.in cffi==2.0.0 # via @@ -53,7 +51,7 @@ execnet==2.1.2 forbiddenfruit==0.1.4 # via blockbuster freezegun==1.5.5 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in frozenlist==1.8.0 # via # -r requirements/runtime-deps.in @@ -91,7 +89,7 @@ packaging==26.2 pathspec==1.1.1 # via mypy pkgconfig==1.6.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pluggy==1.6.0 # via # pytest @@ -102,7 +100,7 @@ propcache==0.5.2 # aiohttp # yarl proxy-py==2.4.10 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pycares==5.0.1 # via aiodns pycparser==3.0 @@ -117,7 +115,7 @@ pygments==2.20.0 # rich pytest==9.0.3 # via - # -r requirements/test-common.in + # -r requirements/test-common-base.in # pytest-aiohttp # pytest-asyncio # pytest-codspeed @@ -132,11 +130,11 @@ pytest-asyncio==1.4.0 pytest-codspeed==5.0.3 # via -r requirements/test-common.in pytest-cov==7.1.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-mock==3.15.1 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-timeout==2.4.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in pytest-xdist==3.8.0 # via -r requirements/test-common.in python-dateutil==2.9.0.post0 @@ -146,7 +144,7 @@ python-on-whales==0.81.0 rich==15.0.0 # via pytest-codspeed setuptools-git==1.2 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in six==1.17.0 # via python-dateutil tomli==2.4.1 @@ -174,10 +172,13 @@ typing-inspection==0.4.2 uvloop==0.22.1 ; platform_system != "Windows" and implementation_name == "cpython" # via -r requirements/base.in wait-for-it==2.3.0 - # via -r requirements/test-common.in + # via -r requirements/test-common-base.in yarl==1.24.2 # via # -r requirements/runtime-deps.in # aiohttp zlib-ng==1.0.0 # via -r requirements/test-common.in + +# The following packages are considered to be unsafe in a requirements file: +# aiohttp diff --git a/setup.cfg b/setup.cfg index e78606ecd57..cecc0c501fe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -87,6 +87,8 @@ filterwarnings = # https://github.com/spulec/freezegun/issues/508 # https://github.com/spulec/freezegun/pull/511 ignore:datetime.*utcnow\(\) is deprecated and scheduled for removal:DeprecationWarning:freezegun.api + # coverage's C tracer is not available on iOS/Android (pure-Python fallback is used instead) + ignore:Couldn't import C tracer:coverage.exceptions.CoverageWarning # Weird issue in Python 3.13+ triggered in test_multipart.py ignore:coroutine method 'aclose' of 'BodyPartReader._decode_content_async' was never awaited:RuntimeWarning # uvloop 0.22+ accesses the deprecated asyncio.AbstractEventLoopPolicy diff --git a/tests/conftest.py b/tests/conftest.py index 931fc01e434..3869d93794e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,12 +14,14 @@ from http.cookies import BaseCookie from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any +from typing import TYPE_CHECKING, Any from unittest import mock from uuid import uuid4 +if TYPE_CHECKING: + import trustme + import pytest -import trustme from multidict import CIMultiDict from yarl import URL @@ -112,6 +114,8 @@ def blockbuster(request: pytest.FixtureRequest) -> Iterator[None]: @pytest.fixture def tls_certificate_authority() -> trustme.CA: + if not TYPE_CHECKING: + trustme = pytest.importorskip("trustme") return trustme.CA() diff --git a/tests/test_benchmarks_client.py b/tests/test_benchmarks_client.py index 022de894b91..548cd34ef28 100644 --- a/tests/test_benchmarks_client.py +++ b/tests/test_benchmarks_client.py @@ -2,16 +2,21 @@ import asyncio from collections.abc import Iterator -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from pytest_aiohttp import AiohttpClient, AiohttpServer -from pytest_codspeed import BenchmarkFixture from yarl import URL from aiohttp import hdrs, request, web from aiohttp.test_utils import TestServer +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + @pytest.fixture def aiohttp_server_sync( diff --git a/tests/test_benchmarks_client_request.py b/tests/test_benchmarks_client_request.py index dcc7c8c0b4e..f63554cd1c5 100644 --- a/tests/test_benchmarks_client_request.py +++ b/tests/test_benchmarks_client_request.py @@ -4,10 +4,10 @@ import sys from collections.abc import Callable from http.cookies import BaseCookie -from typing import Any +from typing import TYPE_CHECKING, Any +import pytest from multidict import CIMultiDict -from pytest_codspeed import BenchmarkFixture from yarl import URL from aiohttp.client_reqrep import ClientRequest, ClientRequestArgs, ClientResponse @@ -22,6 +22,11 @@ _RequestMaker = Callable[[str, URL, Unpack[ClientRequestArgs]], ClientRequest] else: _RequestMaker = Any +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture async def test_client_request_update_cookies( @@ -141,7 +146,6 @@ def write(self, data: bytes | bytearray | memoryview) -> None: """Swallow writes.""" class MockProtocol(asyncio.BaseProtocol): - def __init__(self) -> None: self.transport = MockTransport() @@ -156,7 +160,6 @@ def start_timeout(self) -> None: """Swallow start_timeout.""" class MockConnector: - def __init__(self) -> None: self.force_close = False diff --git a/tests/test_benchmarks_client_ws.py b/tests/test_benchmarks_client_ws.py index ab7405b5d9e..0bcc409696a 100644 --- a/tests/test_benchmarks_client_ws.py +++ b/tests/test_benchmarks_client_ws.py @@ -2,16 +2,21 @@ import asyncio from collections.abc import Awaitable, Callable, Iterator -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from pytest_aiohttp import AiohttpClient -from pytest_codspeed import BenchmarkFixture from aiohttp import web from aiohttp._websocket.helpers import MSG_SIZE from aiohttp.test_utils import TestClient, TestServer +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + @pytest.fixture def aiohttp_client_sync( diff --git a/tests/test_benchmarks_cookiejar.py b/tests/test_benchmarks_cookiejar.py index 78566151ef4..1f820cc061d 100644 --- a/tests/test_benchmarks_cookiejar.py +++ b/tests/test_benchmarks_cookiejar.py @@ -1,12 +1,19 @@ """codspeed benchmarks for cookies.""" from http.cookies import BaseCookie +from typing import TYPE_CHECKING -from pytest_codspeed import BenchmarkFixture +import pytest from yarl import URL from aiohttp.cookiejar import CookieJar +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + async def test_load_cookies_into_temp_cookiejar(benchmark: BenchmarkFixture) -> None: """Benchmark for creating a temp CookieJar and filtering by URL. diff --git a/tests/test_benchmarks_http_websocket.py b/tests/test_benchmarks_http_websocket.py index b3ab17f6cd4..762d365158e 100644 --- a/tests/test_benchmarks_http_websocket.py +++ b/tests/test_benchmarks_http_websocket.py @@ -1,9 +1,9 @@ """codspeed benchmarks for http websocket.""" import asyncio +from typing import TYPE_CHECKING import pytest -from pytest_codspeed import BenchmarkFixture from aiohttp._websocket.helpers import MSG_SIZE, PACK_LEN3 from aiohttp._websocket.reader import WebSocketDataQueue @@ -11,6 +11,12 @@ from aiohttp.helpers import DEFAULT_CHUNK_SIZE from aiohttp.http_websocket import WebSocketReader, WebSocketWriter, WSMsgType +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + def test_read_large_binary_websocket_messages( event_loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture diff --git a/tests/test_benchmarks_http_writer.py b/tests/test_benchmarks_http_writer.py index 0d52ca875e6..d3cd1a72ea6 100644 --- a/tests/test_benchmarks_http_writer.py +++ b/tests/test_benchmarks_http_writer.py @@ -1,11 +1,19 @@ """codspeed benchmarks for http writer.""" +from typing import TYPE_CHECKING + +import pytest from multidict import CIMultiDict -from pytest_codspeed import BenchmarkFixture from aiohttp import hdrs from aiohttp.http_writer import _serialize_headers +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + def test_serialize_headers(benchmark: BenchmarkFixture) -> None: """Benchmark 100 calls to _serialize_headers.""" diff --git a/tests/test_benchmarks_web_fileresponse.py b/tests/test_benchmarks_web_fileresponse.py index 8c217dfbb26..ba9bd1dfa67 100644 --- a/tests/test_benchmarks_web_fileresponse.py +++ b/tests/test_benchmarks_web_fileresponse.py @@ -3,16 +3,21 @@ import asyncio import pathlib from collections.abc import Awaitable, Callable, Iterator -from typing import Any +from typing import TYPE_CHECKING, Any import pytest from multidict import CIMultiDict from pytest_aiohttp import AiohttpClient -from pytest_codspeed import BenchmarkFixture from aiohttp import ClientResponse, web from aiohttp.test_utils import TestClient, TestServer +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + @pytest.fixture def aiohttp_client_sync( diff --git a/tests/test_benchmarks_web_middleware.py b/tests/test_benchmarks_web_middleware.py index e967ba526c6..aacff2eac63 100644 --- a/tests/test_benchmarks_web_middleware.py +++ b/tests/test_benchmarks_web_middleware.py @@ -1,13 +1,20 @@ """codspeed benchmarks for web middlewares.""" import asyncio +from typing import TYPE_CHECKING +import pytest from pytest_aiohttp import AiohttpClient -from pytest_codspeed import BenchmarkFixture from aiohttp import web from aiohttp.typedefs import Handler +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + def test_ten_web_middlewares( benchmark: BenchmarkFixture, diff --git a/tests/test_benchmarks_web_request.py b/tests/test_benchmarks_web_request.py index d8d3a7fff01..6998d563bc7 100644 --- a/tests/test_benchmarks_web_request.py +++ b/tests/test_benchmarks_web_request.py @@ -2,13 +2,19 @@ import asyncio import zlib +from typing import TYPE_CHECKING import pytest from pytest_aiohttp import AiohttpClient -from pytest_codspeed import BenchmarkFixture from aiohttp import web +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + @pytest.mark.usefixtures("parametrize_zlib_backend") def test_read_compressed_post_body( diff --git a/tests/test_benchmarks_web_response.py b/tests/test_benchmarks_web_response.py index fbf1fadf1e1..a4246cea9ae 100644 --- a/tests/test_benchmarks_web_response.py +++ b/tests/test_benchmarks_web_response.py @@ -1,9 +1,17 @@ """codspeed benchmarks for the web responses.""" -from pytest_codspeed import BenchmarkFixture +from typing import TYPE_CHECKING + +import pytest from aiohttp import web +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + def test_simple_web_response(benchmark: BenchmarkFixture) -> None: """Benchmark creating 100 simple web.Response.""" diff --git a/tests/test_benchmarks_web_urldispatcher.py b/tests/test_benchmarks_web_urldispatcher.py index 0304db3b8b6..d931b073d0d 100644 --- a/tests/test_benchmarks_web_urldispatcher.py +++ b/tests/test_benchmarks_web_urldispatcher.py @@ -6,12 +6,11 @@ import random import string from pathlib import Path -from typing import NoReturn, cast +from typing import TYPE_CHECKING, NoReturn, cast from unittest import mock import pytest from multidict import CIMultiDict -from pytest_codspeed import BenchmarkFixture from yarl import URL import aiohttp @@ -19,6 +18,12 @@ from aiohttp.helpers import HeadersDictProxy from aiohttp.http import HttpVersion, RawRequestMessage +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture +else: + pytest_codspeed = pytest.importorskip("pytest_codspeed") + BenchmarkFixture = pytest_codspeed.BenchmarkFixture + @pytest.fixture def github_urls() -> list[str]: diff --git a/tests/test_circular_imports.py b/tests/test_circular_imports.py index 9b5d7ed2697..227da10e53b 100644 --- a/tests/test_circular_imports.py +++ b/tests/test_circular_imports.py @@ -85,6 +85,9 @@ def _discover_path_importables( ) +@pytest.mark.skipif( + sys.platform in ("android", "ios"), reason="subprocess not supported" +) @pytest.mark.parametrize( "import_path", _mark_aiohttp_worker_for_skipping(_find_all_importables(aiohttp)), diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index a301b6b82cd..d41d1cba2af 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -1,4 +1,5 @@ # HTTP client functional tests against aiohttp.web server +from __future__ import annotations # TODO(PY311): Remove import asyncio import datetime @@ -16,15 +17,18 @@ import zlib from collections.abc import AsyncIterator, Awaitable, Callable from contextlib import suppress -from typing import Any, NoReturn +from typing import TYPE_CHECKING, Any, NoReturn from unittest import mock +if TYPE_CHECKING: + import trustme + try: try: import brotlicffi as brotli except ImportError: import brotli -except ImportError: # pragma: no cover +except ImportError: brotli = None try: @@ -33,7 +37,6 @@ ZstdCompressor = None # type: ignore[assignment,misc] # pragma: no cover import pytest -import trustme from multidict import MultiDict from pytest_aiohttp import AiohttpClient, AiohttpServer from pytest_mock import MockerFixture diff --git a/tests/test_formdata.py b/tests/test_formdata.py index a5c7be37627..18148e64ea6 100644 --- a/tests/test_formdata.py +++ b/tests/test_formdata.py @@ -82,13 +82,27 @@ def test_invalid_type_formdata_content_type(val: object) -> None: form.add_field("foo", "bar", content_type=val) # type: ignore[arg-type] -@pytest.mark.parametrize("val", ("\r", "\n", "a\ra\n", "a\na\r")) +@pytest.mark.parametrize("val", ("\r", "\n", "a\ra\n", "a\na\r", "a\x00b")) def test_invalid_value_formdata_content_type(val: str) -> None: form = FormData() with pytest.raises(ValueError): form.add_field("foo", "bar", content_type=val) +@pytest.mark.parametrize("val", ("\r", "\n", "a\ra\n", "a\na\r", "a\x00b")) +def test_invalid_value_formdata_name(val: str) -> None: + form = FormData() + with pytest.raises(ValueError): + form.add_field(val, "bar") + + +@pytest.mark.parametrize("val", ("\r", "\n", "a\ra\n", "a\na\r", "a\x00b")) +def test_invalid_value_formdata_filename(val: str) -> None: + form = FormData() + with pytest.raises(ValueError): + form.add_field("foo", "bar", filename=val) + + def test_invalid_formdata_filename() -> None: form = FormData() invalid_vals = [0, 0.1, {}, [], b"foo"] diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py index bb9ef3393bb..f397c3eb86b 100644 --- a/tests/test_http_parser.py +++ b/tests/test_http_parser.py @@ -41,7 +41,7 @@ import brotlicffi as brotli except ImportError: import brotli -except ImportError: # pragma: no cover +except ImportError: brotli = None try: @@ -2702,6 +2702,9 @@ async def test_feed_eof_no_err_gzip(self, protocol: BaseProtocol) -> None: dbuf.feed_eof() assert buf._eof + @pytest.mark.skipif( + sys.platform in ("android", "ios"), reason="brotli not available" + ) async def test_feed_eof_no_err_brotli(self, protocol: BaseProtocol) -> None: buf = aiohttp.StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) dbuf = DeflateBuffer(buf, "br") diff --git a/tests/test_leaks.py b/tests/test_leaks.py index 07b506bdb99..742d6f9aa65 100644 --- a/tests/test_leaks.py +++ b/tests/test_leaks.py @@ -8,6 +8,9 @@ IS_PYPY = platform.python_implementation() == "PyPy" +@pytest.mark.skipif( + sys.platform in ("android", "ios"), reason="subprocess not supported" +) @pytest.mark.skipif(IS_PYPY, reason="gc.DEBUG_LEAK not available on PyPy") @pytest.mark.parametrize( ("script", "message"), diff --git a/tests/test_multipart.py b/tests/test_multipart.py index 604c3f9cf29..1a912c7c439 100644 --- a/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -124,7 +124,7 @@ async def read(self, size: int | None = None) -> bytes: class TestMultipartResponseWrapper: - def test_at_eof(self) -> None: + async def test_at_eof(self) -> None: m_resp = mock.create_autospec(aiohttp.ClientResponse, spec_set=True) m_stream = mock.create_autospec(MultipartReader, spec_set=True) wrapper = MultipartResponseWrapper(m_resp, m_stream) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 0cb6eaac712..761be8ed1de 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -11,7 +11,6 @@ from unittest import mock from uuid import uuid4 -import proxy import pytest from pytest_aiohttp import AiohttpRawServer, AiohttpServer from pytest_mock import MockerFixture @@ -23,6 +22,11 @@ from aiohttp.client_exceptions import ClientConnectionError from aiohttp.test_utils import TestServer +if TYPE_CHECKING: + import proxy +else: + proxy = pytest.importorskip("proxy") + ASYNCIO_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index a677e3fcaa5..8bb1ec9dac3 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -715,6 +715,9 @@ def test_run_app_multiple_preexisting_sockets( """ +@pytest.mark.skipif( + sys.platform in ("android", "ios"), reason="subprocess not supported" +) def test_sigint() -> None: skip_if_on_windows() @@ -727,6 +730,9 @@ def test_sigint() -> None: assert proc.wait() == 0 +@pytest.mark.skipif( + sys.platform in ("android", "ios"), reason="subprocess not supported" +) def test_sigterm() -> None: skip_if_on_windows() diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index 164f7dc0eef..466ff3bf523 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -1062,7 +1062,7 @@ def test_static_route_user_home(router: web.UrlDispatcher) -> None: except ValueError: pytest.skip("aiohttp folder is not placed in user's HOME") route = router.add_static("/st", str(static_dir)) - assert here == route.get_info()["directory"] + assert here.resolve() == route.get_info()["directory"] def test_static_route_points_to_file(router: web.UrlDispatcher) -> None: @@ -1086,7 +1086,7 @@ async def test_405_for_resource_adapter(router: web.UrlDispatcher) -> None: @pytest.mark.skipif(platform.system() == "Windows", reason="Different path formats") async def test_static_resource_outside_traversal(router: web.UrlDispatcher) -> None: """Test relative path traversing outside root does not resolve.""" - static_file = pathlib.Path(aiohttp.__file__) + static_file = pathlib.Path(aiohttp.__file__).resolve() request_path = "/st" + "/.." * (len(static_file.parts) - 2) + str(static_file) assert pathlib.Path(request_path).resolve() == static_file diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py index 2b9ac6734b6..2dd5bc3a2ac 100644 --- a/tests/test_web_functional.py +++ b/tests/test_web_functional.py @@ -34,7 +34,10 @@ try: import brotlicffi as brotli except ImportError: - import brotli + try: + import brotli + except ImportError: + brotli = None try: import ssl @@ -1156,6 +1159,7 @@ async def handler(request: web.Request) -> web.Response: resp.release() +@pytest.mark.skipif(brotli is None, reason="brotli not available") async def test_response_with_precompressed_body_brotli( aiohttp_client: AiohttpClient, ) -> None: diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py index 2e68385ba97..e4daf828fcd 100644 --- a/tests/test_web_sendfile_functional.py +++ b/tests/test_web_sendfile_functional.py @@ -21,7 +21,10 @@ try: import brotlicffi as brotli except ImportError: - import brotli + try: + import brotli + except ImportError: + brotli = None try: import ssl @@ -56,9 +59,12 @@ def hello_txt( } # Uncompressed file is not actually written to test it is not required. hello["gzip"].write_bytes(gzip.compress(HELLO_AIOHTTP)) - hello["br"].write_bytes(brotli.compress(HELLO_AIOHTTP)) + if brotli is not None: + hello["br"].write_bytes(brotli.compress(HELLO_AIOHTTP)) hello["bzip2"].write_bytes(bz2.compress(HELLO_AIOHTTP)) encoding = getattr(request, "param", None) + if encoding == "br" and brotli is None: + pytest.skip("brotli not available") return hello[encoding] @@ -276,6 +282,8 @@ async def test_static_file_custom_content_type_compress( expect_encoding: str, ) -> None: """Test that custom type with encoding is returned for unencoded requests.""" + if expect_encoding == "br" and brotli is None: + pytest.skip("brotli not available") async def handler(request: web.Request) -> web.FileResponse: resp = sender(hello_txt, chunk_size=16) @@ -311,6 +319,8 @@ async def test_static_file_with_encoding_and_enable_compression( forced_compression: web.ContentCoding | None, ) -> None: """Test that enable_compression does not double compress when an encoded file is also present.""" + if expect_encoding == "br" and brotli is None: + pytest.skip("brotli not available") async def handler(request: web.Request) -> web.FileResponse: resp = sender(hello_txt) diff --git a/tests/test_web_websocket_functional.py b/tests/test_web_websocket_functional.py index 502b1a911e5..d4a1b402249 100644 --- a/tests/test_web_websocket_functional.py +++ b/tests/test_web_websocket_functional.py @@ -30,6 +30,31 @@ async def handler(request: web.Request) -> NoReturn: assert resp.status == 426 +async def test_handshake_connection_header_substring_not_a_token( + aiohttp_client: AiohttpClient, +) -> None: + async def handler(request: web.Request) -> web.WebSocketResponse: + ws = web.WebSocketResponse() + await ws.prepare(request) + await ws.close() + return ws + + app = web.Application() + app.router.add_route("GET", "/", handler) + client = await aiohttp_client(app) + + resp = await client.get( + "/", + headers={ + "Upgrade": "websocket", + "Connection": "keep-alive, notupgrade", + "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", + "Sec-WebSocket-Version": "13", + }, + ) + assert resp.status == 400 + + async def test_websocket_json(aiohttp_client: AiohttpClient) -> None: async def handler(request: web.Request) -> web.WebSocketResponse: ws = web.WebSocketResponse()