[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: Kludex\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-issue.md",
    "content": "---\nname: Issue\nabout: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏\n---\n\nThe starting point for issues should usually be a discussion...\n\nhttps://github.com/Kludex/starlette/discussions\n\nPossible bugs may be raised as a \"Potential Issue\" discussion, feature requests may be raised as an \"Ideas\" discussion. We can then determine if the discussion needs to be escalated into an \"Issue\" or not.\n\nThis will help us ensure that the \"Issues\" list properly reflects ongoing or needed work on the project.\n\n---\n\n- [ ] Initially raised as discussion #...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser\nblank_issues_enabled: false\ncontact_links:\n  - name: Discussions\n    url: https://github.com/Kludex/starlette/discussions\n    about: >\n      The \"Discussions\" forum is where you want to start. 💖\n  - name: Chat\n    url: https://discord.gg/SWU73HffbV\n    about: >\n      Our community chat forum.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"uv\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    groups:\n      python-packages:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: monthly\n    groups:\n      github-actions:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!-- Thanks for contributing to Starlette! 💚\nGiven this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->\n\n# Summary\n\n<!-- Write a small summary about what is happening here. -->\n\n# Checklist\n\n- [ ] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)\n- [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.\n- [ ] I've updated the documentation accordingly.\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "---\nname: Test Suite\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\njobs:\n  tests:\n    name: \"Python ${{ matrix.python-version }}\"\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: ${{ matrix.python-version }}\n          enable-cache: true\n\n      - name: Install dependencies\n        run: scripts/install\n\n      - name: Run linting checks\n        run: scripts/check\n        if: ${{ matrix.python-version != '3.14' }}\n\n      - name: \"Build package & docs\"\n        run: scripts/build\n\n      - name: \"Run tests\"\n        run: scripts/test\n\n      - name: \"Enforce coverage\"\n        run: scripts/coverage\n\n  # https://github.com/marketplace/actions/alls-green#why\n  check:\n    if: always()\n    needs: [tests]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Decide whether the needed jobs succeeded or failed\n        uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2\n        with:\n          jobs: ${{ toJSON(needs) }}\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  push:\n    tags:\n      - \"*\"\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1\n        with:\n          python-version: \"3.11\"\n          enable-cache: true\n\n      - name: Install dependencies\n        run: scripts/install\n\n      - name: Build package & docs\n        run: scripts/build\n\n      - name: Upload package distributions\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: package-distributions\n          path: dist/\n\n      - name: Upload documentation\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: documentation\n          path: site/\n\n  pypi-publish:\n    runs-on: ubuntu-latest\n    needs: build\n    if: success() && startsWith(github.ref, 'refs/tags/')\n\n    permissions:\n      id-token: write\n\n    environment:\n      name: pypi\n      url: https://pypi.org/project/starlette\n\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: package-distributions\n          path: dist/\n\n      - name: Publish distribution 📦 to PyPI\n        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0\n\n  docs-publish:\n    runs-on: ubuntu-latest\n    needs: build\n\n    permissions:\n      contents: read\n      pages: write\n      id-token: write\n\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    steps:\n      - name: Configure GitHub Pages\n        uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0\n\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: documentation\n          path: site/\n\n      - name: Upload Pages artifact\n        uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0\n        with:\n          path: site\n\n      - name: Deploy to GitHub Pages\n        uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n        id: deployment\n\n  docs-cloudflare:\n    runs-on: ubuntu-latest\n    needs: build\n\n    environment:\n      name: cloudflare\n      url: https://starlette.dev\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: documentation\n          path: site/\n\n      - uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1\n        with:\n          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          command: >\n            pages deploy ./site\n            --project-name starlette\n            --commit-hash ${{ github.sha }}\n            --branch main\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\ntest.db\n.coverage\n.pytest_cache/\n.mypy_cache/\n__pycache__/\nhtmlcov/\nsite/\n*.egg-info/\nvenv*/\n.venv/\n.python-version\nbuild/\ndist/\n"
  },
  {
    "path": "CITATION.cff",
    "content": "# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\ncff-version: 1.2.0\ntitle: Starlette\nmessage: >-\n  If you use this software, please cite it using the\n  metadata from this file.\ntype: software\nauthors:\n  - given-names: Marcelo\n    family-names: Trylesinski\n    email: marcelotryle@gmail.com\n  - given-names: Tom\n    family-names: Christie\n    email: tom@tomchristie.com\nrepository-code: \"https://github.com/Kludex/starlette\"\nurl: \"https://starlette.dev/\"\nabstract: Starlette is an ASGI web framework for Python.\nkeywords:\n  - asgi\n  - starlette\nlicense: BSD-3-Clause\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © 2018, [Encode OSS Ltd](https://www.encode.io/).\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/starlette_dark.svg\" width=\"420px\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/starlette.svg\" width=\"420px\">\n    <img alt=\"starlette-logo\" src=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/starlette.svg\">\n  </picture>\n</p>\n\n<p align=\"center\">\n    <em>✨ The little ASGI framework that shines. ✨</em>\n</p>\n\n---\n\n[![Build Status](https://github.com/Kludex/starlette/workflows/Test%20Suite/badge.svg)](https://github.com/Kludex/starlette/actions)\n[![Package version](https://badge.fury.io/py/starlette.svg)](https://pypi.python.org/pypi/starlette)\n[![Supported Python Version](https://img.shields.io/pypi/pyversions/starlette.svg?color=%2334D058)](https://pypi.org/project/starlette)\n[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs)\n\n---\n\n**Documentation**: <a href=\"https://starlette.dev/\" target=\"_blank\">https://starlette.dev</a>\n\n**Source Code**: <a href=\"https://github.com/Kludex/starlette\" target=\"_blank\">https://github.com/Kludex/starlette</a>\n\n---\n\n# Starlette\n\nStarlette is a lightweight [ASGI][asgi] framework/toolkit,\nwhich is ideal for building async web services in Python.\n\nIt is production-ready, and gives you the following:\n\n* A lightweight, low-complexity HTTP web framework.\n* WebSocket support.\n* In-process background tasks.\n* Startup and shutdown events.\n* Test client built on `httpx`.\n* CORS, GZip, Static Files, Streaming responses.\n* Session and Cookie support.\n* 100% test coverage.\n* 100% type annotated codebase.\n* Few hard dependencies.\n* Compatible with `asyncio` and `trio` backends.\n* Great overall performance [against independent benchmarks][techempower].\n\n## Installation\n\n```shell\n$ pip install starlette\n```\n\nYou'll also want to install an ASGI server, such as [uvicorn](https://www.uvicorn.org/), [daphne](https://github.com/django/daphne/), or [hypercorn](https://hypercorn.readthedocs.io/en/latest/).\n\n```shell\n$ pip install uvicorn\n```\n\n## Example\n\n```python title=\"main.py\"\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\n\n\nasync def homepage(request):\n    return JSONResponse({'hello': 'world'})\n\nroutes = [\n    Route(\"/\", endpoint=homepage)\n]\n\napp = Starlette(debug=True, routes=routes)\n```\n\nThen run the application using Uvicorn:\n\n```shell\n$ uvicorn main:app\n```\n\n## Dependencies\n\nStarlette only requires `anyio`, and the following are optional:\n\n* [`httpx`][httpx] - Required if you want to use the `TestClient`.\n* [`jinja2`][jinja2] - Required if you want to use `Jinja2Templates`.\n* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`.\n* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.\n* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.\n\nYou can install all of these with `pip install starlette[full]`.\n\n## Framework or Toolkit\n\nStarlette is designed to be used either as a complete framework, or as\nan ASGI toolkit. You can use any of its components independently.\n\n```python\nfrom starlette.responses import PlainTextResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = PlainTextResponse('Hello, world!')\n    await response(scope, receive, send)\n```\n\nRun the `app` application in `example.py`:\n\n```shell\n$ uvicorn example:app\nINFO: Started server process [11509]\nINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n```\n\nRun uvicorn with `--reload` to enable auto-reloading on code changes.\n\n## Modularity\n\nThe modularity that Starlette is designed on promotes building re-usable\ncomponents that can be shared between any ASGI framework. This should enable\nan ecosystem of shared middleware and mountable applications.\n\nThe clean API separation also means it's easier to understand each component\nin isolation.\n\n---\n\n<p align=\"center\"><i>Starlette is <a href=\"https://github.com/Kludex/starlette/blob/main/LICENSE.md\">BSD licensed</a> code.<br/>Designed & crafted with care.</i></br>&mdash; ⭐️ &mdash;</p>\n\n[asgi]: https://asgi.readthedocs.io/en/latest/\n[httpx]: https://www.python-httpx.org/\n[jinja2]: https://jinja.palletsprojects.com/\n[python-multipart]: https://multipart.fastapiexpert.com/\n[itsdangerous]: https://itsdangerous.palletsprojects.com/\n[sqlalchemy]: https://www.sqlalchemy.org\n[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation\n[techempower]: https://www.techempower.com/benchmarks/#hw=ph&test=fortune&l=zijzen-sf\n"
  },
  {
    "path": "docs/CNAME",
    "content": "www.starlette.io\n"
  },
  {
    "path": "docs/applications.md",
    "content": "\n??? abstract \"API Reference\"\n    ::: starlette.applications.Starlette\n        options:\n            parameter_headings: false\n            show_root_heading: true\n            heading_level: 3\n            filters:\n                - \"__init__\"\n\nStarlette includes an application class `Starlette` that nicely ties together all of\nits other functionality.\n\n```python\nfrom contextlib import asynccontextmanager\n\nfrom starlette.applications import Starlette\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route, Mount, WebSocketRoute\nfrom starlette.staticfiles import StaticFiles\n\n\ndef homepage(request):\n    return PlainTextResponse('Hello, world!')\n\ndef user_me(request):\n    username = \"John Doe\"\n    return PlainTextResponse('Hello, %s!' % username)\n\ndef user(request):\n    username = request.path_params['username']\n    return PlainTextResponse('Hello, %s!' % username)\n\nasync def websocket_endpoint(websocket):\n    await websocket.accept()\n    await websocket.send_text('Hello, websocket!')\n    await websocket.close()\n\n@asynccontextmanager\nasync def lifespan(app):\n    print('Startup')\n    yield\n    print('Shutdown')\n\n\nroutes = [\n    Route('/', homepage),\n    Route('/user/me', user_me),\n    Route('/user/{username}', user),\n    WebSocketRoute('/ws', websocket_endpoint),\n    Mount('/static', StaticFiles(directory=\"static\")),\n]\n\napp = Starlette(debug=True, routes=routes, lifespan=lifespan)\n```\n\n### Storing state on the app instance\n\nYou can store arbitrary extra state on the application instance, using the\ngeneric `app.state` attribute.\n\nFor example:\n\n```python\napp.state.ADMIN_EMAIL = 'admin@example.org'\n```\n\n### Accessing the app instance\n\nWhere a `request` is available (i.e. endpoints and middleware), the app is available on `request.app`.\n"
  },
  {
    "path": "docs/authentication.md",
    "content": "Starlette offers a simple but powerful interface for handling authentication\nand permissions. Once you've installed `AuthenticationMiddleware` with an\nappropriate authentication backend the `request.user` and `request.auth`\ninterfaces will be available in your endpoints.\n\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.authentication import (\n    AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser\n)\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.authentication import AuthenticationMiddleware\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\nimport base64\nimport binascii\n\n\nclass BasicAuthBackend(AuthenticationBackend):\n    async def authenticate(self, conn):\n        if \"Authorization\" not in conn.headers:\n            return\n\n        auth = conn.headers[\"Authorization\"]\n        try:\n            scheme, credentials = auth.split()\n            if scheme.lower() != 'basic':\n                return\n            decoded = base64.b64decode(credentials).decode(\"ascii\")\n        except (ValueError, UnicodeDecodeError, binascii.Error) as exc:\n            raise AuthenticationError('Invalid basic auth credentials')\n\n        username, _, password = decoded.partition(\":\")\n        # TODO: You'd want to verify the username and password here.\n        return AuthCredentials([\"authenticated\"]), SimpleUser(username)\n\n\nasync def homepage(request):\n    if request.user.is_authenticated:\n        return PlainTextResponse('Hello, ' + request.user.display_name)\n    return PlainTextResponse('Hello, you')\n\nroutes = [\n    Route(\"/\", endpoint=homepage)\n]\n\nmiddleware = [\n    Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\n## Users\n\nOnce `AuthenticationMiddleware` is installed the `request.user` interface\nwill be available to endpoints or other middleware.\n\nThis interface should subclass `BaseUser`, which provides two properties,\nas well as whatever other information your user model includes.\n\n* `.is_authenticated`\n* `.display_name`\n\nStarlette provides two built-in user implementations: `UnauthenticatedUser()`,\nand `SimpleUser(username)`.\n\n## AuthCredentials\n\nIt is important that authentication credentials are treated as separate concept\nfrom users. An authentication scheme should be able to restrict or grant\nparticular privileges independently of the user identity.\n\nThe `AuthCredentials` class provides the basic interface that `request.auth`\nexposes:\n\n* `.scopes`\n\n## Permissions\n\nPermissions are implemented as an endpoint decorator, that enforces that the\nincoming request includes the required authentication scopes.\n\n```python\nfrom starlette.authentication import requires\n\n\n@requires('authenticated')\nasync def dashboard(request):\n    ...\n```\n\nYou can include either one or multiple required scopes:\n\n```python\nfrom starlette.authentication import requires\n\n\n@requires(['authenticated', 'admin'])\nasync def dashboard(request):\n    ...\n```\n\nBy default 403 responses will be returned when permissions are not granted.\nIn some cases you might want to customize this, for example to hide information\nabout the URL layout from unauthenticated users.\n\n```python\nfrom starlette.authentication import requires\n\n\n@requires(['authenticated', 'admin'], status_code=404)\nasync def dashboard(request):\n    ...\n```\n\n!!! note\n    The `status_code` parameter is not supported with WebSockets. The 403 (Forbidden)\n    status code will always be used for those.\n\nAlternatively you might want to redirect unauthenticated users to a different\npage.\n\n```python\nfrom starlette.authentication import requires\n\n\nasync def homepage(request):\n    ...\n\n\n@requires('authenticated', redirect='homepage')\nasync def dashboard(request):\n    ...\n```\n\nWhen redirecting users, the page you redirect them to will include URL they originally requested at the `next` query param:\n\n```python\nfrom starlette.authentication import requires\nfrom starlette.responses import RedirectResponse\n\n\n@requires('authenticated', redirect='login')\nasync def admin(request):\n    ...\n\n\nasync def login(request):\n    if request.method == \"POST\":\n        # Now that the user is authenticated,\n        # we can send them to their original request destination\n        if request.user.is_authenticated:\n            next_url = request.query_params.get(\"next\")\n            if next_url:\n                return RedirectResponse(next_url)\n            return RedirectResponse(\"/\")\n```\n\nFor class-based endpoints, you should wrap the decorator\naround a method on the class.\n\n```python\nfrom starlette.authentication import requires\nfrom starlette.endpoints import HTTPEndpoint\n\n\nclass Dashboard(HTTPEndpoint):\n    @requires(\"authenticated\")\n    async def get(self, request):\n        ...\n```\n\n## Custom authentication error responses\n\nYou can customise the error response sent when a `AuthenticationError` is\nraised by an auth backend:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.authentication import AuthenticationMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\n\n\ndef on_auth_error(request: Request, exc: Exception):\n    return JSONResponse({\"error\": str(exc)}, status_code=401)\n\napp = Starlette(\n    middleware=[\n        Middleware(AuthenticationMiddleware, backend=BasicAuthBackend(), on_error=on_auth_error),\n    ],\n)\n```\n"
  },
  {
    "path": "docs/background.md",
    "content": "\nStarlette includes a `BackgroundTask` class for in-process background tasks.\n\nA background task should be attached to a response, and will run only once\nthe response has been sent.\n\n### Background Task\n\nUsed to add a single background task to a response.\n\nSignature: `BackgroundTask(func, *args, **kwargs)`\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\nfrom starlette.background import BackgroundTask\n\n\n...\n\nasync def signup(request):\n    data = await request.json()\n    username = data['username']\n    email = data['email']\n    task = BackgroundTask(send_welcome_email, to_address=email)\n    message = {'status': 'Signup successful'}\n    return JSONResponse(message, background=task)\n\nasync def send_welcome_email(to_address):\n    ...\n\n\nroutes = [\n    ...\n    Route('/user/signup', endpoint=signup, methods=['POST'])\n]\n\napp = Starlette(routes=routes)\n```\n\n### BackgroundTasks\n\nUsed to add multiple background tasks to a response.\n\nSignature: `BackgroundTasks(tasks=[])`\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.background import BackgroundTasks\n\nasync def signup(request):\n    data = await request.json()\n    username = data['username']\n    email = data['email']\n    tasks = BackgroundTasks()\n    tasks.add_task(send_welcome_email, to_address=email)\n    tasks.add_task(send_admin_notification, username=username)\n    message = {'status': 'Signup successful'}\n    return JSONResponse(message, background=tasks)\n\nasync def send_welcome_email(to_address):\n    ...\n\nasync def send_admin_notification(username):\n    ...\n\nroutes = [\n    Route('/user/signup', endpoint=signup, methods=['POST'])\n]\n\napp = Starlette(routes=routes)\n```\n\n!!! important\n    The tasks are executed in order. In case one of the tasks raises\n    an exception, the following tasks will not get the opportunity to be executed.\n"
  },
  {
    "path": "docs/config.md",
    "content": "Starlette encourages a strict separation of configuration from code,\nfollowing [the twelve-factor pattern][twelve-factor].\n\nConfiguration should be stored in environment variables, or in a `.env` file\nthat is not committed to source control.\n\n```python title=\"main.py\"\nfrom sqlalchemy import create_engine\nfrom starlette.applications import Starlette\nfrom starlette.config import Config\nfrom starlette.datastructures import CommaSeparatedStrings, Secret\n\n# Config will be read from environment variables and/or \".env\" files.\nconfig = Config(\".env\")\n\nDEBUG = config('DEBUG', cast=bool, default=False)\nDATABASE_URL = config('DATABASE_URL')\nSECRET_KEY = config('SECRET_KEY', cast=Secret)\nALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)\n\napp = Starlette(debug=DEBUG)\nengine = create_engine(DATABASE_URL)\n...\n```\n\n```shell title=\".env\"\n# Don't commit this to source control.\n# Eg. Include \".env\" in your `.gitignore` file.\nDEBUG=True\nDATABASE_URL=postgresql://user:password@localhost:5432/database\nSECRET_KEY=43n080musdfjt54t-09sdgr\nALLOWED_HOSTS=127.0.0.1, localhost\n```\n\n## Configuration precedence\n\nThe order in which configuration values are read is:\n\n* From an environment variable.\n* From the `.env` file.\n* The default value given in `config`.\n\nIf none of those match, then `config(...)` will raise an error.\n\n## Secrets\n\nFor sensitive keys, the `Secret` class is useful, since it helps minimize\noccasions where the value it holds could leak out into tracebacks or\nother code introspection.\n\nTo get the value of a `Secret` instance, you must explicitly cast it to a string.\nYou should only do this at the point at which the value is used.\n\n```python\n>>> from myproject import settings\n>>> settings.SECRET_KEY\nSecret('**********')\n>>> str(settings.SECRET_KEY)\n'98n349$%8b8-7yjn0n8y93T$23r'\n```\n\n!!! tip\n\n    You can use `DatabaseURL` from `databases`\n    package [here](https://github.com/encode/databases/blob/ab5eb718a78a27afe18775754e9c0fa2ad9cd211/databases/core.py#L420)\n    to store database URLs and avoid leaking them in the logs.\n\n## CommaSeparatedStrings\n\nFor holding multiple inside a single config key, the `CommaSeparatedStrings`\ntype is useful.\n\n```python\n>>> from myproject import settings\n>>> print(settings.ALLOWED_HOSTS)\nCommaSeparatedStrings(['127.0.0.1', 'localhost'])\n>>> print(list(settings.ALLOWED_HOSTS))\n['127.0.0.1', 'localhost']\n>>> print(len(settings.ALLOWED_HOSTS))\n2\n>>> print(settings.ALLOWED_HOSTS[0])\n'127.0.0.1'\n```\n\n## Reading or modifying the environment\n\nIn some cases you might want to read or modify the environment variables programmatically.\nThis is particularly useful in testing, where you may want to override particular\nkeys in the environment.\n\nRather than reading or writing from `os.environ`, you should use Starlette's\n`environ` instance. This instance is a mapping onto the standard `os.environ`\nthat additionally protects you by raising an error if any environment variable\nis set *after* the point that it has already been read by the configuration.\n\nIf you're using `pytest`, then you can setup any initial environment in\n`tests/conftest.py`.\n\n```python title=\"tests/conftest.py\"\nfrom starlette.config import environ\n\nenviron['DEBUG'] = 'TRUE'\n```\n\n## Reading prefixed environment variables\n\nYou can namespace the environment variables by setting `env_prefix` argument.\n\n```python title=\"myproject/settings.py\"\nimport os\n\nfrom starlette.config import Config\n\nos.environ['APP_DEBUG'] = 'yes'\nos.environ['ENVIRONMENT'] = 'dev'\n\nconfig = Config(env_prefix='APP_')\n\nDEBUG = config('DEBUG') # lookups APP_DEBUG, returns \"yes\"\nENVIRONMENT = config('ENVIRONMENT') # lookups APP_ENVIRONMENT, raises KeyError as variable is not defined\n```\n\n## Custom encoding for environment files \n\nBy default, Starlette reads environment files using UTF-8 encoding. \nYou can specify a different encoding by setting `encoding` argument.\n\n```python title=\"myproject/settings.py\"\nfrom starlette.config import Config\n\n# Using custom encoding for .env file\nconfig = Config(\".env\", encoding=\"latin-1\")\n```\n\n## A full example\n\nStructuring large applications can be complex. You need proper separation of\nconfiguration and code, database isolation during tests, separate test and\nproduction databases, etc...\n\nHere we'll take a look at a complete example, that demonstrates how\nwe can start to structure an application.\n\nFirst, let's keep our settings, our database table definitions, and our\napplication logic separated:\n\n```python title=\"myproject/settings.py\"\nfrom starlette.config import Config\nfrom starlette.datastructures import Secret\n\nconfig = Config(\".env\")\n\nDEBUG = config('DEBUG', cast=bool, default=False)\nSECRET_KEY = config('SECRET_KEY', cast=Secret)\n\nDATABASE_URL = config('DATABASE_URL')\n```\n\n```python title=\"myproject/tables.py\"\nimport sqlalchemy\n\n# Database table definitions.\nmetadata = sqlalchemy.MetaData()\n\norganisations = sqlalchemy.Table(\n    ...\n)\n```\n\n```python title=\"myproject/app.py\"\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.sessions import SessionMiddleware\nfrom starlette.routing import Route\n\nfrom myproject import settings\n\n\nasync def homepage(request):\n    ...\n\nroutes = [\n    Route(\"/\", endpoint=homepage)\n]\n\nmiddleware = [\n    Middleware(\n        SessionMiddleware,\n        secret_key=settings.SECRET_KEY,\n    )\n]\n\napp = Starlette(debug=settings.DEBUG, routes=routes, middleware=middleware)\n```\n\nNow let's deal with our test configuration.\nWe'd like to create a new test database every time the test suite runs,\nand drop it once the tests complete. We'd also like to ensure\n\n```python title=\"tests/conftest.py\"\nfrom starlette.config import environ\nfrom starlette.testclient import TestClient\nfrom sqlalchemy import create_engine\nfrom sqlalchemy_utils import create_database, database_exists, drop_database\n\n# This line would raise an error if we use it after 'settings' has been imported.\nenviron['DEBUG'] = 'TRUE'\n\nfrom myproject import settings\nfrom myproject.app import app\nfrom myproject.tables import metadata\n\n\n@pytest.fixture(autouse=True, scope=\"session\")\ndef setup_test_database():\n    \"\"\"\n    Create a clean test database every time the tests are run.\n    \"\"\"\n    url = settings.DATABASE_URL\n    engine = create_engine(url)\n    assert not database_exists(url), 'Test database already exists. Aborting tests.'\n    create_database(url)             # Create the test database.\n    metadata.create_all(engine)      # Create the tables.\n    yield                            # Run the tests.\n    drop_database(url)               # Drop the test database.\n\n\n@pytest.fixture()\ndef client():\n    \"\"\"\n    Make a 'client' fixture available to test cases.\n    \"\"\"\n    # Our fixture is created within a context manager. This ensures that\n    # application lifespan runs for every test case.\n    with TestClient(app) as test_client:\n        yield test_client\n```\n\n[twelve-factor]: https://12factor.net/config\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\nThank you for being interested in contributing to Starlette.\nThere are many ways you can contribute to the project:\n\n- Try Starlette and [report bugs/issues you find](https://github.com/Kludex/starlette/issues/new)\n- [Implement new features](https://github.com/Kludex/starlette/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n- [Review Pull Requests of others](https://github.com/Kludex/starlette/pulls)\n- Write documentation\n- Participate in discussions\n\n## Reporting Bugs or Other Issues\n\nFound something that Starlette should support?\nStumbled upon some unexpected behaviour?\n\nContributions should generally start out with [a discussion](https://github.com/Kludex/starlette/discussions).\nPossible bugs may be raised as a \"Potential Issue\" discussion, feature requests may\nbe raised as an \"Ideas\" discussion. We can then determine if the discussion needs\nto be escalated into an \"Issue\" or not, or if we'd consider a pull request.\n\nTry to be more descriptive as you can and in case of a bug report,\nprovide as much information as possible like:\n\n- OS platform\n- Python version\n- Installed dependencies and versions (`python -m pip freeze`)\n- Code snippet\n- Error traceback\n\nYou should always try to reduce any examples to the *simplest possible case*\nthat demonstrates the issue.\n\n## Development\n\nTo start developing Starlette, create a **fork** of the\n[Starlette repository](https://github.com/Kludex/starlette) on GitHub.\n\nThen clone your fork with the following command replacing `YOUR-USERNAME` with\nyour GitHub username:\n\n```shell\n$ git clone https://github.com/YOUR-USERNAME/starlette\n```\n\nYou can now install the project and its dependencies using:\n\n```shell\n$ cd starlette\n$ scripts/install\n```\n\n## Testing and Linting\n\nWe use custom shell scripts to automate testing, linting,\nand documentation building workflow.\n\nTo run the tests, use:\n\n```shell\n$ scripts/test\n```\n\nAny additional arguments will be passed to `pytest`. See the [pytest documentation](https://docs.pytest.org/en/latest/how-to/usage.html) for more information.\n\nFor example, to run a single test script:\n\n```shell\n$ scripts/test tests/test_application.py\n```\n\nTo run the code auto-formatting:\n\n```shell\n$ scripts/lint\n```\n\nLastly, to run code checks separately (they are also run as part of `scripts/test`), run:\n\n```shell\n$ scripts/check\n```\n\n## Documenting\n\nDocumentation pages are located under the `docs/` folder.\n\nTo run the documentation site locally (useful for previewing changes), use:\n\n```shell\n$ scripts/docs\n```\n\n## Resolving Build / CI Failures\n\nOnce you've submitted your pull request, the test suite will automatically run, and the results will show up in GitHub.\nIf the test suite fails, you'll want to click through to the \"Details\" link, and try to identify why the test suite failed.\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/gh-actions-fail.png\" alt='Failing PR commit status'>\n</p>\n\nHere are some common ways the test suite can fail:\n\n### Check Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/gh-actions-fail-check.png\" alt='Failing GitHub action lint job'>\n</p>\n\nThis job failing means there is either a code formatting issue or type-annotation issue.\nYou can look at the job output to figure out why it's failed or within a shell run:\n\n```shell\n$ scripts/check\n```\n\nIt may be worth it to run `$ scripts/lint` to attempt auto-formatting the code\nand if that job succeeds commit the changes.\n\n### Docs Job Failed\n\nThis job failing means the documentation failed to build. This can happen for\na variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`.\n\n### Python 3.X Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/Kludex/starlette/main/docs/img/gh-actions-fail-test.png\" alt='Failing GitHub action test job'>\n</p>\n\nThis job failing means the unit tests failed or not all code paths are covered by unit tests.\n\nIf tests are failing you will see this message under the coverage report:\n\n`=== 1 failed, 435 passed, 1 skipped, 1 xfailed in 11.09s ===`\n\nIf tests succeed but coverage doesn't reach our current threshold, you will see this\nmessage under the coverage report:\n\n`FAIL Required test coverage of 100% not reached. Total coverage: 99.00%`\n\n## Releasing\n\n*This section is targeted at Starlette maintainers.*\n\nBefore releasing a new version, create a pull request that includes:\n\n- **An update to the changelog**:\n    - We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/).\n    - [Compare](https://github.com/Kludex/starlette/compare/) `main` with the tag of the latest release, and list all entries that are of interest to our users:\n        - Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes.\n        - Things that **should not** go in the changelog: changes to documentation, tests or tooling.\n        - Try sorting entries in descending order of impact / importance.\n        - Keep it concise and to-the-point. 🎯\n- **A version bump**: see `__version__.py`.\n\nFor an example, see [#1600](https://github.com/Kludex/starlette/pull/1600).\n\nOnce the release PR is merged, create a\n[new release](https://github.com/Kludex/starlette/releases/new) including:\n\n- Tag version like `0.13.3`.\n- Release title `Version 0.13.3`\n- Description copied from the changelog.\n\nOnce created this release will be automatically uploaded to PyPI.\n\n"
  },
  {
    "path": "docs/css/custom.css",
    "content": "/* Lighter dark mode colors */\n[data-md-color-scheme=\"slate\"] {\n  --md-default-bg-color: #263238;\n  --md-default-fg-color: #e0e0e0;\n  --md-code-bg-color: #2e3c43;\n}\n\n/* Announcement bar styling */\n.announce-wrapper {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 40px;\n  min-height: 40px;\n  background-color: var(--md-primary-fg-color);\n}\n\n.announce-wrapper #announce-msg {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.announce-wrapper #announce-msg div.item {\n  display: none;\n}\n\n.announce-wrapper #announce-msg div.item:first-child {\n  display: block;\n}\n\na.announce-link:link,\na.announce-link:visited {\n  color: var(--md-primary-bg-color);\n  text-decoration: none;\n  font-weight: 500;\n}\n\na.announce-link:hover {\n  color: var(--md-accent-fg-color);\n  text-decoration: underline;\n}\n"
  },
  {
    "path": "docs/database.md",
    "content": "Starlette is not strictly tied to any particular database implementation.\n\nYou are free to use any async database library that you prefer. Some popular options include:\n\n- [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL toolkit with native async support (2.0+).\n- [SQLModel](https://sqlmodel.tiangolo.com/) - SQL databases in Python, designed for simplicity, built on top of SQLAlchemy and Pydantic.\n- [Tortoise ORM](https://tortoise.github.io/) - An easy-to-use asyncio ORM inspired by Django.\n- [Piccolo](https://piccolo-orm.com/) - A fast, user-friendly ORM and query builder.\n\nRefer to your chosen database library's documentation for specific connection and query patterns.\n"
  },
  {
    "path": "docs/endpoints.md",
    "content": "\nStarlette includes the classes `HTTPEndpoint` and `WebSocketEndpoint` that provide a class-based view pattern for\nhandling HTTP method dispatching and WebSocket sessions.\n\n### HTTPEndpoint\n\nThe `HTTPEndpoint` class can be used as an ASGI application:\n\n```python\nfrom starlette.responses import PlainTextResponse\nfrom starlette.endpoints import HTTPEndpoint\n\n\nclass App(HTTPEndpoint):\n    async def get(self, request):\n        return PlainTextResponse(f\"Hello, world!\")\n```\n\nIf you're using a Starlette application instance to handle routing, you can\ndispatch to an `HTTPEndpoint` class. Make sure to dispatch to the class itself,\nrather than to an instance of the class:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import PlainTextResponse\nfrom starlette.endpoints import HTTPEndpoint\nfrom starlette.routing import Route\n\n\nclass Homepage(HTTPEndpoint):\n    async def get(self, request):\n        return PlainTextResponse(f\"Hello, world!\")\n\n\nclass User(HTTPEndpoint):\n    async def get(self, request):\n        username = request.path_params['username']\n        return PlainTextResponse(f\"Hello, {username}\")\n\nroutes = [\n    Route(\"/\", Homepage),\n    Route(\"/{username}\", User)\n]\n\napp = Starlette(routes=routes)\n```\n\nHTTP endpoint classes will respond with \"405 Method not allowed\" responses for any\nrequest methods which do not map to a corresponding handler.\n\n### WebSocketEndpoint\n\nThe `WebSocketEndpoint` class is an ASGI application that presents a wrapper around\nthe functionality of a `WebSocket` instance.\n\nThe ASGI connection scope is accessible on the endpoint instance via `.scope` and\nhas an attribute `encoding` which may optionally be set, in order to validate the expected websocket data in the `on_receive` method.\n\nThe encoding types are:\n\n* `'json'`\n* `'bytes'`\n* `'text'`\n\nThere are three overridable methods for handling specific ASGI websocket message types:\n\n* `async def on_connect(websocket, **kwargs)`\n* `async def on_receive(websocket, data)`\n* `async def on_disconnect(websocket, close_code)`\n\n```python\nfrom starlette.endpoints import WebSocketEndpoint\n\n\nclass App(WebSocketEndpoint):\n    encoding = 'bytes'\n\n    async def on_connect(self, websocket):\n        await websocket.accept()\n\n    async def on_receive(self, websocket, data):\n        await websocket.send_bytes(b\"Message: \" + data)\n\n    async def on_disconnect(self, websocket, close_code):\n        pass\n```\n\nThe `WebSocketEndpoint` can also be used with the `Starlette` application class:\n\n```python\nimport uvicorn\nfrom starlette.applications import Starlette\nfrom starlette.endpoints import WebSocketEndpoint, HTTPEndpoint\nfrom starlette.responses import HTMLResponse\nfrom starlette.routing import Route, WebSocketRoute\n\n\nhtml = \"\"\"\n<!DOCTYPE html>\n<html>\n    <head>\n        <title>Chat</title>\n    </head>\n    <body>\n        <h1>WebSocket Chat</h1>\n        <form action=\"\" onsubmit=\"sendMessage(event)\">\n            <input type=\"text\" id=\"messageText\" autocomplete=\"off\"/>\n            <button>Send</button>\n        </form>\n        <ul id='messages'>\n        </ul>\n        <script>\n            var ws = new WebSocket(\"ws://localhost:8000/ws\");\n            ws.onmessage = function(event) {\n                var messages = document.getElementById('messages')\n                var message = document.createElement('li')\n                var content = document.createTextNode(event.data)\n                message.appendChild(content)\n                messages.appendChild(message)\n            };\n            function sendMessage(event) {\n                var input = document.getElementById(\"messageText\")\n                ws.send(input.value)\n                input.value = ''\n                event.preventDefault()\n            }\n        </script>\n    </body>\n</html>\n\"\"\"\n\nclass Homepage(HTTPEndpoint):\n    async def get(self, request):\n        return HTMLResponse(html)\n\nclass Echo(WebSocketEndpoint):\n    encoding = \"text\"\n\n    async def on_receive(self, websocket, data):\n        await websocket.send_text(f\"Message text was: {data}\")\n\nroutes = [\n    Route(\"/\", Homepage),\n    WebSocketRoute(\"/ws\", Echo)\n]\n\napp = Starlette(routes=routes)\n```\n"
  },
  {
    "path": "docs/exceptions.md",
    "content": "\nStarlette allows you to install custom exception handlers to deal with\nhow you return responses when errors or handled exceptions occur.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import HTMLResponse\n\n\nHTML_404_PAGE = ...\nHTML_500_PAGE = ...\n\n\nasync def not_found(request: Request, exc: HTTPException):\n    return HTMLResponse(content=HTML_404_PAGE, status_code=exc.status_code)\n\nasync def server_error(request: Request, exc: HTTPException):\n    return HTMLResponse(content=HTML_500_PAGE, status_code=exc.status_code)\n\n\nexception_handlers = {\n    404: not_found,\n    500: server_error\n}\n\napp = Starlette(routes=routes, exception_handlers=exception_handlers)\n```\n\nIf `debug` is enabled and an error occurs, then instead of using the installed\n500 handler, Starlette will respond with a traceback response.\n\n```python\napp = Starlette(debug=True, routes=routes, exception_handlers=exception_handlers)\n```\n\nAs well as registering handlers for specific status codes, you can also\nregister handlers for classes of exceptions.\n\nIn particular you might want to override how the built-in `HTTPException` class\nis handled. For example, to use JSON style responses:\n\n```python\nasync def http_exception(request: Request, exc: HTTPException):\n    return JSONResponse({\"detail\": exc.detail}, status_code=exc.status_code)\n\nexception_handlers = {\n    HTTPException: http_exception\n}\n```\n\nThe `HTTPException` is also equipped with the `headers` argument. Which allows the propagation\nof the headers to the response class:\n\n```python\nasync def http_exception(request: Request, exc: HTTPException):\n    return JSONResponse(\n        {\"detail\": exc.detail},\n        status_code=exc.status_code,\n        headers=exc.headers\n    )\n```\n\nYou might also want to override how `WebSocketException` is handled:\n\n```python\nasync def websocket_exception(websocket: WebSocket, exc: WebSocketException):\n    await websocket.close(code=1008)\n\nexception_handlers = {\n    WebSocketException: websocket_exception\n}\n```\n\n## Errors and handled exceptions\n\nIt is important to differentiate between handled exceptions and errors.\n\nHandled exceptions do not represent error cases. They are coerced into appropriate\nHTTP responses, which are then sent through the standard middleware stack. By default\nthe `HTTPException` class is used to manage any handled exceptions.\n\nErrors are any other exception that occurs within the application. These cases\nshould bubble through the entire middleware stack as exceptions. Any error\nlogging middleware should ensure that it re-raises the exception all the\nway up to the server.\n\nIn practical terms, the error handled used is `exception_handler[500]` or `exception_handler[Exception]`.\nBoth keys `500` and `Exception` can be used. See below:\n\n```python\nasync def handle_error(request: Request, exc: HTTPException):\n    # Perform some logic\n    return JSONResponse({\"detail\": exc.detail}, status_code=exc.status_code)\n\nexception_handlers = {\n    Exception: handle_error  # or \"500: handle_error\"\n}\n```\n\nIt's important to notice that in case a [`BackgroundTask`](background.md) raises an exception,\nit will be handled by the `handle_error` function, but at that point, the response was already sent. In other words,\nthe response created by `handle_error` will be discarded. In case the error happens before the response was sent, then\nit will use the response object - in the above example, the returned `JSONResponse`.\n\nIn order to deal with this behaviour correctly, the middleware stack of a\n`Starlette` application is configured like this:\n\n* `ServerErrorMiddleware` - Returns 500 responses when server errors occur.\n* Installed middleware\n* `ExceptionMiddleware` - Deals with handled exceptions, and returns responses.\n* Router\n* Endpoints\n\n## HTTPException\n\nThe `HTTPException` class provides a base class that you can use for any handled exceptions.\nThe `ExceptionMiddleware` implementation defaults to returning plain-text HTTP responses for any `HTTPException`.\n\n* `HTTPException(status_code, detail=None, headers=None)`\n\nYou should only raise `HTTPException` inside routing or endpoints.\nMiddleware classes should instead just return appropriate responses directly.\n\nYou can use an `HTTPException` on a WebSocket endpoint. In case it's raised before `websocket.accept()`\nthe connection is not upgraded to a WebSocket connection, and the proper HTTP response is returned.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.exceptions import HTTPException\nfrom starlette.routing import WebSocketRoute\nfrom starlette.websockets import WebSocket\n\n\nasync def websocket_endpoint(websocket: WebSocket):\n    raise HTTPException(status_code=400, detail=\"Bad request\")\n\n\napp = Starlette(routes=[WebSocketRoute(\"/ws\", websocket_endpoint)])\n```\n\n## WebSocketException\n\nYou can use the `WebSocketException` class to raise errors inside of WebSocket endpoints.\n\n* `WebSocketException(code=1008, reason=None)`\n\nYou can set any code valid as defined [in the specification](https://tools.ietf.org/html/rfc6455#section-7.4.1).\n"
  },
  {
    "path": "docs/graphql.md",
    "content": "GraphQL support in Starlette was deprecated in version 0.15.0, and removed in version 0.17.0.\n\nAlthough GraphQL support is no longer built in to Starlette, you can still use GraphQL with Starlette via 3rd party libraries. These libraries all have Starlette-specific guides to help you do just that:\n\n- [Ariadne](https://ariadnegraphql.org/docs/starlette-integration.html)\n- [`starlette-graphene3`](https://github.com/ciscorn/starlette-graphene3#example)\n- [Strawberry](https://strawberry.rocks/docs/integrations/starlette)\n- [`tartiflette-asgi`](https://tartiflette.github.io/tartiflette-asgi/usage/#starlette)\n\n"
  },
  {
    "path": "docs/index.md",
    "content": "<p align=\"center\">\n  <img width=\"400px\" src=\"/img/starlette.svg#only-light\" alt=\"starlette\"/>\n  <img width=\"400px\" src=\"/img/starlette_dark.svg#only-dark\" alt=\"starlette\"/>\n</p>\n<p align=\"center\">\n    <em>✨ The little ASGI framework that shines. ✨</em>\n</p>\n<p align=\"center\">\n<a href=\"https://github.com/Kludex/starlette/actions\">\n    <img src=\"https://github.com/Kludex/starlette/workflows/Test%20Suite/badge.svg\" alt=\"Build Status\">\n</a>\n<a href=\"https://pypi.org/project/starlette/\">\n    <img src=\"https://badge.fury.io/py/starlette.svg\" alt=\"Package version\">\n</a>\n<a href=\"https://pypi.org/project/starlette\" target=\"_blank\">\n    <img src=\"https://img.shields.io/pypi/pyversions/starlette.svg?color=%2334D058\" alt=\"Supported Python versions\">\n</a>\n<a href=\"https://discord.gg/RxKUF5JuHs\">\n    <img src=\"https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2\" alt=\"Discord\">\n</a>\n</p>\n\n---\n\n**Documentation**: <a href=\"https://starlette.dev/\" target=\"_blank\">https://starlette.dev</a>\n\n**Source Code**: <a href=\"https://github.com/Kludex/starlette\" target=\"_blank\">https://github.com/Kludex/starlette</a>\n\n---\n\n# Introduction\n\nStarlette is a lightweight [ASGI][asgi] framework/toolkit,\nwhich is ideal for building async web services in Python.\n\nIt is production-ready, and gives you the following:\n\n* A lightweight, low-complexity HTTP web framework.\n* WebSocket support.\n* In-process background tasks.\n* Startup and shutdown events.\n* Test client built on `httpx`.\n* CORS, GZip, Static Files, Streaming responses.\n* Session and Cookie support.\n* 100% test coverage.\n* 100% type annotated codebase.\n* Few hard dependencies.\n* Compatible with `asyncio` and `trio` backends.\n* Great overall performance [against independent benchmarks][techempower].\n\n\n## Sponsorship\n\nHelp us keep Starlette maintained and sustainable by [becoming a sponsor](https://github.com/sponsors/Kludex).\n\n**Current sponsors:**\n\n<div style=\"display: flex; flex-wrap: wrap; gap: 2rem; align-items: center; margin: 1rem 0;\">\n    <a href=\"https://fastapi.tiangolo.com\">\n        <img src=\"https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png\" alt=\"FastAPI\" style=\"height: 80px;\">\n    </a>\n    <a href=\"https://huggingface.co\">\n        <img src=\"https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo-with-title.svg\" alt=\"Hugging Face\" style=\"height: 80px;\">\n    </a>\n</div>\n\n## Installation\n\n```shell\npip install starlette\n```\n\nYou'll also want to install an ASGI server, such as [uvicorn](https://www.uvicorn.org/), [daphne](https://github.com/django/daphne/), or [hypercorn](https://hypercorn.readthedocs.io/en/latest/).\n\n```shell\npip install uvicorn\n```\n\n## Example\n\n```python title=\"main.py\"\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route\n\n\nasync def homepage(request):\n    return JSONResponse({'hello': 'world'})\n\n\napp = Starlette(debug=True, routes=[\n    Route('/', homepage),\n])\n```\n\nThen run the application...\n\n```shell\nuvicorn main:app\n```\n\n## Dependencies\n\nStarlette only requires `anyio`, and the following dependencies are optional:\n\n* [`httpx`][httpx] - Required if you want to use the `TestClient`.\n* [`jinja2`][jinja2] - Required if you want to use `Jinja2Templates`.\n* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`.\n* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.\n* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.\n\nYou can install all of these with `pip install starlette[full]`.\n\n## Framework or Toolkit\n\nStarlette is designed to be used either as a complete framework, or as\nan ASGI toolkit. You can use any of its components independently.\n\n```python title=\"main.py\"\nfrom starlette.responses import PlainTextResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = PlainTextResponse('Hello, world!')\n    await response(scope, receive, send)\n```\n\nRun the `app` application in `main.py`:\n\n```shell\n$ uvicorn main:app\nINFO: Started server process [11509]\nINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n```\n\nRun uvicorn with `--reload` to enable auto-reloading on code changes.\n\n## Modularity\n\nThe modularity that Starlette is designed on promotes building re-usable\ncomponents that can be shared between any ASGI framework. This should enable\nan ecosystem of shared middleware and mountable applications.\n\nThe clean API separation also means it's easier to understand each component\nin isolation.\n\n---\n\n<p align=\"center\"><i>Starlette is <a href=\"https://github.com/Kludex/starlette/blob/main/LICENSE.md\">BSD licensed</a> code.<br/>Designed & crafted with care.</i></br>&mdash; ⭐️ &mdash;</p>\n\n[asgi]: https://asgi.readthedocs.io/en/latest/\n[httpx]: https://www.python-httpx.org/\n[jinja2]: https://jinja.palletsprojects.com/\n[python-multipart]: https://multipart.fastapiexpert.com/\n[itsdangerous]: https://itsdangerous.palletsprojects.com/\n[sqlalchemy]: https://www.sqlalchemy.org\n[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation\n[techempower]: https://www.techempower.com/benchmarks/#hw=ph&test=fortune&l=zijzen-sf\n"
  },
  {
    "path": "docs/js/custom.js",
    "content": "function shuffle(array) {\n    var currentIndex = array.length, temporaryValue, randomIndex;\n    while (0 !== currentIndex) {\n        randomIndex = Math.floor(Math.random() * currentIndex);\n        currentIndex -= 1;\n        temporaryValue = array[currentIndex];\n        array[currentIndex] = array[randomIndex];\n        array[randomIndex] = temporaryValue;\n    }\n    return array;\n}\n\nasync function showRandomAnnouncement(groupId, timeInterval) {\n    const announceGroup = document.getElementById(groupId);\n    if (announceGroup) {\n        let children = [].slice.call(announceGroup.children);\n        children = shuffle(children)\n        let index = 0\n        const announceRandom = () => {\n            children.forEach((el, i) => { el.style.display = \"none\" });\n            children[index].style.display = \"block\"\n            index = (index + 1) % children.length\n        }\n        announceRandom()\n        setInterval(announceRandom, timeInterval)\n    }\n}\n\nasync function main() {\n    showRandomAnnouncement('announce-msg', 5000)\n}\n\ndocument$.subscribe(() => {\n    main()\n})\n"
  },
  {
    "path": "docs/lifespan.md",
    "content": "\nStarlette applications can register a lifespan handler for dealing with\ncode that needs to run before the application starts up, or when the application\nis shutting down.\n\n```python\nimport contextlib\n\nfrom starlette.applications import Starlette\n\n\n@contextlib.asynccontextmanager\nasync def lifespan(app):\n    async with some_async_resource():\n        print(\"Run at startup!\")\n        yield\n        print(\"Run on shutdown!\")\n\n\nroutes = [\n    ...\n]\n\napp = Starlette(routes=routes, lifespan=lifespan)\n```\n\nStarlette will not start serving any incoming requests until the lifespan has been run.\n\nThe lifespan teardown will run once all connections have been closed, and\nany in-process background tasks have completed.\n\nConsider using [`anyio.create_task_group()`](https://anyio.readthedocs.io/en/stable/tasks.html)\nfor managing asynchronous tasks.\n\n## Lifespan State\n\nThe lifespan has the concept of `state`, which is a dictionary that\ncan be used to share the objects between the lifespan, and the requests.\n\n```python\nimport contextlib\nfrom typing import AsyncIterator, TypedDict\n\nimport httpx\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\n\n\nclass State(TypedDict):\n    http_client: httpx.AsyncClient\n\n\n@contextlib.asynccontextmanager\nasync def lifespan(app: Starlette) -> AsyncIterator[State]:\n    async with httpx.AsyncClient() as client:\n        yield {\"http_client\": client}\n\n\nasync def homepage(request: Request) -> PlainTextResponse:\n    client = request.state.http_client\n    response = await client.get(\"https://www.example.com\")\n    return PlainTextResponse(response.text)\n\n\napp = Starlette(\n    lifespan=lifespan,\n    routes=[Route(\"/\", homepage)]\n)\n```\n\nThe `state` received on the requests is a **shallow** copy of the state received on the\nlifespan handler.\n\n## Accessing State\n\nThe state can be accessed using either attribute-style or dictionary-style syntax.\n\nThe dictionary-style syntax was introduced in Starlette 0.52.0 (January 2026), with the idea of\nimproving type safety when using the lifespan state, given that `Request` became a generic over\nthe state type.\n\n```python\nfrom collections.abc import AsyncIterator\nfrom contextlib import asynccontextmanager\nfrom typing import TypedDict\n\nimport httpx\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\n\n\nclass State(TypedDict):\n    http_client: httpx.AsyncClient\n\n\n@asynccontextmanager\nasync def lifespan(app: Starlette) -> AsyncIterator[State]:\n    async with httpx.AsyncClient() as client:\n        yield {\"http_client\": client}\n\n\nasync def homepage(request: Request[State]) -> PlainTextResponse:\n    client = request.state[\"http_client\"]\n\n    reveal_type(client)  # Revealed type is 'httpx.AsyncClient'\n\n    response = await client.get(\"https://www.example.com\")\n    return PlainTextResponse(response.text)\n\napp = Starlette(lifespan=lifespan, routes=[Route(\"/\", homepage)])\n```\n\nThis also works with WebSockets:\n\n```python\nasync def websocket_endpoint(websocket: WebSocket[State]) -> None:\n    await websocket.accept()\n    client = websocket.state[\"http_client\"]\n    response = await client.get(\"https://www.example.com\")\n    await websocket.send_text(response.text)\n    await websocket.close()\n\n\napp = Starlette(lifespan=lifespan, routes=[WebSocketRoute(\"/ws\", websocket_endpoint)])\n```\n\n!!! note\n    There were many attempts to make this work with attribute-style access instead of\n    dictionary-style access, but none were satisfactory, given they would have been\n    breaking changes, or there were typing limitations.\n\n    For more details, see:\n\n    - [@Kludex/starlette#issues/3005](https://github.com/Kludex/starlette/issues/3005)\n    - [@python/typing#discussions/1457](https://github.com/python/typing/discussions/1457)\n    - [@Kludex/starlette#pull/3036](https://github.com/Kludex/starlette/pull/3036)\n\n## Running lifespan in tests\n\nYou should use `TestClient` as a context manager, to ensure that the lifespan is called.\n\n```python\nfrom example import app\nfrom starlette.testclient import TestClient\n\n\ndef test_homepage():\n    with TestClient(app) as client:\n        # Application's lifespan is called on entering the block.\n        response = client.get(\"/\")\n        assert response.status_code == 200\n\n    # And the lifespan's teardown is run when exiting the block.\n```\n"
  },
  {
    "path": "docs/middleware.md",
    "content": "\nStarlette includes several middleware classes for adding behavior that is applied across\nyour entire application. These are all implemented as standard ASGI\nmiddleware classes, and can be applied either to Starlette or to any other ASGI application.\n\n## Using middleware\n\nThe Starlette application class allows you to include the ASGI middleware\nin a way that ensures that it remains wrapped by the exception handler.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\nroutes = ...\n\n# Ensure that all requests include an 'example.com' or\n# '*.example.com' host header, and strictly enforce https-only access.\nmiddleware = [\n    Middleware(\n        TrustedHostMiddleware,\n        allowed_hosts=['example.com', '*.example.com'],\n    ),\n    Middleware(HTTPSRedirectMiddleware)\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nEvery Starlette application automatically includes two pieces of middleware by default:\n\n* `ServerErrorMiddleware` - Ensures that application exceptions may return a custom 500 page, or display an application traceback in DEBUG mode. This is *always* the outermost middleware layer.\n* `ExceptionMiddleware` - Adds exception handlers, so that particular types of expected exception cases can be associated with handler functions. For example raising `HTTPException(status_code=404)` within an endpoint will end up rendering a custom 404 page.\n\nMiddleware is evaluated from top-to-bottom, so the flow of execution in our example\napplication would look like this:\n\n* Middleware\n    * `ServerErrorMiddleware`\n    * `TrustedHostMiddleware`\n    * `HTTPSRedirectMiddleware`\n    * `ExceptionMiddleware`\n* Routing\n* Endpoint\n\nThe following middleware implementations are available in the Starlette package:\n\n## CORSMiddleware\n\nAdds appropriate [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to outgoing responses in order to allow cross-origin requests from browsers.\n\nThe default parameters used by the CORSMiddleware implementation are restrictive by default,\nso you'll need to explicitly enable particular origins, methods, or headers, in order\nfor browsers to be permitted to use them in a Cross-Domain context.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.cors import CORSMiddleware\n\nroutes = ...\n\nmiddleware = [\n    Middleware(CORSMiddleware, allow_origins=['*'])\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nThe following arguments are supported:\n\n* `allow_origins` - A list of origins that should be permitted to make cross-origin requests. eg. `['https://example.org', 'https://www.example.org']`. You can use `['*']` to allow any origin.\n* `allow_origin_regex` - A regex string to match against origins that should be permitted to make cross-origin requests. eg. `'https://[a-zA-Z0-9-]+\\.example\\.org'`. Avoid using `.*` or `.+` as they match URL special characters (`/`, `@`, `#`, `?`) and may result in overly permissive origin matching. Use specific character classes like `[a-zA-Z0-9-]+` instead.\n* `allow_methods` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use `['*']` to allow all standard methods.\n* `allow_headers` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to `[]`. You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for CORS requests.\n* `allow_credentials` - Indicate that cookies should be supported for cross-origin requests. Defaults to `False`. Also, `allow_origins`, `allow_methods` and `allow_headers` cannot be set to `['*']` for credentials to be allowed, all of them must be explicitly specified.\n* `allow_private_network` - Indicates whether to accept cross-origin requests over a private network. Defaults to `False`.\n* `expose_headers` - Indicate any response headers that should be made accessible to the browser. Defaults to `[]`.\n* `max_age` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to `600`.\n\nThe middleware responds to two particular types of HTTP request...\n\n#### CORS preflight requests\n\nThese are any `OPTIONS` request with `Origin` and `Access-Control-Request-Method` headers.\nIn this case the middleware will intercept the incoming request and respond with\nappropriate CORS headers, and either a 200 or 400 response for informational purposes.\n\n#### Simple requests\n\nAny request with an `Origin` header. In this case the middleware will pass the\nrequest through as normal, but will include appropriate CORS headers on the response.\n\n#### Private Network Access (PNA)\n\nPrivate Network Access is a browser security feature that restricts websites from public networks from accessing servers on private networks.\n\nWhen a website attempts to make such a cross-network request, the browser will send a `Access-Control-Request-Private-Network: true` header in the\npre-flight request. If the `allow_private_network` flag is set to `True`, the middleware will include the `Access-Control-Allow-Private-Network: true`\nheader in the response, allowing the request. If set to `False`, the middleware will return a 400 response, blocking the request.\n\n### CORSMiddleware Global Enforcement\n\nWhen using CORSMiddleware with your Starlette application, it's important to ensure that CORS headers are applied even to error responses generated by unhandled exceptions. The recommended solution is to wrap the entire Starlette application with CORSMiddleware. This approach guarantees that even if an exception is caught by ServerErrorMiddleware (or other outer error-handling middleware), the response will still include the proper `Access-Control-Allow-Origin` header.\n\nFor example, instead of adding CORSMiddleware as an inner `middleware` via the Starlette middleware parameter, you can wrap your application as follows:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware.cors import CORSMiddleware\n\nimport uvicorn\n\napp = Starlette()\napp = CORSMiddleware(app=app, allow_origins=[\"*\"])\n\n# ... your routes and middleware configuration ...\n\nif __name__ == '__main__':\n    uvicorn.run(\n        app,\n        host='0.0.0.0',\n        port=8000\n    )\n```\n\n## SessionMiddleware\n\nAdds signed cookie-based HTTP sessions. Session information is readable but not modifiable.\n\nThe session cookie is always set with the `\"HttpOnly\"` flag, preventing client-side JavaScript from accessing it.\n\nAccess or modify the session data using the `request.session` dictionary interface.\n\nThe following arguments are supported:\n\n* `secret_key` - Should be a random string.\n* `session_cookie` - Defaults to \"session\".\n* `max_age` - Session expiry time in seconds. Defaults to 2 weeks. If set to `None` then the cookie will last as long as the browser session.\n* `same_site` - SameSite flag prevents the browser from sending session cookie along with cross-site requests. Defaults to `'lax'`.\n* `path` - The path set for the session cookie. Defaults to `'/'`.\n* `https_only` - Indicate that the `\"Secure\"` flag should be set (can be used with HTTPS only). Defaults to `False`. Set this to `True` in production to ensure the session cookie is only sent over HTTPS.\n* `domain` - Domain of the cookie used to share cookie between subdomains or cross-domains. The browser defaults the domain to the same host that set the cookie, excluding subdomains ([reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#domain_attribute)).\n\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.sessions import SessionMiddleware\n\nroutes = ...\n\nmiddleware = [\n    Middleware(SessionMiddleware, secret_key=..., https_only=True)\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\n## HTTPSRedirectMiddleware\n\nEnforces that all incoming requests must either be `https` or `wss`. Any incoming\nrequests to `http` or `ws` will be redirected to the secure scheme instead.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\n\nroutes = ...\n\nmiddleware = [\n    Middleware(HTTPSRedirectMiddleware)\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nThere are no configuration options for this middleware class.\n\n## TrustedHostMiddleware\n\nEnforces that all incoming requests have a correctly set `Host` header, in order\nto guard against HTTP Host Header attacks.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\nroutes = ...\n\nmiddleware = [\n    Middleware(TrustedHostMiddleware, allowed_hosts=['example.com', '*.example.com'])\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nThe following arguments are supported:\n\n* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard\ndomains such as `*.example.com` are supported for matching subdomains. To allow any\nhostname either use `allowed_hosts=[\"*\"]` or omit the middleware.\n* `www_redirect` - If set to True, requests to non-www versions of the allowed hosts will be redirected to their www counterparts. Defaults to `True`.\n\nIf an incoming request does not validate correctly then a 400 response will be sent.\n\n## GZipMiddleware\n\nHandles GZip responses for any request that includes `\"gzip\"` in the `Accept-Encoding` header.\n\nThe middleware will handle both standard and streaming responses.\n\n??? info \"Buffer on streaming responses\"\n    On streaming responses, the middleware will buffer the response before compressing it.\n\n    The idea is that we don't want to compress every small chunk of data, as it would be inefficient.\n    Instead, we buffer the response until it reaches a certain size, and then compress it.\n\n    This may cause a delay in the response, as the middleware waits for the buffer to fill up before compressing it.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.gzip import GZipMiddleware\n\n\nroutes = ...\n\nmiddleware = [\n    Middleware(GZipMiddleware, minimum_size=1000, compresslevel=9)\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nThe following arguments are supported:\n\n* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`.\n* `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes.\n\nThe middleware won't GZip responses that already have either a `Content-Encoding` set, to prevent them from\nbeing encoded twice, or a `Content-Type` set to `text/event-stream`, to avoid compressing server-sent events.\n\n## BaseHTTPMiddleware\n\nAn abstract class that allows you to write ASGI middleware against a request/response\ninterface.\n\n### Usage\n\nTo implement a middleware class using `BaseHTTPMiddleware`, you must override the\n`async def dispatch(request, call_next)` method.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\n\nclass CustomHeaderMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        response = await call_next(request)\n        response.headers['Custom'] = 'Example'\n        return response\n\nroutes = ...\n\nmiddleware = [\n    Middleware(CustomHeaderMiddleware)\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nIf you want to provide configuration options to the middleware class you should\noverride the `__init__` method, ensuring that the first argument is `app`, and\nany remaining arguments are optional keyword arguments. Make sure to set the `app`\nattribute on the instance if you do this.\n\n```python\nclass CustomHeaderMiddleware(BaseHTTPMiddleware):\n    def __init__(self, app, header_value='Example'):\n        super().__init__(app)\n        self.header_value = header_value\n\n    async def dispatch(self, request, call_next):\n        response = await call_next(request)\n        response.headers['Custom'] = self.header_value\n        return response\n\n\nmiddleware = [\n    Middleware(CustomHeaderMiddleware, header_value='Customized')\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\nMiddleware classes should not modify their state outside of the `__init__` method.\nInstead you should keep any state local to the `dispatch` method, or pass it\naround explicitly, rather than mutating the middleware instance.\n\n### Limitations\n\nCurrently, the `BaseHTTPMiddleware` has some known limitations:\n\n- Using `BaseHTTPMiddleware` will prevent changes to [`contextvars.ContextVar`](https://docs.python.org/3/library/contextvars.html#contextvars.ContextVar)s from propagating upwards. That is, if you set a value for a `ContextVar` in your endpoint and try to read it from a middleware you will find that the value is not the same value you set in your endpoint (see [this test](https://github.com/Kludex/starlette/blob/621abc747a6604825190b93467918a0ec6456a24/tests/middleware/test_base.py#L192-L223) for an example of this behavior). Importantly, this also means that if a `BaseHTTPMiddleware` is positioned earlier in the middleware stack, it will disrupt `contextvars` propagation for any subsequent Pure ASGI Middleware that relies on them.\n\nTo overcome these limitations, use [pure ASGI middleware](#pure-asgi-middleware), as shown below.\n\n## Pure ASGI Middleware\n\nThe [ASGI spec](https://asgi.readthedocs.io/en/latest/) makes it possible to implement ASGI middleware using the ASGI interface directly, as a chain of ASGI applications that call into the next one. In fact, this is how middleware classes shipped with Starlette are implemented.\n\nThis lower-level approach provides greater control over behavior and enhanced interoperability across frameworks and servers. It also overcomes the [limitations of `BaseHTTPMiddleware`](#limitations).\n\n### Writing pure ASGI middleware\n\nThe most common way to create an ASGI middleware is with a class.\n\n```python\nclass ASGIMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        await self.app(scope, receive, send)\n```\n\nThe middleware above is the most basic ASGI middleware. It receives a parent ASGI application as an argument for its constructor, and implements an `async __call__` method which calls into that parent application.\n\nSome implementations such as [`asgi-cors`](https://github.com/simonw/asgi-cors/blob/10ef64bfcc6cd8d16f3014077f20a0fb8544ec39/asgi_cors.py) use an alternative style, using functions:\n\n```python\nimport functools\n\ndef asgi_middleware():\n    def asgi_decorator(app):\n\n        @functools.wraps(app)\n        async def wrapped_app(scope, receive, send):\n            await app(scope, receive, send)\n\n        return wrapped_app\n\n    return asgi_decorator\n```\n\nIn any case, ASGI middleware must be callables that accept three arguments: `scope`, `receive`, and `send`.\n\n* `scope` is a dict holding information about the connection, where `scope[\"type\"]` may be:\n    * [`\"http\"`](https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope): for HTTP requests.\n    * [`\"websocket\"`](https://asgi.readthedocs.io/en/latest/specs/www.html#websocket-connection-scope): for WebSocket connections.\n    * [`\"lifespan\"`](https://asgi.readthedocs.io/en/latest/specs/lifespan.html#scope): for ASGI lifespan messages.\n* `receive` and `send` can be used to exchange ASGI event messages with the ASGI server — more on this below. The type and contents of these messages depend on the scope type. Learn more in the [ASGI specification](https://asgi.readthedocs.io/en/latest/specs/index.html).\n\n### Using pure ASGI middleware\n\nPure ASGI middleware can be used like any other middleware:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nfrom .middleware import ASGIMiddleware\n\nroutes = ...\n\nmiddleware = [\n    Middleware(ASGIMiddleware),\n]\n\napp = Starlette(..., middleware=middleware)\n```\n\nSee also [Using middleware](#using-middleware).\n\n### Type annotations\n\nThere are two ways of annotating a middleware: using Starlette itself or [`asgiref`](https://github.com/django/asgiref).\n\n* Using Starlette: for most common use cases.\n\n```python\nfrom starlette.types import ASGIApp, Message, Scope, Receive, Send\n\n\nclass ASGIMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] != \"http\":\n            return await self.app(scope, receive, send)\n\n        async def send_wrapper(message: Message) -> None:\n            # ... Do something\n            await send(message)\n\n        await self.app(scope, receive, send_wrapper)\n```\n\n* Using [`asgiref`](https://github.com/django/asgiref): for more rigorous type hinting.\n\n```python\nfrom asgiref.typing import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope\nfrom asgiref.typing import ASGIReceiveEvent, ASGISendEvent\n\n\nclass ASGIMiddleware:\n    def __init__(self, app: ASGI3Application) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        async def send_wrapper(message: ASGISendEvent) -> None:\n            # ... Do something\n            await send(message)\n\n        return await self.app(scope, receive, send_wrapper)\n```\n\n### Common patterns\n\n#### Processing certain requests only\n\nASGI middleware can apply specific behavior according to the contents of `scope`.\n\nFor example, to only process HTTP requests, write this...\n\n```python\nclass ASGIMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        ...  # Do something here!\n\n        await self.app(scope, receive, send)\n```\n\nLikewise, WebSocket-only middleware would guard on `scope[\"type\"] != \"websocket\"`.\n\nThe middleware may also act differently based on the request method, URL, headers, etc.\n\n#### Reusing Starlette components\n\nStarlette provides several data structures that accept the ASGI `scope`, `receive` and/or `send` arguments, allowing you to work at a higher level of abstraction. Such data structures include [`Request`](requests.md#request), [`Headers`](requests.md#headers), [`QueryParams`](requests.md#query-parameters), [`URL`](requests.md#url), etc.\n\nFor example, you can instantiate a `Request` to more easily inspect an HTTP request:\n\n```python\nfrom starlette.requests import Request\n\nclass ASGIMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] == \"http\":\n            request = Request(scope)\n            ... # Use `request.method`, `request.url`, `request.headers`, etc.\n\n        await self.app(scope, receive, send)\n```\n\nYou can also reuse [responses](responses.md), which are ASGI applications as well.\n\n#### Sending eager responses\n\nInspecting the connection `scope` allows you to conditionally call into a different ASGI app. One use case might be sending a response without calling into the app.\n\nAs an example, this middleware uses a dictionary to perform permanent redirects based on the requested path. This could be used to implement ongoing support of legacy URLs in case you need to refactor route URL patterns.\n\n```python\nfrom starlette.datastructures import URL\nfrom starlette.responses import RedirectResponse\n\nclass RedirectsMiddleware:\n    def __init__(self, app, path_mapping: dict):\n        self.app = app\n        self.path_mapping = path_mapping\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        url = URL(scope=scope)\n\n        if url.path in self.path_mapping:\n            url = url.replace(path=self.path_mapping[url.path])\n            response = RedirectResponse(url, status_code=301)\n            await response(scope, receive, send)\n            return\n\n        await self.app(scope, receive, send)\n```\n\nExample usage would look like this:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\n\nroutes = ...\n\nredirections = {\n    \"/v1/resource/\": \"/v2/resource/\",\n    # ...\n}\n\nmiddleware = [\n    Middleware(RedirectsMiddleware, path_mapping=redirections),\n]\n\napp = Starlette(routes=routes, middleware=middleware)\n```\n\n\n#### Inspecting or modifying the request\n\nRequest information can be accessed or changed by manipulating the `scope`. For a full example of this pattern, see Uvicorn's [`ProxyHeadersMiddleware`](https://github.com/encode/uvicorn/blob/fd4386fefb8fe8a4568831a7d8b2930d5fb61455/uvicorn/middleware/proxy_headers.py) which inspects and tweaks the `scope` when serving behind a frontend proxy.\n\nBesides, wrapping the `receive` ASGI callable allows you to access or modify the HTTP request body by manipulating [`http.request`](https://asgi.readthedocs.io/en/latest/specs/www.html#request-receive-event) ASGI event messages.\n\nAs an example, this middleware computes and logs the size of the incoming request body...\n\n```python\nclass LoggedRequestBodySizeMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        body_size = 0\n\n        async def receive_logging_request_body_size():\n            nonlocal body_size\n\n            message = await receive()\n            assert message[\"type\"] == \"http.request\"\n\n            body_size += len(message.get(\"body\", b\"\"))\n\n            if not message.get(\"more_body\", False):\n                print(f\"Size of request body was: {body_size} bytes\")\n\n            return message\n\n        await self.app(scope, receive_logging_request_body_size, send)\n```\n\nLikewise, WebSocket middleware may manipulate [`websocket.receive`](https://asgi.readthedocs.io/en/latest/specs/www.html#receive-receive-event) ASGI event messages to inspect or alter incoming WebSocket data.\n\nFor an example that changes the HTTP request body, see [`msgpack-asgi`](https://github.com/florimondmanca/msgpack-asgi).\n\n#### Inspecting or modifying the response\n\nWrapping the `send` ASGI callable allows you to inspect or modify the HTTP response sent by the underlying application. To do so, react to [`http.response.start`](https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event) or [`http.response.body`](https://asgi.readthedocs.io/en/latest/specs/www.html#response-body-send-event) ASGI event messages.\n\nAs an example, this middleware adds some fixed extra response headers:\n\n```python\nfrom starlette.datastructures import MutableHeaders\n\nclass ExtraResponseHeadersMiddleware:\n    def __init__(self, app, headers):\n        self.app = app\n        self.headers = headers\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] != \"http\":\n            return await self.app(scope, receive, send)\n\n        async def send_with_extra_headers(message):\n            if message[\"type\"] == \"http.response.start\":\n                headers = MutableHeaders(scope=message)\n                for key, value in self.headers:\n                    headers.append(key, value)\n\n            await send(message)\n\n        await self.app(scope, receive, send_with_extra_headers)\n```\n\nSee also [`asgi-logger`](https://github.com/Kludex/asgi-logger/blob/main/asgi_logger/middleware.py) for an example that inspects the HTTP response and logs a configurable HTTP access log line.\n\nLikewise, WebSocket middleware may manipulate [`websocket.send`](https://asgi.readthedocs.io/en/latest/specs/www.html#send-send-event) ASGI event messages to inspect or alter outgoing WebSocket data.\n\nNote that if you change the response body, you will need to update the response `Content-Length` header to match the new response body length. See [`brotli-asgi`](https://github.com/fullonic/brotli-asgi) for a complete example.\n\n#### Passing information to endpoints\n\nIf you need to share information with the underlying app or endpoints, you may store it into the `scope` dictionary. Note that this is a convention -- for example, Starlette uses this to share routing information with endpoints -- but it is not part of the ASGI specification. If you do so, be sure to avoid conflicts by using keys that have low chances of being used by other middleware or applications.\n\nFor example, when including the middleware below, endpoints would be able to access `request.scope[\"asgi_transaction_id\"]`.\n\n```python\nimport uuid\n\nclass TransactionIDMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        scope[\"asgi_transaction_id\"] = uuid.uuid4()\n        await self.app(scope, receive, send)\n```\n\n#### Cleanup and error handling\n\nYou can wrap the application in a `try/except/finally` block or a context manager to perform cleanup operations or do error handling.\n\nFor example, the following middleware might collect metrics and process application exceptions...\n\n```python\nimport time\n\nclass MonitoringMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        start = time.time()\n        try:\n            await self.app(scope, receive, send)\n        except Exception as exc:\n            ...  # Process the exception\n            raise\n        finally:\n            end = time.time()\n            elapsed = end - start\n            ...  # Submit `elapsed` as a metric to a monitoring backend\n```\n\nSee also [`timing-asgi`](https://github.com/steinnes/timing-asgi) for a full example of this pattern.\n\n### Gotchas\n\n#### ASGI middleware should be stateless\n\nBecause ASGI is designed to handle concurrent requests, any connection-specific state should be scoped to the `__call__` implementation. Not doing so would typically lead to conflicting variable reads/writes across requests, and most likely bugs.\n\nAs an example, this would conditionally replace the response body, if an `X-Mock` header is present in the response...\n\n=== \"✅ Do\"\n\n    ```python\n    from starlette.datastructures import Headers\n\n    class MockResponseBodyMiddleware:\n        def __init__(self, app, content):\n            self.app = app\n            self.content = content\n\n        async def __call__(self, scope, receive, send):\n            if scope[\"type\"] != \"http\":\n                await self.app(scope, receive, send)\n                return\n\n            # A flag that we will turn `True` if the HTTP response\n            # has the 'X-Mock' header.\n            # ✅: Scoped to this function.\n            should_mock = False\n\n            async def maybe_send_with_mock_content(message):\n                nonlocal should_mock\n\n                if message[\"type\"] == \"http.response.start\":\n                    headers = Headers(raw=message[\"headers\"])\n                    should_mock = headers.get(\"X-Mock\") == \"1\"\n                    await send(message)\n\n                elif message[\"type\"] == \"http.response.body\":\n                    if should_mock:\n                        message = {\"type\": \"http.response.body\", \"body\": self.content}\n                    await send(message)\n\n            await self.app(scope, receive, maybe_send_with_mock_content)\n    ```\n\n=== \"❌ Don't\"\n\n    ```python hl_lines=\"7-8\"\n    from starlette.datastructures import Headers\n\n    class MockResponseBodyMiddleware:\n        def __init__(self, app, content):\n            self.app = app\n            self.content = content\n            # ❌: This variable would be read and written across requests!\n            self.should_mock = False\n\n        async def __call__(self, scope, receive, send):\n            if scope[\"type\"] != \"http\":\n                await self.app(scope, receive, send)\n                return\n\n            async def maybe_send_with_mock_content(message):\n                if message[\"type\"] == \"http.response.start\":\n                    headers = Headers(raw=message[\"headers\"])\n                    self.should_mock = headers.get(\"X-Mock\") == \"1\"\n                    await send(message)\n\n                elif message[\"type\"] == \"http.response.body\":\n                    if self.should_mock:\n                        message = {\"type\": \"http.response.body\", \"body\": self.content}\n                    await send(message)\n\n            await self.app(scope, receive, maybe_send_with_mock_content)\n    ```\n\nSee also [`GZipMiddleware`](https://github.com/Kludex/starlette/blob/9ef1b91c9c043197da6c3f38aa153fd874b95527/starlette/middleware/gzip.py) for a full example implementation that navigates this potential gotcha.\n\n### Further reading\n\nThis documentation should be enough to have a good basis on how to create an ASGI middleware.\n\nNonetheless, there are great articles about the subject:\n\n- [Introduction to ASGI: Emergence of an Async Python Web Ecosystem](https://florimond.dev/en/posts/2019/08/introduction-to-asgi-async-python-web/)\n- [How to write ASGI middleware](https://pgjones.dev/blog/how-to-write-asgi-middleware-2021/)\n\n## Using middleware in other frameworks\n\nTo wrap ASGI middleware around other ASGI applications, you should use the\nmore general pattern of wrapping the application instance:\n\n```python\napp = TrustedHostMiddleware(app, allowed_hosts=['example.com'])\n```\n\nYou can do this with a Starlette application instance too, but it is preferable\nto use the `middleware=<List of Middleware instances>` style, as it will:\n\n* Ensure that everything remains wrapped in a single outermost `ServerErrorMiddleware`.\n* Preserves the top-level `app` instance.\n\n## Applying middleware to groups of routes\n\nMiddleware can also be added to `Mount` instances, which allows you to apply middleware to a group of routes or a sub-application:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.gzip import GZipMiddleware\nfrom starlette.routing import Mount, Route\n\n\nroutes = [\n    Mount(\n        \"/\",\n        routes=[\n            Route(\n                \"/example\",\n                endpoint=...,\n            )\n        ],\n        middleware=[Middleware(GZipMiddleware)]\n    )\n]\n\napp = Starlette(routes=routes)\n```\n\nNote that middleware used in this way is *not* wrapped in exception handling middleware like the middleware applied to the `Starlette` application is.\nThis is often not a problem because it only applies to middleware that inspect or modify the `Response`, and even then you probably don't want to apply this logic to error responses.\nIf you do want to apply the middleware logic to error responses only on some routes you have a couple of options:\n\n* Add an `ExceptionMiddleware` onto the `Mount`\n* Add a `try/except` block to your middleware and return an error response from there\n* Split up marking and processing into two middlewares, one that gets put on `Mount` which marks the response as needing processing (for example by setting `scope[\"log-response\"] = True`) and another applied to the `Starlette` application that does the heavy lifting.\n\nThe `Route`/`WebSocket` class also accepts a `middleware` argument, which allows you to apply middleware to a single route:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.gzip import GZipMiddleware\nfrom starlette.routing import Route\n\n\nroutes = [\n    Route(\n        \"/example\",\n        endpoint=...,\n        middleware=[Middleware(GZipMiddleware)]\n    )\n]\n\napp = Starlette(routes=routes)\n```\n\nYou can also apply middleware to the `Router` class, which allows you to apply middleware to a group of routes:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.gzip import GZipMiddleware\nfrom starlette.routing import Route, Router\n\n\nroutes = [\n    Route(\"/example\", endpoint=...),\n    Route(\"/another\", endpoint=...),\n]\n\nrouter = Router(routes=routes, middleware=[Middleware(GZipMiddleware)])\n```\n\n## Third party middleware\n\n#### [asgi-auth-github](https://github.com/simonw/asgi-auth-github)\n\nThis middleware adds authentication to any ASGI application, requiring users to sign in\nusing their GitHub account (via [OAuth](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)).\nAccess can be restricted to specific users or to members of specific GitHub organizations or teams.\n\n#### [asgi-csrf](https://github.com/simonw/asgi-csrf)\n\nMiddleware for protecting against CSRF attacks. This middleware implements the Double Submit Cookie pattern, where a cookie is set, then it is compared to a csrftoken hidden form field or an `x-csrftoken` HTTP header.\n\n#### [AuthlibMiddleware](https://github.com/aogier/starlette-authlib)\n\nA drop-in replacement for Starlette session middleware, using [authlib's jwt](https://docs.authlib.org/en/latest/jose/jwt.html)\nmodule.\n\n#### [BugsnagMiddleware](https://github.com/ashinabraham/starlette-bugsnag)\n\nA middleware class for logging exceptions to [Bugsnag](https://www.bugsnag.com/).\n\n#### [CSRFMiddleware](https://github.com/frankie567/starlette-csrf)\n\nMiddleware for protecting against CSRF attacks. This middleware implements the Double Submit Cookie pattern, where a cookie is set, then it is compared to an `x-csrftoken` HTTP header.\n\n#### [EarlyDataMiddleware](https://github.com/HarrySky/starlette-early-data)\n\nMiddleware and decorator for detecting and denying [TLSv1.3 early data](https://tools.ietf.org/html/rfc8470) requests.\n\n#### [PrometheusMiddleware](https://github.com/perdy/starlette-prometheus)\n\nA middleware class for capturing Prometheus metrics related to requests and responses, including in progress requests, timing...\n\n#### [ProxyHeadersMiddleware](https://github.com/encode/uvicorn/blob/main/uvicorn/middleware/proxy_headers.py)\n\nUvicorn includes a middleware class for determining the client IP address,\nwhen proxy servers are being used, based on the `X-Forwarded-Proto` and `X-Forwarded-For` headers. For more complex proxy configurations, you might want to adapt this middleware.\n\n#### [RateLimitMiddleware](https://github.com/abersheeran/asgi-ratelimit)\n\nA rate limit middleware. Regular expression matches url; flexible rules; highly customizable. Very easy to use.\n\n#### [RequestIdMiddleware](https://github.com/snok/asgi-correlation-id)\n\nA middleware class for reading/generating request IDs and attaching them to application logs.\n\n#### [RollbarMiddleware](https://docs.rollbar.com/docs/starlette)\n\nA middleware class for logging exceptions, errors, and log messages to [Rollbar](https://www.rollbar.com).\n\n#### [StarletteOpentracing](https://github.com/acidjunk/starlette-opentracing)\n\nA middleware class that emits tracing info to [OpenTracing.io](https://opentracing.io/) compatible tracers and\ncan be used to profile and monitor distributed applications.\n\n#### [SecureCookiesMiddleware](https://github.com/thearchitector/starlette-securecookies)\n\nCustomizable middleware for adding automatic cookie encryption and decryption to Starlette applications, with\nextra support for existing cookie-based middleware.\n\n#### [TimingMiddleware](https://github.com/steinnes/timing-asgi)\n\nA middleware class to emit timing information (cpu and wall time) for each request which\npasses through it.  Includes examples for how to emit these timings as statsd metrics.\n\n#### [WSGIMiddleware](https://github.com/abersheeran/a2wsgi)\n\nA middleware class in charge of converting a WSGI application into an ASGI one.\n"
  },
  {
    "path": "docs/overrides/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block extrahead %}\n{{ super() }}\n<script>\n  // Redirect starlette.io to starlette.dev\n  if (window.location.hostname === 'www.starlette.io' || window.location.hostname === 'starlette.io') {\n    const newUrl = window.location.href.replace(/^https?:\\/\\/(www\\.)?starlette\\.io/, 'https://starlette.dev');\n    window.location.replace(newUrl);\n  }\n</script>\n{% endblock %}\n\n{% block announce %}\n<div class=\"announce-wrapper\">\n  <div id=\"announce-msg\">\n    <a class=\"announce-link announce-item\" href=\"/sponsorship/\">\n      ❤️ Support Starlette via <strong>sponsors</strong>!\n    </a>\n    <a class=\"announce-link announce-item\" href=\"https://github.com/Kludex/starlette/discussions/3099\" target=\"_blank\">\n      📚 Do you like the new docs? <strong>Let us know!</strong>\n    </a>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "docs/overrides/partials/toc-item.html",
    "content": "<!-- Copied from https://github.com/squidfunk/mkdocs-material/issues/4827#issuecomment-1869812019 -->\n<li class=\"md-nav__item\"></li>\n<a href=\"{{ toc_item.url }}\" class=\"md-nav__link\">\n    <span class=\"md-ellipsis\">\n        {{ toc_item.title }}\n    </span>\n</a>\n\n<!-- Table of contents list -->\n{% if toc_item.children %}\n<nav class=\"md-nav\" aria-label=\"{{ toc_item.title | striptags }}\">\n    <ul class=\"md-nav__list\">\n        {% for toc_item in toc_item.children %}\n        {% if not page.meta.toc_depth or toc_item.level <= page.meta.toc_depth %} {% include \"partials/toc-item.html\" %}\n            {% endif %} {% endfor %} </ul>\n</nav>\n{% endif %}\n</li>\n"
  },
  {
    "path": "docs/release-notes.md",
    "content": "---\ntoc_depth: 2\n---\n\n## 1.0.0rc1 (February 23, 2026)\n\nWe're ready! I'm thrilled to announce the first release candidate for Starlette 1.0.\n\nStarlette was created in June 2018 by Tom Christie, and has been on ZeroVer for years. Today, it's downloaded\nalmost [10 million times a day](https://pypistats.org/packages/starlette), serves as the foundation for FastAPI,\nand has inspired many other frameworks. In the age of AI, Starlette continues to play an important role as a\ndependency of the Python MCP SDK.\n\nThis release focuses on removing deprecated features that were marked for removal in 1.0.0, along with some\nlast minute bug fixes. It's a release candidate, so we can gather feedback from the community before the final\n1.0.0 release soon.\n\nA huge thank you to all the contributors who have helped make Starlette what it is today.\nIn particular, I'd like to recognize:\n\n* [Kim Christie](https://github.com/lovelydinosaur) - The original creator of Starlette, Uvicorn, and MkDocs, and the\n  current maintainer of HTTPX. Kim's work helped lay the foundation for the modern async Python ecosystem.\n* [Adrian Garcia Badaracco](https://github.com/adriangb) - One of the smartest people I know, whom I have the pleasure of working with at Pydantic.\n* [Thomas Grainger](https://github.com/graingert) - My async teacher, always ready to help with questions.\n* [Alex Grönholm](https://github.com/agronholm) - Another async mentor, always prompt to help with questions.\n* [Florimond Manca](https://github.com/florimondmanca) - Always present in the early days of both Starlette and Uvicorn, and helped a lot in the ecosystem.\n* [Amin Alaee](https://github.com/aminalaee) - Contributed a lot with file-related PRs.\n* [Sebastián Ramírez](https://github.com/tiangolo) - Maintains FastAPI upstream, and always in contact to help with upstream issues.\n* [Alex Oleshkevich](https://github.com/alex-oleshkevich) - Helped a lot on templates and many discussions.\n* [abersheeran](https://github.com/abersheeran) - My go-to person when I need help on many subjects.\n\nI'd also like to thank my sponsors for their support. A special thanks to\n[@tiangolo](https://github.com/tiangolo), [@huggingface](https://github.com/huggingface),\nand [@elevenlabs](https://github.com/elevenlabs) for their generous sponsorship, and to all my other sponsors:\n\n[@roboflow](https://github.com/roboflow),\n[@ogabrielluiz](https://github.com/ogabrielluiz),\n[@SaboniAmine](https://github.com/SaboniAmine),\n[@russbiggs](https://github.com/russbiggs),\n[@BryceBeagle](https://github.com/BryceBeagle),\n[@chdsbd](https://github.com/chdsbd),\n[@TheR1D](https://github.com/TheR1D),\n[@ddanier](https://github.com/ddanier),\n[@larsyngvelundin](https://github.com/larsyngvelundin),\n[@jpizquierdo](https://github.com/jpizquierdo),\n[@alixlahuec](https://github.com/alixlahuec),\n[@nathanchapman](https://github.com/nathanchapman),\n[@devid8642](https://github.com/devid8642),\n[@comet-ml](https://github.com/comet-ml),\n[@Evil0ctal](https://github.com/Evil0ctal),\n[@msehnout](https://github.com/msehnout),\nand [@codingjoe](https://github.com/codingjoe).\n\n#### Removed\n\n* Remove `on_startup` and `on_shutdown` parameters from `Starlette` and `Router`.\n  Use the `lifespan` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `on_event()` decorator from `Starlette` and `Router`.\n  Use the `lifespan` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `add_event_handler()` method from `Starlette` and `Router`.\n  Use the `lifespan` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `startup()` and `shutdown()` methods from `Router`\n  [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `@app.route()` decorator from `Starlette` and `Router`.\n  Use `Route` in the `routes` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `@app.websocket_route()` decorator from `Starlette` and `Router`.\n  Use `WebSocketRoute` in the `routes` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `@app.exception_handler()` decorator from `Starlette`.\n  Use `exception_handlers` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `@app.middleware()` decorator from `Starlette`.\n  Use `middleware` parameter instead [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `iscoroutinefunction_or_partial()` from `starlette.routing` [#3117](https://github.com/encode/starlette/pull/3117).\n* Remove `**env_options` parameter from `Jinja2Templates`.\n  Use a preconfigured `jinja2.Environment` via the `env` parameter instead [#3118](https://github.com/encode/starlette/pull/3118).\n* Remove deprecated `TemplateResponse(name, context)` signature from `Jinja2Templates`.\n  Use `TemplateResponse(request, name, ...)` instead [#3118](https://github.com/encode/starlette/pull/3118).\n* Remove deprecated `method` parameter from `FileResponse` [#3147](https://github.com/encode/starlette/pull/3147).\n\n#### Added\n\n* Add state generic to `WebSocket` [#3132](https://github.com/encode/starlette/pull/3132).\n\n#### Fixed\n\n* Include `bytes` unit in `Content-Range` header on 416 responses.\n* Handle null bytes in `StaticFiles` path [#3139](https://github.com/encode/starlette/pull/3139).\n* Use sort-based merge for `Range` header parsing [#3138](https://github.com/encode/starlette/pull/3138).\n* Set `Content-Type` instead of `Content-Range` on multi-range responses [#3142](https://github.com/encode/starlette/pull/3142).\n* Use CRLF line endings in multipart byterange boundaries [#3143](https://github.com/encode/starlette/pull/3143).\n* Avoid mutating `FileResponse` headers on range requests [#3144](https://github.com/encode/starlette/pull/3144).\n* Return explicit origin in CORS response when credentials are allowed [#3137](https://github.com/encode/starlette/pull/3137).\n* Enable `autoescape` by default in `Jinja2Templates` [#3148](https://github.com/encode/starlette/pull/3148).\n\n#### Changed\n\n* `jinja2` must now be installed to import `Jinja2Templates`. Previously it would only\n  fail when instantiating the class [#3118](https://github.com/encode/starlette/pull/3118).\n\n## 0.52.1 (January 18, 2026)\n\n#### Fixed\n\n* Only use `typing_extensions` in older Python versions [#3109](https://github.com/Kludex/starlette/pull/3109).\n\n## 0.52.0 (January 18, 2026)\n\nIn this release, `State` can be accessed using dictionary-style syntax for improved type\nsafety ([#3036](https://github.com/Kludex/starlette/pull/3036)).\n\n```python\nfrom collections.abc import AsyncIterator\nfrom contextlib import asynccontextmanager\nfrom typing import TypedDict\n\nimport httpx\n\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\n\n\nclass State(TypedDict):\n    http_client: httpx.AsyncClient\n\n\n@asynccontextmanager\nasync def lifespan(app: Starlette) -> AsyncIterator[State]:\n    async with httpx.AsyncClient() as client:\n        yield {\"http_client\": client}\n\n\nasync def homepage(request: Request[State]):\n    client = request.state[\"http_client\"]\n    # If you run the below line with mypy or pyright, it will reveal the correct type.\n    reveal_type(client)  # Revealed type is 'httpx.AsyncClient'\n```\n\nSee [Accessing State](lifespan.md#accessing-state) for more details.\n\n## 0.51.0 (January 10, 2026)\n\n#### Added\n\n* Add `allow_private_network` in `CORSMiddleware` [#3065](https://github.com/Kludex/starlette/pull/3065).\n\n#### Changed\n\n* Increase warning stacklevel on `DeprecationWarning` for wsgi module [#3082](https://github.com/Kludex/starlette/pull/3082).\n\n## 0.50.0 (November 1, 2025)\n\n#### Removed\n\n* Drop Python 3.9 support [#3061](https://github.com/Kludex/starlette/pull/3061).\n\n## 0.49.3 (November 1, 2025)\n\nThis is the last release that supports Python 3.9, which will be dropped in the next minor release.\n\n#### Fixed\n\n* Relax strictness on `Middleware` type [#3059](https://github.com/Kludex/starlette/pull/3059).\n\n## 0.49.2 (November 1, 2025)\n\n#### Fixed\n\n* Ignore `if-modified-since` header if `if-none-match` is present in `StaticFiles` [#3044](https://github.com/Kludex/starlette/pull/3044).\n\n## 0.49.1 (October 28, 2025)\n\nThis release fixes a security vulnerability in the parsing logic of the `Range` header in `FileResponse`.\n\nYou can view the full security advisory: [GHSA-7f5h-v6xp-fcq8](https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8)\n\n#### Fixed\n\n* Optimize the HTTP ranges parsing logic [4ea6e22b489ec388d6004cfbca52dd5b147127c5](https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5)\n\n## 0.49.0 (October 28, 2025)\n\n#### Added\n\n* Add `encoding` parameter to `Config` class [#2996](https://github.com/Kludex/starlette/pull/2996).\n* Support multiple cookie headers in `Request.cookies` [#3029](https://github.com/Kludex/starlette/pull/3029).\n* Use `Literal` type for `WebSocketEndpoint` encoding values [#3027](https://github.com/Kludex/starlette/pull/3027).\n\n#### Changed\n\n* Do not pollute exception context in `Middleware` when using `BaseHTTPMiddleware` [#2976](https://github.com/Kludex/starlette/pull/2976).\n\n## 0.48.0 (September 13, 2025)\n\n#### Added\n\n* Add official Python 3.14 support [#3013](https://github.com/Kludex/starlette/pull/3013).\n\n#### Changed\n\n* Implement [RFC9110](https://www.rfc-editor.org/rfc/rfc9110) http status names [#2939](https://github.com/Kludex/starlette/pull/2939).\n\n## 0.47.3 (August 24, 2025)\n\n#### Fixed\n\n* Use `asyncio.iscoroutinefunction` for Python 3.12 and older [#2984](https://github.com/Kludex/starlette/pull/2984).\n\n## 0.47.2 (July 20, 2025)\n\n#### Fixed\n\n* Make `UploadFile` check for future rollover [#2962](https://github.com/Kludex/starlette/pull/2962).\n\n## 0.47.1 (June 21, 2025)\n\n#### Fixed\n\n* Use `Self` in `TestClient.__enter__` [#2951](https://github.com/Kludex/starlette/pull/2951).\n* Allow async exception handlers to type-check [#2949](https://github.com/Kludex/starlette/pull/2949).\n\n## 0.47.0 (May 29, 2025)\n\n#### Added\n\n* Add support for ASGI `pathsend` extension [#2671](https://github.com/Kludex/starlette/pull/2671).\n* Add `partitioned` attribute to `Response.set_cookie` [#2501](https://github.com/Kludex/starlette/pull/2501).\n\n#### Changed\n\n* Change `methods` parameter type from `list[str]` to `Collection[str]`\n  [#2903](https://github.com/Kludex/starlette/pull/2903).\n* Replace `import typing` by `from typing import ...` in the whole codebase\n  [#2867](https://github.com/Kludex/starlette/pull/2867).\n\n#### Fixed\n\n* Mark `ExceptionMiddleware.http_exception` as async to prevent thread creation\n  [#2922](https://github.com/Kludex/starlette/pull/2922).\n\n## 0.46.2 (April 13, 2025)\n\n#### Fixed\n\n* Prevents reraising of exception from BaseHttpMiddleware [#2911](https://github.com/Kludex/starlette/pull/2911).\n* Use correct index on backwards compatible logic in `TemplateResponse` [#2909](https://github.com/Kludex/starlette/pull/2909).\n\n## 0.46.1 (March 8, 2025)\n\n#### Fixed\n\n* Allow relative directory path when `follow_symlinks=True` [#2896](https://github.com/Kludex/starlette/pull/2896).\n\n## 0.46.0 (February 22, 2025)\n\n#### Added\n\n* `GZipMiddleware`: Make sure `Vary` header is always added if a response can be compressed [#2865](https://github.com/Kludex/starlette/pull/2865).\n\n#### Fixed\n\n* Raise exception from background task on BaseHTTPMiddleware [#2812](https://github.com/Kludex/starlette/pull/2812).\n* `GZipMiddleware`: Don't compress on server sent events [#2871](https://github.com/Kludex/starlette/pull/2871).\n\n#### Changed\n\n* `MultiPartParser`: Rename `max_file_size` to `spool_max_size` [#2780](https://github.com/Kludex/starlette/pull/2780).\n\n#### Deprecated\n\n* Add deprecated warning to `TestClient(timeout=...)` [#2840](https://github.com/Kludex/starlette/pull/2840).\n\n## 0.45.3 (January 24, 2025)\n\n#### Fixed\n\n* Turn directory into string on `lookup_path` on commonpath comparison [#2851](https://github.com/Kludex/starlette/pull/2851).\n\n## 0.45.2 (January 4, 2025)\n\n#### Fixed\n\n* Make `create_memory_object_stream` compatible with old anyio versions once again, and bump anyio minimum version to 3.6.2 [#2833](https://github.com/Kludex/starlette/pull/2833).\n\n## 0.45.1 (December 30, 2024)\n\n#### Fixed\n\n* Close `MemoryObjectReceiveStream` left unclosed upon exception in `BaseHTTPMiddleware` children [#2813](https://github.com/Kludex/starlette/pull/2813).\n* Collect errors more reliably from the WebSocket logic on the `TestClient` [#2814](https://github.com/Kludex/starlette/pull/2814).\n\n#### Refactor\n\n* Use a pair of memory object streams instead of two queues on the `TestClient` [#2829](https://github.com/Kludex/starlette/pull/2829).\n\n## 0.45.0 (December 29, 2024)\n\n#### Removed\n\n* Drop Python 3.8 support [#2823](https://github.com/Kludex/starlette/pull/2823).\n* Remove `ExceptionMiddleware` import proxy from `starlette.exceptions` module [#2826](https://github.com/Kludex/starlette/pull/2826).\n* Remove deprecated `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` [#2827](https://github.com/Kludex/starlette/pull/2827).\n\n## 0.44.0 (December 28, 2024)\n\n#### Added\n\n* Add `client` parameter to `TestClient` [#2810](https://github.com/Kludex/starlette/pull/2810).\n* Add `max_part_size` parameter to `Request.form()` [#2815](https://github.com/Kludex/starlette/pull/2815).\n\n## 0.43.0 (December 25, 2024)\n\n#### Removed\n\n* Remove deprecated `allow_redirects` argument from `TestClient` [#2808](https://github.com/Kludex/starlette/pull/2808).\n\n#### Added\n\n* Make UUID path parameter conversion more flexible [#2806](https://github.com/Kludex/starlette/pull/2806).\n\n## 0.42.0 (December 14, 2024)\n\n#### Added\n\n* Raise `ClientDisconnect` on `StreamingResponse` [#2732](https://github.com/Kludex/starlette/pull/2732).\n\n#### Fixed\n\n* Use ETag from headers when parsing If-Range in FileResponse [#2761](https://github.com/Kludex/starlette/pull/2761).\n* Follow directory symlinks in `StaticFiles` when `follow_symlinks=True` [#2711](https://github.com/Kludex/starlette/pull/2711).\n* Bump minimum `python-multipart` version to `0.0.18` [0ba8395](https://github.com/Kludex/starlette/commit/0ba83959e609bbd460966f092287df1bbd564cc6).\n* Bump minimum `httpx` version to `0.27.0` [#2773](https://github.com/Kludex/starlette/pull/2773).\n\n## 0.41.3 (November 18, 2024)\n\n#### Fixed\n\n* Exclude the query parameters from the `scope[raw_path]` on the `TestClient` [#2716](https://github.com/Kludex/starlette/pull/2716).\n* Replace `dict` by `Mapping` on `HTTPException.headers` [#2749](https://github.com/Kludex/starlette/pull/2749).\n* Correct middleware argument passing and improve factory pattern [#2752](https://github.com/Kludex/starlette/pull/2752).\n\n## 0.41.2 (October 27, 2024)\n\n#### Fixed\n\n* Revert bump on `python-multipart` on `starlette[full]` extras [#2737](https://github.com/Kludex/starlette/pull/2737).\n\n## 0.41.1 (October 24, 2024)\n\n#### Fixed\n\n* Bump minimum `python-multipart` version to `0.0.13` [#2734](https://github.com/Kludex/starlette/pull/2734).\n* Change `python-multipart` import to `python_multipart` [#2733](https://github.com/Kludex/starlette/pull/2733).\n\n## 0.41.0 (October 15, 2024)\n\n#### Added\n\n- Allow to raise `HTTPException` before `websocket.accept()` [#2725](https://github.com/Kludex/starlette/pull/2725).\n\n## 0.40.0 (October 15, 2024)\n\nThis release fixes a Denial of service (DoS) via `multipart/form-data` requests.\n\nYou can view the full security advisory:\n[GHSA-f96h-pmfr-66vw](https://github.com/Kludex/starlette/security/advisories/GHSA-f96h-pmfr-66vw)\n\n#### Fixed\n\n- Add `max_part_size` to `MultiPartParser` to limit the size of parts in `multipart/form-data`\n  requests [fd038f3](https://github.com/Kludex/starlette/commit/fd038f3070c302bff17ef7d173dbb0b007617733).\n\n## 0.39.2 (September 29, 2024)\n\n#### Fixed\n\n- Allow use of `request.url_for` when only \"app\" scope is available [#2672](https://github.com/Kludex/starlette/pull/2672).\n- Fix internal type hints to support `python-multipart==0.0.12` [#2708](https://github.com/Kludex/starlette/pull/2708).\n\n## 0.39.1 (September 25, 2024)\n\n#### Fixed\n\n- Avoid regex re-compilation in `responses.py` and `schemas.py` [#2700](https://github.com/Kludex/starlette/pull/2700).\n- Improve performance of `get_route_path` by removing regular expression usage\n  [#2701](https://github.com/Kludex/starlette/pull/2701).\n- Consider `FileResponse.chunk_size` when handling multiple ranges [#2703](https://github.com/Kludex/starlette/pull/2703).\n- Use `token_hex` for generating multipart boundary strings [#2702](https://github.com/Kludex/starlette/pull/2702).\n\n## 0.39.0 (September 23, 2024)\n\n#### Added\n\n* Add support for [HTTP Range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests) to\n  `FileResponse` [#2697](https://github.com/Kludex/starlette/pull/2697).\n\n## 0.38.6 (September 22, 2024)\n\n#### Fixed\n\n* Close unclosed `MemoryObjectReceiveStream` in `TestClient` [#2693](https://github.com/Kludex/starlette/pull/2693).\n\n## 0.38.5 (September 7, 2024)\n\n#### Fixed\n\n* Schedule `BackgroundTasks` from within `BaseHTTPMiddleware` [#2688](https://github.com/Kludex/starlette/pull/2688).\n  This behavior was removed in 0.38.3, and is now restored.\n\n## 0.38.4 (September 1, 2024)\n\n#### Fixed\n\n* Ensure accurate `root_path` removal in `get_route_path` function [#2600](https://github.com/Kludex/starlette/pull/2600).\n\n## 0.38.3 (September 1, 2024)\n\n#### Added\n\n* Support for Python 3.13 [#2662](https://github.com/Kludex/starlette/pull/2662).\n\n#### Fixed\n\n* Don't poll for disconnects in `BaseHTTPMiddleware` via `StreamingResponse` [#2620](https://github.com/Kludex/starlette/pull/2620).\n\n## 0.38.2 (July 27, 2024)\n\n#### Fixed\n\n* Not assume all routines have `__name__` on `routing.get_name()` [#2648](https://github.com/Kludex/starlette/pull/2648).\n\n## 0.38.1 (July 23, 2024)\n\n#### Removed\n\n* Revert \"Add support for ASGI pathsend extension\" [#2649](https://github.com/Kludex/starlette/pull/2649).\n\n## 0.38.0 (July 20, 2024)\n\n#### Added\n\n* Allow use of `memoryview` in `StreamingResponse` and `Response` [#2576](https://github.com/Kludex/starlette/pull/2576)\n  and [#2577](https://github.com/Kludex/starlette/pull/2577).\n* Send 404 instead of 500 when filename requested is too long on `StaticFiles` [#2583](https://github.com/Kludex/starlette/pull/2583).\n\n#### Changed\n\n* Fail fast on invalid `Jinja2Template` instantiation parameters [#2568](https://github.com/Kludex/starlette/pull/2568).\n* Check endpoint handler is async only once [#2536](https://github.com/Kludex/starlette/pull/2536).\n\n#### Fixed\n\n* Add proper synchronization to `WebSocketTestSession` [#2597](https://github.com/Kludex/starlette/pull/2597).\n\n## 0.37.2 (March 5, 2024)\n\n#### Added\n\n* Add `bytes` to `_RequestData` type [#2510](https://github.com/Kludex/starlette/pull/2510).\n\n#### Fixed\n\n* Revert \"Turn `scope[\"client\"]` to `None` on `TestClient` (#2377)\" [#2525](https://github.com/Kludex/starlette/pull/2525).\n* Remove deprecated `app` argument passed to `httpx.Client` on the `TestClient` [#2526](https://github.com/Kludex/starlette/pull/2526).\n\n## 0.37.1 (February 9, 2024)\n\n#### Fixed\n\n* Warn instead of raise for missing env file on `Config` [#2485](https://github.com/Kludex/starlette/pull/2485).\n\n## 0.37.0 (February 5, 2024)\n\n#### Added\n\n* Support the WebSocket Denial Response ASGI extension [#2041](https://github.com/Kludex/starlette/pull/2041).\n\n## 0.36.3 (February 4, 2024)\n\n#### Fixed\n\n* Create `anyio.Event` on async context [#2459](https://github.com/Kludex/starlette/pull/2459).\n\n## 0.36.2 (February 3, 2024)\n\n#### Fixed\n\n* Upgrade `python-multipart` to `0.0.7` [13e5c26](http://github.com/Kludex/starlette/commit/13e5c26a27f4903924624736abd6131b2da80cc5).\n* Avoid duplicate charset on `Content-Type` [#2443](https://github.com/Kludex/starlette/2443).\n\n## 0.36.1 (January 23, 2024)\n\n#### Fixed\n\n* Check if \"extensions\" in scope before checking the extension [#2438](http://github.com/Kludex/starlette/pull/2438).\n\n## 0.36.0 (January 22, 2024)\n\n#### Added\n\n* Add support for ASGI `pathsend` extension [#2435](http://github.com/Kludex/starlette/pull/2435).\n* Cancel `WebSocketTestSession` on close [#2427](http://github.com/Kludex/starlette/pull/2427).\n* Raise `WebSocketDisconnect` when `WebSocket.send()` excepts `IOError` [#2425](http://github.com/Kludex/starlette/pull/2425).\n* Raise `FileNotFoundError` when the `env_file` parameter on `Config` is not valid [#2422](http://github.com/Kludex/starlette/pull/2422).\n\n## 0.35.1 (January 11, 2024)\n\n#### Fixed\n\n* Stop using the deprecated \"method\" parameter in `FileResponse` inside of `StaticFiles` [#2406](https://github.com/Kludex/starlette/pull/2406).\n* Make `typing-extensions` optional again [#2409](https://github.com/Kludex/starlette/pull/2409).\n\n## 0.35.0 (January 11, 2024)\n\n#### Added\n\n* Add `*args` to `Middleware` and improve its type hints [#2381](https://github.com/Kludex/starlette/pull/2381).\n\n#### Fixed\n\n* Use `Iterable` instead `Iterator` on `iterate_in_threadpool` [#2362](https://github.com/Kludex/starlette/pull/2362).\n\n#### Changes\n\n* Handle `root_path` to keep compatibility with mounted ASGI applications and WSGI [#2400](https://github.com/Kludex/starlette/pull/2400).\n* Turn `scope[\"client\"]` to `None` on `TestClient` [#2377](https://github.com/Kludex/starlette/pull/2377).\n\n## 0.34.0 (December 16, 2023)\n\n### Added\n\n* Use `ParamSpec` for `run_in_threadpool` [#2375](https://github.com/Kludex/starlette/pull/2375).\n* Add `UploadFile.__repr__` [#2360](https://github.com/Kludex/starlette/pull/2360).\n\n### Fixed\n\n* Merge URLs properly on `TestClient` [#2376](https://github.com/Kludex/starlette/pull/2376).\n* Take weak ETags in consideration on `StaticFiles` [#2334](https://github.com/Kludex/starlette/pull/2334).\n\n### Deprecated\n\n* Deprecate `FileResponse(method=...)` parameter [#2366](https://github.com/Kludex/starlette/pull/2366).\n\n## 0.33.0 (December 1, 2023)\n\n### Added\n\n* Add `middleware` per `Route`/`WebSocketRoute` [#2349](https://github.com/Kludex/starlette/pull/2349).\n* Add `middleware` per `Router` [#2351](https://github.com/Kludex/starlette/pull/2351).\n\n### Fixed\n\n* Do not overwrite `\"path\"` and `\"root_path\"` scope keys [#2352](https://github.com/Kludex/starlette/pull/2352).\n* Set `ensure_ascii=False` on `json.dumps()` for `WebSocket.send_json()` [#2341](https://github.com/Kludex/starlette/pull/2341).\n\n## 0.32.0.post1 (November 5, 2023)\n\n### Fixed\n\n* Revert mkdocs-material from 9.1.17 to 9.4.7 [#2326](https://github.com/Kludex/starlette/pull/2326).\n\n## 0.32.0 (November 4, 2023)\n\n### Added\n\n* Send `reason` on `WebSocketDisconnect` [#2309](https://github.com/Kludex/starlette/pull/2309).\n* Add `domain` parameter to `SessionMiddleware` [#2280](https://github.com/Kludex/starlette/pull/2280).\n\n### Changed\n\n* Inherit from `HTMLResponse` instead of `Response` on `_TemplateResponse` [#2274](https://github.com/Kludex/starlette/pull/2274).\n* Restore the `Response.render` type annotation to its pre-0.31.0 state [#2264](https://github.com/Kludex/starlette/pull/2264).\n\n## 0.31.1 (August 26, 2023)\n\n### Fixed\n\n* Fix import error when `exceptiongroup` isn't available [#2231](https://github.com/Kludex/starlette/pull/2231).\n* Set `url_for` global for custom Jinja environments [#2230](https://github.com/Kludex/starlette/pull/2230).\n\n## 0.31.0 (July 24, 2023)\n\n### Added\n\n* Officially support Python 3.12 [#2214](https://github.com/Kludex/starlette/pull/2214).\n* Support AnyIO 4.0 [#2211](https://github.com/Kludex/starlette/pull/2211).\n* Strictly type annotate Starlette (strict mode on mypy) [#2180](https://github.com/Kludex/starlette/pull/2180).\n\n### Fixed\n\n* Don't group duplicated headers on a single string when using the `TestClient` [#2219](https://github.com/Kludex/starlette/pull/2219).\n\n## 0.30.0 (July 13, 2023)\n\n### Removed\n\n* Drop Python 3.7 support [#2178](https://github.com/Kludex/starlette/pull/2178).\n\n## 0.29.0 (July 13, 2023)\n\n### Added\n\n* Add `follow_redirects` parameter to `TestClient` [#2207](https://github.com/Kludex/starlette/pull/2207).\n* Add `__str__` to `HTTPException` and `WebSocketException` [#2181](https://github.com/Kludex/starlette/pull/2181).\n* Warn users when using `lifespan` together with `on_startup`/`on_shutdown` [#2193](https://github.com/Kludex/starlette/pull/2193).\n* Collect routes from `Host` to generate the OpenAPI schema [#2183](https://github.com/Kludex/starlette/pull/2183).\n* Add `request` argument to `TemplateResponse` [#2191](https://github.com/Kludex/starlette/pull/2191).\n\n### Fixed\n\n* Stop `body_stream` in case `more_body=False` on `BaseHTTPMiddleware` [#2194](https://github.com/Kludex/starlette/pull/2194).\n\n## 0.28.0 (June 7, 2023)\n\n### Changed\n* Reuse `Request`'s body buffer for call_next in `BaseHTTPMiddleware` [#1692](https://github.com/Kludex/starlette/pull/1692).\n* Move exception handling logic to `Route` [#2026](https://github.com/Kludex/starlette/pull/2026).\n\n### Added\n* Add `env` parameter to `Jinja2Templates`, and deprecate `**env_options` [#2159](https://github.com/Kludex/starlette/pull/2159).\n* Add clear error message when `httpx` is not installed [#2177](https://github.com/Kludex/starlette/pull/2177).\n\n### Fixed\n* Allow \"name\" argument on `templates url_for()` [#2127](https://github.com/Kludex/starlette/pull/2127).\n\n## 0.27.0 (May 16, 2023)\n\nThis release fixes a path traversal vulnerability in `StaticFiles`. You can view the full security advisory:\nhttps://github.com/Kludex/starlette/security/advisories/GHSA-v5gw-mw7f-84px\n\n### Added\n* Minify JSON websocket data via `send_json` https://github.com/Kludex/starlette/pull/2128\n\n### Fixed\n* Replace `commonprefix` by `commonpath` on `StaticFiles` [1797de4](https://github.com/Kludex/starlette/commit/1797de464124b090f10cf570441e8292936d63e3).\n* Convert ImportErrors into ModuleNotFoundError [#2135](https://github.com/Kludex/starlette/pull/2135).\n* Correct the RuntimeError message content in websockets [#2141](https://github.com/Kludex/starlette/pull/2141).\n\n## 0.26.1 (March 13, 2023)\n\n### Fixed\n* Fix typing of Lifespan to allow subclasses of Starlette [#2077](https://github.com/Kludex/starlette/pull/2077).\n\n## 0.26.0.post1 (March 9, 2023)\n\n### Fixed\n* Replace reference from Events to Lifespan on the mkdocs.yml [#2072](https://github.com/Kludex/starlette/pull/2072).\n\n## 0.26.0 (March 9, 2023)\n\n### Added\n* Support [lifespan state](lifespan.md) [#2060](https://github.com/Kludex/starlette/pull/2060),\n  [#2065](https://github.com/Kludex/starlette/pull/2065) and [#2064](https://github.com/Kludex/starlette/pull/2064).\n\n### Changed\n* Change `url_for` signature to return a `URL` instance [#1385](https://github.com/Kludex/starlette/pull/1385).\n\n### Fixed\n* Allow \"name\" argument on `url_for()` and `url_path_for()` [#2050](https://github.com/Kludex/starlette/pull/2050).\n\n### Deprecated\n* Deprecate `on_startup` and `on_shutdown` events [#2070](https://github.com/Kludex/starlette/pull/2070).\n\n## 0.25.0 (February 14, 2023)\n\n### Fix\n* Limit the number of fields and files when parsing `multipart/form-data` on the `MultipartParser` [8c74c2c](https://github.com/Kludex/starlette/commit/8c74c2c8dba7030154f8af18e016136bea1938fa) and [#2036](https://github.com/Kludex/starlette/pull/2036).\n\n## 0.24.0 (February 6, 2023)\n\n### Added\n* Allow `StaticFiles` to follow symlinks [#1683](https://github.com/Kludex/starlette/pull/1683).\n* Allow `Request.form()` as a context manager [#1903](https://github.com/Kludex/starlette/pull/1903).\n* Add `size` attribute to `UploadFile` [#1405](https://github.com/Kludex/starlette/pull/1405).\n* Add `env_prefix` argument to `Config` [#1990](https://github.com/Kludex/starlette/pull/1990).\n* Add template context processors [#1904](https://github.com/Kludex/starlette/pull/1904).\n* Support `str` and `datetime` on `expires` parameter on the `Response.set_cookie` method [#1908](https://github.com/Kludex/starlette/pull/1908).\n\n### Changed\n* Lazily build the middleware stack [#2017](https://github.com/Kludex/starlette/pull/2017).\n* Make the `file` argument required on `UploadFile` [#1413](https://github.com/Kludex/starlette/pull/1413).\n* Use debug extension instead of custom response template extension [#1991](https://github.com/Kludex/starlette/pull/1991).\n\n### Fixed\n* Fix url parsing of ipv6 urls on `URL.replace` [#1965](https://github.com/Kludex/starlette/pull/1965).\n\n## 0.23.1 (December 9, 2022)\n\n### Fixed\n* Only stop receiving stream on `body_stream` if body is empty on the `BaseHTTPMiddleware` [#1940](https://github.com/Kludex/starlette/pull/1940).\n\n## 0.23.0 (December 5, 2022)\n\n### Added\n* Add `headers` parameter to the `TestClient` [#1966](https://github.com/Kludex/starlette/pull/1966).\n\n### Deprecated\n* Deprecate `Starlette` and `Router` decorators [#1897](https://github.com/Kludex/starlette/pull/1897).\n\n### Fixed\n* Fix bug on `FloatConvertor` regex [#1973](https://github.com/Kludex/starlette/pull/1973).\n\n## 0.22.0 (November 17, 2022)\n\n### Changed\n* Bypass `GZipMiddleware` when response includes `Content-Encoding` [#1901](https://github.com/Kludex/starlette/pull/1901).\n\n### Fixed\n* Remove unneeded `unquote()` from query parameters on the `TestClient` [#1953](https://github.com/Kludex/starlette/pull/1953).\n* Make sure `MutableHeaders._list` is actually a `list` [#1917](https://github.com/Kludex/starlette/pull/1917).\n* Import compatibility with the next version of `AnyIO` [#1936](https://github.com/Kludex/starlette/pull/1936).\n\n## 0.21.0 (September 26, 2022)\n\nThis release replaces the underlying HTTP client used on the `TestClient` (`requests` :arrow_right: `httpx`), and as those clients [differ _a bit_ on their API](https://www.python-httpx.org/compatibility/), your test suite will likely break. To make the migration smoother, you can use the [`bump-testclient`](https://github.com/Kludex/bump-testclient) tool.\n\n### Changed\n* Replace `requests` with `httpx` in `TestClient` [#1376](https://github.com/Kludex/starlette/pull/1376).\n\n### Added\n* Add `WebSocketException` and support for WebSocket exception handlers [#1263](https://github.com/Kludex/starlette/pull/1263).\n* Add `middleware` parameter to `Mount` class [#1649](https://github.com/Kludex/starlette/pull/1649).\n* Officially support Python 3.11 [#1863](https://github.com/Kludex/starlette/pull/1863).\n* Implement `__repr__` for route classes [#1864](https://github.com/Kludex/starlette/pull/1864).\n\n### Fixed\n* Fix bug on which `BackgroundTasks` were cancelled when using `BaseHTTPMiddleware` and client disconnected [#1715](https://github.com/Kludex/starlette/pull/1715).\n\n## 0.20.4 (June 28, 2022)\n\n### Fixed\n* Remove converter from path when generating OpenAPI schema [#1648](https://github.com/Kludex/starlette/pull/1648).\n\n## 0.20.3 (June 10, 2022)\n\n### Fixed\n* Revert \"Allow `StaticFiles` to follow symlinks\" [#1681](https://github.com/Kludex/starlette/pull/1681).\n\n## 0.20.2 (June 7, 2022)\n\n### Fixed\n* Fix regression on route paths with colons [#1675](https://github.com/Kludex/starlette/pull/1675).\n* Allow `StaticFiles` to follow symlinks [#1337](https://github.com/Kludex/starlette/pull/1377).\n\n## 0.20.1 (May 28, 2022)\n\n### Fixed\n* Improve detection of async callables [#1444](https://github.com/Kludex/starlette/pull/1444).\n* Send 400 (Bad Request) when `boundary` is missing [#1617](https://github.com/Kludex/starlette/pull/1617).\n* Send 400 (Bad Request) when missing \"name\" field on `Content-Disposition` header [#1643](https://github.com/Kludex/starlette/pull/1643).\n* Do not send empty data to `StreamingResponse` on `BaseHTTPMiddleware` [#1609](https://github.com/Kludex/starlette/pull/1609).\n* Add `__bool__` dunder for `Secret` [#1625](https://github.com/Kludex/starlette/pull/1625).\n\n## 0.20.0 (May 3, 2022)\n\n### Removed\n* Drop Python 3.6 support [#1357](https://github.com/Kludex/starlette/pull/1357) and [#1616](https://github.com/Kludex/starlette/pull/1616).\n\n\n## 0.19.1 (April 22, 2022)\n\n### Fixed\n* Fix inference of `Route.name` when created from methods [#1553](https://github.com/Kludex/starlette/pull/1553).\n* Avoid `TypeError` on `websocket.disconnect` when code is `None` [#1574](https://github.com/Kludex/starlette/pull/1574).\n\n### Deprecated\n* Deprecate `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` in favor of `WS_1005_NO_STATUS_RCVD` and `WS_1006_ABNORMAL_CLOSURE`, as the previous constants didn't match the [WebSockets specs](https://www.iana.org/assignments/websocket/websocket.xhtml) [#1580](https://github.com/Kludex/starlette/pull/1580).\n\n\n## 0.19.0 (March 9, 2022)\n\n### Added\n* Error handler will always run, even if the error happens on a background task [#761](https://github.com/Kludex/starlette/pull/761).\n* Add `headers` parameter to `HTTPException` [#1435](https://github.com/Kludex/starlette/pull/1435).\n* Internal responses with `405` status code insert an `Allow` header, as described by [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.5) [#1436](https://github.com/Kludex/starlette/pull/1436).\n* The `content` argument in `JSONResponse` is now required [#1431](https://github.com/Kludex/starlette/pull/1431).\n* Add custom URL convertor register [#1437](https://github.com/Kludex/starlette/pull/1437).\n* Add content disposition type parameter to `FileResponse` [#1266](https://github.com/Kludex/starlette/pull/1266).\n* Add next query param with original request URL in requires decorator [#920](https://github.com/Kludex/starlette/pull/920).\n* Add `raw_path` to `TestClient` scope [#1445](https://github.com/Kludex/starlette/pull/1445).\n* Add union operators to `MutableHeaders` [#1240](https://github.com/Kludex/starlette/pull/1240).\n* Display missing route details on debug page [#1363](https://github.com/Kludex/starlette/pull/1363).\n* Change `anyio` required version range to `>=3.4.0,<5.0` [#1421](https://github.com/Kludex/starlette/pull/1421) and [#1460](https://github.com/Kludex/starlette/pull/1460).\n* Add `typing-extensions>=3.10` requirement - used only on lower versions than Python 3.10 [#1475](https://github.com/Kludex/starlette/pull/1475).\n\n### Fixed\n* Prevent `BaseHTTPMiddleware` from hiding errors of `StreamingResponse` and mounted applications [#1459](https://github.com/Kludex/starlette/pull/1459).\n* `SessionMiddleware` uses an explicit `path=...`, instead of defaulting to the ASGI 'root_path' [#1512](https://github.com/Kludex/starlette/pull/1512).\n* `Request.client` is now compliant with the ASGI specifications [#1462](https://github.com/Kludex/starlette/pull/1462).\n* Raise `KeyError` at early stage for missing boundary [#1349](https://github.com/Kludex/starlette/pull/1349).\n\n### Deprecated\n* Deprecate WSGIMiddleware in favor of a2wsgi [#1504](https://github.com/Kludex/starlette/pull/1504).\n* Deprecate `run_until_first_complete` [#1443](https://github.com/Kludex/starlette/pull/1443).\n\n\n## 0.18.0 (January 23, 2022)\n\n### Added\n* Change default chunk size from 4Kb to 64Kb on `FileResponse` [#1345](https://github.com/Kludex/starlette/pull/1345).\n* Add support for `functools.partial` in `WebSocketRoute` [#1356](https://github.com/Kludex/starlette/pull/1356).\n* Add `StaticFiles` packages with directory [#1350](https://github.com/Kludex/starlette/pull/1350).\n* Allow environment options in `Jinja2Templates` [#1401](https://github.com/Kludex/starlette/pull/1401).\n* Allow HEAD method on `HttpEndpoint` [#1346](https://github.com/Kludex/starlette/pull/1346).\n* Accept additional headers on `websocket.accept` message [#1361](https://github.com/Kludex/starlette/pull/1361) and [#1422](https://github.com/Kludex/starlette/pull/1422).\n* Add `reason` to `WebSocket` close ASGI event [#1417](https://github.com/Kludex/starlette/pull/1417).\n* Add headers attribute to `UploadFile` [#1382](https://github.com/Kludex/starlette/pull/1382).\n* Don't omit `Content-Length` header for `Content-Length: 0` cases [#1395](https://github.com/Kludex/starlette/pull/1395).\n* Don't set headers for responses with 1xx, 204 and 304 status code [#1397](https://github.com/Kludex/starlette/pull/1397).\n* `SessionMiddleware.max_age` now accepts `None`, so cookie can last as long as the browser session [#1387](https://github.com/Kludex/starlette/pull/1387).\n\n### Fixed\n* Tweak `hashlib.md5()` function on `FileResponse`s ETag generation. The parameter [`usedforsecurity`](https://bugs.python.org/issue9216) flag is set to `False`, if the flag is available on the system. This fixes an error raised on systems with [FIPS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/FIPS_Mode_-_an_explanation) enabled [#1366](https://github.com/Kludex/starlette/pull/1366) and [#1410](https://github.com/Kludex/starlette/pull/1410).\n* Fix `path_params` type on `url_path_for()` method i.e. turn `str` into `Any` [#1341](https://github.com/Kludex/starlette/pull/1341).\n* `Host` now ignores `port` on routing [#1322](https://github.com/Kludex/starlette/pull/1322).\n\n## 0.17.1 (November 17, 2021)\n\n### Fixed\n* Fix `IndexError` in authentication `requires` when wrapped function arguments are distributed between `*args` and `**kwargs` [#1335](https://github.com/Kludex/starlette/pull/1335).\n\n## 0.17.0 (November 4, 2021)\n\n### Added\n* `Response.delete_cookie` now accepts the same parameters as `Response.set_cookie` [#1228](https://github.com/Kludex/starlette/pull/1228).\n* Update the `Jinja2Templates` constructor to allow `PathLike` [#1292](https://github.com/Kludex/starlette/pull/1292).\n\n### Fixed\n* Fix BadSignature exception handling in SessionMiddleware [#1264](https://github.com/Kludex/starlette/pull/1264).\n* Change `HTTPConnection.__getitem__` return type from `str` to `typing.Any` [#1118](https://github.com/Kludex/starlette/pull/1118).\n* Change `ImmutableMultiDict.getlist` return type from `typing.List[str]` to `typing.List[typing.Any]` [#1235](https://github.com/Kludex/starlette/pull/1235).\n* Handle `OSError` exceptions on `StaticFiles` [#1220](https://github.com/Kludex/starlette/pull/1220).\n* Fix `StaticFiles` 404.html in HTML mode [#1314](https://github.com/Kludex/starlette/pull/1314).\n* Prevent anyio.ExceptionGroup in error views under a BaseHTTPMiddleware [#1262](https://github.com/Kludex/starlette/pull/1262).\n\n### Removed\n* Remove GraphQL support [#1198](https://github.com/Kludex/starlette/pull/1198).\n\n## 0.16.0 (July 19, 2021)\n\n### Added\n * Added [Encode](https://github.com/sponsors/encode) funding option\n   [#1219](https://github.com/Kludex/starlette/pull/1219)\n\n### Fixed\n * `starlette.websockets.WebSocket` instances are now hashable and compare by identity\n    [#1039](https://github.com/Kludex/starlette/pull/1039)\n * A number of fixes related to running task groups in lifespan\n   [#1213](https://github.com/Kludex/starlette/pull/1213),\n   [#1227](https://github.com/Kludex/starlette/pull/1227)\n\n### Deprecated/removed\n * The method `starlette.templates.Jinja2Templates.get_env` was removed\n   [#1218](https://github.com/Kludex/starlette/pull/1218)\n * The ClassVar `starlette.testclient.TestClient.async_backend` was removed,\n   the backend is now configured using constructor kwargs\n   [#1211](https://github.com/Kludex/starlette/pull/1211)\n * Passing an Async Generator Function or a Generator Function to `starlette.routing.Router(lifespan=)` is deprecated. You should wrap your lifespan in `@contextlib.asynccontextmanager`.\n   [#1227](https://github.com/Kludex/starlette/pull/1227)\n   [#1110](https://github.com/Kludex/starlette/pull/1110)\n\n## 0.15.0 (June 23, 2021)\n\nThis release includes major changes to the low-level asynchronous parts of Starlette. As a result,\n**Starlette now depends on [AnyIO](https://anyio.readthedocs.io/en/stable/)** and some minor API\nchanges have occurred. Another significant change with this release is the\n**deprecation of built-in GraphQL support**.\n\n### Added\n* Starlette now supports [Trio](https://trio.readthedocs.io/en/stable/) as an async runtime via\n  AnyIO - [#1157](https://github.com/Kludex/starlette/pull/1157).\n* `TestClient.websocket_connect()` now must be used as a context manager.\n* Initial support for Python 3.10 - [#1201](https://github.com/Kludex/starlette/pull/1201).\n* The compression level used in `GZipMiddleware` is now adjustable -\n  [#1128](https://github.com/Kludex/starlette/pull/1128).\n\n### Fixed\n* Several fixes to `CORSMiddleware`. See [#1111](https://github.com/Kludex/starlette/pull/1111),\n  [#1112](https://github.com/Kludex/starlette/pull/1112),\n  [#1113](https://github.com/Kludex/starlette/pull/1113),\n  [#1199](https://github.com/Kludex/starlette/pull/1199).\n* Improved exception messages in the case of duplicated path parameter names -\n  [#1177](https://github.com/Kludex/starlette/pull/1177).\n* `RedirectResponse` now uses `quote` instead of `quote_plus` encoding for the `Location` header\n  to better match the behaviour in other frameworks such as Django -\n  [#1164](https://github.com/Kludex/starlette/pull/1164).\n* Exception causes are now preserved in more cases -\n  [#1158](https://github.com/Kludex/starlette/pull/1158).\n* Session cookies now use the ASGI root path in the case of mounted applications -\n  [#1147](https://github.com/Kludex/starlette/pull/1147).\n* Fixed a cache invalidation bug when static files were deleted in certain circumstances -\n  [#1023](https://github.com/Kludex/starlette/pull/1023).\n* Improved memory usage of `BaseHTTPMiddleware` when handling large responses -\n  [#1012](https://github.com/Kludex/starlette/issues/1012) fixed via #1157\n\n### Deprecated/removed\n\n* Built-in GraphQL support via the `GraphQLApp` class has been deprecated and will be removed in a\n  future release. Please see [#619](https://github.com/Kludex/starlette/issues/619). GraphQL is not\n  supported on Python 3.10.\n* The `executor` parameter to `GraphQLApp` was removed. Use `executor_class` instead.\n* The `workers` parameter to `WSGIMiddleware` was removed. This hasn't had any effect since\n  Starlette v0.6.3.\n\n## 0.14.2 (February 2, 2021)\n\n### Fixed\n\n* Fixed `ServerErrorMiddleware` compatibility with Python 3.9.1/3.8.7 when debug mode is enabled -\n  [#1132](https://github.com/Kludex/starlette/pull/1132).\n* Fixed unclosed socket `ResourceWarning`s when using the `TestClient` with WebSocket endpoints -\n  #1132.\n* Improved detection of `async` endpoints wrapped in `functools.partial` on Python 3.8+ -\n  [#1106](https://github.com/Kludex/starlette/pull/1106).\n\n\n## 0.14.1 (November 9th, 2020)\n\n### Removed\n\n* `UJSONResponse` was removed (this change was intended to be included in 0.14.0). Please see the\n  [documentation](https://starlette.dev/responses/#custom-json-serialization) for how to\n  implement responses using custom JSON serialization -\n  [#1074](https://github.com/Kludex/starlette/pull/1047).\n\n## 0.14.0 (November 8th, 2020)\n\n### Added\n\n* Starlette now officially supports Python3.9.\n* In `StreamingResponse`, allow custom async iterator such as objects from classes implementing `__aiter__`.\n* Allow usage of `functools.partial` async handlers in Python versions 3.6 and 3.7.\n* Add 418 I'm A Teapot status code.\n\n### Changed\n\n* Create tasks from handler coroutines before sending them to `asyncio.wait`.\n* Use `format_exception` instead of `format_tb` in `ServerErrorMiddleware`'s `debug` responses.\n* Be more lenient with handler arguments when using the `requires` decorator.\n\n## 0.13.8\n\n* Revert `Queue(maxsize=1)` fix for `BaseHTTPMiddleware` middleware classes and streaming responses.\n\n* The `StaticFiles` constructor now allows `pathlib.Path` in addition to strings for its `directory` argument.\n\n## 0.13.7\n\n* Fix high memory usage when using `BaseHTTPMiddleware` middleware classes and streaming responses.\n\n## 0.13.6\n\n* Fix 404 errors with `StaticFiles`.\n\n## 0.13.5\n\n* Add support for `Starlette(lifespan=...)` functions.\n* More robust path-traversal check in StaticFiles app.\n* Fix WSGI PATH_INFO encoding.\n* RedirectResponse now accepts optional background parameter\n* Allow path routes to contain regex meta characters\n* Treat ASGI HTTP 'body' as an optional key.\n* Don't use thread pooling for writing to in-memory upload files.\n\n## 0.13.0\n\n* Switch to promoting application configuration on init style everywhere.\n  This means dropping the decorator style in favour of declarative routing\n  tables and middleware definitions.\n\n## 0.12.12\n\n* Fix `request.url_for()` for the Mount-within-a-Mount case.\n\n## 0.12.11\n\n* Fix `request.url_for()` when an ASGI `root_path` is being used.\n\n## 0.12.1\n\n* Add `URL.include_query_params(**kwargs)`\n* Add `URL.replace_query_params(**kwargs)`\n* Add `URL.remove_query_params(param_names)`\n* `request.state` properly persisting across middleware.\n* Added `request.scope` interface.\n\n## 0.12.0\n\n* Switch to ASGI 3.0.\n* Fixes to CORS middleware.\n* Add `StaticFiles(html=True)` support.\n* Fix path quoting in redirect responses.\n\n## 0.11.1\n\n* Add `request.state` interface, for storing arbitrary additional information.\n* Support disabling GraphiQL with `GraphQLApp(..., graphiql=False)`.\n\n## 0.11.0\n\n* `DatabaseMiddleware` is now dropped in favour of `databases`\n* Templates are no longer configured on the application instance. Use `templates = Jinja2Templates(directory=...)` and `return templates.TemplateResponse('index.html', {\"request\": request})`\n* Schema generation is no longer attached to the application instance. Use `schemas = SchemaGenerator(...)` and `return schemas.OpenAPIResponse(request=request)`\n* `LifespanMiddleware` is dropped in favor of router-based lifespan handling.\n* Application instances now accept a `routes` argument, `Starlette(routes=[...])`\n* Schema generation now includes mounted routes.\n\n## 0.10.6\n\n* Add `Lifespan` routing component.\n\n## 0.10.5\n\n* Ensure `templating` does not strictly require `jinja2` to be installed.\n\n## 0.10.4\n\n* Templates are now configured independently from the application instance. `templates = Jinja2Templates(directory=...)`. Existing API remains in place, but is no longer documented,\nand will be deprecated in due course. See the template documentation for more details.\n\n## 0.10.3\n\n* Move to independent `databases` package instead of `DatabaseMiddleware`. Existing API\nremains in place, but is no longer documented, and will be deprecated in due course.\n\n## 0.10.2\n\n* Don't drop explicit port numbers on redirects from `HTTPSRedirectMiddleware`.\n\n## 0.10.1\n\n* Add MySQL database support.\n* Add host-based routing.\n\n## 0.10.0\n\n* WebSockets now default to sending/receiving JSON over text data frames. Use `.send_json(data, mode=\"binary\")` and `.receive_json(mode=\"binary\")` for binary framing.\n* `GraphQLApp` now takes an `executor_class` argument, which should be used in preference to the existing `executor` argument. Resolves an issue with async executors being instantiated before the event loop was setup. The `executor` argument is expected to be deprecated in the next median or major release.\n* Authentication and the `@requires` decorator now support WebSocket endpoints.\n* `MultiDict` and `ImmutableMultiDict` classes are available in `uvicorn.datastructures`.\n* `QueryParams` is now instantiated with standard dict-style `*args, **kwargs` arguments.\n\n## 0.9.11\n\n* Session cookies now include browser 'expires', in addition to the existing signed expiry.\n* `request.form()` now returns a multi-dict interface.\n* The query parameter multi-dict implementation now mirrors `dict` more correctly for the\nbehavior of `.keys()`, `.values()`, and `.items()` when multiple same-key items occur.\n* Use `urlsplit` throughout in favor of `urlparse`.\n\n## 0.9.10\n\n* Support `@requires(...)` on class methods.\n* Apply URL escaping to form data.\n* Support `HEAD` requests automatically.\n* Add `await request.is_disconnected()`.\n* Pass operationName to GraphQL executor.\n\n## 0.9.9\n\n* Add `TemplateResponse`.\n* Add `CommaSeparatedStrings` datatype.\n* Add `BackgroundTasks` for multiple tasks.\n* Common subclass for `Request` and `WebSocket`, to eg. share `session` functionality.\n* Expose remote address with `request.client`.\n\n## 0.9.8\n\n* Add `request.database.executemany`.\n\n## 0.9.7\n\n* Ensure that `AuthenticationMiddleware` handles lifespan messages correctly.\n\n## 0.9.6\n\n* Add `AuthenticationMiddleware`, and `@requires()` decorator.\n\n## 0.9.5\n\n* Support either `str` or `Secret` for `SessionMiddleware(secret_key=...)`.\n\n## 0.9.4\n\n* Add `config.environ`.\n* Add `datastructures.Secret`.\n* Add `datastructures.DatabaseURL`.\n\n## 0.9.3\n\n* Add `config.Config(\".env\")`\n\n## 0.9.2\n\n* Add optional database support.\n* Add `request` to GraphQL context.\n* Hide any password component in `URL.__repr__`.\n\n## 0.9.1\n\n* Handle startup/shutdown errors properly.\n\n## 0.9.0\n\n* `TestClient` can now be used as a context manager, instead of `LifespanContext`.\n* Lifespan is now handled as middleware. Startup and Shutdown events are\nvisible throughout the middleware stack.\n\n## 0.8.8\n\n* Better support for third-party API schema generators.\n\n## 0.8.7\n\n* Support chunked requests with TestClient.\n* Cleanup asyncio tasks properly with WSGIMiddleware.\n* Support using TestClient within endpoints, for service mocking.\n\n## 0.8.6\n\n* Session cookies are now set on the root path.\n\n## 0.8.5\n\n* Support URL convertors.\n* Support HTTP 304 cache responses from `StaticFiles`.\n* Resolve character escaping issue with form data.\n\n## 0.8.4\n\n* Default to empty body on responses.\n\n## 0.8.3\n\n* Add 'name' argument to `@app.route()`.\n* Use 'Host' header for URL reconstruction.\n\n## 0.8.2\n\n### StaticFiles\n\n* StaticFiles no longer reads the file for responses to `HEAD` requests.\n\n## 0.8.1\n\n### Templating\n\n* Add a default templating configuration with Jinja2.\n\nAllows the following:\n\n```python\napp = Starlette(template_directory=\"templates\")\n\n@app.route('/')\nasync def homepage(request):\n    # `url_for` is available inside the template.\n    template = app.get_template('index.html')\n    content = template.render(request=request)\n    return HTMLResponse(content)\n```\n\n## 0.8.0\n\n### Exceptions\n\n* Add support for `@app.exception_handler(404)`.\n* Ensure handled exceptions are not seen as errors by the middleware stack.\n\n### SessionMiddleware\n\n* Add `max_age`, and use timestamp-signed cookies. Defaults to two weeks.\n\n### Cookies\n\n* Ensure cookies are strictly HTTP correct.\n\n### StaticFiles\n\n* Check directory exists on instantiation.\n\n## 0.7.4\n\n### Concurrency\n\n* Add `starlette.concurrency.run_in_threadpool`. Now handles `contextvar` support.\n\n## 0.7.3\n\n### Routing\n\n* Add `name=` support to `app.mount()`. This allows eg: `app.mount('/static', StaticFiles(directory='static'), name='static')`.\n\n## 0.7.2\n\n### Middleware\n\n* Add support for `@app.middleware(\"http\")` decorator.\n\n### Routing\n\n* Add \"endpoint\" to ASGI scope.\n\n## 0.7.1\n\n### Debug tracebacks\n\n* Improve debug traceback information & styling.\n\n### URL routing\n\n* Support mounted URL lookups with \"path=\", eg. `url_for('static', path=...)`.\n* Support nested URL lookups, eg. `url_for('admin:user', username=...)`.\n* Add redirect slashes support.\n* Add www redirect support.\n\n### Background tasks\n\n* Add background task support to `FileResponse` and `StreamingResponse`.\n\n## 0.7.0\n\n### API Schema support\n\n* Add `app.schema_generator = SchemaGenerator(...)`.\n* Add `app.schema` property.\n* Add `OpenAPIResponse(...)`.\n\n### GraphQL routing\n\n* Drop `app.add_graphql_route(\"/\", ...)` in favor of more consistent `app.add_route(\"/\", GraphQLApp(...))`.\n\n## 0.6.3\n\n### Routing API\n\n* Support routing to methods.\n* Ensure `url_path_for` works with Mount('/{some_path_params}').\n* Fix Router(default=) argument.\n* Support repeated paths, like: `@app.route(\"/\", methods=[\"GET\"])`, `@app.route(\"/\", methods=[\"POST\"])`\n* Use the default ThreadPoolExecutor for all sync endpoints.\n\n## 0.6.2\n\n### SessionMiddleware\n\nAdded support for `request.session`, with `SessionMiddleware`.\n\n## 0.6.1\n\n### BaseHTTPMiddleware\n\nAdded support for `BaseHTTPMiddleware`, which provides a standard\nrequest/response interface over a regular ASGI middleware.\n\nThis means you can write ASGI middleware while still working at\na request/response level, rather than handling ASGI messages directly.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\n\nclass CustomMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request, call_next):\n        response = await call_next(request)\n        response.headers['Custom-Header'] = 'Example'\n        return response\n\n\napp = Starlette()\napp.add_middleware(CustomMiddleware)\n```\n\n## 0.6.0\n\n### request.path_params\n\nThe biggest change in 0.6 is that endpoint signatures are no longer:\n\n```python\nasync def func(request: Request, **kwargs) -> Response\n```\n\nInstead we just use:\n\n```python\nasync def func(request: Request) -> Response\n```\n\nThe path parameters are available on the request as `request.path_params`.\n\nThis is different to most Python webframeworks, but I think it actually ends up\nbeing much more nicely consistent all the way through.\n\n### request.url_for()\n\nRequest and WebSocketSession now support URL reversing with `request.url_for(name, **path_params)`.\nThis method returns a fully qualified `URL` instance.\nThe URL instance is a string-like object.\n\n### app.url_path_for()\n\nApplications now support URL path reversing with `app.url_path_for(name, **path_params)`.\nThis method returns a `URL` instance with the path and scheme set.\nThe URL instance is a string-like object, and will return only the path if coerced to a string.\n\n### app.routes\n\nApplications now support a `.routes` parameter, which returns a list of `[Route|WebSocketRoute|Mount]`.\n\n### Route, WebSocketRoute, Mount\n\nThe low level components to `Router` now match the `@app.route()`, `@app.websocket_route()`, and `app.mount()` signatures.\n"
  },
  {
    "path": "docs/requests.md",
    "content": "\nStarlette includes a `Request` class that gives you a nicer interface onto\nthe incoming request, rather than accessing the ASGI scope and receive channel directly.\n\n### Request\n\nSignature: `Request(scope, receive=None)`\n\n```python\nfrom starlette.requests import Request\nfrom starlette.responses import Response\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    request = Request(scope, receive)\n    content = '%s %s' % (request.method, request.url.path)\n    response = Response(content, media_type='text/plain')\n    await response(scope, receive, send)\n```\n\nRequests present a mapping interface, so you can use them in the same\nway as a `scope`.\n\nFor instance: `request['path']` will return the ASGI path.\n\nIf you don't need to access the request body you can instantiate a request\nwithout providing an argument to `receive`.\n\n#### Method\n\nThe request method is accessed as `request.method`.\n\n#### URL\n\nThe request URL is accessed as `request.url`.\n\nThe property is a string-like object that exposes all the\ncomponents that can be parsed out of the URL.\n\nFor example: `request.url.path`, `request.url.port`, `request.url.scheme`.\n\n#### Headers\n\nHeaders are exposed as an immutable, case-insensitive, multi-dict.\n\nFor example: `request.headers['content-type']`\n\n#### Query Parameters\n\nQuery parameters are exposed as an immutable multi-dict.\n\nFor example: `request.query_params['search']`\n\n#### Path Parameters\n\nRouter path parameters are exposed as a dictionary interface.\n\nFor example: `request.path_params['username']`\n\n#### Client Address\n\nThe client's remote address is exposed as a named two-tuple `request.client` (or `None`).\n\nThe hostname or IP address: `request.client.host`\n\nThe port number from which the client is connecting: `request.client.port`\n\n#### Cookies\n\nCookies are exposed as a regular dictionary interface.\n\nFor example: `request.cookies.get('mycookie')`\n\nCookies are ignored in case of an invalid cookie. (RFC2109)\n\n#### Body\n\nThere are a few different interfaces for returning the body of the request:\n\nThe request body as bytes: `await request.body()`\n\nThe request body, parsed as form data or multipart: `async with request.form() as form:`\n\nThe request body, parsed as JSON: `await request.json()`\n\nYou can also access the request body as a stream, using the `async for` syntax:\n\n```python\nfrom starlette.requests import Request\nfrom starlette.responses import Response\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    request = Request(scope, receive)\n    body = b''\n    async for chunk in request.stream():\n        body += chunk\n    response = Response(body, media_type='text/plain')\n    await response(scope, receive, send)\n```\n\nIf you access `.stream()` then the byte chunks are provided without storing\nthe entire body to memory. Any subsequent calls to `.body()`, `.form()`, or `.json()`\nwill raise an error.\n\nIn some cases such as long-polling, or streaming responses you might need to\ndetermine if the client has dropped the connection. You can determine this\nstate with `disconnected = await request.is_disconnected()`.\n\n#### Request Files\n\nRequest files are normally sent as multipart form data (`multipart/form-data`).\n\nSignature: `request.form(max_files=1000, max_fields=1000, max_part_size=1024*1024)`\n\nYou can configure the number of maximum fields or files with the parameters `max_files` and `max_fields`; and part size using `max_part_size`:\n\n```python\nasync with request.form(max_files=1000, max_fields=1000, max_part_size=1024*1024):\n    ...\n```\n\n!!! info\n    These limits are for security reasons, allowing an unlimited number of fields or files could lead to a denial of service attack by consuming a lot of CPU and memory parsing too many empty fields.\n\nWhen you call `async with request.form() as form` you receive a `starlette.datastructures.FormData` which is an immutable\nmultidict, containing both file uploads and text input. File upload items are represented as instances of `starlette.datastructures.UploadFile`.\n\n`UploadFile` has the following attributes:\n\n* `filename`: An `str` with the original file name that was uploaded or `None` if its not available (e.g. `myimage.jpg`).\n* `content_type`: An `str` with the content type (MIME type / media type) or `None` if it's not available (e.g. `image/jpeg`).\n* `file`: A <a href=\"https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile\" target=\"_blank\">`SpooledTemporaryFile`</a> (a <a href=\"https://docs.python.org/3/glossary.html#term-file-like-object\" target=\"_blank\">file-like</a> object). This is the actual Python file that you can pass directly to other functions or libraries that expect a \"file-like\" object.\n* `headers`: A `Headers` object. Often this will only be the `Content-Type` header, but if additional headers were included in the multipart field they will be included here. Note that these headers have no relationship with the headers in `Request.headers`.\n* `size`: An `int` with uploaded file's size in bytes. This value is calculated from request's contents, making it better choice to find uploaded file's size than `Content-Length` header. `None` if not set.\n\n`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).\n\n* `async write(data)`: Writes `data` (`bytes`) to the file.\n* `async read(size)`: Reads `size` (`int`) bytes of the file.\n* `async seek(offset)`: Goes to the byte position `offset` (`int`) in the file.\n    * E.g., `await myfile.seek(0)` would go to the start of the file.\n* `async close()`: Closes the file.\n\nAs all these methods are `async` methods, you need to \"await\" them.\n\nFor example, you can get the file name and the contents with:\n\n```python\nasync with request.form() as form:\n    filename = form[\"upload_file\"].filename\n    contents = await form[\"upload_file\"].read()\n```\n\n!!! info\n    As settled in [RFC-7578: 4.2](https://www.ietf.org/rfc/rfc7578.txt), form-data content part that contains file\n    assumed to have `name` and `filename` fields in `Content-Disposition` header: `Content-Disposition: form-data;\n    name=\"user\"; filename=\"somefile\"`. Though `filename` field is optional according to RFC-7578, it helps\n    Starlette to differentiate which data should be treated as file. If `filename` field was supplied, `UploadFile`\n    object will be created to access underlying file, otherwise form-data part will be parsed and available as a raw\n    string.\n\n#### Application\n\nThe originating Starlette application can be accessed via `request.app`.\n\n#### Other state\n\nIf you want to store additional information on the request you can do so\nusing `request.state`.\n\nFor example:\n\n`request.state.time_started = time.time()`\n"
  },
  {
    "path": "docs/responses.md",
    "content": "\nStarlette includes a few response classes that handle sending back the\nappropriate ASGI messages on the `send` channel.\n\n### Response\n\nSignature: `Response(content, status_code=200, headers=None, media_type=None)`\n\n* `content` - A string or bytestring.\n* `status_code` - An integer HTTP status code.\n* `headers` - A dictionary of strings.\n* `media_type` - A string giving the media type. eg. \"text/html\"\n\nStarlette will automatically include a Content-Length header. It will also\ninclude a Content-Type header, based on the media_type and appending a charset\nfor text types, unless a charset has already been specified in the `media_type`.\n\nOnce you've instantiated a response, you can send it by calling it as an\nASGI application instance.\n\n```python\nfrom starlette.responses import Response\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = Response('Hello, world!', media_type='text/plain')\n    await response(scope, receive, send)\n```\n#### Set Cookie\n\nStarlette provides a `set_cookie` method to allow you to set cookies on the response object.\n\nSignature: `Response.set_cookie(key, value, max_age=None, expires=None, path=\"/\", domain=None, secure=False, httponly=False, samesite=\"lax\", partitioned=False)`\n\n* `key` - A string that will be the cookie's key.\n* `value` - A string that will be the cookie's value.\n* `max_age` - An integer that defines the lifetime of the cookie in seconds. A negative integer or a value of `0` will discard the cookie immediately. `Optional`\n* `expires` - Either an integer that defines the number of seconds until the cookie expires, or a datetime. `Optional`\n* `path` - A string that specifies the subset of routes to which the cookie will apply. `Optional`\n* `domain` - A string that specifies the domain for which the cookie is valid. `Optional`\n* `secure` - A bool indicating that the cookie will only be sent to the server if request is made using SSL and the HTTPS protocol. `Optional`\n* `httponly` - A bool indicating that the cookie cannot be accessed via JavaScript through `Document.cookie` property, the `XMLHttpRequest` or `Request` APIs. `Optional`\n* `samesite` - A string that specifies the samesite strategy for the cookie. Valid values are `'lax'`, `'strict'` and `'none'`. Defaults to `'lax'`. `Optional`\n* `partitioned` - A bool that indicates to user agents that these cross-site cookies should only be available in the same top-level context that the cookie was first set in. Only available for Python 3.14+, otherwise an error will be raised. `Optional`\n\n#### Delete Cookie\n\nConversely, Starlette also provides a `delete_cookie` method to manually expire a set cookie.\n\nSignature: `Response.delete_cookie(key, path='/', domain=None)`\n\n\n### HTMLResponse\n\nTakes some text or bytes and returns an HTML response.\n\n```python\nfrom starlette.responses import HTMLResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = HTMLResponse('<html><body><h1>Hello, world!</h1></body></html>')\n    await response(scope, receive, send)\n```\n\n### PlainTextResponse\n\nTakes some text or bytes and returns a plain text response.\n\n```python\nfrom starlette.responses import PlainTextResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = PlainTextResponse('Hello, world!')\n    await response(scope, receive, send)\n```\n\n### JSONResponse\n\nTakes some data and returns an `application/json` encoded response.\n\n```python\nfrom starlette.responses import JSONResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = JSONResponse({'hello': 'world'})\n    await response(scope, receive, send)\n```\n\n#### Custom JSON serialization\n\nIf you need fine-grained control over JSON serialization, you can subclass\n`JSONResponse` and override the `render` method.\n\nFor example, if you wanted to use a third-party JSON library such as\n[orjson](https://pypi.org/project/orjson/):\n\n```python\nfrom typing import Any\n\nimport orjson\nfrom starlette.responses import JSONResponse\n\n\nclass OrjsonResponse(JSONResponse):\n    def render(self, content: Any) -> bytes:\n        return orjson.dumps(content)\n```\n\nIn general you *probably* want to stick with `JSONResponse` by default unless\nyou are micro-optimising a particular endpoint or need to serialize non-standard\nobject types.\n\n### RedirectResponse\n\nReturns an HTTP redirect. Uses a 307 status code by default.\n\n```python\nfrom starlette.responses import PlainTextResponse, RedirectResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    if scope['path'] != '/':\n        response = RedirectResponse(url='/')\n    else:\n        response = PlainTextResponse('Hello, world!')\n    await response(scope, receive, send)\n```\n\n### StreamingResponse\n\nTakes an async generator or a normal generator/iterator and streams the response body.\n\n```python\nfrom starlette.responses import StreamingResponse\nimport asyncio\n\n\nasync def slow_numbers(minimum, maximum):\n    yield '<html><body><ul>'\n    for number in range(minimum, maximum + 1):\n        yield '<li>%d</li>' % number\n        await asyncio.sleep(0.5)\n    yield '</ul></body></html>'\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    generator = slow_numbers(1, 10)\n    response = StreamingResponse(generator, media_type='text/html')\n    await response(scope, receive, send)\n```\n\nHave in mind that <a href=\"https://docs.python.org/3/glossary.html#term-file-like-object\" target=\"_blank\">file-like</a> objects (like those created by `open()`) are normal iterators. So, you can return them directly in a `StreamingResponse`.\n\n### FileResponse\n\nAsynchronously streams a file as the response.\n\nTakes a different set of arguments to instantiate than the other response types:\n\n* `path` - The filepath to the file to stream.\n* `headers` - Any custom headers to include, as a dictionary.\n* `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type.\n* `filename` - If set, this will be included in the response `Content-Disposition`.\n* `content_disposition_type` - will be included in the response `Content-Disposition`. Can be set to \"attachment\" (default) or \"inline\".\n\nFile responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers.\n\n```python\nfrom starlette.responses import FileResponse\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = FileResponse('statics/favicon.ico')\n    await response(scope, receive, send)\n```\n\nFile responses also supports [HTTP range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests).\n\nThe `Accept-Ranges: bytes` header will be included in the response if the file exists. For now, only the `bytes`\nrange unit is supported.\n\nIf the request includes a `Range` header, and the file exists, the response will be a `206 Partial Content` response\nwith the requested range of bytes. If the range is invalid, the response will be a `416 Range Not Satisfiable` response.\n\n## Third party responses\n\n#### [EventSourceResponse](https://github.com/sysid/sse-starlette)\n\nA response class that implements [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html). It enables event streaming from the server to the client without the complexity of websockets.\n"
  },
  {
    "path": "docs/routing.md",
    "content": "## HTTP Routing\n\nStarlette has a simple but capable request routing system. A routing table\nis defined as a list of routes, and passed when instantiating the application.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\n\n\nasync def homepage(request):\n    return PlainTextResponse(\"Homepage\")\n\nasync def about(request):\n    return PlainTextResponse(\"About\")\n\n\nroutes = [\n    Route(\"/\", endpoint=homepage),\n    Route(\"/about\", endpoint=about),\n]\n\napp = Starlette(routes=routes)\n```\n\nThe `endpoint` argument can be one of:\n\n* A regular function or async function, which accepts a single `request`\nargument and which should return a response.\n* A class that implements the ASGI interface, such as Starlette's [HTTPEndpoint](endpoints.md#httpendpoint).\n\n## Path Parameters\n\nPaths can use URI templating style to capture path components.\n\n```python\nRoute('/users/{username}', user)\n```\nBy default this will capture characters up to the end of the path or the next `/`.\n\nYou can use convertors to modify what is captured. The available convertors are:\n\n* `str` returns a string, and is the default.\n* `int` returns a Python integer.\n* `float` returns a Python float.\n* `uuid` return a Python `uuid.UUID` instance.\n* `path` returns the rest of the path, including any additional `/` characters.\n\nConvertors are used by prefixing them with a colon, like so:\n\n```python\nRoute('/users/{user_id:int}', user)\nRoute('/floating-point/{number:float}', floating_point)\nRoute('/uploaded/{rest_of_path:path}', uploaded)\n```\n\nIf you need a different converter that is not defined, you can create your own.\nSee below an example on how to create a `datetime` convertor, and how to register it:\n\n```python\nfrom datetime import datetime\n\nfrom starlette.convertors import Convertor, register_url_convertor\n\n\nclass DateTimeConvertor(Convertor):\n    regex = \"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?\"\n\n    def convert(self, value: str) -> datetime:\n        return datetime.strptime(value, \"%Y-%m-%dT%H:%M:%S\")\n\n    def to_string(self, value: datetime) -> str:\n        return value.strftime(\"%Y-%m-%dT%H:%M:%S\")\n\nregister_url_convertor(\"datetime\", DateTimeConvertor())\n```\n\nAfter registering it, you'll be able to use it as:\n\n```python\nRoute('/history/{date:datetime}', history)\n```\n\nPath parameters are made available in the request, as the `request.path_params`\ndictionary.\n\n```python\nasync def user(request):\n    user_id = request.path_params['user_id']\n    ...\n```\n\n## Handling HTTP methods\n\nRoutes can also specify which HTTP methods are handled by an endpoint:\n\n```python\nRoute('/users/{user_id:int}', user, methods=[\"GET\", \"POST\"])\n```\n\nBy default function endpoints will only accept `GET` requests, unless specified.\n\n## Submounting routes\n\nIn large applications you might find that you want to break out parts of the\nrouting table, based on a common path prefix.\n\n```python\nroutes = [\n    Route('/', homepage),\n    Mount('/users', routes=[\n        Route('/', users, methods=['GET', 'POST']),\n        Route('/{username}', user),\n    ])\n]\n```\n\nThis style allows you to define different subsets of the routing table in\ndifferent parts of your project.\n\n```python\nfrom myproject import users, auth\n\nroutes = [\n    Route('/', homepage),\n    Mount('/users', routes=users.routes),\n    Mount('/auth', routes=auth.routes),\n]\n```\n\nYou can also use mounting to include sub-applications within your Starlette\napplication. For example...\n\n```python\n# This is a standalone static files server:\napp = StaticFiles(directory=\"static\")\n\n# This is a static files server mounted within a Starlette application,\n# underneath the \"/static\" path.\nroutes = [\n    ...\n    Mount(\"/static\", app=StaticFiles(directory=\"static\"), name=\"static\")\n]\n\napp = Starlette(routes=routes)\n```\n\n## Reverse URL lookups\n\nYou'll often want to be able to generate the URL for a particular route,\nsuch as in cases where you need to return a redirect response.\n\n* Signature: `url_for(name, **path_params) -> URL`\n\n```python\nroutes = [\n    Route(\"/\", homepage, name=\"homepage\")\n]\n\n# We can use the following to return a URL...\nurl = request.url_for(\"homepage\")\n```\n\nURL lookups can include path parameters...\n\n```python\nroutes = [\n    Route(\"/users/{username}\", user, name=\"user_detail\")\n]\n\n# We can use the following to return a URL...\nurl = request.url_for(\"user_detail\", username=...)\n```\n\nIf a `Mount` includes a `name`, then submounts should use a `{prefix}:{name}`\nstyle for reverse URL lookups.\n\n```python\nroutes = [\n    Mount(\"/users\", name=\"users\", routes=[\n        Route(\"/\", user, name=\"user_list\"),\n        Route(\"/{username}\", user, name=\"user_detail\")\n    ])\n]\n\n# We can use the following to return URLs...\nurl = request.url_for(\"users:user_list\")\nurl = request.url_for(\"users:user_detail\", username=...)\n```\n\nMounted applications may include a `path=...` parameter.\n\n```python\nroutes = [\n    ...\n    Mount(\"/static\", app=StaticFiles(directory=\"static\"), name=\"static\")\n]\n\n# We can use the following to return URLs...\nurl = request.url_for(\"static\", path=\"/css/base.css\")\n```\n\nFor cases where there is no `request` instance, you can make reverse lookups\nagainst the application, although these will only return the URL path.\n\n```python\nurl = app.url_path_for(\"user_detail\", username=...)\n```\n\n## Host-based routing\n\nIf you want to use different routes for the same path based on the `Host` header.\n\nNote that port is removed from the `Host` header when matching.\nFor example, `Host (host='example.org:3600', ...)` will be processed\neven if the `Host` header contains or does not contain a port other than `3600`\n(`example.org:5600`, `example.org`).\nTherefore, you can specify the port if you need it for use in `url_for`.\n\nThere are several ways to connect host-based routes to your application\n\n```python\nsite = Router()  # Use eg. `@site.route()` to configure this.\napi = Router()  # Use eg. `@api.route()` to configure this.\nnews = Router()  # Use eg. `@news.route()` to configure this.\n\nroutes = [\n    Host('api.example.org', api, name=\"site_api\")\n]\n\napp = Starlette(routes=routes)\n\napp.host('www.example.org', site, name=\"main_site\")\n\nnews_host = Host('news.example.org', news)\napp.router.routes.append(news_host)\n```\n\nURL lookups can include host parameters just like path parameters\n\n```python\nroutes = [\n    Host(\"{subdomain}.example.org\", name=\"sub\", app=Router(routes=[\n        Mount(\"/users\", name=\"users\", routes=[\n            Route(\"/\", user, name=\"user_list\"),\n            Route(\"/{username}\", user, name=\"user_detail\")\n        ])\n    ]))\n]\n...\nurl = request.url_for(\"sub:users:user_detail\", username=..., subdomain=...)\nurl = request.url_for(\"sub:users:user_list\", subdomain=...)\n```\n\n## Route priority\n\nIncoming paths are matched against each `Route` in order.\n\nIn cases where more that one route could match an incoming path, you should\ntake care to ensure that more specific routes are listed before general cases.\n\nFor example:\n\n```python\n# Don't do this: `/users/me` will never match incoming requests.\nroutes = [\n    Route('/users/{username}', user),\n    Route('/users/me', current_user),\n]\n\n# Do this: `/users/me` is tested first.\nroutes = [\n    Route('/users/me', current_user),\n    Route('/users/{username}', user),\n]\n```\n\n## Working with Router instances\n\nIf you're working at a low-level you might want to use a plain `Router`\ninstance, rather that creating a `Starlette` application. This gives you\na lightweight ASGI application that just provides the application routing,\nwithout wrapping it up in any middleware.\n\n```python\napp = Router(routes=[\n    Route('/', homepage),\n    Mount('/users', routes=[\n        Route('/', users, methods=['GET', 'POST']),\n        Route('/{username}', user),\n    ])\n])\n```\n\n## WebSocket Routing\n\nWhen working with WebSocket endpoints, you should use `WebSocketRoute`\ninstead of the usual `Route`.\n\nPath parameters, and reverse URL lookups for `WebSocketRoute` work the same\nas HTTP `Route`, which can be found in the HTTP [Route](#http-routing) section above.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import WebSocketRoute\n\n\nasync def websocket_index(websocket):\n    await websocket.accept()\n    await websocket.send_text(\"Hello, websocket!\")\n    await websocket.close()\n\n\nasync def websocket_user(websocket):\n    name = websocket.path_params[\"name\"]\n    await websocket.accept()\n    await websocket.send_text(f\"Hello, {name}\")\n    await websocket.close()\n\n\nroutes = [\n    WebSocketRoute(\"/\", endpoint=websocket_index),\n    WebSocketRoute(\"/{name}\", endpoint=websocket_user),\n]\n\napp = Starlette(routes=routes)\n```\n\nThe `endpoint` argument can be one of:\n\n* An async function, which accepts a single `websocket` argument.\n* A class that implements the ASGI interface, such as Starlette's [WebSocketEndpoint](endpoints.md#websocketendpoint).\n"
  },
  {
    "path": "docs/schemas.md",
    "content": "Starlette supports generating API schemas, such as the widely used [OpenAPI\nspecification][openapi]. (Formerly known as \"Swagger\".)\n\nSchema generation works by inspecting the routes on the application through\n`app.routes`, and using the docstrings or other attributes on the endpoints\nin order to determine a complete API schema.\n\nStarlette is not tied to any particular schema generation or validation tooling,\nbut includes a simple implementation that generates OpenAPI schemas based on\nthe docstrings.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route\nfrom starlette.schemas import SchemaGenerator\n\n\nschemas = SchemaGenerator(\n    {\"openapi\": \"3.0.0\", \"info\": {\"title\": \"Example API\", \"version\": \"1.0\"}}\n)\n\ndef list_users(request):\n    \"\"\"\n    responses:\n      200:\n        description: A list of users.\n        examples:\n          [{\"username\": \"tom\"}, {\"username\": \"lucy\"}]\n    \"\"\"\n    raise NotImplementedError()\n\n\ndef create_user(request):\n    \"\"\"\n    responses:\n      200:\n        description: A user.\n        examples:\n          {\"username\": \"tom\"}\n    \"\"\"\n    raise NotImplementedError()\n\n\ndef openapi_schema(request):\n    return schemas.OpenAPIResponse(request=request)\n\n\nroutes = [\n    Route(\"/users\", endpoint=list_users, methods=[\"GET\"]),\n    Route(\"/users\", endpoint=create_user, methods=[\"POST\"]),\n    Route(\"/schema\", endpoint=openapi_schema, include_in_schema=False)\n]\n\napp = Starlette(routes=routes)\n```\n\nWe can now access an OpenAPI schema at the \"/schema\" endpoint.\n\nYou can generate the API Schema directly with `.get_schema(routes)`:\n\n```python\nschema = schemas.get_schema(routes=app.routes)\nassert schema == {\n    \"openapi\": \"3.0.0\",\n    \"info\": {\"title\": \"Example API\", \"version\": \"1.0\"},\n    \"paths\": {\n        \"/users\": {\n            \"get\": {\n                \"responses\": {\n                    200: {\n                        \"description\": \"A list of users.\",\n                        \"examples\": [{\"username\": \"tom\"}, {\"username\": \"lucy\"}],\n                    }\n                }\n            },\n            \"post\": {\n                \"responses\": {\n                    200: {\"description\": \"A user.\", \"examples\": {\"username\": \"tom\"}}\n                }\n            },\n        },\n    },\n}\n```\n\nYou might also want to be able to print out the API schema, so that you can\nuse tooling such as generating API documentation.\n\n```python\nif __name__ == '__main__':\n    assert sys.argv[-1] in (\"run\", \"schema\"), \"Usage: example.py [run|schema]\"\n\n    if sys.argv[-1] == \"run\":\n        uvicorn.run(\"example:app\", host='0.0.0.0', port=8000)\n    elif sys.argv[-1] == \"schema\":\n        schema = schemas.get_schema(routes=app.routes)\n        print(yaml.dump(schema, default_flow_style=False))\n```\n\n### Third party packages\n\n#### [starlette-apispec][starlette-apispec]\n\nEasy APISpec integration for Starlette, which supports some object serialization libraries.\n\n[openapi]: https://github.com/OAI/OpenAPI-Specification\n[starlette-apispec]: https://github.com/Woile/starlette-apispec\n"
  },
  {
    "path": "docs/server-push.md",
    "content": "\nStarlette includes support for HTTP/2 and HTTP/3 server push, making it\npossible to push resources to the client to speed up page load times.\n\n### `Request.send_push_promise`\n\nUsed to initiate a server push for a resource. If server push is not available\nthis method does nothing.\n\nSignature: `send_push_promise(path)`\n\n* `path` - A string denoting the path of the resource.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import HTMLResponse\nfrom starlette.routing import Route, Mount\nfrom starlette.staticfiles import StaticFiles\n\n\nasync def homepage(request):\n    \"\"\"\n    Homepage which uses server push to deliver the stylesheet.\n    \"\"\"\n    await request.send_push_promise(\"/static/style.css\")\n    return HTMLResponse(\n        '<html><head><link rel=\"stylesheet\" href=\"/static/style.css\"/></head></html>'\n    )\n\nroutes = [\n    Route(\"/\", endpoint=homepage),\n    Mount(\"/static\", StaticFiles(directory=\"static\"), name=\"static\")\n]\n\napp = Starlette(routes=routes)\n```\n"
  },
  {
    "path": "docs/staticfiles.md",
    "content": "\nStarlette also includes a `StaticFiles` class for serving files in a given directory:\n\n### StaticFiles\n\nSignature: `StaticFiles(directory=None, packages=None, html=False, check_dir=True, follow_symlink=False)`\n\n* `directory` - A string or [os.PathLike][pathlike] denoting a directory path.\n* `packages` - A list of strings or list of tuples of strings of python packages.\n* `html` - Run in HTML mode. Automatically loads `index.html` for directories if such file exist.\n* `check_dir` - Ensure that the directory exists upon instantiation. Defaults to `True`.\n* `follow_symlink` - A boolean indicating if symbolic links for files and directories should be followed. Defaults to `False`.\n\nYou can combine this ASGI application with Starlette's routing to provide\ncomprehensive static file serving.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Mount\nfrom starlette.staticfiles import StaticFiles\n\n\nroutes = [\n    ...\n    Mount('/static', app=StaticFiles(directory='static'), name=\"static\"),\n]\n\napp = Starlette(routes=routes)\n```\n\nStatic files will respond with \"404 Not found\" or \"405 Method not allowed\"\nresponses for requests which do not match. In HTML mode if `404.html` file\nexists it will be shown as 404 response.\n\nThe `packages` option can be used to include \"static\" directories contained within\na python package. The Python \"bootstrap4\" package is an example of this.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Mount\nfrom starlette.staticfiles import StaticFiles\n\n\nroutes=[\n    ...\n    Mount('/static', app=StaticFiles(directory='static', packages=['bootstrap4']), name=\"static\"),\n]\n\napp = Starlette(routes=routes)\n```\n\nBy default `StaticFiles` will look for `statics` directory in each package,\nyou can change the default directory by specifying a tuple of strings.\n\n```python\nroutes=[\n    ...\n    Mount('/static', app=StaticFiles(packages=[('bootstrap4', 'static')]), name=\"static\"),\n]\n```\n\nYou may prefer to include static files directly inside the \"static\" directory\nrather than using Python packaging to include static files, but it can be useful\nfor bundling up reusable components.\n\n[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike\n"
  },
  {
    "path": "docs/templates.md",
    "content": "Starlette is not _strictly_ coupled to any particular templating engine, but\nJinja2 provides an excellent choice.\n\n??? abstract \"API Reference\"\n    ::: starlette.templating.Jinja2Templates\n        options:\n            parameter_headings: false\n            show_root_heading: true\n            heading_level: 3\n            filters:\n                - \"__init__\"\n\nStarlette provides a simple way to get `jinja2` configured. This is probably\nwhat you want to use by default.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route, Mount\nfrom starlette.templating import Jinja2Templates\nfrom starlette.staticfiles import StaticFiles\n\n\ntemplates = Jinja2Templates(directory='templates')\n\nasync def homepage(request):\n    return templates.TemplateResponse(request, 'index.html')\n\nroutes = [\n    Route('/', endpoint=homepage),\n    Mount('/static', StaticFiles(directory='static'), name='static')\n]\n\napp = Starlette(debug=True, routes=routes)\n```\n\nNote that the incoming `request` instance must be included as part of the\ntemplate context.\n\nThe Jinja2 template context will automatically include a `url_for` function,\nso we can correctly hyperlink to other pages within the application.\n\nFor example, we can link to static files from within our HTML templates:\n\n```html\n<link href=\"{{ url_for('static', path='/css/bootstrap.min.css') }}\" rel=\"stylesheet\" />\n```\n\nIf you want to use [custom filters][jinja2], you will need to update the `env`\nproperty of `Jinja2Templates`:\n\n```python\nfrom commonmark import commonmark\nfrom starlette.templating import Jinja2Templates\n\ndef marked_filter(text):\n    return commonmark(text)\n\ntemplates = Jinja2Templates(directory='templates')\ntemplates.env.filters['marked'] = marked_filter\n```\n\n\n## Using custom jinja2.Environment instance\n\nStarlette also accepts a preconfigured [`jinja2.Environment`](https://jinja.palletsprojects.com/en/3.0.x/api/#api) instance. \n\n\n```python\nimport jinja2\nfrom starlette.templating import Jinja2Templates\n\nenv = jinja2.Environment(...)\ntemplates = Jinja2Templates(env=env)\n```\n\n\n## Autoescape\n\nWhen using the `directory` argument, Starlette enables autoescape by default for\n`.html`, `.htm`, and `.xml` templates using [`jinja2.select_autoescape()`](https://jinja.palletsprojects.com/en/stable/api/#jinja2.select_autoescape).\n\nThis protects against Cross-Site Scripting (XSS) vulnerabilities by escaping\nuser-provided content before rendering it in the template. For example, if a user\nsubmits `<script>alert('XSS')</script>` as their name, it will be rendered as\n`&lt;script&gt;alert('XSS')&lt;/script&gt;` instead of being executed as JavaScript.\n\n## Context processors\n\nA context processor is a function that returns a dictionary to be merged into a template context.\nEvery function takes only one argument `request` and must return a dictionary to add to the context.\n\nA common use case of template processors is to extend the template context with shared variables.\n\n```python\nimport typing\nfrom starlette.requests import Request\n\ndef app_context(request: Request) -> typing.Dict[str, typing.Any]:\n    return {'app': request.app}\n```\n\n### Registering context templates\n\nPass context processors to `context_processors` argument of the `Jinja2Templates` class.\n\n```python\nimport typing\n\nfrom starlette.requests import Request\nfrom starlette.templating import Jinja2Templates\n\ndef app_context(request: Request) -> typing.Dict[str, typing.Any]:\n    return {'app': request.app}\n\ntemplates = Jinja2Templates(\n    directory='templates', context_processors=[app_context]\n)\n```\n\n!!! info\n    Asynchronous functions as context processors are not supported.\n\n## Testing template responses\n\nWhen using the test client, template responses include `.template` and `.context`\nattributes.\n\n```python\nfrom starlette.testclient import TestClient\n\n\ndef test_homepage():\n    client = TestClient(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.template.name == 'index.html'\n    assert \"request\" in response.context\n```\n\n## Asynchronous template rendering\n\nJinja2 supports async template rendering, however as a general rule\nwe'd recommend that you keep your templates free from logic that invokes\ndatabase lookups, or other I/O operations.\n\nInstead we'd recommend that you ensure that your endpoints perform all I/O,\nfor example, strictly evaluate any database queries within the view and\ninclude the final results in the context.\n\n[jinja2]: https://jinja.palletsprojects.com/en/3.0.x/api/?highlight=environment#writing-filters\n[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike\n"
  },
  {
    "path": "docs/testclient.md",
    "content": "\n??? abstract \"API Reference\"\n    ::: starlette.testclient.TestClient\n        options:\n            parameter_headings: false\n            show_bases: false\n            show_root_heading: true\n            heading_level: 3\n            filters:\n                - \"__init__\"\n\nThe test client allows you to make requests against your ASGI application,\nusing the `httpx` library.\n\n```python\nfrom starlette.responses import HTMLResponse\nfrom starlette.testclient import TestClient\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'http'\n    response = HTMLResponse('<html><body>Hello, world!</body></html>')\n    await response(scope, receive, send)\n\n\ndef test_app():\n    client = TestClient(app)\n    response = client.get('/')\n    assert response.status_code == 200\n```\n\nThe test client exposes the same interface as any other `httpx` session.\nIn particular, note that the calls to make a request are just standard\nfunction calls, not awaitables.\n\nYou can use any of `httpx` standard API, such as authentication, session\ncookies handling, or file uploads.\n\nFor example, to set headers on the TestClient you can do:\n\n```python\nclient = TestClient(app)\n\n# Set headers on the client for future requests\nclient.headers = {\"Authorization\": \"...\"}\nresponse = client.get(\"/\")\n\n# Set headers for each request separately\nresponse = client.get(\"/\", headers={\"Authorization\": \"...\"})\n```\n\nAnd for example to send files with the TestClient:\n\n```python\nclient = TestClient(app)\n\n# Send a single file\nwith open(\"example.txt\", \"rb\") as f:\n    response = client.post(\"/form\", files={\"file\": f})\n\n# Send multiple files\nwith open(\"example.txt\", \"rb\") as f1:\n    with open(\"example.png\", \"rb\") as f2:\n        files = {\"file1\": f1, \"file2\": (\"filename\", f2, \"image/png\")}\n        response = client.post(\"/form\", files=files)\n```\n\nFor more information you can check the `httpx` [documentation](https://www.python-httpx.org/advanced/).\n\nBy default the `TestClient` will raise any exceptions that occur in the\napplication. Occasionally you might want to test the content of 500 error\nresponses, rather than allowing client to raise the server exception. In this\ncase you should use `client = TestClient(app, raise_server_exceptions=False)`.\n\n!!! note\n\n    If you want the `TestClient` to run the `lifespan` handler,\n    you will need to use the `TestClient` as a context manager. It will\n    not be triggered when the `TestClient` is instantiated. You can learn more about it\n    [here](lifespan.md#running-lifespan-in-tests).\n\n### Change client address\n\nBy default, the TestClient will set the client host to `\"testserver\"` and the port to `50000`.\n\nYou can change the client address by setting the `client` attribute of the `TestClient` instance:\n\n```python\nclient = TestClient(app, client=('localhost', 8000))\n```\n\n### Selecting the Async backend\n\n`TestClient` takes arguments `backend` (a string) and `backend_options` (a dictionary).\nThese options are passed to `anyio.start_blocking_portal()`.\nSee the [anyio documentation](https://anyio.readthedocs.io/en/stable/basics.html#backend-options)\nfor more information about the accepted backend options.\nBy default, `asyncio` is used with default options.\n\nTo run `Trio`, pass `backend=\"trio\"`. For example:\n\n```python\ndef test_app()\n    with TestClient(app, backend=\"trio\") as client:\n       ...\n```\n\nTo run `asyncio` with `uvloop`, pass `backend_options={\"use_uvloop\": True}`.  For example:\n\n```python\ndef test_app()\n    with TestClient(app, backend_options={\"use_uvloop\": True}) as client:\n       ...\n```\n\n### Testing WebSocket sessions\n\nYou can also test websocket sessions with the test client.\n\nThe `httpx` library will be used to build the initial handshake, meaning you\ncan use the same authentication options and other headers between both http and\nwebsocket testing.\n\n```python\nfrom starlette.testclient import TestClient\nfrom starlette.websockets import WebSocket\n\n\nasync def app(scope, receive, send):\n    assert scope['type'] == 'websocket'\n    websocket = WebSocket(scope, receive=receive, send=send)\n    await websocket.accept()\n    await websocket.send_text('Hello, world!')\n    await websocket.close()\n\n\ndef test_app():\n    client = TestClient(app)\n    with client.websocket_connect('/') as websocket:\n        data = websocket.receive_text()\n        assert data == 'Hello, world!'\n```\n\nThe operations on session are standard function calls, not awaitables.\n\nIt's important to use the session within a context-managed `with` block. This\nensure that the background thread on which the ASGI application is properly\nterminated, and that any exceptions that occur within the application are\nalways raised by the test client.\n\n#### Establishing a test session\n\n* `.websocket_connect(url, subprotocols=None, **options)` - Takes the same set of arguments as `httpx.get()`.\n\nMay raise `starlette.websockets.WebSocketDisconnect` if the application does not accept the websocket connection.\n\n`websocket_connect()` must be used as a context manager (in a `with` block).\n\n!!! note\n    The `params` argument is not supported by `websocket_connect`. If you need to pass query arguments, hard code it\n    directly in the URL.\n\n    ```python\n    with client.websocket_connect('/path?foo=bar') as websocket:\n        ...\n    ```\n\n#### Sending data\n\n* `.send_text(data)` - Send the given text to the application.\n* `.send_bytes(data)` - Send the given bytes to the application.\n* `.send_json(data, mode=\"text\")` - Send the given data to the application. Use `mode=\"binary\"` to send JSON over binary data frames.\n\n#### Receiving data\n\n* `.receive_text()` - Wait for incoming text sent by the application and return it.\n* `.receive_bytes()` - Wait for incoming bytestring sent by the application and return it.\n* `.receive_json(mode=\"text\")` - Wait for incoming json data sent by the application and return it. Use `mode=\"binary\"` to receive JSON over binary data frames.\n\nMay raise `starlette.websockets.WebSocketDisconnect`.\n\n#### Closing the connection\n\n* `.close(code=1000)` - Perform a client-side close of the websocket connection.\n\n### Asynchronous tests\n\nSometimes you will want to do async things outside of your application.\nFor example, you might want to check the state of your database after calling your app\nusing your existing async database client/infrastructure.\n\nFor these situations, using `TestClient` is difficult because it creates it's own event loop and async\nresources (like a database connection) often cannot be shared across event loops.\nThe simplest way to work around this is to just make your entire test async and use an async client, like [httpx.AsyncClient].\n\nHere is an example of such a test:\n\n```python\nfrom httpx import AsyncClient, ASGITransport\nfrom starlette.applications import Starlette\nfrom starlette.routing import Route\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\n\n\ndef hello(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Hello World!\")\n\n\napp = Starlette(routes=[Route(\"/\", hello)])\n\n\n# if you're using pytest, you'll need to to add an async marker like:\n# @pytest.mark.anyio  # using https://github.com/agronholm/anyio\n# or install and configure pytest-asyncio (https://github.com/pytest-dev/pytest-asyncio)\nasync def test_app() -> None:\n    # note: you _must_ set `base_url` for relative urls like \"/\" to work\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n        r = await client.get(\"/\")\n        assert r.status_code == 200\n        assert r.text == \"Hello World!\"\n```\n\n[httpx.AsyncClient]: https://www.python-httpx.org/advanced/#calling-into-python-web-apps\n"
  },
  {
    "path": "docs/third-party-packages.md",
    "content": "\nStarlette has a rapidly growing community of developers, building tools that integrate into Starlette, tools that depend on Starlette, etc.\n\nHere are some of those third party packages:\n\n## Plugins\n\n### Apitally\n\n<a href=\"https://github.com/apitally/apitally-py\" target=\"_blank\">GitHub</a> |\n<a href=\"https://docs.apitally.io/frameworks/starlette\" target=\"_blank\">Documentation</a>\n\nAnalytics, request logging and monitoring for REST APIs built with Starlette (and other frameworks).\n\n### Authlib\n\n<a href=\"https://github.com/lepture/Authlib\" target=\"_blank\">GitHub</a> |\n<a href=\"https://docs.authlib.org/en/latest/\" target=\"_blank\">Documentation</a>\n\nThe ultimate Python library in building OAuth and OpenID Connect clients and servers. Check out how to integrate with [Starlette](https://docs.authlib.org/en/latest/client/starlette.html).\n\n### ChannelBox\n\n<a href=\"https://github.com/Sobolev5/channel-box\" target=\"_blank\">GitHub Repository</a>\n\nChannelBox is a lightweight solution for WebSocket broadcasting in ASGI applications.\nIt allows sending messages to named WebSocket channel groups and integrates with Starlette and FastAPI.\n\n### Imia\n\n<a href=\"https://github.com/alex-oleshkevich/imia\" target=\"_blank\">GitHub</a>\n\nAn authentication framework for Starlette with pluggable authenticators and login/logout flow.\n\n### Mangum\n\n<a href=\"https://github.com/erm/mangum\" target=\"_blank\">GitHub</a>\n\nServerless ASGI adapter for AWS Lambda & API Gateway.\n\n### Nejma\n\n<a href=\"https://github.com/taoufik07/nejma\" target=\"_blank\">GitHub</a>\n\nManage and send messages to groups of channels using websockets.\nCheckout <a href=\"https://github.com/taoufik07/nejma-chat\" target=\"_blank\">nejma-chat</a>, a simple chat application built using `nejma` and `starlette`.\n\n### Scout APM\n\n<a href=\"https://github.com/scoutapp/scout_apm_python\" target=\"_blank\">GitHub</a>\n\nAn APM (Application Performance Monitoring) solution that can\ninstrument your application to find performance bottlenecks.\n\n### SpecTree\n\n<a href=\"https://github.com/0b01001001/spectree\" target=\"_blank\">GitHub</a>\n\nGenerate OpenAPI spec document and validate request & response with Python annotations. Less boilerplate code(no need for YAML).\n\n### Starlette APISpec\n\n<a href=\"https://github.com/Woile/starlette-apispec\" target=\"_blank\">GitHub</a>\n\nSimple APISpec integration for Starlette.\nDocument your REST API built with Starlette by declaring OpenAPI (Swagger)\nschemas in YAML format in your endpoint's docstrings.\n\n### Starlette Compress\n\n<a href=\"https://github.com/Zaczero/starlette-compress\" target=\"_blank\">GitHub</a>\n\nStarlette-Compress is a fast and simple middleware for compressing responses in Starlette.\nIt adds ZStd, Brotli, and GZip compression support with sensible default configuration.\n\n### Starlette Context\n\n<a href=\"https://github.com/tomwojcik/starlette-context\" target=\"_blank\">GitHub</a>\n\nMiddleware for Starlette that allows you to store and access the context data of a request.\nCan be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.\n\n### Starlette Cramjam\n\n<a href=\"https://github.com/developmentseed/starlette-cramjam\" target=\"_blank\">GitHub</a>\n\nA Starlette middleware that allows **brotli**, **gzip** and **deflate** compression algorithm with a minimal requirements.\n\n### Starlette OAuth2 API\n\n<a href=\"https://gitlab.com/jorgecarleitao/starlette-oauth2-api\" target=\"_blank\">GitLab</a>\n\nA starlette middleware to add authentication and authorization through JWTs.\nIt relies solely on an auth provider to issue access and/or id tokens to clients.\n\n### Starlette Prometheus\n\n<a href=\"https://github.com/perdy/starlette-prometheus\" target=\"_blank\">GitHub</a>\n\nA plugin for providing an endpoint that exposes [Prometheus](https://prometheus.io/) metrics based on its [official python client](https://github.com/prometheus/client_python).\n\n### Starlette WTF\n\n<a href=\"https://github.com/muicss/starlette-wtf\" target=\"_blank\">GitHub</a>\n\nA simple tool for integrating Starlette and WTForms. It is modeled on the excellent Flask-WTF library.\n\n### Starlette-Login\n\n<a href=\"https://github.com/jockerz/Starlette-Login\" target=\"_blank\">GitHub</a> |\n<a href=\"https://starlette-login.readthedocs.io/en/stable/\" target=\"_blank\">Documentation</a>\n\nUser session management for Starlette.\nIt handles the common tasks of logging in, logging out, and remembering your users' sessions over extended periods of time.\n\n\n### Starsessions\n\n<a href=\"https://github.com/alex-oleshkevich/starsessions\" target=\"_blank\">GitHub</a>\n\nAn alternate session support implementation with customizable storage backends.\n\n### webargs-starlette\n\n<a href=\"https://github.com/sloria/webargs-starlette\" target=\"_blank\">GitHub</a>\n\nDeclarative request parsing and validation for Starlette, built on top\nof [webargs](https://github.com/marshmallow-code/webargs).\n\nAllows you to parse querystring, JSON, form, headers, and cookies using\ntype annotations.\n\n### DecoRouter\n\n<a href=\"https://github.com/MrPigss/DecoRouter\" target=\"_blank\">GitHub</a>\n\nFastAPI style routing for Starlette.\n\nAllows you to use decorators to generate routing tables.\n\n### Starception\n\n<a href=\"https://github.com/alex-oleshkevich/starception\" target=\"_blank\">GitHub</a>\n\nBeautiful exception page for Starlette apps.\n\n### Starlette-Admin\n\n<a href=\"https://github.com/jowilf/starlette-admin\" target=\"_blank\">GitHub</a> |\n<a href=\"https://jowilf.github.io/starlette-admin\" target=\"_blank\">Documentation</a>\n\nSimple and extensible admin interface framework.\n\nBuilt with [Tabler](https://tabler.io/) and [Datatables](https://datatables.net/), it allows you\nto quickly generate fully customizable admin interface for your models. You can export your data to many formats (*CSV*, *PDF*,\n*Excel*, etc), filter your data with complex query including `AND` and `OR` conditions,  upload files, ...\n\n### Vellox\n\n<a href=\"https://github.com/junah201/vellox\" target=\"_blank\">GitHub</a>\n\nServerless ASGI adapter for GCP Cloud Functions.\n\n## Starlette Bridge\n\n<a href=\"https://github.com/tarsil/starlette-bridge\" target=\"_blank\">GitHub</a> |\n<a href=\"https://starlette-bridge.tarsild.io/\" target=\"_blank\">Documentation</a>\n\nWith the deprecation of `on_startup` and `on_shutdown`, Starlette Bridge makes sure you can still\nuse the old ways of declaring events with a particularity that internally, in fact, creates the\n`lifespan` for you. This way backwards compatibility is assured for the existing packages out there\nwhile maintaining the integrity of the newly `lifespan` events of `Starlette`.\n\n## Frameworks\n\n### FastAPI\n\n<a href=\"https://github.com/tiangolo/fastapi\" target=\"_blank\">GitHub</a> |\n<a href=\"https://fastapi.tiangolo.com/\" target=\"_blank\">Documentation</a>\n\nHigh performance, easy to learn, fast to code, ready for production web API framework.\nInspired by **APIStar**'s previous server system with type declarations for route parameters, based on the OpenAPI specification version 3.0.0+ (with JSON Schema), powered by **Pydantic** for the data handling.\n\n### Flama\n\n<a href=\"https://github.com/vortico/flama\" target=\"_blank\">GitHub</a> |\n<a href=\"https://flama.dev/\" target=\"_blank\">Documentation</a>\n\nFlama is a **data-science oriented framework** to rapidly build modern and robust **machine learning** (ML) APIs. The main aim of the framework is to make ridiculously simple the deployment of ML APIs. With Flama, data scientists can now quickly turn their ML models into asynchronous, auto-documented APIs with just a single line of code. All in just few seconds!\n\nFlama comes with an intuitive CLI, and provides an easy-to-learn philosophy to speed up the building of **highly performant** GraphQL, REST, and ML APIs. Besides, it comprises an ideal solution for the development of asynchronous and **production-ready** services, offering **automatic deployment** for ML models.\n\n### Greppo\n\n<a href=\"https://github.com/greppo-io/greppo\" target=\"_blank\">GitHub</a> |\n<a href=\"https://docs.greppo.io/\" target=\"_blank\">Documentation</a>\n\nA Python framework for building geospatial dashboards and web-applications.\n\nGreppo is an open-source Python framework that makes it easy to build geospatial dashboards and web-applications. It provides a toolkit to quickly integrate data, algorithms, visualizations and UI for interactivity. It provides APIs to the update the variables in the backend, recompute the logic, and reflect the changes in the frontend (data mutation hook).\n\n### Responder\n\n<a href=\"https://github.com/taoufik07/responder\" target=\"_blank\">GitHub</a> |\n<a href=\"https://python-responder.org/en/latest/\" target=\"_blank\">Documentation</a>\n\nAsync web service framework. Some Features: flask-style route expression,\nyaml support, OpenAPI schema generation, background tasks, graphql.\n\n### Starlette-apps\n\nRoll your own framework with a simple app system, like [Django-GDAPS](https://gdaps.readthedocs.io/en/latest/) or [CakePHP](https://cakephp.org/).\n\n<a href=\"https://github.com/yourlabs/starlette-apps\" target=\"_blank\">GitHub</a>\n\n### Dark Star\n\nA simple framework to help minimise the code needed to get HTML to the browser. Changes your file paths into Starlette routes and puts your view code right next to your template. Includes support for [htmx](https://htmx.org) to help enhance your frontend.\n\n<a href=\"https://lllama.github.io/dark-star\" target=\"_blank\">Docs</a>\n<a href=\"https://github.com/lllama/dark-star\" target=\"_blank\">GitHub</a>\n\n### Xpresso\n\nA flexible and extendable web framework built on top of Starlette, Pydantic and [di](https://github.com/adriangb/di).\n\n<a href=\"https://github.com/adriangb/xpresso\" target=\"_blank\">GitHub</a> |\n<a href=https://xpresso-api.dev/\" target=\"_blank\">Documentation</a>\n\n### Ellar\n\n<a href=\"https://github.com/eadwinCode/ellar\" target=\"_blank\">GitHub</a> |\n<a href=\"https://eadwincode.github.io/ellar/\" target=\"_blank\">Documentation</a>\n\nEllar is an ASGI web framework for building fast, efficient and scalable RESTAPIs and server-side applications. It offers a high level of abstraction in building server-side applications and combines elements of OOP (Object Oriented Programming), and FP (Functional Programming) - Inspired by Nestjs.\n\nIt is built on 3 core libraries **Starlette**, **Pydantic**, and **injector**.\n\n### Apiman\n\nAn extension to integrate Swagger/OpenAPI document easily for Starlette project and provide [SwaggerUI](http://swagger.io/swagger-ui/) and [RedocUI](https://rebilly.github.io/ReDoc/).\n\n<a href=\"https://github.com/strongbugman/apiman\" target=\"_blank\">GitHub</a>\n\n### Starlette-Babel\n\nProvides translations, localization, and timezone support via Babel integration.\n\n<a href=\"https://github.com/alex-oleshkevich/starlette_babel\" target=\"_blank\">GitHub</a>\n\n### Starlette-StaticResources\n\n<a href=\"https://github.com/DavidVentura/starlette-static-resources\" target=\"_blank\">GitHub</a>\n\nAllows mounting [package resources](https://docs.python.org/3/library/importlib.resources.html#module-importlib.resources) for static data, similar to [StaticFiles](staticfiles.md).\n\n### Sentry\n\n<a href=\"https://github.com/getsentry/sentry-python\" target=\"_blank\">GitHub</a> |\n<a href=\"https://docs.sentry.io/platforms/python/guides/starlette/\" target=\"_blank\">Documentation</a>\n\nSentry is a software error detection tool. It offers actionable insights for resolving performance issues and errors, allowing users to diagnose, fix, and optimize Python debugging. Additionally, it integrates seamlessly with Starlette for Python application development. Sentry's capabilities include error tracking, performance insights, contextual information, and alerts/notifications.\n\n### Shiny\n\n<a href=\"https://github.com/posit-dev/py-shiny\" target=\"_blank\">GitHub</a> |\n<a href=\"https://shiny.posit.co/py/\" target=\"_blank\">Documentation</a>\n\nLeveraging Starlette and asyncio, Shiny allows developers to create effortless Python web applications using the power of reactive programming. Shiny eliminates the hassle of manual state management, automatically determining the best execution path for your app at runtime while simultaneously minimizing re-rendering. This means that Shiny can support everything from the simplest dashboard to full-featured web apps.\n"
  },
  {
    "path": "docs/threadpool.md",
    "content": "# Thread Pool\n\nStarlette uses a thread pool in several scenarios to avoid blocking the event loop:\n\n- When you create a synchronous endpoint using `def` instead of `async def`\n- When serving files with [`FileResponse`](responses.md#fileresponse)\n- When handling file uploads with [`UploadFile`](requests.md#request-files)\n- When running synchronous background tasks with [`BackgroundTask`](background.md)\n- And some other scenarios that may not be documented...\n\nStarlette will run your code in a thread pool to avoid blocking the event loop.\nThis applies for endpoint functions and background tasks you create, but also for internal Starlette code.\n\nTo be more precise, Starlette uses `anyio.to_thread.run_sync` to run the synchronous code.\n\n## Concurrency Limitations\n\nThe default thread pool size is only 40 _tokens_. This means that only 40 threads can run at the same time.\nThis limit is shared with other libraries: for example FastAPI also uses `anyio` to run sync dependencies, which also uses up thread capacity.\n\nIf you need to run more threads, you can increase the number of _tokens_:\n\n```py\nimport anyio.to_thread\n\nlimiter = anyio.to_thread.current_default_thread_limiter()\nlimiter.total_tokens = 100\n```\n\nThe above code will increase the number of _tokens_ to 100.\n\nIncreasing the number of threads may have a performance and memory impact, so be careful when doing so.\n"
  },
  {
    "path": "docs/websockets.md",
    "content": "\nStarlette includes a `WebSocket` class that fulfils a similar role\nto the HTTP request, but that allows sending and receiving data on a websocket.\n\n### WebSocket\n\nSignature: `WebSocket(scope, receive=None, send=None)`\n\n```python\nfrom starlette.websockets import WebSocket\n\n\nasync def app(scope, receive, send):\n    websocket = WebSocket(scope=scope, receive=receive, send=send)\n    await websocket.accept()\n    await websocket.send_text('Hello, world!')\n    await websocket.close()\n```\n\nWebSockets present a mapping interface, so you can use them in the same\nway as a `scope`.\n\nFor instance: `websocket['path']` will return the ASGI path.\n\n#### URL\n\nThe websocket URL is accessed as `websocket.url`.\n\nThe property is actually a subclass of `str`, and also exposes all the\ncomponents that can be parsed out of the URL.\n\nFor example: `websocket.url.path`, `websocket.url.port`, `websocket.url.scheme`.\n\n#### Headers\n\nHeaders are exposed as an immutable, case-insensitive, multi-dict.\n\nFor example: `websocket.headers['sec-websocket-version']`\n\n#### Query Parameters\n\nQuery parameters are exposed as an immutable multi-dict.\n\nFor example: `websocket.query_params['search']`\n\n#### Path Parameters\n\nRouter path parameters are exposed as a dictionary interface.\n\nFor example: `websocket.path_params['username']`\n\n### Accepting the connection\n\n* `await websocket.accept(subprotocol=None, headers=None)`\n\n### Sending data\n\n* `await websocket.send_text(data)`\n* `await websocket.send_bytes(data)`\n* `await websocket.send_json(data)`\n\nJSON messages default to being sent over text data frames, from version 0.10.0 onwards.\nUse `websocket.send_json(data, mode=\"binary\")` to send JSON over binary data frames.\n\n### Receiving data\n\n* `await websocket.receive_text()`\n* `await websocket.receive_bytes()`\n* `await websocket.receive_json()`\n\nMay raise `starlette.websockets.WebSocketDisconnect()`.\n\nJSON messages default to being received over text data frames, from version 0.10.0 onwards.\nUse `websocket.receive_json(data, mode=\"binary\")` to receive JSON over binary data frames.\n\n### Iterating data\n\n* `websocket.iter_text()`\n* `websocket.iter_bytes()`\n* `websocket.iter_json()`\n\nSimilar to `receive_text`, `receive_bytes`, and `receive_json` but returns an\nasync iterator.\n\n```python hl_lines=\"7-8\"\nfrom starlette.websockets import WebSocket\n\n\nasync def app(scope, receive, send):\n    websocket = WebSocket(scope=scope, receive=receive, send=send)\n    await websocket.accept()\n    async for message in websocket.iter_text():\n        await websocket.send_text(f\"Message text was: {message}\")\n    await websocket.close()\n```\n\nWhen `starlette.websockets.WebSocketDisconnect` is raised, the iterator will exit.\n\n### Closing the connection\n\n* `await websocket.close(code=1000, reason=None)`\n\n### Sending and receiving messages\n\nIf you need to send or receive raw ASGI messages then you should use\n`websocket.send()` and `websocket.receive()` rather than using the raw `send` and\n`receive` callables. This will ensure that the websocket's state is kept\ncorrectly updated.\n\n* `await websocket.send(message)`\n* `await websocket.receive()`\n\n### Send Denial Response\n\nIf you call `websocket.close()` before calling `websocket.accept()` then\nthe server will automatically send a HTTP 403 error to the client.\n\nIf you want to send a different error response, you can use the\n`websocket.send_denial_response()` method. This will send the response\nand then close the connection.\n\n* `await websocket.send_denial_response(response)`\n\nThis requires the ASGI server to support the WebSocket Denial Response\nextension. If it is not supported a `RuntimeError` will be raised.\n\nIn the context of `Starlette`, you can also use the `HTTPException` to achieve the same result.\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.exceptions import HTTPException\nfrom starlette.routing import WebSocketRoute\nfrom starlette.websockets import WebSocket\n\n\ndef is_authorized(subprotocols: list[str]):\n    if len(subprotocols) != 2:\n        return False\n    if subprotocols[0] != \"Authorization\":\n        return False\n    # Here we are hard coding the token, in a real application you would validate the token\n    # against a database or an external service.\n    if subprotocols[1] != \"token\":\n        return False\n    return True\n\n\nasync def websocket_endpoint(websocket: WebSocket):\n    subprotocols = websocket.scope[\"subprotocols\"]\n    if not is_authorized(subprotocols):\n        raise HTTPException(status_code=401, detail=\"Unauthorized\")\n    await websocket.accept(\"Authorization\")\n    await websocket.send_text(\"Hello, world!\")\n    await websocket.close()\n\n\napp = Starlette(debug=True, routes=[WebSocketRoute(\"/ws\", websocket_endpoint)])\n```\n\n<!-- Test the above with `npx wscat -c ws://localhost:8000/ws -s Authorization -s token` -->\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Starlette\nsite_description: The little ASGI library that shines.\nsite_url: https://starlette.dev\n\nrepo_name: Kludex/starlette\nrepo_url: https://github.com/Kludex/starlette\nedit_uri: edit/main/docs/\n\nstrict: true\n\ntheme:\n  name: \"material\"\n  custom_dir: docs/overrides\n  palette:\n    - scheme: \"default\"\n      media: \"(prefers-color-scheme: light)\"\n      toggle:\n        icon: \"material/lightbulb\"\n        name: \"Switch to dark mode\"\n    - scheme: \"slate\"\n      media: \"(prefers-color-scheme: dark)\"\n      primary: \"blue\"\n      toggle:\n        icon: \"material/lightbulb-outline\"\n        name: \"Switch to light mode\"\n  icon:\n    repo: fontawesome/brands/github\n  features:\n    - content.code.copy\n    - toc.follow\n\nnav:\n  - Introduction: \"index.md\"\n  - Features:\n      - Applications: \"applications.md\"\n      - Requests: \"requests.md\"\n      - Responses: \"responses.md\"\n      - WebSockets: \"websockets.md\"\n      - Routing: \"routing.md\"\n      - Endpoints: \"endpoints.md\"\n      - Middleware: \"middleware.md\"\n      - Static Files: \"staticfiles.md\"\n      - Templates: \"templates.md\"\n      - Database: \"database.md\"\n      - GraphQL: \"graphql.md\"\n      - Authentication: \"authentication.md\"\n      - API Schemas: \"schemas.md\"\n      - Lifespan: \"lifespan.md\"\n      - Background Tasks: \"background.md\"\n      - Server Push: \"server-push.md\"\n      - Exceptions: \"exceptions.md\"\n      - Configuration: \"config.md\"\n      - Thread Pool: \"threadpool.md\"\n      - Test Client: \"testclient.md\"\n  - Release Notes: \"release-notes.md\"\n  - Community:\n      - Third Party Packages: \"third-party-packages.md\"\n      - Contributing: \"contributing.md\"\n\nextra_css:\n  - css/custom.css\n\nextra_javascript:\n  - js/custom.js\n\nextra:\n  analytics:\n    provider: google\n    property: G-Z37GTYBR6M\n  social:\n    - icon: fontawesome/brands/github-alt\n      link: https://github.com/Kludex/starlette\n    - icon: fontawesome/brands/discord\n      link: https://discord.com/invite/RxKUF5JuHs\n    - icon: fontawesome/brands/twitter\n      link: https://x.com/marcelotryle\n    - icon: fontawesome/brands/linkedin\n      link: https://www.linkedin.com/in/marcelotryle\n    - icon: fontawesome/solid/globe\n      link: https://fastapiexpert.com\n\nmarkdown_extensions:\n  - attr_list\n  - admonition\n  - pymdownx.highlight\n  - pymdownx.superfences\n  - pymdownx.details\n  - pymdownx.tabbed:\n      alternate_style: true\n  - pymdownx.emoji:\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n  - pymdownx.tasklist:\n      custom_checkbox: true\n\nwatch:\n  - starlette\n\nplugins:\n  - search\n  - mkdocstrings:\n      handlers:\n        python:\n          options:\n            docstring_section_style: list\n            show_root_toc_entry: false\n            members_order: source\n            separate_signature: true\n            filters: [\"!^_\"]\n            docstring_options:\n              ignore_init_summary: true\n            merge_init_into_class: true\n            parameter_headings: true\n            show_signature_annotations: true\n            show_source: false\n            signature_crossrefs: true\n          inventories:\n            - url: https://docs.python.org/3/objects.inv\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"starlette\"\ndynamic = [\"version\"]\ndescription = \"The little ASGI library that shines.\"\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nlicense-files = [\"LICENSE.md\"]\nrequires-python = \">=3.10\"\nauthors = [\n    { name = \"Tom Christie\", email = \"tom@tomchristie.com\" }\n]\nmaintainers = [\n    { name = \"Marcelo Trylesinski\", email = \"marcelotryle@gmail.com\" },\n]\nclassifiers = [\n    \"Development Status :: 3 - Alpha\",\n    \"Environment :: Web Environment\",\n    \"Framework :: AnyIO\",\n    \"Intended Audience :: Developers\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Topic :: Internet :: WWW/HTTP\",\n]\ndependencies = [\n    \"anyio>=3.6.2,<5\",\n    \"typing_extensions>=4.10.0; python_version < '3.13'\",\n]\n\n[project.optional-dependencies]\nfull = [\n    \"itsdangerous\",\n    \"jinja2\",\n    \"python-multipart>=0.0.18\",\n    \"pyyaml\",\n    \"httpx>=0.27.0,<0.29.0\",\n]\n\n[dependency-groups]\ndev = [\n    # We add starlette[full] so `uv sync` considers the extras.\n    \"starlette[full]\",\n    \"coverage>=7.8.2\",\n    \"importlib-metadata==8.7.1\",\n    \"mypy==1.16.1\",\n    \"ruff==0.15.4\",\n    \"types-PyYAML==6.0.12.20250516\",\n    \"pytest==9.0.2\",\n    \"trio==0.33.0\",\n    # Check dist\n    \"twine==6.2.0\",\n]\ndocs = [\n    \"black==26.3.1\",\n    \"mkdocstrings>=1.0.2\",\n    \"mkdocstrings-python>=2.0.1\",\n    \"zensical>=0.0.19\",\n]\n\n[tool.uv]\ndefault-groups = [\"dev\", \"docs\"]\nrequired-version = \">=0.8.6\"\n\n[project.urls]\nHomepage = \"https://github.com/Kludex/starlette\"\nDocumentation = \"https://starlette.dev/\"\nChangelog = \"https://starlette.dev/release-notes/\"\nFunding = \"https://github.com/sponsors/Kludex\"\nSource = \"https://github.com/Kludex/starlette\"\n\n[tool.hatch.version]\npath = \"starlette/__init__.py\"\n\n[tool.ruff]\nline-length = 120\n\n[tool.ruff.lint]\nselect = [\n    \"E\",      # https://docs.astral.sh/ruff/rules/#error-e\n    \"F\",      # https://docs.astral.sh/ruff/rules/#pyflakes-f\n    \"I\",      # https://docs.astral.sh/ruff/rules/#isort-i\n    \"FA\",     # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa\n    \"UP\",     # https://docs.astral.sh/ruff/rules/#pyupgrade-up\n    \"RUF100\", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf\n]\nignore = [\"UP031\"] # https://docs.astral.sh/ruff/rules/printf-string-formatting/\n\n[tool.ruff.lint.isort]\ncombine-as-imports = true\n\n[tool.mypy]\nstrict = true\n\n[[tool.mypy.overrides]]\nmodule = \"starlette.testclient.*\"\nimplicit_optional = true\n\n[tool.pytest.ini_options]\naddopts = \"-rXs --strict-config --strict-markers\"\nxfail_strict = true\nfilterwarnings = [\n    # Turn warnings that aren't filtered into exceptions\n    \"error\",\n    \"ignore: run_until_first_complete is deprecated and will be removed in a future version.:DeprecationWarning\",\n    \"ignore: starlette.middleware.wsgi is deprecated and will be removed in a future release.*:DeprecationWarning\",\n    \"ignore: Async generator 'starlette.requests.Request.stream' was garbage collected before it had been exhausted.*:ResourceWarning\",\n    \"ignore: Use 'content=<...>' to upload raw bytes/text content.:DeprecationWarning\",\n]\n\n[tool.coverage.run]\nbranch = true\nsource_pkgs = [\"starlette\", \"tests\"]\n\n[tool.coverage.report]\nexclude_also = [\n    \"@overload\",\n    \"raise NotImplementedError\",\n]\n"
  },
  {
    "path": "scripts/README.md",
    "content": "# Development Scripts\n\n* `scripts/install` - Install dependencies in a virtual environment.\n* `scripts/test` - Run the test suite.\n* `scripts/lint` - Run the automated code linting/formatting tools.\n* `scripts/check` - Run the code linting, checking that it passes.\n* `scripts/coverage` - Check that code coverage is complete.\n* `scripts/build` - Build source and wheel packages.\n\nStyled after GitHub's [\"Scripts to Rule Them All\"](https://github.com/github/scripts-to-rule-them-all).\n"
  },
  {
    "path": "scripts/build",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv build\nuv run twine check dist/*\nuv run zensical build --clean\n"
  },
  {
    "path": "scripts/check",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"starlette tests\"\n\nset -x\n\n./scripts/sync-version\nuv run ruff format --check --diff $SOURCE_FILES\nuv run mypy $SOURCE_FILES\nuv run ruff check $SOURCE_FILES\n"
  },
  {
    "path": "scripts/coverage",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv run coverage report --show-missing --skip-covered --fail-under=100\n"
  },
  {
    "path": "scripts/docs",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv run zensical serve\n"
  },
  {
    "path": "scripts/install",
    "content": "#!/bin/sh -e\n\nset -x\n\nuv sync --frozen\n"
  },
  {
    "path": "scripts/lint",
    "content": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"starlette tests\"\n\nset -x\n\nuv run ruff format $SOURCE_FILES\nuv run ruff check --fix $SOURCE_FILES\n"
  },
  {
    "path": "scripts/sync-version",
    "content": "#!/bin/sh -e\n\nSEMVER_REGEX=\"([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*))?(\\+[0-9A-Za-z-]+)?\"\nCHANGELOG_VERSION=$(grep -o -E $SEMVER_REGEX docs/release-notes.md | head -1)\nVERSION=$(grep -o -E $SEMVER_REGEX starlette/__init__.py | head -1)\nif [ \"$CHANGELOG_VERSION\" != \"$VERSION\" ]; then\n    echo \"Version in changelog does not match version in starlette/__init__.py!\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/check\nfi\n\nuv run coverage run -m pytest $@\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/coverage\nfi\n"
  },
  {
    "path": "starlette/__init__.py",
    "content": "__version__ = \"1.0.0rc1\"\n"
  },
  {
    "path": "starlette/_exception_handler.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom starlette._utils import is_async_callable\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\n\nExceptionHandlers = dict[Any, ExceptionHandler]\nStatusHandlers = dict[int, ExceptionHandler]\n\n\ndef _lookup_exception_handler(exc_handlers: ExceptionHandlers, exc: Exception) -> ExceptionHandler | None:\n    for cls in type(exc).__mro__:\n        if cls in exc_handlers:\n            return exc_handlers[cls]\n    return None\n\n\ndef wrap_app_handling_exceptions(app: ASGIApp, conn: Request | WebSocket) -> ASGIApp:\n    exception_handlers: ExceptionHandlers\n    status_handlers: StatusHandlers\n    try:\n        exception_handlers, status_handlers = conn.scope[\"starlette.exception_handlers\"]\n    except KeyError:\n        exception_handlers, status_handlers = {}, {}\n\n    async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:\n        response_started = False\n\n        async def sender(message: Message) -> None:\n            nonlocal response_started\n\n            if message[\"type\"] == \"http.response.start\":\n                response_started = True\n            await send(message)\n\n        try:\n            await app(scope, receive, sender)\n        except Exception as exc:\n            handler = None\n\n            if isinstance(exc, HTTPException):\n                handler = status_handlers.get(exc.status_code)\n\n            if handler is None:\n                handler = _lookup_exception_handler(exception_handlers, exc)\n\n            if handler is None:\n                raise exc\n\n            if response_started:\n                raise RuntimeError(\"Caught handled exception, but response already started.\") from exc\n\n            if is_async_callable(handler):\n                response = await handler(conn, exc)\n            else:\n                response = await run_in_threadpool(handler, conn, exc)\n            if response is not None:\n                await response(scope, receive, sender)\n\n    return wrapped_app\n"
  },
  {
    "path": "starlette/_utils.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport sys\nfrom collections.abc import Awaitable, Callable, Generator\nfrom contextlib import AbstractAsyncContextManager, contextmanager\nfrom typing import Any, Generic, Protocol, TypeVar, overload\n\nfrom starlette.types import Scope\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from inspect import iscoroutinefunction\n    from typing import TypeIs\nelse:  # pragma: no cover\n    from asyncio import iscoroutinefunction\n\n    from typing_extensions import TypeIs\n\nhas_exceptiongroups = True\nif sys.version_info < (3, 11):  # pragma: no cover\n    try:\n        from exceptiongroup import BaseExceptionGroup  # type: ignore[unused-ignore,import-not-found]\n    except ImportError:\n        has_exceptiongroups = False\n\nT = TypeVar(\"T\")\nAwaitableCallable = Callable[..., Awaitable[T]]\n\n\n@overload\ndef is_async_callable(obj: AwaitableCallable[T]) -> TypeIs[AwaitableCallable[T]]: ...\n\n\n@overload\ndef is_async_callable(obj: Any) -> TypeIs[AwaitableCallable[Any]]: ...\n\n\ndef is_async_callable(obj: Any) -> Any:\n    while isinstance(obj, functools.partial):\n        obj = obj.func\n\n    return iscoroutinefunction(obj) or (callable(obj) and iscoroutinefunction(obj.__call__))\n\n\nT_co = TypeVar(\"T_co\", covariant=True)\n\n\nclass AwaitableOrContextManager(\n    Awaitable[T_co], AbstractAsyncContextManager[T_co], Protocol[T_co]\n): ...  # pragma: no branch\n\n\nclass SupportsAsyncClose(Protocol):\n    async def close(self) -> None: ...  # pragma: no cover\n\n\nSupportsAsyncCloseType = TypeVar(\"SupportsAsyncCloseType\", bound=SupportsAsyncClose, covariant=False)\n\n\nclass AwaitableOrContextManagerWrapper(Generic[SupportsAsyncCloseType]):\n    __slots__ = (\"aw\", \"entered\")\n\n    def __init__(self, aw: Awaitable[SupportsAsyncCloseType]) -> None:\n        self.aw = aw\n\n    def __await__(self) -> Generator[Any, None, SupportsAsyncCloseType]:\n        return self.aw.__await__()\n\n    async def __aenter__(self) -> SupportsAsyncCloseType:\n        self.entered = await self.aw\n        return self.entered\n\n    async def __aexit__(self, *args: Any) -> None | bool:\n        await self.entered.close()\n        return None\n\n\n@contextmanager\ndef collapse_excgroups() -> Generator[None, None, None]:\n    try:\n        yield\n    except BaseException as exc:\n        if has_exceptiongroups:  # pragma: no cover\n            while isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1:\n                exc = exc.exceptions[0]\n\n        raise exc\n\n\ndef get_route_path(scope: Scope) -> str:\n    path: str = scope[\"path\"]\n    root_path = scope.get(\"root_path\", \"\")\n    if not root_path:\n        return path\n\n    if not path.startswith(root_path):\n        return path\n\n    if path == root_path:\n        return \"\"\n\n    if path[len(root_path)] == \"/\":\n        return path[len(root_path) :]\n\n    return path\n"
  },
  {
    "path": "starlette/applications.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Awaitable, Callable, Mapping, Sequence\nfrom typing import Any, ParamSpec, TypeVar\n\nfrom starlette.datastructures import State, URLPath\nfrom starlette.middleware import Middleware, _MiddlewareFactory\nfrom starlette.middleware.errors import ServerErrorMiddleware\nfrom starlette.middleware.exceptions import ExceptionMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import BaseRoute, Router\nfrom starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send\n\nAppType = TypeVar(\"AppType\", bound=\"Starlette\")\nP = ParamSpec(\"P\")\n\n\nclass Starlette:\n    \"\"\"Creates an Starlette application.\"\"\"\n\n    def __init__(\n        self: AppType,\n        debug: bool = False,\n        routes: Sequence[BaseRoute] | None = None,\n        middleware: Sequence[Middleware] | None = None,\n        exception_handlers: Mapping[Any, ExceptionHandler] | None = None,\n        lifespan: Lifespan[AppType] | None = None,\n    ) -> None:\n        \"\"\"Initializes the application.\n\n        Parameters:\n            debug: Boolean indicating if debug tracebacks should be returned on errors.\n            routes: A list of routes to serve incoming HTTP and WebSocket requests.\n            middleware: A list of middleware to run for every request. A starlette\n                application will always automatically include two middleware classes.\n                `ServerErrorMiddleware` is added as the very outermost middleware, to handle\n                any uncaught errors occurring anywhere in the entire stack.\n                `ExceptionMiddleware` is added as the very innermost middleware, to deal\n                with handled exception cases occurring in the routing or endpoints.\n            exception_handlers: A mapping of either integer status codes,\n                or exception class types onto callables which handle the exceptions.\n                Exception handler callables should be of the form\n                `handler(request, exc) -> response` and may be either standard functions, or\n                async functions.\n            lifespan: A lifespan context function, which can be used to perform\n                startup and shutdown tasks. This is a newer style that replaces the\n                `on_startup` and `on_shutdown` handlers. Use one or the other, not both.\n        \"\"\"\n        self.debug = debug\n        self.state = State()\n        self.router = Router(routes, lifespan=lifespan)\n        self.exception_handlers = {} if exception_handlers is None else dict(exception_handlers)\n        self.user_middleware = [] if middleware is None else list(middleware)\n        self.middleware_stack: ASGIApp | None = None\n\n    def build_middleware_stack(self) -> ASGIApp:\n        debug = self.debug\n        error_handler = None\n        exception_handlers: dict[Any, ExceptionHandler] = {}\n\n        for key, value in self.exception_handlers.items():\n            if key in (500, Exception):\n                error_handler = value\n            else:\n                exception_handlers[key] = value\n\n        middleware = (\n            [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]\n            + self.user_middleware\n            + [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]\n        )\n\n        app = self.router\n        for cls, args, kwargs in reversed(middleware):\n            app = cls(app, *args, **kwargs)\n        return app\n\n    @property\n    def routes(self) -> list[BaseRoute]:\n        return self.router.routes\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        return self.router.url_path_for(name, **path_params)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        scope[\"app\"] = self\n        if self.middleware_stack is None:\n            self.middleware_stack = self.build_middleware_stack()\n        await self.middleware_stack(scope, receive, send)\n\n    def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:\n        self.router.mount(path, app=app, name=name)  # pragma: no cover\n\n    def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:\n        self.router.host(host, app=app, name=name)  # pragma: no cover\n\n    def add_middleware(self, middleware_class: _MiddlewareFactory[P], *args: P.args, **kwargs: P.kwargs) -> None:\n        if self.middleware_stack is not None:  # pragma: no cover\n            raise RuntimeError(\"Cannot add middleware after an application has started\")\n        self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs))\n\n    def add_exception_handler(\n        self,\n        exc_class_or_status_code: int | type[Exception],\n        handler: ExceptionHandler,\n    ) -> None:  # pragma: no cover\n        self.exception_handlers[exc_class_or_status_code] = handler\n\n    def add_route(\n        self,\n        path: str,\n        route: Callable[[Request], Awaitable[Response] | Response],\n        methods: list[str] | None = None,\n        name: str | None = None,\n        include_in_schema: bool = True,\n    ) -> None:  # pragma: no cover\n        self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema)\n"
  },
  {
    "path": "starlette/authentication.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport inspect\nfrom collections.abc import Callable, Sequence\nfrom typing import Any, ParamSpec\nfrom urllib.parse import urlencode\n\nfrom starlette._utils import is_async_callable\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import HTTPConnection, Request\nfrom starlette.responses import RedirectResponse\nfrom starlette.websockets import WebSocket\n\n_P = ParamSpec(\"_P\")\n\n\ndef has_required_scope(conn: HTTPConnection, scopes: Sequence[str]) -> bool:\n    for scope in scopes:\n        if scope not in conn.auth.scopes:\n            return False\n    return True\n\n\ndef requires(\n    scopes: str | Sequence[str],\n    status_code: int = 403,\n    redirect: str | None = None,\n) -> Callable[[Callable[_P, Any]], Callable[_P, Any]]:\n    scopes_list = [scopes] if isinstance(scopes, str) else list(scopes)\n\n    def decorator(\n        func: Callable[_P, Any],\n    ) -> Callable[_P, Any]:\n        sig = inspect.signature(func)\n        for idx, parameter in enumerate(sig.parameters.values()):\n            if parameter.name == \"request\" or parameter.name == \"websocket\":\n                type_ = parameter.name\n                break\n        else:\n            raise Exception(f'No \"request\" or \"websocket\" argument on function \"{func}\"')\n\n        if type_ == \"websocket\":\n            # Handle websocket functions. (Always async)\n            @functools.wraps(func)\n            async def websocket_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> None:\n                websocket = kwargs.get(\"websocket\", args[idx] if idx < len(args) else None)\n                assert isinstance(websocket, WebSocket)\n\n                if not has_required_scope(websocket, scopes_list):\n                    await websocket.close()\n                else:\n                    await func(*args, **kwargs)\n\n            return websocket_wrapper\n\n        elif is_async_callable(func):\n            # Handle async request/response functions.\n            @functools.wraps(func)\n            async def async_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Any:\n                request = kwargs.get(\"request\", args[idx] if idx < len(args) else None)\n                assert isinstance(request, Request)\n\n                if not has_required_scope(request, scopes_list):\n                    if redirect is not None:\n                        orig_request_qparam = urlencode({\"next\": str(request.url)})\n                        next_url = f\"{request.url_for(redirect)}?{orig_request_qparam}\"\n                        return RedirectResponse(url=next_url, status_code=303)\n                    raise HTTPException(status_code=status_code)\n                return await func(*args, **kwargs)\n\n            return async_wrapper\n\n        else:\n            # Handle sync request/response functions.\n            @functools.wraps(func)\n            def sync_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Any:\n                request = kwargs.get(\"request\", args[idx] if idx < len(args) else None)\n                assert isinstance(request, Request)\n\n                if not has_required_scope(request, scopes_list):\n                    if redirect is not None:\n                        orig_request_qparam = urlencode({\"next\": str(request.url)})\n                        next_url = f\"{request.url_for(redirect)}?{orig_request_qparam}\"\n                        return RedirectResponse(url=next_url, status_code=303)\n                    raise HTTPException(status_code=status_code)\n                return func(*args, **kwargs)\n\n            return sync_wrapper\n\n    return decorator\n\n\nclass AuthenticationError(Exception):\n    pass\n\n\nclass AuthenticationBackend:\n    async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, BaseUser] | None:\n        raise NotImplementedError()  # pragma: no cover\n\n\nclass AuthCredentials:\n    def __init__(self, scopes: Sequence[str] | None = None):\n        self.scopes = [] if scopes is None else list(scopes)\n\n\nclass BaseUser:\n    @property\n    def is_authenticated(self) -> bool:\n        raise NotImplementedError()  # pragma: no cover\n\n    @property\n    def display_name(self) -> str:\n        raise NotImplementedError()  # pragma: no cover\n\n    @property\n    def identity(self) -> str:\n        raise NotImplementedError()  # pragma: no cover\n\n\nclass SimpleUser(BaseUser):\n    def __init__(self, username: str) -> None:\n        self.username = username\n\n    @property\n    def is_authenticated(self) -> bool:\n        return True\n\n    @property\n    def display_name(self) -> str:\n        return self.username\n\n\nclass UnauthenticatedUser(BaseUser):\n    @property\n    def is_authenticated(self) -> bool:\n        return False\n\n    @property\n    def display_name(self) -> str:\n        return \"\"\n"
  },
  {
    "path": "starlette/background.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable, Sequence\nfrom typing import Any, ParamSpec\n\nfrom starlette._utils import is_async_callable\nfrom starlette.concurrency import run_in_threadpool\n\nP = ParamSpec(\"P\")\n\n\nclass BackgroundTask:\n    def __init__(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None:\n        self.func = func\n        self.args = args\n        self.kwargs = kwargs\n        self.is_async = is_async_callable(func)\n\n    async def __call__(self) -> None:\n        if self.is_async:\n            await self.func(*self.args, **self.kwargs)\n        else:\n            await run_in_threadpool(self.func, *self.args, **self.kwargs)\n\n\nclass BackgroundTasks(BackgroundTask):\n    def __init__(self, tasks: Sequence[BackgroundTask] | None = None):\n        self.tasks = list(tasks) if tasks else []\n\n    def add_task(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None:\n        task = BackgroundTask(func, *args, **kwargs)\n        self.tasks.append(task)\n\n    async def __call__(self) -> None:\n        for task in self.tasks:\n            await task()\n"
  },
  {
    "path": "starlette/concurrency.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport warnings\nfrom collections.abc import AsyncIterator, Callable, Coroutine, Iterable, Iterator\nfrom typing import ParamSpec, TypeVar\n\nimport anyio.to_thread\n\nP = ParamSpec(\"P\")\nT = TypeVar(\"T\")\n\n\nasync def run_until_first_complete(*args: tuple[Callable, dict]) -> None:  # type: ignore[type-arg]\n    warnings.warn(\n        \"run_until_first_complete is deprecated and will be removed in a future version.\",\n        DeprecationWarning,\n    )\n\n    async with anyio.create_task_group() as task_group:\n\n        async def run(func: Callable[[], Coroutine]) -> None:  # type: ignore[type-arg]\n            await func()\n            task_group.cancel_scope.cancel()\n\n        for func, kwargs in args:\n            task_group.start_soon(run, functools.partial(func, **kwargs))\n\n\nasync def run_in_threadpool(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:\n    func = functools.partial(func, *args, **kwargs)\n    return await anyio.to_thread.run_sync(func)\n\n\nclass _StopIteration(Exception):\n    pass\n\n\ndef _next(iterator: Iterator[T]) -> T:\n    # We can't raise `StopIteration` from within the threadpool iterator\n    # and catch it outside that context, so we coerce them into a different\n    # exception type.\n    try:\n        return next(iterator)\n    except StopIteration:\n        raise _StopIteration\n\n\nasync def iterate_in_threadpool(\n    iterator: Iterable[T],\n) -> AsyncIterator[T]:\n    as_iterator = iter(iterator)\n    while True:\n        try:\n            yield await anyio.to_thread.run_sync(_next, as_iterator)\n        except _StopIteration:\n            break\n"
  },
  {
    "path": "starlette/config.py",
    "content": "from __future__ import annotations\n\nimport os\nimport warnings\nfrom collections.abc import Callable, Iterator, Mapping, MutableMapping\nfrom pathlib import Path\nfrom typing import Any, TypeVar, overload\n\n\nclass undefined:\n    pass\n\n\nclass EnvironError(Exception):\n    pass\n\n\nclass Environ(MutableMapping[str, str]):\n    def __init__(self, environ: MutableMapping[str, str] = os.environ):\n        self._environ = environ\n        self._has_been_read: set[str] = set()\n\n    def __getitem__(self, key: str) -> str:\n        self._has_been_read.add(key)\n        return self._environ.__getitem__(key)\n\n    def __setitem__(self, key: str, value: str) -> None:\n        if key in self._has_been_read:\n            raise EnvironError(f\"Attempting to set environ['{key}'], but the value has already been read.\")\n        self._environ.__setitem__(key, value)\n\n    def __delitem__(self, key: str) -> None:\n        if key in self._has_been_read:\n            raise EnvironError(f\"Attempting to delete environ['{key}'], but the value has already been read.\")\n        self._environ.__delitem__(key)\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self._environ)\n\n    def __len__(self) -> int:\n        return len(self._environ)\n\n\nenviron = Environ()\n\nT = TypeVar(\"T\")\n\n\nclass Config:\n    def __init__(\n        self,\n        env_file: str | Path | None = None,\n        environ: Mapping[str, str] = environ,\n        env_prefix: str = \"\",\n        encoding: str = \"utf-8\",\n    ) -> None:\n        self.environ = environ\n        self.env_prefix = env_prefix\n        self.file_values: dict[str, str] = {}\n        if env_file is not None:\n            if not os.path.isfile(env_file):\n                warnings.warn(f\"Config file '{env_file}' not found.\")\n            else:\n                self.file_values = self._read_file(env_file, encoding)\n\n    @overload\n    def __call__(self, key: str, *, default: None) -> str | None: ...\n\n    @overload\n    def __call__(self, key: str, cast: type[T], default: T = ...) -> T: ...\n\n    @overload\n    def __call__(self, key: str, cast: type[str] = ..., default: str = ...) -> str: ...\n\n    @overload\n    def __call__(\n        self,\n        key: str,\n        cast: Callable[[Any], T] = ...,\n        default: Any = ...,\n    ) -> T: ...\n\n    @overload\n    def __call__(self, key: str, cast: type[str] = ..., default: T = ...) -> T | str: ...\n\n    def __call__(\n        self,\n        key: str,\n        cast: Callable[[Any], Any] | None = None,\n        default: Any = undefined,\n    ) -> Any:\n        return self.get(key, cast, default)\n\n    def get(\n        self,\n        key: str,\n        cast: Callable[[Any], Any] | None = None,\n        default: Any = undefined,\n    ) -> Any:\n        key = self.env_prefix + key\n        if key in self.environ:\n            value = self.environ[key]\n            return self._perform_cast(key, value, cast)\n        if key in self.file_values:\n            value = self.file_values[key]\n            return self._perform_cast(key, value, cast)\n        if default is not undefined:\n            return self._perform_cast(key, default, cast)\n        raise KeyError(f\"Config '{key}' is missing, and has no default.\")\n\n    def _read_file(self, file_name: str | Path, encoding: str) -> dict[str, str]:\n        file_values: dict[str, str] = {}\n        with open(file_name, encoding=encoding) as input_file:\n            for line in input_file.readlines():\n                line = line.strip()\n                if \"=\" in line and not line.startswith(\"#\"):\n                    key, value = line.split(\"=\", 1)\n                    key = key.strip()\n                    value = value.strip().strip(\"\\\"'\")\n                    file_values[key] = value\n        return file_values\n\n    def _perform_cast(\n        self,\n        key: str,\n        value: Any,\n        cast: Callable[[Any], Any] | None = None,\n    ) -> Any:\n        if cast is None or value is None:\n            return value\n        elif cast is bool and isinstance(value, str):\n            mapping = {\"true\": True, \"1\": True, \"false\": False, \"0\": False}\n            value = value.lower()\n            if value not in mapping:\n                raise ValueError(f\"Config '{key}' has value '{value}'. Not a valid bool.\")\n            return mapping[value]\n        try:\n            return cast(value)\n        except (TypeError, ValueError):\n            raise ValueError(f\"Config '{key}' has value '{value}'. Not a valid {cast.__name__}.\")\n"
  },
  {
    "path": "starlette/convertors.py",
    "content": "from __future__ import annotations\n\nimport math\nimport uuid\nfrom typing import Any, ClassVar, Generic, TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass Convertor(Generic[T]):\n    regex: ClassVar[str] = \"\"\n\n    def convert(self, value: str) -> T:\n        raise NotImplementedError()  # pragma: no cover\n\n    def to_string(self, value: T) -> str:\n        raise NotImplementedError()  # pragma: no cover\n\n\nclass StringConvertor(Convertor[str]):\n    regex = \"[^/]+\"\n\n    def convert(self, value: str) -> str:\n        return value\n\n    def to_string(self, value: str) -> str:\n        value = str(value)\n        assert \"/\" not in value, \"May not contain path separators\"\n        assert value, \"Must not be empty\"\n        return value\n\n\nclass PathConvertor(Convertor[str]):\n    regex = \".*\"\n\n    def convert(self, value: str) -> str:\n        return str(value)\n\n    def to_string(self, value: str) -> str:\n        return str(value)\n\n\nclass IntegerConvertor(Convertor[int]):\n    regex = \"[0-9]+\"\n\n    def convert(self, value: str) -> int:\n        return int(value)\n\n    def to_string(self, value: int) -> str:\n        value = int(value)\n        assert value >= 0, \"Negative integers are not supported\"\n        return str(value)\n\n\nclass FloatConvertor(Convertor[float]):\n    regex = r\"[0-9]+(\\.[0-9]+)?\"\n\n    def convert(self, value: str) -> float:\n        return float(value)\n\n    def to_string(self, value: float) -> str:\n        value = float(value)\n        assert value >= 0.0, \"Negative floats are not supported\"\n        assert not math.isnan(value), \"NaN values are not supported\"\n        assert not math.isinf(value), \"Infinite values are not supported\"\n        return (\"%0.20f\" % value).rstrip(\"0\").rstrip(\".\")\n\n\nclass UUIDConvertor(Convertor[uuid.UUID]):\n    regex = \"[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}\"\n\n    def convert(self, value: str) -> uuid.UUID:\n        return uuid.UUID(value)\n\n    def to_string(self, value: uuid.UUID) -> str:\n        return str(value)\n\n\nCONVERTOR_TYPES: dict[str, Convertor[Any]] = {\n    \"str\": StringConvertor(),\n    \"path\": PathConvertor(),\n    \"int\": IntegerConvertor(),\n    \"float\": FloatConvertor(),\n    \"uuid\": UUIDConvertor(),\n}\n\n\ndef register_url_convertor(key: str, convertor: Convertor[Any]) -> None:\n    CONVERTOR_TYPES[key] = convertor\n"
  },
  {
    "path": "starlette/datastructures.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, MutableMapping, Sequence, ValuesView\nfrom shlex import shlex\nfrom typing import (\n    Any,\n    BinaryIO,\n    NamedTuple,\n    TypeVar,\n    cast,\n)\nfrom urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit\n\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.types import Scope\n\n\nclass Address(NamedTuple):\n    host: str\n    port: int\n\n\n_KeyType = TypeVar(\"_KeyType\")\n# Mapping keys are invariant but their values are covariant since\n# you can only read them\n# that is, you can't do `Mapping[str, Animal]()[\"fido\"] = Dog()`\n_CovariantValueType = TypeVar(\"_CovariantValueType\", covariant=True)\n\n\nclass URL:\n    def __init__(\n        self,\n        url: str = \"\",\n        scope: Scope | None = None,\n        **components: Any,\n    ) -> None:\n        if scope is not None:\n            assert not url, 'Cannot set both \"url\" and \"scope\".'\n            assert not components, 'Cannot set both \"scope\" and \"**components\".'\n            scheme = scope.get(\"scheme\", \"http\")\n            server = scope.get(\"server\", None)\n            path = scope[\"path\"]\n            query_string = scope.get(\"query_string\", b\"\")\n\n            host_header = None\n            for key, value in scope[\"headers\"]:\n                if key == b\"host\":\n                    host_header = value.decode(\"latin-1\")\n                    break\n\n            if host_header is not None:\n                url = f\"{scheme}://{host_header}{path}\"\n            elif server is None:\n                url = path\n            else:\n                host, port = server\n                default_port = {\"http\": 80, \"https\": 443, \"ws\": 80, \"wss\": 443}[scheme]\n                if port == default_port:\n                    url = f\"{scheme}://{host}{path}\"\n                else:\n                    url = f\"{scheme}://{host}:{port}{path}\"\n\n            if query_string:\n                url += \"?\" + query_string.decode()\n        elif components:\n            assert not url, 'Cannot set both \"url\" and \"**components\".'\n            url = URL(\"\").replace(**components).components.geturl()\n\n        self._url = url\n\n    @property\n    def components(self) -> SplitResult:\n        if not hasattr(self, \"_components\"):\n            self._components = urlsplit(self._url)\n        return self._components\n\n    @property\n    def scheme(self) -> str:\n        return self.components.scheme\n\n    @property\n    def netloc(self) -> str:\n        return self.components.netloc\n\n    @property\n    def path(self) -> str:\n        return self.components.path\n\n    @property\n    def query(self) -> str:\n        return self.components.query\n\n    @property\n    def fragment(self) -> str:\n        return self.components.fragment\n\n    @property\n    def username(self) -> None | str:\n        return self.components.username\n\n    @property\n    def password(self) -> None | str:\n        return self.components.password\n\n    @property\n    def hostname(self) -> None | str:\n        return self.components.hostname\n\n    @property\n    def port(self) -> int | None:\n        return self.components.port\n\n    @property\n    def is_secure(self) -> bool:\n        return self.scheme in (\"https\", \"wss\")\n\n    def replace(self, **kwargs: Any) -> URL:\n        if \"username\" in kwargs or \"password\" in kwargs or \"hostname\" in kwargs or \"port\" in kwargs:\n            hostname = kwargs.pop(\"hostname\", None)\n            port = kwargs.pop(\"port\", self.port)\n            username = kwargs.pop(\"username\", self.username)\n            password = kwargs.pop(\"password\", self.password)\n\n            if hostname is None:\n                netloc = self.netloc\n                _, _, hostname = netloc.rpartition(\"@\")\n\n                if hostname[-1] != \"]\":\n                    hostname = hostname.rsplit(\":\", 1)[0]\n\n            netloc = hostname\n            if port is not None:\n                netloc += f\":{port}\"\n            if username is not None:\n                userpass = username\n                if password is not None:\n                    userpass += f\":{password}\"\n                netloc = f\"{userpass}@{netloc}\"\n\n            kwargs[\"netloc\"] = netloc\n\n        components = self.components._replace(**kwargs)\n        return self.__class__(components.geturl())\n\n    def include_query_params(self, **kwargs: Any) -> URL:\n        params = MultiDict(parse_qsl(self.query, keep_blank_values=True))\n        params.update({str(key): str(value) for key, value in kwargs.items()})\n        query = urlencode(params.multi_items())\n        return self.replace(query=query)\n\n    def replace_query_params(self, **kwargs: Any) -> URL:\n        query = urlencode([(str(key), str(value)) for key, value in kwargs.items()])\n        return self.replace(query=query)\n\n    def remove_query_params(self, keys: str | Sequence[str]) -> URL:\n        if isinstance(keys, str):\n            keys = [keys]\n        params = MultiDict(parse_qsl(self.query, keep_blank_values=True))\n        for key in keys:\n            params.pop(key, None)\n        query = urlencode(params.multi_items())\n        return self.replace(query=query)\n\n    def __eq__(self, other: Any) -> bool:\n        return str(self) == str(other)\n\n    def __str__(self) -> str:\n        return self._url\n\n    def __repr__(self) -> str:\n        url = str(self)\n        if self.password:\n            url = str(self.replace(password=\"********\"))\n        return f\"{self.__class__.__name__}({repr(url)})\"\n\n\nclass URLPath(str):\n    \"\"\"\n    A URL path string that may also hold an associated protocol and/or host.\n    Used by the routing to return `url_path_for` matches.\n    \"\"\"\n\n    def __new__(cls, path: str, protocol: str = \"\", host: str = \"\") -> URLPath:\n        assert protocol in (\"http\", \"websocket\", \"\")\n        return str.__new__(cls, path)\n\n    def __init__(self, path: str, protocol: str = \"\", host: str = \"\") -> None:\n        self.protocol = protocol\n        self.host = host\n\n    def make_absolute_url(self, base_url: str | URL) -> URL:\n        if isinstance(base_url, str):\n            base_url = URL(base_url)\n        if self.protocol:\n            scheme = {\n                \"http\": {True: \"https\", False: \"http\"},\n                \"websocket\": {True: \"wss\", False: \"ws\"},\n            }[self.protocol][base_url.is_secure]\n        else:\n            scheme = base_url.scheme\n\n        netloc = self.host or base_url.netloc\n        path = base_url.path.rstrip(\"/\") + str(self)\n        return URL(scheme=scheme, netloc=netloc, path=path)\n\n\nclass Secret:\n    \"\"\"\n    Holds a string value that should not be revealed in tracebacks etc.\n    You should cast the value to `str` at the point it is required.\n    \"\"\"\n\n    def __init__(self, value: str):\n        self._value = value\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return f\"{class_name}('**********')\"\n\n    def __str__(self) -> str:\n        return self._value\n\n    def __bool__(self) -> bool:\n        return bool(self._value)\n\n\nclass CommaSeparatedStrings(Sequence[str]):\n    def __init__(self, value: str | Sequence[str]):\n        if isinstance(value, str):\n            splitter = shlex(value, posix=True)\n            splitter.whitespace = \",\"\n            splitter.whitespace_split = True\n            self._items = [item.strip() for item in splitter]\n        else:\n            self._items = list(value)\n\n    def __len__(self) -> int:\n        return len(self._items)\n\n    def __getitem__(self, index: int | slice) -> Any:\n        return self._items[index]\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self._items)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        items = [item for item in self]\n        return f\"{class_name}({items!r})\"\n\n    def __str__(self) -> str:\n        return \", \".join(repr(item) for item in self)\n\n\nclass ImmutableMultiDict(Mapping[_KeyType, _CovariantValueType]):\n    _dict: dict[_KeyType, _CovariantValueType]\n\n    def __init__(\n        self,\n        *args: ImmutableMultiDict[_KeyType, _CovariantValueType]\n        | Mapping[_KeyType, _CovariantValueType]\n        | Iterable[tuple[_KeyType, _CovariantValueType]],\n        **kwargs: Any,\n    ) -> None:\n        assert len(args) < 2, \"Too many arguments.\"\n\n        value: Any = args[0] if args else []\n        if kwargs:\n            value = ImmutableMultiDict(value).multi_items() + ImmutableMultiDict(kwargs).multi_items()\n\n        if not value:\n            _items: list[tuple[Any, Any]] = []\n        elif hasattr(value, \"multi_items\"):\n            value = cast(ImmutableMultiDict[_KeyType, _CovariantValueType], value)\n            _items = list(value.multi_items())\n        elif hasattr(value, \"items\"):\n            value = cast(Mapping[_KeyType, _CovariantValueType], value)\n            _items = list(value.items())\n        else:\n            value = cast(\"list[tuple[Any, Any]]\", value)\n            _items = list(value)\n\n        self._dict = {k: v for k, v in _items}\n        self._list = _items\n\n    def getlist(self, key: Any) -> list[_CovariantValueType]:\n        return [item_value for item_key, item_value in self._list if item_key == key]\n\n    def keys(self) -> KeysView[_KeyType]:\n        return self._dict.keys()\n\n    def values(self) -> ValuesView[_CovariantValueType]:\n        return self._dict.values()\n\n    def items(self) -> ItemsView[_KeyType, _CovariantValueType]:\n        return self._dict.items()\n\n    def multi_items(self) -> list[tuple[_KeyType, _CovariantValueType]]:\n        return list(self._list)\n\n    def __getitem__(self, key: _KeyType) -> _CovariantValueType:\n        return self._dict[key]\n\n    def __contains__(self, key: Any) -> bool:\n        return key in self._dict\n\n    def __iter__(self) -> Iterator[_KeyType]:\n        return iter(self.keys())\n\n    def __len__(self) -> int:\n        return len(self._dict)\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, self.__class__):\n            return False\n        return sorted(self._list) == sorted(other._list)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        items = self.multi_items()\n        return f\"{class_name}({items!r})\"\n\n\nclass MultiDict(ImmutableMultiDict[Any, Any]):\n    def __setitem__(self, key: Any, value: Any) -> None:\n        self.setlist(key, [value])\n\n    def __delitem__(self, key: Any) -> None:\n        self._list = [(k, v) for k, v in self._list if k != key]\n        del self._dict[key]\n\n    def pop(self, key: Any, default: Any = None) -> Any:\n        self._list = [(k, v) for k, v in self._list if k != key]\n        return self._dict.pop(key, default)\n\n    def popitem(self) -> tuple[Any, Any]:\n        key, value = self._dict.popitem()\n        self._list = [(k, v) for k, v in self._list if k != key]\n        return key, value\n\n    def poplist(self, key: Any) -> list[Any]:\n        values = [v for k, v in self._list if k == key]\n        self.pop(key)\n        return values\n\n    def clear(self) -> None:\n        self._dict.clear()\n        self._list.clear()\n\n    def setdefault(self, key: Any, default: Any = None) -> Any:\n        if key not in self:\n            self._dict[key] = default\n            self._list.append((key, default))\n\n        return self[key]\n\n    def setlist(self, key: Any, values: list[Any]) -> None:\n        if not values:\n            self.pop(key, None)\n        else:\n            existing_items = [(k, v) for (k, v) in self._list if k != key]\n            self._list = existing_items + [(key, value) for value in values]\n            self._dict[key] = values[-1]\n\n    def append(self, key: Any, value: Any) -> None:\n        self._list.append((key, value))\n        self._dict[key] = value\n\n    def update(\n        self,\n        *args: MultiDict | Mapping[Any, Any] | list[tuple[Any, Any]],\n        **kwargs: Any,\n    ) -> None:\n        value = MultiDict(*args, **kwargs)\n        existing_items = [(k, v) for (k, v) in self._list if k not in value.keys()]\n        self._list = existing_items + value.multi_items()\n        self._dict.update(value)\n\n\nclass QueryParams(ImmutableMultiDict[str, str]):\n    \"\"\"\n    An immutable multidict.\n    \"\"\"\n\n    def __init__(\n        self,\n        *args: ImmutableMultiDict[Any, Any] | Mapping[Any, Any] | list[tuple[Any, Any]] | str | bytes,\n        **kwargs: Any,\n    ) -> None:\n        assert len(args) < 2, \"Too many arguments.\"\n\n        value = args[0] if args else []\n\n        if isinstance(value, str):\n            super().__init__(parse_qsl(value, keep_blank_values=True), **kwargs)\n        elif isinstance(value, bytes):\n            super().__init__(parse_qsl(value.decode(\"latin-1\"), keep_blank_values=True), **kwargs)\n        else:\n            super().__init__(*args, **kwargs)  # type: ignore[arg-type]\n        self._list = [(str(k), str(v)) for k, v in self._list]\n        self._dict = {str(k): str(v) for k, v in self._dict.items()}\n\n    def __str__(self) -> str:\n        return urlencode(self._list)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        query_string = str(self)\n        return f\"{class_name}({query_string!r})\"\n\n\nclass UploadFile:\n    \"\"\"\n    An uploaded file included as part of the request data.\n    \"\"\"\n\n    def __init__(\n        self,\n        file: BinaryIO,\n        *,\n        size: int | None = None,\n        filename: str | None = None,\n        headers: Headers | None = None,\n    ) -> None:\n        self.filename = filename\n        self.file = file\n        self.size = size\n        self.headers = headers or Headers()\n\n        # Capture max size from SpooledTemporaryFile if one is provided. This slightly speeds up future checks.\n        # Note 0 means unlimited mirroring SpooledTemporaryFile's __init__\n        self._max_mem_size = getattr(self.file, \"_max_size\", 0)\n\n    @property\n    def content_type(self) -> str | None:\n        return self.headers.get(\"content-type\", None)\n\n    @property\n    def _in_memory(self) -> bool:\n        # check for SpooledTemporaryFile._rolled\n        rolled_to_disk = getattr(self.file, \"_rolled\", True)\n        return not rolled_to_disk\n\n    def _will_roll(self, size_to_add: int) -> bool:\n        # If we're not in_memory then we will always roll\n        if not self._in_memory:\n            return True\n\n        # Check for SpooledTemporaryFile._max_size\n        future_size = self.file.tell() + size_to_add\n        return bool(future_size > self._max_mem_size) if self._max_mem_size else False\n\n    async def write(self, data: bytes) -> None:\n        new_data_len = len(data)\n        if self.size is not None:\n            self.size += new_data_len\n\n        if self._will_roll(new_data_len):\n            await run_in_threadpool(self.file.write, data)\n        else:\n            self.file.write(data)\n\n    async def read(self, size: int = -1) -> bytes:\n        if self._in_memory:\n            return self.file.read(size)\n        return await run_in_threadpool(self.file.read, size)\n\n    async def seek(self, offset: int) -> None:\n        if self._in_memory:\n            self.file.seek(offset)\n        else:\n            await run_in_threadpool(self.file.seek, offset)\n\n    async def close(self) -> None:\n        if self._in_memory:\n            self.file.close()\n        else:\n            await run_in_threadpool(self.file.close)\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}(filename={self.filename!r}, size={self.size!r}, headers={self.headers!r})\"\n\n\nclass FormData(ImmutableMultiDict[str, UploadFile | str]):\n    \"\"\"\n    An immutable multidict, containing both file uploads and text input.\n    \"\"\"\n\n    def __init__(\n        self,\n        *args: FormData | Mapping[str, str | UploadFile] | list[tuple[str, str | UploadFile]],\n        **kwargs: str | UploadFile,\n    ) -> None:\n        super().__init__(*args, **kwargs)\n\n    async def close(self) -> None:\n        for key, value in self.multi_items():\n            if isinstance(value, UploadFile):\n                await value.close()\n\n\nclass Headers(Mapping[str, str]):\n    \"\"\"\n    An immutable, case-insensitive multidict.\n    \"\"\"\n\n    def __init__(\n        self,\n        headers: Mapping[str, str] | None = None,\n        raw: list[tuple[bytes, bytes]] | None = None,\n        scope: MutableMapping[str, Any] | None = None,\n    ) -> None:\n        self._list: list[tuple[bytes, bytes]] = []\n        if headers is not None:\n            assert raw is None, 'Cannot set both \"headers\" and \"raw\".'\n            assert scope is None, 'Cannot set both \"headers\" and \"scope\".'\n            self._list = [(key.lower().encode(\"latin-1\"), value.encode(\"latin-1\")) for key, value in headers.items()]\n        elif raw is not None:\n            assert scope is None, 'Cannot set both \"raw\" and \"scope\".'\n            self._list = raw\n        elif scope is not None:\n            # scope[\"headers\"] isn't necessarily a list\n            # it might be a tuple or other iterable\n            self._list = scope[\"headers\"] = list(scope[\"headers\"])\n\n    @property\n    def raw(self) -> list[tuple[bytes, bytes]]:\n        return list(self._list)\n\n    def keys(self) -> list[str]:  # type: ignore[override]\n        return [key.decode(\"latin-1\") for key, value in self._list]\n\n    def values(self) -> list[str]:  # type: ignore[override]\n        return [value.decode(\"latin-1\") for key, value in self._list]\n\n    def items(self) -> list[tuple[str, str]]:  # type: ignore[override]\n        return [(key.decode(\"latin-1\"), value.decode(\"latin-1\")) for key, value in self._list]\n\n    def getlist(self, key: str) -> list[str]:\n        get_header_key = key.lower().encode(\"latin-1\")\n        return [item_value.decode(\"latin-1\") for item_key, item_value in self._list if item_key == get_header_key]\n\n    def mutablecopy(self) -> MutableHeaders:\n        return MutableHeaders(raw=self._list[:])\n\n    def __getitem__(self, key: str) -> str:\n        get_header_key = key.lower().encode(\"latin-1\")\n        for header_key, header_value in self._list:\n            if header_key == get_header_key:\n                return header_value.decode(\"latin-1\")\n        raise KeyError(key)\n\n    def __contains__(self, key: Any) -> bool:\n        get_header_key = key.lower().encode(\"latin-1\")\n        for header_key, header_value in self._list:\n            if header_key == get_header_key:\n                return True\n        return False\n\n    def __iter__(self) -> Iterator[Any]:\n        return iter(self.keys())\n\n    def __len__(self) -> int:\n        return len(self._list)\n\n    def __eq__(self, other: Any) -> bool:\n        if not isinstance(other, Headers):\n            return False\n        return sorted(self._list) == sorted(other._list)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        as_dict = dict(self.items())\n        if len(as_dict) == len(self):\n            return f\"{class_name}({as_dict!r})\"\n        return f\"{class_name}(raw={self.raw!r})\"\n\n\nclass MutableHeaders(Headers):\n    def __setitem__(self, key: str, value: str) -> None:\n        \"\"\"\n        Set the header `key` to `value`, removing any duplicate entries.\n        Retains insertion order.\n        \"\"\"\n        set_key = key.lower().encode(\"latin-1\")\n        set_value = value.encode(\"latin-1\")\n\n        found_indexes: list[int] = []\n        for idx, (item_key, item_value) in enumerate(self._list):\n            if item_key == set_key:\n                found_indexes.append(idx)\n\n        for idx in reversed(found_indexes[1:]):\n            del self._list[idx]\n\n        if found_indexes:\n            idx = found_indexes[0]\n            self._list[idx] = (set_key, set_value)\n        else:\n            self._list.append((set_key, set_value))\n\n    def __delitem__(self, key: str) -> None:\n        \"\"\"\n        Remove the header `key`.\n        \"\"\"\n        del_key = key.lower().encode(\"latin-1\")\n\n        pop_indexes: list[int] = []\n        for idx, (item_key, item_value) in enumerate(self._list):\n            if item_key == del_key:\n                pop_indexes.append(idx)\n\n        for idx in reversed(pop_indexes):\n            del self._list[idx]\n\n    def __ior__(self, other: Mapping[str, str]) -> MutableHeaders:\n        if not isinstance(other, Mapping):\n            raise TypeError(f\"Expected a mapping but got {other.__class__.__name__}\")\n        self.update(other)\n        return self\n\n    def __or__(self, other: Mapping[str, str]) -> MutableHeaders:\n        if not isinstance(other, Mapping):\n            raise TypeError(f\"Expected a mapping but got {other.__class__.__name__}\")\n        new = self.mutablecopy()\n        new.update(other)\n        return new\n\n    @property\n    def raw(self) -> list[tuple[bytes, bytes]]:\n        return self._list\n\n    def setdefault(self, key: str, value: str) -> str:\n        \"\"\"\n        If the header `key` does not exist, then set it to `value`.\n        Returns the header value.\n        \"\"\"\n        set_key = key.lower().encode(\"latin-1\")\n        set_value = value.encode(\"latin-1\")\n\n        for idx, (item_key, item_value) in enumerate(self._list):\n            if item_key == set_key:\n                return item_value.decode(\"latin-1\")\n        self._list.append((set_key, set_value))\n        return value\n\n    def update(self, other: Mapping[str, str]) -> None:\n        for key, val in other.items():\n            self[key] = val\n\n    def append(self, key: str, value: str) -> None:\n        \"\"\"\n        Append a header, preserving any duplicate entries.\n        \"\"\"\n        append_key = key.lower().encode(\"latin-1\")\n        append_value = value.encode(\"latin-1\")\n        self._list.append((append_key, append_value))\n\n    def add_vary_header(self, vary: str) -> None:\n        existing = self.get(\"vary\")\n        if existing is not None:\n            vary = \", \".join([existing, vary])\n        self[\"vary\"] = vary\n\n\nclass State:\n    \"\"\"\n    An object that can be used to store arbitrary state.\n\n    Used for `request.state` and `app.state`.\n    \"\"\"\n\n    _state: dict[str, Any]\n\n    def __init__(self, state: dict[str, Any] | None = None):\n        if state is None:\n            state = {}\n        super().__setattr__(\"_state\", state)\n\n    def __setattr__(self, key: Any, value: Any) -> None:\n        self._state[key] = value\n\n    def __getattr__(self, key: Any) -> Any:\n        try:\n            return self._state[key]\n        except KeyError:\n            message = \"'{}' object has no attribute '{}'\"\n            raise AttributeError(message.format(self.__class__.__name__, key))\n\n    def __delattr__(self, key: Any) -> None:\n        del self._state[key]\n\n    def __getitem__(self, key: str) -> Any:\n        return self._state[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self._state[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self._state[key]\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self._state)\n\n    def __len__(self) -> int:\n        return len(self._state)\n"
  },
  {
    "path": "starlette/endpoints.py",
    "content": "from __future__ import annotations\n\nimport json\nfrom collections.abc import Callable, Generator\nfrom typing import Any, Literal\n\nfrom starlette import status\nfrom starlette._utils import is_async_callable\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\n\n\nclass HTTPEndpoint:\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"http\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n        self._allowed_methods = [\n            method\n            for method in (\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\")\n            if getattr(self, method.lower(), None) is not None\n        ]\n\n    def __await__(self) -> Generator[Any, None, None]:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        request = Request(self.scope, receive=self.receive)\n        handler_name = \"get\" if request.method == \"HEAD\" and not hasattr(self, \"head\") else request.method.lower()\n\n        handler: Callable[[Request], Any] = getattr(self, handler_name, self.method_not_allowed)\n        is_async = is_async_callable(handler)\n        if is_async:\n            response = await handler(request)\n        else:\n            response = await run_in_threadpool(handler, request)\n        await response(self.scope, self.receive, self.send)\n\n    async def method_not_allowed(self, request: Request) -> Response:\n        # If we're running inside a starlette application then raise an\n        # exception, so that the configurable exception handler can deal with\n        # returning the response. For plain ASGI apps, just return the response.\n        headers = {\"Allow\": \", \".join(self._allowed_methods)}\n        if \"app\" in self.scope:\n            raise HTTPException(status_code=405, headers=headers)\n        return PlainTextResponse(\"Method Not Allowed\", status_code=405, headers=headers)\n\n\nclass WebSocketEndpoint:\n    encoding: Literal[\"text\", \"bytes\", \"json\"] | None = None\n\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"websocket\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self) -> Generator[Any, None, None]:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        websocket = WebSocket(self.scope, receive=self.receive, send=self.send)\n        await self.on_connect(websocket)\n\n        close_code = status.WS_1000_NORMAL_CLOSURE\n\n        try:\n            while True:\n                message = await websocket.receive()\n                if message[\"type\"] == \"websocket.receive\":\n                    data = await self.decode(websocket, message)\n                    await self.on_receive(websocket, data)\n                elif message[\"type\"] == \"websocket.disconnect\":  # pragma: no branch\n                    close_code = int(message.get(\"code\") or status.WS_1000_NORMAL_CLOSURE)\n                    break\n        except Exception as exc:\n            close_code = status.WS_1011_INTERNAL_ERROR\n            raise exc\n        finally:\n            await self.on_disconnect(websocket, close_code)\n\n    async def decode(self, websocket: WebSocket, message: Message) -> Any:\n        if self.encoding == \"text\":\n            if \"text\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected text websocket messages, but got bytes\")\n            return message[\"text\"]\n\n        elif self.encoding == \"bytes\":\n            if \"bytes\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected bytes websocket messages, but got text\")\n            return message[\"bytes\"]\n\n        elif self.encoding == \"json\":\n            if message.get(\"text\") is not None:\n                text = message[\"text\"]\n            else:\n                text = message[\"bytes\"].decode(\"utf-8\")\n\n            try:\n                return json.loads(text)\n            except json.decoder.JSONDecodeError:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Malformed JSON data received.\")\n\n        assert self.encoding is None, f\"Unsupported 'encoding' attribute {self.encoding}\"\n        return message[\"text\"] if message.get(\"text\") else message[\"bytes\"]\n\n    async def on_connect(self, websocket: WebSocket) -> None:\n        \"\"\"Override to handle an incoming websocket connection\"\"\"\n        await websocket.accept()\n\n    async def on_receive(self, websocket: WebSocket, data: Any) -> None:\n        \"\"\"Override to handle an incoming websocket message\"\"\"\n\n    async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:\n        \"\"\"Override to handle a disconnecting websocket\"\"\"\n"
  },
  {
    "path": "starlette/exceptions.py",
    "content": "from __future__ import annotations\n\nimport http\nfrom collections.abc import Mapping\n\n\nclass HTTPException(Exception):\n    def __init__(self, status_code: int, detail: str | None = None, headers: Mapping[str, str] | None = None) -> None:\n        if detail is None:\n            detail = http.HTTPStatus(status_code).phrase\n        self.status_code = status_code\n        self.detail = detail\n        self.headers = headers\n\n    def __str__(self) -> str:\n        return f\"{self.status_code}: {self.detail}\"\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return f\"{class_name}(status_code={self.status_code!r}, detail={self.detail!r})\"\n\n\nclass WebSocketException(Exception):\n    def __init__(self, code: int, reason: str | None = None) -> None:\n        self.code = code\n        self.reason = reason or \"\"\n\n    def __str__(self) -> str:\n        return f\"{self.code}: {self.reason}\"\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return f\"{class_name}(code={self.code!r}, reason={self.reason!r})\"\n"
  },
  {
    "path": "starlette/formparsers.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import AsyncGenerator\nfrom dataclasses import dataclass, field\nfrom enum import Enum\nfrom tempfile import SpooledTemporaryFile\nfrom typing import TYPE_CHECKING\nfrom urllib.parse import unquote_plus\n\nfrom starlette.datastructures import FormData, Headers, UploadFile\n\nif TYPE_CHECKING:\n    import python_multipart as multipart\n    from python_multipart.multipart import MultipartCallbacks, QuerystringCallbacks, parse_options_header\nelse:\n    try:\n        try:\n            import python_multipart as multipart\n            from python_multipart.multipart import parse_options_header\n        except ModuleNotFoundError:  # pragma: no cover\n            import multipart\n            from multipart.multipart import parse_options_header\n    except ModuleNotFoundError:  # pragma: no cover\n        multipart = None\n        parse_options_header = None\n\n\nclass FormMessage(Enum):\n    FIELD_START = 1\n    FIELD_NAME = 2\n    FIELD_DATA = 3\n    FIELD_END = 4\n    END = 5\n\n\n@dataclass\nclass MultipartPart:\n    content_disposition: bytes | None = None\n    field_name: str = \"\"\n    data: bytearray = field(default_factory=bytearray)\n    file: UploadFile | None = None\n    item_headers: list[tuple[bytes, bytes]] = field(default_factory=list)\n\n\ndef _user_safe_decode(src: bytes | bytearray, codec: str) -> str:\n    try:\n        return src.decode(codec)\n    except (UnicodeDecodeError, LookupError):\n        return src.decode(\"latin-1\")\n\n\nclass MultiPartException(Exception):\n    def __init__(self, message: str) -> None:\n        self.message = message\n\n\nclass FormParser:\n    def __init__(self, headers: Headers, stream: AsyncGenerator[bytes, None]) -> None:\n        assert multipart is not None, \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.messages: list[tuple[FormMessage, bytes]] = []\n\n    def on_field_start(self) -> None:\n        message = (FormMessage.FIELD_START, b\"\")\n        self.messages.append(message)\n\n    def on_field_name(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_NAME, data[start:end])\n        self.messages.append(message)\n\n    def on_field_data(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_field_end(self) -> None:\n        message = (FormMessage.FIELD_END, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        message = (FormMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> FormData:\n        # Callbacks dictionary.\n        callbacks: QuerystringCallbacks = {\n            \"on_field_start\": self.on_field_start,\n            \"on_field_name\": self.on_field_name,\n            \"on_field_data\": self.on_field_data,\n            \"on_field_end\": self.on_field_end,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.QuerystringParser(callbacks)\n        field_name = bytearray()\n        field_value = bytearray()\n\n        items: list[tuple[str, str | UploadFile]] = []\n\n        # Feed the parser with data from the request.\n        async for chunk in self.stream:\n            if chunk:\n                parser.write(chunk)\n            else:\n                parser.finalize()\n            messages = list(self.messages)\n            self.messages.clear()\n            for message_type, message_bytes in messages:\n                if message_type == FormMessage.FIELD_START:\n                    field_name = bytearray()\n                    field_value = bytearray()\n                elif message_type == FormMessage.FIELD_NAME:\n                    field_name.extend(message_bytes)\n                elif message_type == FormMessage.FIELD_DATA:\n                    field_value.extend(message_bytes)\n                elif message_type == FormMessage.FIELD_END:\n                    name = unquote_plus(field_name.decode(\"latin-1\"))\n                    value = unquote_plus(field_value.decode(\"latin-1\"))\n                    items.append((name, value))\n\n        return FormData(items)\n\n\nclass MultiPartParser:\n    spool_max_size = 1024 * 1024  # 1MB\n    \"\"\"The maximum size of the spooled temporary file used to store file data.\"\"\"\n    max_part_size = 1024 * 1024  # 1MB\n    \"\"\"The maximum size of a part in the multipart request.\"\"\"\n\n    def __init__(\n        self,\n        headers: Headers,\n        stream: AsyncGenerator[bytes, None],\n        *,\n        max_files: int | float = 1000,\n        max_fields: int | float = 1000,\n        max_part_size: int = 1024 * 1024,  # 1MB\n    ) -> None:\n        assert multipart is not None, \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.max_files = max_files\n        self.max_fields = max_fields\n        self.items: list[tuple[str, str | UploadFile]] = []\n        self._current_files = 0\n        self._current_fields = 0\n        self._current_partial_header_name: bytes = b\"\"\n        self._current_partial_header_value: bytes = b\"\"\n        self._current_part = MultipartPart()\n        self._charset = \"\"\n        self._file_parts_to_write: list[tuple[MultipartPart, bytes]] = []\n        self._file_parts_to_finish: list[MultipartPart] = []\n        self._files_to_close_on_error: list[SpooledTemporaryFile[bytes]] = []\n        self.max_part_size = max_part_size\n\n    def on_part_begin(self) -> None:\n        self._current_part = MultipartPart()\n\n    def on_part_data(self, data: bytes, start: int, end: int) -> None:\n        message_bytes = data[start:end]\n        if self._current_part.file is None:\n            if len(self._current_part.data) + len(message_bytes) > self.max_part_size:\n                raise MultiPartException(f\"Part exceeded maximum size of {int(self.max_part_size / 1024)}KB.\")\n            self._current_part.data.extend(message_bytes)\n        else:\n            self._file_parts_to_write.append((self._current_part, message_bytes))\n\n    def on_part_end(self) -> None:\n        if self._current_part.file is None:\n            self.items.append(\n                (\n                    self._current_part.field_name,\n                    _user_safe_decode(self._current_part.data, self._charset),\n                )\n            )\n        else:\n            self._file_parts_to_finish.append(self._current_part)\n            # The file can be added to the items right now even though it's not\n            # finished yet, because it will be finished in the `parse()` method, before\n            # self.items is used in the return value.\n            self.items.append((self._current_part.field_name, self._current_part.file))\n\n    def on_header_field(self, data: bytes, start: int, end: int) -> None:\n        self._current_partial_header_name += data[start:end]\n\n    def on_header_value(self, data: bytes, start: int, end: int) -> None:\n        self._current_partial_header_value += data[start:end]\n\n    def on_header_end(self) -> None:\n        field = self._current_partial_header_name.lower()\n        if field == b\"content-disposition\":\n            self._current_part.content_disposition = self._current_partial_header_value\n        self._current_part.item_headers.append((field, self._current_partial_header_value))\n        self._current_partial_header_name = b\"\"\n        self._current_partial_header_value = b\"\"\n\n    def on_headers_finished(self) -> None:\n        disposition, options = parse_options_header(self._current_part.content_disposition)\n        try:\n            self._current_part.field_name = _user_safe_decode(options[b\"name\"], self._charset)\n        except KeyError:\n            raise MultiPartException('The Content-Disposition header field \"name\" must be provided.')\n        if b\"filename\" in options:\n            self._current_files += 1\n            if self._current_files > self.max_files:\n                raise MultiPartException(f\"Too many files. Maximum number of files is {self.max_files}.\")\n            filename = _user_safe_decode(options[b\"filename\"], self._charset)\n            tempfile = SpooledTemporaryFile(max_size=self.spool_max_size)\n            self._files_to_close_on_error.append(tempfile)\n            self._current_part.file = UploadFile(\n                file=tempfile,  # type: ignore[arg-type]\n                size=0,\n                filename=filename,\n                headers=Headers(raw=self._current_part.item_headers),\n            )\n        else:\n            self._current_fields += 1\n            if self._current_fields > self.max_fields:\n                raise MultiPartException(f\"Too many fields. Maximum number of fields is {self.max_fields}.\")\n            self._current_part.file = None\n\n    def on_end(self) -> None:\n        pass\n\n    async def parse(self) -> FormData:\n        # Parse the Content-Type header to get the multipart boundary.\n        _, params = parse_options_header(self.headers[\"Content-Type\"])\n        charset = params.get(b\"charset\", \"utf-8\")\n        if isinstance(charset, bytes):\n            charset = charset.decode(\"latin-1\")\n        self._charset = charset\n        try:\n            boundary = params[b\"boundary\"]\n        except KeyError:\n            raise MultiPartException(\"Missing boundary in multipart.\")\n\n        # Callbacks dictionary.\n        callbacks: MultipartCallbacks = {\n            \"on_part_begin\": self.on_part_begin,\n            \"on_part_data\": self.on_part_data,\n            \"on_part_end\": self.on_part_end,\n            \"on_header_field\": self.on_header_field,\n            \"on_header_value\": self.on_header_value,\n            \"on_header_end\": self.on_header_end,\n            \"on_headers_finished\": self.on_headers_finished,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.MultipartParser(boundary, callbacks)\n        try:\n            # Feed the parser with data from the request.\n            async for chunk in self.stream:\n                parser.write(chunk)\n                # Write file data, it needs to use await with the UploadFile methods\n                # that call the corresponding file methods *in a threadpool*,\n                # otherwise, if they were called directly in the callback methods above\n                # (regular, non-async functions), that would block the event loop in\n                # the main thread.\n                for part, data in self._file_parts_to_write:\n                    assert part.file  # for type checkers\n                    await part.file.write(data)\n                for part in self._file_parts_to_finish:\n                    assert part.file  # for type checkers\n                    await part.file.seek(0)\n                self._file_parts_to_write.clear()\n                self._file_parts_to_finish.clear()\n            parser.finalize()\n        except MultiPartException as exc:\n            # Close all the files if there was an error.\n            for file in self._files_to_close_on_error:\n                file.close()\n            raise exc\n\n        return FormData(self.items)\n"
  },
  {
    "path": "starlette/middleware/__init__.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Awaitable, Callable, Iterator\nfrom typing import Any, ParamSpec, Protocol\n\nP = ParamSpec(\"P\")\n\n\n_Scope = Any\n_Receive = Callable[[], Awaitable[Any]]\n_Send = Callable[[Any], Awaitable[None]]\n# Since `starlette.types.ASGIApp` type differs from `ASGIApplication` from `asgiref`\n# we need to define a more permissive version of ASGIApp that doesn't cause type errors.\n_ASGIApp = Callable[[_Scope, _Receive, _Send], Awaitable[None]]\n\n\nclass _MiddlewareFactory(Protocol[P]):\n    def __call__(self, app: _ASGIApp, /, *args: P.args, **kwargs: P.kwargs) -> _ASGIApp: ...  # pragma: no cover\n\n\nclass Middleware:\n    def __init__(self, cls: _MiddlewareFactory[P], *args: P.args, **kwargs: P.kwargs) -> None:\n        self.cls = cls\n        self.args = args\n        self.kwargs = kwargs\n\n    def __iter__(self) -> Iterator[Any]:\n        as_tuple = (self.cls, self.args, self.kwargs)\n        return iter(as_tuple)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        args_strings = [f\"{value!r}\" for value in self.args]\n        option_strings = [f\"{key}={value!r}\" for key, value in self.kwargs.items()]\n        name = getattr(self.cls, \"__name__\", \"\")\n        args_repr = \", \".join([name] + args_strings + option_strings)\n        return f\"{class_name}({args_repr})\"\n"
  },
  {
    "path": "starlette/middleware/authentication.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable\n\nfrom starlette.authentication import (\n    AuthCredentials,\n    AuthenticationBackend,\n    AuthenticationError,\n    UnauthenticatedUser,\n)\nfrom starlette.requests import HTTPConnection\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\n\nclass AuthenticationMiddleware:\n    def __init__(\n        self,\n        app: ASGIApp,\n        backend: AuthenticationBackend,\n        on_error: Callable[[HTTPConnection, AuthenticationError], Response] | None = None,\n    ) -> None:\n        self.app = app\n        self.backend = backend\n        self.on_error: Callable[[HTTPConnection, AuthenticationError], Response] = (\n            on_error if on_error is not None else self.default_on_error\n        )\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] not in [\"http\", \"websocket\"]:\n            await self.app(scope, receive, send)\n            return\n\n        conn = HTTPConnection(scope)\n        try:\n            auth_result = await self.backend.authenticate(conn)\n        except AuthenticationError as exc:\n            response = self.on_error(conn, exc)\n            if scope[\"type\"] == \"websocket\":\n                await send({\"type\": \"websocket.close\", \"code\": 1000})\n            else:\n                await response(scope, receive, send)\n            return\n\n        if auth_result is None:\n            auth_result = AuthCredentials(), UnauthenticatedUser()\n        scope[\"auth\"], scope[\"user\"] = auth_result\n        await self.app(scope, receive, send)\n\n    @staticmethod\n    def default_on_error(conn: HTTPConnection, exc: Exception) -> Response:\n        return PlainTextResponse(str(exc), status_code=400)\n"
  },
  {
    "path": "starlette/middleware/base.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Callable, Mapping, MutableMapping\nfrom typing import Any, TypeVar\n\nimport anyio\n\nfrom starlette._utils import collapse_excgroups\nfrom starlette.requests import ClientDisconnect, Request\nfrom starlette.responses import Response\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\n\nRequestResponseEndpoint = Callable[[Request], Awaitable[Response]]\nDispatchFunction = Callable[[Request, RequestResponseEndpoint], Awaitable[Response]]\nBodyStreamGenerator = AsyncGenerator[bytes | MutableMapping[str, Any], None]\nAsyncContentStream = AsyncIterable[str | bytes | memoryview | MutableMapping[str, Any]]\nT = TypeVar(\"T\")\n\n\nclass _CachedRequest(Request):\n    \"\"\"\n    If the user calls Request.body() from their dispatch function\n    we cache the entire request body in memory and pass that to downstream middlewares,\n    but if they call Request.stream() then all we do is send an\n    empty body so that downstream things don't hang forever.\n    \"\"\"\n\n    def __init__(self, scope: Scope, receive: Receive):\n        super().__init__(scope, receive)\n        self._wrapped_rcv_disconnected = False\n        self._wrapped_rcv_consumed = False\n        self._wrapped_rc_stream = self.stream()\n\n    async def wrapped_receive(self) -> Message:\n        # wrapped_rcv state 1: disconnected\n        if self._wrapped_rcv_disconnected:\n            # we've already sent a disconnect to the downstream app\n            # we don't need to wait to get another one\n            # (although most ASGI servers will just keep sending it)\n            return {\"type\": \"http.disconnect\"}\n        # wrapped_rcv state 1: consumed but not yet disconnected\n        if self._wrapped_rcv_consumed:\n            # since the downstream app has consumed us all that is left\n            # is to send it a disconnect\n            if self._is_disconnected:\n                # the middleware has already seen the disconnect\n                # since we know the client is disconnected no need to wait\n                # for the message\n                self._wrapped_rcv_disconnected = True\n                return {\"type\": \"http.disconnect\"}\n            # we don't know yet if the client is disconnected or not\n            # so we'll wait until we get that message\n            msg = await self.receive()\n            if msg[\"type\"] != \"http.disconnect\":  # pragma: no cover\n                # at this point a disconnect is all that we should be receiving\n                # if we get something else, things went wrong somewhere\n                raise RuntimeError(f\"Unexpected message received: {msg['type']}\")\n            self._wrapped_rcv_disconnected = True\n            return msg\n\n        # wrapped_rcv state 3: not yet consumed\n        if getattr(self, \"_body\", None) is not None:\n            # body() was called, we return it even if the client disconnected\n            self._wrapped_rcv_consumed = True\n            return {\n                \"type\": \"http.request\",\n                \"body\": self._body,\n                \"more_body\": False,\n            }\n        elif self._stream_consumed:\n            # stream() was called to completion\n            # return an empty body so that downstream apps don't hang\n            # waiting for a disconnect\n            self._wrapped_rcv_consumed = True\n            return {\n                \"type\": \"http.request\",\n                \"body\": b\"\",\n                \"more_body\": False,\n            }\n        else:\n            # body() was never called and stream() wasn't consumed\n            try:\n                stream = self.stream()\n                chunk = await stream.__anext__()\n                self._wrapped_rcv_consumed = self._stream_consumed\n                return {\n                    \"type\": \"http.request\",\n                    \"body\": chunk,\n                    \"more_body\": not self._stream_consumed,\n                }\n            except ClientDisconnect:\n                self._wrapped_rcv_disconnected = True\n                return {\"type\": \"http.disconnect\"}\n\n\nclass BaseHTTPMiddleware:\n    def __init__(self, app: ASGIApp, dispatch: DispatchFunction | None = None) -> None:\n        self.app = app\n        self.dispatch_func = self.dispatch if dispatch is None else dispatch\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        request = _CachedRequest(scope, receive)\n        wrapped_receive = request.wrapped_receive\n        response_sent = anyio.Event()\n        app_exc: Exception | None = None\n        exception_already_raised = False\n\n        async def call_next(request: Request) -> Response:\n            async def receive_or_disconnect() -> Message:\n                if response_sent.is_set():\n                    return {\"type\": \"http.disconnect\"}\n\n                async with anyio.create_task_group() as task_group:\n\n                    async def wrap(func: Callable[[], Awaitable[T]]) -> T:\n                        result = await func()\n                        task_group.cancel_scope.cancel()\n                        return result\n\n                    task_group.start_soon(wrap, response_sent.wait)\n                    message = await wrap(wrapped_receive)\n\n                if response_sent.is_set():\n                    return {\"type\": \"http.disconnect\"}\n\n                return message\n\n            async def send_no_error(message: Message) -> None:\n                try:\n                    await send_stream.send(message)\n                except anyio.BrokenResourceError:\n                    # recv_stream has been closed, i.e. response_sent has been set.\n                    return\n\n            async def coro() -> None:\n                nonlocal app_exc\n\n                with send_stream:\n                    try:\n                        await self.app(scope, receive_or_disconnect, send_no_error)\n                    except Exception as exc:\n                        app_exc = exc\n\n            task_group.start_soon(coro)\n\n            try:\n                message = await recv_stream.receive()\n                info = message.get(\"info\", None)\n                if message[\"type\"] == \"http.response.debug\" and info is not None:\n                    message = await recv_stream.receive()\n            except anyio.EndOfStream:\n                if app_exc is not None:\n                    nonlocal exception_already_raised\n                    exception_already_raised = True\n                    # Prevent `anyio.EndOfStream` from polluting app exception context.\n                    # If both cause and context are None then the context is suppressed\n                    # and `anyio.EndOfStream` is not present in the exception traceback.\n                    # If exception cause is not None then it is propagated with\n                    # reraising here.\n                    # If exception has no cause but has context set then the context is\n                    # propagated as a cause with the reraise. This is necessary in order\n                    # to prevent `anyio.EndOfStream` from polluting the exception\n                    # context.\n                    raise app_exc from app_exc.__cause__ or app_exc.__context__\n                raise RuntimeError(\"No response returned.\")\n\n            assert message[\"type\"] == \"http.response.start\"\n\n            async def body_stream() -> BodyStreamGenerator:\n                async for message in recv_stream:\n                    if message[\"type\"] == \"http.response.pathsend\":\n                        yield message\n                        break\n                    assert message[\"type\"] == \"http.response.body\", f\"Unexpected message: {message}\"\n                    body = message.get(\"body\", b\"\")\n                    if body:\n                        yield body\n                    if not message.get(\"more_body\", False):\n                        break\n\n            response = _StreamingResponse(status_code=message[\"status\"], content=body_stream(), info=info)\n            response.raw_headers = message[\"headers\"]\n            return response\n\n        streams: anyio.create_memory_object_stream[Message] = anyio.create_memory_object_stream()\n        send_stream, recv_stream = streams\n        with recv_stream, send_stream, collapse_excgroups():\n            async with anyio.create_task_group() as task_group:\n                response = await self.dispatch_func(request, call_next)\n                await response(scope, wrapped_receive, send)\n                response_sent.set()\n                recv_stream.close()\n        if app_exc is not None and not exception_already_raised:\n            raise app_exc\n\n    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:\n        raise NotImplementedError()  # pragma: no cover\n\n\nclass _StreamingResponse(Response):\n    def __init__(\n        self,\n        content: AsyncContentStream,\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        info: Mapping[str, Any] | None = None,\n    ) -> None:\n        self.info = info\n        self.body_iterator = content\n        self.status_code = status_code\n        self.media_type = media_type\n        self.init_headers(headers)\n        self.background = None\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if self.info is not None:\n            await send({\"type\": \"http.response.debug\", \"info\": self.info})\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": self.status_code,\n                \"headers\": self.raw_headers,\n            }\n        )\n\n        should_close_body = True\n        async for chunk in self.body_iterator:\n            if isinstance(chunk, dict):\n                # We got an ASGI message which is not response body (eg: pathsend)\n                should_close_body = False\n                await send(chunk)\n                continue\n            await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True})\n\n        if should_close_body:\n            await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n        if self.background:\n            await self.background()\n"
  },
  {
    "path": "starlette/middleware/cors.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport re\nfrom collections.abc import Sequence\n\nfrom starlette.datastructures import Headers, MutableHeaders\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\n\nALL_METHODS = (\"DELETE\", \"GET\", \"HEAD\", \"OPTIONS\", \"PATCH\", \"POST\", \"PUT\")\nSAFELISTED_HEADERS = {\"Accept\", \"Accept-Language\", \"Content-Language\", \"Content-Type\"}\n\n\nclass CORSMiddleware:\n    def __init__(\n        self,\n        app: ASGIApp,\n        allow_origins: Sequence[str] = (),\n        allow_methods: Sequence[str] = (\"GET\",),\n        allow_headers: Sequence[str] = (),\n        allow_credentials: bool = False,\n        allow_origin_regex: str | None = None,\n        allow_private_network: bool = False,\n        expose_headers: Sequence[str] = (),\n        max_age: int = 600,\n    ) -> None:\n        if \"*\" in allow_methods:\n            allow_methods = ALL_METHODS\n\n        compiled_allow_origin_regex = None\n        if allow_origin_regex is not None:\n            compiled_allow_origin_regex = re.compile(allow_origin_regex)\n\n        allow_all_origins = \"*\" in allow_origins\n        allow_all_headers = \"*\" in allow_headers\n        preflight_explicit_allow_origin = not allow_all_origins or allow_credentials\n\n        simple_headers: dict[str, str] = {}\n        if allow_all_origins:\n            simple_headers[\"Access-Control-Allow-Origin\"] = \"*\"\n        if allow_credentials:\n            simple_headers[\"Access-Control-Allow-Credentials\"] = \"true\"\n        if expose_headers:\n            simple_headers[\"Access-Control-Expose-Headers\"] = \", \".join(expose_headers)\n\n        preflight_headers: dict[str, str] = {}\n        if preflight_explicit_allow_origin:\n            # The origin value will be set in preflight_response() if it is allowed.\n            preflight_headers[\"Vary\"] = \"Origin\"\n        else:\n            preflight_headers[\"Access-Control-Allow-Origin\"] = \"*\"\n        preflight_headers.update(\n            {\n                \"Access-Control-Allow-Methods\": \", \".join(allow_methods),\n                \"Access-Control-Max-Age\": str(max_age),\n            }\n        )\n        allow_headers = sorted(SAFELISTED_HEADERS | set(allow_headers))\n        if allow_headers and not allow_all_headers:\n            preflight_headers[\"Access-Control-Allow-Headers\"] = \", \".join(allow_headers)\n        if allow_credentials:\n            preflight_headers[\"Access-Control-Allow-Credentials\"] = \"true\"\n\n        self.app = app\n        self.allow_origins = allow_origins\n        self.allow_methods = allow_methods\n        self.allow_headers = [h.lower() for h in allow_headers]\n        self.allow_all_origins = allow_all_origins\n        self.allow_all_headers = allow_all_headers\n        self.allow_credentials = allow_credentials\n        self.preflight_explicit_allow_origin = preflight_explicit_allow_origin\n        self.allow_origin_regex = compiled_allow_origin_regex\n        self.allow_private_network = allow_private_network\n        self.simple_headers = simple_headers\n        self.preflight_headers = preflight_headers\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] != \"http\":  # pragma: no cover\n            await self.app(scope, receive, send)\n            return\n\n        method = scope[\"method\"]\n        headers = Headers(scope=scope)\n        origin = headers.get(\"origin\")\n\n        if origin is None:\n            await self.app(scope, receive, send)\n            return\n\n        if method == \"OPTIONS\" and \"access-control-request-method\" in headers:\n            response = self.preflight_response(request_headers=headers)\n            await response(scope, receive, send)\n            return\n\n        await self.simple_response(scope, receive, send, request_headers=headers)\n\n    def is_allowed_origin(self, origin: str) -> bool:\n        if self.allow_all_origins:\n            return True\n\n        if self.allow_origin_regex is not None and self.allow_origin_regex.fullmatch(origin):\n            return True\n\n        return origin in self.allow_origins\n\n    def preflight_response(self, request_headers: Headers) -> Response:\n        requested_origin = request_headers[\"origin\"]\n        requested_method = request_headers[\"access-control-request-method\"]\n        requested_headers = request_headers.get(\"access-control-request-headers\")\n        requested_private_network = request_headers.get(\"access-control-request-private-network\")\n\n        headers = dict(self.preflight_headers)\n        failures: list[str] = []\n\n        if self.is_allowed_origin(origin=requested_origin):\n            if self.preflight_explicit_allow_origin:\n                # The \"else\" case is already accounted for in self.preflight_headers\n                # and the value would be \"*\".\n                headers[\"Access-Control-Allow-Origin\"] = requested_origin\n        else:\n            failures.append(\"origin\")\n\n        if requested_method not in self.allow_methods:\n            failures.append(\"method\")\n\n        # If we allow all headers, then we have to mirror back any requested\n        # headers in the response.\n        if self.allow_all_headers and requested_headers is not None:\n            headers[\"Access-Control-Allow-Headers\"] = requested_headers\n        elif requested_headers is not None:\n            for header in [h.lower() for h in requested_headers.split(\",\")]:\n                if header.strip() not in self.allow_headers:\n                    failures.append(\"headers\")\n                    break\n\n        if requested_private_network is not None:\n            if self.allow_private_network:\n                headers[\"Access-Control-Allow-Private-Network\"] = \"true\"\n            else:\n                failures.append(\"private-network\")\n\n        # We don't strictly need to use 400 responses here, since its up to\n        # the browser to enforce the CORS policy, but its more informative\n        # if we do.\n        if failures:\n            failure_text = \"Disallowed CORS \" + \", \".join(failures)\n            return PlainTextResponse(failure_text, status_code=400, headers=headers)\n\n        return PlainTextResponse(\"OK\", status_code=200, headers=headers)\n\n    async def simple_response(self, scope: Scope, receive: Receive, send: Send, request_headers: Headers) -> None:\n        send = functools.partial(self.send, send=send, request_headers=request_headers)\n        await self.app(scope, receive, send)\n\n    async def send(self, message: Message, send: Send, request_headers: Headers) -> None:\n        if message[\"type\"] != \"http.response.start\":\n            await send(message)\n            return\n\n        message.setdefault(\"headers\", [])\n        headers = MutableHeaders(scope=message)\n        headers.update(self.simple_headers)\n        origin = request_headers[\"Origin\"]\n\n        # If credentials are allowed, then we must respond with the specific origin instead of '*'.\n        if self.allow_all_origins and self.allow_credentials:\n            self.allow_explicit_origin(headers, origin)\n\n        # If we only allow specific origins, then we have to mirror back the Origin header in the response.\n        elif not self.allow_all_origins and self.is_allowed_origin(origin=origin):\n            self.allow_explicit_origin(headers, origin)\n\n        await send(message)\n\n    @staticmethod\n    def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None:\n        headers[\"Access-Control-Allow-Origin\"] = origin\n        headers.add_vary_header(\"Origin\")\n"
  },
  {
    "path": "starlette/middleware/errors.py",
    "content": "from __future__ import annotations\n\nimport html\nimport inspect\nimport sys\nimport traceback\n\nfrom starlette._utils import is_async_callable\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.requests import Request\nfrom starlette.responses import HTMLResponse, PlainTextResponse, Response\nfrom starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send\n\nSTYLES = \"\"\"\np {\n    color: #211c1c;\n}\n.traceback-container {\n    border: 1px solid #038BB8;\n}\n.traceback-title {\n    background-color: #038BB8;\n    color: lemonchiffon;\n    padding: 12px;\n    font-size: 20px;\n    margin-top: 0px;\n}\n.frame-line {\n    padding-left: 10px;\n    font-family: monospace;\n}\n.frame-filename {\n    font-family: monospace;\n}\n.center-line {\n    background-color: #038BB8;\n    color: #f9f6e1;\n    padding: 5px 0px 5px 5px;\n}\n.lineno {\n    margin-right: 5px;\n}\n.frame-title {\n    font-weight: unset;\n    padding: 10px 10px 10px 10px;\n    background-color: #E4F4FD;\n    margin-right: 10px;\n    color: #191f21;\n    font-size: 17px;\n    border: 1px solid #c7dce8;\n}\n.collapse-btn {\n    float: right;\n    padding: 0px 5px 1px 5px;\n    border: solid 1px #96aebb;\n    cursor: pointer;\n}\n.collapsed {\n  display: none;\n}\n.source-code {\n  font-family: courier;\n  font-size: small;\n  padding-bottom: 10px;\n}\n\"\"\"\n\nJS = \"\"\"\n<script type=\"text/javascript\">\n    function collapse(element){\n        const frameId = element.getAttribute(\"data-frame-id\");\n        const frame = document.getElementById(frameId);\n\n        if (frame.classList.contains(\"collapsed\")){\n            element.innerHTML = \"&#8210;\";\n            frame.classList.remove(\"collapsed\");\n        } else {\n            element.innerHTML = \"+\";\n            frame.classList.add(\"collapsed\");\n        }\n    }\n</script>\n\"\"\"\n\nTEMPLATE = \"\"\"\n<html>\n    <head>\n        <style type='text/css'>\n            {styles}\n        </style>\n        <title>Starlette Debugger</title>\n    </head>\n    <body>\n        <h1>500 Server Error</h1>\n        <h2>{error}</h2>\n        <div class=\"traceback-container\">\n            <p class=\"traceback-title\">Traceback</p>\n            <div>{exc_html}</div>\n        </div>\n        {js}\n    </body>\n</html>\n\"\"\"\n\nFRAME_TEMPLATE = \"\"\"\n<div>\n    <p class=\"frame-title\">File <span class=\"frame-filename\">{frame_filename}</span>,\n    line <i>{frame_lineno}</i>,\n    in <b>{frame_name}</b>\n    <span class=\"collapse-btn\" data-frame-id=\"{frame_filename}-{frame_lineno}\" onclick=\"collapse(this)\">{collapse_button}</span>\n    </p>\n    <div id=\"{frame_filename}-{frame_lineno}\" class=\"source-code {collapsed}\">{code_context}</div>\n</div>\n\"\"\"  # noqa: E501\n\nLINE = \"\"\"\n<p><span class=\"frame-line\">\n<span class=\"lineno\">{lineno}.</span> {line}</span></p>\n\"\"\"\n\nCENTER_LINE = \"\"\"\n<p class=\"center-line\"><span class=\"frame-line center-line\">\n<span class=\"lineno\">{lineno}.</span> {line}</span></p>\n\"\"\"\n\n\nclass ServerErrorMiddleware:\n    \"\"\"\n    Handles returning 500 responses when a server error occurs.\n\n    If 'debug' is set, then traceback responses will be returned,\n    otherwise the designated 'handler' will be called.\n\n    This middleware class should generally be used to wrap *everything*\n    else up, so that unhandled exceptions anywhere in the stack\n    always result in an appropriate 500 response.\n    \"\"\"\n\n    def __init__(\n        self,\n        app: ASGIApp,\n        handler: ExceptionHandler | None = None,\n        debug: bool = False,\n    ) -> None:\n        self.app = app\n        self.handler = handler\n        self.debug = debug\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        response_started = False\n\n        async def _send(message: Message) -> None:\n            nonlocal response_started, send\n\n            if message[\"type\"] == \"http.response.start\":\n                response_started = True\n            await send(message)\n\n        try:\n            await self.app(scope, receive, _send)\n        except Exception as exc:\n            request = Request(scope)\n            if self.debug:\n                # In debug mode, return traceback responses.\n                response = self.debug_response(request, exc)\n            elif self.handler is None:\n                # Use our default 500 error handler.\n                response = self.error_response(request, exc)\n            else:\n                # Use an installed 500 error handler.\n                if is_async_callable(self.handler):\n                    response = await self.handler(request, exc)\n                else:\n                    response = await run_in_threadpool(self.handler, request, exc)\n\n            if not response_started:\n                await response(scope, receive, send)\n\n            # We always continue to raise the exception.\n            # This allows servers to log the error, or allows test clients\n            # to optionally raise the error within the test case.\n            raise exc\n\n    def format_line(self, index: int, line: str, frame_lineno: int, frame_index: int) -> str:\n        values = {\n            # HTML escape - line could contain < or >\n            \"line\": html.escape(line).replace(\" \", \"&nbsp\"),\n            \"lineno\": (frame_lineno - frame_index) + index,\n        }\n\n        if index != frame_index:\n            return LINE.format(**values)\n        return CENTER_LINE.format(**values)\n\n    def generate_frame_html(self, frame: inspect.FrameInfo, is_collapsed: bool) -> str:\n        code_context = \"\".join(\n            self.format_line(\n                index,\n                line,\n                frame.lineno,\n                frame.index,  # type: ignore[arg-type]\n            )\n            for index, line in enumerate(frame.code_context or [])\n        )\n\n        values = {\n            # HTML escape - filename could contain < or >, especially if it's a virtual\n            # file e.g. <stdin> in the REPL\n            \"frame_filename\": html.escape(frame.filename),\n            \"frame_lineno\": frame.lineno,\n            # HTML escape - if you try very hard it's possible to name a function with <\n            # or >\n            \"frame_name\": html.escape(frame.function),\n            \"code_context\": code_context,\n            \"collapsed\": \"collapsed\" if is_collapsed else \"\",\n            \"collapse_button\": \"+\" if is_collapsed else \"&#8210;\",\n        }\n        return FRAME_TEMPLATE.format(**values)\n\n    def generate_html(self, exc: Exception, limit: int = 7) -> str:\n        traceback_obj = traceback.TracebackException.from_exception(exc, capture_locals=True)\n\n        exc_html = \"\"\n        is_collapsed = False\n        exc_traceback = exc.__traceback__\n        if exc_traceback is not None:\n            frames = inspect.getinnerframes(exc_traceback, limit)\n            for frame in reversed(frames):\n                exc_html += self.generate_frame_html(frame, is_collapsed)\n                is_collapsed = True\n\n        if sys.version_info >= (3, 13):  # pragma: no cover\n            exc_type_str = traceback_obj.exc_type_str\n        else:  # pragma: no cover\n            exc_type_str = traceback_obj.exc_type.__name__\n\n        # escape error class and text\n        error = f\"{html.escape(exc_type_str)}: {html.escape(str(traceback_obj))}\"\n\n        return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html)\n\n    def generate_plain_text(self, exc: Exception) -> str:\n        return \"\".join(traceback.format_exception(type(exc), exc, exc.__traceback__))\n\n    def debug_response(self, request: Request, exc: Exception) -> Response:\n        accept = request.headers.get(\"accept\", \"\")\n\n        if \"text/html\" in accept:\n            content = self.generate_html(exc)\n            return HTMLResponse(content, status_code=500)\n        content = self.generate_plain_text(exc)\n        return PlainTextResponse(content, status_code=500)\n\n    def error_response(self, request: Request, exc: Exception) -> Response:\n        return PlainTextResponse(\"Internal Server Error\", status_code=500)\n"
  },
  {
    "path": "starlette/middleware/exceptions.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Mapping\nfrom typing import Any\n\nfrom starlette._exception_handler import (\n    ExceptionHandlers,\n    StatusHandlers,\n    wrap_app_handling_exceptions,\n)\nfrom starlette.exceptions import HTTPException, WebSocketException\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import ASGIApp, ExceptionHandler, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\n\n\nclass ExceptionMiddleware:\n    def __init__(\n        self,\n        app: ASGIApp,\n        handlers: Mapping[Any, ExceptionHandler] | None = None,\n        debug: bool = False,\n    ) -> None:\n        self.app = app\n        self.debug = debug  # TODO: We ought to handle 404 cases if debug is set.\n        self._status_handlers: StatusHandlers = {}\n        self._exception_handlers: ExceptionHandlers = {\n            HTTPException: self.http_exception,\n            WebSocketException: self.websocket_exception,\n        }\n        if handlers is not None:  # pragma: no branch\n            for key, value in handlers.items():\n                self.add_exception_handler(key, value)\n\n    def add_exception_handler(\n        self,\n        exc_class_or_status_code: int | type[Exception],\n        handler: ExceptionHandler,\n    ) -> None:\n        if isinstance(exc_class_or_status_code, int):\n            self._status_handlers[exc_class_or_status_code] = handler\n        else:\n            assert issubclass(exc_class_or_status_code, Exception)\n            self._exception_handlers[exc_class_or_status_code] = handler\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] not in (\"http\", \"websocket\"):\n            await self.app(scope, receive, send)\n            return\n\n        scope[\"starlette.exception_handlers\"] = (\n            self._exception_handlers,\n            self._status_handlers,\n        )\n\n        conn: Request | WebSocket\n        if scope[\"type\"] == \"http\":\n            conn = Request(scope, receive, send)\n        else:\n            conn = WebSocket(scope, receive, send)\n\n        await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)\n\n    async def http_exception(self, request: Request, exc: Exception) -> Response:\n        assert isinstance(exc, HTTPException)\n        if exc.status_code in {204, 304}:\n            return Response(status_code=exc.status_code, headers=exc.headers)\n        return PlainTextResponse(exc.detail, status_code=exc.status_code, headers=exc.headers)\n\n    async def websocket_exception(self, websocket: WebSocket, exc: Exception) -> None:\n        assert isinstance(exc, WebSocketException)\n        await websocket.close(code=exc.code, reason=exc.reason)  # pragma: no cover\n"
  },
  {
    "path": "starlette/middleware/gzip.py",
    "content": "import gzip\nimport io\nfrom typing import NoReturn\n\nfrom starlette.datastructures import Headers, MutableHeaders\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\n\nDEFAULT_EXCLUDED_CONTENT_TYPES = (\"text/event-stream\",)\n\n\nclass GZipMiddleware:\n    def __init__(self, app: ASGIApp, minimum_size: int = 500, compresslevel: int = 9) -> None:\n        self.app = app\n        self.minimum_size = minimum_size\n        self.compresslevel = compresslevel\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] != \"http\":  # pragma: no cover\n            await self.app(scope, receive, send)\n            return\n\n        headers = Headers(scope=scope)\n        responder: ASGIApp\n        if \"gzip\" in headers.get(\"Accept-Encoding\", \"\"):\n            responder = GZipResponder(self.app, self.minimum_size, compresslevel=self.compresslevel)\n        else:\n            responder = IdentityResponder(self.app, self.minimum_size)\n\n        await responder(scope, receive, send)\n\n\nclass IdentityResponder:\n    content_encoding: str\n\n    def __init__(self, app: ASGIApp, minimum_size: int) -> None:\n        self.app = app\n        self.minimum_size = minimum_size\n        self.send: Send = unattached_send\n        self.initial_message: Message = {}\n        self.started = False\n        self.content_encoding_set = False\n        self.content_type_is_excluded = False\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        self.send = send\n        await self.app(scope, receive, self.send_with_compression)\n\n    async def send_with_compression(self, message: Message) -> None:\n        message_type = message[\"type\"]\n        if message_type == \"http.response.start\":\n            # Don't send the initial message until we've determined how to\n            # modify the outgoing headers correctly.\n            self.initial_message = message\n            headers = Headers(raw=self.initial_message[\"headers\"])\n            self.content_encoding_set = \"content-encoding\" in headers\n            self.content_type_is_excluded = headers.get(\"content-type\", \"\").startswith(DEFAULT_EXCLUDED_CONTENT_TYPES)\n        elif message_type == \"http.response.body\" and (self.content_encoding_set or self.content_type_is_excluded):\n            if not self.started:\n                self.started = True\n                await self.send(self.initial_message)\n            await self.send(message)\n        elif message_type == \"http.response.body\" and not self.started:\n            self.started = True\n            body = message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n            if len(body) < self.minimum_size and not more_body:\n                # Don't apply compression to small outgoing responses.\n                await self.send(self.initial_message)\n                await self.send(message)\n            elif not more_body:\n                # Standard response.\n                body = self.apply_compression(body, more_body=False)\n\n                headers = MutableHeaders(raw=self.initial_message[\"headers\"])\n                headers.add_vary_header(\"Accept-Encoding\")\n                if body != message[\"body\"]:\n                    headers[\"Content-Encoding\"] = self.content_encoding\n                    headers[\"Content-Length\"] = str(len(body))\n                    message[\"body\"] = body\n\n                await self.send(self.initial_message)\n                await self.send(message)\n            else:\n                # Initial body in streaming response.\n                body = self.apply_compression(body, more_body=True)\n\n                headers = MutableHeaders(raw=self.initial_message[\"headers\"])\n                headers.add_vary_header(\"Accept-Encoding\")\n                if body != message[\"body\"]:\n                    headers[\"Content-Encoding\"] = self.content_encoding\n                    del headers[\"Content-Length\"]\n                    message[\"body\"] = body\n\n                await self.send(self.initial_message)\n                await self.send(message)\n        elif message_type == \"http.response.body\":\n            # Remaining body in streaming response.\n            body = message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n\n            message[\"body\"] = self.apply_compression(body, more_body=more_body)\n\n            await self.send(message)\n        elif message_type == \"http.response.pathsend\":  # pragma: no branch\n            # Don't apply GZip to pathsend responses\n            await self.send(self.initial_message)\n            await self.send(message)\n\n    def apply_compression(self, body: bytes, *, more_body: bool) -> bytes:\n        \"\"\"Apply compression on the response body.\n\n        If more_body is False, any compression file should be closed. If it\n        isn't, it won't be closed automatically until all background tasks\n        complete.\n        \"\"\"\n        return body\n\n\nclass GZipResponder(IdentityResponder):\n    content_encoding = \"gzip\"\n\n    def __init__(self, app: ASGIApp, minimum_size: int, compresslevel: int = 9) -> None:\n        super().__init__(app, minimum_size)\n\n        self.gzip_buffer = io.BytesIO()\n        self.gzip_file = gzip.GzipFile(mode=\"wb\", fileobj=self.gzip_buffer, compresslevel=compresslevel)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        with self.gzip_buffer, self.gzip_file:\n            await super().__call__(scope, receive, send)\n\n    def apply_compression(self, body: bytes, *, more_body: bool) -> bytes:\n        self.gzip_file.write(body)\n        if not more_body:\n            self.gzip_file.close()\n\n        body = self.gzip_buffer.getvalue()\n        self.gzip_buffer.seek(0)\n        self.gzip_buffer.truncate()\n\n        return body\n\n\nasync def unattached_send(message: Message) -> NoReturn:\n    raise RuntimeError(\"send awaitable not set\")  # pragma: no cover\n"
  },
  {
    "path": "starlette/middleware/httpsredirect.py",
    "content": "from starlette.datastructures import URL\nfrom starlette.responses import RedirectResponse\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\n\nclass HTTPSRedirectMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] in (\"http\", \"websocket\") and scope[\"scheme\"] in (\"http\", \"ws\"):\n            url = URL(scope=scope)\n            redirect_scheme = {\"http\": \"https\", \"ws\": \"wss\"}[url.scheme]\n            netloc = url.hostname if url.port in (80, 443) else url.netloc\n            url = url.replace(scheme=redirect_scheme, netloc=netloc)\n            response = RedirectResponse(url, status_code=307)\n            await response(scope, receive, send)\n        else:\n            await self.app(scope, receive, send)\n"
  },
  {
    "path": "starlette/middleware/sessions.py",
    "content": "from __future__ import annotations\n\nimport json\nimport typing\nfrom base64 import b64decode, b64encode\nfrom typing import Literal\n\nimport itsdangerous\nfrom itsdangerous.exc import BadSignature\n\nfrom starlette.datastructures import MutableHeaders, Secret\nfrom starlette.requests import HTTPConnection\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\n\n\nclass SessionMiddleware:\n    def __init__(\n        self,\n        app: ASGIApp,\n        secret_key: str | Secret,\n        session_cookie: str = \"session\",\n        max_age: int | None = 14 * 24 * 60 * 60,  # 14 days, in seconds\n        path: str = \"/\",\n        same_site: Literal[\"lax\", \"strict\", \"none\"] = \"lax\",\n        https_only: bool = False,\n        domain: str | None = None,\n    ) -> None:\n        self.app = app\n        self.signer = itsdangerous.TimestampSigner(str(secret_key))\n        self.session_cookie = session_cookie\n        self.max_age = max_age\n        self.path = path\n        self.security_flags = \"httponly; samesite=\" + same_site\n        if https_only:  # Secure flag can be used with HTTPS only\n            self.security_flags += \"; secure\"\n        if domain is not None:\n            self.security_flags += f\"; domain={domain}\"\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] not in (\"http\", \"websocket\"):  # pragma: no cover\n            await self.app(scope, receive, send)\n            return\n\n        connection = HTTPConnection(scope)\n        initial_session_was_empty = True\n\n        if self.session_cookie in connection.cookies:\n            data = connection.cookies[self.session_cookie].encode(\"utf-8\")\n            try:\n                data = self.signer.unsign(data, max_age=self.max_age)\n                scope[\"session\"] = Session(json.loads(b64decode(data)))\n                initial_session_was_empty = False\n            except BadSignature:\n                scope[\"session\"] = Session()\n        else:\n            scope[\"session\"] = Session()\n\n        async def send_wrapper(message: Message) -> None:\n            if message[\"type\"] == \"http.response.start\":\n                session: Session = scope[\"session\"]\n                headers = MutableHeaders(scope=message)\n                if session.accessed:\n                    headers.add_vary_header(\"Cookie\")\n                if session.modified and session:\n                    # We have session data to persist.\n                    data = b64encode(json.dumps(session).encode(\"utf-8\"))\n                    data = self.signer.sign(data)\n                    header_value = \"{session_cookie}={data}; path={path}; {max_age}{security_flags}\".format(\n                        session_cookie=self.session_cookie,\n                        data=data.decode(\"utf-8\"),\n                        path=self.path,\n                        max_age=f\"Max-Age={self.max_age}; \" if self.max_age else \"\",\n                        security_flags=self.security_flags,\n                    )\n                    headers.append(\"Set-Cookie\", header_value)\n                elif session.modified and not initial_session_was_empty:\n                    # The session has been cleared.\n                    header_value = \"{session_cookie}={data}; path={path}; {expires}{security_flags}\".format(\n                        session_cookie=self.session_cookie,\n                        data=\"null\",\n                        path=self.path,\n                        expires=\"expires=Thu, 01 Jan 1970 00:00:00 GMT; \",\n                        security_flags=self.security_flags,\n                    )\n                    headers.append(\"Set-Cookie\", header_value)\n            await send(message)\n\n        await self.app(scope, receive, send_wrapper)\n\n\nclass Session(dict[str, typing.Any]):\n    accessed: bool = False\n    modified: bool = False\n\n    def mark_accessed(self) -> None:\n        self.accessed = True\n\n    def mark_modified(self) -> None:\n        self.accessed = True\n        self.modified = True\n\n    def __setitem__(self, key: str, value: typing.Any) -> None:\n        self.mark_modified()\n        super().__setitem__(key, value)\n\n    def __delitem__(self, key: str) -> None:\n        self.mark_modified()\n        super().__delitem__(key)\n\n    def clear(self) -> None:\n        self.mark_modified()\n        super().clear()\n\n    def pop(self, key: str, *args: typing.Any) -> typing.Any:\n        self.modified = self.modified or key in self\n        return super().pop(key, *args)\n\n    def setdefault(self, key: str, default: typing.Any = None) -> typing.Any:\n        if key not in self:\n            self.mark_modified()\n        return super().setdefault(key, default)\n\n    def update(self, *args: typing.Any, **kwargs: typing.Any) -> None:\n        self.mark_modified()\n        super().update(*args, **kwargs)\n"
  },
  {
    "path": "starlette/middleware/trustedhost.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Sequence\n\nfrom starlette.datastructures import URL, Headers\nfrom starlette.responses import PlainTextResponse, RedirectResponse, Response\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\nENFORCE_DOMAIN_WILDCARD = \"Domain wildcard patterns must be like '*.example.com'.\"\n\n\nclass TrustedHostMiddleware:\n    def __init__(\n        self,\n        app: ASGIApp,\n        allowed_hosts: Sequence[str] | None = None,\n        www_redirect: bool = True,\n    ) -> None:\n        if allowed_hosts is None:\n            allowed_hosts = [\"*\"]\n\n        for pattern in allowed_hosts:\n            assert \"*\" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD\n            if pattern.startswith(\"*\") and pattern != \"*\":\n                assert pattern.startswith(\"*.\"), ENFORCE_DOMAIN_WILDCARD\n        self.app = app\n        self.allowed_hosts = list(allowed_hosts)\n        self.allow_any = \"*\" in allowed_hosts\n        self.www_redirect = www_redirect\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if self.allow_any or scope[\"type\"] not in (\n            \"http\",\n            \"websocket\",\n        ):  # pragma: no cover\n            await self.app(scope, receive, send)\n            return\n\n        headers = Headers(scope=scope)\n        host = headers.get(\"host\", \"\").split(\":\")[0]\n        is_valid_host = False\n        found_www_redirect = False\n        for pattern in self.allowed_hosts:\n            if host == pattern or (pattern.startswith(\"*\") and host.endswith(pattern[1:])):\n                is_valid_host = True\n                break\n            elif \"www.\" + host == pattern:\n                found_www_redirect = True\n\n        if is_valid_host:\n            await self.app(scope, receive, send)\n        else:\n            response: Response\n            if found_www_redirect and self.www_redirect:\n                url = URL(scope=scope)\n                redirect_url = url.replace(netloc=\"www.\" + url.netloc)\n                response = RedirectResponse(url=str(redirect_url))\n            else:\n                response = PlainTextResponse(\"Invalid host header\", status_code=400)\n            await response(scope, receive, send)\n"
  },
  {
    "path": "starlette/middleware/wsgi.py",
    "content": "from __future__ import annotations\n\nimport io\nimport math\nimport sys\nimport warnings\nfrom collections.abc import Callable, MutableMapping\nfrom typing import Any\n\nimport anyio\nfrom anyio.abc import ObjectReceiveStream, ObjectSendStream\n\nfrom starlette.types import Receive, Scope, Send\n\nwarnings.warn(\n    \"starlette.middleware.wsgi is deprecated and will be removed in a future release. \"\n    \"Please refer to https://github.com/abersheeran/a2wsgi as a replacement.\",\n    DeprecationWarning,\n    stacklevel=2,\n)\n\n\ndef build_environ(scope: Scope, body: bytes) -> dict[str, Any]:\n    \"\"\"\n    Builds a scope and request body into a WSGI environ object.\n    \"\"\"\n\n    script_name = scope.get(\"root_path\", \"\").encode(\"utf8\").decode(\"latin1\")\n    path_info = scope[\"path\"].encode(\"utf8\").decode(\"latin1\")\n    if path_info.startswith(script_name):\n        path_info = path_info[len(script_name) :]\n\n    environ = {\n        \"REQUEST_METHOD\": scope[\"method\"],\n        \"SCRIPT_NAME\": script_name,\n        \"PATH_INFO\": path_info,\n        \"QUERY_STRING\": scope[\"query_string\"].decode(\"ascii\"),\n        \"SERVER_PROTOCOL\": f\"HTTP/{scope['http_version']}\",\n        \"wsgi.version\": (1, 0),\n        \"wsgi.url_scheme\": scope.get(\"scheme\", \"http\"),\n        \"wsgi.input\": io.BytesIO(body),\n        \"wsgi.errors\": sys.stdout,\n        \"wsgi.multithread\": True,\n        \"wsgi.multiprocess\": True,\n        \"wsgi.run_once\": False,\n    }\n\n    # Get server name and port - required in WSGI, not in ASGI\n    server = scope.get(\"server\") or (\"localhost\", 80)\n    environ[\"SERVER_NAME\"] = server[0]\n    environ[\"SERVER_PORT\"] = server[1]\n\n    # Get client IP address\n    if scope.get(\"client\"):\n        environ[\"REMOTE_ADDR\"] = scope[\"client\"][0]\n\n    # Go through headers and make them into environ entries\n    for name, value in scope.get(\"headers\", []):\n        name = name.decode(\"latin1\")\n        if name == \"content-length\":\n            corrected_name = \"CONTENT_LENGTH\"\n        elif name == \"content-type\":\n            corrected_name = \"CONTENT_TYPE\"\n        else:\n            corrected_name = f\"HTTP_{name}\".upper().replace(\"-\", \"_\")\n        # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in\n        # case\n        value = value.decode(\"latin1\")\n        if corrected_name in environ:\n            value = environ[corrected_name] + \",\" + value\n        environ[corrected_name] = value\n    return environ\n\n\nclass WSGIMiddleware:\n    def __init__(self, app: Callable[..., Any]) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"http\"\n        responder = WSGIResponder(self.app, scope)\n        await responder(receive, send)\n\n\nclass WSGIResponder:\n    stream_send: ObjectSendStream[MutableMapping[str, Any]]\n    stream_receive: ObjectReceiveStream[MutableMapping[str, Any]]\n\n    def __init__(self, app: Callable[..., Any], scope: Scope) -> None:\n        self.app = app\n        self.scope = scope\n        self.status = None\n        self.response_headers = None\n        self.stream_send, self.stream_receive = anyio.create_memory_object_stream(math.inf)\n        self.response_started = False\n        self.exc_info: Any = None\n\n    async def __call__(self, receive: Receive, send: Send) -> None:\n        body = b\"\"\n        more_body = True\n        while more_body:\n            message = await receive()\n            body += message.get(\"body\", b\"\")\n            more_body = message.get(\"more_body\", False)\n        environ = build_environ(self.scope, body)\n\n        async with anyio.create_task_group() as task_group:\n            task_group.start_soon(self.sender, send)\n            async with self.stream_send:\n                await anyio.to_thread.run_sync(self.wsgi, environ, self.start_response)\n        if self.exc_info is not None:\n            raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])\n\n    async def sender(self, send: Send) -> None:\n        async with self.stream_receive:\n            async for message in self.stream_receive:\n                await send(message)\n\n    def start_response(\n        self,\n        status: str,\n        response_headers: list[tuple[str, str]],\n        exc_info: Any = None,\n    ) -> None:\n        self.exc_info = exc_info\n        if not self.response_started:  # pragma: no branch\n            self.response_started = True\n            status_code_string, _ = status.split(\" \", 1)\n            status_code = int(status_code_string)\n            headers = [\n                (name.strip().encode(\"ascii\").lower(), value.strip().encode(\"ascii\"))\n                for name, value in response_headers\n            ]\n            anyio.from_thread.run(\n                self.stream_send.send,\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": status_code,\n                    \"headers\": headers,\n                },\n            )\n\n    def wsgi(\n        self,\n        environ: dict[str, Any],\n        start_response: Callable[..., Any],\n    ) -> None:\n        for chunk in self.app(environ, start_response):\n            anyio.from_thread.run(\n                self.stream_send.send,\n                {\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True},\n            )\n\n        anyio.from_thread.run(self.stream_send.send, {\"type\": \"http.response.body\", \"body\": b\"\"})\n"
  },
  {
    "path": "starlette/py.typed",
    "content": ""
  },
  {
    "path": "starlette/requests.py",
    "content": "from __future__ import annotations\n\nimport json\nimport sys\nfrom collections.abc import AsyncGenerator, Iterator, Mapping\nfrom http import cookies as http_cookies\nfrom typing import TYPE_CHECKING, Any, Generic, NoReturn, cast\n\nimport anyio\n\nfrom starlette._utils import AwaitableOrContextManager, AwaitableOrContextManagerWrapper\nfrom starlette.datastructures import URL, Address, FormData, Headers, QueryParams, State\nfrom starlette.exceptions import HTTPException\nfrom starlette.formparsers import FormParser, MultiPartException, MultiPartParser\nfrom starlette.types import Message, Receive, Scope, Send\n\nif TYPE_CHECKING:\n    from python_multipart.multipart import parse_options_header\n\n    from starlette.applications import Starlette\n    from starlette.middleware.sessions import Session\n    from starlette.routing import Router\nelse:\n    try:\n        try:\n            from python_multipart.multipart import parse_options_header\n        except ModuleNotFoundError:  # pragma: no cover\n            from multipart.multipart import parse_options_header\n    except ModuleNotFoundError:  # pragma: no cover\n        parse_options_header = None\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from typing import TypeVar\nelse:  # pragma: no cover\n    from typing_extensions import TypeVar\n\nSERVER_PUSH_HEADERS_TO_COPY = {\n    \"accept\",\n    \"accept-encoding\",\n    \"accept-language\",\n    \"cache-control\",\n    \"user-agent\",\n}\n\n\ndef cookie_parser(cookie_string: str) -> dict[str, str]:\n    \"\"\"\n    This function parses a ``Cookie`` HTTP header into a dict of key/value pairs.\n\n    It attempts to mimic browser cookie parsing behavior: browsers and web servers\n    frequently disregard the spec (RFC 6265) when setting and reading cookies,\n    so we attempt to suit the common scenarios here.\n\n    This function has been adapted from Django 3.1.0.\n    Note: we are explicitly _NOT_ using `SimpleCookie.load` because it is based\n    on an outdated spec and will fail on lots of input we want to support\n    \"\"\"\n    cookie_dict: dict[str, str] = {}\n    for chunk in cookie_string.split(\";\"):\n        if \"=\" in chunk:\n            key, val = chunk.split(\"=\", 1)\n        else:\n            # Assume an empty name per\n            # https://bugzilla.mozilla.org/show_bug.cgi?id=169091\n            key, val = \"\", chunk\n        key, val = key.strip(), val.strip()\n        if key or val:\n            # unquote using Python's algorithm.\n            cookie_dict[key] = http_cookies._unquote(val)\n    return cookie_dict\n\n\nclass ClientDisconnect(Exception):\n    pass\n\n\nStateT = TypeVar(\"StateT\", bound=Mapping[str, Any] | State, default=State)\n\n\nclass HTTPConnection(Mapping[str, Any], Generic[StateT]):\n    \"\"\"\n    A base class for incoming HTTP connections, that is used to provide\n    any functionality that is common to both `Request` and `WebSocket`.\n    \"\"\"\n\n    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:\n        assert scope[\"type\"] in (\"http\", \"websocket\")\n        self.scope = scope\n\n    def __getitem__(self, key: str) -> Any:\n        return self.scope[key]\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self.scope)\n\n    def __len__(self) -> int:\n        return len(self.scope)\n\n    # Don't use the `abc.Mapping.__eq__` implementation.\n    # Connection instances should never be considered equal\n    # unless `self is other`.\n    __eq__ = object.__eq__\n    __hash__ = object.__hash__\n\n    @property\n    def app(self) -> Any:\n        return self.scope[\"app\"]\n\n    @property\n    def url(self) -> URL:\n        if not hasattr(self, \"_url\"):  # pragma: no branch\n            self._url = URL(scope=self.scope)\n        return self._url\n\n    @property\n    def base_url(self) -> URL:\n        if not hasattr(self, \"_base_url\"):\n            base_url_scope = dict(self.scope)\n            # This is used by request.url_for, it might be used inside a Mount which\n            # would have its own child scope with its own root_path, but the base URL\n            # for url_for should still be the top level app root path.\n            app_root_path = base_url_scope.get(\"app_root_path\", base_url_scope.get(\"root_path\", \"\"))\n            path = app_root_path\n            if not path.endswith(\"/\"):\n                path += \"/\"\n            base_url_scope[\"path\"] = path\n            base_url_scope[\"query_string\"] = b\"\"\n            base_url_scope[\"root_path\"] = app_root_path\n            self._base_url = URL(scope=base_url_scope)\n        return self._base_url\n\n    @property\n    def headers(self) -> Headers:\n        if not hasattr(self, \"_headers\"):\n            self._headers = Headers(scope=self.scope)\n        return self._headers\n\n    @property\n    def query_params(self) -> QueryParams:\n        if not hasattr(self, \"_query_params\"):  # pragma: no branch\n            self._query_params = QueryParams(self.scope[\"query_string\"])\n        return self._query_params\n\n    @property\n    def path_params(self) -> dict[str, Any]:\n        return self.scope.get(\"path_params\", {})\n\n    @property\n    def cookies(self) -> dict[str, str]:\n        if not hasattr(self, \"_cookies\"):\n            cookies: dict[str, str] = {}\n            cookie_headers = self.headers.getlist(\"cookie\")\n\n            for header in cookie_headers:\n                cookies.update(cookie_parser(header))\n\n            self._cookies = cookies\n        return self._cookies\n\n    @property\n    def client(self) -> Address | None:\n        # client is a 2 item tuple of (host, port), None if missing\n        host_port = self.scope.get(\"client\")\n        if host_port is not None:\n            return Address(*host_port)\n        return None\n\n    @property\n    def session(self) -> dict[str, Any]:\n        assert \"session\" in self.scope, \"SessionMiddleware must be installed to access request.session\"\n        session: Session = self.scope[\"session\"]\n        # We keep the hasattr in case people actually use their own `SessionMiddleware` implementation.\n        if hasattr(session, \"mark_accessed\"):  # pragma: no branch\n            session.mark_accessed()\n        return session\n\n    @property\n    def auth(self) -> Any:\n        assert \"auth\" in self.scope, \"AuthenticationMiddleware must be installed to access request.auth\"\n        return self.scope[\"auth\"]\n\n    @property\n    def user(self) -> Any:\n        assert \"user\" in self.scope, \"AuthenticationMiddleware must be installed to access request.user\"\n        return self.scope[\"user\"]\n\n    @property\n    def state(self) -> StateT:\n        if not hasattr(self, \"_state\"):\n            # Ensure 'state' has an empty dict if it's not already populated.\n            self.scope.setdefault(\"state\", {})\n            # Create a state instance with a reference to the dict in which it should\n            # store info\n            self._state = State(self.scope[\"state\"])\n        return cast(StateT, self._state)\n\n    def url_for(self, name: str, /, **path_params: Any) -> URL:\n        url_path_provider: Router | Starlette | None = self.scope.get(\"router\") or self.scope.get(\"app\")\n        if url_path_provider is None:\n            raise RuntimeError(\"The `url_for` method can only be used inside a Starlette application or with a router.\")\n        url_path = url_path_provider.url_path_for(name, **path_params)\n        return url_path.make_absolute_url(base_url=self.base_url)\n\n\nasync def empty_receive() -> NoReturn:\n    raise RuntimeError(\"Receive channel has not been made available\")\n\n\nasync def empty_send(message: Message) -> NoReturn:\n    raise RuntimeError(\"Send channel has not been made available\")\n\n\nclass Request(HTTPConnection[StateT]):\n    _form: FormData | None\n\n    def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send):\n        super().__init__(scope)\n        assert scope[\"type\"] == \"http\"\n        self._receive = receive\n        self._send = send\n        self._stream_consumed = False\n        self._is_disconnected = False\n        self._form = None\n\n    @property\n    def method(self) -> str:\n        return cast(str, self.scope[\"method\"])\n\n    @property\n    def receive(self) -> Receive:\n        return self._receive\n\n    async def stream(self) -> AsyncGenerator[bytes, None]:\n        if hasattr(self, \"_body\"):\n            yield self._body\n            yield b\"\"\n            return\n        if self._stream_consumed:\n            raise RuntimeError(\"Stream consumed\")\n        while not self._stream_consumed:\n            message = await self._receive()\n            if message[\"type\"] == \"http.request\":\n                body = message.get(\"body\", b\"\")\n                if not message.get(\"more_body\", False):\n                    self._stream_consumed = True\n                if body:\n                    yield body\n            elif message[\"type\"] == \"http.disconnect\":  # pragma: no branch\n                self._is_disconnected = True\n                raise ClientDisconnect()\n        yield b\"\"\n\n    async def body(self) -> bytes:\n        if not hasattr(self, \"_body\"):\n            chunks: list[bytes] = []\n            async for chunk in self.stream():\n                chunks.append(chunk)\n            self._body = b\"\".join(chunks)\n        return self._body\n\n    async def json(self) -> Any:\n        if not hasattr(self, \"_json\"):  # pragma: no branch\n            body = await self.body()\n            self._json = json.loads(body)\n        return self._json\n\n    async def _get_form(\n        self,\n        *,\n        max_files: int | float = 1000,\n        max_fields: int | float = 1000,\n        max_part_size: int = 1024 * 1024,\n    ) -> FormData:\n        if self._form is None:  # pragma: no branch\n            assert parse_options_header is not None, (\n                \"The `python-multipart` library must be installed to use form parsing.\"\n            )\n            content_type_header = self.headers.get(\"Content-Type\")\n            content_type: bytes\n            content_type, _ = parse_options_header(content_type_header)\n            if content_type == b\"multipart/form-data\":\n                try:\n                    multipart_parser = MultiPartParser(\n                        self.headers,\n                        self.stream(),\n                        max_files=max_files,\n                        max_fields=max_fields,\n                        max_part_size=max_part_size,\n                    )\n                    self._form = await multipart_parser.parse()\n                except MultiPartException as exc:\n                    if \"app\" in self.scope:\n                        raise HTTPException(status_code=400, detail=exc.message)\n                    raise exc\n            elif content_type == b\"application/x-www-form-urlencoded\":\n                form_parser = FormParser(self.headers, self.stream())\n                self._form = await form_parser.parse()\n            else:\n                self._form = FormData()\n        return self._form\n\n    def form(\n        self,\n        *,\n        max_files: int | float = 1000,\n        max_fields: int | float = 1000,\n        max_part_size: int = 1024 * 1024,\n    ) -> AwaitableOrContextManager[FormData]:\n        return AwaitableOrContextManagerWrapper(\n            self._get_form(max_files=max_files, max_fields=max_fields, max_part_size=max_part_size)\n        )\n\n    async def close(self) -> None:\n        if self._form is not None:  # pragma: no branch\n            await self._form.close()\n\n    async def is_disconnected(self) -> bool:\n        if not self._is_disconnected:\n            message: Message = {}\n\n            # If message isn't immediately available, move on\n            with anyio.CancelScope() as cs:\n                cs.cancel()\n                message = await self._receive()\n\n            if message.get(\"type\") == \"http.disconnect\":\n                self._is_disconnected = True\n\n        return self._is_disconnected\n\n    async def send_push_promise(self, path: str) -> None:\n        if \"http.response.push\" in self.scope.get(\"extensions\", {}):\n            raw_headers: list[tuple[bytes, bytes]] = []\n            for name in SERVER_PUSH_HEADERS_TO_COPY:\n                for value in self.headers.getlist(name):\n                    raw_headers.append((name.encode(\"latin-1\"), value.encode(\"latin-1\")))\n            await self._send({\"type\": \"http.response.push\", \"path\": path, \"headers\": raw_headers})\n"
  },
  {
    "path": "starlette/responses.py",
    "content": "from __future__ import annotations\n\nimport hashlib\nimport http.cookies\nimport json\nimport os\nimport stat\nimport sys\nfrom collections.abc import AsyncIterable, Awaitable, Callable, Iterable, Mapping, Sequence\nfrom datetime import datetime\nfrom email.utils import format_datetime, formatdate\nfrom functools import partial\nfrom mimetypes import guess_type\nfrom secrets import token_hex\nfrom typing import Any, Literal\nfrom urllib.parse import quote\n\nimport anyio\nimport anyio.to_thread\n\nfrom starlette._utils import collapse_excgroups\nfrom starlette.background import BackgroundTask\nfrom starlette.concurrency import iterate_in_threadpool\nfrom starlette.datastructures import URL, Headers, MutableHeaders\nfrom starlette.requests import ClientDisconnect\nfrom starlette.types import Message, Receive, Scope, Send\n\n\nclass Response:\n    media_type = None\n    charset = \"utf-8\"\n\n    def __init__(\n        self,\n        content: Any = None,\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n    ) -> None:\n        self.status_code = status_code\n        if media_type is not None:\n            self.media_type = media_type\n        self.background = background\n        self.body = self.render(content)\n        self.init_headers(headers)\n\n    def render(self, content: Any) -> bytes | memoryview:\n        if content is None:\n            return b\"\"\n        if isinstance(content, bytes | memoryview):\n            return content\n        return content.encode(self.charset)  # type: ignore\n\n    def init_headers(self, headers: Mapping[str, str] | None = None) -> None:\n        if headers is None:\n            raw_headers: list[tuple[bytes, bytes]] = []\n            populate_content_length = True\n            populate_content_type = True\n        else:\n            raw_headers = [(k.lower().encode(\"latin-1\"), v.encode(\"latin-1\")) for k, v in headers.items()]\n            keys = [h[0] for h in raw_headers]\n            populate_content_length = b\"content-length\" not in keys\n            populate_content_type = b\"content-type\" not in keys\n\n        body = getattr(self, \"body\", None)\n        if (\n            body is not None\n            and populate_content_length\n            and not (self.status_code < 200 or self.status_code in (204, 304))\n        ):\n            content_length = str(len(body))\n            raw_headers.append((b\"content-length\", content_length.encode(\"latin-1\")))\n\n        content_type = self.media_type\n        if content_type is not None and populate_content_type:\n            if content_type.startswith(\"text/\") and \"charset=\" not in content_type.lower():\n                content_type += \"; charset=\" + self.charset\n            raw_headers.append((b\"content-type\", content_type.encode(\"latin-1\")))\n\n        self.raw_headers = raw_headers\n\n    @property\n    def headers(self) -> MutableHeaders:\n        if not hasattr(self, \"_headers\"):\n            self._headers = MutableHeaders(raw=self.raw_headers)\n        return self._headers\n\n    def set_cookie(\n        self,\n        key: str,\n        value: str = \"\",\n        max_age: int | None = None,\n        expires: datetime | str | int | None = None,\n        path: str | None = \"/\",\n        domain: str | None = None,\n        secure: bool = False,\n        httponly: bool = False,\n        samesite: Literal[\"lax\", \"strict\", \"none\"] | None = \"lax\",\n        partitioned: bool = False,\n    ) -> None:\n        cookie: http.cookies.BaseCookie[str] = http.cookies.SimpleCookie()\n        cookie[key] = value\n        if max_age is not None:\n            cookie[key][\"max-age\"] = max_age\n        if expires is not None:\n            if isinstance(expires, datetime):\n                cookie[key][\"expires\"] = format_datetime(expires, usegmt=True)\n            else:\n                cookie[key][\"expires\"] = expires\n        if path is not None:\n            cookie[key][\"path\"] = path\n        if domain is not None:\n            cookie[key][\"domain\"] = domain\n        if secure:\n            cookie[key][\"secure\"] = True\n        if httponly:\n            cookie[key][\"httponly\"] = True\n        if samesite is not None:\n            assert samesite.lower() in [\n                \"strict\",\n                \"lax\",\n                \"none\",\n            ], \"samesite must be either 'strict', 'lax' or 'none'\"\n            cookie[key][\"samesite\"] = samesite\n        if partitioned:\n            if sys.version_info < (3, 14):\n                raise ValueError(\"Partitioned cookies are only supported in Python 3.14 and above.\")  # pragma: no cover\n            cookie[key][\"partitioned\"] = True  # pragma: no cover\n\n        cookie_val = cookie.output(header=\"\").strip()\n        self.raw_headers.append((b\"set-cookie\", cookie_val.encode(\"latin-1\")))\n\n    def delete_cookie(\n        self,\n        key: str,\n        path: str = \"/\",\n        domain: str | None = None,\n        secure: bool = False,\n        httponly: bool = False,\n        samesite: Literal[\"lax\", \"strict\", \"none\"] | None = \"lax\",\n    ) -> None:\n        self.set_cookie(\n            key,\n            max_age=0,\n            expires=0,\n            path=path,\n            domain=domain,\n            secure=secure,\n            httponly=httponly,\n            samesite=samesite,\n        )\n\n    def _wrap_websocket_denial_send(self, send: Send) -> Send:\n        async def wrapped(message: Message) -> None:\n            message_type = message[\"type\"]\n            if message_type in {\"http.response.start\", \"http.response.body\"}:  # pragma: no branch\n                message = {**message, \"type\": \"websocket.\" + message_type}\n            await send(message)\n\n        return wrapped\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] == \"websocket\":\n            send = self._wrap_websocket_denial_send(send)\n        await send({\"type\": \"http.response.start\", \"status\": self.status_code, \"headers\": self.raw_headers})\n        await send({\"type\": \"http.response.body\", \"body\": self.body})\n\n        if self.background is not None:\n            await self.background()\n\n\nclass HTMLResponse(Response):\n    media_type = \"text/html\"\n\n\nclass PlainTextResponse(Response):\n    media_type = \"text/plain\"\n\n\nclass JSONResponse(Response):\n    media_type = \"application/json\"\n\n    def __init__(\n        self,\n        content: Any,\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n    ) -> None:\n        super().__init__(content, status_code, headers, media_type, background)\n\n    def render(self, content: Any) -> bytes:\n        return json.dumps(\n            content,\n            ensure_ascii=False,\n            allow_nan=False,\n            indent=None,\n            separators=(\",\", \":\"),\n        ).encode(\"utf-8\")\n\n\nclass RedirectResponse(Response):\n    def __init__(\n        self,\n        url: str | URL,\n        status_code: int = 307,\n        headers: Mapping[str, str] | None = None,\n        background: BackgroundTask | None = None,\n    ) -> None:\n        super().__init__(content=b\"\", status_code=status_code, headers=headers, background=background)\n        self.headers[\"location\"] = quote(str(url), safe=\":/%#?=@[]!$&'()*+,;\")\n\n\nContent = str | bytes | memoryview\nSyncContentStream = Iterable[Content]\nAsyncContentStream = AsyncIterable[Content]\nContentStream = AsyncContentStream | SyncContentStream\n\n\nclass StreamingResponse(Response):\n    body_iterator: AsyncContentStream\n\n    def __init__(\n        self,\n        content: ContentStream,\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n    ) -> None:\n        if isinstance(content, AsyncIterable):\n            self.body_iterator = content\n        else:\n            self.body_iterator = iterate_in_threadpool(content)\n        self.status_code = status_code\n        self.media_type = self.media_type if media_type is None else media_type\n        self.background = background\n        self.init_headers(headers)\n\n    async def listen_for_disconnect(self, receive: Receive) -> None:\n        while True:\n            message = await receive()\n            if message[\"type\"] == \"http.disconnect\":\n                break\n\n    async def stream_response(self, send: Send) -> None:\n        await send({\"type\": \"http.response.start\", \"status\": self.status_code, \"headers\": self.raw_headers})\n        async for chunk in self.body_iterator:\n            if not isinstance(chunk, bytes | memoryview):\n                chunk = chunk.encode(self.charset)\n            await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True})\n\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] == \"websocket\":\n            send = self._wrap_websocket_denial_send(send)\n            await self.stream_response(send)\n            if self.background is not None:\n                await self.background()\n            return\n\n        spec_version = tuple(map(int, scope.get(\"asgi\", {}).get(\"spec_version\", \"2.0\").split(\".\")))\n\n        if spec_version >= (2, 4):\n            try:\n                await self.stream_response(send)\n            except OSError:\n                raise ClientDisconnect()\n        else:\n            with collapse_excgroups():\n                async with anyio.create_task_group() as task_group:\n\n                    async def wrap(func: Callable[[], Awaitable[None]]) -> None:\n                        await func()\n                        task_group.cancel_scope.cancel()\n\n                    task_group.start_soon(wrap, partial(self.stream_response, send))\n                    await wrap(partial(self.listen_for_disconnect, receive))\n\n        if self.background is not None:\n            await self.background()\n\n\nclass MalformedRangeHeader(Exception):\n    def __init__(self, content: str = \"Malformed range header.\") -> None:\n        self.content = content\n\n\nclass RangeNotSatisfiable(Exception):\n    def __init__(self, max_size: int) -> None:\n        self.max_size = max_size\n\n\nclass FileResponse(Response):\n    chunk_size = 64 * 1024\n\n    def __init__(\n        self,\n        path: str | os.PathLike[str],\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n        filename: str | None = None,\n        stat_result: os.stat_result | None = None,\n        content_disposition_type: str = \"attachment\",\n    ) -> None:\n        self.path = path\n        self.status_code = status_code\n        self.filename = filename\n        if media_type is None:\n            media_type = guess_type(filename or path)[0] or \"text/plain\"\n        self.media_type = media_type\n        self.background = background\n        self.init_headers(headers)\n        self.headers.setdefault(\"accept-ranges\", \"bytes\")\n        if self.filename is not None:\n            content_disposition_filename = quote(self.filename)\n            if content_disposition_filename != self.filename:\n                content_disposition = f\"{content_disposition_type}; filename*=utf-8''{content_disposition_filename}\"\n            else:\n                content_disposition = f'{content_disposition_type}; filename=\"{self.filename}\"'\n            self.headers.setdefault(\"content-disposition\", content_disposition)\n        self.stat_result = stat_result\n        if stat_result is not None:\n            self.set_stat_headers(stat_result)\n\n    def set_stat_headers(self, stat_result: os.stat_result) -> None:\n        content_length = str(stat_result.st_size)\n        last_modified = formatdate(stat_result.st_mtime, usegmt=True)\n        etag_base = str(stat_result.st_mtime) + \"-\" + str(stat_result.st_size)\n        etag = f'\"{hashlib.md5(etag_base.encode(), usedforsecurity=False).hexdigest()}\"'\n\n        self.headers.setdefault(\"content-length\", content_length)\n        self.headers.setdefault(\"last-modified\", last_modified)\n        self.headers.setdefault(\"etag\", etag)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        scope_type = scope[\"type\"]\n        send_header_only = scope_type == \"http\" and scope[\"method\"].upper() == \"HEAD\"\n        send_pathsend = scope_type == \"http\" and \"http.response.pathsend\" in scope.get(\"extensions\", {})\n        if scope_type == \"websocket\":\n            send = self._wrap_websocket_denial_send(send)\n\n        if self.stat_result is None:\n            try:\n                stat_result = await anyio.to_thread.run_sync(os.stat, self.path)\n                self.set_stat_headers(stat_result)\n            except FileNotFoundError:\n                raise RuntimeError(f\"File at path {self.path} does not exist.\")\n            else:\n                mode = stat_result.st_mode\n                if not stat.S_ISREG(mode):\n                    raise RuntimeError(f\"File at path {self.path} is not a file.\")\n        else:\n            stat_result = self.stat_result\n\n        headers = Headers(scope=scope)\n        http_range = headers.get(\"range\")\n        http_if_range = headers.get(\"if-range\")\n\n        if http_range is None or (http_if_range is not None and not self._should_use_range(http_if_range)):\n            await self._handle_simple(send, send_header_only, send_pathsend)\n        else:\n            try:\n                ranges = self._parse_range_header(http_range, stat_result.st_size)\n            except MalformedRangeHeader as exc:\n                return await PlainTextResponse(exc.content, status_code=400)(scope, receive, send)\n            except RangeNotSatisfiable as exc:\n                response = PlainTextResponse(status_code=416, headers={\"Content-Range\": f\"bytes */{exc.max_size}\"})\n                return await response(scope, receive, send)\n\n            if len(ranges) == 1:\n                start, end = ranges[0]\n                await self._handle_single_range(send, start, end, stat_result.st_size, send_header_only)\n            else:\n                await self._handle_multiple_ranges(send, ranges, stat_result.st_size, send_header_only)\n\n        if self.background is not None:\n            await self.background()\n\n    async def _handle_simple(self, send: Send, send_header_only: bool, send_pathsend: bool) -> None:\n        await send({\"type\": \"http.response.start\", \"status\": self.status_code, \"headers\": self.raw_headers})\n        if send_header_only:\n            await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n        elif send_pathsend:\n            await send({\"type\": \"http.response.pathsend\", \"path\": str(self.path)})\n        else:\n            async with await anyio.open_file(self.path, mode=\"rb\") as file:\n                more_body = True\n                while more_body:\n                    chunk = await file.read(self.chunk_size)\n                    more_body = len(chunk) == self.chunk_size\n                    await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": more_body})\n\n    async def _handle_single_range(\n        self, send: Send, start: int, end: int, file_size: int, send_header_only: bool\n    ) -> None:\n        headers = MutableHeaders(raw=list(self.raw_headers))\n        headers[\"content-range\"] = f\"bytes {start}-{end - 1}/{file_size}\"\n        headers[\"content-length\"] = str(end - start)\n        await send({\"type\": \"http.response.start\", \"status\": 206, \"headers\": headers.raw})\n        if send_header_only:\n            await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n        else:\n            async with await anyio.open_file(self.path, mode=\"rb\") as file:\n                await file.seek(start)\n                more_body = True\n                while more_body:\n                    chunk = await file.read(min(self.chunk_size, end - start))\n                    start += len(chunk)\n                    more_body = len(chunk) == self.chunk_size and start < end\n                    await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": more_body})\n\n    async def _handle_multiple_ranges(\n        self,\n        send: Send,\n        ranges: list[tuple[int, int]],\n        file_size: int,\n        send_header_only: bool,\n    ) -> None:\n        # In firefox and chrome, they use boundary with 95-96 bits entropy (that's roughly 13 bytes).\n        boundary = token_hex(13)\n        content_length, header_generator = self.generate_multipart(\n            ranges, boundary, file_size, self.headers[\"content-type\"]\n        )\n        headers = MutableHeaders(raw=list(self.raw_headers))\n        headers[\"content-type\"] = f\"multipart/byteranges; boundary={boundary}\"\n        headers[\"content-length\"] = str(content_length)\n        await send({\"type\": \"http.response.start\", \"status\": 206, \"headers\": headers.raw})\n        if send_header_only:\n            await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n        else:\n            async with await anyio.open_file(self.path, mode=\"rb\") as file:\n                for start, end in ranges:\n                    await send({\"type\": \"http.response.body\", \"body\": header_generator(start, end), \"more_body\": True})\n                    await file.seek(start)\n                    while start < end:\n                        chunk = await file.read(min(self.chunk_size, end - start))\n                        start += len(chunk)\n                        await send({\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": True})\n                    await send({\"type\": \"http.response.body\", \"body\": b\"\\r\\n\", \"more_body\": True})\n                await send(\n                    {\n                        \"type\": \"http.response.body\",\n                        \"body\": f\"--{boundary}--\".encode(\"latin-1\"),\n                        \"more_body\": False,\n                    }\n                )\n\n    def _should_use_range(self, http_if_range: str) -> bool:\n        return http_if_range == self.headers[\"last-modified\"] or http_if_range == self.headers[\"etag\"]\n\n    @classmethod\n    def _parse_range_header(cls, http_range: str, file_size: int) -> list[tuple[int, int]]:\n        ranges: list[tuple[int, int]] = []\n        try:\n            units, range_ = http_range.split(\"=\", 1)\n        except ValueError:\n            raise MalformedRangeHeader()\n\n        units = units.strip().lower()\n\n        if units != \"bytes\":\n            raise MalformedRangeHeader(\"Only support bytes range\")\n\n        ranges = cls._parse_ranges(range_, file_size)\n\n        if len(ranges) == 0:\n            raise MalformedRangeHeader(\"Range header: range must be requested\")\n\n        if any(not (0 <= start < file_size) for start, _ in ranges):\n            raise RangeNotSatisfiable(file_size)\n\n        if any(start > end for start, end in ranges):\n            raise MalformedRangeHeader(\"Range header: start must be less than end\")\n\n        if len(ranges) == 1:\n            return ranges\n\n        # Merge overlapping ranges\n        ranges.sort()\n        result: list[tuple[int, int]] = [ranges[0]]\n        for start, end in ranges[1:]:\n            last_start, last_end = result[-1]\n            if start <= last_end:\n                result[-1] = (last_start, max(last_end, end))\n            else:\n                result.append((start, end))\n\n        return result\n\n    @classmethod\n    def _parse_ranges(cls, range_: str, file_size: int) -> list[tuple[int, int]]:\n        ranges: list[tuple[int, int]] = []\n\n        for part in range_.split(\",\"):\n            part = part.strip()\n\n            # If the range is empty or a single dash, we ignore it.\n            if not part or part == \"-\":\n                continue\n\n            # If the range is not in the format \"start-end\", we ignore it.\n            if \"-\" not in part:\n                continue\n\n            start_str, end_str = part.split(\"-\", 1)\n            start_str = start_str.strip()\n            end_str = end_str.strip()\n\n            try:\n                start = int(start_str) if start_str else file_size - int(end_str)\n                end = int(end_str) + 1 if start_str and end_str and int(end_str) < file_size else file_size\n                ranges.append((start, end))\n            except ValueError:\n                # If the range is not numeric, we ignore it.\n                continue\n\n        return ranges\n\n    def generate_multipart(\n        self,\n        ranges: Sequence[tuple[int, int]],\n        boundary: str,\n        max_size: int,\n        content_type: str,\n    ) -> tuple[int, Callable[[int, int], bytes]]:\n        r\"\"\"\n        Multipart response headers generator.\n\n        ```\n        --{boundary}\\r\\n\n        Content-Type: {content_type}\\r\\n\n        Content-Range: bytes {start}-{end-1}/{max_size}\\r\\n\n        \\r\\n\n        ..........content...........\\r\\n\n        --{boundary}\\r\\n\n        Content-Type: {content_type}\\r\\n\n        Content-Range: bytes {start}-{end-1}/{max_size}\\r\\n\n        \\r\\n\n        ..........content...........\\r\\n\n        --{boundary}--\n        ```\n        \"\"\"\n        boundary_len = len(boundary)\n        static_header_part_len = 49 + boundary_len + len(content_type) + len(str(max_size))\n        content_length = sum(\n            (len(str(start)) + len(str(end - 1)) + static_header_part_len)  # Headers\n            + (end - start)  # Content\n            for start, end in ranges\n        ) + (\n            4 + boundary_len  # --boundary--\n        )\n        return (\n            content_length,\n            lambda start, end: (\n                f\"\"\"\\\n--{boundary}\\r\nContent-Type: {content_type}\\r\nContent-Range: bytes {start}-{end - 1}/{max_size}\\r\n\\r\n\"\"\"\n            ).encode(\"latin-1\"),\n        )\n"
  },
  {
    "path": "starlette/routing.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport functools\nimport inspect\nimport re\nimport traceback\nimport types\nimport warnings\nfrom collections.abc import Awaitable, Callable, Collection, Generator, Sequence\nfrom contextlib import AbstractAsyncContextManager, AbstractContextManager, asynccontextmanager\nfrom enum import Enum\nfrom re import Pattern\nfrom typing import Any, TypeVar\n\nfrom starlette._exception_handler import wrap_app_handling_exceptions\nfrom starlette._utils import get_route_path, is_async_callable\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.convertors import CONVERTOR_TYPES, Convertor\nfrom starlette.datastructures import URL, Headers, URLPath\nfrom starlette.exceptions import HTTPException\nfrom starlette.middleware import Middleware\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse, RedirectResponse, Response\nfrom starlette.types import ASGIApp, Lifespan, Receive, Scope, Send\nfrom starlette.websockets import WebSocket, WebSocketClose\n\n\nclass NoMatchFound(Exception):\n    \"\"\"\n    Raised by `.url_for(name, **path_params)` and `.url_path_for(name, **path_params)`\n    if no matching route exists.\n    \"\"\"\n\n    def __init__(self, name: str, path_params: dict[str, Any]) -> None:\n        params = \", \".join(list(path_params.keys()))\n        super().__init__(f'No route exists for name \"{name}\" and params \"{params}\".')\n\n\nclass Match(Enum):\n    NONE = 0\n    PARTIAL = 1\n    FULL = 2\n\n\ndef request_response(\n    func: Callable[[Request], Awaitable[Response] | Response],\n) -> ASGIApp:\n    \"\"\"\n    Takes a function or coroutine `func(request) -> response`,\n    and returns an ASGI application.\n    \"\"\"\n    f: Callable[[Request], Awaitable[Response]] = (\n        func if is_async_callable(func) else functools.partial(run_in_threadpool, func)\n    )\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive, send)\n\n        async def app(scope: Scope, receive: Receive, send: Send) -> None:\n            response = await f(request)\n            await response(scope, receive, send)\n\n        await wrap_app_handling_exceptions(app, request)(scope, receive, send)\n\n    return app\n\n\ndef websocket_session(\n    func: Callable[[WebSocket], Awaitable[None]],\n) -> ASGIApp:\n    \"\"\"\n    Takes a coroutine `func(session)`, and returns an ASGI application.\n    \"\"\"\n    # assert asyncio.iscoroutinefunction(func), \"WebSocket endpoints must be async\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        session = WebSocket(scope, receive=receive, send=send)\n\n        async def app(scope: Scope, receive: Receive, send: Send) -> None:\n            await func(session)\n\n        await wrap_app_handling_exceptions(app, session)(scope, receive, send)\n\n    return app\n\n\ndef get_name(endpoint: Callable[..., Any]) -> str:\n    return getattr(endpoint, \"__name__\", endpoint.__class__.__name__)\n\n\ndef replace_params(\n    path: str,\n    param_convertors: dict[str, Convertor[Any]],\n    path_params: dict[str, str],\n) -> tuple[str, dict[str, str]]:\n    for key, value in list(path_params.items()):\n        if \"{\" + key + \"}\" in path:\n            convertor = param_convertors[key]\n            value = convertor.to_string(value)\n            path = path.replace(\"{\" + key + \"}\", value)\n            path_params.pop(key)\n    return path, path_params\n\n\n# Match parameters in URL paths, eg. '{param}', and '{param:int}'\nPARAM_REGEX = re.compile(\"{([a-zA-Z_][a-zA-Z0-9_]*)(:[a-zA-Z_][a-zA-Z0-9_]*)?}\")\n\n\ndef compile_path(\n    path: str,\n) -> tuple[Pattern[str], str, dict[str, Convertor[Any]]]:\n    \"\"\"\n    Given a path string, like: \"/{username:str}\",\n    or a host string, like: \"{subdomain}.mydomain.org\", return a three-tuple\n    of (regex, format, {param_name:convertor}).\n\n    regex:      \"/(?P<username>[^/]+)\"\n    format:     \"/{username}\"\n    convertors: {\"username\": StringConvertor()}\n    \"\"\"\n    is_host = not path.startswith(\"/\")\n\n    path_regex = \"^\"\n    path_format = \"\"\n    duplicated_params: set[str] = set()\n\n    idx = 0\n    param_convertors = {}\n    for match in PARAM_REGEX.finditer(path):\n        param_name, convertor_type = match.groups(\"str\")\n        convertor_type = convertor_type.lstrip(\":\")\n        assert convertor_type in CONVERTOR_TYPES, f\"Unknown path convertor '{convertor_type}'\"\n        convertor = CONVERTOR_TYPES[convertor_type]\n\n        path_regex += re.escape(path[idx : match.start()])\n        path_regex += f\"(?P<{param_name}>{convertor.regex})\"\n\n        path_format += path[idx : match.start()]\n        path_format += \"{%s}\" % param_name\n\n        if param_name in param_convertors:\n            duplicated_params.add(param_name)\n\n        param_convertors[param_name] = convertor\n\n        idx = match.end()\n\n    if duplicated_params:\n        names = \", \".join(sorted(duplicated_params))\n        ending = \"s\" if len(duplicated_params) > 1 else \"\"\n        raise ValueError(f\"Duplicated param name{ending} {names} at path {path}\")\n\n    if is_host:\n        # Align with `Host.matches()` behavior, which ignores port.\n        hostname = path[idx:].split(\":\")[0]\n        path_regex += re.escape(hostname) + \"$\"\n    else:\n        path_regex += re.escape(path[idx:]) + \"$\"\n\n    path_format += path[idx:]\n\n    return re.compile(path_regex), path_format, param_convertors\n\n\nclass BaseRoute:\n    def matches(self, scope: Scope) -> tuple[Match, Scope]:\n        raise NotImplementedError()  # pragma: no cover\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        raise NotImplementedError()  # pragma: no cover\n\n    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:\n        raise NotImplementedError()  # pragma: no cover\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"\n        A route may be used in isolation as a stand-alone ASGI app.\n        This is a somewhat contrived case, as they'll almost always be used\n        within a Router, but could be useful for some tooling and minimal apps.\n        \"\"\"\n        match, child_scope = self.matches(scope)\n        if match == Match.NONE:\n            if scope[\"type\"] == \"http\":\n                response = PlainTextResponse(\"Not Found\", status_code=404)\n                await response(scope, receive, send)\n            elif scope[\"type\"] == \"websocket\":  # pragma: no branch\n                websocket_close = WebSocketClose()\n                await websocket_close(scope, receive, send)\n            return\n\n        scope.update(child_scope)\n        await self.handle(scope, receive, send)\n\n\nclass Route(BaseRoute):\n    def __init__(\n        self,\n        path: str,\n        endpoint: Callable[..., Any],\n        *,\n        methods: Collection[str] | None = None,\n        name: str | None = None,\n        include_in_schema: bool = True,\n        middleware: Sequence[Middleware] | None = None,\n    ) -> None:\n        assert path.startswith(\"/\"), \"Routed paths must start with '/'\"\n        self.path = path\n        self.endpoint = endpoint\n        self.name = get_name(endpoint) if name is None else name\n        self.include_in_schema = include_in_schema\n\n        endpoint_handler = endpoint\n        while isinstance(endpoint_handler, functools.partial):\n            endpoint_handler = endpoint_handler.func\n        if inspect.isfunction(endpoint_handler) or inspect.ismethod(endpoint_handler):\n            # Endpoint is function or method. Treat it as `func(request) -> response`.\n            self.app = request_response(endpoint)\n            if methods is None:\n                methods = [\"GET\"]\n        else:\n            # Endpoint is a class. Treat it as ASGI.\n            self.app = endpoint\n\n        if middleware is not None:\n            for cls, args, kwargs in reversed(middleware):\n                self.app = cls(self.app, *args, **kwargs)\n\n        if methods is None:\n            self.methods = None\n        else:\n            self.methods = {method.upper() for method in methods}\n            if \"GET\" in self.methods:\n                self.methods.add(\"HEAD\")\n\n        self.path_regex, self.path_format, self.param_convertors = compile_path(path)\n\n    def matches(self, scope: Scope) -> tuple[Match, Scope]:\n        path_params: dict[str, Any]\n        if scope[\"type\"] == \"http\":\n            route_path = get_route_path(scope)\n            match = self.path_regex.match(route_path)\n            if match:\n                matched_params = match.groupdict()\n                for key, value in matched_params.items():\n                    matched_params[key] = self.param_convertors[key].convert(value)\n                path_params = dict(scope.get(\"path_params\", {}))\n                path_params.update(matched_params)\n                child_scope = {\"endpoint\": self.endpoint, \"path_params\": path_params}\n                if self.methods and scope[\"method\"] not in self.methods:\n                    return Match.PARTIAL, child_scope\n                else:\n                    return Match.FULL, child_scope\n        return Match.NONE, {}\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        seen_params = set(path_params.keys())\n        expected_params = set(self.param_convertors.keys())\n\n        if name != self.name or seen_params != expected_params:\n            raise NoMatchFound(name, path_params)\n\n        path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params)\n        assert not remaining_params\n        return URLPath(path=path, protocol=\"http\")\n\n    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if self.methods and scope[\"method\"] not in self.methods:\n            headers = {\"Allow\": \", \".join(self.methods)}\n            if \"app\" in scope:\n                raise HTTPException(status_code=405, headers=headers)\n            else:\n                response = PlainTextResponse(\"Method Not Allowed\", status_code=405, headers=headers)\n            await response(scope, receive, send)\n        else:\n            await self.app(scope, receive, send)\n\n    def __eq__(self, other: Any) -> bool:\n        return (\n            isinstance(other, Route)\n            and self.path == other.path\n            and self.endpoint == other.endpoint\n            and self.methods == other.methods\n        )\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        methods = sorted(self.methods or [])\n        path, name = self.path, self.name\n        return f\"{class_name}(path={path!r}, name={name!r}, methods={methods!r})\"\n\n\nclass WebSocketRoute(BaseRoute):\n    def __init__(\n        self,\n        path: str,\n        endpoint: Callable[..., Any],\n        *,\n        name: str | None = None,\n        middleware: Sequence[Middleware] | None = None,\n    ) -> None:\n        assert path.startswith(\"/\"), \"Routed paths must start with '/'\"\n        self.path = path\n        self.endpoint = endpoint\n        self.name = get_name(endpoint) if name is None else name\n\n        endpoint_handler = endpoint\n        while isinstance(endpoint_handler, functools.partial):\n            endpoint_handler = endpoint_handler.func\n        if inspect.isfunction(endpoint_handler) or inspect.ismethod(endpoint_handler):\n            # Endpoint is function or method. Treat it as `func(websocket)`.\n            self.app = websocket_session(endpoint)\n        else:\n            # Endpoint is a class. Treat it as ASGI.\n            self.app = endpoint\n\n        if middleware is not None:\n            for cls, args, kwargs in reversed(middleware):\n                self.app = cls(self.app, *args, **kwargs)\n\n        self.path_regex, self.path_format, self.param_convertors = compile_path(path)\n\n    def matches(self, scope: Scope) -> tuple[Match, Scope]:\n        path_params: dict[str, Any]\n        if scope[\"type\"] == \"websocket\":\n            route_path = get_route_path(scope)\n            match = self.path_regex.match(route_path)\n            if match:\n                matched_params = match.groupdict()\n                for key, value in matched_params.items():\n                    matched_params[key] = self.param_convertors[key].convert(value)\n                path_params = dict(scope.get(\"path_params\", {}))\n                path_params.update(matched_params)\n                child_scope = {\"endpoint\": self.endpoint, \"path_params\": path_params}\n                return Match.FULL, child_scope\n        return Match.NONE, {}\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        seen_params = set(path_params.keys())\n        expected_params = set(self.param_convertors.keys())\n\n        if name != self.name or seen_params != expected_params:\n            raise NoMatchFound(name, path_params)\n\n        path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params)\n        assert not remaining_params\n        return URLPath(path=path, protocol=\"websocket\")\n\n    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await self.app(scope, receive, send)\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, WebSocketRoute) and self.path == other.path and self.endpoint == other.endpoint\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}(path={self.path!r}, name={self.name!r})\"\n\n\nclass Mount(BaseRoute):\n    def __init__(\n        self,\n        path: str,\n        app: ASGIApp | None = None,\n        routes: Sequence[BaseRoute] | None = None,\n        name: str | None = None,\n        *,\n        middleware: Sequence[Middleware] | None = None,\n    ) -> None:\n        assert path == \"\" or path.startswith(\"/\"), \"Routed paths must start with '/'\"\n        assert app is not None or routes is not None, \"Either 'app=...', or 'routes=' must be specified\"\n        self.path = path.rstrip(\"/\")\n        if app is not None:\n            self._base_app: ASGIApp = app\n        else:\n            self._base_app = Router(routes=routes)\n        self.app = self._base_app\n        if middleware is not None:\n            for cls, args, kwargs in reversed(middleware):\n                self.app = cls(self.app, *args, **kwargs)\n        self.name = name\n        self.path_regex, self.path_format, self.param_convertors = compile_path(self.path + \"/{path:path}\")\n\n    @property\n    def routes(self) -> list[BaseRoute]:\n        return getattr(self._base_app, \"routes\", [])\n\n    def matches(self, scope: Scope) -> tuple[Match, Scope]:\n        path_params: dict[str, Any]\n        if scope[\"type\"] in (\"http\", \"websocket\"):  # pragma: no branch\n            root_path = scope.get(\"root_path\", \"\")\n            route_path = get_route_path(scope)\n            match = self.path_regex.match(route_path)\n            if match:\n                matched_params = match.groupdict()\n                for key, value in matched_params.items():\n                    matched_params[key] = self.param_convertors[key].convert(value)\n                remaining_path = \"/\" + matched_params.pop(\"path\")\n                matched_path = route_path[: -len(remaining_path)]\n                path_params = dict(scope.get(\"path_params\", {}))\n                path_params.update(matched_params)\n                child_scope = {\n                    \"path_params\": path_params,\n                    # app_root_path will only be set at the top level scope,\n                    # initialized with the (optional) value of a root_path\n                    # set above/before Starlette. And even though any\n                    # mount will have its own child scope with its own respective\n                    # root_path, the app_root_path will always be available in all\n                    # the child scopes with the same top level value because it's\n                    # set only once here with a default, any other child scope will\n                    # just inherit that app_root_path default value stored in the\n                    # scope. All this is needed to support Request.url_for(), as it\n                    # uses the app_root_path to build the URL path.\n                    \"app_root_path\": scope.get(\"app_root_path\", root_path),\n                    \"root_path\": root_path + matched_path,\n                    \"endpoint\": self.app,\n                }\n                return Match.FULL, child_scope\n        return Match.NONE, {}\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        if self.name is not None and name == self.name and \"path\" in path_params:\n            # 'name' matches \"<mount_name>\".\n            path_params[\"path\"] = path_params[\"path\"].lstrip(\"/\")\n            path, remaining_params = replace_params(self.path_format, self.param_convertors, path_params)\n            if not remaining_params:\n                return URLPath(path=path)\n        elif self.name is None or name.startswith(self.name + \":\"):\n            if self.name is None:\n                # No mount name.\n                remaining_name = name\n            else:\n                # 'name' matches \"<mount_name>:<child_name>\".\n                remaining_name = name[len(self.name) + 1 :]\n            path_kwarg = path_params.get(\"path\")\n            path_params[\"path\"] = \"\"\n            path_prefix, remaining_params = replace_params(self.path_format, self.param_convertors, path_params)\n            if path_kwarg is not None:\n                remaining_params[\"path\"] = path_kwarg\n            for route in self.routes or []:\n                try:\n                    url = route.url_path_for(remaining_name, **remaining_params)\n                    return URLPath(path=path_prefix.rstrip(\"/\") + str(url), protocol=url.protocol)\n                except NoMatchFound:\n                    pass\n        raise NoMatchFound(name, path_params)\n\n    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await self.app(scope, receive, send)\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, Mount) and self.path == other.path and self.app == other.app\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        name = self.name or \"\"\n        return f\"{class_name}(path={self.path!r}, name={name!r}, app={self.app!r})\"\n\n\nclass Host(BaseRoute):\n    def __init__(self, host: str, app: ASGIApp, name: str | None = None) -> None:\n        assert not host.startswith(\"/\"), \"Host must not start with '/'\"\n        self.host = host\n        self.app = app\n        self.name = name\n        self.host_regex, self.host_format, self.param_convertors = compile_path(host)\n\n    @property\n    def routes(self) -> list[BaseRoute]:\n        return getattr(self.app, \"routes\", [])\n\n    def matches(self, scope: Scope) -> tuple[Match, Scope]:\n        if scope[\"type\"] in (\"http\", \"websocket\"):  # pragma:no branch\n            headers = Headers(scope=scope)\n            host = headers.get(\"host\", \"\").split(\":\")[0]\n            match = self.host_regex.match(host)\n            if match:\n                matched_params = match.groupdict()\n                for key, value in matched_params.items():\n                    matched_params[key] = self.param_convertors[key].convert(value)\n                path_params = dict(scope.get(\"path_params\", {}))\n                path_params.update(matched_params)\n                child_scope = {\"path_params\": path_params, \"endpoint\": self.app}\n                return Match.FULL, child_scope\n        return Match.NONE, {}\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        if self.name is not None and name == self.name and \"path\" in path_params:\n            # 'name' matches \"<mount_name>\".\n            path = path_params.pop(\"path\")\n            host, remaining_params = replace_params(self.host_format, self.param_convertors, path_params)\n            if not remaining_params:\n                return URLPath(path=path, host=host)\n        elif self.name is None or name.startswith(self.name + \":\"):\n            if self.name is None:\n                # No mount name.\n                remaining_name = name\n            else:\n                # 'name' matches \"<mount_name>:<child_name>\".\n                remaining_name = name[len(self.name) + 1 :]\n            host, remaining_params = replace_params(self.host_format, self.param_convertors, path_params)\n            for route in self.routes or []:\n                try:\n                    url = route.url_path_for(remaining_name, **remaining_params)\n                    return URLPath(path=str(url), protocol=url.protocol, host=host)\n                except NoMatchFound:\n                    pass\n        raise NoMatchFound(name, path_params)\n\n    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await self.app(scope, receive, send)\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, Host) and self.host == other.host and self.app == other.app\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        name = self.name or \"\"\n        return f\"{class_name}(host={self.host!r}, name={name!r}, app={self.app!r})\"\n\n\n_T = TypeVar(\"_T\")\n\n\nclass _AsyncLiftContextManager(AbstractAsyncContextManager[_T]):\n    def __init__(self, cm: AbstractContextManager[_T]):\n        self._cm = cm\n\n    async def __aenter__(self) -> _T:\n        return self._cm.__enter__()\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: types.TracebackType | None,\n    ) -> bool | None:\n        return self._cm.__exit__(exc_type, exc_value, traceback)\n\n\ndef _wrap_gen_lifespan_context(\n    lifespan_context: Callable[[Any], Generator[Any, Any, Any]],\n) -> Callable[[Any], AbstractAsyncContextManager[Any]]:\n    cmgr = contextlib.contextmanager(lifespan_context)\n\n    @functools.wraps(cmgr)\n    def wrapper(app: Any) -> _AsyncLiftContextManager[Any]:\n        return _AsyncLiftContextManager(cmgr(app))\n\n    return wrapper\n\n\nclass _DefaultLifespan:\n    def __init__(self, router: Router):\n        self._router = router\n\n    async def __aenter__(self) -> None:\n        pass\n\n    async def __aexit__(self, *exc_info: object) -> None:\n        pass\n\n    def __call__(self: _T, app: object) -> _T:\n        return self\n\n\nclass Router:\n    def __init__(\n        self,\n        routes: Sequence[BaseRoute] | None = None,\n        redirect_slashes: bool = True,\n        default: ASGIApp | None = None,\n        # the generic to Lifespan[AppType] is the type of the top level application\n        # which the router cannot know statically, so we use Any\n        lifespan: Lifespan[Any] | None = None,\n        *,\n        middleware: Sequence[Middleware] | None = None,\n    ) -> None:\n        self.routes = [] if routes is None else list(routes)\n        self.redirect_slashes = redirect_slashes\n        self.default = self.not_found if default is None else default\n\n        if lifespan is None:\n            self.lifespan_context: Lifespan[Any] = _DefaultLifespan(self)\n\n        elif inspect.isasyncgenfunction(lifespan):\n            warnings.warn(\n                \"async generator function lifespans are deprecated, \"\n                \"use an @contextlib.asynccontextmanager function instead\",\n                DeprecationWarning,\n            )\n            self.lifespan_context = asynccontextmanager(lifespan)\n        elif inspect.isgeneratorfunction(lifespan):\n            warnings.warn(\n                \"generator function lifespans are deprecated, use an @contextlib.asynccontextmanager function instead\",\n                DeprecationWarning,\n            )\n            self.lifespan_context = _wrap_gen_lifespan_context(lifespan)\n        else:\n            self.lifespan_context = lifespan\n\n        self.middleware_stack = self.app\n        if middleware:\n            for cls, args, kwargs in reversed(middleware):\n                self.middleware_stack = cls(self.middleware_stack, *args, **kwargs)\n\n    async def not_found(self, scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"type\"] == \"websocket\":\n            websocket_close = WebSocketClose()\n            await websocket_close(scope, receive, send)\n            return\n\n        # If we're running inside a starlette application then raise an\n        # exception, so that the configurable exception handler can deal with\n        # returning the response. For plain ASGI apps, just return the response.\n        if \"app\" in scope:\n            raise HTTPException(status_code=404)\n        else:\n            response = PlainTextResponse(\"Not Found\", status_code=404)\n        await response(scope, receive, send)\n\n    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:\n        for route in self.routes:\n            try:\n                return route.url_path_for(name, **path_params)\n            except NoMatchFound:\n                pass\n        raise NoMatchFound(name, path_params)\n\n    async def lifespan(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"\n        Handle ASGI lifespan messages, which allows us to manage application\n        startup and shutdown events.\n        \"\"\"\n        started = False\n        app: Any = scope.get(\"app\")\n        await receive()\n        try:\n            async with self.lifespan_context(app) as maybe_state:\n                if maybe_state is not None:\n                    if \"state\" not in scope:\n                        raise RuntimeError('The server does not support \"state\" in the lifespan scope.')\n                    scope[\"state\"].update(maybe_state)\n                await send({\"type\": \"lifespan.startup.complete\"})\n                started = True\n                await receive()\n        except BaseException:\n            exc_text = traceback.format_exc()\n            if started:\n                await send({\"type\": \"lifespan.shutdown.failed\", \"message\": exc_text})\n            else:\n                await send({\"type\": \"lifespan.startup.failed\", \"message\": exc_text})\n            raise\n        else:\n            await send({\"type\": \"lifespan.shutdown.complete\"})\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"\n        The main entry point to the Router class.\n        \"\"\"\n        await self.middleware_stack(scope, receive, send)\n\n    async def app(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] in (\"http\", \"websocket\", \"lifespan\")\n\n        if \"router\" not in scope:\n            scope[\"router\"] = self\n\n        if scope[\"type\"] == \"lifespan\":\n            await self.lifespan(scope, receive, send)\n            return\n\n        partial = None\n\n        for route in self.routes:\n            # Determine if any route matches the incoming scope,\n            # and hand over to the matching route if found.\n            match, child_scope = route.matches(scope)\n            if match == Match.FULL:\n                scope.update(child_scope)\n                await route.handle(scope, receive, send)\n                return\n            elif match == Match.PARTIAL and partial is None:\n                partial = route\n                partial_scope = child_scope\n\n        if partial is not None:\n            #  Handle partial matches. These are cases where an endpoint is\n            # able to handle the request, but is not a preferred option.\n            # We use this in particular to deal with \"405 Method Not Allowed\".\n            scope.update(partial_scope)\n            await partial.handle(scope, receive, send)\n            return\n\n        route_path = get_route_path(scope)\n        if scope[\"type\"] == \"http\" and self.redirect_slashes and route_path != \"/\":\n            redirect_scope = dict(scope)\n            if route_path.endswith(\"/\"):\n                redirect_scope[\"path\"] = redirect_scope[\"path\"].rstrip(\"/\")\n            else:\n                redirect_scope[\"path\"] = redirect_scope[\"path\"] + \"/\"\n\n            for route in self.routes:\n                match, child_scope = route.matches(redirect_scope)\n                if match != Match.NONE:\n                    redirect_url = URL(scope=redirect_scope)\n                    response = RedirectResponse(url=str(redirect_url))\n                    await response(scope, receive, send)\n                    return\n\n        await self.default(scope, receive, send)\n\n    def __eq__(self, other: Any) -> bool:\n        return isinstance(other, Router) and self.routes == other.routes\n\n    def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:  # pragma: no cover\n        route = Mount(path, app=app, name=name)\n        self.routes.append(route)\n\n    def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:  # pragma: no cover\n        route = Host(host, app=app, name=name)\n        self.routes.append(route)\n\n    def add_route(\n        self,\n        path: str,\n        endpoint: Callable[[Request], Awaitable[Response] | Response],\n        methods: Collection[str] | None = None,\n        name: str | None = None,\n        include_in_schema: bool = True,\n    ) -> None:  # pragma: no cover\n        route = Route(\n            path,\n            endpoint=endpoint,\n            methods=methods,\n            name=name,\n            include_in_schema=include_in_schema,\n        )\n        self.routes.append(route)\n\n    def add_websocket_route(\n        self,\n        path: str,\n        endpoint: Callable[[WebSocket], Awaitable[None]],\n        name: str | None = None,\n    ) -> None:  # pragma: no cover\n        route = WebSocketRoute(path, endpoint=endpoint, name=name)\n        self.routes.append(route)\n"
  },
  {
    "path": "starlette/schemas.py",
    "content": "from __future__ import annotations\n\nimport inspect\nimport re\nfrom collections.abc import Callable\nfrom typing import Any, NamedTuple\n\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import BaseRoute, Host, Mount, Route\n\ntry:\n    import yaml\nexcept ModuleNotFoundError:  # pragma: no cover\n    yaml = None  # type: ignore[assignment]\n\n\nclass OpenAPIResponse(Response):\n    media_type = \"application/vnd.oai.openapi\"\n\n    def render(self, content: Any) -> bytes:\n        assert yaml is not None, \"`pyyaml` must be installed to use OpenAPIResponse.\"\n        assert isinstance(content, dict), \"The schema passed to OpenAPIResponse should be a dictionary.\"\n        return yaml.dump(content, default_flow_style=False).encode(\"utf-8\")\n\n\nclass EndpointInfo(NamedTuple):\n    path: str\n    http_method: str\n    func: Callable[..., Any]\n\n\n_remove_converter_pattern = re.compile(r\":\\w+}\")\n\n\nclass BaseSchemaGenerator:\n    def get_schema(self, routes: list[BaseRoute]) -> dict[str, Any]:\n        raise NotImplementedError()  # pragma: no cover\n\n    def get_endpoints(self, routes: list[BaseRoute]) -> list[EndpointInfo]:\n        \"\"\"\n        Given the routes, yields the following information:\n\n        - path\n            eg: /users/\n        - http_method\n            one of 'get', 'post', 'put', 'patch', 'delete', 'options'\n        - func\n            method ready to extract the docstring\n        \"\"\"\n        endpoints_info: list[EndpointInfo] = []\n\n        for route in routes:\n            if isinstance(route, Mount | Host):\n                routes = route.routes or []\n                if isinstance(route, Mount):\n                    path = self._remove_converter(route.path)\n                else:\n                    path = \"\"\n                sub_endpoints = [\n                    EndpointInfo(\n                        path=\"\".join((path, sub_endpoint.path)),\n                        http_method=sub_endpoint.http_method,\n                        func=sub_endpoint.func,\n                    )\n                    for sub_endpoint in self.get_endpoints(routes)\n                ]\n                endpoints_info.extend(sub_endpoints)\n\n            elif not isinstance(route, Route) or not route.include_in_schema:\n                continue\n\n            elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):\n                path = self._remove_converter(route.path)\n                for method in route.methods or [\"GET\"]:\n                    if method == \"HEAD\":\n                        continue\n                    endpoints_info.append(EndpointInfo(path, method.lower(), route.endpoint))\n            else:\n                path = self._remove_converter(route.path)\n                for method in [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"]:\n                    if not hasattr(route.endpoint, method):\n                        continue\n                    func = getattr(route.endpoint, method)\n                    endpoints_info.append(EndpointInfo(path, method.lower(), func))\n\n        return endpoints_info\n\n    def _remove_converter(self, path: str) -> str:\n        \"\"\"\n        Remove the converter from the path.\n        For example, a route like this:\n            Route(\"/users/{id:int}\", endpoint=get_user, methods=[\"GET\"])\n        Should be represented as `/users/{id}` in the OpenAPI schema.\n        \"\"\"\n        return _remove_converter_pattern.sub(\"}\", path)\n\n    def parse_docstring(self, func_or_method: Callable[..., Any]) -> dict[str, Any]:\n        \"\"\"\n        Given a function, parse the docstring as YAML and return a dictionary of info.\n        \"\"\"\n        docstring = func_or_method.__doc__\n        if not docstring:\n            return {}\n\n        assert yaml is not None, \"`pyyaml` must be installed to use parse_docstring.\"\n\n        # We support having regular docstrings before the schema\n        # definition. Here we return just the schema part from\n        # the docstring.\n        docstring = docstring.split(\"---\")[-1]\n\n        parsed = yaml.safe_load(docstring)\n\n        if not isinstance(parsed, dict):\n            # A regular docstring (not yaml formatted) can return\n            # a simple string here, which wouldn't follow the schema.\n            return {}\n\n        return parsed\n\n    def OpenAPIResponse(self, request: Request) -> Response:\n        routes = request.app.routes\n        schema = self.get_schema(routes=routes)\n        return OpenAPIResponse(schema)\n\n\nclass SchemaGenerator(BaseSchemaGenerator):\n    def __init__(self, base_schema: dict[str, Any]) -> None:\n        self.base_schema = base_schema\n\n    def get_schema(self, routes: list[BaseRoute]) -> dict[str, Any]:\n        schema = dict(self.base_schema)\n        schema.setdefault(\"paths\", {})\n        endpoints_info = self.get_endpoints(routes)\n\n        for endpoint in endpoints_info:\n            parsed = self.parse_docstring(endpoint.func)\n\n            if not parsed:\n                continue\n\n            if endpoint.path not in schema[\"paths\"]:\n                schema[\"paths\"][endpoint.path] = {}\n\n            schema[\"paths\"][endpoint.path][endpoint.http_method] = parsed\n\n        return schema\n"
  },
  {
    "path": "starlette/staticfiles.py",
    "content": "from __future__ import annotations\n\nimport errno\nimport importlib.util\nimport os\nimport stat\nfrom email.utils import parsedate\nfrom typing import Union\n\nimport anyio\nimport anyio.to_thread\n\nfrom starlette._utils import get_route_path\nfrom starlette.datastructures import URL, Headers\nfrom starlette.exceptions import HTTPException\nfrom starlette.responses import FileResponse, RedirectResponse, Response\nfrom starlette.types import Receive, Scope, Send\n\nPathLike = Union[str, \"os.PathLike[str]\"]\n\n\nclass NotModifiedResponse(Response):\n    NOT_MODIFIED_HEADERS = (\n        \"cache-control\",\n        \"content-location\",\n        \"date\",\n        \"etag\",\n        \"expires\",\n        \"vary\",\n    )\n\n    def __init__(self, headers: Headers):\n        super().__init__(\n            status_code=304,\n            headers={name: value for name, value in headers.items() if name in self.NOT_MODIFIED_HEADERS},\n        )\n\n\nclass StaticFiles:\n    def __init__(\n        self,\n        *,\n        directory: PathLike | None = None,\n        packages: list[str | tuple[str, str]] | None = None,\n        html: bool = False,\n        check_dir: bool = True,\n        follow_symlink: bool = False,\n    ) -> None:\n        self.directory = directory\n        self.packages = packages\n        self.all_directories = self.get_directories(directory, packages)\n        self.html = html\n        self.config_checked = False\n        self.follow_symlink = follow_symlink\n        if check_dir and directory is not None and not os.path.isdir(directory):\n            raise RuntimeError(f\"Directory '{directory}' does not exist\")\n\n    def get_directories(\n        self,\n        directory: PathLike | None = None,\n        packages: list[str | tuple[str, str]] | None = None,\n    ) -> list[PathLike]:\n        \"\"\"\n        Given `directory` and `packages` arguments, return a list of all the\n        directories that should be used for serving static files from.\n        \"\"\"\n        directories = []\n        if directory is not None:\n            directories.append(directory)\n\n        for package in packages or []:\n            if isinstance(package, tuple):\n                package, statics_dir = package\n            else:\n                statics_dir = \"statics\"\n            spec = importlib.util.find_spec(package)\n            assert spec is not None, f\"Package {package!r} could not be found.\"\n            assert spec.origin is not None, f\"Package {package!r} could not be found.\"\n            package_directory = os.path.normpath(os.path.join(spec.origin, \"..\", statics_dir))\n            assert os.path.isdir(package_directory), (\n                f\"Directory '{statics_dir!r}' in package {package!r} could not be found.\"\n            )\n            directories.append(package_directory)\n\n        return directories\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        \"\"\"\n        The ASGI entry point.\n        \"\"\"\n        assert scope[\"type\"] == \"http\"\n\n        if not self.config_checked:\n            await self.check_config()\n            self.config_checked = True\n\n        path = self.get_path(scope)\n        response = await self.get_response(path, scope)\n        await response(scope, receive, send)\n\n    def get_path(self, scope: Scope) -> str:\n        \"\"\"\n        Given the ASGI scope, return the `path` string to serve up,\n        with OS specific path separators, and any '..', '.' components removed.\n        \"\"\"\n        route_path = get_route_path(scope)\n        return os.path.normpath(os.path.join(*route_path.split(\"/\")))\n\n    async def get_response(self, path: str, scope: Scope) -> Response:\n        \"\"\"\n        Returns an HTTP response, given the incoming path, method and request headers.\n        \"\"\"\n        if scope[\"method\"] not in (\"GET\", \"HEAD\"):\n            raise HTTPException(status_code=405)\n\n        try:\n            full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, path)\n        except PermissionError:\n            raise HTTPException(status_code=401)\n        except OSError as exc:\n            # Filename is too long, so it can't be a valid static file.\n            if exc.errno == errno.ENAMETOOLONG:\n                raise HTTPException(status_code=404)\n\n            raise exc\n        except ValueError:\n            # Null bytes or other invalid characters in the path.\n            raise HTTPException(status_code=404)\n\n        if stat_result and stat.S_ISREG(stat_result.st_mode):\n            # We have a static file to serve.\n            return self.file_response(full_path, stat_result, scope)\n\n        elif stat_result and stat.S_ISDIR(stat_result.st_mode) and self.html:\n            # We're in HTML mode, and have got a directory URL.\n            # Check if we have 'index.html' file to serve.\n            index_path = os.path.join(path, \"index.html\")\n            full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, index_path)\n            if stat_result is not None and stat.S_ISREG(stat_result.st_mode):\n                if not scope[\"path\"].endswith(\"/\"):\n                    # Directory URLs should redirect to always end in \"/\".\n                    url = URL(scope=scope)\n                    url = url.replace(path=url.path + \"/\")\n                    return RedirectResponse(url=url)\n                return self.file_response(full_path, stat_result, scope)\n\n        if self.html:\n            # Check for '404.html' if we're in HTML mode.\n            full_path, stat_result = await anyio.to_thread.run_sync(self.lookup_path, \"404.html\")\n            if stat_result and stat.S_ISREG(stat_result.st_mode):\n                return FileResponse(full_path, stat_result=stat_result, status_code=404)\n        raise HTTPException(status_code=404)\n\n    def lookup_path(self, path: str) -> tuple[str, os.stat_result | None]:\n        for directory in self.all_directories:\n            joined_path = os.path.join(directory, path)\n            if self.follow_symlink:\n                full_path = os.path.abspath(joined_path)\n                directory = os.path.abspath(directory)\n            else:\n                full_path = os.path.realpath(joined_path)\n                directory = os.path.realpath(directory)\n            if os.path.commonpath([full_path, directory]) != str(directory):\n                # Don't allow misbehaving clients to break out of the static files directory.\n                continue\n            try:\n                return full_path, os.stat(full_path)\n            except (FileNotFoundError, NotADirectoryError):\n                continue\n        return \"\", None\n\n    def file_response(\n        self,\n        full_path: PathLike,\n        stat_result: os.stat_result,\n        scope: Scope,\n        status_code: int = 200,\n    ) -> Response:\n        request_headers = Headers(scope=scope)\n\n        response = FileResponse(full_path, status_code=status_code, stat_result=stat_result)\n        if self.is_not_modified(response.headers, request_headers):\n            return NotModifiedResponse(response.headers)\n        return response\n\n    async def check_config(self) -> None:\n        \"\"\"\n        Perform a one-off configuration check that StaticFiles is actually\n        pointed at a directory, so that we can raise loud errors rather than\n        just returning 404 responses.\n        \"\"\"\n        if self.directory is None:\n            return\n\n        try:\n            stat_result = await anyio.to_thread.run_sync(os.stat, self.directory)\n        except FileNotFoundError:\n            raise RuntimeError(f\"StaticFiles directory '{self.directory}' does not exist.\")\n        if not (stat.S_ISDIR(stat_result.st_mode) or stat.S_ISLNK(stat_result.st_mode)):\n            raise RuntimeError(f\"StaticFiles path '{self.directory}' is not a directory.\")\n\n    def is_not_modified(self, response_headers: Headers, request_headers: Headers) -> bool:\n        \"\"\"\n        Given the request and response headers, return `True` if an HTTP\n        \"Not Modified\" response could be returned instead.\n        \"\"\"\n        if if_none_match := request_headers.get(\"if-none-match\"):\n            # The \"etag\" header is added by FileResponse, so it's always present.\n            etag = response_headers[\"etag\"]\n            return etag in [tag.strip(\" W/\") for tag in if_none_match.split(\",\")]\n\n        try:\n            if_modified_since = parsedate(request_headers[\"if-modified-since\"])\n            last_modified = parsedate(response_headers[\"last-modified\"])\n            if if_modified_since is not None and last_modified is not None and if_modified_since >= last_modified:\n                return True\n        except KeyError:\n            pass\n\n        return False\n"
  },
  {
    "path": "starlette/status.py",
    "content": "\"\"\"\nHTTP codes\nSee HTTP Status Code Registry:\nhttps://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml\n\nAnd RFC 9110 - https://www.rfc-editor.org/rfc/rfc9110\n\"\"\"\n\nfrom __future__ import annotations\n\nimport warnings\n\n__all__ = [\n    \"HTTP_100_CONTINUE\",\n    \"HTTP_101_SWITCHING_PROTOCOLS\",\n    \"HTTP_102_PROCESSING\",\n    \"HTTP_103_EARLY_HINTS\",\n    \"HTTP_200_OK\",\n    \"HTTP_201_CREATED\",\n    \"HTTP_202_ACCEPTED\",\n    \"HTTP_203_NON_AUTHORITATIVE_INFORMATION\",\n    \"HTTP_204_NO_CONTENT\",\n    \"HTTP_205_RESET_CONTENT\",\n    \"HTTP_206_PARTIAL_CONTENT\",\n    \"HTTP_207_MULTI_STATUS\",\n    \"HTTP_208_ALREADY_REPORTED\",\n    \"HTTP_226_IM_USED\",\n    \"HTTP_300_MULTIPLE_CHOICES\",\n    \"HTTP_301_MOVED_PERMANENTLY\",\n    \"HTTP_302_FOUND\",\n    \"HTTP_303_SEE_OTHER\",\n    \"HTTP_304_NOT_MODIFIED\",\n    \"HTTP_305_USE_PROXY\",\n    \"HTTP_306_RESERVED\",\n    \"HTTP_307_TEMPORARY_REDIRECT\",\n    \"HTTP_308_PERMANENT_REDIRECT\",\n    \"HTTP_400_BAD_REQUEST\",\n    \"HTTP_401_UNAUTHORIZED\",\n    \"HTTP_402_PAYMENT_REQUIRED\",\n    \"HTTP_403_FORBIDDEN\",\n    \"HTTP_404_NOT_FOUND\",\n    \"HTTP_405_METHOD_NOT_ALLOWED\",\n    \"HTTP_406_NOT_ACCEPTABLE\",\n    \"HTTP_407_PROXY_AUTHENTICATION_REQUIRED\",\n    \"HTTP_408_REQUEST_TIMEOUT\",\n    \"HTTP_409_CONFLICT\",\n    \"HTTP_410_GONE\",\n    \"HTTP_411_LENGTH_REQUIRED\",\n    \"HTTP_412_PRECONDITION_FAILED\",\n    \"HTTP_413_CONTENT_TOO_LARGE\",\n    \"HTTP_414_URI_TOO_LONG\",\n    \"HTTP_415_UNSUPPORTED_MEDIA_TYPE\",\n    \"HTTP_416_RANGE_NOT_SATISFIABLE\",\n    \"HTTP_417_EXPECTATION_FAILED\",\n    \"HTTP_418_IM_A_TEAPOT\",\n    \"HTTP_421_MISDIRECTED_REQUEST\",\n    \"HTTP_422_UNPROCESSABLE_CONTENT\",\n    \"HTTP_423_LOCKED\",\n    \"HTTP_424_FAILED_DEPENDENCY\",\n    \"HTTP_425_TOO_EARLY\",\n    \"HTTP_426_UPGRADE_REQUIRED\",\n    \"HTTP_428_PRECONDITION_REQUIRED\",\n    \"HTTP_429_TOO_MANY_REQUESTS\",\n    \"HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE\",\n    \"HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS\",\n    \"HTTP_500_INTERNAL_SERVER_ERROR\",\n    \"HTTP_501_NOT_IMPLEMENTED\",\n    \"HTTP_502_BAD_GATEWAY\",\n    \"HTTP_503_SERVICE_UNAVAILABLE\",\n    \"HTTP_504_GATEWAY_TIMEOUT\",\n    \"HTTP_505_HTTP_VERSION_NOT_SUPPORTED\",\n    \"HTTP_506_VARIANT_ALSO_NEGOTIATES\",\n    \"HTTP_507_INSUFFICIENT_STORAGE\",\n    \"HTTP_508_LOOP_DETECTED\",\n    \"HTTP_510_NOT_EXTENDED\",\n    \"HTTP_511_NETWORK_AUTHENTICATION_REQUIRED\",\n    \"WS_1000_NORMAL_CLOSURE\",\n    \"WS_1001_GOING_AWAY\",\n    \"WS_1002_PROTOCOL_ERROR\",\n    \"WS_1003_UNSUPPORTED_DATA\",\n    \"WS_1005_NO_STATUS_RCVD\",\n    \"WS_1006_ABNORMAL_CLOSURE\",\n    \"WS_1007_INVALID_FRAME_PAYLOAD_DATA\",\n    \"WS_1008_POLICY_VIOLATION\",\n    \"WS_1009_MESSAGE_TOO_BIG\",\n    \"WS_1010_MANDATORY_EXT\",\n    \"WS_1011_INTERNAL_ERROR\",\n    \"WS_1012_SERVICE_RESTART\",\n    \"WS_1013_TRY_AGAIN_LATER\",\n    \"WS_1014_BAD_GATEWAY\",\n    \"WS_1015_TLS_HANDSHAKE\",\n]\n\nHTTP_100_CONTINUE = 100\nHTTP_101_SWITCHING_PROTOCOLS = 101\nHTTP_102_PROCESSING = 102\nHTTP_103_EARLY_HINTS = 103\nHTTP_200_OK = 200\nHTTP_201_CREATED = 201\nHTTP_202_ACCEPTED = 202\nHTTP_203_NON_AUTHORITATIVE_INFORMATION = 203\nHTTP_204_NO_CONTENT = 204\nHTTP_205_RESET_CONTENT = 205\nHTTP_206_PARTIAL_CONTENT = 206\nHTTP_207_MULTI_STATUS = 207\nHTTP_208_ALREADY_REPORTED = 208\nHTTP_226_IM_USED = 226\nHTTP_300_MULTIPLE_CHOICES = 300\nHTTP_301_MOVED_PERMANENTLY = 301\nHTTP_302_FOUND = 302\nHTTP_303_SEE_OTHER = 303\nHTTP_304_NOT_MODIFIED = 304\nHTTP_305_USE_PROXY = 305\nHTTP_306_RESERVED = 306\nHTTP_307_TEMPORARY_REDIRECT = 307\nHTTP_308_PERMANENT_REDIRECT = 308\nHTTP_400_BAD_REQUEST = 400\nHTTP_401_UNAUTHORIZED = 401\nHTTP_402_PAYMENT_REQUIRED = 402\nHTTP_403_FORBIDDEN = 403\nHTTP_404_NOT_FOUND = 404\nHTTP_405_METHOD_NOT_ALLOWED = 405\nHTTP_406_NOT_ACCEPTABLE = 406\nHTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407\nHTTP_408_REQUEST_TIMEOUT = 408\nHTTP_409_CONFLICT = 409\nHTTP_410_GONE = 410\nHTTP_411_LENGTH_REQUIRED = 411\nHTTP_412_PRECONDITION_FAILED = 412\nHTTP_413_CONTENT_TOO_LARGE = 413\nHTTP_414_URI_TOO_LONG = 414\nHTTP_415_UNSUPPORTED_MEDIA_TYPE = 415\nHTTP_416_RANGE_NOT_SATISFIABLE = 416\nHTTP_417_EXPECTATION_FAILED = 417\nHTTP_418_IM_A_TEAPOT = 418\nHTTP_421_MISDIRECTED_REQUEST = 421\nHTTP_422_UNPROCESSABLE_CONTENT = 422\nHTTP_423_LOCKED = 423\nHTTP_424_FAILED_DEPENDENCY = 424\nHTTP_425_TOO_EARLY = 425\nHTTP_426_UPGRADE_REQUIRED = 426\nHTTP_428_PRECONDITION_REQUIRED = 428\nHTTP_429_TOO_MANY_REQUESTS = 429\nHTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431\nHTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451\nHTTP_500_INTERNAL_SERVER_ERROR = 500\nHTTP_501_NOT_IMPLEMENTED = 501\nHTTP_502_BAD_GATEWAY = 502\nHTTP_503_SERVICE_UNAVAILABLE = 503\nHTTP_504_GATEWAY_TIMEOUT = 504\nHTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505\nHTTP_506_VARIANT_ALSO_NEGOTIATES = 506\nHTTP_507_INSUFFICIENT_STORAGE = 507\nHTTP_508_LOOP_DETECTED = 508\nHTTP_510_NOT_EXTENDED = 510\nHTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511\n\n\n\"\"\"\nWebSocket codes\nhttps://www.iana.org/assignments/websocket/websocket.xml#close-code-number\nhttps://developer.mozilla.org/en-US/docs/Web/API/CloseEvent\n\"\"\"\nWS_1000_NORMAL_CLOSURE = 1000\nWS_1001_GOING_AWAY = 1001\nWS_1002_PROTOCOL_ERROR = 1002\nWS_1003_UNSUPPORTED_DATA = 1003\nWS_1005_NO_STATUS_RCVD = 1005\nWS_1006_ABNORMAL_CLOSURE = 1006\nWS_1007_INVALID_FRAME_PAYLOAD_DATA = 1007\nWS_1008_POLICY_VIOLATION = 1008\nWS_1009_MESSAGE_TOO_BIG = 1009\nWS_1010_MANDATORY_EXT = 1010\nWS_1011_INTERNAL_ERROR = 1011\nWS_1012_SERVICE_RESTART = 1012\nWS_1013_TRY_AGAIN_LATER = 1013\nWS_1014_BAD_GATEWAY = 1014\nWS_1015_TLS_HANDSHAKE = 1015\n\n__deprecated__ = {\n    \"HTTP_413_REQUEST_ENTITY_TOO_LARGE\": 413,\n    \"HTTP_414_REQUEST_URI_TOO_LONG\": 414,\n    \"HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE\": 416,\n    \"HTTP_422_UNPROCESSABLE_ENTITY\": 422,\n}\n\n\ndef __getattr__(name: str) -> int:\n    deprecation_changes = {\n        \"HTTP_413_REQUEST_ENTITY_TOO_LARGE\": \"HTTP_413_CONTENT_TOO_LARGE\",\n        \"HTTP_414_REQUEST_URI_TOO_LONG\": \"HTTP_414_URI_TOO_LONG\",\n        \"HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE\": \"HTTP_416_RANGE_NOT_SATISFIABLE\",\n        \"HTTP_422_UNPROCESSABLE_ENTITY\": \"HTTP_422_UNPROCESSABLE_CONTENT\",\n    }\n\n    deprecated = __deprecated__.get(name)\n    if deprecated:\n        warnings.warn(\n            f\"'{name}' is deprecated. Use '{deprecation_changes[name]}' instead.\",\n            category=DeprecationWarning,\n            stacklevel=3,\n        )\n        return deprecated\n\n    raise AttributeError(f\"module 'starlette.status' has no attribute '{name}'\")\n\n\ndef __dir__() -> list[str]:\n    return sorted(list(__all__) + list(__deprecated__.keys()))  # pragma: no cover\n"
  },
  {
    "path": "starlette/templating.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable, Mapping, Sequence\nfrom os import PathLike\nfrom typing import TYPE_CHECKING, Any, overload\n\nfrom starlette.background import BackgroundTask\nfrom starlette.datastructures import URL\nfrom starlette.requests import Request\nfrom starlette.responses import HTMLResponse\nfrom starlette.types import Receive, Scope, Send\n\ntry:\n    import jinja2\n\n    # @contextfunction was renamed to @pass_context in Jinja 3.0, and was removed in 3.1\n    # hence we try to get pass_context (most installs will be >=3.1)\n    # and fall back to contextfunction,\n    # adding a type ignore for mypy to let us access an attribute that may not exist\n    if TYPE_CHECKING:\n        pass_context = jinja2.pass_context\n    else:\n        if hasattr(jinja2, \"pass_context\"):\n            pass_context = jinja2.pass_context\n        else:  # pragma: no cover\n            pass_context = jinja2.contextfunction  # type: ignore[attr-defined]\nexcept ImportError as _import_error:  # pragma: no cover\n    raise ImportError(\"jinja2 must be installed to use Jinja2Templates\") from _import_error\n\n\nclass _TemplateResponse(HTMLResponse):\n    def __init__(\n        self,\n        template: Any,\n        context: dict[str, Any],\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n    ):\n        self.template = template\n        self.context = context\n        content = template.render(context)\n        super().__init__(content, status_code, headers, media_type, background)\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        request = self.context.get(\"request\", {})\n        extensions = request.get(\"extensions\", {})\n        if \"http.response.debug\" in extensions:  # pragma: no branch\n            await send({\"type\": \"http.response.debug\", \"info\": {\"template\": self.template, \"context\": self.context}})\n        await super().__call__(scope, receive, send)\n\n\nclass Jinja2Templates:\n    \"\"\"Jinja2 template renderer.\n\n    Example:\n        ```python\n        from starlette.templating import Jinja2Templates\n\n        templates = Jinja2Templates(directory=\"templates\")\n\n        async def homepage(request: Request) -> Response:\n            return templates.TemplateResponse(request, \"index.html\")\n        ```\n    \"\"\"\n\n    @overload\n    def __init__(\n        self,\n        directory: str | PathLike[str] | Sequence[str | PathLike[str]],\n        *,\n        context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,\n    ) -> None: ...\n\n    @overload\n    def __init__(\n        self,\n        *,\n        env: jinja2.Environment,\n        context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,\n    ) -> None: ...\n\n    def __init__(\n        self,\n        directory: str | PathLike[str] | Sequence[str | PathLike[str]] | None = None,\n        *,\n        context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,\n        env: jinja2.Environment | None = None,\n    ) -> None:\n        assert bool(directory) ^ bool(env), \"either 'directory' or 'env' arguments must be passed\"\n        self.context_processors = context_processors or []\n        if directory is not None:\n            loader = jinja2.FileSystemLoader(directory)\n            self.env = jinja2.Environment(loader=loader, autoescape=jinja2.select_autoescape())\n        elif env is not None:  # pragma: no branch\n            self.env = env\n\n        self._setup_env_defaults(self.env)\n\n    def _setup_env_defaults(self, env: jinja2.Environment) -> None:\n        @pass_context\n        def url_for(\n            context: dict[str, Any],\n            name: str,\n            /,\n            **path_params: Any,\n        ) -> URL:\n            request: Request = context[\"request\"]\n            return request.url_for(name, **path_params)\n\n        env.globals.setdefault(\"url_for\", url_for)\n\n    def get_template(self, name: str) -> jinja2.Template:\n        return self.env.get_template(name)\n\n    def TemplateResponse(\n        self,\n        request: Request,\n        name: str,\n        context: dict[str, Any] | None = None,\n        status_code: int = 200,\n        headers: Mapping[str, str] | None = None,\n        media_type: str | None = None,\n        background: BackgroundTask | None = None,\n    ) -> _TemplateResponse:\n        \"\"\"\n        Render a template and return an HTML response.\n\n        Args:\n            request: The incoming request instance.\n            name: The template file name to render.\n            context: Variables to pass to the template.\n            status_code: HTTP status code for the response.\n            headers: Additional headers to include in the response.\n            media_type: Media type for the response.\n            background: Background task to run after response is sent.\n\n        Returns:\n            An HTML response with the rendered template content.\n        \"\"\"\n        context = context or {}\n\n        context.setdefault(\"request\", request)\n        for context_processor in self.context_processors:\n            context.update(context_processor(request))\n\n        template = self.get_template(name)\n        return _TemplateResponse(\n            template,\n            context,\n            status_code=status_code,\n            headers=headers,\n            media_type=media_type,\n            background=background,\n        )\n"
  },
  {
    "path": "starlette/testclient.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport inspect\nimport io\nimport json\nimport math\nimport sys\nimport warnings\nfrom collections.abc import Awaitable, Callable, Generator, Iterable, Mapping, MutableMapping, Sequence\nfrom concurrent.futures import Future\nfrom contextlib import AbstractContextManager\nfrom types import GeneratorType\nfrom typing import (\n    Any,\n    Literal,\n    TypedDict,\n    TypeGuard,\n    cast,\n)\nfrom urllib.parse import unquote, urljoin\n\nimport anyio\nimport anyio.abc\nimport anyio.from_thread\nfrom anyio.streams.stapled import StapledObjectStream\n\nfrom starlette._utils import is_async_callable\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocketDisconnect\n\nif sys.version_info >= (3, 11):  # pragma: no cover\n    from typing import Self\nelse:  # pragma: no cover\n    from typing_extensions import Self\n\ntry:\n    import httpx\nexcept ModuleNotFoundError:  # pragma: no cover\n    raise RuntimeError(\n        \"The starlette.testclient module requires the httpx package to be installed.\\n\"\n        \"You can install this with:\\n\"\n        \"    $ pip install httpx\\n\"\n    )\n_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]\n\nASGIInstance = Callable[[Receive, Send], Awaitable[None]]\nASGI2App = Callable[[Scope], ASGIInstance]\nASGI3App = Callable[[Scope, Receive, Send], Awaitable[None]]\n\n\n_RequestData = Mapping[str, str | Iterable[str] | bytes]\n\n\ndef _is_asgi3(app: ASGI2App | ASGI3App) -> TypeGuard[ASGI3App]:\n    if inspect.isclass(app):\n        return hasattr(app, \"__await__\")\n    return is_async_callable(app)\n\n\nclass _WrapASGI2:\n    \"\"\"\n    Provide an ASGI3 interface onto an ASGI2 app.\n    \"\"\"\n\n    def __init__(self, app: ASGI2App) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        instance = self.app(scope)\n        await instance(receive, send)\n\n\nclass _AsyncBackend(TypedDict):\n    backend: str\n    backend_options: dict[str, Any]\n\n\nclass _Upgrade(Exception):\n    def __init__(self, session: WebSocketTestSession) -> None:\n        self.session = session\n\n\nclass WebSocketDenialResponse(  # type: ignore[misc]\n    httpx.Response,\n    WebSocketDisconnect,\n):\n    \"\"\"\n    A special case of `WebSocketDisconnect`, raised in the `TestClient` if the\n    `WebSocket` is closed before being accepted with a `send_denial_response()`.\n    \"\"\"\n\n\nclass WebSocketTestSession:\n    def __init__(\n        self,\n        app: ASGI3App,\n        scope: Scope,\n        portal_factory: _PortalFactoryType,\n    ) -> None:\n        self.app = app\n        self.scope = scope\n        self.accepted_subprotocol = None\n        self.portal_factory = portal_factory\n        self.extra_headers = None\n\n    def __enter__(self) -> WebSocketTestSession:\n        with contextlib.ExitStack() as stack:\n            self.portal = portal = stack.enter_context(self.portal_factory())\n            fut, cs = portal.start_task(self._run)\n            stack.callback(fut.result)\n            stack.callback(portal.call, cs.cancel)\n            self.send({\"type\": \"websocket.connect\"})\n            message = self.receive()\n            self._raise_on_close(message)\n            self.accepted_subprotocol = message.get(\"subprotocol\", None)\n            self.extra_headers = message.get(\"headers\", None)\n            stack.callback(self.close, 1000)\n            self.exit_stack = stack.pop_all()\n            return self\n\n    def __exit__(self, *args: Any) -> bool | None:\n        return self.exit_stack.__exit__(*args)\n\n    async def _run(self, *, task_status: anyio.abc.TaskStatus[anyio.CancelScope]) -> None:\n        \"\"\"\n        The sub-thread in which the websocket session runs.\n        \"\"\"\n        send: anyio.create_memory_object_stream[Message] = anyio.create_memory_object_stream(math.inf)\n        send_tx, send_rx = send\n        receive: anyio.create_memory_object_stream[Message] = anyio.create_memory_object_stream(math.inf)\n        receive_tx, receive_rx = receive\n        with send_tx, send_rx, receive_tx, receive_rx, anyio.CancelScope() as cs:\n            self._receive_tx = receive_tx\n            self._send_rx = send_rx\n            task_status.started(cs)\n            await self.app(self.scope, receive_rx.receive, send_tx.send)\n\n            # wait for cs.cancel to be called before closing streams\n            await anyio.sleep_forever()\n\n    def _raise_on_close(self, message: Message) -> None:\n        if message[\"type\"] == \"websocket.close\":\n            raise WebSocketDisconnect(code=message.get(\"code\", 1000), reason=message.get(\"reason\", \"\"))\n        elif message[\"type\"] == \"websocket.http.response.start\":\n            status_code: int = message[\"status\"]\n            headers: list[tuple[bytes, bytes]] = message[\"headers\"]\n            body: list[bytes] = []\n            while True:\n                message = self.receive()\n                assert message[\"type\"] == \"websocket.http.response.body\"\n                body.append(message[\"body\"])\n                if not message.get(\"more_body\", False):\n                    break\n            raise WebSocketDenialResponse(status_code=status_code, headers=headers, content=b\"\".join(body))\n\n    def send(self, message: Message) -> None:\n        self.portal.call(self._receive_tx.send, message)\n\n    def send_text(self, data: str) -> None:\n        self.send({\"type\": \"websocket.receive\", \"text\": data})\n\n    def send_bytes(self, data: bytes) -> None:\n        self.send({\"type\": \"websocket.receive\", \"bytes\": data})\n\n    def send_json(self, data: Any, mode: Literal[\"text\", \"binary\"] = \"text\") -> None:\n        text = json.dumps(data, separators=(\",\", \":\"), ensure_ascii=False)\n        if mode == \"text\":\n            self.send({\"type\": \"websocket.receive\", \"text\": text})\n        else:\n            self.send({\"type\": \"websocket.receive\", \"bytes\": text.encode(\"utf-8\")})\n\n    def close(self, code: int = 1000, reason: str | None = None) -> None:\n        self.send({\"type\": \"websocket.disconnect\", \"code\": code, \"reason\": reason})\n\n    def receive(self) -> Message:\n        return self.portal.call(self._send_rx.receive)\n\n    def receive_text(self) -> str:\n        message = self.receive()\n        self._raise_on_close(message)\n        return cast(str, message[\"text\"])\n\n    def receive_bytes(self) -> bytes:\n        message = self.receive()\n        self._raise_on_close(message)\n        return cast(bytes, message[\"bytes\"])\n\n    def receive_json(self, mode: Literal[\"text\", \"binary\"] = \"text\") -> Any:\n        message = self.receive()\n        self._raise_on_close(message)\n        if mode == \"text\":\n            text = message[\"text\"]\n        else:\n            text = message[\"bytes\"].decode(\"utf-8\")\n        return json.loads(text)\n\n\nclass _TestClientTransport(httpx.BaseTransport):\n    def __init__(\n        self,\n        app: ASGI3App,\n        portal_factory: _PortalFactoryType,\n        raise_server_exceptions: bool = True,\n        root_path: str = \"\",\n        *,\n        client: tuple[str, int],\n        app_state: dict[str, Any],\n    ) -> None:\n        self.app = app\n        self.raise_server_exceptions = raise_server_exceptions\n        self.root_path = root_path\n        self.portal_factory = portal_factory\n        self.app_state = app_state\n        self.client = client\n\n    def handle_request(self, request: httpx.Request) -> httpx.Response:\n        scheme = request.url.scheme\n        netloc = request.url.netloc.decode(encoding=\"ascii\")\n        path = request.url.path\n        raw_path = request.url.raw_path\n        query = request.url.query.decode(encoding=\"ascii\")\n\n        default_port = {\"http\": 80, \"ws\": 80, \"https\": 443, \"wss\": 443}[scheme]\n\n        if \":\" in netloc:\n            host, port_string = netloc.split(\":\", 1)\n            port = int(port_string)\n        else:\n            host = netloc\n            port = default_port\n\n        # Include the 'host' header.\n        if \"host\" in request.headers:\n            headers: list[tuple[bytes, bytes]] = []\n        elif port == default_port:  # pragma: no cover\n            headers = [(b\"host\", host.encode())]\n        else:  # pragma: no cover\n            headers = [(b\"host\", (f\"{host}:{port}\").encode())]\n\n        # Include other request headers.\n        headers += [(key.lower().encode(), value.encode()) for key, value in request.headers.multi_items()]\n\n        scope: dict[str, Any]\n\n        if scheme in {\"ws\", \"wss\"}:\n            subprotocol = request.headers.get(\"sec-websocket-protocol\", None)\n            if subprotocol is None:\n                subprotocols: Sequence[str] = []\n            else:\n                subprotocols = [value.strip() for value in subprotocol.split(\",\")]\n            scope = {\n                \"type\": \"websocket\",\n                \"path\": unquote(path),\n                \"raw_path\": raw_path.split(b\"?\", 1)[0],\n                \"root_path\": self.root_path,\n                \"scheme\": scheme,\n                \"query_string\": query.encode(),\n                \"headers\": headers,\n                \"client\": self.client,\n                \"server\": [host, port],\n                \"subprotocols\": subprotocols,\n                \"state\": self.app_state.copy(),\n                \"extensions\": {\"websocket.http.response\": {}},\n            }\n            session = WebSocketTestSession(self.app, scope, self.portal_factory)\n            raise _Upgrade(session)\n\n        scope = {\n            \"type\": \"http\",\n            \"http_version\": \"1.1\",\n            \"method\": request.method,\n            \"path\": unquote(path),\n            \"raw_path\": raw_path.split(b\"?\", 1)[0],\n            \"root_path\": self.root_path,\n            \"scheme\": scheme,\n            \"query_string\": query.encode(),\n            \"headers\": headers,\n            \"client\": self.client,\n            \"server\": [host, port],\n            \"extensions\": {\"http.response.debug\": {}},\n            \"state\": self.app_state.copy(),\n        }\n\n        request_complete = False\n        response_started = False\n        response_complete: anyio.Event\n        raw_kwargs: dict[str, Any] = {\"stream\": io.BytesIO()}\n        template = None\n        context = None\n\n        async def receive() -> Message:\n            nonlocal request_complete\n\n            if request_complete:\n                if not response_complete.is_set():\n                    await response_complete.wait()\n                return {\"type\": \"http.disconnect\"}\n\n            body = request.read()\n            if isinstance(body, str):\n                body_bytes: bytes = body.encode(\"utf-8\")  # pragma: no cover\n            elif body is None:\n                body_bytes = b\"\"  # pragma: no cover\n            elif isinstance(body, GeneratorType):\n                try:  # pragma: no cover\n                    chunk = body.send(None)\n                    if isinstance(chunk, str):\n                        chunk = chunk.encode(\"utf-8\")\n                    return {\"type\": \"http.request\", \"body\": chunk, \"more_body\": True}\n                except StopIteration:  # pragma: no cover\n                    request_complete = True\n                    return {\"type\": \"http.request\", \"body\": b\"\"}\n            else:\n                body_bytes = body\n\n            request_complete = True\n            return {\"type\": \"http.request\", \"body\": body_bytes}\n\n        async def send(message: Message) -> None:\n            nonlocal raw_kwargs, response_started, template, context\n\n            if message[\"type\"] == \"http.response.start\":\n                assert not response_started, 'Received multiple \"http.response.start\" messages.'\n                raw_kwargs[\"status_code\"] = message[\"status\"]\n                raw_kwargs[\"headers\"] = [(key.decode(), value.decode()) for key, value in message.get(\"headers\", [])]\n                response_started = True\n            elif message[\"type\"] == \"http.response.body\":\n                assert response_started, 'Received \"http.response.body\" without \"http.response.start\".'\n                assert not response_complete.is_set(), 'Received \"http.response.body\" after response completed.'\n                body = message.get(\"body\", b\"\")\n                more_body = message.get(\"more_body\", False)\n                if request.method != \"HEAD\":\n                    raw_kwargs[\"stream\"].write(body)\n                if not more_body:\n                    raw_kwargs[\"stream\"].seek(0)\n                    response_complete.set()\n            elif message[\"type\"] == \"http.response.debug\":\n                template = message[\"info\"][\"template\"]\n                context = message[\"info\"][\"context\"]\n\n        try:\n            with self.portal_factory() as portal:\n                response_complete = portal.call(anyio.Event)\n                portal.call(self.app, scope, receive, send)\n        except BaseException as exc:\n            if self.raise_server_exceptions:\n                raise exc\n\n        if self.raise_server_exceptions:\n            assert response_started, \"TestClient did not receive any response.\"\n        elif not response_started:\n            raw_kwargs = {\n                \"status_code\": 500,\n                \"headers\": [],\n                \"stream\": io.BytesIO(),\n            }\n\n        raw_kwargs[\"stream\"] = httpx.ByteStream(raw_kwargs[\"stream\"].read())\n\n        response = httpx.Response(**raw_kwargs, request=request)\n        if template is not None:\n            response.template = template  # type: ignore[attr-defined]\n            response.context = context  # type: ignore[attr-defined]\n        return response\n\n\nclass TestClient(httpx.Client):\n    __test__ = False\n    task: Future[None]\n    portal: anyio.abc.BlockingPortal | None = None\n\n    def __init__(\n        self,\n        app: ASGIApp,\n        base_url: str = \"http://testserver\",\n        raise_server_exceptions: bool = True,\n        root_path: str = \"\",\n        backend: Literal[\"asyncio\", \"trio\"] = \"asyncio\",\n        backend_options: dict[str, Any] | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        headers: dict[str, str] | None = None,\n        follow_redirects: bool = True,\n        client: tuple[str, int] = (\"testclient\", 50000),\n    ) -> None:\n        self.async_backend = _AsyncBackend(backend=backend, backend_options=backend_options or {})\n        if _is_asgi3(app):\n            asgi_app = app\n        else:\n            app = cast(ASGI2App, app)  # type: ignore[assignment]\n            asgi_app = _WrapASGI2(app)  # type: ignore[arg-type]\n        self.app = asgi_app\n        self.app_state: dict[str, Any] = {}\n        transport = _TestClientTransport(\n            self.app,\n            portal_factory=self._portal_factory,\n            raise_server_exceptions=raise_server_exceptions,\n            root_path=root_path,\n            app_state=self.app_state,\n            client=client,\n        )\n        if headers is None:\n            headers = {}\n        headers.setdefault(\"user-agent\", \"testclient\")\n        super().__init__(\n            base_url=base_url,\n            headers=headers,\n            transport=transport,\n            follow_redirects=follow_redirects,\n            cookies=cookies,\n        )\n\n    @contextlib.contextmanager\n    def _portal_factory(self) -> Generator[anyio.abc.BlockingPortal, None, None]:\n        if self.portal is not None:\n            yield self.portal\n        else:\n            with anyio.from_thread.start_blocking_portal(**self.async_backend) as portal:\n                yield portal\n\n    def request(  # type: ignore[override]\n        self,\n        method: str,\n        url: httpx._types.URLTypes,\n        *,\n        content: httpx._types.RequestContent | None = None,\n        data: _RequestData | None = None,\n        files: httpx._types.RequestFiles | None = None,\n        json: Any = None,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        if timeout is not httpx.USE_CLIENT_DEFAULT:\n            warnings.warn(\n                \"You should not use the 'timeout' argument with the TestClient. \"\n                \"See https://github.com/Kludex/starlette/issues/1108 for more information.\",\n                DeprecationWarning,\n            )\n        url = self._merge_url(url)\n        return super().request(\n            method,\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def get(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().get(\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def options(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().options(\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def head(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().head(\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def post(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        content: httpx._types.RequestContent | None = None,\n        data: _RequestData | None = None,\n        files: httpx._types.RequestFiles | None = None,\n        json: Any = None,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().post(\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def put(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        content: httpx._types.RequestContent | None = None,\n        data: _RequestData | None = None,\n        files: httpx._types.RequestFiles | None = None,\n        json: Any = None,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().put(\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def patch(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        content: httpx._types.RequestContent | None = None,\n        data: _RequestData | None = None,\n        files: httpx._types.RequestFiles | None = None,\n        json: Any = None,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().patch(\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def delete(  # type: ignore[override]\n        self,\n        url: httpx._types.URLTypes,\n        *,\n        params: httpx._types.QueryParamTypes | None = None,\n        headers: httpx._types.HeaderTypes | None = None,\n        cookies: httpx._types.CookieTypes | None = None,\n        auth: httpx._types.AuthTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        follow_redirects: bool | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        timeout: httpx._types.TimeoutTypes | httpx._client.UseClientDefault = httpx._client.USE_CLIENT_DEFAULT,\n        extensions: dict[str, Any] | None = None,\n    ) -> httpx.Response:\n        return super().delete(\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def websocket_connect(\n        self,\n        url: str,\n        subprotocols: Sequence[str] | None = None,\n        **kwargs: Any,\n    ) -> WebSocketTestSession:\n        url = urljoin(\"ws://testserver\", url)\n        headers = kwargs.get(\"headers\", {})\n        headers.setdefault(\"connection\", \"upgrade\")\n        headers.setdefault(\"sec-websocket-key\", \"testserver==\")\n        headers.setdefault(\"sec-websocket-version\", \"13\")\n        if subprotocols is not None:\n            headers.setdefault(\"sec-websocket-protocol\", \", \".join(subprotocols))\n        kwargs[\"headers\"] = headers\n        try:\n            super().request(\"GET\", url, **kwargs)\n        except _Upgrade as exc:\n            session = exc.session\n        else:\n            raise RuntimeError(\"Expected WebSocket upgrade\")  # pragma: no cover\n\n        return session\n\n    def __enter__(self) -> Self:\n        with contextlib.ExitStack() as stack:\n            self.portal = portal = stack.enter_context(anyio.from_thread.start_blocking_portal(**self.async_backend))\n\n            @stack.callback\n            def reset_portal() -> None:\n                self.portal = None\n\n            send: anyio.create_memory_object_stream[MutableMapping[str, Any] | None] = (\n                anyio.create_memory_object_stream(math.inf)\n            )\n            receive: anyio.create_memory_object_stream[MutableMapping[str, Any]] = anyio.create_memory_object_stream(\n                math.inf\n            )\n            for channel in (*send, *receive):\n                stack.callback(channel.close)\n            self.stream_send = StapledObjectStream(*send)\n            self.stream_receive = StapledObjectStream(*receive)\n            self.task = portal.start_task_soon(self.lifespan)\n            portal.call(self.wait_startup)\n\n            @stack.callback\n            def wait_shutdown() -> None:\n                portal.call(self.wait_shutdown)\n\n            self.exit_stack = stack.pop_all()\n\n        return self\n\n    def __exit__(self, *args: Any) -> None:\n        self.exit_stack.close()\n\n    async def lifespan(self) -> None:\n        scope = {\"type\": \"lifespan\", \"state\": self.app_state}\n        try:\n            await self.app(scope, self.stream_receive.receive, self.stream_send.send)\n        finally:\n            await self.stream_send.send(None)\n\n    async def wait_startup(self) -> None:\n        await self.stream_receive.send({\"type\": \"lifespan.startup\"})\n\n        async def receive() -> Any:\n            message = await self.stream_send.receive()\n            if message is None:\n                self.task.result()\n            return message\n\n        message = await receive()\n        assert message[\"type\"] in (\n            \"lifespan.startup.complete\",\n            \"lifespan.startup.failed\",\n        )\n        if message[\"type\"] == \"lifespan.startup.failed\":\n            await receive()\n\n    async def wait_shutdown(self) -> None:\n        async def receive() -> Any:\n            message = await self.stream_send.receive()\n            if message is None:\n                self.task.result()\n            return message\n\n        await self.stream_receive.send({\"type\": \"lifespan.shutdown\"})\n        message = await receive()\n        assert message[\"type\"] in (\n            \"lifespan.shutdown.complete\",\n            \"lifespan.shutdown.failed\",\n        )\n        if message[\"type\"] == \"lifespan.shutdown.failed\":\n            await receive()\n"
  },
  {
    "path": "starlette/types.py",
    "content": "from collections.abc import Awaitable, Callable, Mapping, MutableMapping\nfrom contextlib import AbstractAsyncContextManager\nfrom typing import TYPE_CHECKING, Any, TypeVar\n\nif TYPE_CHECKING:\n    from starlette.requests import Request\n    from starlette.responses import Response\n    from starlette.websockets import WebSocket\n\nAppType = TypeVar(\"AppType\")\n\nScope = MutableMapping[str, Any]\nMessage = MutableMapping[str, Any]\n\nReceive = Callable[[], Awaitable[Message]]\nSend = Callable[[Message], Awaitable[None]]\n\nASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]]\n\nStatelessLifespan = Callable[[AppType], AbstractAsyncContextManager[None]]\nStatefulLifespan = Callable[[AppType], AbstractAsyncContextManager[Mapping[str, Any]]]\nLifespan = StatelessLifespan[AppType] | StatefulLifespan[AppType]\n\nHTTPExceptionHandler = Callable[[\"Request\", Exception], \"Response | Awaitable[Response]\"]\nWebSocketExceptionHandler = Callable[[\"WebSocket\", Exception], Awaitable[None]]\nExceptionHandler = HTTPExceptionHandler | WebSocketExceptionHandler\n"
  },
  {
    "path": "starlette/websockets.py",
    "content": "from __future__ import annotations\n\nimport enum\nimport json\nfrom collections.abc import AsyncIterator, Iterable\nfrom typing import Any, cast\n\nfrom starlette.requests import HTTPConnection, StateT\nfrom starlette.responses import Response\nfrom starlette.types import Message, Receive, Scope, Send\n\n\nclass WebSocketState(enum.Enum):\n    CONNECTING = 0\n    CONNECTED = 1\n    DISCONNECTED = 2\n    RESPONSE = 3\n\n\nclass WebSocketDisconnect(Exception):\n    def __init__(self, code: int = 1000, reason: str | None = None) -> None:\n        self.code = code\n        self.reason = reason or \"\"\n\n\nclass WebSocket(HTTPConnection[StateT]):\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        super().__init__(scope)\n        assert scope[\"type\"] == \"websocket\"\n        self._receive = receive\n        self._send = send\n        self.client_state = WebSocketState.CONNECTING\n        self.application_state = WebSocketState.CONNECTING\n\n    async def receive(self) -> Message:\n        \"\"\"\n        Receive ASGI websocket messages, ensuring valid state transitions.\n        \"\"\"\n        if self.client_state == WebSocketState.CONNECTING:\n            message = await self._receive()\n            message_type = message[\"type\"]\n            if message_type != \"websocket.connect\":\n                raise RuntimeError(f'Expected ASGI message \"websocket.connect\", but got {message_type!r}')\n            self.client_state = WebSocketState.CONNECTED\n            return message\n        elif self.client_state == WebSocketState.CONNECTED:\n            message = await self._receive()\n            message_type = message[\"type\"]\n            if message_type not in {\"websocket.receive\", \"websocket.disconnect\"}:\n                raise RuntimeError(\n                    f'Expected ASGI message \"websocket.receive\" or \"websocket.disconnect\", but got {message_type!r}'\n                )\n            if message_type == \"websocket.disconnect\":\n                self.client_state = WebSocketState.DISCONNECTED\n            return message\n        else:\n            raise RuntimeError('Cannot call \"receive\" once a disconnect message has been received.')\n\n    async def send(self, message: Message) -> None:\n        \"\"\"\n        Send ASGI websocket messages, ensuring valid state transitions.\n        \"\"\"\n        if self.application_state == WebSocketState.CONNECTING:\n            message_type = message[\"type\"]\n            if message_type not in {\"websocket.accept\", \"websocket.close\", \"websocket.http.response.start\"}:\n                raise RuntimeError(\n                    'Expected ASGI message \"websocket.accept\", \"websocket.close\" or \"websocket.http.response.start\", '\n                    f\"but got {message_type!r}\"\n                )\n            if message_type == \"websocket.close\":\n                self.application_state = WebSocketState.DISCONNECTED\n            elif message_type == \"websocket.http.response.start\":\n                self.application_state = WebSocketState.RESPONSE\n            else:\n                self.application_state = WebSocketState.CONNECTED\n            await self._send(message)\n        elif self.application_state == WebSocketState.CONNECTED:\n            message_type = message[\"type\"]\n            if message_type not in {\"websocket.send\", \"websocket.close\"}:\n                raise RuntimeError(\n                    f'Expected ASGI message \"websocket.send\" or \"websocket.close\", but got {message_type!r}'\n                )\n            if message_type == \"websocket.close\":\n                self.application_state = WebSocketState.DISCONNECTED\n            try:\n                await self._send(message)\n            except OSError:\n                self.application_state = WebSocketState.DISCONNECTED\n                raise WebSocketDisconnect(code=1006)\n        elif self.application_state == WebSocketState.RESPONSE:\n            message_type = message[\"type\"]\n            if message_type != \"websocket.http.response.body\":\n                raise RuntimeError(f'Expected ASGI message \"websocket.http.response.body\", but got {message_type!r}')\n            if not message.get(\"more_body\", False):\n                self.application_state = WebSocketState.DISCONNECTED\n            await self._send(message)\n        else:\n            raise RuntimeError('Cannot call \"send\" once a close message has been sent.')\n\n    async def accept(\n        self,\n        subprotocol: str | None = None,\n        headers: Iterable[tuple[bytes, bytes]] | None = None,\n    ) -> None:\n        headers = headers or []\n\n        if self.client_state == WebSocketState.CONNECTING:  # pragma: no branch\n            # If we haven't yet seen the 'connect' message, then wait for it first.\n            await self.receive()\n        await self.send({\"type\": \"websocket.accept\", \"subprotocol\": subprotocol, \"headers\": headers})\n\n    def _raise_on_disconnect(self, message: Message) -> None:\n        if message[\"type\"] == \"websocket.disconnect\":\n            raise WebSocketDisconnect(message[\"code\"], message.get(\"reason\"))\n\n    async def receive_text(self) -> str:\n        if self.application_state != WebSocketState.CONNECTED:\n            raise RuntimeError('WebSocket is not connected. Need to call \"accept\" first.')\n        message = await self.receive()\n        self._raise_on_disconnect(message)\n        return cast(str, message[\"text\"])\n\n    async def receive_bytes(self) -> bytes:\n        if self.application_state != WebSocketState.CONNECTED:\n            raise RuntimeError('WebSocket is not connected. Need to call \"accept\" first.')\n        message = await self.receive()\n        self._raise_on_disconnect(message)\n        return cast(bytes, message[\"bytes\"])\n\n    async def receive_json(self, mode: str = \"text\") -> Any:\n        if mode not in {\"text\", \"binary\"}:\n            raise RuntimeError('The \"mode\" argument should be \"text\" or \"binary\".')\n        if self.application_state != WebSocketState.CONNECTED:\n            raise RuntimeError('WebSocket is not connected. Need to call \"accept\" first.')\n        message = await self.receive()\n        self._raise_on_disconnect(message)\n\n        if mode == \"text\":\n            text = message[\"text\"]\n        else:\n            text = message[\"bytes\"].decode(\"utf-8\")\n        return json.loads(text)\n\n    async def iter_text(self) -> AsyncIterator[str]:\n        try:\n            while True:\n                yield await self.receive_text()\n        except WebSocketDisconnect:\n            pass\n\n    async def iter_bytes(self) -> AsyncIterator[bytes]:\n        try:\n            while True:\n                yield await self.receive_bytes()\n        except WebSocketDisconnect:\n            pass\n\n    async def iter_json(self) -> AsyncIterator[Any]:\n        try:\n            while True:\n                yield await self.receive_json()\n        except WebSocketDisconnect:\n            pass\n\n    async def send_text(self, data: str) -> None:\n        await self.send({\"type\": \"websocket.send\", \"text\": data})\n\n    async def send_bytes(self, data: bytes) -> None:\n        await self.send({\"type\": \"websocket.send\", \"bytes\": data})\n\n    async def send_json(self, data: Any, mode: str = \"text\") -> None:\n        if mode not in {\"text\", \"binary\"}:\n            raise RuntimeError('The \"mode\" argument should be \"text\" or \"binary\".')\n        text = json.dumps(data, separators=(\",\", \":\"), ensure_ascii=False)\n        if mode == \"text\":\n            await self.send({\"type\": \"websocket.send\", \"text\": text})\n        else:\n            await self.send({\"type\": \"websocket.send\", \"bytes\": text.encode(\"utf-8\")})\n\n    async def close(self, code: int = 1000, reason: str | None = None) -> None:\n        await self.send({\"type\": \"websocket.close\", \"code\": code, \"reason\": reason or \"\"})\n\n    async def send_denial_response(self, response: Response) -> None:\n        if \"websocket.http.response\" in self.scope.get(\"extensions\", {}):\n            await response(self.scope, self.receive, self.send)\n        else:\n            raise RuntimeError(\"The server doesn't support the Websocket Denial Response extension.\")\n\n\nclass WebSocketClose:\n    def __init__(self, code: int = 1000, reason: str | None = None) -> None:\n        self.code = code\n        self.reason = reason or \"\"\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await send({\"type\": \"websocket.close\", \"code\": self.code, \"reason\": self.reason})\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "from __future__ import annotations\n\nimport functools\nfrom typing import Any, Literal\n\nimport pytest\n\nfrom starlette.testclient import TestClient\nfrom tests.types import TestClientFactory\n\n\n@pytest.fixture\ndef test_client_factory(\n    anyio_backend_name: Literal[\"asyncio\", \"trio\"],\n    anyio_backend_options: dict[str, Any],\n) -> TestClientFactory:\n    # anyio_backend_name defined by:\n    # https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on\n    return functools.partial(\n        TestClient,\n        backend=anyio_backend_name,\n        backend_options=anyio_backend_options,\n    )\n"
  },
  {
    "path": "tests/middleware/__init__.py",
    "content": ""
  },
  {
    "path": "tests/middleware/test_base.py",
    "content": "from __future__ import annotations\n\nimport contextvars\nfrom collections.abc import AsyncGenerator, AsyncIterator, Generator\nfrom contextlib import AsyncExitStack\nfrom pathlib import Path\nfrom typing import Any\n\nimport anyio\nimport pytest\nfrom anyio.abc import TaskStatus\n\nfrom starlette.applications import Starlette\nfrom starlette.background import BackgroundTask\nfrom starlette.middleware import Middleware, _MiddlewareFactory\nfrom starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint\nfrom starlette.requests import ClientDisconnect, Request\nfrom starlette.responses import FileResponse, PlainTextResponse, Response, StreamingResponse\nfrom starlette.routing import Route, WebSocketRoute\nfrom starlette.testclient import TestClient\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\nfrom tests.types import TestClientFactory\n\n\nclass CustomMiddleware(BaseHTTPMiddleware):\n    async def dispatch(\n        self,\n        request: Request,\n        call_next: RequestResponseEndpoint,\n    ) -> Response:\n        response = await call_next(request)\n        response.headers[\"Custom-Header\"] = \"Example\"\n        return response\n\n\ndef homepage(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Homepage\")\n\n\ndef exc(request: Request) -> None:\n    raise Exception(\"Exc\")\n\n\ndef exc_stream(request: Request) -> StreamingResponse:\n    return StreamingResponse(_generate_faulty_stream())\n\n\ndef _generate_faulty_stream() -> Generator[bytes, None, None]:\n    yield b\"Ok\"\n    raise Exception(\"Faulty Stream\")\n\n\nclass NoResponse:\n    def __init__(\n        self,\n        scope: Scope,\n        receive: Receive,\n        send: Send,\n    ):\n        pass\n\n    def __await__(self) -> Generator[Any, None, None]:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        pass\n\n\nasync def websocket_endpoint(session: WebSocket) -> None:\n    await session.accept()\n    await session.send_text(\"Hello, world!\")\n    await session.close()\n\n\napp = Starlette(\n    routes=[\n        Route(\"/\", endpoint=homepage),\n        Route(\"/exc\", endpoint=exc),\n        Route(\"/exc-stream\", endpoint=exc_stream),\n        Route(\"/no-response\", endpoint=NoResponse),\n        WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n    ],\n    middleware=[Middleware(CustomMiddleware)],\n)\n\n\ndef test_custom_middleware(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"Custom-Header\"] == \"Example\"\n\n    with pytest.raises(Exception) as ctx:\n        response = client.get(\"/exc\")\n    assert str(ctx.value) == \"Exc\"\n\n    with pytest.raises(Exception) as ctx:\n        response = client.get(\"/exc-stream\")\n    assert str(ctx.value) == \"Faulty Stream\"\n\n    with pytest.raises(RuntimeError):\n        response = client.get(\"/no-response\")\n\n    with client.websocket_connect(\"/ws\") as session:\n        text = session.receive_text()\n        assert text == \"Hello, world!\"\n\n\ndef test_state_data_across_multiple_middlewares(\n    test_client_factory: TestClientFactory,\n) -> None:\n    expected_value1 = \"foo\"\n    expected_value2 = \"bar\"\n\n    class aMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            request.state.foo = expected_value1\n            response = await call_next(request)\n            return response\n\n    class bMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            request.state.bar = expected_value2\n            response = await call_next(request)\n            response.headers[\"X-State-Foo\"] = request.state.foo\n            return response\n\n    class cMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            response = await call_next(request)\n            response.headers[\"X-State-Bar\"] = request.state.bar\n            return response\n\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"OK\")\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage)],\n        middleware=[\n            Middleware(aMiddleware),\n            Middleware(bMiddleware),\n            Middleware(cMiddleware),\n        ],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"OK\"\n    assert response.headers[\"X-State-Foo\"] == expected_value1\n    assert response.headers[\"X-State-Bar\"] == expected_value2\n\n\ndef test_app_middleware_argument(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\")\n\n    app = Starlette(routes=[Route(\"/\", homepage)], middleware=[Middleware(CustomMiddleware)])\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"Custom-Header\"] == \"Example\"\n\n\ndef test_fully_evaluated_response(test_client_factory: TestClientFactory) -> None:\n    # Test for https://github.com/Kludex/starlette/issues/1022\n    class CustomMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> PlainTextResponse:\n            await call_next(request)\n            return PlainTextResponse(\"Custom\")\n\n    app = Starlette(middleware=[Middleware(CustomMiddleware)])\n\n    client = test_client_factory(app)\n    response = client.get(\"/does_not_exist\")\n    assert response.text == \"Custom\"\n\n\nctxvar: contextvars.ContextVar[str] = contextvars.ContextVar(\"ctxvar\")\n\n\nclass CustomMiddlewareWithoutBaseHTTPMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        ctxvar.set(\"set by middleware\")\n        await self.app(scope, receive, send)\n        assert ctxvar.get() == \"set by endpoint\"\n\n\nclass CustomMiddlewareUsingBaseHTTPMiddleware(BaseHTTPMiddleware):\n    async def dispatch(\n        self,\n        request: Request,\n        call_next: RequestResponseEndpoint,\n    ) -> Response:\n        ctxvar.set(\"set by middleware\")\n        resp = await call_next(request)\n        assert ctxvar.get() == \"set by endpoint\"\n        return resp  # pragma: no cover\n\n\n@pytest.mark.parametrize(\n    \"middleware_cls\",\n    [\n        CustomMiddlewareWithoutBaseHTTPMiddleware,\n        pytest.param(\n            CustomMiddlewareUsingBaseHTTPMiddleware,\n            marks=pytest.mark.xfail(\n                reason=(\n                    \"BaseHTTPMiddleware creates a TaskGroup which copies the context\"\n                    \"and erases any changes to it made within the TaskGroup\"\n                ),\n                raises=AssertionError,\n            ),\n        ),\n    ],\n)\ndef test_contextvars(\n    test_client_factory: TestClientFactory,\n    middleware_cls: _MiddlewareFactory[Any],\n) -> None:\n    # this has to be an async endpoint because Starlette calls run_in_threadpool\n    # on sync endpoints which has it's own set of peculiarities w.r.t propagating\n    # contextvars (it propagates them forwards but not backwards)\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert ctxvar.get() == \"set by middleware\"\n        ctxvar.set(\"set by endpoint\")\n        return PlainTextResponse(\"Homepage\")\n\n    app = Starlette(middleware=[Middleware(middleware_cls)], routes=[Route(\"/\", homepage)])\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200, response.content\n\n\n@pytest.mark.anyio\nasync def test_run_background_tasks_even_if_client_disconnects() -> None:\n    # test for https://github.com/Kludex/starlette/issues/1438\n    response_complete = anyio.Event()\n    background_task_run = anyio.Event()\n\n    async def sleep_and_set() -> None:\n        # small delay to give BaseHTTPMiddleware a chance to cancel us\n        # this is required to make the test fail prior to fixing the issue\n        # so do not be surprised if you remove it and the test still passes\n        await anyio.sleep(0.1)\n        background_task_run.set()\n\n    async def endpoint_with_background_task(_: Request) -> PlainTextResponse:\n        return PlainTextResponse(background=BackgroundTask(sleep_and_set))\n\n    async def passthrough(\n        request: Request,\n        call_next: RequestResponseEndpoint,\n    ) -> Response:\n        return await call_next(request)\n\n    app = Starlette(\n        middleware=[Middleware(BaseHTTPMiddleware, dispatch=passthrough)],\n        routes=[Route(\"/\", endpoint_with_background_task)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n    }\n\n    async def receive() -> Message:\n        raise NotImplementedError(\"Should not be called!\")\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.body\":\n            if not message.get(\"more_body\", False):  # pragma: no branch\n                response_complete.set()\n\n    await app(scope, receive, send)\n\n    assert background_task_run.is_set()\n\n\ndef test_run_background_tasks_raise_exceptions(test_client_factory: TestClientFactory) -> None:\n    # test for https://github.com/Kludex/starlette/issues/2625\n\n    async def sleep_and_set() -> None:\n        await anyio.sleep(0.1)\n        raise ValueError(\"TEST\")\n\n    async def endpoint_with_background_task(_: Request) -> PlainTextResponse:\n        return PlainTextResponse(background=BackgroundTask(sleep_and_set))\n\n    async def passthrough(request: Request, call_next: RequestResponseEndpoint) -> Response:\n        return await call_next(request)\n\n    app = Starlette(\n        middleware=[Middleware(BaseHTTPMiddleware, dispatch=passthrough)],\n        routes=[Route(\"/\", endpoint_with_background_task)],\n    )\n\n    client = test_client_factory(app)\n    with pytest.raises(ValueError, match=\"TEST\"):\n        client.get(\"/\")\n\n\ndef test_exception_can_be_caught(test_client_factory: TestClientFactory) -> None:\n    async def error_endpoint(_: Request) -> None:\n        raise ValueError(\"TEST\")\n\n    async def catches_error(request: Request, call_next: RequestResponseEndpoint) -> Response:\n        try:\n            return await call_next(request)\n        except ValueError as exc:\n            return PlainTextResponse(content=str(exc), status_code=400)\n\n    app = Starlette(\n        middleware=[Middleware(BaseHTTPMiddleware, dispatch=catches_error)],\n        routes=[Route(\"/\", error_endpoint)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 400\n    assert response.text == \"TEST\"\n\n\n@pytest.mark.anyio\nasync def test_do_not_block_on_background_tasks() -> None:\n    response_complete = anyio.Event()\n    events: list[str | Message] = []\n\n    async def sleep_and_set() -> None:\n        events.append(\"Background task started\")\n        await anyio.sleep(0.1)\n        events.append(\"Background task finished\")\n\n    async def endpoint_with_background_task(_: Request) -> PlainTextResponse:\n        return PlainTextResponse(content=\"Hello\", background=BackgroundTask(sleep_and_set))\n\n    async def passthrough(request: Request, call_next: RequestResponseEndpoint) -> Response:\n        return await call_next(request)\n\n    app = Starlette(\n        middleware=[Middleware(BaseHTTPMiddleware, dispatch=passthrough)],\n        routes=[Route(\"/\", endpoint_with_background_task)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n    }\n\n    async def receive() -> Message:\n        raise NotImplementedError(\"Should not be called!\")\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.body\":\n            events.append(message)\n            if not message.get(\"more_body\", False):\n                response_complete.set()\n\n    async with anyio.create_task_group() as tg:\n        tg.start_soon(app, scope, receive, send)\n        tg.start_soon(app, scope, receive, send)\n\n    # Without the fix, the background tasks would start and finish before the\n    # last http.response.body is sent.\n    assert events == [\n        {\"body\": b\"Hello\", \"more_body\": True, \"type\": \"http.response.body\"},\n        {\"body\": b\"\", \"more_body\": False, \"type\": \"http.response.body\"},\n        {\"body\": b\"Hello\", \"more_body\": True, \"type\": \"http.response.body\"},\n        {\"body\": b\"\", \"more_body\": False, \"type\": \"http.response.body\"},\n        \"Background task started\",\n        \"Background task started\",\n        \"Background task finished\",\n        \"Background task finished\",\n    ]\n\n\n@pytest.mark.anyio\nasync def test_run_context_manager_exit_even_if_client_disconnects() -> None:\n    # test for https://github.com/Kludex/starlette/issues/1678#issuecomment-1172916042\n    response_complete = anyio.Event()\n    context_manager_exited = anyio.Event()\n\n    async def sleep_and_set() -> None:\n        # small delay to give BaseHTTPMiddleware a chance to cancel us\n        # this is required to make the test fail prior to fixing the issue\n        # so do not be surprised if you remove it and the test still passes\n        await anyio.sleep(0.1)\n        context_manager_exited.set()\n\n    class ContextManagerMiddleware:\n        def __init__(self, app: ASGIApp):\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            async with AsyncExitStack() as stack:\n                stack.push_async_callback(sleep_and_set)\n                await self.app(scope, receive, send)\n\n    async def simple_endpoint(_: Request) -> PlainTextResponse:\n        return PlainTextResponse(background=BackgroundTask(sleep_and_set))\n\n    async def passthrough(\n        request: Request,\n        call_next: RequestResponseEndpoint,\n    ) -> Response:\n        return await call_next(request)\n\n    app = Starlette(\n        middleware=[\n            Middleware(BaseHTTPMiddleware, dispatch=passthrough),\n            Middleware(ContextManagerMiddleware),\n        ],\n        routes=[Route(\"/\", simple_endpoint)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n    }\n\n    async def receive() -> Message:\n        raise NotImplementedError(\"Should not be called!\")\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.body\":\n            if not message.get(\"more_body\", False):  # pragma: no branch\n                response_complete.set()\n\n    await app(scope, receive, send)\n\n    assert context_manager_exited.is_set()\n\n\ndef test_app_receives_http_disconnect_while_sending_if_discarded(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class DiscardingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: Any,\n        ) -> PlainTextResponse:\n            # As a matter of ordering, this test targets the case where the downstream\n            # app response is discarded while it is sending a response body.\n            # We need to wait for the downstream app to begin sending a response body\n            # before sending the middleware response that will overwrite the downstream\n            # response.\n            downstream_app_response = await call_next(request)\n            body_generator = downstream_app_response.body_iterator\n            try:\n                await body_generator.__anext__()\n            finally:\n                await body_generator.aclose()\n\n            return PlainTextResponse(\"Custom\")\n\n    async def downstream_app(\n        scope: Scope,\n        receive: Receive,\n        send: Send,\n    ) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": 200,\n                \"headers\": [\n                    (b\"content-type\", b\"text/plain\"),\n                ],\n            }\n        )\n        async with anyio.create_task_group() as task_group:\n\n            async def cancel_on_disconnect(\n                *,\n                task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED,\n            ) -> None:\n                task_status.started()\n                while True:\n                    message = await receive()\n                    if message[\"type\"] == \"http.disconnect\":  # pragma: no branch\n                        task_group.cancel_scope.cancel()\n                        break\n\n            # Using start instead of start_soon to ensure that\n            # cancel_on_disconnect is scheduled by the event loop\n            # before we start returning the body\n            await task_group.start(cancel_on_disconnect)\n\n            # A timeout is set for 0.1 second in order to ensure that\n            # we never deadlock the test run in an infinite loop\n            with anyio.move_on_after(0.1):\n                while True:\n                    await send(\n                        {\n                            \"type\": \"http.response.body\",\n                            \"body\": b\"chunk \",\n                            \"more_body\": True,\n                        }\n                    )\n\n            pytest.fail(\"http.disconnect should have been received and canceled the scope\")  # pragma: no cover\n\n    app = DiscardingMiddleware(downstream_app)\n\n    client = test_client_factory(app)\n    response = client.get(\"/does_not_exist\")\n    assert response.text == \"Custom\"\n\n\ndef test_app_receives_http_disconnect_after_sending_if_discarded(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class DiscardingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> PlainTextResponse:\n            await call_next(request)\n            return PlainTextResponse(\"Custom\")\n\n    async def downstream_app(\n        scope: Scope,\n        receive: Receive,\n        send: Send,\n    ) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": 200,\n                \"headers\": [\n                    (b\"content-type\", b\"text/plain\"),\n                ],\n            }\n        )\n        await send(\n            {\n                \"type\": \"http.response.body\",\n                \"body\": b\"first chunk, \",\n                \"more_body\": True,\n            }\n        )\n        await send(\n            {\n                \"type\": \"http.response.body\",\n                \"body\": b\"second chunk\",\n                \"more_body\": True,\n            }\n        )\n        message = await receive()\n        assert message[\"type\"] == \"http.disconnect\"\n\n    app = DiscardingMiddleware(downstream_app)\n\n    client = test_client_factory(app)\n    response = client.get(\"/does_not_exist\")\n    assert response.text == \"Custom\"\n\n\ndef test_read_request_stream_in_app_after_middleware_calls_stream(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        expected = [b\"\"]\n        async for chunk in request.stream():\n            assert chunk == expected.pop(0)\n        assert expected == []\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            expected = [b\"a\", b\"\"]\n            async for chunk in request.stream():\n                assert chunk == expected.pop(0)\n            assert expected == []\n            return await call_next(request)\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_stream_in_app_after_middleware_calls_body(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        expected = [b\"a\", b\"\"]\n        async for chunk in request.stream():\n            assert chunk == expected.pop(0)\n        assert expected == []\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            assert await request.body() == b\"a\"\n            return await call_next(request)\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_body_in_app_after_middleware_calls_stream(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert await request.body() == b\"\"\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            expected = [b\"a\", b\"\"]\n            async for chunk in request.stream():\n                assert chunk == expected.pop(0)\n            assert expected == []\n            return await call_next(request)\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_body_in_app_after_middleware_calls_body(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert await request.body() == b\"a\"\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            assert await request.body() == b\"a\"\n            return await call_next(request)\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_stream_in_dispatch_after_app_calls_stream(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        expected = [b\"a\", b\"\"]\n        async for chunk in request.stream():\n            assert chunk == expected.pop(0)\n        assert expected == []\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            resp = await call_next(request)\n            with pytest.raises(RuntimeError, match=\"Stream consumed\"):\n                async for _ in request.stream():\n                    raise AssertionError(\"should not be called\")  # pragma: no cover\n            return resp\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_stream_in_dispatch_after_app_calls_body(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert await request.body() == b\"a\"\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            resp = await call_next(request)\n            with pytest.raises(RuntimeError, match=\"Stream consumed\"):\n                async for _ in request.stream():\n                    raise AssertionError(\"should not be called\")  # pragma: no cover\n            return resp\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_read_request_stream_in_dispatch_wrapping_app_calls_body() -> None:\n    async def endpoint(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        async for chunk in request.stream():  # pragma: no branch\n            assert chunk == b\"2\"\n            break\n        await Response()(scope, receive, send)\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            expected = b\"1\"\n            response: Response | None = None\n            async for chunk in request.stream():  # pragma: no branch\n                assert chunk == expected\n                if expected == b\"1\":\n                    response = await call_next(request)\n                    expected = b\"3\"\n                else:\n                    break\n            assert response is not None\n            return response\n\n    async def rcv() -> AsyncGenerator[Message, None]:\n        yield {\"type\": \"http.request\", \"body\": b\"1\", \"more_body\": True}\n        yield {\"type\": \"http.request\", \"body\": b\"2\", \"more_body\": True}\n        yield {\"type\": \"http.request\", \"body\": b\"3\"}\n        raise AssertionError(  # pragma: no cover\n            \"Should not be called, no need to poll for disconnect\"\n        )\n\n    sent: list[Message] = []\n\n    async def send(msg: Message) -> None:\n        sent.append(msg)\n\n    app: ASGIApp = endpoint\n    app = ConsumingMiddleware(app)\n\n    rcv_stream = rcv()\n\n    await app({\"type\": \"http\"}, rcv_stream.__anext__, send)\n\n    assert sent == [\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [(b\"content-length\", b\"0\")],\n        },\n        {\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False},\n    ]\n\n    await rcv_stream.aclose()\n\n\ndef test_read_request_stream_in_dispatch_after_app_calls_body_with_middleware_calling_body_before_call_next(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert await request.body() == b\"a\"\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            assert await request.body() == b\"a\"  # this buffers the request body in memory\n            resp = await call_next(request)\n            async for chunk in request.stream():\n                if chunk:\n                    assert chunk == b\"a\"\n            return resp\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\ndef test_read_request_body_in_dispatch_after_app_calls_body_with_middleware_calling_body_before_call_next(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        assert await request.body() == b\"a\"\n        return PlainTextResponse(\"Homepage\")\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            assert await request.body() == b\"a\"  # this buffers the request body in memory\n            resp = await call_next(request)\n            assert await request.body() == b\"a\"  # no problem here\n            return resp\n\n    app = Starlette(\n        routes=[Route(\"/\", homepage, methods=[\"POST\"])],\n        middleware=[Middleware(ConsumingMiddleware)],\n    )\n\n    client: TestClient = test_client_factory(app)\n    response = client.post(\"/\", content=b\"a\")\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_read_request_disconnected_client() -> None:\n    \"\"\"If we receive a disconnect message when the downstream ASGI\n    app calls receive() the Request instance passed into the dispatch function\n    should get marked as disconnected.\n    The downstream ASGI app should not get a ClientDisconnect raised,\n    instead if should just receive the disconnect message.\n    \"\"\"\n\n    async def endpoint(scope: Scope, receive: Receive, send: Send) -> None:\n        msg = await receive()\n        assert msg[\"type\"] == \"http.disconnect\"\n        await Response()(scope, receive, send)\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            response = await call_next(request)\n            disconnected = await request.is_disconnected()\n            assert disconnected is True\n            return response\n\n    scope = {\"type\": \"http\", \"method\": \"POST\", \"path\": \"/\"}\n\n    async def receive() -> AsyncGenerator[Message, None]:\n        yield {\"type\": \"http.disconnect\"}\n        raise AssertionError(\"Should not be called, would hang\")  # pragma: no cover\n\n    async def send(msg: Message) -> None:\n        if msg[\"type\"] == \"http.response.start\":\n            assert msg[\"status\"] == 200\n\n    app: ASGIApp = ConsumingMiddleware(endpoint)\n\n    rcv = receive()\n\n    await app(scope, rcv.__anext__, send)\n\n    await rcv.aclose()\n\n\n@pytest.mark.anyio\nasync def test_read_request_disconnected_after_consuming_steam() -> None:\n    async def endpoint(scope: Scope, receive: Receive, send: Send) -> None:\n        msg = await receive()\n        assert msg.pop(\"more_body\", False) is False\n        assert msg == {\"type\": \"http.request\", \"body\": b\"hi\"}\n        msg = await receive()\n        assert msg == {\"type\": \"http.disconnect\"}\n        await Response()(scope, receive, send)\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            await request.body()\n            disconnected = await request.is_disconnected()\n            assert disconnected is True\n            response = await call_next(request)\n            return response\n\n    scope = {\"type\": \"http\", \"method\": \"POST\", \"path\": \"/\"}\n\n    async def receive() -> AsyncGenerator[Message, None]:\n        yield {\"type\": \"http.request\", \"body\": b\"hi\"}\n        yield {\"type\": \"http.disconnect\"}\n        raise AssertionError(\"Should not be called, would hang\")  # pragma: no cover\n\n    async def send(msg: Message) -> None:\n        if msg[\"type\"] == \"http.response.start\":\n            assert msg[\"status\"] == 200\n\n    app: ASGIApp = ConsumingMiddleware(endpoint)\n\n    rcv = receive()\n\n    await app(scope, rcv.__anext__, send)\n\n    await rcv.aclose()\n\n\ndef test_downstream_middleware_modifies_receive(\n    test_client_factory: TestClientFactory,\n) -> None:\n    \"\"\"If a downstream middleware modifies receive() the final ASGI app\n    should see the modified version.\n    \"\"\"\n\n    async def endpoint(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        body = await request.body()\n        assert body == b\"foo foo \"\n        await Response()(scope, receive, send)\n\n    class ConsumingMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            body = await request.body()\n            assert body == b\"foo \"\n            return await call_next(request)\n\n    def modifying_middleware(app: ASGIApp) -> ASGIApp:\n        async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:\n            async def wrapped_receive() -> Message:\n                msg = await receive()\n                if msg[\"type\"] == \"http.request\":  # pragma: no branch\n                    msg[\"body\"] = msg[\"body\"] * 2\n                return msg\n\n            await app(scope, wrapped_receive, send)\n\n        return wrapped_app\n\n    client = test_client_factory(ConsumingMiddleware(modifying_middleware(endpoint)))\n\n    resp = client.post(\"/\", content=b\"foo \")\n    assert resp.status_code == 200\n\n\ndef test_pr_1519_comment_1236166180_example() -> None:\n    \"\"\"\n    https://github.com/Kludex/starlette/pull/1519#issuecomment-1236166180\n    \"\"\"\n    bodies: list[bytes] = []\n\n    class LogRequestBodySize(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            print(len(await request.body()))\n            return await call_next(request)\n\n    def replace_body_middleware(app: ASGIApp) -> ASGIApp:\n        async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:\n            async def wrapped_rcv() -> Message:\n                msg = await receive()\n                msg[\"body\"] += b\"-foo\"\n                return msg\n\n            await app(scope, wrapped_rcv, send)\n\n        return wrapped_app\n\n    async def endpoint(request: Request) -> Response:\n        body = await request.body()\n        bodies.append(body)\n        return Response()\n\n    app: ASGIApp = Starlette(routes=[Route(\"/\", endpoint, methods=[\"POST\"])])\n    app = replace_body_middleware(app)\n    app = LogRequestBodySize(app)\n\n    client = TestClient(app)\n    resp = client.post(\"/\", content=b\"Hello, World!\")\n    resp.raise_for_status()\n\n    assert bodies == [b\"Hello, World!-foo\"]\n\n\n@pytest.mark.anyio\nasync def test_multiple_middlewares_stacked_client_disconnected() -> None:\n    \"\"\"\n    Tests for:\n    - https://github.com/Kludex/starlette/issues/2516\n    - https://github.com/Kludex/starlette/pull/2687\n    \"\"\"\n    ordered_events: list[str] = []\n    unordered_events: list[str] = []\n\n    class MyMiddleware(BaseHTTPMiddleware):\n        def __init__(self, app: ASGIApp, version: int) -> None:\n            self.version = version\n            super().__init__(app)\n\n        async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:\n            ordered_events.append(f\"{self.version}:STARTED\")\n            res = await call_next(request)\n            ordered_events.append(f\"{self.version}:COMPLETED\")\n\n            def background() -> None:\n                unordered_events.append(f\"{self.version}:BACKGROUND\")\n\n            assert res.background is None\n            res.background = BackgroundTask(background)\n            return res\n\n    async def sleepy(request: Request) -> Response:\n        try:\n            await request.body()\n        except ClientDisconnect:\n            pass\n        else:  # pragma: no cover\n            raise AssertionError(\"Should have raised ClientDisconnect\")\n        return Response(b\"\")\n\n    app = Starlette(\n        routes=[Route(\"/\", sleepy)],\n        middleware=[Middleware(MyMiddleware, version=_ + 1) for _ in range(10)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n    }\n\n    async def receive() -> AsyncIterator[Message]:\n        yield {\"type\": \"http.disconnect\"}\n\n    sent: list[Message] = []\n\n    async def send(message: Message) -> None:\n        sent.append(message)\n\n    await app(scope, receive().__anext__, send)\n\n    assert ordered_events == [\n        \"1:STARTED\",\n        \"2:STARTED\",\n        \"3:STARTED\",\n        \"4:STARTED\",\n        \"5:STARTED\",\n        \"6:STARTED\",\n        \"7:STARTED\",\n        \"8:STARTED\",\n        \"9:STARTED\",\n        \"10:STARTED\",\n        \"10:COMPLETED\",\n        \"9:COMPLETED\",\n        \"8:COMPLETED\",\n        \"7:COMPLETED\",\n        \"6:COMPLETED\",\n        \"5:COMPLETED\",\n        \"4:COMPLETED\",\n        \"3:COMPLETED\",\n        \"2:COMPLETED\",\n        \"1:COMPLETED\",\n    ]\n\n    assert sorted(unordered_events) == sorted(\n        [\n            \"1:BACKGROUND\",\n            \"2:BACKGROUND\",\n            \"3:BACKGROUND\",\n            \"4:BACKGROUND\",\n            \"5:BACKGROUND\",\n            \"6:BACKGROUND\",\n            \"7:BACKGROUND\",\n            \"8:BACKGROUND\",\n            \"9:BACKGROUND\",\n            \"10:BACKGROUND\",\n        ]\n    )\n\n    assert sent == [\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [(b\"content-length\", b\"0\")],\n        },\n        {\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False},\n    ]\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\"send_body\", [True, False])\nasync def test_poll_for_disconnect_repeated(send_body: bool) -> None:\n    async def app_poll_disconnect(scope: Scope, receive: Receive, send: Send) -> None:\n        for _ in range(2):\n            msg = await receive()\n            while msg[\"type\"] == \"http.request\":\n                msg = await receive()\n            assert msg[\"type\"] == \"http.disconnect\"\n        await Response(b\"good!\")(scope, receive, send)\n\n    class MyMiddleware(BaseHTTPMiddleware):\n        async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:\n            return await call_next(request)\n\n    app = MyMiddleware(app_poll_disconnect)\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n    }\n\n    async def receive() -> AsyncIterator[Message]:\n        # the key here is that we only ever send 1 htt.disconnect message\n        if send_body:\n            yield {\"type\": \"http.request\", \"body\": b\"hello\", \"more_body\": True}\n            yield {\"type\": \"http.request\", \"body\": b\"\", \"more_body\": False}\n        yield {\"type\": \"http.disconnect\"}\n        raise AssertionError(\"Should not be called, would hang\")  # pragma: no cover\n\n    sent: list[Message] = []\n\n    async def send(message: Message) -> None:\n        sent.append(message)\n\n    await app(scope, receive().__anext__, send)\n\n    assert sent == [\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [(b\"content-length\", b\"5\")],\n        },\n        {\"type\": \"http.response.body\", \"body\": b\"good!\", \"more_body\": True},\n        {\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False},\n    ]\n\n\n@pytest.mark.anyio\nasync def test_asgi_pathsend_events(tmpdir: Path) -> None:\n    path = tmpdir / \"example.txt\"\n    with path.open(\"w\") as file:\n        file.write(\"<file content>\")\n\n    response_complete = anyio.Event()\n    events: list[Message] = []\n\n    async def endpoint_with_pathsend(_: Request) -> FileResponse:\n        return FileResponse(path)\n\n    async def passthrough(request: Request, call_next: RequestResponseEndpoint) -> Response:\n        return await call_next(request)\n\n    app = Starlette(\n        middleware=[Middleware(BaseHTTPMiddleware, dispatch=passthrough)],\n        routes=[Route(\"/\", endpoint_with_pathsend)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n        \"headers\": [],\n        \"extensions\": {\"http.response.pathsend\": {}},\n    }\n\n    async def receive() -> Message:\n        raise NotImplementedError(\"Should not be called!\")  # pragma: no cover\n\n    async def send(message: Message) -> None:\n        events.append(message)\n        if message[\"type\"] == \"http.response.pathsend\":\n            response_complete.set()\n\n    await app(scope, receive, send)\n\n    assert len(events) == 2\n    assert events[0][\"type\"] == \"http.response.start\"\n    assert events[1][\"type\"] == \"http.response.pathsend\"\n\n\ndef test_error_context_propagation(test_client_factory: TestClientFactory) -> None:\n    class PassthroughMiddleware(BaseHTTPMiddleware):\n        async def dispatch(\n            self,\n            request: Request,\n            call_next: RequestResponseEndpoint,\n        ) -> Response:\n            return await call_next(request)\n\n    def exception_without_context(request: Request) -> None:\n        raise Exception(\"Exception\")\n\n    def exception_with_context(request: Request) -> None:\n        try:\n            raise Exception(\"Inner exception\")\n        except Exception:\n            raise Exception(\"Outer exception\")\n\n    def exception_with_cause(request: Request) -> None:\n        try:\n            raise Exception(\"Inner exception\")\n        except Exception as e:\n            raise Exception(\"Outer exception\") from e\n\n    app = Starlette(\n        routes=[\n            Route(\"/exception-without-context\", endpoint=exception_without_context),\n            Route(\"/exception-with-context\", endpoint=exception_with_context),\n            Route(\"/exception-with-cause\", endpoint=exception_with_cause),\n        ],\n        middleware=[Middleware(PassthroughMiddleware)],\n    )\n    client = test_client_factory(app)\n\n    # For exceptions without context the context is filled with the `anyio.EndOfStream`\n    # but it is suppressed therefore not propagated to traceback.\n    with pytest.raises(Exception) as ctx:\n        client.get(\"/exception-without-context\")\n    assert str(ctx.value) == \"Exception\"\n    assert ctx.value.__cause__ is None\n    assert ctx.value.__context__ is not None\n    assert ctx.value.__suppress_context__ is True\n\n    # For exceptions with context the context is propagated as a cause to avoid\n    # `anyio.EndOfStream` error from overwriting it.\n    with pytest.raises(Exception) as ctx:\n        client.get(\"/exception-with-context\")\n    assert str(ctx.value) == \"Outer exception\"\n    assert ctx.value.__cause__ is not None\n    assert str(ctx.value.__cause__) == \"Inner exception\"\n\n    # For exceptions with cause check that it gets correctly propagated.\n    with pytest.raises(Exception) as ctx:\n        client.get(\"/exception-with-cause\")\n    assert str(ctx.value) == \"Outer exception\"\n    assert ctx.value.__cause__ is not None\n    assert str(ctx.value.__cause__) == \"Inner exception\"\n"
  },
  {
    "path": "tests/middleware/test_cors.py",
    "content": "from starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.cors import CORSMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\nfrom tests.types import TestClientFactory\n\n\ndef test_cors_allow_all(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"*\"],\n                allow_headers=[\"*\"],\n                allow_methods=[\"*\"],\n                expose_headers=[\"X-Status\"],\n                allow_credentials=True,\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Example\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-allow-headers\"] == \"X-Example\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n    assert response.headers[\"vary\"] == \"Origin\"\n\n    # Test standard response\n    headers = {\"Origin\": \"https://example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-expose-headers\"] == \"X-Status\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n\n    # Test standard credentialed response\n    headers = {\"Origin\": \"https://example.org\", \"Cookie\": \"star_cookie=sugar\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-expose-headers\"] == \"X-Status\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n\n    # Test non-CORS response\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n\ndef test_cors_allow_all_except_credentials(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"*\"],\n                allow_headers=[\"*\"],\n                allow_methods=[\"*\"],\n                expose_headers=[\"X-Status\"],\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Example\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert response.headers[\"access-control-allow-origin\"] == \"*\"\n    assert response.headers[\"access-control-allow-headers\"] == \"X-Example\"\n    assert \"access-control-allow-credentials\" not in response.headers\n    assert \"vary\" not in response.headers\n\n    # Test standard response\n    headers = {\"Origin\": \"https://example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"*\"\n    assert response.headers[\"access-control-expose-headers\"] == \"X-Status\"\n    assert \"access-control-allow-credentials\" not in response.headers\n\n    # Test non-CORS response\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n\ndef test_cors_allow_specific_origin(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"https://example.org\"],\n                allow_headers=[\"X-Example\", \"Content-Type\"],\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Example, Content-Type\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-allow-headers\"] == (\n        \"Accept, Accept-Language, Content-Language, Content-Type, X-Example\"\n    )\n    assert \"access-control-allow-credentials\" not in response.headers\n\n    # Test standard response\n    headers = {\"Origin\": \"https://example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert \"access-control-allow-credentials\" not in response.headers\n\n    # Test non-CORS response\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n\ndef test_cors_disallowed_preflight(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> None:\n        pass  # pragma: no cover\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"https://example.org\"],\n                allow_headers=[\"X-Example\"],\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://another.org\",\n        \"Access-Control-Request-Method\": \"POST\",\n        \"Access-Control-Request-Headers\": \"X-Nope\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 400\n    assert response.text == \"Disallowed CORS origin, method, headers\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n    # Bug specific test, https://github.com/Kludex/starlette/pull/1199\n    # Test preflight response text with multiple disallowed headers\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Nope-1, X-Nope-2\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.text == \"Disallowed CORS headers\"\n\n\ndef test_preflight_allows_request_origin_if_origins_wildcard_and_credentials_allowed(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> None:\n        return  # pragma: no cover\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"*\"],\n                allow_methods=[\"POST\"],\n                allow_credentials=True,\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"POST\",\n    }\n    response = client.options(\n        \"/\",\n        headers=headers,\n    )\n    assert response.status_code == 200\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n    assert response.headers[\"vary\"] == \"Origin\"\n\n\ndef test_cors_preflight_allow_all_methods(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> None:\n        pass  # pragma: no cover\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"*\"], allow_methods=[\"*\"])],\n    )\n\n    client = test_client_factory(app)\n\n    headers = {\n        \"Origin\": \"https://example.org\",\n        \"Access-Control-Request-Method\": \"POST\",\n    }\n\n    for method in (\"DELETE\", \"GET\", \"HEAD\", \"OPTIONS\", \"PATCH\", \"POST\", \"PUT\"):\n        response = client.options(\"/\", headers=headers)\n        assert response.status_code == 200\n        assert method in response.headers[\"access-control-allow-methods\"]\n\n\ndef test_cors_allow_all_methods(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[\n            Route(\n                \"/\",\n                endpoint=homepage,\n                methods=[\"delete\", \"get\", \"head\", \"options\", \"patch\", \"post\", \"put\"],\n            )\n        ],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"*\"], allow_methods=[\"*\"])],\n    )\n\n    client = test_client_factory(app)\n\n    headers = {\"Origin\": \"https://example.org\"}\n\n    for method in (\"patch\", \"post\", \"put\"):\n        response = getattr(client, method)(\"/\", headers=headers, json={})\n        assert response.status_code == 200\n    for method in (\"delete\", \"get\", \"head\", \"options\"):\n        response = getattr(client, method)(\"/\", headers=headers)\n        assert response.status_code == 200\n\n\ndef test_cors_allow_origin_regex(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_headers=[\"X-Example\", \"Content-Type\"],\n                allow_origin_regex=\"https://.*\",\n                allow_credentials=True,\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test standard response\n    headers = {\"Origin\": \"https://example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n\n    # Test standard credentialed response\n    headers = {\"Origin\": \"https://example.org\", \"Cookie\": \"star_cookie=sugar\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n\n    # Test disallowed standard response\n    # Note that enforcement is a browser concern. The disallowed-ness is reflected\n    # in the lack of an \"access-control-allow-origin\" header in the response.\n    headers = {\"Origin\": \"http://example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n    # Test pre-flight response\n    headers = {\n        \"Origin\": \"https://another.com\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Example, content-type\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://another.com\"\n    assert response.headers[\"access-control-allow-headers\"] == (\n        \"Accept, Accept-Language, Content-Language, Content-Type, X-Example\"\n    )\n    assert response.headers[\"access-control-allow-credentials\"] == \"true\"\n\n    # Test disallowed pre-flight response\n    headers = {\n        \"Origin\": \"http://another.com\",\n        \"Access-Control-Request-Method\": \"GET\",\n        \"Access-Control-Request-Headers\": \"X-Example\",\n    }\n    response = client.options(\"/\", headers=headers)\n    assert response.status_code == 400\n    assert response.text == \"Disallowed CORS origin\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n\ndef test_cors_allow_origin_regex_fullmatch(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_headers=[\"X-Example\", \"Content-Type\"],\n                allow_origin_regex=r\"https://.*\\.example.org\",\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test standard response\n    headers = {\"Origin\": \"https://subdomain.example.org\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert response.headers[\"access-control-allow-origin\"] == \"https://subdomain.example.org\"\n    assert \"access-control-allow-credentials\" not in response.headers\n\n    # Test disallowed standard response\n    headers = {\"Origin\": \"https://subdomain.example.org.hacker.com\"}\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-origin\" not in response.headers\n\n\ndef test_cors_vary_header_defaults_to_origin(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"https://example.org\"])],\n    )\n\n    headers = {\"Origin\": \"https://example.org\"}\n\n    client = test_client_factory(app)\n\n    response = client.get(\"/\", headers=headers)\n    assert response.status_code == 200\n    assert response.headers[\"vary\"] == \"Origin\"\n\n\ndef test_cors_vary_header_is_not_set_for_non_credentialed_request(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200, headers={\"Vary\": \"Accept-Encoding\"})\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"*\"])],\n    )\n    client = test_client_factory(app)\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://someplace.org\"})\n    assert response.status_code == 200\n    assert response.headers[\"vary\"] == \"Accept-Encoding\"\n\n\ndef test_cors_vary_header_is_properly_set_for_credentialed_request(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200, headers={\"Vary\": \"Accept-Encoding\"})\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"*\"], allow_credentials=True)],\n    )\n    client = test_client_factory(app)\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://someplace.org\"})\n    assert response.status_code == 200\n    assert response.headers[\"vary\"] == \"Accept-Encoding, Origin\"\n\n\ndef test_cors_vary_header_is_properly_set_when_allow_origins_is_not_wildcard(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200, headers={\"Vary\": \"Accept-Encoding\"})\n\n    app = Starlette(\n        routes=[\n            Route(\"/\", endpoint=homepage),\n        ],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"https://example.org\"])],\n    )\n    client = test_client_factory(app)\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://example.org\"})\n    assert response.status_code == 200\n    assert response.headers[\"vary\"] == \"Accept-Encoding, Origin\"\n\n\ndef test_cors_allowed_origin_does_not_leak_between_requests(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CORSMiddleware, allow_origins=[\"https://example.org\"])],\n    )\n\n    client = test_client_factory(app)\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://example.org\"})\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://other.org\"})\n    assert \"access-control-allow-origin\" not in response.headers\n\n    response = client.get(\"/\", headers={\"Origin\": \"https://example.org\"})\n    assert response.headers[\"access-control-allow-origin\"] == \"https://example.org\"\n\n\ndef test_cors_private_network_access_allowed(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Homepage\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"*\"],\n                allow_methods=[\"*\"],\n                allow_private_network=True,\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    headers_without_pna = {\"Origin\": \"https://example.org\", \"Access-Control-Request-Method\": \"GET\"}\n    headers_with_pna = {**headers_without_pna, \"Access-Control-Request-Private-Network\": \"true\"}\n\n    # Test preflight with Private Network Access request\n    response = client.options(\"/\", headers=headers_with_pna)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert response.headers[\"access-control-allow-private-network\"] == \"true\"\n\n    # Test preflight without Private Network Access request\n    response = client.options(\"/\", headers=headers_without_pna)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert \"access-control-allow-private-network\" not in response.headers\n\n    # The access-control-allow-private-network header is not set for non-preflight requests\n    response = client.get(\"/\", headers=headers_with_pna)\n    assert response.status_code == 200\n    assert response.text == \"Homepage\"\n    assert \"access-control-allow-private-network\" not in response.headers\n    assert \"access-control-allow-origin\" in response.headers\n\n\ndef test_cors_private_network_access_disallowed(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> None: ...  # pragma: no cover\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[\n            Middleware(\n                CORSMiddleware,\n                allow_origins=[\"*\"],\n                allow_methods=[\"*\"],\n                allow_private_network=False,\n            )\n        ],\n    )\n\n    client = test_client_factory(app)\n\n    # Test preflight with Private Network Access request when not allowed\n    headers_without_pna = {\"Origin\": \"https://example.org\", \"Access-Control-Request-Method\": \"GET\"}\n    headers_with_pna = {**headers_without_pna, \"Access-Control-Request-Private-Network\": \"true\"}\n\n    response = client.options(\"/\", headers=headers_without_pna)\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert \"access-control-allow-private-network\" not in response.headers\n\n    # If the request includes a Private Network Access header, but the middleware is configured to disallow it, the\n    # request should be denied with a 400 response.\n    response = client.options(\"/\", headers=headers_with_pna)\n    assert response.status_code == 400\n    assert response.text == \"Disallowed CORS private-network\"\n    assert \"access-control-allow-private-network\" not in response.headers\n"
  },
  {
    "path": "tests/middleware/test_errors.py",
    "content": "from typing import Any\n\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.background import BackgroundTask\nfrom starlette.middleware.errors import ServerErrorMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, Response\nfrom starlette.routing import Route\nfrom starlette.types import Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\ndef test_handler(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        raise RuntimeError(\"Something went wrong\")\n\n    def error_500(request: Request, exc: Exception) -> JSONResponse:\n        return JSONResponse({\"detail\": \"Server Error\"}, status_code=500)\n\n    app = ServerErrorMiddleware(app, handler=error_500)\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\")\n    assert response.status_code == 500\n    assert response.json() == {\"detail\": \"Server Error\"}\n\n\ndef test_debug_text(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        raise RuntimeError(\"Something went wrong\")\n\n    app = ServerErrorMiddleware(app, debug=True)\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\")\n    assert response.status_code == 500\n    assert response.headers[\"content-type\"].startswith(\"text/plain\")\n    assert \"RuntimeError: Something went wrong\" in response.text\n\n\ndef test_debug_html(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        raise RuntimeError(\"Something went wrong\")\n\n    app = ServerErrorMiddleware(app, debug=True)\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\", headers={\"Accept\": \"text/html, */*\"})\n    assert response.status_code == 500\n    assert response.headers[\"content-type\"].startswith(\"text/html\")\n    assert \"RuntimeError\" in response.text\n\n\ndef test_debug_after_response_sent(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(b\"\", status_code=204)\n        await response(scope, receive, send)\n        raise RuntimeError(\"Something went wrong\")\n\n    app = ServerErrorMiddleware(app, debug=True)\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        client.get(\"/\")\n\n\ndef test_debug_not_http(test_client_factory: TestClientFactory) -> None:\n    \"\"\"\n    DebugMiddleware should just pass through any non-http messages as-is.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        raise RuntimeError(\"Something went wrong\")\n\n    app = ServerErrorMiddleware(app)\n\n    with pytest.raises(RuntimeError):\n        client = test_client_factory(app)\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_background_task(test_client_factory: TestClientFactory) -> None:\n    accessed_error_handler = False\n\n    def error_handler(request: Request, exc: Exception) -> Any:\n        nonlocal accessed_error_handler\n        accessed_error_handler = True\n\n    def raise_exception() -> None:\n        raise Exception(\"Something went wrong\")\n\n    async def endpoint(request: Request) -> Response:\n        task = BackgroundTask(raise_exception)\n        return Response(status_code=204, background=task)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=endpoint)],\n        exception_handlers={Exception: error_handler},\n    )\n\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\")\n    assert response.status_code == 204\n    assert accessed_error_handler\n"
  },
  {
    "path": "tests/middleware/test_gzip.py",
    "content": "from __future__ import annotations\n\nfrom pathlib import Path\n\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.gzip import GZipMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import ContentStream, FileResponse, PlainTextResponse, StreamingResponse\nfrom starlette.routing import Route\nfrom starlette.types import Message\nfrom tests.types import TestClientFactory\n\n\ndef test_gzip_responses(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"x\" * 4000, status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"gzip\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert response.headers[\"Content-Encoding\"] == \"gzip\"\n    assert response.headers[\"Vary\"] == \"Accept-Encoding\"\n    assert int(response.headers[\"Content-Length\"]) < 4000\n\n\ndef test_gzip_not_in_accept_encoding(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"x\" * 4000, status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"identity\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert \"Content-Encoding\" not in response.headers\n    assert response.headers[\"Vary\"] == \"Accept-Encoding\"\n    assert int(response.headers[\"Content-Length\"]) == 4000\n\n\ndef test_gzip_ignored_for_small_responses(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"OK\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"gzip\"})\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n    assert \"Content-Encoding\" not in response.headers\n    assert \"Vary\" not in response.headers\n    assert int(response.headers[\"Content-Length\"]) == 2\n\n\ndef test_gzip_streaming_response(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> StreamingResponse:\n        async def generator(bytes: bytes, count: int) -> ContentStream:\n            for index in range(count):\n                yield bytes\n\n        streaming = generator(bytes=b\"x\" * 400, count=10)\n        return StreamingResponse(streaming, status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"gzip\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert response.headers[\"Content-Encoding\"] == \"gzip\"\n    assert response.headers[\"Vary\"] == \"Accept-Encoding\"\n    assert \"Content-Length\" not in response.headers\n\n\ndef test_gzip_streaming_response_identity(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> StreamingResponse:\n        async def generator(bytes: bytes, count: int) -> ContentStream:\n            for index in range(count):\n                yield bytes\n\n        streaming = generator(bytes=b\"x\" * 400, count=10)\n        return StreamingResponse(streaming, status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"identity\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert \"Content-Encoding\" not in response.headers\n    assert response.headers[\"Vary\"] == \"Accept-Encoding\"\n    assert \"Content-Length\" not in response.headers\n\n\ndef test_gzip_ignored_for_responses_with_encoding_set(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def homepage(request: Request) -> StreamingResponse:\n        async def generator(bytes: bytes, count: int) -> ContentStream:\n            for index in range(count):\n                yield bytes\n\n        streaming = generator(bytes=b\"x\" * 400, count=10)\n        return StreamingResponse(streaming, status_code=200, headers={\"Content-Encoding\": \"text\"})\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"gzip, text\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert response.headers[\"Content-Encoding\"] == \"text\"\n    assert \"Vary\" not in response.headers\n    assert \"Content-Length\" not in response.headers\n\n\ndef test_gzip_ignored_on_server_sent_events(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> StreamingResponse:\n        async def generator(bytes: bytes, count: int) -> ContentStream:\n            for _ in range(count):\n                yield bytes\n\n        streaming = generator(bytes=b\"x\" * 400, count=10)\n        return StreamingResponse(streaming, status_code=200, media_type=\"text/event-stream\")\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"accept-encoding\": \"gzip\"})\n    assert response.status_code == 200\n    assert response.text == \"x\" * 4000\n    assert \"Content-Encoding\" not in response.headers\n    assert \"Content-Length\" not in response.headers\n\n\n@pytest.mark.anyio\nasync def test_gzip_ignored_for_pathsend_responses(tmpdir: Path) -> None:\n    path = tmpdir / \"example.txt\"\n    with path.open(\"w\") as file:\n        file.write(\"<file content>\")\n\n    events: list[Message] = []\n\n    async def endpoint_with_pathsend(request: Request) -> FileResponse:\n        _ = await request.body()\n        return FileResponse(path)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=endpoint_with_pathsend)],\n        middleware=[Middleware(GZipMiddleware)],\n    )\n\n    scope = {\n        \"type\": \"http\",\n        \"version\": \"3\",\n        \"method\": \"GET\",\n        \"path\": \"/\",\n        \"headers\": [(b\"accept-encoding\", b\"gzip, text\")],\n        \"extensions\": {\"http.response.pathsend\": {}},\n    }\n\n    async def receive() -> Message:\n        return {\"type\": \"http.request\", \"body\": b\"\", \"more_body\": False}\n\n    async def send(message: Message) -> None:\n        events.append(message)\n\n    await app(scope, receive, send)\n\n    assert len(events) == 2\n    assert events[0][\"type\"] == \"http.response.start\"\n    assert events[1][\"type\"] == \"http.response.pathsend\"\n"
  },
  {
    "path": "tests/middleware/test_https_redirect.py",
    "content": "from starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\nfrom tests.types import TestClientFactory\n\n\ndef test_https_redirect_middleware(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"OK\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(HTTPSRedirectMiddleware)],\n    )\n\n    client = test_client_factory(app, base_url=\"https://testserver\")\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", follow_redirects=False)\n    assert response.status_code == 307\n    assert response.headers[\"location\"] == \"https://testserver/\"\n\n    client = test_client_factory(app, base_url=\"http://testserver:80\")\n    response = client.get(\"/\", follow_redirects=False)\n    assert response.status_code == 307\n    assert response.headers[\"location\"] == \"https://testserver/\"\n\n    client = test_client_factory(app, base_url=\"http://testserver:443\")\n    response = client.get(\"/\", follow_redirects=False)\n    assert response.status_code == 307\n    assert response.headers[\"location\"] == \"https://testserver/\"\n\n    client = test_client_factory(app, base_url=\"http://testserver:123\")\n    response = client.get(\"/\", follow_redirects=False)\n    assert response.status_code == 307\n    assert response.headers[\"location\"] == \"https://testserver:123/\"\n"
  },
  {
    "path": "tests/middleware/test_middleware.py",
    "content": "from starlette.middleware import Middleware\nfrom starlette.types import ASGIApp, Receive, Scope, Send\n\n\nclass CustomMiddleware:  # pragma: no cover\n    def __init__(self, app: ASGIApp, foo: str, *, bar: int) -> None:\n        self.app = app\n        self.foo = foo\n        self.bar = bar\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        await self.app(scope, receive, send)\n\n\ndef test_middleware_repr() -> None:\n    middleware = Middleware(CustomMiddleware, \"foo\", bar=123)\n    assert repr(middleware) == \"Middleware(CustomMiddleware, 'foo', bar=123)\"\n\n\ndef test_middleware_iter() -> None:\n    cls, args, kwargs = Middleware(CustomMiddleware, \"foo\", bar=123)\n    assert (cls, args, kwargs) == (CustomMiddleware, (\"foo\",), {\"bar\": 123})\n"
  },
  {
    "path": "tests/middleware/test_session.py",
    "content": "import re\n\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.sessions import Session, SessionMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Mount, Route\nfrom starlette.testclient import TestClient\nfrom tests.types import TestClientFactory\n\n\ndef view_session(request: Request) -> JSONResponse:\n    return JSONResponse({\"session\": request.session})\n\n\nasync def update_session(request: Request) -> JSONResponse:\n    data = await request.json()\n    request.session.update(data)\n    return JSONResponse({\"session\": request.session})\n\n\nasync def clear_session(request: Request) -> JSONResponse:\n    request.session.clear()\n    return JSONResponse({\"session\": request.session})\n\n\ndef no_session_access(request: Request) -> JSONResponse:\n    return JSONResponse({\"status\": \"ok\"})\n\n\ndef test_session(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n            Route(\"/clear_session\", endpoint=clear_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\")],\n    )\n    client = test_client_factory(app)\n\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    # check cookie max-age\n    set_cookie = response.headers[\"set-cookie\"]\n    max_age_matches = re.search(r\"; Max-Age=([0-9]+);\", set_cookie)\n    assert max_age_matches is not None\n    assert int(max_age_matches[1]) == 14 * 24 * 3600\n\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    response = client.post(\"/clear_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_session_expires(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\", max_age=-1)],\n    )\n    client = test_client_factory(app)\n\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    # requests removes expired cookies from response.cookies, we need to\n    # fetch session id from the headers and pass it explicitly\n    expired_cookie_header = response.headers[\"set-cookie\"]\n    expired_session_match = re.search(r\"session=([^;]*);\", expired_cookie_header)\n    assert expired_session_match is not None\n    expired_session_value = expired_session_match[1]\n    client = test_client_factory(app, cookies={\"session\": expired_session_value})\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_secure_session(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n            Route(\"/clear_session\", endpoint=clear_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\", https_only=True)],\n    )\n    secure_client = test_client_factory(app, base_url=\"https://testserver\")\n    unsecure_client = test_client_factory(app, base_url=\"http://testserver\")\n\n    response = unsecure_client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = unsecure_client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    response = unsecure_client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = secure_client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = secure_client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    response = secure_client.get(\"/view_session\")\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    response = secure_client.post(\"/clear_session\")\n    assert response.json() == {\"session\": {}}\n\n    response = secure_client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_session_cookie_subpath(test_client_factory: TestClientFactory) -> None:\n    second_app = Starlette(\n        routes=[\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\", path=\"/second_app\")],\n    )\n    app = Starlette(routes=[Mount(\"/second_app\", app=second_app)])\n    client = test_client_factory(app, base_url=\"http://testserver/second_app\")\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.status_code == 200\n    cookie = response.headers[\"set-cookie\"]\n    cookie_path_match = re.search(r\"; path=(\\S+);\", cookie)\n    assert cookie_path_match is not None\n    cookie_path = cookie_path_match.groups()[0]\n    assert cookie_path == \"/second_app\"\n\n\ndef test_invalid_session_cookie(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\")],\n    )\n    client = test_client_factory(app)\n\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    # we expect it to not raise an exception if we provide a bogus session cookie\n    client = test_client_factory(app, cookies={\"session\": \"invalid\"})\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_session_cookie(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\", max_age=None)],\n    )\n    client: TestClient = test_client_factory(app)\n\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    # check cookie max-age\n    set_cookie = response.headers[\"set-cookie\"]\n    assert \"Max-Age\" not in set_cookie\n\n    client.cookies.delete(\"session\")\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_domain_cookie(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\", domain=\".example.com\")],\n    )\n    client: TestClient = test_client_factory(app)\n\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n\n    # check cookie max-age\n    set_cookie = response.headers[\"set-cookie\"]\n    assert \"domain=.example.com\" in set_cookie\n\n    client.cookies.delete(\"session\")\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {}}\n\n\ndef test_set_cookie_only_on_modification(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\")],\n    )\n    client = test_client_factory(app)\n\n    # Write to session - should send Set-Cookie\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert \"set-cookie\" in response.headers\n\n    # Read-only access - should NOT send Set-Cookie\n    response = client.get(\"/view_session\")\n    assert response.json() == {\"session\": {\"some\": \"data\"}}\n    assert \"set-cookie\" not in response.headers\n\n\ndef test_vary_cookie_on_access(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(\n        routes=[\n            Route(\"/view_session\", endpoint=view_session),\n            Route(\"/update_session\", endpoint=update_session, methods=[\"POST\"]),\n            Route(\"/no_session\", endpoint=no_session_access),\n        ],\n        middleware=[Middleware(SessionMiddleware, secret_key=\"example\")],\n    )\n    client = test_client_factory(app)\n\n    # Modifying session should add Vary: Cookie\n    response = client.post(\"/update_session\", json={\"some\": \"data\"})\n    assert \"cookie\" in response.headers.get(\"vary\", \"\").lower()\n\n    # Reading a non-empty session should add Vary: Cookie\n    response = client.get(\"/view_session\")\n    assert \"cookie\" in response.headers.get(\"vary\", \"\").lower()\n\n    # Not accessing session at all should NOT add Vary: Cookie\n    response = client.get(\"/no_session\")\n    assert \"cookie\" not in response.headers.get(\"vary\", \"\").lower()\n\n\ndef test_session_tracks_modification() -> None:\n    session = Session({\"a\": \"1\", \"b\": \"2\"})\n    assert not session.modified\n\n    # __setitem__\n    session[\"c\"] = \"3\"\n    assert session.modified\n\n    # __delitem__\n    session = Session({\"a\": \"1\"})\n    del session[\"a\"]\n    assert session.modified\n\n    # clear\n    session = Session({\"a\": \"1\"})\n    session.clear()\n    assert session.modified\n\n    # pop with existing key\n    session = Session({\"a\": \"1\"})\n    session.pop(\"a\")\n    assert session.modified\n\n    # pop with missing key\n    session = Session({\"a\": \"1\"})\n    session.pop(\"missing\", None)\n    assert not session.modified\n\n    # setdefault with missing key\n    session = Session({\"a\": \"1\"})\n    session.setdefault(\"b\", \"2\")\n    assert session.modified\n\n    # setdefault with existing key\n    session = Session({\"a\": \"1\"})\n    session.setdefault(\"a\", \"2\")\n    assert not session.modified\n"
  },
  {
    "path": "tests/middleware/test_trusted_host.py",
    "content": "from starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\nfrom tests.types import TestClientFactory\n\n\ndef test_trusted_host_middleware(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"OK\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(TrustedHostMiddleware, allowed_hosts=[\"testserver\", \"*.testserver\"])],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    client = test_client_factory(app, base_url=\"http://subdomain.testserver\")\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    client = test_client_factory(app, base_url=\"http://invalidhost\")\n    response = client.get(\"/\")\n    assert response.status_code == 400\n\n\ndef test_default_allowed_hosts() -> None:\n    app = Starlette()\n    middleware = TrustedHostMiddleware(app)\n    assert middleware.allowed_hosts == [\"*\"]\n\n\ndef test_www_redirect(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"OK\", status_code=200)\n\n    app = Starlette(\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(TrustedHostMiddleware, allowed_hosts=[\"www.example.com\"])],\n    )\n\n    client = test_client_factory(app, base_url=\"https://example.com\")\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.url == \"https://www.example.com/\"\n"
  },
  {
    "path": "tests/middleware/test_wsgi.py",
    "content": "import sys\nfrom collections.abc import Callable, Iterable\nfrom typing import Any\n\nimport pytest\n\nfrom starlette._utils import collapse_excgroups\nfrom starlette.middleware.wsgi import WSGIMiddleware, build_environ\nfrom tests.types import TestClientFactory\n\nWSGIResponse = Iterable[bytes]\nStartResponse = Callable[..., Any]\nEnvironment = dict[str, Any]\n\n\ndef hello_world(\n    environ: Environment,\n    start_response: StartResponse,\n) -> WSGIResponse:\n    status = \"200 OK\"\n    output = b\"Hello World!\\n\"\n    headers = [\n        (\"Content-Type\", \"text/plain; charset=utf-8\"),\n        (\"Content-Length\", str(len(output))),\n    ]\n    start_response(status, headers)\n    return [output]\n\n\ndef echo_body(\n    environ: Environment,\n    start_response: StartResponse,\n) -> WSGIResponse:\n    status = \"200 OK\"\n    output = environ[\"wsgi.input\"].read()\n    headers = [\n        (\"Content-Type\", \"text/plain; charset=utf-8\"),\n        (\"Content-Length\", str(len(output))),\n    ]\n    start_response(status, headers)\n    return [output]\n\n\ndef raise_exception(\n    environ: Environment,\n    start_response: StartResponse,\n) -> WSGIResponse:\n    raise RuntimeError(\"Something went wrong\")\n\n\ndef return_exc_info(\n    environ: Environment,\n    start_response: StartResponse,\n) -> WSGIResponse:\n    try:\n        raise RuntimeError(\"Something went wrong\")\n    except RuntimeError:\n        status = \"500 Internal Server Error\"\n        output = b\"Internal Server Error\"\n        headers = [\n            (\"Content-Type\", \"text/plain; charset=utf-8\"),\n            (\"Content-Length\", str(len(output))),\n        ]\n        start_response(status, headers, exc_info=sys.exc_info())\n        return [output]\n\n\ndef test_wsgi_get(test_client_factory: TestClientFactory) -> None:\n    app = WSGIMiddleware(hello_world)\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello World!\\n\"\n\n\ndef test_wsgi_post(test_client_factory: TestClientFactory) -> None:\n    app = WSGIMiddleware(echo_body)\n    client = test_client_factory(app)\n    response = client.post(\"/\", json={\"example\": 123})\n    assert response.status_code == 200\n    assert response.text == '{\"example\":123}'\n\n\ndef test_wsgi_exception(test_client_factory: TestClientFactory) -> None:\n    # Note that we're testing the WSGI app directly here.\n    # The HTTP protocol implementations would catch this error and return 500.\n    app = WSGIMiddleware(raise_exception)\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError), collapse_excgroups():\n        client.get(\"/\")\n\n\ndef test_wsgi_exc_info(test_client_factory: TestClientFactory) -> None:\n    # Note that we're testing the WSGI app directly here.\n    # The HTTP protocol implementations would catch this error and return 500.\n    app = WSGIMiddleware(return_exc_info)\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        response = client.get(\"/\")\n\n    app = WSGIMiddleware(return_exc_info)\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\")\n    assert response.status_code == 500\n    assert response.text == \"Internal Server Error\"\n\n\ndef test_build_environ() -> None:\n    scope = {\n        \"type\": \"http\",\n        \"http_version\": \"1.1\",\n        \"method\": \"GET\",\n        \"scheme\": \"https\",\n        \"path\": \"/sub/\",\n        \"root_path\": \"/sub\",\n        \"query_string\": b\"a=123&b=456\",\n        \"headers\": [\n            (b\"host\", b\"www.example.org\"),\n            (b\"content-type\", b\"application/json\"),\n            (b\"content-length\", b\"18\"),\n            (b\"accept\", b\"application/json\"),\n            (b\"accept\", b\"text/plain\"),\n        ],\n        \"client\": (\"134.56.78.4\", 1453),\n        \"server\": (\"www.example.org\", 443),\n    }\n    body = b'{\"example\":\"body\"}'\n    environ = build_environ(scope, body)\n    stream = environ.pop(\"wsgi.input\")\n    assert stream.read() == b'{\"example\":\"body\"}'\n    assert environ == {\n        \"CONTENT_LENGTH\": \"18\",\n        \"CONTENT_TYPE\": \"application/json\",\n        \"HTTP_ACCEPT\": \"application/json,text/plain\",\n        \"HTTP_HOST\": \"www.example.org\",\n        \"PATH_INFO\": \"/\",\n        \"QUERY_STRING\": \"a=123&b=456\",\n        \"REMOTE_ADDR\": \"134.56.78.4\",\n        \"REQUEST_METHOD\": \"GET\",\n        \"SCRIPT_NAME\": \"/sub\",\n        \"SERVER_NAME\": \"www.example.org\",\n        \"SERVER_PORT\": 443,\n        \"SERVER_PROTOCOL\": \"HTTP/1.1\",\n        \"wsgi.errors\": sys.stdout,\n        \"wsgi.multiprocess\": True,\n        \"wsgi.multithread\": True,\n        \"wsgi.run_once\": False,\n        \"wsgi.url_scheme\": \"https\",\n        \"wsgi.version\": (1, 0),\n    }\n\n\ndef test_build_environ_encoding() -> None:\n    scope = {\n        \"type\": \"http\",\n        \"http_version\": \"1.1\",\n        \"method\": \"GET\",\n        \"path\": \"/小星\",\n        \"root_path\": \"/中国\",\n        \"query_string\": b\"a=123&b=456\",\n        \"headers\": [],\n    }\n    environ = build_environ(scope, b\"\")\n    assert environ[\"SCRIPT_NAME\"] == \"/中国\".encode().decode(\"latin-1\")\n    assert environ[\"PATH_INFO\"] == \"/小星\".encode().decode(\"latin-1\")\n"
  },
  {
    "path": "tests/statics/example.txt",
    "content": "123\n"
  },
  {
    "path": "tests/test__utils.py",
    "content": "import functools\nfrom typing import Any\nfrom unittest.mock import create_autospec\n\nimport pytest\n\nfrom starlette._utils import get_route_path, is_async_callable\nfrom starlette.types import Scope\n\n\ndef test_async_func() -> None:\n    async def async_func() -> None: ...  # pragma: no cover\n\n    def func() -> None: ...  # pragma: no cover\n\n    assert is_async_callable(async_func)\n    assert not is_async_callable(func)\n\n\ndef test_async_partial() -> None:\n    async def async_func(a: Any, b: Any) -> None: ...  # pragma: no cover\n\n    def func(a: Any, b: Any) -> None: ...  # pragma: no cover\n\n    partial = functools.partial(async_func, 1)\n    assert is_async_callable(partial)\n\n    partial = functools.partial(func, 1)  # type: ignore\n    assert not is_async_callable(partial)\n\n\ndef test_async_method() -> None:\n    class Async:\n        async def method(self) -> None: ...  # pragma: no cover\n\n    class Sync:\n        def method(self) -> None: ...  # pragma: no cover\n\n    assert is_async_callable(Async().method)\n    assert not is_async_callable(Sync().method)\n\n\ndef test_async_object_call() -> None:\n    class Async:\n        async def __call__(self) -> None: ...  # pragma: no cover\n\n    class Sync:\n        def __call__(self) -> None: ...  # pragma: no cover\n\n    assert is_async_callable(Async())\n    assert not is_async_callable(Sync())\n\n\ndef test_async_partial_object_call() -> None:\n    class Async:\n        async def __call__(\n            self,\n            a: Any,\n            b: Any,\n        ) -> None: ...  # pragma: no cover\n\n    class Sync:\n        def __call__(\n            self,\n            a: Any,\n            b: Any,\n        ) -> None: ...  # pragma: no cover\n\n    partial = functools.partial(Async(), 1)\n    assert is_async_callable(partial)\n\n    partial = functools.partial(Sync(), 1)  # type: ignore\n    assert not is_async_callable(partial)\n\n\ndef test_async_nested_partial() -> None:\n    async def async_func(\n        a: Any,\n        b: Any,\n    ) -> None: ...  # pragma: no cover\n\n    partial = functools.partial(async_func, b=2)\n    nested_partial = functools.partial(partial, a=1)\n    assert is_async_callable(nested_partial)\n\n\ndef test_async_mocked_async_function() -> None:\n    async def async_func() -> None: ...  # pragma: no cover\n\n    mock = create_autospec(async_func)\n    assert is_async_callable(mock)\n\n\n@pytest.mark.parametrize(\n    \"scope, expected_result\",\n    [\n        ({\"path\": \"/foo-123/bar\", \"root_path\": \"/foo\"}, \"/foo-123/bar\"),\n        ({\"path\": \"/foo/bar\", \"root_path\": \"/foo\"}, \"/bar\"),\n        ({\"path\": \"/foo\", \"root_path\": \"/foo\"}, \"\"),\n        ({\"path\": \"/foo/bar\", \"root_path\": \"/bar\"}, \"/foo/bar\"),\n    ],\n)\ndef test_get_route_path(scope: Scope, expected_result: str) -> None:\n    assert get_route_path(scope) == expected_result\n"
  },
  {
    "path": "tests/test_applications.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom collections.abc import AsyncGenerator, AsyncIterator, Callable, Generator\nfrom contextlib import asynccontextmanager\nfrom pathlib import Path\nfrom typing import TypedDict\n\nimport anyio.from_thread\nimport pytest\n\nfrom starlette import status\nfrom starlette.applications import Starlette\nfrom starlette.endpoints import HTTPEndpoint\nfrom starlette.exceptions import HTTPException, WebSocketException\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, PlainTextResponse\nfrom starlette.routing import Host, Mount, Route, Router, WebSocketRoute\nfrom starlette.staticfiles import StaticFiles\nfrom starlette.testclient import TestClient, WebSocketDenialResponse\nfrom starlette.types import ASGIApp, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\nfrom tests.types import TestClientFactory\n\n\nasync def error_500(request: Request, exc: HTTPException) -> JSONResponse:\n    return JSONResponse({\"detail\": \"Server Error\"}, status_code=500)\n\n\nasync def method_not_allowed(request: Request, exc: HTTPException) -> JSONResponse:\n    return JSONResponse({\"detail\": \"Custom message\"}, status_code=405)\n\n\nasync def http_exception(request: Request, exc: HTTPException) -> JSONResponse:\n    return JSONResponse({\"detail\": exc.detail}, status_code=exc.status_code)\n\n\ndef func_homepage(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Hello, world!\")\n\n\nasync def async_homepage(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Hello, world!\")\n\n\nclass Homepage(HTTPEndpoint):\n    def get(self, request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Hello, world!\")\n\n\ndef all_users_page(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Hello, everyone!\")\n\n\ndef user_page(request: Request) -> PlainTextResponse:\n    username = request.path_params[\"username\"]\n    return PlainTextResponse(f\"Hello, {username}!\")\n\n\ndef custom_subdomain(request: Request) -> PlainTextResponse:\n    return PlainTextResponse(\"Subdomain: \" + request.path_params[\"subdomain\"])\n\n\ndef runtime_error(request: Request) -> None:\n    raise RuntimeError()\n\n\nasync def websocket_endpoint(session: WebSocket) -> None:\n    await session.accept()\n    await session.send_text(\"Hello, world!\")\n    await session.close()\n\n\nasync def websocket_raise_websocket_exception(websocket: WebSocket) -> None:\n    await websocket.accept()\n    raise WebSocketException(code=status.WS_1003_UNSUPPORTED_DATA)\n\n\nasync def websocket_raise_http_exception(websocket: WebSocket) -> None:\n    raise HTTPException(status_code=401, detail=\"Unauthorized\")\n\n\nclass CustomWSException(Exception):\n    pass\n\n\nasync def websocket_raise_custom(websocket: WebSocket) -> None:\n    await websocket.accept()\n    raise CustomWSException()\n\n\nasync def websocket_state(websocket: WebSocket[CustomState]) -> None:\n    await websocket.accept()\n    await websocket.send_json({\"count\": websocket.state[\"count\"]})\n    await websocket.close()\n\n\ndef custom_ws_exception_handler(websocket: WebSocket, exc: CustomWSException) -> None:\n    anyio.from_thread.run(websocket.close, status.WS_1013_TRY_AGAIN_LATER)\n\n\nclass CustomState(TypedDict):\n    count: int\n\n\n@asynccontextmanager\nasync def lifespan(app: Starlette) -> AsyncGenerator[CustomState]:\n    yield {\"count\": 1}\n\n\nasync def state_count(request: Request[CustomState]) -> JSONResponse:\n    return JSONResponse({\"count\": request.state[\"count\"]}, status_code=200)\n\n\nusers = Router(\n    routes=[\n        Route(\"/\", endpoint=all_users_page),\n        Route(\"/{username}\", endpoint=user_page),\n    ]\n)\n\nsubdomain = Router(\n    routes=[\n        Route(\"/\", custom_subdomain),\n    ]\n)\n\nexception_handlers = {\n    500: error_500,\n    405: method_not_allowed,\n    HTTPException: http_exception,\n    CustomWSException: custom_ws_exception_handler,\n}\n\nmiddleware = [Middleware(TrustedHostMiddleware, allowed_hosts=[\"testserver\", \"*.example.org\"])]\n\napp = Starlette(\n    routes=[\n        Route(\"/func\", endpoint=func_homepage),\n        Route(\"/async\", endpoint=async_homepage),\n        Route(\"/class\", endpoint=Homepage),\n        Route(\"/state\", endpoint=state_count),\n        Route(\"/500\", endpoint=runtime_error),\n        WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n        WebSocketRoute(\"/ws-raise-websocket\", endpoint=websocket_raise_websocket_exception),\n        WebSocketRoute(\"/ws-raise-http\", endpoint=websocket_raise_http_exception),\n        WebSocketRoute(\"/ws-raise-custom\", endpoint=websocket_raise_custom),\n        WebSocketRoute(\"/ws-state\", endpoint=websocket_state),\n        Mount(\"/users\", app=users),\n        Host(\"{subdomain}.example.org\", app=subdomain),\n    ],\n    exception_handlers=exception_handlers,  # type: ignore\n    middleware=middleware,\n    lifespan=lifespan,\n)\n\n\n@pytest.fixture\ndef client(test_client_factory: TestClientFactory) -> Generator[TestClient, None, None]:\n    with test_client_factory(app) as client:\n        yield client\n\n\ndef test_url_path_for() -> None:\n    assert app.url_path_for(\"func_homepage\") == \"/func\"\n\n\ndef test_func_route(client: TestClient) -> None:\n    response = client.get(\"/func\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n    response = client.head(\"/func\")\n    assert response.status_code == 200\n    assert response.text == \"\"\n\n\ndef test_async_route(client: TestClient) -> None:\n    response = client.get(\"/async\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\ndef test_class_route(client: TestClient) -> None:\n    response = client.get(\"/class\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\ndef test_mounted_route(client: TestClient) -> None:\n    response = client.get(\"/users/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, everyone!\"\n\n\ndef test_mounted_route_path_params(client: TestClient) -> None:\n    response = client.get(\"/users/tomchristie\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, tomchristie!\"\n\n\ndef test_subdomain_route(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app, base_url=\"https://foo.example.org/\")\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Subdomain: foo\"\n\n\ndef test_websocket_route(client: TestClient) -> None:\n    with client.websocket_connect(\"/ws\") as session:\n        text = session.receive_text()\n        assert text == \"Hello, world!\"\n\n\ndef test_400(client: TestClient) -> None:\n    response = client.get(\"/404\")\n    assert response.status_code == 404\n    assert response.json() == {\"detail\": \"Not Found\"}\n\n\ndef test_405(client: TestClient) -> None:\n    response = client.post(\"/func\")\n    assert response.status_code == 405\n    assert response.json() == {\"detail\": \"Custom message\"}\n\n    response = client.post(\"/class\")\n    assert response.status_code == 405\n    assert response.json() == {\"detail\": \"Custom message\"}\n\n\ndef test_500(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/500\")\n    assert response.status_code == 500\n    assert response.json() == {\"detail\": \"Server Error\"}\n\n\ndef test_request_state(client: TestClient) -> None:\n    response = client.get(\"/state\")\n    assert response.status_code == 200\n    assert response.json() == {\"count\": 1}\n\n\ndef test_websocket_raise_websocket_exception(client: TestClient) -> None:\n    with client.websocket_connect(\"/ws-raise-websocket\") as session:\n        response = session.receive()\n        assert response == {\n            \"type\": \"websocket.close\",\n            \"code\": status.WS_1003_UNSUPPORTED_DATA,\n            \"reason\": \"\",\n        }\n\n\ndef test_websocket_state(client: TestClient) -> None:\n    with client.websocket_connect(\"/ws-state\") as session:\n        response = session.receive_json()\n        assert response == {\"count\": 1}\n\n\ndef test_websocket_raise_http_exception(client: TestClient) -> None:\n    with pytest.raises(WebSocketDenialResponse) as exc:\n        with client.websocket_connect(\"/ws-raise-http\"):\n            pass  # pragma: no cover\n    assert exc.value.status_code == 401\n    assert exc.value.content == b'{\"detail\":\"Unauthorized\"}'\n\n\ndef test_websocket_raise_custom_exception(client: TestClient) -> None:\n    with client.websocket_connect(\"/ws-raise-custom\") as session:\n        response = session.receive()\n        assert response == {\n            \"type\": \"websocket.close\",\n            \"code\": status.WS_1013_TRY_AGAIN_LATER,\n            \"reason\": \"\",\n        }\n\n\ndef test_middleware(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app, base_url=\"http://incorrecthost\")\n    response = client.get(\"/func\")\n    assert response.status_code == 400\n    assert response.text == \"Invalid host header\"\n\n\ndef test_routes() -> None:\n    assert app.routes == [\n        Route(\"/func\", endpoint=func_homepage, methods=[\"GET\"]),\n        Route(\"/async\", endpoint=async_homepage, methods=[\"GET\"]),\n        Route(\"/class\", endpoint=Homepage),\n        Route(\"/state\", endpoint=state_count, methods=[\"GET\"]),\n        Route(\"/500\", endpoint=runtime_error, methods=[\"GET\"]),\n        WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n        WebSocketRoute(\"/ws-raise-websocket\", endpoint=websocket_raise_websocket_exception),\n        WebSocketRoute(\"/ws-raise-http\", endpoint=websocket_raise_http_exception),\n        WebSocketRoute(\"/ws-raise-custom\", endpoint=websocket_raise_custom),\n        WebSocketRoute(\"/ws-state\", endpoint=websocket_state),\n        Mount(\n            \"/users\",\n            app=Router(\n                routes=[\n                    Route(\"/\", endpoint=all_users_page),\n                    Route(\"/{username}\", endpoint=user_page),\n                ]\n            ),\n        ),\n        Host(\n            \"{subdomain}.example.org\",\n            app=Router(routes=[Route(\"/\", endpoint=custom_subdomain)]),\n        ),\n    ]\n\n\ndef test_app_mount(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = Starlette(\n        routes=[\n            Mount(\"/static\", StaticFiles(directory=tmpdir)),\n        ]\n    )\n\n    client = test_client_factory(app)\n\n    response = client.get(\"/static/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"<file content>\"\n\n    response = client.post(\"/static/example.txt\")\n    assert response.status_code == 405\n    assert response.text == \"Method Not Allowed\"\n\n\ndef test_app_debug(test_client_factory: TestClientFactory) -> None:\n    async def homepage(request: Request) -> None:\n        raise RuntimeError()\n\n    app = Starlette(\n        routes=[\n            Route(\"/\", homepage),\n        ],\n    )\n    app.debug = True\n\n    client = test_client_factory(app, raise_server_exceptions=False)\n    response = client.get(\"/\")\n    assert response.status_code == 500\n    assert \"RuntimeError\" in response.text\n    assert app.debug\n\n\ndef test_app_add_route(test_client_factory: TestClientFactory) -> None:\n    async def homepage(request: Request) -> PlainTextResponse:\n        return PlainTextResponse(\"Hello, World!\")\n\n    app = Starlette(\n        routes=[\n            Route(\"/\", endpoint=homepage),\n        ]\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, World!\"\n\n\ndef test_app_add_websocket_route(test_client_factory: TestClientFactory) -> None:\n    async def websocket_endpoint(session: WebSocket) -> None:\n        await session.accept()\n        await session.send_text(\"Hello, world!\")\n        await session.close()\n\n    app = Starlette(\n        routes=[\n            WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n        ]\n    )\n    client = test_client_factory(app)\n\n    with client.websocket_connect(\"/ws\") as session:\n        text = session.receive_text()\n        assert text == \"Hello, world!\"\n\n\ndef test_app_async_cm_lifespan(test_client_factory: TestClientFactory) -> None:\n    startup_complete = False\n    cleanup_complete = False\n\n    @asynccontextmanager\n    async def lifespan(app: ASGIApp) -> AsyncGenerator[None, None]:\n        nonlocal startup_complete, cleanup_complete\n        startup_complete = True\n        yield\n        cleanup_complete = True\n\n    app = Starlette(lifespan=lifespan)\n\n    assert not startup_complete\n    assert not cleanup_complete\n    with test_client_factory(app):\n        assert startup_complete\n        assert not cleanup_complete\n    assert startup_complete\n    assert cleanup_complete\n\n\ndeprecated_lifespan = pytest.mark.filterwarnings(\n    r\"ignore\"\n    r\":(async )?generator function lifespans are deprecated, use an \"\n    r\"@contextlib\\.asynccontextmanager function instead\"\n    r\":DeprecationWarning\"\n    r\":starlette.routing\"\n)\n\n\n@deprecated_lifespan\ndef test_app_async_gen_lifespan(test_client_factory: TestClientFactory) -> None:\n    startup_complete = False\n    cleanup_complete = False\n\n    async def lifespan(app: ASGIApp) -> AsyncGenerator[None, None]:\n        nonlocal startup_complete, cleanup_complete\n        startup_complete = True\n        yield\n        cleanup_complete = True\n\n    app = Starlette(lifespan=lifespan)  # type: ignore\n\n    assert not startup_complete\n    assert not cleanup_complete\n    with test_client_factory(app):\n        assert startup_complete\n        assert not cleanup_complete\n    assert startup_complete\n    assert cleanup_complete\n\n\n@deprecated_lifespan\ndef test_app_sync_gen_lifespan(test_client_factory: TestClientFactory) -> None:\n    startup_complete = False\n    cleanup_complete = False\n\n    def lifespan(app: ASGIApp) -> Generator[None, None, None]:\n        nonlocal startup_complete, cleanup_complete\n        startup_complete = True\n        yield\n        cleanup_complete = True\n\n    app = Starlette(lifespan=lifespan)  # type: ignore\n\n    assert not startup_complete\n    assert not cleanup_complete\n    with test_client_factory(app):\n        assert startup_complete\n        assert not cleanup_complete\n    assert startup_complete\n    assert cleanup_complete\n\n\ndef test_middleware_stack_init(test_client_factory: TestClientFactory) -> None:\n    class NoOpMiddleware:\n        def __init__(self, app: ASGIApp):\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            await self.app(scope, receive, send)\n\n    class SimpleInitializableMiddleware:\n        counter = 0\n\n        def __init__(self, app: ASGIApp):\n            self.app = app\n            SimpleInitializableMiddleware.counter += 1\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            await self.app(scope, receive, send)\n\n    def get_app() -> ASGIApp:\n        app = Starlette()\n        app.add_middleware(SimpleInitializableMiddleware)\n        app.add_middleware(NoOpMiddleware)\n        return app\n\n    app = get_app()\n\n    with test_client_factory(app):\n        pass\n\n    assert SimpleInitializableMiddleware.counter == 1\n\n    test_client_factory(app).get(\"/foo\")\n\n    assert SimpleInitializableMiddleware.counter == 1\n\n    app = get_app()\n\n    test_client_factory(app).get(\"/foo\")\n\n    assert SimpleInitializableMiddleware.counter == 2\n\n\ndef test_middleware_args(test_client_factory: TestClientFactory) -> None:\n    calls: list[str] = []\n\n    class MiddlewareWithArgs:\n        def __init__(self, app: ASGIApp, arg: str) -> None:\n            self.app = app\n            self.arg = arg\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            calls.append(self.arg)\n            await self.app(scope, receive, send)\n\n    app = Starlette()\n    app.add_middleware(MiddlewareWithArgs, \"foo\")\n    app.add_middleware(MiddlewareWithArgs, \"bar\")\n\n    with test_client_factory(app):\n        pass\n\n    assert calls == [\"bar\", \"foo\"]\n\n\ndef test_middleware_factory(test_client_factory: TestClientFactory) -> None:\n    calls: list[str] = []\n\n    def _middleware_factory(app: ASGIApp, arg: str) -> ASGIApp:\n        async def _app(scope: Scope, receive: Receive, send: Send) -> None:\n            calls.append(arg)\n            await app(scope, receive, send)\n\n        return _app\n\n    def get_middleware_factory() -> Callable[[ASGIApp, str], ASGIApp]:\n        return _middleware_factory\n\n    app = Starlette()\n    app.add_middleware(_middleware_factory, arg=\"foo\")\n    app.add_middleware(get_middleware_factory(), \"bar\")\n\n    with test_client_factory(app):\n        pass\n\n    assert calls == [\"bar\", \"foo\"]\n\n\ndef test_lifespan_app_subclass() -> None:\n    # This test exists to make sure that subclasses of Starlette\n    # (like FastAPI) are compatible with the types hints for Lifespan\n\n    class App(Starlette):\n        pass\n\n    @asynccontextmanager\n    async def lifespan(app: App) -> AsyncIterator[None]:  # pragma: no cover\n        yield\n\n    App(lifespan=lifespan)\n"
  },
  {
    "path": "tests/test_authentication.py",
    "content": "from __future__ import annotations\n\nimport base64\nimport binascii\nfrom collections.abc import Awaitable, Callable\nfrom typing import Any\nfrom urllib.parse import urlencode\n\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser, requires\nfrom starlette.endpoints import HTTPEndpoint\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.authentication import AuthenticationMiddleware\nfrom starlette.requests import HTTPConnection, Request\nfrom starlette.responses import JSONResponse, Response\nfrom starlette.routing import Route, WebSocketRoute\nfrom starlette.websockets import WebSocket, WebSocketDisconnect\nfrom tests.types import TestClientFactory\n\nAsyncEndpoint = Callable[..., Awaitable[Response]]\nSyncEndpoint = Callable[..., Response]\n\n\nclass BasicAuth(AuthenticationBackend):\n    async def authenticate(\n        self,\n        request: HTTPConnection,\n    ) -> tuple[AuthCredentials, SimpleUser] | None:\n        if \"Authorization\" not in request.headers:\n            return None\n\n        auth = request.headers[\"Authorization\"]\n        try:\n            scheme, credentials = auth.split()\n            decoded = base64.b64decode(credentials).decode(\"ascii\")\n        except (ValueError, UnicodeDecodeError, binascii.Error):\n            raise AuthenticationError(\"Invalid basic auth credentials\")\n\n        username, _, password = decoded.partition(\":\")\n        return AuthCredentials([\"authenticated\"]), SimpleUser(username)\n\n\ndef homepage(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\n@requires(\"authenticated\")\nasync def dashboard(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\n@requires(\"authenticated\", redirect=\"homepage\")\nasync def admin(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\n@requires(\"authenticated\")\ndef dashboard_sync(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\nclass Dashboard(HTTPEndpoint):\n    @requires(\"authenticated\")\n    def get(self, request: Request) -> JSONResponse:\n        return JSONResponse(\n            {\n                \"authenticated\": request.user.is_authenticated,\n                \"user\": request.user.display_name,\n            }\n        )\n\n\n@requires(\"authenticated\", redirect=\"homepage\")\ndef admin_sync(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\n@requires(\"authenticated\")\nasync def websocket_endpoint(websocket: WebSocket) -> None:\n    await websocket.accept()\n    await websocket.send_json(\n        {\n            \"authenticated\": websocket.user.is_authenticated,\n            \"user\": websocket.user.display_name,\n        }\n    )\n\n\ndef async_inject_decorator(\n    **kwargs: Any,\n) -> Callable[[AsyncEndpoint], Callable[..., Awaitable[Response]]]:\n    def wrapper(endpoint: AsyncEndpoint) -> Callable[..., Awaitable[Response]]:\n        async def app(request: Request) -> Response:\n            return await endpoint(request=request, **kwargs)\n\n        return app\n\n    return wrapper\n\n\n@async_inject_decorator(additional=\"payload\")\n@requires(\"authenticated\")\nasync def decorated_async(request: Request, additional: str) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n            \"additional\": additional,\n        }\n    )\n\n\ndef sync_inject_decorator(\n    **kwargs: Any,\n) -> Callable[[SyncEndpoint], Callable[..., Response]]:\n    def wrapper(endpoint: SyncEndpoint) -> Callable[..., Response]:\n        def app(request: Request) -> Response:\n            return endpoint(request=request, **kwargs)\n\n        return app\n\n    return wrapper\n\n\n@sync_inject_decorator(additional=\"payload\")\n@requires(\"authenticated\")\ndef decorated_sync(request: Request, additional: str) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n            \"additional\": additional,\n        }\n    )\n\n\ndef ws_inject_decorator(**kwargs: Any) -> Callable[..., AsyncEndpoint]:\n    def wrapper(endpoint: AsyncEndpoint) -> AsyncEndpoint:\n        def app(websocket: WebSocket) -> Awaitable[Response]:\n            return endpoint(websocket=websocket, **kwargs)\n\n        return app\n\n    return wrapper\n\n\n@ws_inject_decorator(additional=\"payload\")\n@requires(\"authenticated\")\nasync def websocket_endpoint_decorated(websocket: WebSocket, additional: str) -> None:\n    await websocket.accept()\n    await websocket.send_json(\n        {\n            \"authenticated\": websocket.user.is_authenticated,\n            \"user\": websocket.user.display_name,\n            \"additional\": additional,\n        }\n    )\n\n\napp = Starlette(\n    middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuth())],\n    routes=[\n        Route(\"/\", endpoint=homepage),\n        Route(\"/dashboard\", endpoint=dashboard),\n        Route(\"/admin\", endpoint=admin),\n        Route(\"/dashboard/sync\", endpoint=dashboard_sync),\n        Route(\"/dashboard/class\", endpoint=Dashboard),\n        Route(\"/admin/sync\", endpoint=admin_sync),\n        Route(\"/dashboard/decorated\", endpoint=decorated_async),\n        Route(\"/dashboard/decorated/sync\", endpoint=decorated_sync),\n        WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n        WebSocketRoute(\"/ws/decorated\", endpoint=websocket_endpoint_decorated),\n    ],\n)\n\n\ndef test_invalid_decorator_usage() -> None:\n    with pytest.raises(Exception):\n\n        @requires(\"authenticated\")\n        def foo() -> None:  # pragma: no cover\n            pass\n\n\ndef test_user_interface(test_client_factory: TestClientFactory) -> None:\n    with test_client_factory(app) as client:\n        response = client.get(\"/\")\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": False, \"user\": \"\"}\n\n        response = client.get(\"/\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n\ndef test_authentication_required(test_client_factory: TestClientFactory) -> None:\n    with test_client_factory(app) as client:\n        response = client.get(\"/dashboard\")\n        assert response.status_code == 403\n\n        response = client.get(\"/dashboard\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        response = client.get(\"/dashboard/sync\")\n        assert response.status_code == 403\n\n        response = client.get(\"/dashboard/sync\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        response = client.get(\"/dashboard/class\")\n        assert response.status_code == 403\n\n        response = client.get(\"/dashboard/class\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        response = client.get(\"/dashboard/decorated\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\n            \"authenticated\": True,\n            \"user\": \"tomchristie\",\n            \"additional\": \"payload\",\n        }\n\n        response = client.get(\"/dashboard/decorated\")\n        assert response.status_code == 403\n\n        response = client.get(\"/dashboard/decorated/sync\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\n            \"authenticated\": True,\n            \"user\": \"tomchristie\",\n            \"additional\": \"payload\",\n        }\n\n        response = client.get(\"/dashboard/decorated/sync\")\n        assert response.status_code == 403\n\n        response = client.get(\"/dashboard\", headers={\"Authorization\": \"basic foobar\"})\n        assert response.status_code == 400\n        assert response.text == \"Invalid basic auth credentials\"\n\n\ndef test_websocket_authentication_required(\n    test_client_factory: TestClientFactory,\n) -> None:\n    with test_client_factory(app) as client:\n        with pytest.raises(WebSocketDisconnect):\n            with client.websocket_connect(\"/ws\"):\n                pass  # pragma: no cover\n\n        with pytest.raises(WebSocketDisconnect):\n            with client.websocket_connect(\"/ws\", headers={\"Authorization\": \"basic foobar\"}):\n                pass  # pragma: no cover\n\n        with client.websocket_connect(\"/ws\", auth=(\"tomchristie\", \"example\")) as websocket:\n            data = websocket.receive_json()\n            assert data == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        with pytest.raises(WebSocketDisconnect):\n            with client.websocket_connect(\"/ws/decorated\"):\n                pass  # pragma: no cover\n\n        with pytest.raises(WebSocketDisconnect):\n            with client.websocket_connect(\"/ws/decorated\", headers={\"Authorization\": \"basic foobar\"}):\n                pass  # pragma: no cover\n\n        with client.websocket_connect(\"/ws/decorated\", auth=(\"tomchristie\", \"example\")) as websocket:\n            data = websocket.receive_json()\n            assert data == {\n                \"authenticated\": True,\n                \"user\": \"tomchristie\",\n                \"additional\": \"payload\",\n            }\n\n\ndef test_authentication_redirect(test_client_factory: TestClientFactory) -> None:\n    with test_client_factory(app) as client:\n        response = client.get(\"/admin\")\n        assert response.status_code == 200\n        url = \"{}?{}\".format(\"http://testserver/\", urlencode({\"next\": \"http://testserver/admin\"}))\n        assert response.url == url\n\n        response = client.get(\"/admin\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        response = client.get(\"/admin/sync\")\n        assert response.status_code == 200\n        url = \"{}?{}\".format(\"http://testserver/\", urlencode({\"next\": \"http://testserver/admin/sync\"}))\n        assert response.url == url\n\n        response = client.get(\"/admin/sync\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n\ndef on_auth_error(request: HTTPConnection, exc: AuthenticationError) -> JSONResponse:\n    return JSONResponse({\"error\": str(exc)}, status_code=401)\n\n\n@requires(\"authenticated\")\ndef control_panel(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"authenticated\": request.user.is_authenticated,\n            \"user\": request.user.display_name,\n        }\n    )\n\n\nother_app = Starlette(\n    routes=[Route(\"/control-panel\", control_panel)],\n    middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuth(), on_error=on_auth_error)],\n)\n\n\ndef test_custom_on_error(test_client_factory: TestClientFactory) -> None:\n    with test_client_factory(other_app) as client:\n        response = client.get(\"/control-panel\", auth=(\"tomchristie\", \"example\"))\n        assert response.status_code == 200\n        assert response.json() == {\"authenticated\": True, \"user\": \"tomchristie\"}\n\n        response = client.get(\"/control-panel\", headers={\"Authorization\": \"basic foobar\"})\n        assert response.status_code == 401\n        assert response.json() == {\"error\": \"Invalid basic auth credentials\"}\n"
  },
  {
    "path": "tests/test_background.py",
    "content": "import pytest\n\nfrom starlette.background import BackgroundTask, BackgroundTasks\nfrom starlette.responses import Response\nfrom starlette.types import Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\ndef test_async_task(test_client_factory: TestClientFactory) -> None:\n    TASK_COMPLETE = False\n\n    async def async_task() -> None:\n        nonlocal TASK_COMPLETE\n        TASK_COMPLETE = True\n\n    task = BackgroundTask(async_task)\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"task initiated\", media_type=\"text/plain\", background=task)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"task initiated\"\n    assert TASK_COMPLETE\n\n\ndef test_sync_task(test_client_factory: TestClientFactory) -> None:\n    TASK_COMPLETE = False\n\n    def sync_task() -> None:\n        nonlocal TASK_COMPLETE\n        TASK_COMPLETE = True\n\n    task = BackgroundTask(sync_task)\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"task initiated\", media_type=\"text/plain\", background=task)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"task initiated\"\n    assert TASK_COMPLETE\n\n\ndef test_multiple_tasks(test_client_factory: TestClientFactory) -> None:\n    TASK_COUNTER = 0\n\n    def increment(amount: int) -> None:\n        nonlocal TASK_COUNTER\n        TASK_COUNTER += amount\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        tasks = BackgroundTasks()\n        tasks.add_task(increment, amount=1)\n        tasks.add_task(increment, amount=2)\n        tasks.add_task(increment, amount=3)\n        response = Response(\"tasks initiated\", media_type=\"text/plain\", background=tasks)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"tasks initiated\"\n    assert TASK_COUNTER == 1 + 2 + 3\n\n\ndef test_multi_tasks_failure_avoids_next_execution(\n    test_client_factory: TestClientFactory,\n) -> None:\n    TASK_COUNTER = 0\n\n    def increment() -> None:\n        nonlocal TASK_COUNTER\n        TASK_COUNTER += 1\n        if TASK_COUNTER == 1:  # pragma: no branch\n            raise Exception(\"task failed\")\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        tasks = BackgroundTasks()\n        tasks.add_task(increment)\n        tasks.add_task(increment)\n        response = Response(\"tasks initiated\", media_type=\"text/plain\", background=tasks)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    with pytest.raises(Exception):\n        client.get(\"/\")\n    assert TASK_COUNTER == 1\n"
  },
  {
    "path": "tests/test_concurrency.py",
    "content": "from collections.abc import Iterator\nfrom contextvars import ContextVar\n\nimport anyio\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.concurrency import iterate_in_threadpool, run_until_first_complete\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import Route\nfrom tests.types import TestClientFactory\n\n\n@pytest.mark.anyio\nasync def test_run_until_first_complete() -> None:\n    task1_finished = anyio.Event()\n    task2_finished = anyio.Event()\n\n    async def task1() -> None:\n        task1_finished.set()\n\n    async def task2() -> None:\n        await task1_finished.wait()\n        await anyio.sleep(0)  # pragma: no cover\n        task2_finished.set()  # pragma: no cover\n\n    await run_until_first_complete((task1, {}), (task2, {}))\n    assert task1_finished.is_set()\n    assert not task2_finished.is_set()\n\n\ndef test_accessing_context_from_threaded_sync_endpoint(\n    test_client_factory: TestClientFactory,\n) -> None:\n    ctxvar: ContextVar[bytes] = ContextVar(\"ctxvar\")\n    ctxvar.set(b\"data\")\n\n    def endpoint(request: Request) -> Response:\n        return Response(ctxvar.get())\n\n    app = Starlette(routes=[Route(\"/\", endpoint)])\n    client = test_client_factory(app)\n\n    resp = client.get(\"/\")\n    assert resp.content == b\"data\"\n\n\n@pytest.mark.anyio\nasync def test_iterate_in_threadpool() -> None:\n    class CustomIterable:\n        def __iter__(self) -> Iterator[int]:\n            yield from range(3)\n\n    assert [v async for v in iterate_in_threadpool(CustomIterable())] == [0, 1, 2]\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "import os\nfrom pathlib import Path\nfrom typing import Any\n\nimport pytest\nfrom typing_extensions import assert_type\n\nfrom starlette.config import Config, Environ, EnvironError\nfrom starlette.datastructures import URL, Secret\n\n\ndef test_config_types() -> None:\n    \"\"\"\n    We use `assert_type` to test the types returned by Config via mypy.\n    \"\"\"\n    config = Config(environ={\"STR\": \"some_str_value\", \"STR_CAST\": \"some_str_value\", \"BOOL\": \"true\"})\n\n    assert_type(config(\"STR\"), str)\n    assert_type(config(\"STR_DEFAULT\", default=\"\"), str)\n    assert_type(config(\"STR_CAST\", cast=str), str)\n    assert_type(config(\"STR_NONE\", default=None), str | None)\n    assert_type(config(\"STR_CAST_NONE\", cast=str, default=None), str | None)\n    assert_type(config(\"STR_CAST_STR\", cast=str, default=\"\"), str)\n\n    assert_type(config(\"BOOL\", cast=bool), bool)\n    assert_type(config(\"BOOL_DEFAULT\", cast=bool, default=False), bool)\n    assert_type(config(\"BOOL_NONE\", cast=bool, default=None), bool | None)\n\n    def cast_to_int(v: Any) -> int:\n        return int(v)\n\n    # our type annotations allow these `cast` and `default` configurations, but\n    # the code will error at runtime.\n    with pytest.raises(ValueError):\n        config(\"INT_CAST_DEFAULT_STR\", cast=cast_to_int, default=\"true\")\n    with pytest.raises(ValueError):\n        config(\"INT_DEFAULT_STR\", cast=int, default=\"true\")\n\n\ndef test_config(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:\n    path = os.path.join(tmpdir, \".env\")\n    with open(path, \"w\") as file:\n        file.write(\"# Do not commit to source control\\n\")\n        file.write(\"DATABASE_URL=postgres://user:pass@localhost/dbname\\n\")\n        file.write(\"REQUEST_HOSTNAME=example.com\\n\")\n        file.write(\"SECRET_KEY=12345\\n\")\n        file.write(\"BOOL_AS_INT=0\\n\")\n        file.write(\"\\n\")\n        file.write(\"\\n\")\n\n    config = Config(path, environ={\"DEBUG\": \"true\"})\n\n    def cast_to_int(v: Any) -> int:\n        return int(v)\n\n    DEBUG = config(\"DEBUG\", cast=bool)\n    DATABASE_URL = config(\"DATABASE_URL\", cast=URL)\n    REQUEST_TIMEOUT = config(\"REQUEST_TIMEOUT\", cast=int, default=10)\n    REQUEST_HOSTNAME = config(\"REQUEST_HOSTNAME\")\n    MAIL_HOSTNAME = config(\"MAIL_HOSTNAME\", default=None)\n    SECRET_KEY = config(\"SECRET_KEY\", cast=Secret)\n    UNSET_SECRET = config(\"UNSET_SECRET\", cast=Secret, default=None)\n    EMPTY_SECRET = config(\"EMPTY_SECRET\", cast=Secret, default=\"\")\n    assert config(\"BOOL_AS_INT\", cast=bool) is False\n    assert config(\"BOOL_AS_INT\", cast=cast_to_int) == 0\n    assert config(\"DEFAULTED_BOOL\", cast=cast_to_int, default=True) == 1\n\n    assert DEBUG is True\n    assert DATABASE_URL.path == \"/dbname\"\n    assert DATABASE_URL.password == \"pass\"\n    assert DATABASE_URL.username == \"user\"\n    assert REQUEST_TIMEOUT == 10\n    assert REQUEST_HOSTNAME == \"example.com\"\n    assert MAIL_HOSTNAME is None\n    assert repr(SECRET_KEY) == \"Secret('**********')\"\n    assert str(SECRET_KEY) == \"12345\"\n    assert bool(SECRET_KEY)\n    assert not bool(EMPTY_SECRET)\n    assert not bool(UNSET_SECRET)\n\n    with pytest.raises(KeyError):\n        config.get(\"MISSING\")\n\n    with pytest.raises(ValueError):\n        config.get(\"DEBUG\", cast=int)\n\n    with pytest.raises(ValueError):\n        config.get(\"REQUEST_HOSTNAME\", cast=bool)\n\n    config = Config(Path(path))\n    REQUEST_HOSTNAME = config(\"REQUEST_HOSTNAME\")\n    assert REQUEST_HOSTNAME == \"example.com\"\n\n    config = Config()\n    monkeypatch.setenv(\"STARLETTE_EXAMPLE_TEST\", \"123\")\n    monkeypatch.setenv(\"BOOL_AS_INT\", \"1\")\n    assert config.get(\"STARLETTE_EXAMPLE_TEST\", cast=int) == 123\n    assert config.get(\"BOOL_AS_INT\", cast=bool) is True\n\n    monkeypatch.setenv(\"BOOL_AS_INT\", \"2\")\n    with pytest.raises(ValueError):\n        config.get(\"BOOL_AS_INT\", cast=bool)\n\n\ndef test_missing_env_file_raises(tmpdir: Path) -> None:\n    path = os.path.join(tmpdir, \".env\")\n\n    with pytest.warns(UserWarning, match=f\"Config file '{path}' not found.\"):\n        Config(path)\n\n\ndef test_environ() -> None:\n    environ = Environ()\n\n    # We can mutate the environ at this point.\n    environ[\"TESTING\"] = \"True\"\n    environ[\"GONE\"] = \"123\"\n    del environ[\"GONE\"]\n\n    # We can read the environ.\n    assert environ[\"TESTING\"] == \"True\"\n    assert \"GONE\" not in environ\n\n    # We cannot mutate these keys now that we've read them.\n    with pytest.raises(EnvironError):\n        environ[\"TESTING\"] = \"False\"\n\n    with pytest.raises(EnvironError):\n        del environ[\"GONE\"]\n\n    # Test coverage of abstract methods for MutableMapping.\n    environ = Environ()\n    assert list(iter(environ)) == list(iter(os.environ))\n    assert len(environ) == len(os.environ)\n\n\ndef test_config_with_env_prefix(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:\n    config = Config(environ={\"APP_DEBUG\": \"value\", \"ENVIRONMENT\": \"dev\"}, env_prefix=\"APP_\")\n    assert config.get(\"DEBUG\") == \"value\"\n\n    with pytest.raises(KeyError):\n        config.get(\"ENVIRONMENT\")\n\n\ndef test_config_with_encoding(tmpdir: Path) -> None:\n    path = tmpdir / \".env\"\n    path.write_text(\"MESSAGE=Hello 世界\\n\", encoding=\"utf-8\")\n    config = Config(path, encoding=\"utf-8\")\n    assert config.get(\"MESSAGE\") == \"Hello 世界\"\n"
  },
  {
    "path": "tests/test_convertors.py",
    "content": "from collections.abc import Iterator\nfrom datetime import datetime\nfrom uuid import UUID\n\nimport pytest\n\nfrom starlette import convertors\nfrom starlette.convertors import Convertor, register_url_convertor\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Route, Router\nfrom tests.types import TestClientFactory\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef refresh_convertor_types() -> Iterator[None]:\n    convert_types = convertors.CONVERTOR_TYPES.copy()\n    yield\n    convertors.CONVERTOR_TYPES = convert_types\n\n\nclass DateTimeConvertor(Convertor[datetime]):\n    regex = \"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?\"\n\n    def convert(self, value: str) -> datetime:\n        return datetime.strptime(value, \"%Y-%m-%dT%H:%M:%S\")\n\n    def to_string(self, value: datetime) -> str:\n        return value.strftime(\"%Y-%m-%dT%H:%M:%S\")\n\n\n@pytest.fixture(scope=\"function\")\ndef app() -> Router:\n    register_url_convertor(\"datetime\", DateTimeConvertor())\n\n    def datetime_convertor(request: Request) -> JSONResponse:\n        param = request.path_params[\"param\"]\n        assert isinstance(param, datetime)\n        return JSONResponse({\"datetime\": param.strftime(\"%Y-%m-%dT%H:%M:%S\")})\n\n    return Router(\n        routes=[\n            Route(\n                \"/datetime/{param:datetime}\",\n                endpoint=datetime_convertor,\n                name=\"datetime-convertor\",\n            )\n        ]\n    )\n\n\ndef test_datetime_convertor(test_client_factory: TestClientFactory, app: Router) -> None:\n    client = test_client_factory(app)\n    response = client.get(\"/datetime/2020-01-01T00:00:00\")\n    assert response.json() == {\"datetime\": \"2020-01-01T00:00:00\"}\n\n    assert (\n        app.url_path_for(\"datetime-convertor\", param=datetime(1996, 1, 22, 23, 0, 0)) == \"/datetime/1996-01-22T23:00:00\"\n    )\n\n\n@pytest.mark.parametrize(\"param, status_code\", [(\"1.0\", 200), (\"1-0\", 404)])\ndef test_default_float_convertor(test_client_factory: TestClientFactory, param: str, status_code: int) -> None:\n    def float_convertor(request: Request) -> JSONResponse:\n        param = request.path_params[\"param\"]\n        assert isinstance(param, float)\n        return JSONResponse({\"float\": param})\n\n    app = Router(routes=[Route(\"/{param:float}\", endpoint=float_convertor)])\n\n    client = test_client_factory(app)\n    response = client.get(f\"/{param}\")\n    assert response.status_code == status_code\n\n\n@pytest.mark.parametrize(\n    \"param, status_code\",\n    [\n        (\"00000000-aaaa-ffff-9999-000000000000\", 200),\n        (\"00000000aaaaffff9999000000000000\", 200),\n        (\"00000000-AAAA-FFFF-9999-000000000000\", 200),\n        (\"00000000AAAAFFFF9999000000000000\", 200),\n        (\"not-a-uuid\", 404),\n    ],\n)\ndef test_default_uuid_convertor(test_client_factory: TestClientFactory, param: str, status_code: int) -> None:\n    def uuid_convertor(request: Request) -> JSONResponse:\n        param = request.path_params[\"param\"]\n        assert isinstance(param, UUID)\n        return JSONResponse(\"ok\")\n\n    app = Router(routes=[Route(\"/{param:uuid}\", endpoint=uuid_convertor)])\n\n    client = test_client_factory(app)\n    response = client.get(f\"/{param}\")\n    assert response.status_code == status_code\n"
  },
  {
    "path": "tests/test_datastructures.py",
    "content": "import io\nfrom tempfile import SpooledTemporaryFile\nfrom typing import BinaryIO\n\nimport pytest\n\nfrom starlette.datastructures import (\n    URL,\n    CommaSeparatedStrings,\n    FormData,\n    Headers,\n    MultiDict,\n    MutableHeaders,\n    QueryParams,\n    UploadFile,\n)\n\n\ndef test_url() -> None:\n    u = URL(\"https://example.org:123/path/to/somewhere?abc=123#anchor\")\n    assert u.scheme == \"https\"\n    assert u.hostname == \"example.org\"\n    assert u.port == 123\n    assert u.netloc == \"example.org:123\"\n    assert u.username is None\n    assert u.password is None\n    assert u.path == \"/path/to/somewhere\"\n    assert u.query == \"abc=123\"\n    assert u.fragment == \"anchor\"\n\n    new = u.replace(scheme=\"http\")\n    assert new == \"http://example.org:123/path/to/somewhere?abc=123#anchor\"\n    assert new.scheme == \"http\"\n\n    new = u.replace(port=None)\n    assert new == \"https://example.org/path/to/somewhere?abc=123#anchor\"\n    assert new.port is None\n\n    new = u.replace(hostname=\"example.com\")\n    assert new == \"https://example.com:123/path/to/somewhere?abc=123#anchor\"\n    assert new.hostname == \"example.com\"\n\n    ipv6_url = URL(\"https://[fe::2]:12345\")\n    new = ipv6_url.replace(port=8080)\n    assert new == \"https://[fe::2]:8080\"\n\n    new = ipv6_url.replace(username=\"username\", password=\"password\")\n    assert new == \"https://username:password@[fe::2]:12345\"\n    assert new.netloc == \"username:password@[fe::2]:12345\"\n\n    ipv6_url = URL(\"https://[fe::2]\")\n    new = ipv6_url.replace(port=123)\n    assert new == \"https://[fe::2]:123\"\n\n    url = URL(\"http://u:p@host/\")\n    assert url.replace(hostname=\"bar\") == URL(\"http://u:p@bar/\")\n\n    url = URL(\"http://u:p@host:80\")\n    assert url.replace(port=88) == URL(\"http://u:p@host:88\")\n\n    url = URL(\"http://host:80\")\n    assert url.replace(username=\"u\") == URL(\"http://u@host:80\")\n\n\ndef test_url_query_params() -> None:\n    u = URL(\"https://example.org/path/?page=3\")\n    assert u.query == \"page=3\"\n    u = u.include_query_params(page=4)\n    assert str(u) == \"https://example.org/path/?page=4\"\n    u = u.include_query_params(search=\"testing\")\n    assert str(u) == \"https://example.org/path/?page=4&search=testing\"\n    u = u.replace_query_params(order=\"name\")\n    assert str(u) == \"https://example.org/path/?order=name\"\n    u = u.remove_query_params(\"order\")\n    assert str(u) == \"https://example.org/path/\"\n    u = u.include_query_params(page=4, search=\"testing\")\n    assert str(u) == \"https://example.org/path/?page=4&search=testing\"\n    u = u.remove_query_params([\"page\", \"search\"])\n    assert str(u) == \"https://example.org/path/\"\n\n\ndef test_hidden_password() -> None:\n    u = URL(\"https://example.org/path/to/somewhere\")\n    assert repr(u) == \"URL('https://example.org/path/to/somewhere')\"\n\n    u = URL(\"https://username@example.org/path/to/somewhere\")\n    assert repr(u) == \"URL('https://username@example.org/path/to/somewhere')\"\n\n    u = URL(\"https://username:password@example.org/path/to/somewhere\")\n    assert repr(u) == \"URL('https://username:********@example.org/path/to/somewhere')\"\n\n\ndef test_csv() -> None:\n    csv = CommaSeparatedStrings('\"localhost\", \"127.0.0.1\", 0.0.0.0')\n    assert list(csv) == [\"localhost\", \"127.0.0.1\", \"0.0.0.0\"]\n    assert repr(csv) == \"CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])\"\n    assert str(csv) == \"'localhost', '127.0.0.1', '0.0.0.0'\"\n    assert csv[0] == \"localhost\"\n    assert len(csv) == 3\n\n    csv = CommaSeparatedStrings(\"'localhost', '127.0.0.1', 0.0.0.0\")\n    assert list(csv) == [\"localhost\", \"127.0.0.1\", \"0.0.0.0\"]\n    assert repr(csv) == \"CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])\"\n    assert str(csv) == \"'localhost', '127.0.0.1', '0.0.0.0'\"\n\n    csv = CommaSeparatedStrings(\"localhost, 127.0.0.1, 0.0.0.0\")\n    assert list(csv) == [\"localhost\", \"127.0.0.1\", \"0.0.0.0\"]\n    assert repr(csv) == \"CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])\"\n    assert str(csv) == \"'localhost', '127.0.0.1', '0.0.0.0'\"\n\n    csv = CommaSeparatedStrings([\"localhost\", \"127.0.0.1\", \"0.0.0.0\"])\n    assert list(csv) == [\"localhost\", \"127.0.0.1\", \"0.0.0.0\"]\n    assert repr(csv) == \"CommaSeparatedStrings(['localhost', '127.0.0.1', '0.0.0.0'])\"\n    assert str(csv) == \"'localhost', '127.0.0.1', '0.0.0.0'\"\n\n\ndef test_url_from_scope() -> None:\n    u = URL(scope={\"path\": \"/path/to/somewhere\", \"query_string\": b\"abc=123\", \"headers\": []})\n    assert u == \"/path/to/somewhere?abc=123\"\n    assert repr(u) == \"URL('/path/to/somewhere?abc=123')\"\n\n    u = URL(\n        scope={\n            \"scheme\": \"https\",\n            \"server\": (\"example.org\", 123),\n            \"path\": \"/path/to/somewhere\",\n            \"query_string\": b\"abc=123\",\n            \"headers\": [],\n        }\n    )\n    assert u == \"https://example.org:123/path/to/somewhere?abc=123\"\n    assert repr(u) == \"URL('https://example.org:123/path/to/somewhere?abc=123')\"\n\n    u = URL(\n        scope={\n            \"scheme\": \"https\",\n            \"server\": (\"example.org\", 443),\n            \"path\": \"/path/to/somewhere\",\n            \"query_string\": b\"abc=123\",\n            \"headers\": [],\n        }\n    )\n    assert u == \"https://example.org/path/to/somewhere?abc=123\"\n    assert repr(u) == \"URL('https://example.org/path/to/somewhere?abc=123')\"\n\n    u = URL(\n        scope={\n            \"scheme\": \"http\",\n            \"path\": \"/some/path\",\n            \"query_string\": b\"query=string\",\n            \"headers\": [\n                (b\"content-type\", b\"text/html\"),\n                (b\"host\", b\"example.com:8000\"),\n                (b\"accept\", b\"text/html\"),\n            ],\n        }\n    )\n    assert u == \"http://example.com:8000/some/path?query=string\"\n    assert repr(u) == \"URL('http://example.com:8000/some/path?query=string')\"\n\n\ndef test_headers() -> None:\n    h = Headers(raw=[(b\"a\", b\"123\"), (b\"a\", b\"456\"), (b\"b\", b\"789\")])\n    assert \"a\" in h\n    assert \"A\" in h\n    assert \"b\" in h\n    assert \"B\" in h\n    assert \"c\" not in h\n    assert h[\"a\"] == \"123\"\n    assert h.get(\"a\") == \"123\"\n    assert h.get(\"nope\", default=None) is None\n    assert h.getlist(\"a\") == [\"123\", \"456\"]\n    assert h.keys() == [\"a\", \"a\", \"b\"]\n    assert h.values() == [\"123\", \"456\", \"789\"]\n    assert h.items() == [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")]\n    assert list(h) == [\"a\", \"a\", \"b\"]\n    assert dict(h) == {\"a\": \"123\", \"b\": \"789\"}\n    assert repr(h) == \"Headers(raw=[(b'a', b'123'), (b'a', b'456'), (b'b', b'789')])\"\n    assert h == Headers(raw=[(b\"a\", b\"123\"), (b\"b\", b\"789\"), (b\"a\", b\"456\")])\n    assert h != [(b\"a\", b\"123\"), (b\"A\", b\"456\"), (b\"b\", b\"789\")]\n\n    h = Headers({\"a\": \"123\", \"b\": \"789\"})\n    assert h[\"A\"] == \"123\"\n    assert h[\"B\"] == \"789\"\n    assert h.raw == [(b\"a\", b\"123\"), (b\"b\", b\"789\")]\n    assert repr(h) == \"Headers({'a': '123', 'b': '789'})\"\n\n\ndef test_mutable_headers() -> None:\n    h = MutableHeaders()\n    assert dict(h) == {}\n    h[\"a\"] = \"1\"\n    assert dict(h) == {\"a\": \"1\"}\n    h[\"a\"] = \"2\"\n    assert dict(h) == {\"a\": \"2\"}\n    h.setdefault(\"a\", \"3\")\n    assert dict(h) == {\"a\": \"2\"}\n    h.setdefault(\"b\", \"4\")\n    assert dict(h) == {\"a\": \"2\", \"b\": \"4\"}\n    del h[\"a\"]\n    assert dict(h) == {\"b\": \"4\"}\n    assert h.raw == [(b\"b\", b\"4\")]\n\n\ndef test_mutable_headers_merge() -> None:\n    h = MutableHeaders()\n    h = h | MutableHeaders({\"a\": \"1\"})\n    assert isinstance(h, MutableHeaders)\n    assert dict(h) == {\"a\": \"1\"}\n    assert h.items() == [(\"a\", \"1\")]\n    assert h.raw == [(b\"a\", b\"1\")]\n\n\ndef test_mutable_headers_merge_dict() -> None:\n    h = MutableHeaders()\n    h = h | {\"a\": \"1\"}\n    assert isinstance(h, MutableHeaders)\n    assert dict(h) == {\"a\": \"1\"}\n    assert h.items() == [(\"a\", \"1\")]\n    assert h.raw == [(b\"a\", b\"1\")]\n\n\ndef test_mutable_headers_update() -> None:\n    h = MutableHeaders()\n    h |= MutableHeaders({\"a\": \"1\"})\n    assert isinstance(h, MutableHeaders)\n    assert dict(h) == {\"a\": \"1\"}\n    assert h.items() == [(\"a\", \"1\")]\n    assert h.raw == [(b\"a\", b\"1\")]\n\n\ndef test_mutable_headers_update_dict() -> None:\n    h = MutableHeaders()\n    h |= {\"a\": \"1\"}\n    assert isinstance(h, MutableHeaders)\n    assert dict(h) == {\"a\": \"1\"}\n    assert h.items() == [(\"a\", \"1\")]\n    assert h.raw == [(b\"a\", b\"1\")]\n\n\ndef test_mutable_headers_merge_not_mapping() -> None:\n    h = MutableHeaders()\n    with pytest.raises(TypeError):\n        h |= {\"not_mapping\"}  # type: ignore[arg-type]\n    with pytest.raises(TypeError):\n        h | {\"not_mapping\"}  # type: ignore[operator]\n\n\ndef test_headers_mutablecopy() -> None:\n    h = Headers(raw=[(b\"a\", b\"123\"), (b\"a\", b\"456\"), (b\"b\", b\"789\")])\n    c = h.mutablecopy()\n    assert c.items() == [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")]\n    c[\"a\"] = \"abc\"\n    assert c.items() == [(\"a\", \"abc\"), (\"b\", \"789\")]\n\n\ndef test_mutable_headers_from_scope() -> None:\n    # \"headers\" in scope must not necessarily be a list\n    h = MutableHeaders(scope={\"headers\": ((b\"a\", b\"1\"),)})\n    assert dict(h) == {\"a\": \"1\"}\n    h.update({\"b\": \"2\"})\n    assert dict(h) == {\"a\": \"1\", \"b\": \"2\"}\n    assert list(h.items()) == [(\"a\", \"1\"), (\"b\", \"2\")]\n    assert list(h.raw) == [(b\"a\", b\"1\"), (b\"b\", b\"2\")]\n\n\ndef test_url_blank_params() -> None:\n    q = QueryParams(\"a=123&abc&def&b=456\")\n    assert \"a\" in q\n    assert \"abc\" in q\n    assert \"def\" in q\n    assert \"b\" in q\n    val = q.get(\"abc\")\n    assert val is not None\n    assert len(val) == 0\n    assert len(q[\"a\"]) == 3\n    assert list(q.keys()) == [\"a\", \"abc\", \"def\", \"b\"]\n\n\ndef test_queryparams() -> None:\n    q = QueryParams(\"a=123&a=456&b=789\")\n    assert \"a\" in q\n    assert \"A\" not in q\n    assert \"c\" not in q\n    assert q[\"a\"] == \"456\"\n    assert q.get(\"a\") == \"456\"\n    assert q.get(\"nope\", default=None) is None\n    assert q.getlist(\"a\") == [\"123\", \"456\"]\n    assert list(q.keys()) == [\"a\", \"b\"]\n    assert list(q.values()) == [\"456\", \"789\"]\n    assert list(q.items()) == [(\"a\", \"456\"), (\"b\", \"789\")]\n    assert len(q) == 2\n    assert list(q) == [\"a\", \"b\"]\n    assert dict(q) == {\"a\": \"456\", \"b\": \"789\"}\n    assert str(q) == \"a=123&a=456&b=789\"\n    assert repr(q) == \"QueryParams('a=123&a=456&b=789')\"\n    assert QueryParams({\"a\": \"123\", \"b\": \"456\"}) == QueryParams([(\"a\", \"123\"), (\"b\", \"456\")])\n    assert QueryParams({\"a\": \"123\", \"b\": \"456\"}) == QueryParams(\"a=123&b=456\")\n    assert QueryParams({\"a\": \"123\", \"b\": \"456\"}) == QueryParams({\"b\": \"456\", \"a\": \"123\"})\n    assert QueryParams() == QueryParams({})\n    assert QueryParams([(\"a\", \"123\"), (\"a\", \"456\")]) == QueryParams(\"a=123&a=456\")\n    assert QueryParams({\"a\": \"123\", \"b\": \"456\"}) != \"invalid\"\n\n    q = QueryParams([(\"a\", \"123\"), (\"a\", \"456\")])\n    assert QueryParams(q) == q\n\n\n@pytest.mark.anyio\nasync def test_upload_file_file_input() -> None:\n    \"\"\"Test passing file/stream into the UploadFile constructor\"\"\"\n    stream = io.BytesIO(b\"data\")\n    file = UploadFile(filename=\"file\", file=stream, size=4)\n    assert await file.read() == b\"data\"\n    assert file.size == 4\n    await file.write(b\" and more data!\")\n    assert await file.read() == b\"\"\n    assert file.size == 19\n    await file.seek(0)\n    assert await file.read() == b\"data and more data!\"\n\n\n@pytest.mark.anyio\nasync def test_upload_file_without_size() -> None:\n    \"\"\"Test passing file/stream into the UploadFile constructor without size\"\"\"\n    stream = io.BytesIO(b\"data\")\n    file = UploadFile(filename=\"file\", file=stream)\n    assert await file.read() == b\"data\"\n    assert file.size is None\n    await file.write(b\" and more data!\")\n    assert await file.read() == b\"\"\n    assert file.size is None\n    await file.seek(0)\n    assert await file.read() == b\"data and more data!\"\n\n\n@pytest.mark.anyio\n@pytest.mark.parametrize(\"max_size\", [1, 1024], ids=[\"rolled\", \"unrolled\"])\nasync def test_uploadfile_rolling(max_size: int) -> None:\n    \"\"\"Test that we can r/w to a SpooledTemporaryFile\n    managed by UploadFile before and after it rolls to disk\n    \"\"\"\n    stream: BinaryIO = SpooledTemporaryFile(  # type: ignore[assignment]\n        max_size=max_size\n    )\n    file = UploadFile(filename=\"file\", file=stream, size=0)\n    assert await file.read() == b\"\"\n    assert file.size == 0\n    await file.write(b\"data\")\n    assert await file.read() == b\"\"\n    assert file.size == 4\n    await file.seek(0)\n    assert await file.read() == b\"data\"\n    await file.write(b\" more\")\n    assert await file.read() == b\"\"\n    assert file.size == 9\n    await file.seek(0)\n    assert await file.read() == b\"data more\"\n    assert file.size == 9\n    await file.close()\n\n\ndef test_formdata() -> None:\n    stream = io.BytesIO(b\"data\")\n    upload = UploadFile(filename=\"file\", file=stream, size=4)\n    form = FormData([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", upload)])\n    assert \"a\" in form\n    assert \"A\" not in form\n    assert \"c\" not in form\n    assert form[\"a\"] == \"456\"\n    assert form.get(\"a\") == \"456\"\n    assert form.get(\"nope\", default=None) is None\n    assert form.getlist(\"a\") == [\"123\", \"456\"]\n    assert list(form.keys()) == [\"a\", \"b\"]\n    assert list(form.values()) == [\"456\", upload]\n    assert list(form.items()) == [(\"a\", \"456\"), (\"b\", upload)]\n    assert len(form) == 2\n    assert list(form) == [\"a\", \"b\"]\n    assert dict(form) == {\"a\": \"456\", \"b\": upload}\n    assert repr(form) == \"FormData([('a', '123'), ('a', '456'), ('b', \" + repr(upload) + \")])\"\n    assert FormData(form) == form\n    assert FormData({\"a\": \"123\", \"b\": \"789\"}) == FormData([(\"a\", \"123\"), (\"b\", \"789\")])\n    assert FormData({\"a\": \"123\", \"b\": \"789\"}) != {\"a\": \"123\", \"b\": \"789\"}\n\n\n@pytest.mark.anyio\nasync def test_upload_file_repr() -> None:\n    stream = io.BytesIO(b\"data\")\n    file = UploadFile(filename=\"file\", file=stream, size=4)\n    assert repr(file) == \"UploadFile(filename='file', size=4, headers=Headers({}))\"\n\n\n@pytest.mark.anyio\nasync def test_upload_file_repr_headers() -> None:\n    stream = io.BytesIO(b\"data\")\n    file = UploadFile(filename=\"file\", file=stream, headers=Headers({\"foo\": \"bar\"}))\n    assert repr(file) == \"UploadFile(filename='file', size=None, headers=Headers({'foo': 'bar'}))\"\n\n\ndef test_multidict() -> None:\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    assert \"a\" in q\n    assert \"A\" not in q\n    assert \"c\" not in q\n    assert q[\"a\"] == \"456\"\n    assert q.get(\"a\") == \"456\"\n    assert q.get(\"nope\", default=None) is None\n    assert q.getlist(\"a\") == [\"123\", \"456\"]\n    assert list(q.keys()) == [\"a\", \"b\"]\n    assert list(q.values()) == [\"456\", \"789\"]\n    assert list(q.items()) == [(\"a\", \"456\"), (\"b\", \"789\")]\n    assert len(q) == 2\n    assert list(q) == [\"a\", \"b\"]\n    assert dict(q) == {\"a\": \"456\", \"b\": \"789\"}\n    assert str(q) == \"MultiDict([('a', '123'), ('a', '456'), ('b', '789')])\"\n    assert repr(q) == \"MultiDict([('a', '123'), ('a', '456'), ('b', '789')])\"\n    assert MultiDict({\"a\": \"123\", \"b\": \"456\"}) == MultiDict([(\"a\", \"123\"), (\"b\", \"456\")])\n    assert MultiDict({\"a\": \"123\", \"b\": \"456\"}) == MultiDict({\"b\": \"456\", \"a\": \"123\"})\n    assert MultiDict() == MultiDict({})\n    assert MultiDict({\"a\": \"123\", \"b\": \"456\"}) != \"invalid\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\")])\n    assert MultiDict(q) == q\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\")])\n    q[\"a\"] = \"789\"\n    assert q[\"a\"] == \"789\"\n    assert q.get(\"a\") == \"789\"\n    assert q.getlist(\"a\") == [\"789\"]\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\")])\n    del q[\"a\"]\n    assert q.get(\"a\") is None\n    assert repr(q) == \"MultiDict([])\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    assert q.pop(\"a\") == \"456\"\n    assert q.get(\"a\", None) is None\n    assert repr(q) == \"MultiDict([('b', '789')])\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    item = q.popitem()\n    assert q.get(item[0]) is None\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    assert q.poplist(\"a\") == [\"123\", \"456\"]\n    assert q.get(\"a\") is None\n    assert repr(q) == \"MultiDict([('b', '789')])\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    q.clear()\n    assert q.get(\"a\") is None\n    assert repr(q) == \"MultiDict([])\"\n\n    q = MultiDict([(\"a\", \"123\")])\n    q.setlist(\"a\", [\"456\", \"789\"])\n    assert q.getlist(\"a\") == [\"456\", \"789\"]\n    q.setlist(\"b\", [])\n    assert \"b\" not in q\n\n    q = MultiDict([(\"a\", \"123\")])\n    assert q.setdefault(\"a\", \"456\") == \"123\"\n    assert q.getlist(\"a\") == [\"123\"]\n    assert q.setdefault(\"b\", \"456\") == \"456\"\n    assert q.getlist(\"b\") == [\"456\"]\n    assert repr(q) == \"MultiDict([('a', '123'), ('b', '456')])\"\n\n    q = MultiDict([(\"a\", \"123\")])\n    q.append(\"a\", \"456\")\n    assert q.getlist(\"a\") == [\"123\", \"456\"]\n    assert repr(q) == \"MultiDict([('a', '123'), ('a', '456')])\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"b\", \"456\")])\n    q.update({\"a\": \"789\"})\n    assert q.getlist(\"a\") == [\"789\"]\n    assert q == MultiDict([(\"a\", \"789\"), (\"b\", \"456\")])\n\n    q = MultiDict([(\"a\", \"123\"), (\"b\", \"456\")])\n    q.update(q)\n    assert repr(q) == \"MultiDict([('a', '123'), ('b', '456')])\"\n\n    q = MultiDict([(\"a\", \"123\"), (\"a\", \"456\")])\n    q.update([(\"a\", \"123\")])\n    assert q.getlist(\"a\") == [\"123\"]\n    q.update([(\"a\", \"456\")], a=\"789\", b=\"123\")\n    assert q == MultiDict([(\"a\", \"456\"), (\"a\", \"789\"), (\"b\", \"123\")])\n"
  },
  {
    "path": "tests/test_endpoints.py",
    "content": "from collections.abc import Iterator\n\nimport pytest\n\nfrom starlette.endpoints import HTTPEndpoint, WebSocketEndpoint\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route, Router\nfrom starlette.testclient import TestClient\nfrom starlette.websockets import WebSocket\nfrom tests.types import TestClientFactory\n\n\nclass Homepage(HTTPEndpoint):\n    async def get(self, request: Request) -> PlainTextResponse:\n        username = request.path_params.get(\"username\")\n        if username is None:\n            return PlainTextResponse(\"Hello, world!\")\n        return PlainTextResponse(f\"Hello, {username}!\")\n\n\napp = Router(routes=[Route(\"/\", endpoint=Homepage), Route(\"/{username}\", endpoint=Homepage)])\n\n\n@pytest.fixture\ndef client(test_client_factory: TestClientFactory) -> Iterator[TestClient]:\n    with test_client_factory(app) as client:\n        yield client\n\n\ndef test_http_endpoint_route(client: TestClient) -> None:\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\ndef test_http_endpoint_route_path_params(client: TestClient) -> None:\n    response = client.get(\"/tomchristie\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, tomchristie!\"\n\n\ndef test_http_endpoint_route_method(client: TestClient) -> None:\n    response = client.post(\"/\")\n    assert response.status_code == 405\n    assert response.text == \"Method Not Allowed\"\n    assert response.headers[\"allow\"] == \"GET\"\n\n\ndef test_websocket_endpoint_on_connect(test_client_factory: TestClientFactory) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        async def on_connect(self, websocket: WebSocket) -> None:\n            assert websocket[\"subprotocols\"] == [\"soap\", \"wamp\"]\n            await websocket.accept(subprotocol=\"wamp\")\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\", subprotocols=[\"soap\", \"wamp\"]) as websocket:\n        assert websocket.accepted_subprotocol == \"wamp\"\n\n\ndef test_websocket_endpoint_on_receive_bytes(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        encoding = \"bytes\"\n\n        async def on_receive(self, websocket: WebSocket, data: bytes) -> None:\n            await websocket.send_bytes(b\"Message bytes was: \" + data)\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.send_bytes(b\"Hello, world!\")\n        _bytes = websocket.receive_bytes()\n        assert _bytes == b\"Message bytes was: Hello, world!\"\n\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/ws\") as websocket:\n            websocket.send_text(\"Hello world\")\n\n\ndef test_websocket_endpoint_on_receive_json(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        encoding = \"json\"\n\n        async def on_receive(self, websocket: WebSocket, data: str) -> None:\n            await websocket.send_json({\"message\": data})\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.send_json({\"hello\": \"world\"})\n        data = websocket.receive_json()\n        assert data == {\"message\": {\"hello\": \"world\"}}\n\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/ws\") as websocket:\n            websocket.send_text(\"Hello world\")\n\n\ndef test_websocket_endpoint_on_receive_json_binary(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        encoding = \"json\"\n\n        async def on_receive(self, websocket: WebSocket, data: str) -> None:\n            await websocket.send_json({\"message\": data}, mode=\"binary\")\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.send_json({\"hello\": \"world\"}, mode=\"binary\")\n        data = websocket.receive_json(mode=\"binary\")\n        assert data == {\"message\": {\"hello\": \"world\"}}\n\n\ndef test_websocket_endpoint_on_receive_text(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        encoding = \"text\"\n\n        async def on_receive(self, websocket: WebSocket, data: str) -> None:\n            await websocket.send_text(f\"Message text was: {data}\")\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.send_text(\"Hello, world!\")\n        _text = websocket.receive_text()\n        assert _text == \"Message text was: Hello, world!\"\n\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/ws\") as websocket:\n            websocket.send_bytes(b\"Hello world\")\n\n\ndef test_websocket_endpoint_on_default(test_client_factory: TestClientFactory) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        encoding = None\n\n        async def on_receive(self, websocket: WebSocket, data: str) -> None:\n            await websocket.send_text(f\"Message text was: {data}\")\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.send_text(\"Hello, world!\")\n        _text = websocket.receive_text()\n        assert _text == \"Message text was: Hello, world!\"\n\n\ndef test_websocket_endpoint_on_disconnect(\n    test_client_factory: TestClientFactory,\n) -> None:\n    class WebSocketApp(WebSocketEndpoint):\n        async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:\n            assert close_code == 1001\n            await websocket.close(code=close_code)\n\n    client = test_client_factory(WebSocketApp)\n    with client.websocket_connect(\"/ws\") as websocket:\n        websocket.close(code=1001)\n"
  },
  {
    "path": "tests/test_exceptions.py",
    "content": "from collections.abc import Generator\nfrom typing import Any\n\nimport pytest\nfrom pytest import MonkeyPatch\n\nfrom starlette.exceptions import HTTPException, WebSocketException\nfrom starlette.middleware.exceptions import ExceptionMiddleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, PlainTextResponse\nfrom starlette.routing import Route, Router, WebSocketRoute\nfrom starlette.testclient import TestClient\nfrom starlette.types import Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\ndef raise_runtime_error(request: Request) -> None:\n    raise RuntimeError(\"Yikes\")\n\n\ndef not_acceptable(request: Request) -> None:\n    raise HTTPException(status_code=406)\n\n\ndef no_content(request: Request) -> None:\n    raise HTTPException(status_code=204)\n\n\ndef not_modified(request: Request) -> None:\n    raise HTTPException(status_code=304)\n\n\ndef with_headers(request: Request) -> None:\n    raise HTTPException(status_code=200, headers={\"x-potato\": \"always\"})\n\n\nclass BadBodyException(HTTPException):\n    pass\n\n\nasync def read_body_and_raise_exc(request: Request) -> None:\n    await request.body()\n    raise BadBodyException(422)\n\n\nasync def handler_that_reads_body(request: Request, exc: BadBodyException) -> JSONResponse:\n    body = await request.body()\n    return JSONResponse(status_code=422, content={\"body\": body.decode()})\n\n\nclass HandledExcAfterResponse:\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        response = PlainTextResponse(\"OK\", status_code=200)\n        await response(scope, receive, send)\n        raise HTTPException(status_code=406)\n\n\nrouter = Router(\n    routes=[\n        Route(\"/runtime_error\", endpoint=raise_runtime_error),\n        Route(\"/not_acceptable\", endpoint=not_acceptable),\n        Route(\"/no_content\", endpoint=no_content),\n        Route(\"/not_modified\", endpoint=not_modified),\n        Route(\"/with_headers\", endpoint=with_headers),\n        Route(\"/handled_exc_after_response\", endpoint=HandledExcAfterResponse()),\n        WebSocketRoute(\"/runtime_error\", endpoint=raise_runtime_error),\n        Route(\"/consume_body_in_endpoint_and_handler\", endpoint=read_body_and_raise_exc, methods=[\"POST\"]),\n    ]\n)\n\n\napp = ExceptionMiddleware(\n    router,\n    handlers={BadBodyException: handler_that_reads_body},  # type: ignore[dict-item]\n)\n\n\n@pytest.fixture\ndef client(test_client_factory: TestClientFactory) -> Generator[TestClient, None, None]:\n    with test_client_factory(app) as client:\n        yield client\n\n\ndef test_not_acceptable(client: TestClient) -> None:\n    response = client.get(\"/not_acceptable\")\n    assert response.status_code == 406\n    assert response.text == \"Not Acceptable\"\n\n\ndef test_no_content(client: TestClient) -> None:\n    response = client.get(\"/no_content\")\n    assert response.status_code == 204\n    assert \"content-length\" not in response.headers\n\n\ndef test_not_modified(client: TestClient) -> None:\n    response = client.get(\"/not_modified\")\n    assert response.status_code == 304\n    assert response.text == \"\"\n\n\ndef test_with_headers(client: TestClient) -> None:\n    response = client.get(\"/with_headers\")\n    assert response.status_code == 200\n    assert response.headers[\"x-potato\"] == \"always\"\n\n\ndef test_websockets_should_raise(client: TestClient) -> None:\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/runtime_error\"):\n            pass  # pragma: no cover\n\n\ndef test_handled_exc_after_response(test_client_factory: TestClientFactory, client: TestClient) -> None:\n    # A 406 HttpException is raised *after* the response has already been sent.\n    # The exception middleware should raise a RuntimeError.\n    with pytest.raises(RuntimeError, match=\"Caught handled exception, but response already started.\"):\n        client.get(\"/handled_exc_after_response\")\n\n    # If `raise_server_exceptions=False` then the test client will still allow\n    # us to see the response as it will have been seen by the client.\n    allow_200_client = test_client_factory(app, raise_server_exceptions=False)\n    response = allow_200_client.get(\"/handled_exc_after_response\")\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n\n\ndef test_force_500_response(test_client_factory: TestClientFactory) -> None:\n    # use a sentinel variable to make sure we actually\n    # make it into the endpoint and don't get a 500\n    # from an incorrect ASGI app signature or something\n    called = False\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        nonlocal called\n        called = True\n        raise RuntimeError()\n\n    force_500_client = test_client_factory(app, raise_server_exceptions=False)\n    response = force_500_client.get(\"/\")\n    assert called\n    assert response.status_code == 500\n    assert response.text == \"\"\n\n\ndef test_http_str() -> None:\n    assert str(HTTPException(status_code=404)) == \"404: Not Found\"\n    assert str(HTTPException(404, \"Not Found: foo\")) == \"404: Not Found: foo\"\n    assert str(HTTPException(404, headers={\"key\": \"value\"})) == \"404: Not Found\"\n\n\ndef test_http_repr() -> None:\n    assert repr(HTTPException(404)) == (\"HTTPException(status_code=404, detail='Not Found')\")\n    assert repr(HTTPException(404, detail=\"Not Found: foo\")) == (\n        \"HTTPException(status_code=404, detail='Not Found: foo')\"\n    )\n\n    class CustomHTTPException(HTTPException):\n        pass\n\n    assert repr(CustomHTTPException(500, detail=\"Something custom\")) == (\n        \"CustomHTTPException(status_code=500, detail='Something custom')\"\n    )\n\n\ndef test_websocket_str() -> None:\n    assert str(WebSocketException(1008)) == \"1008: \"\n    assert str(WebSocketException(1008, \"Policy Violation\")) == \"1008: Policy Violation\"\n\n\ndef test_websocket_repr() -> None:\n    assert repr(WebSocketException(1008, reason=\"Policy Violation\")) == (\n        \"WebSocketException(code=1008, reason='Policy Violation')\"\n    )\n\n    class CustomWebSocketException(WebSocketException):\n        pass\n\n    assert (\n        repr(CustomWebSocketException(1013, reason=\"Something custom\"))\n        == \"CustomWebSocketException(code=1013, reason='Something custom')\"\n    )\n\n\ndef test_request_in_app_and_handler_is_the_same_object(client: TestClient) -> None:\n    response = client.post(\"/consume_body_in_endpoint_and_handler\", content=b\"Hello!\")\n    assert response.status_code == 422\n    assert response.json() == {\"body\": \"Hello!\"}\n\n\ndef test_http_exception_does_not_use_threadpool(client: TestClient, monkeypatch: MonkeyPatch) -> None:\n    \"\"\"\n    Verify that handling HTTPException does not invoke run_in_threadpool,\n    confirming the handler correctly runs in the main async context.\n    \"\"\"\n    from starlette import _exception_handler\n\n    # Replace run_in_threadpool with a function that raises an error\n    def mock_run_in_threadpool(*args: Any, **kwargs: Any) -> None:\n        pytest.fail(\"run_in_threadpool should not be called for HTTP exceptions\")  # pragma: no cover\n\n    # Apply the monkeypatch only during this test\n    monkeypatch.setattr(_exception_handler, \"run_in_threadpool\", mock_run_in_threadpool)\n\n    # This should succeed because http_exception is async and won't use run_in_threadpool\n    response = client.get(\"/not_acceptable\")\n    assert response.status_code == 406\n\n\ndef test_handlers_annotations() -> None:\n    \"\"\"Check that async exception handlers are accepted by type checkers.\n\n    We annotate the handlers' exceptions with plain `Exception` to avoid variance issues\n    when using other exception types.\n    \"\"\"\n\n    async def async_catch_all_handler(request: Request, exc: Exception) -> JSONResponse:\n        raise NotImplementedError\n\n    def sync_catch_all_handler(request: Request, exc: Exception) -> JSONResponse:\n        raise NotImplementedError\n\n    ExceptionMiddleware(router, handlers={Exception: sync_catch_all_handler})\n    ExceptionMiddleware(router, handlers={Exception: async_catch_all_handler})\n"
  },
  {
    "path": "tests/test_formparsers.py",
    "content": "from __future__ import annotations\n\nimport os\nimport threading\nfrom collections.abc import Generator\nfrom contextlib import AbstractContextManager, nullcontext as does_not_raise\nfrom io import BytesIO\nfrom pathlib import Path\nfrom tempfile import SpooledTemporaryFile\nfrom typing import Any, ClassVar\nfrom unittest import mock\n\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.datastructures import UploadFile\nfrom starlette.formparsers import MultiPartException, MultiPartParser, _user_safe_decode\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Mount\nfrom starlette.types import ASGIApp, Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\nclass ForceMultipartDict(dict[Any, Any]):\n    def __bool__(self) -> bool:\n        return True\n\n\n# FORCE_MULTIPART is an empty dict that boolean-evaluates as `True`.\nFORCE_MULTIPART = ForceMultipartDict()\n\n\nasync def app(scope: Scope, receive: Receive, send: Send) -> None:\n    request = Request(scope, receive)\n    data = await request.form()\n    output: dict[str, Any] = {}\n    for key, value in data.items():\n        if isinstance(value, UploadFile):\n            content = await value.read()\n            output[key] = {\n                \"filename\": value.filename,\n                \"size\": value.size,\n                \"content\": content.decode(),\n                \"content_type\": value.content_type,\n            }\n        else:\n            output[key] = value\n    await request.close()\n    response = JSONResponse(output)\n    await response(scope, receive, send)\n\n\nasync def multi_items_app(scope: Scope, receive: Receive, send: Send) -> None:\n    request = Request(scope, receive)\n    data = await request.form()\n    output: dict[str, list[Any]] = {}\n    for key, value in data.multi_items():\n        if key not in output:\n            output[key] = []\n        if isinstance(value, UploadFile):\n            content = await value.read()\n            output[key].append(\n                {\n                    \"filename\": value.filename,\n                    \"size\": value.size,\n                    \"content\": content.decode(),\n                    \"content_type\": value.content_type,\n                }\n            )\n        else:\n            output[key].append(value)\n    await request.close()\n    response = JSONResponse(output)\n    await response(scope, receive, send)\n\n\nasync def app_with_headers(scope: Scope, receive: Receive, send: Send) -> None:\n    request = Request(scope, receive)\n    data = await request.form()\n    output: dict[str, Any] = {}\n    for key, value in data.items():\n        if isinstance(value, UploadFile):\n            content = await value.read()\n            output[key] = {\n                \"filename\": value.filename,\n                \"size\": value.size,\n                \"content\": content.decode(),\n                \"content_type\": value.content_type,\n                \"headers\": list(value.headers.items()),\n            }\n        else:\n            output[key] = value\n    await request.close()\n    response = JSONResponse(output)\n    await response(scope, receive, send)\n\n\nasync def app_read_body(scope: Scope, receive: Receive, send: Send) -> None:\n    request = Request(scope, receive)\n    # Read bytes, to force request.stream() to return the already parsed body\n    await request.body()\n    data = await request.form()\n    output = {}\n    for key, value in data.items():\n        output[key] = value\n    await request.close()\n    response = JSONResponse(output)\n    await response(scope, receive, send)\n\n\nasync def app_monitor_thread(scope: Scope, receive: Receive, send: Send) -> None:\n    \"\"\"Helper app to monitor what thread the app was called on.\n\n    This can later be used to validate thread/event loop operations.\n    \"\"\"\n    request = Request(scope, receive)\n\n    # Make sure we parse the form\n    await request.form()\n    await request.close()\n\n    # Send back the current thread id\n    response = JSONResponse({\"thread_ident\": threading.current_thread().ident})\n    await response(scope, receive, send)\n\n\ndef make_app_max_parts(max_files: int = 1000, max_fields: int = 1000, max_part_size: int = 1024 * 1024) -> ASGIApp:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        data = await request.form(max_files=max_files, max_fields=max_fields, max_part_size=max_part_size)\n        output: dict[str, Any] = {}\n        for key, value in data.items():\n            if isinstance(value, UploadFile):\n                content = await value.read()\n                output[key] = {\n                    \"filename\": value.filename,\n                    \"size\": value.size,\n                    \"content\": content.decode(),\n                    \"content_type\": value.content_type,\n                }\n            else:\n                output[key] = value\n        await request.close()\n        response = JSONResponse(output)\n        await response(scope, receive, send)\n\n    return app\n\n\ndef test_multipart_request_data(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\"/\", data={\"some\": \"data\"}, files=FORCE_MULTIPART)\n    assert response.json() == {\"some\": \"data\"}\n\n\ndef test_multipart_request_files(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"test.txt\")\n    with open(path, \"wb\") as file:\n        file.write(b\"<file content>\")\n\n    client = test_client_factory(app)\n    with open(path, \"rb\") as f:\n        response = client.post(\"/\", files={\"test\": f})\n        assert response.json() == {\n            \"test\": {\n                \"filename\": \"test.txt\",\n                \"size\": 14,\n                \"content\": \"<file content>\",\n                \"content_type\": \"text/plain\",\n            }\n        }\n\n\ndef test_multipart_request_files_with_content_type(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"test.txt\")\n    with open(path, \"wb\") as file:\n        file.write(b\"<file content>\")\n\n    client = test_client_factory(app)\n    with open(path, \"rb\") as f:\n        response = client.post(\"/\", files={\"test\": (\"test.txt\", f, \"text/plain\")})\n        assert response.json() == {\n            \"test\": {\n                \"filename\": \"test.txt\",\n                \"size\": 14,\n                \"content\": \"<file content>\",\n                \"content_type\": \"text/plain\",\n            }\n        }\n\n\ndef test_multipart_request_multiple_files(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path1 = os.path.join(tmpdir, \"test1.txt\")\n    with open(path1, \"wb\") as file:\n        file.write(b\"<file1 content>\")\n\n    path2 = os.path.join(tmpdir, \"test2.txt\")\n    with open(path2, \"wb\") as file:\n        file.write(b\"<file2 content>\")\n\n    client = test_client_factory(app)\n    with open(path1, \"rb\") as f1, open(path2, \"rb\") as f2:\n        response = client.post(\"/\", files={\"test1\": f1, \"test2\": (\"test2.txt\", f2, \"text/plain\")})\n        assert response.json() == {\n            \"test1\": {\n                \"filename\": \"test1.txt\",\n                \"size\": 15,\n                \"content\": \"<file1 content>\",\n                \"content_type\": \"text/plain\",\n            },\n            \"test2\": {\n                \"filename\": \"test2.txt\",\n                \"size\": 15,\n                \"content\": \"<file2 content>\",\n                \"content_type\": \"text/plain\",\n            },\n        }\n\n\ndef test_multipart_request_multiple_files_with_headers(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path1 = os.path.join(tmpdir, \"test1.txt\")\n    with open(path1, \"wb\") as file:\n        file.write(b\"<file1 content>\")\n\n    path2 = os.path.join(tmpdir, \"test2.txt\")\n    with open(path2, \"wb\") as file:\n        file.write(b\"<file2 content>\")\n\n    client = test_client_factory(app_with_headers)\n    with open(path1, \"rb\") as f1, open(path2, \"rb\") as f2:\n        response = client.post(\n            \"/\",\n            files=[\n                (\"test1\", (None, f1)),\n                (\"test2\", (\"test2.txt\", f2, \"text/plain\", {\"x-custom\": \"f2\"})),\n            ],\n        )\n        assert response.json() == {\n            \"test1\": \"<file1 content>\",\n            \"test2\": {\n                \"filename\": \"test2.txt\",\n                \"size\": 15,\n                \"content\": \"<file2 content>\",\n                \"content_type\": \"text/plain\",\n                \"headers\": [\n                    [\n                        \"content-disposition\",\n                        'form-data; name=\"test2\"; filename=\"test2.txt\"',\n                    ],\n                    [\"x-custom\", \"f2\"],\n                    [\"content-type\", \"text/plain\"],\n                ],\n            },\n        }\n\n\ndef test_multi_items(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path1 = os.path.join(tmpdir, \"test1.txt\")\n    with open(path1, \"wb\") as file:\n        file.write(b\"<file1 content>\")\n\n    path2 = os.path.join(tmpdir, \"test2.txt\")\n    with open(path2, \"wb\") as file:\n        file.write(b\"<file2 content>\")\n\n    client = test_client_factory(multi_items_app)\n    with open(path1, \"rb\") as f1, open(path2, \"rb\") as f2:\n        response = client.post(\n            \"/\",\n            data={\"test1\": \"abc\"},\n            files=[(\"test1\", f1), (\"test1\", (\"test2.txt\", f2, \"text/plain\"))],\n        )\n        assert response.json() == {\n            \"test1\": [\n                \"abc\",\n                {\n                    \"filename\": \"test1.txt\",\n                    \"size\": 15,\n                    \"content\": \"<file1 content>\",\n                    \"content_type\": \"text/plain\",\n                },\n                {\n                    \"filename\": \"test2.txt\",\n                    \"size\": 15,\n                    \"content\": \"<file2 content>\",\n                    \"content_type\": \"text/plain\",\n                },\n            ]\n        }\n\n\ndef test_multipart_request_mixed_files_and_data(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\n        \"/\",\n        data=(\n            # data\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"  # type: ignore\n            b'Content-Disposition: form-data; name=\"field0\"\\r\\n\\r\\n'\n            b\"value0\\r\\n\"\n            # file\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"\\r\\n'\n            b\"Content-Type: text/plain\\r\\n\\r\\n\"\n            b\"<file content>\\r\\n\"\n            # data\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"\n            b'Content-Disposition: form-data; name=\"field1\"\\r\\n\\r\\n'\n            b\"value1\\r\\n\"\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c--\\r\\n\"\n        ),\n        headers={\"Content-Type\": (\"multipart/form-data; boundary=a7f7ac8d4e2e437c877bb7b8d7cc549c\")},\n    )\n    assert response.json() == {\n        \"file\": {\n            \"filename\": \"file.txt\",\n            \"size\": 14,\n            \"content\": \"<file content>\",\n            \"content_type\": \"text/plain\",\n        },\n        \"field0\": \"value0\",\n        \"field1\": \"value1\",\n    }\n\n\nclass ThreadTrackingSpooledTemporaryFile(SpooledTemporaryFile[bytes]):\n    \"\"\"Helper class to track which threads performed the rollover operation.\n\n    This is not threadsafe/multi-test safe.\n    \"\"\"\n\n    rollover_threads: ClassVar[set[int | None]] = set()\n\n    def rollover(self) -> None:\n        ThreadTrackingSpooledTemporaryFile.rollover_threads.add(threading.current_thread().ident)\n        super().rollover()\n\n\n@pytest.fixture\ndef mock_spooled_temporary_file() -> Generator[None]:\n    try:\n        with mock.patch(\"starlette.formparsers.SpooledTemporaryFile\", ThreadTrackingSpooledTemporaryFile):\n            yield\n    finally:\n        ThreadTrackingSpooledTemporaryFile.rollover_threads.clear()\n\n\ndef test_multipart_request_large_file_rollover_in_background_thread(\n    mock_spooled_temporary_file: None, test_client_factory: TestClientFactory\n) -> None:\n    \"\"\"Test that Spooled file rollovers happen in background threads.\"\"\"\n    data = BytesIO(b\" \" * (MultiPartParser.spool_max_size + 1))\n\n    client = test_client_factory(app_monitor_thread)\n    response = client.post(\"/\", files=[(\"test_large\", data)])\n    assert response.status_code == 200\n\n    # Parse the event thread id from the API response and ensure we have one\n    app_thread_ident = response.json().get(\"thread_ident\")\n    assert app_thread_ident is not None\n\n    # Ensure the app thread was not the same as the rollover one and that a rollover thread exists\n    assert app_thread_ident not in ThreadTrackingSpooledTemporaryFile.rollover_threads\n    assert len(ThreadTrackingSpooledTemporaryFile.rollover_threads) == 1\n\n\ndef test_multipart_request_with_charset_for_filename(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\n        \"/\",\n        data=(\n            # file\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"  # type: ignore\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"\\xe6\\x96\\x87\\xe6\\x9b\\xb8.txt\"\\r\\n'\n            b\"Content-Type: text/plain\\r\\n\\r\\n\"\n            b\"<file content>\\r\\n\"\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c--\\r\\n\"\n        ),\n        headers={\"Content-Type\": (\"multipart/form-data; charset=utf-8; boundary=a7f7ac8d4e2e437c877bb7b8d7cc549c\")},\n    )\n    assert response.json() == {\n        \"file\": {\n            \"filename\": \"文書.txt\",\n            \"size\": 14,\n            \"content\": \"<file content>\",\n            \"content_type\": \"text/plain\",\n        }\n    }\n\n\ndef test_multipart_request_without_charset_for_filename(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\n        \"/\",\n        data=(\n            # file\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"  # type: ignore\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"\\xe7\\x94\\xbb\\xe5\\x83\\x8f.jpg\"\\r\\n'\n            b\"Content-Type: image/jpeg\\r\\n\\r\\n\"\n            b\"<file content>\\r\\n\"\n            b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c--\\r\\n\"\n        ),\n        headers={\"Content-Type\": (\"multipart/form-data; boundary=a7f7ac8d4e2e437c877bb7b8d7cc549c\")},\n    )\n    assert response.json() == {\n        \"file\": {\n            \"filename\": \"画像.jpg\",\n            \"size\": 14,\n            \"content\": \"<file content>\",\n            \"content_type\": \"image/jpeg\",\n        }\n    }\n\n\ndef test_multipart_request_with_encoded_value(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\n        \"/\",\n        data=(\n            b\"--20b303e711c4ab8c443184ac833ab00f\\r\\n\"  # type: ignore\n            b\"Content-Disposition: form-data; \"\n            b'name=\"value\"\\r\\n\\r\\n'\n            b\"Transf\\xc3\\xa9rer\\r\\n\"\n            b\"--20b303e711c4ab8c443184ac833ab00f--\\r\\n\"\n        ),\n        headers={\"Content-Type\": (\"multipart/form-data; charset=utf-8; boundary=20b303e711c4ab8c443184ac833ab00f\")},\n    )\n    assert response.json() == {\"value\": \"Transférer\"}\n\n\ndef test_urlencoded_request_data(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\"/\", data={\"some\": \"data\"})\n    assert response.json() == {\"some\": \"data\"}\n\n\ndef test_no_request_data(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\"/\")\n    assert response.json() == {}\n\n\ndef test_urlencoded_percent_encoding(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\"/\", data={\"some\": \"da ta\"})\n    assert response.json() == {\"some\": \"da ta\"}\n\n\ndef test_urlencoded_percent_encoding_keys(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.post(\"/\", data={\"so me\": \"data\"})\n    assert response.json() == {\"so me\": \"data\"}\n\n\ndef test_urlencoded_multi_field_app_reads_body(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app_read_body)\n    response = client.post(\"/\", data={\"some\": \"data\", \"second\": \"key pair\"})\n    assert response.json() == {\"some\": \"data\", \"second\": \"key pair\"}\n\n\ndef test_multipart_multi_field_app_reads_body(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app_read_body)\n    response = client.post(\"/\", data={\"some\": \"data\", \"second\": \"key pair\"}, files=FORCE_MULTIPART)\n    assert response.json() == {\"some\": \"data\", \"second\": \"key pair\"}\n\n\ndef test_user_safe_decode_helper() -> None:\n    result = _user_safe_decode(b\"\\xc4\\x99\\xc5\\xbc\\xc4\\x87\", \"utf-8\")\n    assert result == \"ężć\"\n\n\ndef test_user_safe_decode_ignores_wrong_charset() -> None:\n    result = _user_safe_decode(b\"abc\", \"latin-8\")\n    assert result == \"abc\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_missing_boundary_parameter(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=(\n                # file\n                b'Content-Disposition: form-data; name=\"file\"; filename=\"\\xe6\\x96\\x87\\xe6\\x9b\\xb8.txt\"\\r\\n'  # type: ignore\n                b\"Content-Type: text/plain\\r\\n\\r\\n\"\n                b\"<file content>\\r\\n\"\n            ),\n            headers={\"Content-Type\": \"multipart/form-data; charset=utf-8\"},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Missing boundary in multipart.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_missing_name_parameter_on_content_disposition(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=(\n                # data\n                b\"--a7f7ac8d4e2e437c877bb7b8d7cc549c\\r\\n\"  # type: ignore\n                b'Content-Disposition: form-data; =\"field0\"\\r\\n\\r\\n'\n                b\"value0\\r\\n\"\n            ),\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=a7f7ac8d4e2e437c877bb7b8d7cc549c\")},\n        )\n        assert res.status_code == 400\n        assert res.text == 'The Content-Disposition header field \"name\" must be provided.'\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_too_many_fields_raise(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(1001):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many fields. Maximum number of fields is 1000.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_too_many_files_raise(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(1001):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N{i}\"; filename=\"F{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many files. Maximum number of files is 1000.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_too_many_files_single_field_raise(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(1001):\n        # This uses the same field name \"N\" for all files, equivalent to a\n        # multifile upload form field\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N\"; filename=\"F{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many files. Maximum number of files is 1000.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_too_many_files_and_fields_raise(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(1001):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"F{i}\"; filename=\"F{i}\";\\r\\n\\r\\n\\r\\n')\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many files. Maximum number of files is 1000.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (make_app_max_parts(max_fields=1), pytest.raises(MultiPartException)),\n        (\n            Starlette(routes=[Mount(\"/\", app=make_app_max_parts(max_fields=1))]),\n            does_not_raise(),\n        ),\n    ],\n)\ndef test_max_fields_is_customizable_low_raises(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(2):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many fields. Maximum number of fields is 1.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (make_app_max_parts(max_files=1), pytest.raises(MultiPartException)),\n        (\n            Starlette(routes=[Mount(\"/\", app=make_app_max_parts(max_files=1))]),\n            does_not_raise(),\n        ),\n    ],\n)\ndef test_max_files_is_customizable_low_raises(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    fields = []\n    for i in range(2):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"F{i}\"; filename=\"F{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    with expectation:\n        res = client.post(\n            \"/\",\n            data=data,  # type: ignore\n            headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n        )\n        assert res.status_code == 400\n        assert res.text == \"Too many files. Maximum number of files is 1.\"\n\n\ndef test_max_fields_is_customizable_high(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(make_app_max_parts(max_fields=2000, max_files=2000))\n    fields = []\n    for i in range(2000):\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"N{i}\";\\r\\n\\r\\n\\r\\n')\n        fields.append(f'--B\\r\\nContent-Disposition: form-data; name=\"F{i}\"; filename=\"F{i}\";\\r\\n\\r\\n\\r\\n')\n    data = \"\".join(fields).encode(\"utf-8\")\n    data += b\"--B--\\r\\n\"\n    res = client.post(\n        \"/\",\n        data=data,  # type: ignore\n        headers={\"Content-Type\": (\"multipart/form-data; boundary=B\")},\n    )\n    assert res.status_code == 200\n    res_data = res.json()\n    assert res_data[\"N1999\"] == \"\"\n    assert res_data[\"F1999\"] == {\n        \"filename\": \"F1999\",\n        \"size\": 0,\n        \"content\": \"\",\n        \"content_type\": None,\n    }\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (app, pytest.raises(MultiPartException)),\n        (Starlette(routes=[Mount(\"/\", app=app)]), does_not_raise()),\n    ],\n)\ndef test_max_part_size_exceeds_limit(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    boundary = \"------------------------4K1ON9fZkj9uCUmqLHRbbR\"\n\n    multipart_data = (\n        f\"--{boundary}\\r\\n\"\n        f'Content-Disposition: form-data; name=\"small\"\\r\\n\\r\\n'\n        \"small content\\r\\n\"\n        f\"--{boundary}\\r\\n\"\n        f'Content-Disposition: form-data; name=\"large\"\\r\\n\\r\\n'\n        + (\"x\" * 1024 * 1024 + \"x\")  # 1MB + 1 byte of data\n        + \"\\r\\n\"\n        f\"--{boundary}--\\r\\n\"\n    ).encode(\"utf-8\")\n\n    headers = {\n        \"Content-Type\": f\"multipart/form-data; boundary={boundary}\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n\n    with expectation:\n        response = client.post(\"/\", data=multipart_data, headers=headers)  # type: ignore\n        assert response.status_code == 400\n        assert response.text == \"Part exceeded maximum size of 1024KB.\"\n\n\n@pytest.mark.parametrize(\n    \"app,expectation\",\n    [\n        (make_app_max_parts(max_part_size=1024 * 10), pytest.raises(MultiPartException)),\n        (\n            Starlette(routes=[Mount(\"/\", app=make_app_max_parts(max_part_size=1024 * 10))]),\n            does_not_raise(),\n        ),\n    ],\n)\ndef test_max_part_size_exceeds_custom_limit(\n    app: ASGIApp,\n    expectation: AbstractContextManager[Exception],\n    test_client_factory: TestClientFactory,\n) -> None:\n    client = test_client_factory(app)\n    boundary = \"------------------------4K1ON9fZkj9uCUmqLHRbbR\"\n\n    multipart_data = (\n        f\"--{boundary}\\r\\n\"\n        f'Content-Disposition: form-data; name=\"small\"\\r\\n\\r\\n'\n        \"small content\\r\\n\"\n        f\"--{boundary}\\r\\n\"\n        f'Content-Disposition: form-data; name=\"large\"\\r\\n\\r\\n'\n        + (\"x\" * 1024 * 10 + \"x\")  # 1MB + 1 byte of data\n        + \"\\r\\n\"\n        f\"--{boundary}--\\r\\n\"\n    ).encode(\"utf-8\")\n\n    headers = {\n        \"Content-Type\": f\"multipart/form-data; boundary={boundary}\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n\n    with expectation:\n        response = client.post(\"/\", content=multipart_data, headers=headers)\n        assert response.status_code == 400\n        assert response.text == \"Part exceeded maximum size of 10KB.\"\n"
  },
  {
    "path": "tests/test_requests.py",
    "content": "from __future__ import annotations\n\nimport sys\nfrom collections.abc import Iterator\nfrom typing import Any\n\nimport anyio\nimport pytest\n\nfrom starlette.datastructures import URL, Address, State\nfrom starlette.requests import ClientDisconnect, Request\nfrom starlette.responses import JSONResponse, PlainTextResponse, Response\nfrom starlette.types import Message, Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\ndef test_request_url(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        data = {\"method\": request.method, \"url\": str(request.url)}\n        response = JSONResponse(data)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/123?a=abc\")\n    assert response.json() == {\"method\": \"GET\", \"url\": \"http://testserver/123?a=abc\"}\n\n    response = client.get(\"https://example.org:123/\")\n    assert response.json() == {\"method\": \"GET\", \"url\": \"https://example.org:123/\"}\n\n\ndef test_request_query_params(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        params = dict(request.query_params)\n        response = JSONResponse({\"params\": params})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/?a=123&b=456\")\n    assert response.json() == {\"params\": {\"a\": \"123\", \"b\": \"456\"}}\n\n\n@pytest.mark.skipif(\n    any(module in sys.modules for module in (\"brotli\", \"brotlicffi\")),\n    reason='urllib3 includes \"br\" to the \"accept-encoding\" headers.',\n)\ndef test_request_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        headers = dict(request.headers)\n        response = JSONResponse({\"headers\": headers})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"host\": \"example.org\"})\n    assert response.json() == {\n        \"headers\": {\n            \"host\": \"example.org\",\n            \"user-agent\": \"testclient\",\n            \"accept-encoding\": \"gzip, deflate\",\n            \"accept\": \"*/*\",\n            \"connection\": \"keep-alive\",\n        }\n    }\n\n\n@pytest.mark.parametrize(\n    \"scope,expected_client\",\n    [\n        ({\"client\": [\"client\", 42]}, Address(\"client\", 42)),\n        ({\"client\": None}, None),\n        ({}, None),\n    ],\n)\ndef test_request_client(scope: Scope, expected_client: Address | None) -> None:\n    scope.update({\"type\": \"http\"})  # required by Request's constructor\n    client = Request(scope).client\n    assert client == expected_client\n\n\ndef test_request_body(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        body = await request.body()\n        response = JSONResponse({\"body\": body.decode()})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.get(\"/\")\n    assert response.json() == {\"body\": \"\"}\n\n    response = client.post(\"/\", json={\"a\": \"123\"})\n    assert response.json() == {\"body\": '{\"a\":\"123\"}'}\n\n    response = client.post(\"/\", data=\"abc\")  # type: ignore\n    assert response.json() == {\"body\": \"abc\"}\n\n\ndef test_request_stream(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        body = b\"\"\n        async for chunk in request.stream():\n            body += chunk\n        response = JSONResponse({\"body\": body.decode()})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.get(\"/\")\n    assert response.json() == {\"body\": \"\"}\n\n    response = client.post(\"/\", json={\"a\": \"123\"})\n    assert response.json() == {\"body\": '{\"a\":\"123\"}'}\n\n    response = client.post(\"/\", data=\"abc\")  # type: ignore\n    assert response.json() == {\"body\": \"abc\"}\n\n\ndef test_request_form_urlencoded(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        form = await request.form()\n        response = JSONResponse({\"form\": dict(form)})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.post(\"/\", data={\"abc\": \"123 @\"})\n    assert response.json() == {\"form\": {\"abc\": \"123 @\"}}\n\n\ndef test_request_form_context_manager(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        async with request.form() as form:\n            response = JSONResponse({\"form\": dict(form)})\n            await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.post(\"/\", data={\"abc\": \"123 @\"})\n    assert response.json() == {\"form\": {\"abc\": \"123 @\"}}\n\n\ndef test_request_body_then_stream(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        body = await request.body()\n        chunks = b\"\"\n        async for chunk in request.stream():\n            chunks += chunk\n        response = JSONResponse({\"body\": body.decode(), \"stream\": chunks.decode()})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.post(\"/\", data=\"abc\")  # type: ignore\n    assert response.json() == {\"body\": \"abc\", \"stream\": \"abc\"}\n\n\ndef test_request_stream_then_body(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        chunks = b\"\"\n        async for chunk in request.stream():  # pragma: no branch\n            chunks += chunk\n        try:\n            body = await request.body()\n        except RuntimeError:\n            body = b\"<stream consumed>\"\n        response = JSONResponse({\"body\": body.decode(), \"stream\": chunks.decode()})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    response = client.post(\"/\", data=\"abc\")  # type: ignore\n    assert response.json() == {\"body\": \"<stream consumed>\", \"stream\": \"abc\"}\n\n\ndef test_request_json(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        data = await request.json()\n        response = JSONResponse({\"json\": data})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.post(\"/\", json={\"a\": \"123\"})\n    assert response.json() == {\"json\": {\"a\": \"123\"}}\n\n\ndef test_request_scope_interface() -> None:\n    \"\"\"\n    A Request can be instantiated with a scope, and presents a `Mapping`\n    interface.\n    \"\"\"\n    request = Request({\"type\": \"http\", \"method\": \"GET\", \"path\": \"/abc/\"})\n    assert request[\"method\"] == \"GET\"\n    assert dict(request) == {\"type\": \"http\", \"method\": \"GET\", \"path\": \"/abc/\"}\n    assert len(request) == 3\n\n\ndef test_request_raw_path(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        path = request.scope[\"path\"]\n        raw_path = request.scope[\"raw_path\"]\n        response = PlainTextResponse(f\"{path}, {raw_path}\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/he%2Fllo\")\n    assert response.text == \"/he/llo, b'/he%2Fllo'\"\n\n\ndef test_request_without_setting_receive(\n    test_client_factory: TestClientFactory,\n) -> None:\n    \"\"\"\n    If Request is instantiated without the receive channel, then .body()\n    is not available.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope)\n        try:\n            data = await request.json()\n        except RuntimeError:\n            data = \"Receive channel not available\"\n        response = JSONResponse({\"json\": data})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.post(\"/\", json={\"a\": \"123\"})\n    assert response.json() == {\"json\": \"Receive channel not available\"}\n\n\ndef test_request_disconnect(\n    anyio_backend_name: str,\n    anyio_backend_options: dict[str, Any],\n) -> None:\n    \"\"\"\n    If a client disconnect occurs while reading request body\n    then ClientDisconnect should be raised.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        await request.body()\n\n    async def receiver() -> Message:\n        return {\"type\": \"http.disconnect\"}\n\n    scope = {\"type\": \"http\", \"method\": \"POST\", \"path\": \"/\"}\n    with pytest.raises(ClientDisconnect):\n        anyio.run(\n            app,  # type: ignore\n            scope,\n            receiver,\n            None,\n            backend=anyio_backend_name,\n            backend_options=anyio_backend_options,\n        )\n\n\ndef test_request_is_disconnected(test_client_factory: TestClientFactory) -> None:\n    \"\"\"\n    If a client disconnect occurs after reading request body\n    then request will be set disconnected properly.\n    \"\"\"\n    disconnected_after_response = None\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        nonlocal disconnected_after_response\n\n        request = Request(scope, receive)\n        body = await request.body()\n        disconnected = await request.is_disconnected()\n        response = JSONResponse({\"body\": body.decode(), \"disconnected\": disconnected})\n        await response(scope, receive, send)\n        disconnected_after_response = await request.is_disconnected()\n\n    client = test_client_factory(app)\n    response = client.post(\"/\", content=\"foo\")\n    assert response.json() == {\"body\": \"foo\", \"disconnected\": False}\n    assert disconnected_after_response\n\n\ndef test_request_state_object() -> None:\n    scope = {\"state\": {\"old\": \"foo\"}}\n\n    s = State(scope[\"state\"])\n\n    s.new = \"value\"\n    assert s.new == \"value\"\n\n    del s.new\n\n    with pytest.raises(AttributeError):\n        s.new\n\n    # Test dictionary-style methods\n    # Test __setitem__\n    s[\"dict_key\"] = \"dict_value\"\n    assert s[\"dict_key\"] == \"dict_value\"\n    assert s.dict_key == \"dict_value\"\n\n    # Test __iter__\n    s[\"another_key\"] = \"another_value\"\n    keys = list(s)\n    assert \"old\" in keys\n    assert \"dict_key\" in keys\n    assert \"another_key\" in keys\n\n    # Test __len__\n    assert len(s) == 3\n\n    # Test __delitem__\n    del s[\"dict_key\"]\n    assert len(s) == 2\n    with pytest.raises(KeyError):\n        s[\"dict_key\"]\n\n\ndef test_request_state(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        request.state.example = 123\n        response = JSONResponse({\"state.example\": request.state.example})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/123?a=abc\")\n    assert response.json() == {\"state.example\": 123}\n\n\ndef test_request_cookies(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        mycookie = request.cookies.get(\"mycookie\")\n        if mycookie:\n            response = Response(mycookie, media_type=\"text/plain\")\n        else:\n            response = Response(\"Hello, world!\", media_type=\"text/plain\")\n            response.set_cookie(\"mycookie\", \"Hello, cookies!\")\n\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n    response = client.get(\"/\")\n    assert response.text == \"Hello, cookies!\"\n\n\ndef test_cookie_lenient_parsing(test_client_factory: TestClientFactory) -> None:\n    \"\"\"\n    The following test is based on a cookie set by Okta, a well-known authorization\n    service. It turns out that it's common practice to set cookies that would be\n    invalid according to the spec.\n    \"\"\"\n    tough_cookie = (\n        \"provider-oauth-nonce=validAsciiblabla; \"\n        'okta-oauth-redirect-params={\"responseType\":\"code\",\"state\":\"somestate\",'\n        '\"nonce\":\"somenonce\",\"scopes\":[\"openid\",\"profile\",\"email\",\"phone\"],'\n        '\"urls\":{\"issuer\":\"https://subdomain.okta.com/oauth2/authServer\",'\n        '\"authorizeUrl\":\"https://subdomain.okta.com/oauth2/authServer/v1/authorize\",'\n        '\"userinfoUrl\":\"https://subdomain.okta.com/oauth2/authServer/v1/userinfo\"}}; '\n        \"importantCookie=importantValue; sessionCookie=importantSessionValue\"\n    )\n    expected_keys = {\n        \"importantCookie\",\n        \"okta-oauth-redirect-params\",\n        \"provider-oauth-nonce\",\n        \"sessionCookie\",\n    }\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        response = JSONResponse({\"cookies\": request.cookies})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"cookie\": tough_cookie})\n    result = response.json()\n    assert len(result[\"cookies\"]) == 4\n    assert set(result[\"cookies\"].keys()) == expected_keys\n\n\n# These test cases copied from Tornado's implementation\n@pytest.mark.parametrize(\n    \"set_cookie,expected\",\n    [\n        (\"chips=ahoy; vienna=finger\", {\"chips\": \"ahoy\", \"vienna\": \"finger\"}),\n        # all semicolons are delimiters, even within quotes\n        (\n            'keebler=\"E=mc2; L=\\\\\"Loves\\\\\"; fudge=\\\\012;\"',\n            {\"keebler\": '\"E=mc2', \"L\": '\\\\\"Loves\\\\\"', \"fudge\": \"\\\\012\", \"\": '\"'},\n        ),\n        # Illegal cookies that have an '=' char in an unquoted value.\n        (\"keebler=E=mc2\", {\"keebler\": \"E=mc2\"}),\n        # Cookies with ':' character in their name.\n        (\"key:term=value:term\", {\"key:term\": \"value:term\"}),\n        # Cookies with '[' and ']'.\n        (\"a=b; c=[; d=r; f=h\", {\"a\": \"b\", \"c\": \"[\", \"d\": \"r\", \"f\": \"h\"}),\n        # Cookies that RFC6265 allows.\n        (\"a=b; Domain=example.com\", {\"a\": \"b\", \"Domain\": \"example.com\"}),\n        # parse_cookie() keeps only the last cookie with the same name.\n        (\"a=b; h=i; a=c\", {\"a\": \"c\", \"h\": \"i\"}),\n    ],\n)\ndef test_cookies_edge_cases(\n    set_cookie: str,\n    expected: dict[str, str],\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        response = JSONResponse({\"cookies\": request.cookies})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"cookie\": set_cookie})\n    result = response.json()\n    assert result[\"cookies\"] == expected\n\n\n@pytest.mark.parametrize(\n    \"set_cookie,expected\",\n    [\n        # Chunks without an equals sign appear as unnamed values per\n        # https://bugzilla.mozilla.org/show_bug.cgi?id=169091\n        (\n            \"abc=def; unnamed; django_language=en\",\n            {\"\": \"unnamed\", \"abc\": \"def\", \"django_language\": \"en\"},\n        ),\n        # Even a double quote may be an unamed value.\n        ('a=b; \"; c=d', {\"a\": \"b\", \"\": '\"', \"c\": \"d\"}),\n        # Spaces in names and values, and an equals sign in values.\n        (\"a b c=d e = f; gh=i\", {\"a b c\": \"d e = f\", \"gh\": \"i\"}),\n        # More characters the spec forbids.\n        ('a   b,c<>@:/[]?{}=d  \"  =e,f g', {\"a   b,c<>@:/[]?{}\": 'd  \"  =e,f g'}),\n        # Unicode characters. The spec only allows ASCII.\n        # (\"saint=André Bessette\", {\"saint\": \"André Bessette\"}),\n        # Browsers don't send extra whitespace or semicolons in Cookie headers,\n        # but cookie_parser() should parse whitespace the same way\n        # document.cookie parses whitespace.\n        (\"  =  b  ;  ;  =  ;   c  =  ;  \", {\"\": \"b\", \"c\": \"\"}),\n    ],\n)\ndef test_cookies_invalid(\n    set_cookie: str,\n    expected: dict[str, str],\n    test_client_factory: TestClientFactory,\n) -> None:\n    \"\"\"\n    Cookie strings that are against the RFC6265 spec but which browsers will send if set\n    via document.cookie.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        response = JSONResponse({\"cookies\": request.cookies})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"cookie\": set_cookie})\n    result = response.json()\n    assert result[\"cookies\"] == expected\n\n\ndef test_multiple_cookie_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        scope[\"headers\"] = [(b\"cookie\", b\"a=abc\"), (b\"cookie\", b\"b=def\"), (b\"cookie\", b\"c=ghi\")]\n        request = Request(scope, receive)\n        response = JSONResponse({\"cookies\": request.cookies})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    result = response.json()\n    assert result[\"cookies\"] == {\"a\": \"abc\", \"b\": \"def\", \"c\": \"ghi\"}\n\n\ndef test_chunked_encoding(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        body = await request.body()\n        response = JSONResponse({\"body\": body.decode()})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n\n    def post_body() -> Iterator[bytes]:\n        yield b\"foo\"\n        yield b\"bar\"\n\n    response = client.post(\"/\", data=post_body())  # type: ignore\n    assert response.json() == {\"body\": \"foobar\"}\n\n\ndef test_request_send_push_promise(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        # the server is push-enabled\n        scope[\"extensions\"][\"http.response.push\"] = {}\n\n        request = Request(scope, receive, send)\n        await request.send_push_promise(\"/style.css\")\n\n        response = JSONResponse({\"json\": \"OK\"})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() == {\"json\": \"OK\"}\n\n\ndef test_request_send_push_promise_without_push_extension(\n    test_client_factory: TestClientFactory,\n) -> None:\n    \"\"\"\n    If server does not support the `http.response.push` extension,\n    .send_push_promise() does nothing.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope)\n        await request.send_push_promise(\"/style.css\")\n\n        response = JSONResponse({\"json\": \"OK\"})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() == {\"json\": \"OK\"}\n\n\ndef test_request_send_push_promise_without_setting_send(\n    test_client_factory: TestClientFactory,\n) -> None:\n    \"\"\"\n    If Request is instantiated without the send channel, then\n    .send_push_promise() is not available.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        # the server is push-enabled\n        scope[\"extensions\"][\"http.response.push\"] = {}\n\n        data = \"OK\"\n        request = Request(scope)\n        try:\n            await request.send_push_promise(\"/style.css\")\n        except RuntimeError:\n            data = \"Send channel not available\"\n        response = JSONResponse({\"json\": data})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() == {\"json\": \"Send channel not available\"}\n\n\n@pytest.mark.parametrize(\n    \"messages\",\n    [\n        [{\"body\": b\"123\", \"more_body\": True}, {\"body\": b\"\"}],\n        [{\"body\": b\"\", \"more_body\": True}, {\"body\": b\"123\"}],\n        [{\"body\": b\"12\", \"more_body\": True}, {\"body\": b\"3\"}],\n        [\n            {\"body\": b\"123\", \"more_body\": True},\n            {\"body\": b\"\", \"more_body\": True},\n            {\"body\": b\"\"},\n        ],\n    ],\n)\n@pytest.mark.anyio\nasync def test_request_rcv(messages: list[Message]) -> None:\n    messages = messages.copy()\n\n    async def rcv() -> Message:\n        return {\"type\": \"http.request\", **messages.pop(0)}\n\n    request = Request({\"type\": \"http\"}, rcv)\n\n    body = await request.body()\n\n    assert body == b\"123\"\n\n\n@pytest.mark.anyio\nasync def test_request_stream_called_twice() -> None:\n    messages: list[Message] = [\n        {\"type\": \"http.request\", \"body\": b\"1\", \"more_body\": True},\n        {\"type\": \"http.request\", \"body\": b\"2\", \"more_body\": True},\n        {\"type\": \"http.request\", \"body\": b\"3\"},\n    ]\n\n    async def rcv() -> Message:\n        return messages.pop(0)\n\n    request = Request({\"type\": \"http\"}, rcv)\n\n    s1 = request.stream()\n    s2 = request.stream()\n\n    msg = await s1.__anext__()\n    assert msg == b\"1\"\n\n    msg = await s2.__anext__()\n    assert msg == b\"2\"\n\n    msg = await s1.__anext__()\n    assert msg == b\"3\"\n\n    # at this point we've consumed the entire body\n    # so we should not wait for more body (which would hang us forever)\n    msg = await s1.__anext__()\n    assert msg == b\"\"\n    msg = await s2.__anext__()\n    assert msg == b\"\"\n\n    # and now both streams are exhausted\n    with pytest.raises(StopAsyncIteration):\n        assert await s2.__anext__()\n    with pytest.raises(StopAsyncIteration):\n        await s1.__anext__()\n\n\ndef test_request_url_outside_starlette_context(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        request.url_for(\"index\")\n\n    client = test_client_factory(app)\n    with pytest.raises(\n        RuntimeError,\n        match=\"The `url_for` method can only be used inside a Starlette application or with a router.\",\n    ):\n        client.get(\"/\")\n\n\ndef test_request_url_starlette_context(test_client_factory: TestClientFactory) -> None:\n    from starlette.applications import Starlette\n    from starlette.middleware import Middleware\n    from starlette.routing import Route\n    from starlette.types import ASGIApp\n\n    url_for = None\n\n    async def homepage(request: Request) -> Response:\n        return PlainTextResponse(\"Hello, world!\")\n\n    class CustomMiddleware:\n        def __init__(self, app: ASGIApp) -> None:\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            nonlocal url_for\n            request = Request(scope, receive)\n            url_for = request.url_for(\"homepage\")\n            await self.app(scope, receive, send)\n\n    app = Starlette(routes=[Route(\"/home\", homepage)], middleware=[Middleware(CustomMiddleware)])\n\n    client = test_client_factory(app)\n    client.get(\"/home\")\n    assert url_for == URL(\"http://testserver/home\")\n"
  },
  {
    "path": "tests/test_responses.py",
    "content": "from __future__ import annotations\n\nimport datetime as dt\nimport sys\nimport time\nfrom collections.abc import AsyncGenerator, AsyncIterator, Iterator\nfrom dataclasses import dataclass\nfrom http.cookies import SimpleCookie\nfrom pathlib import Path\nfrom typing import Any\n\nimport anyio\nimport pytest\nfrom python_multipart import MultipartParser\n\nfrom starlette import status\nfrom starlette.background import BackgroundTask\nfrom starlette.datastructures import Headers\nfrom starlette.requests import ClientDisconnect, Request\nfrom starlette.responses import FileResponse, JSONResponse, RedirectResponse, Response, StreamingResponse\nfrom starlette.testclient import TestClient\nfrom starlette.types import Message, Receive, Scope, Send\nfrom tests.types import TestClientFactory\n\n\ndef test_text_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"hello, world\", media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"hello, world\"\n\n\ndef test_bytes_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(b\"xxxxx\", media_type=\"image/png\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.content == b\"xxxxx\"\n\n\ndef test_json_none_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = JSONResponse(None)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() is None\n    assert response.content == b\"null\"\n\n\ndef test_redirect_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"path\"] == \"/\":\n            response = Response(\"hello, world\", media_type=\"text/plain\")\n        else:\n            response = RedirectResponse(\"/\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/redirect\")\n    assert response.text == \"hello, world\"\n    assert response.url == \"http://testserver/\"\n\n\ndef test_quoting_redirect_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"path\"] == \"/I ♥ Starlette/\":\n            response = Response(\"hello, world\", media_type=\"text/plain\")\n        else:\n            response = RedirectResponse(\"/I ♥ Starlette/\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/redirect\")\n    assert response.text == \"hello, world\"\n    assert response.url == \"http://testserver/I%20%E2%99%A5%20Starlette/\"\n\n\ndef test_redirect_response_content_length_header(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        if scope[\"path\"] == \"/\":\n            response = Response(\"hello\", media_type=\"text/plain\")  # pragma: no cover\n        else:\n            response = RedirectResponse(\"/\")\n        await response(scope, receive, send)\n\n    client: TestClient = test_client_factory(app)\n    response = client.request(\"GET\", \"/redirect\", follow_redirects=False)\n    assert response.url == \"http://testserver/redirect\"\n    assert response.headers[\"content-length\"] == \"0\"\n\n\ndef test_streaming_response(test_client_factory: TestClientFactory) -> None:\n    filled_by_bg_task = \"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        async def numbers(minimum: int, maximum: int) -> AsyncIterator[str]:\n            for i in range(minimum, maximum + 1):\n                yield str(i)\n                if i != maximum:\n                    yield \", \"\n                await anyio.sleep(0)\n\n        async def numbers_for_cleanup(start: int = 1, stop: int = 5) -> None:\n            nonlocal filled_by_bg_task\n            async for thing in numbers(start, stop):\n                filled_by_bg_task = filled_by_bg_task + thing\n\n        cleanup_task = BackgroundTask(numbers_for_cleanup, start=6, stop=9)\n        generator = numbers(1, 5)\n        response = StreamingResponse(generator, media_type=\"text/plain\", background=cleanup_task)\n        await response(scope, receive, send)\n\n    assert filled_by_bg_task == \"\"\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"1, 2, 3, 4, 5\"\n    assert filled_by_bg_task == \"6, 7, 8, 9\"\n\n\ndef test_streaming_response_custom_iterator(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        class CustomAsyncIterator:\n            def __init__(self) -> None:\n                self._called = 0\n\n            def __aiter__(self) -> AsyncIterator[str]:\n                return self\n\n            async def __anext__(self) -> str:\n                if self._called == 5:\n                    raise StopAsyncIteration()\n                self._called += 1\n                return str(self._called)\n\n        response = StreamingResponse(CustomAsyncIterator(), media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"12345\"\n\n\ndef test_streaming_response_custom_iterable(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        class CustomAsyncIterable:\n            async def __aiter__(self) -> AsyncIterator[str | bytes]:\n                for i in range(5):\n                    yield str(i + 1)\n\n        response = StreamingResponse(CustomAsyncIterable(), media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"12345\"\n\n\ndef test_sync_streaming_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        def numbers(minimum: int, maximum: int) -> Iterator[str]:\n            for i in range(minimum, maximum + 1):\n                yield str(i)\n                if i != maximum:\n                    yield \", \"\n\n        generator = numbers(1, 5)\n        response = StreamingResponse(generator, media_type=\"text/plain\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"1, 2, 3, 4, 5\"\n\n\ndef test_response_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        headers = {\"x-header-1\": \"123\", \"x-header-2\": \"456\"}\n        response = Response(\"hello, world\", media_type=\"text/plain\", headers=headers)\n        response.headers[\"x-header-2\"] = \"789\"\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"x-header-1\"] == \"123\"\n    assert response.headers[\"x-header-2\"] == \"789\"\n\n\ndef test_response_phrase(test_client_factory: TestClientFactory) -> None:\n    app = Response(status_code=204)\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.reason_phrase == \"No Content\"\n\n    app = Response(b\"\", status_code=123)\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.reason_phrase == \"\"\n\n\ndef test_file_response(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"xyz\"\n    content = b\"<file content>\" * 1000\n    path.write_bytes(content)\n\n    filled_by_bg_task = \"\"\n\n    async def numbers(minimum: int, maximum: int) -> AsyncIterator[str]:\n        for i in range(minimum, maximum + 1):\n            yield str(i)\n            if i != maximum:\n                yield \", \"\n            await anyio.sleep(0)\n\n    async def numbers_for_cleanup(start: int = 1, stop: int = 5) -> None:\n        nonlocal filled_by_bg_task\n        async for thing in numbers(start, stop):\n            filled_by_bg_task = filled_by_bg_task + thing\n\n    cleanup_task = BackgroundTask(numbers_for_cleanup, start=6, stop=9)\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = FileResponse(path=path, filename=\"example.png\", background=cleanup_task)\n        await response(scope, receive, send)\n\n    assert filled_by_bg_task == \"\"\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    expected_disposition = 'attachment; filename=\"example.png\"'\n    assert response.status_code == status.HTTP_200_OK\n    assert response.content == content\n    assert response.headers[\"content-type\"] == \"image/png\"\n    assert response.headers[\"content-disposition\"] == expected_disposition\n    assert \"content-length\" in response.headers\n    assert \"last-modified\" in response.headers\n    assert \"etag\" in response.headers\n    assert filled_by_bg_task == \"6, 7, 8, 9\"\n\n\n@pytest.mark.anyio\nasync def test_file_response_on_head_method(tmp_path: Path) -> None:\n    path = tmp_path / \"xyz\"\n    content = b\"<file content>\" * 1000\n    path.write_bytes(content)\n\n    app = FileResponse(path=path, filename=\"example.png\")\n\n    async def receive() -> Message:  # type: ignore[empty-body]\n        ...  # pragma: no cover\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.start\":\n            assert message[\"status\"] == status.HTTP_200_OK\n            headers = Headers(raw=message[\"headers\"])\n            assert headers[\"content-type\"] == \"image/png\"\n            assert \"content-length\" in headers\n            assert \"content-disposition\" in headers\n            assert \"last-modified\" in headers\n            assert \"etag\" in headers\n        elif message[\"type\"] == \"http.response.body\":  # pragma: no branch\n            assert message[\"body\"] == b\"\"\n            assert message[\"more_body\"] is False\n\n    # Since the TestClient drops the response body on HEAD requests, we need to test\n    # this directly.\n    await app({\"type\": \"http\", \"method\": \"head\", \"headers\": [(b\"key\", b\"value\")]}, receive, send)\n\n\ndef test_file_response_set_media_type(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"xyz\"\n    path.write_bytes(b\"<file content>\")\n\n    # By default, FileResponse will determine the `content-type` based on\n    # the filename or path, unless a specific `media_type` is provided.\n    app = FileResponse(path=path, filename=\"example.png\", media_type=\"image/jpeg\")\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"content-type\"] == \"image/jpeg\"\n\n\ndef test_file_response_with_directory_raises_error(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    app = FileResponse(path=tmp_path, filename=\"example.png\")\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError) as exc_info:\n        client.get(\"/\")\n    assert \"is not a file\" in str(exc_info.value)\n\n\ndef test_file_response_with_missing_file_raises_error(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"404.txt\"\n    app = FileResponse(path=path, filename=\"404.txt\")\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError) as exc_info:\n        client.get(\"/\")\n    assert \"does not exist\" in str(exc_info.value)\n\n\ndef test_file_response_with_chinese_filename(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    content = b\"file content\"\n    filename = \"你好.txt\"  # probably \"Hello.txt\" in Chinese\n    path = tmp_path / filename\n    path.write_bytes(content)\n    app = FileResponse(path=path, filename=filename)\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    expected_disposition = \"attachment; filename*=utf-8''%E4%BD%A0%E5%A5%BD.txt\"\n    assert response.status_code == status.HTTP_200_OK\n    assert response.content == content\n    assert response.headers[\"content-disposition\"] == expected_disposition\n\n\ndef test_file_response_with_inline_disposition(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    content = b\"file content\"\n    filename = \"hello.txt\"\n    path = tmp_path / filename\n    path.write_bytes(content)\n    app = FileResponse(path=path, filename=filename, content_disposition_type=\"inline\")\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    expected_disposition = 'inline; filename=\"hello.txt\"'\n    assert response.status_code == status.HTTP_200_OK\n    assert response.content == content\n    assert response.headers[\"content-disposition\"] == expected_disposition\n\n\ndef test_file_response_with_range_header(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    content = b\"file content\"\n    filename = \"hello.txt\"\n    path = tmp_path / filename\n    path.write_bytes(content)\n    etag = '\"a_non_autogenerated_etag\"'\n    app = FileResponse(path=path, filename=filename, headers={\"etag\": etag})\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers={\"range\": \"bytes=0-4\", \"if-range\": etag})\n    assert response.status_code == status.HTTP_206_PARTIAL_CONTENT\n    assert response.content == content[:5]\n    assert response.headers[\"etag\"] == etag\n    assert response.headers[\"content-length\"] == \"5\"\n    assert response.headers[\"content-range\"] == f\"bytes 0-4/{len(content)}\"\n\n\n@pytest.mark.anyio\nasync def test_file_response_with_pathsend(tmpdir: Path) -> None:\n    path = tmpdir / \"xyz\"\n    content = b\"<file content>\" * 1000\n    with open(path, \"wb\") as file:\n        file.write(content)\n\n    app = FileResponse(path=path, filename=\"example.png\")\n\n    async def receive() -> Message:  # type: ignore[empty-body]\n        ...  # pragma: no cover\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.start\":\n            assert message[\"status\"] == status.HTTP_200_OK\n            headers = Headers(raw=message[\"headers\"])\n            assert headers[\"content-type\"] == \"image/png\"\n            assert \"content-length\" in headers\n            assert \"content-disposition\" in headers\n            assert \"last-modified\" in headers\n            assert \"etag\" in headers\n        elif message[\"type\"] == \"http.response.pathsend\":  # pragma: no branch\n            assert message[\"path\"] == str(path)\n\n    # Since the TestClient doesn't support `pathsend`, we need to test this directly.\n    await app(\n        {\"type\": \"http\", \"method\": \"get\", \"headers\": [], \"extensions\": {\"http.response.pathsend\": {}}},\n        receive,\n        send,\n    )\n\n\ndef test_set_cookie(test_client_factory: TestClientFactory, monkeypatch: pytest.MonkeyPatch) -> None:\n    # Mock time used as a reference for `Expires` by stdlib `SimpleCookie`.\n    mocked_now = dt.datetime(2037, 1, 22, 12, 0, 0, tzinfo=dt.timezone.utc)\n    monkeypatch.setattr(time, \"time\", lambda: mocked_now.timestamp())\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        response.set_cookie(\n            \"mycookie\",\n            \"myvalue\",\n            max_age=10,\n            expires=10,\n            path=\"/\",\n            domain=\"localhost\",\n            secure=True,\n            httponly=True,\n            samesite=\"none\",\n            partitioned=True if sys.version_info >= (3, 14) else False,\n        )\n        await response(scope, receive, send)\n\n    partitioned_text = \"Partitioned; \" if sys.version_info >= (3, 14) else \"\"\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n    assert (\n        response.headers[\"set-cookie\"] == \"mycookie=myvalue; Domain=localhost; expires=Thu, 22 Jan 2037 12:00:10 GMT; \"\n        f\"HttpOnly; Max-Age=10; {partitioned_text}Path=/; SameSite=none; Secure\"\n    )\n\n\n@pytest.mark.skipif(sys.version_info >= (3, 14), reason=\"Only relevant for <3.14\")\ndef test_set_cookie_raises_for_invalid_python_version(\n    test_client_factory: TestClientFactory,\n) -> None:  # pragma: no cover\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        with pytest.raises(ValueError):\n            response.set_cookie(\"mycookie\", \"myvalue\", partitioned=True)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n    assert response.headers.get(\"set-cookie\") is None\n\n\ndef test_set_cookie_path_none(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        response.set_cookie(\"mycookie\", \"myvalue\", path=None)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n    assert response.headers[\"set-cookie\"] == \"mycookie=myvalue; SameSite=lax\"\n\n\ndef test_set_cookie_samesite_none(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        response.set_cookie(\"mycookie\", \"myvalue\", samesite=None)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n    assert response.headers[\"set-cookie\"] == \"mycookie=myvalue; Path=/\"\n\n\n@pytest.mark.parametrize(\n    \"expires\",\n    [\n        pytest.param(dt.datetime(2037, 1, 22, 12, 0, 10, tzinfo=dt.timezone.utc), id=\"datetime\"),\n        pytest.param(\"Thu, 22 Jan 2037 12:00:10 GMT\", id=\"str\"),\n        pytest.param(10, id=\"int\"),\n    ],\n)\ndef test_expires_on_set_cookie(\n    test_client_factory: TestClientFactory,\n    monkeypatch: pytest.MonkeyPatch,\n    expires: str,\n) -> None:\n    # Mock time used as a reference for `Expires` by stdlib `SimpleCookie`.\n    mocked_now = dt.datetime(2037, 1, 22, 12, 0, 0, tzinfo=dt.timezone.utc)\n    monkeypatch.setattr(time, \"time\", lambda: mocked_now.timestamp())\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        response.set_cookie(\"mycookie\", \"myvalue\", expires=expires)\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    cookie = SimpleCookie(response.headers.get(\"set-cookie\"))\n    assert cookie[\"mycookie\"][\"expires\"] == \"Thu, 22 Jan 2037 12:00:10 GMT\"\n\n\ndef test_delete_cookie(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        request = Request(scope, receive)\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        if request.cookies.get(\"mycookie\"):\n            response.delete_cookie(\"mycookie\")\n        else:\n            response.set_cookie(\"mycookie\", \"myvalue\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.cookies[\"mycookie\"]\n    response = client.get(\"/\")\n    assert not response.cookies.get(\"mycookie\")\n\n\ndef test_populate_headers(test_client_factory: TestClientFactory) -> None:\n    app = Response(content=\"hi\", headers={}, media_type=\"text/html\")\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"hi\"\n    assert response.headers[\"content-length\"] == \"2\"\n    assert response.headers[\"content-type\"] == \"text/html; charset=utf-8\"\n\n\ndef test_head_method(test_client_factory: TestClientFactory) -> None:\n    app = Response(\"hello, world\", media_type=\"text/plain\")\n    client = test_client_factory(app)\n    response = client.head(\"/\")\n    assert response.text == \"\"\n\n\ndef test_empty_response(test_client_factory: TestClientFactory) -> None:\n    app = Response()\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.content == b\"\"\n    assert response.headers[\"content-length\"] == \"0\"\n    assert \"content-type\" not in response.headers\n\n\ndef test_empty_204_response(test_client_factory: TestClientFactory) -> None:\n    app = Response(status_code=204)\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert \"content-length\" not in response.headers\n\n\ndef test_non_empty_response(test_client_factory: TestClientFactory) -> None:\n    app = Response(content=\"hi\")\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"content-length\"] == \"2\"\n\n\ndef test_response_do_not_add_redundant_charset(\n    test_client_factory: TestClientFactory,\n) -> None:\n    app = Response(media_type=\"text/plain; charset=utf-8\")\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n\n\ndef test_file_response_known_size(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"xyz\"\n    content = b\"<file content>\" * 1000\n    path.write_bytes(content)\n\n    app = FileResponse(path=path, filename=\"example.png\")\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"content-length\"] == str(len(content))\n\n\ndef test_streaming_response_unknown_size(\n    test_client_factory: TestClientFactory,\n) -> None:\n    app = StreamingResponse(content=iter([\"hello\", \"world\"]))\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert \"content-length\" not in response.headers\n\n\ndef test_streaming_response_known_size(test_client_factory: TestClientFactory) -> None:\n    app = StreamingResponse(content=iter([\"hello\", \"world\"]), headers={\"content-length\": \"10\"})\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.headers[\"content-length\"] == \"10\"\n\n\ndef test_response_memoryview(test_client_factory: TestClientFactory) -> None:\n    app = Response(content=memoryview(b\"\\xc0\"))\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.content == b\"\\xc0\"\n\n\ndef test_streaming_response_memoryview(test_client_factory: TestClientFactory) -> None:\n    app = StreamingResponse(content=iter([memoryview(b\"\\xc0\"), memoryview(b\"\\xf5\")]))\n    client: TestClient = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.content == b\"\\xc0\\xf5\"\n\n\n@pytest.mark.anyio\nasync def test_streaming_response_stops_if_receiving_http_disconnect() -> None:\n    streamed = 0\n\n    disconnected = anyio.Event()\n\n    async def receive_disconnect() -> Message:\n        await disconnected.wait()\n        return {\"type\": \"http.disconnect\"}\n\n    async def send(message: Message) -> None:\n        nonlocal streamed\n        if message[\"type\"] == \"http.response.body\":\n            streamed += len(message.get(\"body\", b\"\"))\n            # Simulate disconnection after download has started\n            if streamed >= 16:\n                disconnected.set()\n\n    async def stream_indefinitely() -> AsyncIterator[bytes]:\n        while True:\n            # Need a sleep for the event loop to switch to another task\n            await anyio.sleep(0)\n            yield b\"chunk \"\n\n    response = StreamingResponse(content=stream_indefinitely())\n\n    with anyio.move_on_after(1) as cancel_scope:\n        await response({\"type\": \"http\"}, receive_disconnect, send)\n    assert not cancel_scope.cancel_called, \"Content streaming should stop itself.\"\n\n\n@pytest.mark.anyio\nasync def test_streaming_response_on_client_disconnects() -> None:\n    chunks = bytearray()\n    streamed = False\n\n    async def receive_disconnect() -> Message:\n        raise NotImplementedError\n\n    async def send(message: Message) -> None:\n        nonlocal streamed\n        if message[\"type\"] == \"http.response.body\":\n            if not streamed:\n                chunks.extend(message.get(\"body\", b\"\"))\n                streamed = True\n            else:\n                raise OSError\n\n    async def stream_indefinitely() -> AsyncGenerator[bytes, None]:\n        while True:\n            await anyio.sleep(0)\n            yield b\"chunk\"\n\n    stream = stream_indefinitely()\n    response = StreamingResponse(content=stream)\n\n    with anyio.move_on_after(1) as cancel_scope:\n        with pytest.raises(ClientDisconnect):\n            await response({\"type\": \"http\", \"asgi\": {\"spec_version\": \"2.4\"}}, receive_disconnect, send)\n    assert not cancel_scope.cancel_called, \"Content streaming should stop itself.\"\n    assert chunks == b\"chunk\"\n    await stream.aclose()\n\n\n@pytest.mark.anyio\nasync def test_streaming_response_runs_background_on_websocket_scope() -> None:\n    background_called = False\n    sent: list[Message] = []\n\n    async def receive() -> Message:\n        return {}  # pragma: no cover\n\n    async def send(message: Message) -> None:\n        sent.append(message)\n\n    def run_background() -> None:\n        nonlocal background_called\n        background_called = True\n\n    async def stream() -> AsyncIterator[bytes]:\n        yield b\"chunk\"\n\n    response = StreamingResponse(stream(), background=BackgroundTask(run_background))\n\n    await response({\"type\": \"websocket\"}, receive, send)\n\n    assert background_called\n    assert [message[\"type\"] for message in sent] == [\n        \"websocket.http.response.start\",\n        \"websocket.http.response.body\",\n        \"websocket.http.response.body\",\n    ]\n\n\nREADME = \"\"\"\\\n# BáiZé\n\nPowerful and exquisite WSGI/ASGI framework/toolkit.\n\nThe minimize implementation of methods required in the Web framework. No redundant implementation means that you can freely customize functions without considering the conflict with baize's own implementation.\n\nUnder the ASGI/WSGI protocol, the interface of the request object and the response object is almost the same, only need to add or delete `await` in the appropriate place. In addition, it should be noted that ASGI supports WebSocket but WSGI does not.\n\"\"\"  # noqa: E501\n\n\n@pytest.fixture\ndef readme_file(tmp_path: Path) -> Path:\n    filepath = tmp_path / \"README.txt\"\n    filepath.write_bytes(README.encode(\"utf8\"))\n    return filepath\n\n\n@pytest.fixture\ndef file_response_client(readme_file: Path, test_client_factory: TestClientFactory) -> TestClient:\n    return test_client_factory(app=FileResponse(str(readme_file)))\n\n\ndef test_file_response_without_range(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\")\n    assert response.status_code == 200\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-length\"] == str(len(README.encode(\"utf8\")))\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n    assert response.text == README\n\n\ndef test_file_response_head(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\")\n    assert response.status_code == 200\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-length\"] == str(len(README.encode(\"utf8\")))\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n    assert response.content == b\"\"\n\n\ndef test_file_response_range(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=0-100\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-range\"] == f\"bytes 0-100/{len(README.encode('utf8'))}\"\n    assert response.headers[\"content-length\"] == \"101\"\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n    assert response.content == README.encode(\"utf8\")[:101]\n\n\ndef test_file_response_range_head(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\", headers={\"Range\": \"bytes=0-100\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-range\"] == f\"bytes 0-100/{len(README.encode('utf8'))}\"\n    assert response.headers[\"content-length\"] == str(101)\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n    assert response.content == b\"\"\n\n\ndef test_file_response_range_multi(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=0-100, 200-300\"})\n    assert response.status_code == 206\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-length\"] == \"448\"\n    assert response.headers[\"content-type\"].startswith(\"multipart/byteranges; boundary=\")\n\n\ndef test_file_response_range_multi_head(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\", headers={\"Range\": \"bytes=0-100, 200-300\"})\n    assert response.status_code == 206\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-length\"] == \"448\"\n    assert response.headers[\"content-type\"].startswith(\"multipart/byteranges; boundary=\")\n    assert response.content == b\"\"\n\n    response = file_response_client.head(\n        \"/\",\n        headers={\"Range\": \"bytes=200-300\", \"if-range\": response.headers[\"etag\"][:-1]},\n    )\n    assert response.status_code == 200\n    response = file_response_client.head(\n        \"/\",\n        headers={\"Range\": \"bytes=200-300\", \"if-range\": response.headers[\"etag\"]},\n    )\n    assert response.status_code == 206\n\n\ndef test_file_response_range_invalid(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\", headers={\"Range\": \"bytes: 0-1000\"})\n    assert response.status_code == 400\n\n\ndef test_file_response_range_head_max(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\", headers={\"Range\": f\"bytes=0-{len(README.encode('utf8')) + 1}\"})\n    assert response.status_code == 206\n\n\ndef test_file_response_range_416(file_response_client: TestClient) -> None:\n    response = file_response_client.head(\"/\", headers={\"Range\": f\"bytes={len(README.encode('utf8')) + 1}-\"})\n    assert response.status_code == 416\n    assert response.headers[\"Content-Range\"] == f\"bytes */{len(README.encode('utf8'))}\"\n\n\ndef test_file_response_only_support_bytes_range(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"items=0-100\"})\n    assert response.status_code == 400\n    assert response.text == \"Only support bytes range\"\n\n\ndef test_file_response_range_must_be_requested(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=\"})\n    assert response.status_code == 400\n    assert response.text == \"Range header: range must be requested\"\n\n\ndef test_file_response_start_must_be_less_than_end(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=100-0\"})\n    assert response.status_code == 400\n    assert response.text == \"Range header: start must be less than end\"\n\n\ndef test_file_response_merge_ranges(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=0-100, 50-200\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-length\"] == \"201\"\n    assert response.headers[\"content-range\"] == f\"bytes 0-200/{len(README.encode('utf8'))}\"\n\n\n@dataclass\nclass MultipartPart:\n    headers: dict[bytes, bytes]\n    data: bytes\n\n\ndef parse_multipart_data(data: bytes, boundary: bytes | str) -> list[MultipartPart]:\n    parts: list[MultipartPart] = []\n    done = False\n\n    current_headers: dict[bytes, bytes] = {}\n    current_header_field: bytes = b\"\"\n\n    def on_part_begin() -> None:\n        nonlocal current_headers\n        current_headers = {}\n\n    def on_part_data(data: bytes, start: int, end: int) -> None:\n        parts.append(MultipartPart(current_headers, data[start:end]))\n\n    def on_header_field(data: bytes, start: int, end: int) -> None:\n        nonlocal current_header_field\n        current_header_field = data[start:end]\n\n    def on_header_value(data: bytes, start: int, end: int) -> None:\n        current_headers[current_header_field] = data[start:end]\n\n    def on_end() -> None:\n        nonlocal done\n        done = True\n\n    parser = MultipartParser(\n        boundary,\n        dict(\n            on_part_begin=on_part_begin,\n            on_part_data=on_part_data,\n            on_header_field=on_header_field,\n            on_header_value=on_header_value,\n            on_end=on_end,\n        ),\n    )\n    parser.write(data)\n    parser.finalize()\n    assert done\n    return parts\n\n\ndef test_file_response_insert_ranges(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=100-200, 0-50\"})\n\n    assert response.status_code == 206\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-type\"].startswith(\"multipart/byteranges; boundary=\")\n    boundary = response.headers[\"content-type\"].split(\"boundary=\")[1]\n    assert response.text.splitlines() == [\n        f\"--{boundary}\",\n        \"Content-Type: text/plain; charset=utf-8\",\n        \"Content-Range: bytes 0-50/526\",\n        \"\",\n        \"# BáiZé\",\n        \"\",\n        \"Powerful and exquisite WSGI/ASGI framewo\",\n        f\"--{boundary}\",\n        \"Content-Type: text/plain; charset=utf-8\",\n        \"Content-Range: bytes 100-200/526\",\n        \"\",\n        \"ds required in the Web framework. No redundant implementation means that you can freely customize fun\",\n        f\"--{boundary}--\",\n    ]\n\n    parts = parse_multipart_data(response._content, boundary)\n    assert all(\n        value == b\"text/plain; charset=utf-8\"\n        for part in parts\n        for key, value in part.headers.items()\n        if key == b\"Content-Type\"\n    )\n    assert len(parts) == 2\n    assert parts[0].headers[b\"Content-Range\"] == b\"bytes 0-50/526\"\n    assert parts[0].data == \"# BáiZé\\n\\nPowerful and exquisite WSGI/ASGI framewo\".encode()\n    assert parts[1].headers[b\"Content-Range\"] == b\"bytes 100-200/526\"\n    assert (\n        parts[1].data\n        == b\"ds required in the Web framework. No redundant implementation means that you can freely customize fun\"\n    )\n\n\ndef test_file_response_range_without_dash(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=100, 0-50\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-range\"] == f\"bytes 0-50/{len(README.encode('utf8'))}\"\n\n\ndef test_file_response_range_empty_start_and_end(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes= - , 0-50\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-range\"] == f\"bytes 0-50/{len(README.encode('utf8'))}\"\n\n\ndef test_file_response_range_ignore_non_numeric(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=abc-def, 0-50\"})\n    assert response.status_code == 206\n    assert response.headers[\"content-range\"] == f\"bytes 0-50/{len(README.encode('utf8'))}\"\n\n\ndef test_file_response_suffix_range(file_response_client: TestClient) -> None:\n    # Test suffix range (last N bytes) - line 523 with empty start_str\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=-100\"})\n    assert response.status_code == 206\n    file_size = len(README.encode(\"utf8\"))\n    assert response.headers[\"content-range\"] == f\"bytes {file_size - 100}-{file_size - 1}/{file_size}\"\n    assert response.headers[\"content-length\"] == \"100\"\n    assert response.content == README.encode(\"utf8\")[-100:]\n\n\ndef test_file_response_multiple_calls(file_response_client: TestClient) -> None:\n    response = file_response_client.get(\"/\", headers={\"Range\": \"bytes=0-100\"})\n    assert response.status_code == 206\n\n    response = file_response_client.get(\"/\")\n    assert response.status_code == 200\n    assert \"content-range\" not in response.headers\n    assert response.headers[\"content-length\"] == str(len(README.encode(\"utf8\")))\n    assert response.headers[\"content-type\"] == \"text/plain; charset=utf-8\"\n\n\n@pytest.mark.anyio\nasync def test_file_response_multi_small_chunk_size(readme_file: Path) -> None:\n    class SmallChunkSizeFileResponse(FileResponse):\n        chunk_size = 10\n\n    app = SmallChunkSizeFileResponse(path=str(readme_file))\n\n    received_chunks: list[bytes] = []\n    start_message: dict[str, Any] = {}\n\n    async def receive() -> Message:\n        raise NotImplementedError(\"Should not be called!\")\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"http.response.start\":\n            start_message.update(message)\n        elif message[\"type\"] == \"http.response.body\":  # pragma: no branch\n            received_chunks.append(message[\"body\"])\n\n    await app({\"type\": \"http\", \"method\": \"get\", \"headers\": [(b\"range\", b\"bytes=0-15,20-35,35-50\")]}, receive, send)\n    assert start_message[\"status\"] == 206\n\n    headers = Headers(raw=start_message[\"headers\"])\n    assert \"content-range\" not in headers\n    assert headers.get(\"accept-ranges\") == \"bytes\"\n    assert \"content-length\" in headers\n    assert \"last-modified\" in headers\n    assert \"etag\" in headers\n    assert headers[\"content-type\"].startswith(\"multipart/byteranges; boundary=\")\n    boundary = headers[\"content-type\"].split(\"boundary=\")[1]\n\n    assert received_chunks == [\n        # Send the part headers.\n        f\"--{boundary}\\r\\nContent-Type: text/plain; charset=utf-8\\r\\nContent-Range: bytes 0-15/526\\r\\n\\r\\n\".encode(),\n        # Send the first chunk (10 bytes).\n        b\"# B\\xc3\\xa1iZ\\xc3\\xa9\\n\",\n        # Send the second chunk (6 bytes).\n        b\"\\nPower\",\n        # Send the new line to separate the parts.\n        b\"\\r\\n\",\n        # Send the part headers. We merge the ranges 20-35 and 35-50 into a single part.\n        f\"--{boundary}\\r\\nContent-Type: text/plain; charset=utf-8\\r\\nContent-Range: bytes 20-50/526\\r\\n\\r\\n\".encode(),\n        # Send the first chunk (10 bytes).\n        b\"and exquis\",\n        # Send the second chunk (10 bytes).\n        b\"ite WSGI/A\",\n        # Send the third chunk (10 bytes).\n        b\"SGI framew\",\n        # Send the last chunk (1 byte).\n        b\"o\",\n        b\"\\r\\n\",\n        f\"--{boundary}--\".encode(),\n    ]\n"
  },
  {
    "path": "tests/test_routing.py",
    "content": "from __future__ import annotations\n\nimport contextlib\nimport functools\nimport json\nimport uuid\nfrom collections.abc import AsyncGenerator, AsyncIterator, Callable, Generator\nfrom typing import TypedDict\n\nimport pytest\nfrom typing_extensions import Never\n\nfrom starlette.applications import Starlette\nfrom starlette.exceptions import HTTPException\nfrom starlette.middleware import Middleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, PlainTextResponse, Response\nfrom starlette.routing import Host, Mount, NoMatchFound, Route, Router, WebSocketRoute\nfrom starlette.testclient import TestClient\nfrom starlette.types import ASGIApp, Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket, WebSocketDisconnect\nfrom tests.types import TestClientFactory\n\n\ndef homepage(request: Request) -> Response:\n    return Response(\"Hello, world\", media_type=\"text/plain\")\n\n\ndef users(request: Request) -> Response:\n    return Response(\"All users\", media_type=\"text/plain\")\n\n\ndef user(request: Request) -> Response:\n    content = \"User \" + request.path_params[\"username\"]\n    return Response(content, media_type=\"text/plain\")\n\n\ndef user_me(request: Request) -> Response:\n    content = \"User fixed me\"\n    return Response(content, media_type=\"text/plain\")\n\n\ndef disable_user(request: Request) -> Response:\n    content = \"User \" + request.path_params[\"username\"] + \" disabled\"\n    return Response(content, media_type=\"text/plain\")\n\n\ndef user_no_match(request: Request) -> Response:  # pragma: no cover\n    content = \"User fixed no match\"\n    return Response(content, media_type=\"text/plain\")\n\n\nasync def partial_endpoint(arg: str, request: Request) -> JSONResponse:\n    return JSONResponse({\"arg\": arg})\n\n\nasync def partial_ws_endpoint(websocket: WebSocket) -> None:\n    await websocket.accept()\n    await websocket.send_json({\"url\": str(websocket.url)})\n    await websocket.close()\n\n\nclass PartialRoutes:\n    @classmethod\n    async def async_endpoint(cls, arg: str, request: Request) -> JSONResponse:\n        return JSONResponse({\"arg\": arg})\n\n    @classmethod\n    async def async_ws_endpoint(cls, websocket: WebSocket) -> None:\n        await websocket.accept()\n        await websocket.send_json({\"url\": str(websocket.url)})\n        await websocket.close()\n\n\ndef func_homepage(request: Request) -> Response:\n    return Response(\"Hello, world!\", media_type=\"text/plain\")\n\n\ndef contact(request: Request) -> Response:\n    return Response(\"Hello, POST!\", media_type=\"text/plain\")\n\n\ndef int_convertor(request: Request) -> JSONResponse:\n    number = request.path_params[\"param\"]\n    return JSONResponse({\"int\": number})\n\n\ndef float_convertor(request: Request) -> JSONResponse:\n    num = request.path_params[\"param\"]\n    return JSONResponse({\"float\": num})\n\n\ndef path_convertor(request: Request) -> JSONResponse:\n    path = request.path_params[\"param\"]\n    return JSONResponse({\"path\": path})\n\n\ndef uuid_converter(request: Request) -> JSONResponse:\n    uuid_param = request.path_params[\"param\"]\n    return JSONResponse({\"uuid\": str(uuid_param)})\n\n\ndef path_with_parentheses(request: Request) -> JSONResponse:\n    number = request.path_params[\"param\"]\n    return JSONResponse({\"int\": number})\n\n\nasync def websocket_endpoint(session: WebSocket) -> None:\n    await session.accept()\n    await session.send_text(\"Hello, world!\")\n    await session.close()\n\n\nasync def websocket_params(session: WebSocket) -> None:\n    await session.accept()\n    await session.send_text(f\"Hello, {session.path_params['room']}!\")\n    await session.close()\n\n\napp = Router(\n    [\n        Route(\"/\", endpoint=homepage, methods=[\"GET\"]),\n        Mount(\n            \"/users\",\n            routes=[\n                Route(\"/\", endpoint=users),\n                Route(\"/me\", endpoint=user_me),\n                Route(\"/{username}\", endpoint=user),\n                Route(\"/{username}:disable\", endpoint=disable_user, methods=[\"PUT\"]),\n                Route(\"/nomatch\", endpoint=user_no_match),\n            ],\n        ),\n        Mount(\n            \"/partial\",\n            routes=[\n                Route(\"/\", endpoint=functools.partial(partial_endpoint, \"foo\")),\n                Route(\n                    \"/cls\",\n                    endpoint=functools.partial(PartialRoutes.async_endpoint, \"foo\"),\n                ),\n                WebSocketRoute(\"/ws\", endpoint=functools.partial(partial_ws_endpoint)),\n                WebSocketRoute(\n                    \"/ws/cls\",\n                    endpoint=functools.partial(PartialRoutes.async_ws_endpoint),\n                ),\n            ],\n        ),\n        Mount(\"/static\", app=Response(\"xxxxx\", media_type=\"image/png\")),\n        Route(\"/func\", endpoint=func_homepage, methods=[\"GET\"]),\n        Route(\"/func\", endpoint=contact, methods=[\"POST\"]),\n        Route(\"/int/{param:int}\", endpoint=int_convertor, name=\"int-convertor\"),\n        Route(\"/float/{param:float}\", endpoint=float_convertor, name=\"float-convertor\"),\n        Route(\"/path/{param:path}\", endpoint=path_convertor, name=\"path-convertor\"),\n        Route(\"/uuid/{param:uuid}\", endpoint=uuid_converter, name=\"uuid-convertor\"),\n        # Route with chars that conflict with regex meta chars\n        Route(\n            \"/path-with-parentheses({param:int})\",\n            endpoint=path_with_parentheses,\n            name=\"path-with-parentheses\",\n        ),\n        WebSocketRoute(\"/ws\", endpoint=websocket_endpoint),\n        WebSocketRoute(\"/ws/{room}\", endpoint=websocket_params),\n    ]\n)\n\n\n@pytest.fixture\ndef client(\n    test_client_factory: TestClientFactory,\n) -> Generator[TestClient, None, None]:\n    with test_client_factory(app) as client:\n        yield client\n\n\n@pytest.mark.filterwarnings(\n    r\"ignore\"\n    r\":Trying to detect encoding from a tiny portion of \\(5\\) byte\\(s\\)\\.\"\n    r\":UserWarning\"\n    r\":charset_normalizer.api\"\n)\ndef test_router(client: TestClient) -> None:\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world\"\n\n    response = client.post(\"/\")\n    assert response.status_code == 405\n    assert response.text == \"Method Not Allowed\"\n    assert set(response.headers[\"allow\"].split(\", \")) == {\"HEAD\", \"GET\"}\n\n    response = client.get(\"/foo\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n    response = client.get(\"/users\")\n    assert response.status_code == 200\n    assert response.text == \"All users\"\n\n    response = client.get(\"/users/tomchristie\")\n    assert response.status_code == 200\n    assert response.text == \"User tomchristie\"\n\n    response = client.get(\"/users/me\")\n    assert response.status_code == 200\n    assert response.text == \"User fixed me\"\n\n    response = client.get(\"/users/tomchristie/\")\n    assert response.status_code == 200\n    assert response.url == \"http://testserver/users/tomchristie\"\n    assert response.text == \"User tomchristie\"\n\n    response = client.put(\"/users/tomchristie:disable\")\n    assert response.status_code == 200\n    assert response.url == \"http://testserver/users/tomchristie:disable\"\n    assert response.text == \"User tomchristie disabled\"\n\n    response = client.get(\"/users/nomatch\")\n    assert response.status_code == 200\n    assert response.text == \"User nomatch\"\n\n    response = client.get(\"/static/123\")\n    assert response.status_code == 200\n    assert response.text == \"xxxxx\"\n\n\ndef test_route_converters(client: TestClient) -> None:\n    # Test integer conversion\n    response = client.get(\"/int/5\")\n    assert response.status_code == 200\n    assert response.json() == {\"int\": 5}\n    assert app.url_path_for(\"int-convertor\", param=5) == \"/int/5\"\n\n    # Test path with parentheses\n    response = client.get(\"/path-with-parentheses(7)\")\n    assert response.status_code == 200\n    assert response.json() == {\"int\": 7}\n    assert app.url_path_for(\"path-with-parentheses\", param=7) == \"/path-with-parentheses(7)\"\n\n    # Test float conversion\n    response = client.get(\"/float/25.5\")\n    assert response.status_code == 200\n    assert response.json() == {\"float\": 25.5}\n    assert app.url_path_for(\"float-convertor\", param=25.5) == \"/float/25.5\"\n\n    # Test path conversion\n    response = client.get(\"/path/some/example\")\n    assert response.status_code == 200\n    assert response.json() == {\"path\": \"some/example\"}\n    assert app.url_path_for(\"path-convertor\", param=\"some/example\") == \"/path/some/example\"\n\n    # Test UUID conversion\n    response = client.get(\"/uuid/ec38df32-ceda-4cfa-9b4a-1aeb94ad551a\")\n    assert response.status_code == 200\n    assert response.json() == {\"uuid\": \"ec38df32-ceda-4cfa-9b4a-1aeb94ad551a\"}\n    assert (\n        app.url_path_for(\"uuid-convertor\", param=uuid.UUID(\"ec38df32-ceda-4cfa-9b4a-1aeb94ad551a\"))\n        == \"/uuid/ec38df32-ceda-4cfa-9b4a-1aeb94ad551a\"\n    )\n\n\ndef test_url_path_for() -> None:\n    assert app.url_path_for(\"homepage\") == \"/\"\n    assert app.url_path_for(\"user\", username=\"tomchristie\") == \"/users/tomchristie\"\n    assert app.url_path_for(\"websocket_endpoint\") == \"/ws\"\n    with pytest.raises(NoMatchFound, match='No route exists for name \"broken\" and params \"\".'):\n        assert app.url_path_for(\"broken\")\n    with pytest.raises(NoMatchFound, match='No route exists for name \"broken\" and params \"key, key2\".'):\n        assert app.url_path_for(\"broken\", key=\"value\", key2=\"value2\")\n    with pytest.raises(AssertionError):\n        app.url_path_for(\"user\", username=\"tom/christie\")\n    with pytest.raises(AssertionError):\n        app.url_path_for(\"user\", username=\"\")\n\n\ndef test_url_for() -> None:\n    assert app.url_path_for(\"homepage\").make_absolute_url(base_url=\"https://example.org\") == \"https://example.org/\"\n    assert (\n        app.url_path_for(\"homepage\").make_absolute_url(base_url=\"https://example.org/root_path/\")\n        == \"https://example.org/root_path/\"\n    )\n    assert (\n        app.url_path_for(\"user\", username=\"tomchristie\").make_absolute_url(base_url=\"https://example.org\")\n        == \"https://example.org/users/tomchristie\"\n    )\n    assert (\n        app.url_path_for(\"user\", username=\"tomchristie\").make_absolute_url(base_url=\"https://example.org/root_path/\")\n        == \"https://example.org/root_path/users/tomchristie\"\n    )\n    assert (\n        app.url_path_for(\"websocket_endpoint\").make_absolute_url(base_url=\"https://example.org\")\n        == \"wss://example.org/ws\"\n    )\n\n\ndef test_router_add_route(client: TestClient) -> None:\n    response = client.get(\"/func\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\ndef test_router_duplicate_path(client: TestClient) -> None:\n    response = client.post(\"/func\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, POST!\"\n\n\ndef test_router_add_websocket_route(client: TestClient) -> None:\n    with client.websocket_connect(\"/ws\") as session:\n        text = session.receive_text()\n        assert text == \"Hello, world!\"\n\n    with client.websocket_connect(\"/ws/test\") as session:\n        text = session.receive_text()\n        assert text == \"Hello, test!\"\n\n\ndef test_router_middleware(test_client_factory: TestClientFactory) -> None:\n    class CustomMiddleware:\n        def __init__(self, app: ASGIApp) -> None:\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            response = PlainTextResponse(\"OK\")\n            await response(scope, receive, send)\n\n    app = Router(\n        routes=[Route(\"/\", homepage)],\n        middleware=[Middleware(CustomMiddleware)],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"OK\"\n\n\ndef http_endpoint(request: Request) -> Response:\n    url = request.url_for(\"http_endpoint\")\n    return Response(f\"URL: {url}\", media_type=\"text/plain\")\n\n\nclass WebSocketEndpoint:\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope=scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.send_json({\"URL\": str(websocket.url_for(\"websocket_endpoint\"))})\n        await websocket.close()\n\n\nmixed_protocol_app = Router(\n    routes=[\n        Route(\"/\", endpoint=http_endpoint),\n        WebSocketRoute(\"/\", endpoint=WebSocketEndpoint(), name=\"websocket_endpoint\"),\n    ]\n)\n\n\ndef test_protocol_switch(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(mixed_protocol_app)\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"URL: http://testserver/\"\n\n    with client.websocket_connect(\"/\") as session:\n        assert session.receive_json() == {\"URL\": \"ws://testserver/\"}\n\n    with pytest.raises(WebSocketDisconnect):\n        with client.websocket_connect(\"/404\"):\n            pass  # pragma: no cover\n\n\nok = PlainTextResponse(\"OK\")\n\n\ndef test_mount_urls(test_client_factory: TestClientFactory) -> None:\n    mounted = Router([Mount(\"/users\", ok, name=\"users\")])\n    client = test_client_factory(mounted)\n    assert client.get(\"/users\").status_code == 200\n    assert client.get(\"/users\").url == \"http://testserver/users/\"\n    assert client.get(\"/users/\").status_code == 200\n    assert client.get(\"/users/a\").status_code == 200\n    assert client.get(\"/usersa\").status_code == 404\n\n\ndef test_reverse_mount_urls() -> None:\n    mounted = Router([Mount(\"/users\", ok, name=\"users\")])\n    assert mounted.url_path_for(\"users\", path=\"/a\") == \"/users/a\"\n\n    users = Router([Route(\"/{username}\", ok, name=\"user\")])\n    mounted = Router([Mount(\"/{subpath}/users\", users, name=\"users\")])\n    assert mounted.url_path_for(\"users:user\", subpath=\"test\", username=\"tom\") == \"/test/users/tom\"\n    assert mounted.url_path_for(\"users\", subpath=\"test\", path=\"/tom\") == \"/test/users/tom\"\n\n    mounted = Router([Mount(\"/users\", ok, name=\"users\")])\n    with pytest.raises(NoMatchFound):\n        mounted.url_path_for(\"users\", path=\"/a\", foo=\"bar\")\n\n    mounted = Router([Mount(\"/users\", ok, name=\"users\")])\n    with pytest.raises(NoMatchFound):\n        mounted.url_path_for(\"users\")\n\n\ndef test_mount_at_root(test_client_factory: TestClientFactory) -> None:\n    mounted = Router([Mount(\"/\", ok, name=\"users\")])\n    client = test_client_factory(mounted)\n    assert client.get(\"/\").status_code == 200\n\n\ndef users_api(request: Request) -> JSONResponse:\n    return JSONResponse({\"users\": [{\"username\": \"tom\"}]})\n\n\nmixed_hosts_app = Router(\n    routes=[\n        Host(\n            \"www.example.org\",\n            app=Router(\n                [\n                    Route(\"/\", homepage, name=\"homepage\"),\n                    Route(\"/users\", users, name=\"users\"),\n                ]\n            ),\n        ),\n        Host(\n            \"api.example.org\",\n            name=\"api\",\n            app=Router([Route(\"/users\", users_api, name=\"users\")]),\n        ),\n        Host(\n            \"port.example.org:3600\",\n            name=\"port\",\n            app=Router([Route(\"/\", homepage, name=\"homepage\")]),\n        ),\n    ]\n)\n\n\ndef test_host_routing(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(mixed_hosts_app, base_url=\"https://api.example.org/\")\n\n    response = client.get(\"/users\")\n    assert response.status_code == 200\n    assert response.json() == {\"users\": [{\"username\": \"tom\"}]}\n\n    response = client.get(\"/\")\n    assert response.status_code == 404\n\n    client = test_client_factory(mixed_hosts_app, base_url=\"https://www.example.org/\")\n\n    response = client.get(\"/users\")\n    assert response.status_code == 200\n    assert response.text == \"All users\"\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    client = test_client_factory(mixed_hosts_app, base_url=\"https://port.example.org:3600/\")\n\n    response = client.get(\"/users\")\n    assert response.status_code == 404\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    # Port in requested Host is irrelevant.\n\n    client = test_client_factory(mixed_hosts_app, base_url=\"https://port.example.org/\")\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n    client = test_client_factory(mixed_hosts_app, base_url=\"https://port.example.org:5600/\")\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n\ndef test_host_reverse_urls() -> None:\n    assert mixed_hosts_app.url_path_for(\"homepage\").make_absolute_url(\"https://whatever\") == \"https://www.example.org/\"\n    assert (\n        mixed_hosts_app.url_path_for(\"users\").make_absolute_url(\"https://whatever\") == \"https://www.example.org/users\"\n    )\n    assert (\n        mixed_hosts_app.url_path_for(\"api:users\").make_absolute_url(\"https://whatever\")\n        == \"https://api.example.org/users\"\n    )\n    assert (\n        mixed_hosts_app.url_path_for(\"port:homepage\").make_absolute_url(\"https://whatever\")\n        == \"https://port.example.org:3600/\"\n    )\n    with pytest.raises(NoMatchFound):\n        mixed_hosts_app.url_path_for(\"api\", path=\"whatever\", foo=\"bar\")\n\n\nasync def subdomain_app(scope: Scope, receive: Receive, send: Send) -> None:\n    response = JSONResponse({\"subdomain\": scope[\"path_params\"][\"subdomain\"]})\n    await response(scope, receive, send)\n\n\nsubdomain_router = Router(routes=[Host(\"{subdomain}.example.org\", app=subdomain_app, name=\"subdomains\")])\n\n\ndef test_subdomain_routing(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(subdomain_router, base_url=\"https://foo.example.org/\")\n\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.json() == {\"subdomain\": \"foo\"}\n\n\ndef test_subdomain_reverse_urls() -> None:\n    assert (\n        subdomain_router.url_path_for(\"subdomains\", subdomain=\"foo\", path=\"/homepage\").make_absolute_url(\n            \"https://whatever\"\n        )\n        == \"https://foo.example.org/homepage\"\n    )\n\n\nasync def echo_urls(request: Request) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"index\": str(request.url_for(\"index\")),\n            \"submount\": str(request.url_for(\"mount:submount\")),\n        }\n    )\n\n\necho_url_routes = [\n    Route(\"/\", echo_urls, name=\"index\", methods=[\"GET\"]),\n    Mount(\n        \"/submount\",\n        name=\"mount\",\n        routes=[Route(\"/\", echo_urls, name=\"submount\", methods=[\"GET\"])],\n    ),\n]\n\n\ndef test_url_for_with_root_path(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(routes=echo_url_routes)\n    client = test_client_factory(app, base_url=\"https://www.example.org/\", root_path=\"/sub_path\")\n    response = client.get(\"/sub_path/\")\n    assert response.json() == {\n        \"index\": \"https://www.example.org/sub_path/\",\n        \"submount\": \"https://www.example.org/sub_path/submount/\",\n    }\n    response = client.get(\"/sub_path/submount/\")\n    assert response.json() == {\n        \"index\": \"https://www.example.org/sub_path/\",\n        \"submount\": \"https://www.example.org/sub_path/submount/\",\n    }\n\n\nasync def stub_app(scope: Scope, receive: Receive, send: Send) -> None:\n    pass  # pragma: no cover\n\n\ndouble_mount_routes = [\n    Mount(\"/mount\", name=\"mount\", routes=[Mount(\"/static\", stub_app, name=\"static\")]),\n]\n\n\ndef test_url_for_with_double_mount() -> None:\n    app = Starlette(routes=double_mount_routes)\n    url = app.url_path_for(\"mount:static\", path=\"123\")\n    assert url == \"/mount/static/123\"\n\n\ndef test_url_for_with_root_path_ending_with_slash(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> JSONResponse:\n        return JSONResponse({\"index\": str(request.url_for(\"homepage\"))})\n\n    app = Starlette(routes=[Route(\"/\", homepage, name=\"homepage\")])\n    client = test_client_factory(app, base_url=\"https://www.example.org/\", root_path=\"/sub_path/\")\n    response = client.get(\"/sub_path/\")\n    assert response.json() == {\"index\": \"https://www.example.org/sub_path/\"}\n\n\ndef test_standalone_route_matches(\n    test_client_factory: TestClientFactory,\n) -> None:\n    app = Route(\"/\", PlainTextResponse(\"Hello, World!\"))\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, World!\"\n\n\ndef test_standalone_route_does_not_match(\n    test_client_factory: Callable[..., TestClient],\n) -> None:\n    app = Route(\"/\", PlainTextResponse(\"Hello, World!\"))\n    client = test_client_factory(app)\n    response = client.get(\"/invalid\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\nasync def ws_helloworld(websocket: WebSocket) -> None:\n    await websocket.accept()\n    await websocket.send_text(\"Hello, world!\")\n    await websocket.close()\n\n\ndef test_standalone_ws_route_matches(\n    test_client_factory: TestClientFactory,\n) -> None:\n    app = WebSocketRoute(\"/\", ws_helloworld)\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        text = websocket.receive_text()\n        assert text == \"Hello, world!\"\n\n\ndef test_standalone_ws_route_does_not_match(\n    test_client_factory: TestClientFactory,\n) -> None:\n    app = WebSocketRoute(\"/\", ws_helloworld)\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDisconnect):\n        with client.websocket_connect(\"/invalid\"):\n            pass  # pragma: no cover\n\n\ndef test_lifespan_state_unsupported(test_client_factory: TestClientFactory) -> None:\n    @contextlib.asynccontextmanager\n    async def lifespan(app: ASGIApp) -> AsyncGenerator[dict[str, str], None]:\n        yield {\"foo\": \"bar\"}\n\n    app = Router(\n        lifespan=lifespan,\n        routes=[Mount(\"/\", PlainTextResponse(\"hello, world\"))],\n    )\n\n    async def no_state_wrapper(scope: Scope, receive: Receive, send: Send) -> None:\n        del scope[\"state\"]\n        await app(scope, receive, send)\n\n    with pytest.raises(RuntimeError, match='The server does not support \"state\" in the lifespan scope'):\n        with test_client_factory(no_state_wrapper):\n            raise AssertionError(\"Should not be called\")  # pragma: no cover\n\n\ndef test_lifespan_state_async_cm(test_client_factory: TestClientFactory) -> None:\n    startup_complete = False\n    shutdown_complete = False\n\n    class State(TypedDict):\n        count: int\n        items: list[int]\n\n    async def hello_world(request: Request) -> Response:\n        # modifications to the state should not leak across requests\n        assert request.state.count == 0\n        # modify the state, this should not leak to the lifespan or other requests\n        request.state.count += 1\n        # since state.items is a mutable object this modification _will_ leak across\n        # requests and to the lifespan\n        request.state.items.append(1)\n        return PlainTextResponse(\"hello, world\")\n\n    @contextlib.asynccontextmanager\n    async def lifespan(app: Starlette) -> AsyncIterator[State]:\n        nonlocal startup_complete, shutdown_complete\n        startup_complete = True\n        state = State(count=0, items=[])\n        yield state\n        shutdown_complete = True\n        # modifications made to the state from a request do not leak to the lifespan\n        assert state[\"count\"] == 0\n        # unless of course the request mutates a mutable object that is referenced\n        # via state\n        assert state[\"items\"] == [1, 1]\n\n    app = Router(\n        lifespan=lifespan,\n        routes=[Route(\"/\", hello_world)],\n    )\n\n    assert not startup_complete\n    assert not shutdown_complete\n    with test_client_factory(app) as client:\n        assert startup_complete\n        assert not shutdown_complete\n        client.get(\"/\")\n        # Calling it a second time to ensure that the state is preserved.\n        client.get(\"/\")\n    assert startup_complete\n    assert shutdown_complete\n\n\ndef test_raise_on_startup(test_client_factory: TestClientFactory) -> None:\n    @contextlib.asynccontextmanager\n    async def lifespan(app: Starlette) -> AsyncIterator[Never]:\n        raise RuntimeError()\n        yield  # pragma: no cover\n\n    router = Router(lifespan=lifespan)\n    startup_failed = False\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        async def _send(message: Message) -> None:\n            nonlocal startup_failed\n            if message[\"type\"] == \"lifespan.startup.failed\":  # pragma: no branch\n                startup_failed = True\n            return await send(message)\n\n        await router(scope, receive, _send)\n\n    with pytest.raises(RuntimeError):\n        with test_client_factory(app):\n            pass  # pragma: no cover\n    assert startup_failed\n\n\ndef test_raise_on_shutdown(test_client_factory: TestClientFactory) -> None:\n    @contextlib.asynccontextmanager\n    async def lifespan(app: Starlette) -> AsyncIterator[None]:\n        yield\n        raise RuntimeError(\"Shutdown failed\")\n\n    app = Router(lifespan=lifespan)\n\n    with pytest.raises(RuntimeError, match=\"Shutdown failed\"):\n        with test_client_factory(app):\n            pass  # pragma: no cover\n\n\ndef test_partial_async_endpoint(test_client_factory: TestClientFactory) -> None:\n    test_client = test_client_factory(app)\n    response = test_client.get(\"/partial\")\n    assert response.status_code == 200\n    assert response.json() == {\"arg\": \"foo\"}\n\n    cls_method_response = test_client.get(\"/partial/cls\")\n    assert cls_method_response.status_code == 200\n    assert cls_method_response.json() == {\"arg\": \"foo\"}\n\n\ndef test_partial_async_ws_endpoint(\n    test_client_factory: TestClientFactory,\n) -> None:\n    test_client = test_client_factory(app)\n    with test_client.websocket_connect(\"/partial/ws\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"url\": \"ws://testserver/partial/ws\"}\n\n    with test_client.websocket_connect(\"/partial/ws/cls\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"url\": \"ws://testserver/partial/ws/cls\"}\n\n\ndef test_duplicated_param_names() -> None:\n    with pytest.raises(\n        ValueError,\n        match=\"Duplicated param name id at path /{id}/{id}\",\n    ):\n        Route(\"/{id}/{id}\", user)\n\n    with pytest.raises(\n        ValueError,\n        match=\"Duplicated param names id, name at path /{id}/{name}/{id}/{name}\",\n    ):\n        Route(\"/{id}/{name}/{id}/{name}\", user)\n\n\nclass Endpoint:\n    async def my_method(self, request: Request) -> None: ...  # pragma: no cover\n\n    @classmethod\n    async def my_classmethod(cls, request: Request) -> None: ...  # pragma: no cover\n\n    @staticmethod\n    async def my_staticmethod(request: Request) -> None: ...  # pragma: no cover\n\n    def __call__(self, request: Request) -> None: ...  # pragma: no cover\n\n\n@pytest.mark.parametrize(\n    \"endpoint, expected_name\",\n    [\n        pytest.param(func_homepage, \"func_homepage\", id=\"function\"),\n        pytest.param(Endpoint().my_method, \"my_method\", id=\"method\"),\n        pytest.param(Endpoint.my_classmethod, \"my_classmethod\", id=\"classmethod\"),\n        pytest.param(\n            Endpoint.my_staticmethod,\n            \"my_staticmethod\",\n            id=\"staticmethod\",\n        ),\n        pytest.param(Endpoint(), \"Endpoint\", id=\"object\"),\n        pytest.param(lambda request: ..., \"<lambda>\", id=\"lambda\"),  # pragma: no branch\n    ],\n)\ndef test_route_name(endpoint: Callable[..., Response], expected_name: str) -> None:\n    assert Route(path=\"/\", endpoint=endpoint).name == expected_name\n\n\nclass AddHeadersMiddleware:\n    def __init__(self, app: ASGIApp) -> None:\n        self.app = app\n\n    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        scope[\"add_headers_middleware\"] = True\n\n        async def modified_send(msg: Message) -> None:\n            if msg[\"type\"] == \"http.response.start\":\n                msg[\"headers\"].append((b\"X-Test\", b\"Set by middleware\"))\n            await send(msg)\n\n        await self.app(scope, receive, modified_send)\n\n\ndef assert_middleware_header_route(request: Request) -> Response:\n    assert request.scope[\"add_headers_middleware\"] is True\n    return Response()\n\n\nroute_with_middleware = Starlette(\n    routes=[\n        Route(\n            \"/http\",\n            endpoint=assert_middleware_header_route,\n            methods=[\"GET\"],\n            middleware=[Middleware(AddHeadersMiddleware)],\n        ),\n        Route(\"/home\", homepage),\n    ]\n)\n\nmounted_routes_with_middleware = Starlette(\n    routes=[\n        Mount(\n            \"/http\",\n            routes=[\n                Route(\n                    \"/\",\n                    endpoint=assert_middleware_header_route,\n                    methods=[\"GET\"],\n                    name=\"route\",\n                ),\n            ],\n            middleware=[Middleware(AddHeadersMiddleware)],\n        ),\n        Route(\"/home\", homepage),\n    ]\n)\n\n\nmounted_app_with_middleware = Starlette(\n    routes=[\n        Mount(\n            \"/http\",\n            app=Route(\n                \"/\",\n                endpoint=assert_middleware_header_route,\n                methods=[\"GET\"],\n                name=\"route\",\n            ),\n            middleware=[Middleware(AddHeadersMiddleware)],\n        ),\n        Route(\"/home\", homepage),\n    ]\n)\n\n\n@pytest.mark.parametrize(\n    \"app\",\n    [\n        mounted_routes_with_middleware,\n        mounted_app_with_middleware,\n        route_with_middleware,\n    ],\n)\ndef test_base_route_middleware(\n    test_client_factory: TestClientFactory,\n    app: Starlette,\n) -> None:\n    test_client = test_client_factory(app)\n\n    response = test_client.get(\"/home\")\n    assert response.status_code == 200\n    assert \"X-Test\" not in response.headers\n\n    response = test_client.get(\"/http\")\n    assert response.status_code == 200\n    assert response.headers[\"X-Test\"] == \"Set by middleware\"\n\n\ndef test_mount_routes_with_middleware_url_path_for() -> None:\n    \"\"\"Checks that url_path_for still works with mounted routes with Middleware\"\"\"\n    assert mounted_routes_with_middleware.url_path_for(\"route\") == \"/http/\"\n\n\ndef test_mount_asgi_app_with_middleware_url_path_for() -> None:\n    \"\"\"Mounted ASGI apps do not work with url path for,\n    middleware does not change this\n    \"\"\"\n    with pytest.raises(NoMatchFound):\n        mounted_app_with_middleware.url_path_for(\"route\")\n\n\ndef test_add_route_to_app_after_mount(\n    test_client_factory: Callable[..., TestClient],\n) -> None:\n    \"\"\"Checks that Mount will pick up routes\n    added to the underlying app after it is mounted\n    \"\"\"\n    inner_app = Router()\n    app = Mount(\"/http\", app=inner_app)\n    inner_app.add_route(\n        \"/inner\",\n        endpoint=homepage,\n        methods=[\"GET\"],\n    )\n    client = test_client_factory(app)\n    response = client.get(\"/http/inner\")\n    assert response.status_code == 200\n\n\ndef test_exception_on_mounted_apps(\n    test_client_factory: TestClientFactory,\n) -> None:\n    def exc(request: Request) -> None:\n        raise Exception(\"Exc\")\n\n    sub_app = Starlette(routes=[Route(\"/\", exc)])\n    app = Starlette(routes=[Mount(\"/sub\", app=sub_app)])\n\n    client = test_client_factory(app)\n    with pytest.raises(Exception) as ctx:\n        client.get(\"/sub/\")\n    assert str(ctx.value) == \"Exc\"\n\n\ndef test_mounted_middleware_does_not_catch_exception(\n    test_client_factory: Callable[..., TestClient],\n) -> None:\n    # https://github.com/Kludex/starlette/pull/1649#discussion_r960236107\n    def exc(request: Request) -> Response:\n        raise HTTPException(status_code=403, detail=\"auth\")\n\n    class NamedMiddleware:\n        def __init__(self, app: ASGIApp, name: str) -> None:\n            self.app = app\n            self.name = name\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            async def modified_send(msg: Message) -> None:\n                if msg[\"type\"] == \"http.response.start\":\n                    msg[\"headers\"].append((f\"X-{self.name}\".encode(), b\"true\"))\n                await send(msg)\n\n            await self.app(scope, receive, modified_send)\n\n    app = Starlette(\n        routes=[\n            Mount(\n                \"/mount\",\n                routes=[\n                    Route(\"/err\", exc),\n                    Route(\"/home\", homepage),\n                ],\n                middleware=[Middleware(NamedMiddleware, name=\"Mounted\")],\n            ),\n            Route(\"/err\", exc),\n            Route(\"/home\", homepage),\n        ],\n        middleware=[Middleware(NamedMiddleware, name=\"Outer\")],\n    )\n\n    client = test_client_factory(app)\n\n    resp = client.get(\"/home\")\n    assert resp.status_code == 200, resp.content\n    assert \"X-Outer\" in resp.headers\n\n    resp = client.get(\"/err\")\n    assert resp.status_code == 403, resp.content\n    assert \"X-Outer\" in resp.headers\n\n    resp = client.get(\"/mount/home\")\n    assert resp.status_code == 200, resp.content\n    assert \"X-Mounted\" in resp.headers\n\n    resp = client.get(\"/mount/err\")\n    assert resp.status_code == 403, resp.content\n    assert \"X-Mounted\" in resp.headers\n\n\ndef test_websocket_route_middleware(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def websocket_endpoint(session: WebSocket) -> None:\n        await session.accept()\n        await session.send_text(\"Hello, world!\")\n        await session.close()\n\n    class WebsocketMiddleware:\n        def __init__(self, app: ASGIApp) -> None:\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            async def modified_send(msg: Message) -> None:\n                if msg[\"type\"] == \"websocket.accept\":\n                    msg[\"headers\"].append((b\"X-Test\", b\"Set by middleware\"))\n                await send(msg)\n\n            await self.app(scope, receive, modified_send)\n\n    app = Starlette(\n        routes=[\n            WebSocketRoute(\n                \"/ws\",\n                endpoint=websocket_endpoint,\n                middleware=[Middleware(WebsocketMiddleware)],\n            )\n        ]\n    )\n\n    client = test_client_factory(app)\n\n    with client.websocket_connect(\"/ws\") as websocket:\n        text = websocket.receive_text()\n        assert text == \"Hello, world!\"\n        assert websocket.extra_headers == [(b\"X-Test\", b\"Set by middleware\")]\n\n\ndef test_route_repr() -> None:\n    route = Route(\"/welcome\", endpoint=homepage)\n    assert repr(route) == \"Route(path='/welcome', name='homepage', methods=['GET', 'HEAD'])\"\n\n\ndef test_route_repr_without_methods() -> None:\n    route = Route(\"/welcome\", endpoint=Endpoint, methods=None)\n    assert repr(route) == \"Route(path='/welcome', name='Endpoint', methods=[])\"\n\n\ndef test_websocket_route_repr() -> None:\n    route = WebSocketRoute(\"/ws\", endpoint=websocket_endpoint)\n    assert repr(route) == \"WebSocketRoute(path='/ws', name='websocket_endpoint')\"\n\n\ndef test_mount_repr() -> None:\n    route = Mount(\n        \"/app\",\n        routes=[\n            Route(\"/\", endpoint=homepage),\n        ],\n    )\n    # test for substring because repr(Router) returns unique object ID\n    assert repr(route).startswith(\"Mount(path='/app', name='', app=\")\n\n\ndef test_mount_named_repr() -> None:\n    route = Mount(\n        \"/app\",\n        name=\"app\",\n        routes=[\n            Route(\"/\", endpoint=homepage),\n        ],\n    )\n    # test for substring because repr(Router) returns unique object ID\n    assert repr(route).startswith(\"Mount(path='/app', name='app', app=\")\n\n\ndef test_host_repr() -> None:\n    route = Host(\n        \"example.com\",\n        app=Router(\n            [\n                Route(\"/\", endpoint=homepage),\n            ]\n        ),\n    )\n    # test for substring because repr(Router) returns unique object ID\n    assert repr(route).startswith(\"Host(host='example.com', name='', app=\")\n\n\ndef test_host_named_repr() -> None:\n    route = Host(\n        \"example.com\",\n        name=\"app\",\n        app=Router(\n            [\n                Route(\"/\", endpoint=homepage),\n            ]\n        ),\n    )\n    # test for substring because repr(Router) returns unique object ID\n    assert repr(route).startswith(\"Host(host='example.com', name='app', app=\")\n\n\nasync def echo_paths(request: Request, name: str) -> JSONResponse:\n    return JSONResponse(\n        {\n            \"name\": name,\n            \"path\": request.scope[\"path\"],\n            \"root_path\": request.scope[\"root_path\"],\n        }\n    )\n\n\nasync def pure_asgi_echo_paths(scope: Scope, receive: Receive, send: Send, name: str) -> None:\n    data = {\"name\": name, \"path\": scope[\"path\"], \"root_path\": scope[\"root_path\"]}\n    content = json.dumps(data).encode(\"utf-8\")\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [(b\"content-type\", b\"application/json\")],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": content})\n\n\necho_paths_routes = [\n    Route(\n        \"/path\",\n        functools.partial(echo_paths, name=\"path\"),\n        name=\"path\",\n        methods=[\"GET\"],\n    ),\n    Route(\n        \"/root-queue/path\",\n        functools.partial(echo_paths, name=\"queue_path\"),\n        name=\"queue_path\",\n        methods=[\"POST\"],\n    ),\n    Mount(\"/asgipath\", app=functools.partial(pure_asgi_echo_paths, name=\"asgipath\")),\n    Mount(\n        \"/sub\",\n        name=\"mount\",\n        routes=[\n            Route(\n                \"/path\",\n                functools.partial(echo_paths, name=\"subpath\"),\n                name=\"subpath\",\n                methods=[\"GET\"],\n            ),\n        ],\n    ),\n]\n\n\ndef test_paths_with_root_path(test_client_factory: TestClientFactory) -> None:\n    app = Starlette(routes=echo_paths_routes)\n    client = test_client_factory(app, base_url=\"https://www.example.org/\", root_path=\"/root\")\n    response = client.get(\"/root/path\")\n    assert response.status_code == 200\n    assert response.json() == {\n        \"name\": \"path\",\n        \"path\": \"/root/path\",\n        \"root_path\": \"/root\",\n    }\n    response = client.get(\"/root/asgipath/\")\n    assert response.status_code == 200\n    assert response.json() == {\n        \"name\": \"asgipath\",\n        \"path\": \"/root/asgipath/\",\n        # Things that mount other ASGI apps, like WSGIMiddleware, would not be aware\n        # of the prefixed path, and would have their own notion of their own paths,\n        # so they need to be able to rely on the root_path to know the location they\n        # are mounted on\n        \"root_path\": \"/root/asgipath\",\n    }\n\n    response = client.get(\"/root/sub/path\")\n    assert response.status_code == 200\n    assert response.json() == {\n        \"name\": \"subpath\",\n        \"path\": \"/root/sub/path\",\n        \"root_path\": \"/root/sub\",\n    }\n\n    response = client.post(\"/root/root-queue/path\")\n    assert response.status_code == 200\n    assert response.json() == {\n        \"name\": \"queue_path\",\n        \"path\": \"/root/root-queue/path\",\n        \"root_path\": \"/root\",\n    }\n"
  },
  {
    "path": "tests/test_schemas.py",
    "content": "from starlette.applications import Starlette\nfrom starlette.endpoints import HTTPEndpoint\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import Host, Mount, Route, Router, WebSocketRoute\nfrom starlette.schemas import SchemaGenerator\nfrom starlette.websockets import WebSocket\nfrom tests.types import TestClientFactory\n\nschemas = SchemaGenerator({\"openapi\": \"3.0.0\", \"info\": {\"title\": \"Example API\", \"version\": \"1.0\"}})\n\n\ndef ws(session: WebSocket) -> None:\n    \"\"\"ws\"\"\"\n    pass  # pragma: no cover\n\n\ndef get_user(request: Request) -> None:\n    \"\"\"\n    responses:\n        200:\n            description: A user.\n            examples:\n                {\"username\": \"tom\"}\n    \"\"\"\n    pass  # pragma: no cover\n\n\ndef list_users(request: Request) -> None:\n    \"\"\"\n    responses:\n      200:\n        description: A list of users.\n        examples:\n          [{\"username\": \"tom\"}, {\"username\": \"lucy\"}]\n    \"\"\"\n    pass  # pragma: no cover\n\n\ndef create_user(request: Request) -> None:\n    \"\"\"\n    responses:\n      200:\n        description: A user.\n        examples:\n          {\"username\": \"tom\"}\n    \"\"\"\n    pass  # pragma: no cover\n\n\nclass OrganisationsEndpoint(HTTPEndpoint):\n    def get(self, request: Request) -> None:\n        \"\"\"\n        responses:\n          200:\n            description: A list of organisations.\n            examples:\n              [{\"name\": \"Foo Corp.\"}, {\"name\": \"Acme Ltd.\"}]\n        \"\"\"\n        pass  # pragma: no cover\n\n    def post(self, request: Request) -> None:\n        \"\"\"\n        responses:\n          200:\n            description: An organisation.\n            examples:\n              {\"name\": \"Foo Corp.\"}\n        \"\"\"\n        pass  # pragma: no cover\n\n\ndef regular_docstring_and_schema(request: Request) -> None:\n    \"\"\"\n    This a regular docstring example (not included in schema)\n\n    ---\n\n    responses:\n      200:\n        description: This is included in the schema.\n    \"\"\"\n    pass  # pragma: no cover\n\n\ndef regular_docstring(request: Request) -> None:\n    \"\"\"\n    This a regular docstring example (not included in schema)\n    \"\"\"\n    pass  # pragma: no cover\n\n\ndef no_docstring(request: Request) -> None:\n    pass  # pragma: no cover\n\n\ndef subapp_endpoint(request: Request) -> None:\n    \"\"\"\n    responses:\n      200:\n        description: This endpoint is part of a subapp.\n    \"\"\"\n    pass  # pragma: no cover\n\n\ndef schema(request: Request) -> Response:\n    return schemas.OpenAPIResponse(request=request)\n\n\nsubapp = Starlette(\n    routes=[\n        Route(\"/subapp-endpoint\", endpoint=subapp_endpoint),\n    ]\n)\n\napp = Starlette(\n    routes=[\n        WebSocketRoute(\"/ws\", endpoint=ws),\n        Route(\"/users/{id:int}\", endpoint=get_user, methods=[\"GET\"]),\n        Route(\"/users\", endpoint=list_users, methods=[\"GET\", \"HEAD\"]),\n        Route(\"/users\", endpoint=create_user, methods=[\"POST\"]),\n        Route(\"/orgs\", endpoint=OrganisationsEndpoint),\n        Route(\"/regular-docstring-and-schema\", endpoint=regular_docstring_and_schema),\n        Route(\"/regular-docstring\", endpoint=regular_docstring),\n        Route(\"/no-docstring\", endpoint=no_docstring),\n        Route(\"/schema\", endpoint=schema, methods=[\"GET\"], include_in_schema=False),\n        Mount(\"/subapp\", subapp),\n        Host(\"sub.domain.com\", app=Router(routes=[Mount(\"/subapp2\", subapp)])),\n    ]\n)\n\n\ndef test_schema_generation() -> None:\n    schema = schemas.get_schema(routes=app.routes)\n    assert schema == {\n        \"openapi\": \"3.0.0\",\n        \"info\": {\"title\": \"Example API\", \"version\": \"1.0\"},\n        \"paths\": {\n            \"/orgs\": {\n                \"get\": {\n                    \"responses\": {\n                        200: {\n                            \"description\": \"A list of organisations.\",\n                            \"examples\": [{\"name\": \"Foo Corp.\"}, {\"name\": \"Acme Ltd.\"}],\n                        }\n                    }\n                },\n                \"post\": {\n                    \"responses\": {\n                        200: {\n                            \"description\": \"An organisation.\",\n                            \"examples\": {\"name\": \"Foo Corp.\"},\n                        }\n                    }\n                },\n            },\n            \"/regular-docstring-and-schema\": {\n                \"get\": {\"responses\": {200: {\"description\": \"This is included in the schema.\"}}}\n            },\n            \"/subapp/subapp-endpoint\": {\n                \"get\": {\"responses\": {200: {\"description\": \"This endpoint is part of a subapp.\"}}}\n            },\n            \"/subapp2/subapp-endpoint\": {\n                \"get\": {\"responses\": {200: {\"description\": \"This endpoint is part of a subapp.\"}}}\n            },\n            \"/users\": {\n                \"get\": {\n                    \"responses\": {\n                        200: {\n                            \"description\": \"A list of users.\",\n                            \"examples\": [{\"username\": \"tom\"}, {\"username\": \"lucy\"}],\n                        }\n                    }\n                },\n                \"post\": {\"responses\": {200: {\"description\": \"A user.\", \"examples\": {\"username\": \"tom\"}}}},\n            },\n            \"/users/{id}\": {\n                \"get\": {\n                    \"responses\": {\n                        200: {\n                            \"description\": \"A user.\",\n                            \"examples\": {\"username\": \"tom\"},\n                        }\n                    }\n                },\n            },\n        },\n    }\n\n\nEXPECTED_SCHEMA = \"\"\"\ninfo:\n  title: Example API\n  version: '1.0'\nopenapi: 3.0.0\npaths:\n  /orgs:\n    get:\n      responses:\n        200:\n          description: A list of organisations.\n          examples:\n          - name: Foo Corp.\n          - name: Acme Ltd.\n    post:\n      responses:\n        200:\n          description: An organisation.\n          examples:\n            name: Foo Corp.\n  /regular-docstring-and-schema:\n    get:\n      responses:\n        200:\n          description: This is included in the schema.\n  /subapp/subapp-endpoint:\n    get:\n      responses:\n        200:\n          description: This endpoint is part of a subapp.\n  /subapp2/subapp-endpoint:\n    get:\n      responses:\n        200:\n          description: This endpoint is part of a subapp.\n  /users:\n    get:\n      responses:\n        200:\n          description: A list of users.\n          examples:\n          - username: tom\n          - username: lucy\n    post:\n      responses:\n        200:\n          description: A user.\n          examples:\n            username: tom\n  /users/{id}:\n    get:\n      responses:\n        200:\n          description: A user.\n          examples:\n            username: tom\n\"\"\"\n\n\ndef test_schema_endpoint(test_client_factory: TestClientFactory) -> None:\n    client = test_client_factory(app)\n    response = client.get(\"/schema\")\n    assert response.headers[\"Content-Type\"] == \"application/vnd.oai.openapi\"\n    assert response.text.strip() == EXPECTED_SCHEMA.strip()\n"
  },
  {
    "path": "tests/test_staticfiles.py",
    "content": "import os\nimport stat\nimport tempfile\nimport time\nfrom pathlib import Path\nfrom typing import Any\n\nimport anyio\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.exceptions import HTTPException\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import Mount\nfrom starlette.staticfiles import StaticFiles\nfrom tests.types import TestClientFactory\n\n\ndef test_staticfiles(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"<file content>\"\n\n\ndef test_staticfiles_with_pathlib(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"example.txt\"\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=tmp_path)\n    client = test_client_factory(app)\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"<file content>\"\n\n\ndef test_staticfiles_head_with_middleware(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    \"\"\"\n    see https://github.com/Kludex/starlette/pull/935\n    \"\"\"\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"x\" * 100)\n\n    async def does_nothing_middleware(request: Request, call_next: RequestResponseEndpoint) -> Response:\n        response = await call_next(request)\n        return response\n\n    routes = [Mount(\"/static\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    middleware = [Middleware(BaseHTTPMiddleware, dispatch=does_nothing_middleware)]\n    app = Starlette(routes=routes, middleware=middleware)\n\n    client = test_client_factory(app)\n    response = client.head(\"/static/example.txt\")\n    assert response.status_code == 200\n    assert response.headers.get(\"content-length\") == \"100\"\n\n\ndef test_staticfiles_with_package(test_client_factory: TestClientFactory) -> None:\n    app = StaticFiles(packages=[\"tests\"])\n    client = test_client_factory(app)\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"123\\n\"\n\n    app = StaticFiles(packages=[(\"tests\", \"statics\")])\n    client = test_client_factory(app)\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"123\\n\"\n\n\ndef test_staticfiles_post(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.post(\"/example.txt\")\n    assert response.status_code == 405\n    assert response.text == \"Method Not Allowed\"\n\n\ndef test_staticfiles_with_directory_returns_404(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.get(\"/\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\ndef test_staticfiles_with_missing_file_returns_404(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.get(\"/404.txt\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\ndef test_staticfiles_instantiated_with_missing_directory(tmpdir: Path) -> None:\n    with pytest.raises(RuntimeError) as exc_info:\n        path = os.path.join(tmpdir, \"no_such_directory\")\n        StaticFiles(directory=path)\n    assert \"does not exist\" in str(exc_info.value)\n\n\ndef test_staticfiles_configured_with_missing_directory(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"no_such_directory\")\n    app = StaticFiles(directory=path, check_dir=False)\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError) as exc_info:\n        client.get(\"/example.txt\")\n    assert \"does not exist\" in str(exc_info.value)\n\n\ndef test_staticfiles_configured_with_file_instead_of_directory(\n    tmpdir: Path, test_client_factory: TestClientFactory\n) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=path, check_dir=False)\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError) as exc_info:\n        client.get(\"/example.txt\")\n    assert \"is not a directory\" in str(exc_info.value)\n\n\ndef test_staticfiles_config_check_occurs_only_once(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    assert not app.config_checked\n\n    with pytest.raises(HTTPException):\n        client.get(\"/\")\n\n    assert app.config_checked\n\n    with pytest.raises(HTTPException):\n        client.get(\"/\")\n\n\ndef test_staticfiles_prevents_breaking_out_of_directory(tmpdir: Path) -> None:\n    directory = os.path.join(tmpdir, \"foo\")\n    os.mkdir(directory)\n\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"outside root dir\")\n\n    app = StaticFiles(directory=directory)\n    # We can't test this with 'httpx', so we test the app directly here.\n    path = app.get_path({\"path\": \"/../example.txt\"})\n    scope = {\"method\": \"GET\"}\n\n    with pytest.raises(HTTPException) as exc_info:\n        anyio.run(app.get_response, path, scope)\n\n    assert exc_info.value.status_code == 404\n    assert exc_info.value.detail == \"Not Found\"\n\n\ndef test_staticfiles_never_read_file_for_head_method(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    response = client.head(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.content == b\"\"\n    assert response.headers[\"content-length\"] == \"14\"\n\n\ndef test_staticfiles_304_with_etag_match(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    first_resp = client.get(\"/example.txt\")\n    assert first_resp.status_code == 200\n    last_etag = first_resp.headers[\"etag\"]\n    second_resp = client.get(\"/example.txt\", headers={\"if-none-match\": last_etag})\n    assert second_resp.status_code == 304\n    assert second_resp.content == b\"\"\n    second_resp = client.get(\"/example.txt\", headers={\"if-none-match\": f'W/{last_etag}, \"123\"'})\n    assert second_resp.status_code == 304\n    assert second_resp.content == b\"\"\n\n\ndef test_staticfiles_200_with_etag_mismatch(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    first_resp = client.get(\"/example.txt\")\n    assert first_resp.status_code == 200\n    assert first_resp.headers[\"etag\"] != '\"123\"'\n    second_resp = client.get(\"/example.txt\", headers={\"if-none-match\": '\"123\"'})\n    assert second_resp.status_code == 200\n    assert second_resp.content == b\"<file content>\"\n\n\ndef test_staticfiles_200_with_etag_mismatch_and_timestamp_match(\n    tmpdir: Path, test_client_factory: TestClientFactory\n) -> None:\n    path = tmpdir / \"example.txt\"\n    path.write_text(\"<file content>\", encoding=\"utf-8\")\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    first_resp = client.get(\"/example.txt\")\n    assert first_resp.status_code == 200\n    assert first_resp.headers[\"etag\"] != '\"123\"'\n    last_modified = first_resp.headers[\"last-modified\"]\n    # If `if-none-match` is present, `if-modified-since` is ignored.\n    second_resp = client.get(\"/example.txt\", headers={\"if-none-match\": '\"123\"', \"if-modified-since\": last_modified})\n    assert second_resp.status_code == 200\n    assert second_resp.content == b\"<file content>\"\n\n\ndef test_staticfiles_304_with_last_modified_compare_last_req(\n    tmpdir: Path, test_client_factory: TestClientFactory\n) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    file_last_modified_time = time.mktime(time.strptime(\"2013-10-10 23:40:00\", \"%Y-%m-%d %H:%M:%S\"))\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n    os.utime(path, (file_last_modified_time, file_last_modified_time))\n\n    app = StaticFiles(directory=tmpdir)\n    client = test_client_factory(app)\n    # last modified less than last request, 304\n    response = client.get(\"/example.txt\", headers={\"If-Modified-Since\": \"Thu, 11 Oct 2013 15:30:19 GMT\"})\n    assert response.status_code == 304\n    assert response.content == b\"\"\n    # last modified greater than last request, 200 with content\n    response = client.get(\"/example.txt\", headers={\"If-Modified-Since\": \"Thu, 20 Feb 2012 15:30:19 GMT\"})\n    assert response.status_code == 200\n    assert response.content == b\"<file content>\"\n\n\ndef test_staticfiles_html_normal(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"404.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<h1>Custom not found page</h1>\")\n    path = os.path.join(tmpdir, \"dir\")\n    os.mkdir(path)\n    path = os.path.join(path, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    app = StaticFiles(directory=tmpdir, html=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/dir/\")\n    assert response.url == \"http://testserver/dir/\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n    response = client.get(\"/dir\")\n    assert response.url == \"http://testserver/dir/\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n    response = client.get(\"/dir/index.html\")\n    assert response.url == \"http://testserver/dir/index.html\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n    response = client.get(\"/missing\")\n    assert response.status_code == 404\n    assert response.text == \"<h1>Custom not found page</h1>\"\n\n\ndef test_staticfiles_html_without_index(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"404.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<h1>Custom not found page</h1>\")\n    path = os.path.join(tmpdir, \"dir\")\n    os.mkdir(path)\n\n    app = StaticFiles(directory=tmpdir, html=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/dir/\")\n    assert response.url == \"http://testserver/dir/\"\n    assert response.status_code == 404\n    assert response.text == \"<h1>Custom not found page</h1>\"\n\n    response = client.get(\"/dir\")\n    assert response.url == \"http://testserver/dir\"\n    assert response.status_code == 404\n    assert response.text == \"<h1>Custom not found page</h1>\"\n\n    response = client.get(\"/missing\")\n    assert response.status_code == 404\n    assert response.text == \"<h1>Custom not found page</h1>\"\n\n\ndef test_staticfiles_html_without_404(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"dir\")\n    os.mkdir(path)\n    path = os.path.join(path, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    app = StaticFiles(directory=tmpdir, html=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/dir/\")\n    assert response.url == \"http://testserver/dir/\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n    response = client.get(\"/dir\")\n    assert response.url == \"http://testserver/dir/\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n    with pytest.raises(HTTPException) as exc_info:\n        response = client.get(\"/missing\")\n    assert exc_info.value.status_code == 404\n\n\ndef test_staticfiles_html_only_files(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"hello.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    app = StaticFiles(directory=tmpdir, html=True)\n    client = test_client_factory(app)\n\n    with pytest.raises(HTTPException) as exc_info:\n        response = client.get(\"/\")\n    assert exc_info.value.status_code == 404\n\n    response = client.get(\"/hello.html\")\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n\ndef test_staticfiles_cache_invalidation_for_deleted_file_html_mode(\n    tmpdir: Path, test_client_factory: TestClientFactory\n) -> None:\n    path_404 = os.path.join(tmpdir, \"404.html\")\n    with open(path_404, \"w\") as file:\n        file.write(\"<p>404 file</p>\")\n    path_some = os.path.join(tmpdir, \"some.html\")\n    with open(path_some, \"w\") as file:\n        file.write(\"<p>some file</p>\")\n\n    common_modified_time = time.mktime(time.strptime(\"2013-10-10 23:40:00\", \"%Y-%m-%d %H:%M:%S\"))\n    os.utime(path_404, (common_modified_time, common_modified_time))\n    os.utime(path_some, (common_modified_time, common_modified_time))\n\n    app = StaticFiles(directory=tmpdir, html=True)\n    client = test_client_factory(app)\n\n    resp_exists = client.get(\"/some.html\")\n    assert resp_exists.status_code == 200\n    assert resp_exists.text == \"<p>some file</p>\"\n\n    resp_cached = client.get(\n        \"/some.html\",\n        headers={\"If-Modified-Since\": resp_exists.headers[\"last-modified\"]},\n    )\n    assert resp_cached.status_code == 304\n\n    os.remove(path_some)\n\n    resp_deleted = client.get(\n        \"/some.html\",\n        headers={\"If-Modified-Since\": resp_exists.headers[\"last-modified\"]},\n    )\n    assert resp_deleted.status_code == 404\n    assert resp_deleted.text == \"<p>404 file</p>\"\n\n\ndef test_staticfiles_with_invalid_dir_permissions_returns_401(\n    tmp_path: Path, test_client_factory: TestClientFactory\n) -> None:\n    (tmp_path / \"example.txt\").write_bytes(b\"<file content>\")\n\n    original_mode = tmp_path.stat().st_mode\n    tmp_path.chmod(stat.S_IRWXO)\n    try:\n        routes = [\n            Mount(\n                \"/\",\n                app=StaticFiles(directory=os.fsdecode(tmp_path)),\n                name=\"static\",\n            )\n        ]\n        app = Starlette(routes=routes)\n        client = test_client_factory(app)\n\n        response = client.get(\"/example.txt\")\n        assert response.status_code == 401\n        assert response.text == \"Unauthorized\"\n    finally:\n        tmp_path.chmod(original_mode)\n\n\ndef test_staticfiles_with_missing_dir_returns_404(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.get(\"/foo/example.txt\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\ndef test_staticfiles_access_file_as_dir_returns_404(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.get(\"/example.txt/foo\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\ndef test_staticfiles_null_byte_in_path(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    response = client.get(\"/example%00.txt\")\n    assert response.status_code == 404\n\n\ndef test_staticfiles_filename_too_long(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app)\n\n    path_max_size = os.pathconf(\"/\", \"PC_PATH_MAX\")\n    response = client.get(f\"/{'a' * path_max_size}.txt\")\n    assert response.status_code == 404\n    assert response.text == \"Not Found\"\n\n\ndef test_staticfiles_unhandled_os_error_returns_500(\n    tmpdir: Path,\n    test_client_factory: TestClientFactory,\n    monkeypatch: pytest.MonkeyPatch,\n) -> None:\n    def mock_timeout(*args: Any, **kwargs: Any) -> None:\n        raise TimeoutError\n\n    path = os.path.join(tmpdir, \"example.txt\")\n    with open(path, \"w\") as file:\n        file.write(\"<file content>\")\n\n    routes = [Mount(\"/\", app=StaticFiles(directory=tmpdir), name=\"static\")]\n    app = Starlette(routes=routes)\n    client = test_client_factory(app, raise_server_exceptions=False)\n\n    monkeypatch.setattr(\"starlette.staticfiles.StaticFiles.lookup_path\", mock_timeout)\n\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 500\n    assert response.text == \"Internal Server Error\"\n\n\ndef test_staticfiles_follows_symlinks(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    statics_path = os.path.join(tmpdir, \"statics\")\n    os.mkdir(statics_path)\n\n    source_path = tempfile.mkdtemp()\n    source_file_path = os.path.join(source_path, \"page.html\")\n    with open(source_file_path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    statics_file_path = os.path.join(statics_path, \"index.html\")\n    os.symlink(source_file_path, statics_file_path)\n\n    app = StaticFiles(directory=statics_path, follow_symlink=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/index.html\")\n    assert response.url == \"http://testserver/index.html\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n\ndef test_staticfiles_follows_symlink_directories(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    statics_path = os.path.join(tmpdir, \"statics\")\n    statics_html_path = os.path.join(statics_path, \"html\")\n    os.mkdir(statics_path)\n\n    source_path = tempfile.mkdtemp()\n    source_file_path = os.path.join(source_path, \"page.html\")\n    with open(source_file_path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    os.symlink(source_path, statics_html_path)\n\n    app = StaticFiles(directory=statics_path, follow_symlink=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/html/page.html\")\n    assert response.url == \"http://testserver/html/page.html\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n\ndef test_staticfiles_disallows_path_traversal_with_symlinks(tmpdir: Path) -> None:\n    statics_path = os.path.join(tmpdir, \"statics\")\n\n    root_source_path = tempfile.mkdtemp()\n    source_path = os.path.join(root_source_path, \"statics\")\n    os.mkdir(source_path)\n\n    source_file_path = os.path.join(root_source_path, \"index.html\")\n    with open(source_file_path, \"w\") as file:\n        file.write(\"<h1>Hello</h1>\")\n\n    os.symlink(source_path, statics_path)\n\n    app = StaticFiles(directory=statics_path, follow_symlink=True)\n    # We can't test this with 'httpx', so we test the app directly here.\n    path = app.get_path({\"path\": \"/../index.html\"})\n    scope = {\"method\": \"GET\"}\n\n    with pytest.raises(HTTPException) as exc_info:\n        anyio.run(app.get_response, path, scope)\n\n    assert exc_info.value.status_code == 404\n    assert exc_info.value.detail == \"Not Found\"\n\n\ndef test_staticfiles_avoids_path_traversal(tmp_path: Path) -> None:\n    statics_path = tmp_path / \"static\"\n    statics_disallow_path = tmp_path / \"static_disallow\"\n\n    statics_path.mkdir()\n    statics_disallow_path.mkdir()\n\n    static_index_file = statics_path / \"index.html\"\n    statics_disallow_path_index_file = statics_disallow_path / \"index.html\"\n    static_file = tmp_path / \"static1.txt\"\n\n    static_index_file.write_text(\"<h1>Hello</h1>\")\n    statics_disallow_path_index_file.write_text(\"<h1>Private</h1>\")\n    static_file.write_text(\"Private\")\n\n    app = StaticFiles(directory=statics_path)\n\n    # We can't test this with 'httpx', so we test the app directly here.\n    path = app.get_path({\"path\": \"/../static1.txt\"})\n    with pytest.raises(HTTPException) as exc_info:\n        anyio.run(app.get_response, path, {\"method\": \"GET\"})\n\n    assert exc_info.value.status_code == 404\n    assert exc_info.value.detail == \"Not Found\"\n\n    path = app.get_path({\"path\": \"/../static_disallow/index.html\"})\n    with pytest.raises(HTTPException) as exc_info:\n        anyio.run(app.get_response, path, {\"method\": \"GET\"})\n\n    assert exc_info.value.status_code == 404\n    assert exc_info.value.detail == \"Not Found\"\n\n\ndef test_staticfiles_self_symlinks(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    statics_path = tmp_path / \"statics\"\n    statics_path.mkdir()\n\n    source_file_path = statics_path / \"index.html\"\n    source_file_path.write_text(\"<h1>Hello</h1>\", encoding=\"utf-8\")\n\n    statics_symlink_path = tmp_path / \"statics_symlink\"\n    statics_symlink_path.symlink_to(statics_path)\n\n    app = StaticFiles(directory=statics_symlink_path, follow_symlink=True)\n    client = test_client_factory(app)\n\n    response = client.get(\"/index.html\")\n    assert response.url == \"http://testserver/index.html\"\n    assert response.status_code == 200\n    assert response.text == \"<h1>Hello</h1>\"\n\n\ndef test_staticfiles_relative_directory_symlinks(test_client_factory: TestClientFactory) -> None:\n    app = StaticFiles(directory=\"tests/statics\", follow_symlink=True)\n    client = test_client_factory(app)\n    response = client.get(\"/example.txt\")\n    assert response.status_code == 200\n    assert response.text == \"123\\n\"\n"
  },
  {
    "path": "tests/test_status.py",
    "content": "import importlib\n\nimport pytest\n\n\n@pytest.mark.parametrize(\n    \"constant,msg\",\n    (\n        (\n            \"HTTP_413_REQUEST_ENTITY_TOO_LARGE\",\n            \"'HTTP_413_REQUEST_ENTITY_TOO_LARGE' is deprecated. Use 'HTTP_413_CONTENT_TOO_LARGE' instead.\",\n        ),\n        (\n            \"HTTP_414_REQUEST_URI_TOO_LONG\",\n            \"'HTTP_414_REQUEST_URI_TOO_LONG' is deprecated. Use 'HTTP_414_URI_TOO_LONG' instead.\",\n        ),\n    ),\n)\ndef test_deprecated_types(constant: str, msg: str) -> None:\n    with pytest.warns(DeprecationWarning) as record:\n        getattr(importlib.import_module(\"starlette.status\"), constant)\n        assert len(record) == 1\n        assert msg in str(record.list[0])\n\n\ndef test_unknown_status() -> None:\n    with pytest.raises(\n        AttributeError,\n        match=\"module 'starlette.status' has no attribute 'HTTP_999_UNKNOWN_STATUS_CODE'\",\n    ):\n        getattr(importlib.import_module(\"starlette.status\"), \"HTTP_999_UNKNOWN_STATUS_CODE\")\n"
  },
  {
    "path": "tests/test_templates.py",
    "content": "from __future__ import annotations\n\nimport os\nfrom pathlib import Path\n\nimport jinja2\nimport pytest\n\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import Route\nfrom starlette.templating import Jinja2Templates\nfrom tests.types import TestClientFactory\n\n\ndef test_templates(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<html>Hello, <a href='{{ url_for('homepage') }}'>world</a></html>\")\n\n    async def homepage(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"index.html\")\n\n    app = Starlette(debug=True, routes=[Route(\"/\", endpoint=homepage)])\n    templates = Jinja2Templates(directory=str(tmpdir))\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"<html>Hello, <a href='http://testserver/'>world</a></html>\"\n    assert response.template.name == \"index.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\"}  # type: ignore\n\n\ndef test_templates_autoescape(tmp_path: Path) -> None:\n    path = tmp_path / \"index.html\"\n    path.write_text(\"Hello, {{ name }}\")\n\n    templates = Jinja2Templates(directory=tmp_path)\n    template = templates.get_template(\"index.html\")\n    assert (\n        template.render(name=\"<script>alert('XSS')</script>\")\n        == \"Hello, &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;\"\n    )\n\n\ndef test_calls_context_processors(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    path = tmp_path / \"index.html\"\n    path.write_text(\"<html>Hello {{ username }}</html>\")\n\n    async def homepage(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"index.html\")\n\n    def hello_world_processor(request: Request) -> dict[str, str]:\n        return {\"username\": \"World\"}\n\n    app = Starlette(\n        debug=True,\n        routes=[Route(\"/\", endpoint=homepage)],\n    )\n    templates = Jinja2Templates(\n        directory=tmp_path,\n        context_processors=[\n            hello_world_processor,\n        ],\n    )\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"<html>Hello World</html>\"\n    assert response.template.name == \"index.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\", \"username\"}  # type: ignore\n\n\ndef test_template_with_middleware(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<html>Hello, <a href='{{ url_for('homepage') }}'>world</a></html>\")\n\n    async def homepage(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"index.html\")\n\n    class CustomMiddleware(BaseHTTPMiddleware):\n        async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:\n            return await call_next(request)\n\n    app = Starlette(\n        debug=True,\n        routes=[Route(\"/\", endpoint=homepage)],\n        middleware=[Middleware(CustomMiddleware)],\n    )\n    templates = Jinja2Templates(directory=str(tmpdir))\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"<html>Hello, <a href='http://testserver/'>world</a></html>\"\n    assert response.template.name == \"index.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\"}  # type: ignore\n\n\ndef test_templates_with_directories(tmp_path: Path, test_client_factory: TestClientFactory) -> None:\n    dir_a = tmp_path.resolve() / \"a\"\n    dir_a.mkdir()\n    template_a = dir_a / \"template_a.html\"\n    template_a.write_text(\"<html><a href='{{ url_for('page_a') }}'></a> a</html>\")\n\n    async def page_a(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"template_a.html\")\n\n    dir_b = tmp_path.resolve() / \"b\"\n    dir_b.mkdir()\n    template_b = dir_b / \"template_b.html\"\n    template_b.write_text(\"<html><a href='{{ url_for('page_b') }}'></a> b</html>\")\n\n    async def page_b(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"template_b.html\")\n\n    app = Starlette(\n        debug=True,\n        routes=[Route(\"/a\", endpoint=page_a), Route(\"/b\", endpoint=page_b)],\n    )\n    templates = Jinja2Templates(directory=[dir_a, dir_b])\n\n    client = test_client_factory(app)\n    response = client.get(\"/a\")\n    assert response.text == \"<html><a href='http://testserver/a'></a> a</html>\"\n    assert response.template.name == \"template_a.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\"}  # type: ignore\n\n    response = client.get(\"/b\")\n    assert response.text == \"<html><a href='http://testserver/b'></a> b</html>\"\n    assert response.template.name == \"template_b.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\"}  # type: ignore\n\n\ndef test_templates_require_directory_or_environment() -> None:\n    with pytest.raises(AssertionError, match=\"either 'directory' or 'env' arguments must be passed\"):\n        Jinja2Templates()  # type: ignore[call-overload]\n\n\ndef test_templates_require_directory_or_environment_not_both() -> None:\n    with pytest.raises(AssertionError, match=\"either 'directory' or 'env' arguments must be passed\"):\n        Jinja2Templates(directory=\"dir\", env=jinja2.Environment())  # type: ignore[call-overload]\n\n\ndef test_templates_with_directory(tmpdir: Path) -> None:\n    path = os.path.join(tmpdir, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"Hello\")\n\n    templates = Jinja2Templates(directory=str(tmpdir))\n    template = templates.get_template(\"index.html\")\n    assert template.render({}) == \"Hello\"\n\n\ndef test_templates_with_environment(tmpdir: Path, test_client_factory: TestClientFactory) -> None:\n    path = os.path.join(tmpdir, \"index.html\")\n    with open(path, \"w\") as file:\n        file.write(\"<html>Hello, <a href='{{ url_for('homepage') }}'>world</a></html>\")\n\n    async def homepage(request: Request) -> Response:\n        return templates.TemplateResponse(request, \"index.html\")\n\n    env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(tmpdir)))\n    app = Starlette(\n        debug=True,\n        routes=[Route(\"/\", endpoint=homepage)],\n    )\n    templates = Jinja2Templates(env=env)\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"<html>Hello, <a href='http://testserver/'>world</a></html>\"\n    assert response.template.name == \"index.html\"  # type: ignore\n    assert set(response.context.keys()) == {\"request\"}  # type: ignore\n"
  },
  {
    "path": "tests/test_testclient.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport sys\nfrom asyncio import Task, current_task as asyncio_current_task\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\nfrom typing import Any\n\nimport anyio\nimport anyio.lowlevel\nimport pytest\nimport sniffio\nimport trio.lowlevel\n\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, RedirectResponse, Response\nfrom starlette.routing import Route\nfrom starlette.testclient import ASGIInstance, TestClient\nfrom starlette.types import ASGIApp, Receive, Scope, Send\nfrom starlette.websockets import WebSocket, WebSocketDisconnect\nfrom tests.types import TestClientFactory\n\n\ndef mock_service_endpoint(request: Request) -> JSONResponse:\n    return JSONResponse({\"mock\": \"example\"})\n\n\nmock_service = Starlette(routes=[Route(\"/\", endpoint=mock_service_endpoint)])\n\n\ndef current_task() -> Task[Any] | trio.lowlevel.Task:\n    # anyio's TaskInfo comparisons are invalid after their associated native\n    # task object is GC'd https://github.com/agronholm/anyio/issues/324\n    asynclib_name = sniffio.current_async_library()\n    if asynclib_name == \"trio\":\n        return trio.lowlevel.current_task()\n\n    if asynclib_name == \"asyncio\":\n        task = asyncio_current_task()\n        if task is None:\n            raise RuntimeError(\"must be called from a running task\")  # pragma: no cover\n        return task\n    raise RuntimeError(f\"unsupported asynclib={asynclib_name}\")  # pragma: no cover\n\n\ndef test_use_testclient_in_endpoint(test_client_factory: TestClientFactory) -> None:\n    \"\"\"\n    We should be able to use the test client within applications.\n\n    This is useful if we need to mock out other services,\n    during tests or in development.\n    \"\"\"\n\n    def homepage(request: Request) -> JSONResponse:\n        client = test_client_factory(mock_service)\n        response = client.get(\"/\")\n        return JSONResponse(response.json())\n\n    app = Starlette(routes=[Route(\"/\", endpoint=homepage)])\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() == {\"mock\": \"example\"}\n\n\ndef test_testclient_headers_behavior() -> None:\n    \"\"\"\n    We should be able to use the test client with user defined headers.\n\n    This is useful if we need to set custom headers for authentication\n    during tests or in development.\n    \"\"\"\n\n    client = TestClient(mock_service)\n    assert client.headers.get(\"user-agent\") == \"testclient\"\n\n    client = TestClient(mock_service, headers={\"user-agent\": \"non-default-agent\"})\n    assert client.headers.get(\"user-agent\") == \"non-default-agent\"\n\n    client = TestClient(mock_service, headers={\"Authentication\": \"Bearer 123\"})\n    assert client.headers.get(\"user-agent\") == \"testclient\"\n    assert client.headers.get(\"Authentication\") == \"Bearer 123\"\n\n\ndef test_use_testclient_as_contextmanager(test_client_factory: TestClientFactory, anyio_backend_name: str) -> None:\n    \"\"\"\n    This test asserts a number of properties that are important for an\n    app level task_group\n    \"\"\"\n    counter = itertools.count()\n    identity_runvar = anyio.lowlevel.RunVar[int](\"identity_runvar\")\n\n    def get_identity() -> int:\n        try:\n            return identity_runvar.get()\n        except LookupError:\n            token = next(counter)\n            identity_runvar.set(token)\n            return token\n\n    startup_task = object()\n    startup_loop = None\n    shutdown_task = object()\n    shutdown_loop = None\n\n    @asynccontextmanager\n    async def lifespan_context(app: Starlette) -> AsyncGenerator[None, None]:\n        nonlocal startup_task, startup_loop, shutdown_task, shutdown_loop\n\n        startup_task = current_task()\n        startup_loop = get_identity()\n        async with anyio.create_task_group():\n            yield\n        shutdown_task = current_task()\n        shutdown_loop = get_identity()\n\n    async def loop_id(request: Request) -> JSONResponse:\n        return JSONResponse(get_identity())\n\n    app = Starlette(\n        lifespan=lifespan_context,\n        routes=[Route(\"/loop_id\", endpoint=loop_id)],\n    )\n\n    client = test_client_factory(app)\n\n    with client:\n        # within a TestClient context every async request runs in the same thread\n        assert client.get(\"/loop_id\").json() == 0\n        assert client.get(\"/loop_id\").json() == 0\n\n    # that thread is also the same as the lifespan thread\n    assert startup_loop == 0\n    assert shutdown_loop == 0\n\n    # lifespan events run in the same task, this is important because a task\n    # group must be entered and exited in the same task.\n    assert startup_task is shutdown_task\n\n    # outside the TestClient context, new requests continue to spawn in new\n    # event loops in new threads\n    assert client.get(\"/loop_id\").json() == 1\n    assert client.get(\"/loop_id\").json() == 2\n\n    first_task = startup_task\n\n    with client:\n        # the TestClient context can be re-used, starting a new lifespan task\n        # in a new thread\n        assert client.get(\"/loop_id\").json() == 3\n        assert client.get(\"/loop_id\").json() == 3\n\n    assert startup_loop == 3\n    assert shutdown_loop == 3\n\n    # lifespan events still run in the same task, with the context but...\n    assert startup_task is shutdown_task\n\n    # ... the second TestClient context creates a new lifespan task.\n    assert first_task is not startup_task\n\n\ndef test_error_on_startup(test_client_factory: TestClientFactory) -> None:\n    @asynccontextmanager\n    async def lifespan(app: Starlette) -> AsyncGenerator[None, None]:\n        raise RuntimeError(\"Startup error\")\n        yield\n\n    startup_error_app = Starlette(lifespan=lifespan)\n\n    with pytest.raises(RuntimeError, match=\"Startup error\"):\n        with test_client_factory(startup_error_app):\n            pass  # pragma: no cover\n\n\ndef test_exception_in_middleware(test_client_factory: TestClientFactory) -> None:\n    class MiddlewareException(Exception):\n        pass\n\n    class BrokenMiddleware:\n        def __init__(self, app: ASGIApp):\n            self.app = app\n\n        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:\n            raise MiddlewareException()\n\n    broken_middleware = Starlette(middleware=[Middleware(BrokenMiddleware)])\n\n    with pytest.raises(MiddlewareException):\n        with test_client_factory(broken_middleware):\n            pass  # pragma: no cover\n\n\ndef test_testclient_asgi2(test_client_factory: TestClientFactory) -> None:\n    def app(scope: Scope) -> ASGIInstance:\n        async def inner(receive: Receive, send: Send) -> None:\n            await send(\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": 200,\n                    \"headers\": [[b\"content-type\", b\"text/plain\"]],\n                }\n            )\n            await send({\"type\": \"http.response.body\", \"body\": b\"Hello, world!\"})\n\n        return inner\n\n    client = test_client_factory(app)  # type: ignore\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n\n\ndef test_testclient_asgi3(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        await send(\n            {\n                \"type\": \"http.response.start\",\n                \"status\": 200,\n                \"headers\": [[b\"content-type\", b\"text/plain\"]],\n            }\n        )\n        await send({\"type\": \"http.response.body\", \"body\": b\"Hello, world!\"})\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.text == \"Hello, world!\"\n\n\ndef test_websocket_blocking_receive(test_client_factory: TestClientFactory) -> None:\n    def app(scope: Scope) -> ASGIInstance:\n        async def respond(websocket: WebSocket) -> None:\n            await websocket.send_json({\"message\": \"test\"})\n\n        async def asgi(receive: Receive, send: Send) -> None:\n            websocket = WebSocket(scope, receive=receive, send=send)\n            await websocket.accept()\n            async with anyio.create_task_group() as task_group:\n                task_group.start_soon(respond, websocket)\n                try:\n                    # this will block as the client does not send us data\n                    # it should not prevent `respond` from executing though\n                    await websocket.receive_json()\n                except WebSocketDisconnect:\n                    pass\n\n        return asgi\n\n    client = test_client_factory(app)  # type: ignore\n    with client.websocket_connect(\"/\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"message\": \"test\"}\n\n\ndef test_websocket_not_block_on_close(test_client_factory: TestClientFactory) -> None:\n    cancelled = False\n\n    def app(scope: Scope) -> ASGIInstance:\n        async def asgi(receive: Receive, send: Send) -> None:\n            nonlocal cancelled\n            try:\n                websocket = WebSocket(scope, receive=receive, send=send)\n                await websocket.accept()\n                await anyio.sleep_forever()\n            except anyio.get_cancelled_exc_class():\n                cancelled = True\n                raise\n\n        return asgi\n\n    client = test_client_factory(app)  # type: ignore\n    with client.websocket_connect(\"/\"):\n        ...\n    assert cancelled\n\n\ndef test_client(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        client = scope.get(\"client\")\n        assert client is not None\n        host, port = client\n        response = JSONResponse({\"host\": host, \"port\": port})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    assert response.json() == {\"host\": \"testclient\", \"port\": 50000}\n\n\ndef test_client_custom_client(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        client = scope.get(\"client\")\n        assert client is not None\n        host, port = client\n        response = JSONResponse({\"host\": host, \"port\": port})\n        await response(scope, receive, send)\n\n    client = test_client_factory(app, client=(\"192.168.0.1\", 3000))\n    response = client.get(\"/\")\n    assert response.json() == {\"host\": \"192.168.0.1\", \"port\": 3000}\n\n\n@pytest.mark.parametrize(\"param\", (\"2020-07-14T00:00:00+00:00\", \"España\", \"voilà\"))\ndef test_query_params(test_client_factory: TestClientFactory, param: str) -> None:\n    def homepage(request: Request) -> Response:\n        return Response(request.query_params[\"param\"])\n\n    app = Starlette(routes=[Route(\"/\", endpoint=homepage)])\n    client = test_client_factory(app)\n    response = client.get(\"/\", params={\"param\": param})\n    assert response.text == param\n\n\n@pytest.mark.parametrize(\n    \"domain, ok\",\n    [\n        pytest.param(\n            \"testserver\",\n            True,\n            marks=[\n                pytest.mark.xfail(\n                    sys.version_info < (3, 11),\n                    reason=\"Fails due to domain handling in http.cookiejar module (see #2152)\",\n                ),\n            ],\n        ),\n        (\"testserver.local\", True),\n        (\"localhost\", False),\n        (\"example.com\", False),\n    ],\n)\ndef test_domain_restricted_cookies(test_client_factory: TestClientFactory, domain: str, ok: bool) -> None:\n    \"\"\"\n    Test that test client discards domain restricted cookies which do not match the\n    base_url of the testclient (`http://testserver` by default).\n\n    The domain `testserver.local` works because the Python http.cookiejar module derives\n    the \"effective domain\" by appending `.local` to non-dotted request domains\n    in accordance with RFC 2965.\n    \"\"\"\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(\"Hello, world!\", media_type=\"text/plain\")\n        response.set_cookie(\n            \"mycookie\",\n            \"myvalue\",\n            path=\"/\",\n            domain=domain,\n        )\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/\")\n    cookie_set = len(response.cookies) == 1\n    assert cookie_set == ok\n\n\ndef test_forward_follow_redirects(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        if \"/ok\" in scope[\"path\"]:\n            response = Response(\"ok\")\n        else:\n            response = RedirectResponse(\"/ok\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app, follow_redirects=True)\n    response = client.get(\"/\")\n    assert response.status_code == 200\n\n\ndef test_forward_nofollow_redirects(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = RedirectResponse(\"/ok\")\n        await response(scope, receive, send)\n\n    client = test_client_factory(app, follow_redirects=False)\n    response = client.get(\"/\")\n    assert response.status_code == 307\n\n\ndef test_with_duplicate_headers(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> JSONResponse:\n        return JSONResponse({\"x-token\": request.headers.getlist(\"x-token\")})\n\n    app = Starlette(routes=[Route(\"/\", endpoint=homepage)])\n    client = test_client_factory(app)\n    response = client.get(\"/\", headers=[(\"x-token\", \"foo\"), (\"x-token\", \"bar\")])\n    assert response.json() == {\"x-token\": [\"foo\", \"bar\"]}\n\n\ndef test_merge_url(test_client_factory: TestClientFactory) -> None:\n    def homepage(request: Request) -> Response:\n        return Response(request.url.path)\n\n    app = Starlette(routes=[Route(\"/api/v1/bar\", endpoint=homepage)])\n    client = test_client_factory(app, base_url=\"http://testserver/api/v1/\")\n    response = client.get(\"/bar\")\n    assert response.text == \"/api/v1/bar\"\n\n\ndef test_raw_path_with_querystring(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        response = Response(scope.get(\"raw_path\"))\n        await response(scope, receive, send)\n\n    client = test_client_factory(app)\n    response = client.get(\"/hello-world\", params={\"foo\": \"bar\"})\n    assert response.content == b\"/hello-world\"\n\n\ndef test_websocket_raw_path_without_params(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        raw_path = scope.get(\"raw_path\")\n        assert raw_path is not None\n        await websocket.send_bytes(raw_path)\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/hello-world\", params={\"foo\": \"bar\"}) as websocket:\n        data = websocket.receive_bytes()\n        assert data == b\"/hello-world\"\n\n\ndef test_timeout_deprecation() -> None:\n    with pytest.deprecated_call(match=\"You should not use the 'timeout' argument with the TestClient.\"):\n        client = TestClient(mock_service)\n        client.get(\"/\", timeout=1)\n"
  },
  {
    "path": "tests/test_websockets.py",
    "content": "import sys\nfrom collections.abc import AsyncGenerator, MutableMapping\nfrom pathlib import Path\nfrom typing import Any\n\nimport anyio\nimport pytest\nfrom anyio.abc import ObjectReceiveStream, ObjectSendStream\n\nfrom starlette import status\nfrom starlette.responses import FileResponse, Response, StreamingResponse\nfrom starlette.testclient import WebSocketDenialResponse\nfrom starlette.types import Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState\nfrom tests.types import TestClientFactory\n\n\ndef test_websocket_url(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.send_json({\"url\": str(websocket.url)})\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/123?a=abc\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"url\": \"ws://testserver/123?a=abc\"}\n\n\ndef test_websocket_binary_json(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        message = await websocket.receive_json(mode=\"binary\")\n        await websocket.send_json(message, mode=\"binary\")\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/123?a=abc\") as websocket:\n        websocket.send_json({\"test\": \"data\"}, mode=\"binary\")\n        data = websocket.receive_json(mode=\"binary\")\n        assert data == {\"test\": \"data\"}\n\n\ndef test_websocket_ensure_unicode_on_send_json(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n\n        await websocket.accept()\n        message = await websocket.receive_json(mode=\"text\")\n        await websocket.send_json(message, mode=\"text\")\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/123?a=abc\") as websocket:\n        websocket.send_json({\"test\": \"数据\"}, mode=\"text\")\n        data = websocket.receive_text()\n        assert data == '{\"test\":\"数据\"}'\n\n\ndef test_websocket_query_params(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        query_params = dict(websocket.query_params)\n        await websocket.accept()\n        await websocket.send_json({\"params\": query_params})\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/?a=abc&b=456\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"params\": {\"a\": \"abc\", \"b\": \"456\"}}\n\n\n@pytest.mark.skipif(\n    any(module in sys.modules for module in (\"brotli\", \"brotlicffi\")),\n    reason='urllib3 includes \"br\" to the \"accept-encoding\" headers.',\n)\ndef test_websocket_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        headers = dict(websocket.headers)\n        await websocket.accept()\n        await websocket.send_json({\"headers\": headers})\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        expected_headers = {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate\",\n            \"connection\": \"upgrade\",\n            \"host\": \"testserver\",\n            \"user-agent\": \"testclient\",\n            \"sec-websocket-key\": \"testserver==\",\n            \"sec-websocket-version\": \"13\",\n        }\n        data = websocket.receive_json()\n        assert data == {\"headers\": expected_headers}\n\n\ndef test_websocket_port(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.send_json({\"port\": websocket.url.port})\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"ws://example.com:123/123?a=abc\") as websocket:\n        data = websocket.receive_json()\n        assert data == {\"port\": 123}\n\n\ndef test_websocket_send_and_receive_text(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        data = await websocket.receive_text()\n        await websocket.send_text(\"Message was: \" + data)\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_text(\"Hello, world!\")\n        data = websocket.receive_text()\n        assert data == \"Message was: Hello, world!\"\n\n\ndef test_websocket_send_and_receive_bytes(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        data = await websocket.receive_bytes()\n        await websocket.send_bytes(b\"Message was: \" + data)\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_bytes(b\"Hello, world!\")\n        data = websocket.receive_bytes()\n        assert data == b\"Message was: Hello, world!\"\n\n\ndef test_websocket_send_and_receive_json(\n    test_client_factory: TestClientFactory,\n) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        data = await websocket.receive_json()\n        await websocket.send_json({\"message\": data})\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_json({\"hello\": \"world\"})\n        data = websocket.receive_json()\n        assert data == {\"message\": {\"hello\": \"world\"}}\n\n\ndef test_websocket_iter_text(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        async for data in websocket.iter_text():\n            await websocket.send_text(\"Message was: \" + data)\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_text(\"Hello, world!\")\n        data = websocket.receive_text()\n        assert data == \"Message was: Hello, world!\"\n\n\ndef test_websocket_iter_bytes(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        async for data in websocket.iter_bytes():\n            await websocket.send_bytes(b\"Message was: \" + data)\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_bytes(b\"Hello, world!\")\n        data = websocket.receive_bytes()\n        assert data == b\"Message was: Hello, world!\"\n\n\ndef test_websocket_iter_json(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        async for data in websocket.iter_json():\n            await websocket.send_json({\"message\": data})\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_json({\"hello\": \"world\"})\n        data = websocket.receive_json()\n        assert data == {\"message\": {\"hello\": \"world\"}}\n\n\ndef test_websocket_concurrency_pattern(test_client_factory: TestClientFactory) -> None:\n    stream_send: ObjectSendStream[MutableMapping[str, Any]]\n    stream_receive: ObjectReceiveStream[MutableMapping[str, Any]]\n    stream_send, stream_receive = anyio.create_memory_object_stream()\n\n    async def reader(websocket: WebSocket) -> None:\n        async with stream_send:\n            async for data in websocket.iter_json():\n                await stream_send.send(data)\n\n    async def writer(websocket: WebSocket) -> None:\n        async with stream_receive:\n            async for message in stream_receive:\n                await websocket.send_json(message)\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        async with anyio.create_task_group() as task_group:\n            task_group.start_soon(reader, websocket)\n            await writer(websocket)\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.send_json({\"hello\": \"world\"})\n        data = websocket.receive_json()\n        assert data == {\"hello\": \"world\"}\n\n\ndef test_client_close(test_client_factory: TestClientFactory) -> None:\n    close_code = None\n    close_reason = None\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        nonlocal close_code, close_reason\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        try:\n            await websocket.receive_text()\n        except WebSocketDisconnect as exc:\n            close_code = exc.code\n            close_reason = exc.reason\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        websocket.close(code=status.WS_1001_GOING_AWAY, reason=\"Going Away\")\n    assert close_code == status.WS_1001_GOING_AWAY\n    assert close_reason == \"Going Away\"\n\n\n@pytest.mark.anyio\nasync def test_client_disconnect_on_send() -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.send_text(\"Hello, world!\")\n\n    async def receive() -> Message:\n        return {\"type\": \"websocket.connect\"}\n\n    async def send(message: Message) -> None:\n        if message[\"type\"] == \"websocket.accept\":\n            return\n        # Simulate the exception the server would send to the application when the client disconnects.\n        raise OSError\n\n    with pytest.raises(WebSocketDisconnect) as ctx:\n        await app({\"type\": \"websocket\", \"path\": \"/\"}, receive, send)\n    assert ctx.value.code == status.WS_1006_ABNORMAL_CLOSURE\n\n\ndef test_application_close(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.close(status.WS_1001_GOING_AWAY)\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        with pytest.raises(WebSocketDisconnect) as exc:\n            websocket.receive_text()\n        assert exc.value.code == status.WS_1001_GOING_AWAY\n\n\ndef test_rejected_connection(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        await websocket.close(status.WS_1001_GOING_AWAY)\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDisconnect) as exc:\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n    assert exc.value.code == status.WS_1001_GOING_AWAY\n\n\ndef test_send_denial_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        response = Response(status_code=404, content=\"foo\")\n        await websocket.send_denial_response(response)\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDenialResponse) as exc:\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n    assert exc.value.status_code == 404\n    assert exc.value.content == b\"foo\"\n\n\ndef test_send_denial_response_with_streaming_response(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        message = await websocket.receive()\n        assert message == {\"type\": \"websocket.connect\"}\n\n        async def content() -> AsyncGenerator[bytes]:\n            yield b\"hello\"\n            yield b\"world\"\n\n        await websocket.send_denial_response(StreamingResponse(content(), status_code=403))\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDenialResponse) as exc:\n        with client.websocket_connect(\"/\"):\n            ...  # pragma: no cover\n    assert exc.value.status_code == 403\n    assert exc.value.content == b\"helloworld\"\n\n\ndef test_send_denial_response_with_file_response(test_client_factory: TestClientFactory, tmp_path: Path) -> None:\n    file_path = tmp_path / \"denial.txt\"\n    file_path.write_text(\"test content\")\n\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        await websocket.send_denial_response(FileResponse(file_path, status_code=401))\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDenialResponse) as exc:\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n    assert exc.value.status_code == 401\n    assert exc.value.content == b\"test content\"\n\n\ndef test_send_response_multi(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        await websocket.send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": 404,\n                \"headers\": [(b\"content-type\", b\"text/plain\"), (b\"foo\", b\"bar\")],\n            }\n        )\n        await websocket.send({\"type\": \"websocket.http.response.body\", \"body\": b\"hard\", \"more_body\": True})\n        await websocket.send({\"type\": \"websocket.http.response.body\", \"body\": b\"body\"})\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDenialResponse) as exc:\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n    assert exc.value.status_code == 404\n    assert exc.value.content == b\"hardbody\"\n    assert exc.value.headers[\"foo\"] == \"bar\"\n\n\ndef test_send_response_unsupported(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        del scope[\"extensions\"][\"websocket.http.response\"]\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        response = Response(status_code=404, content=\"foo\")\n        with pytest.raises(\n            RuntimeError,\n            match=\"The server doesn't support the Websocket Denial Response extension.\",\n        ):\n            await websocket.send_denial_response(response)\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with pytest.raises(WebSocketDisconnect) as exc:\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n    assert exc.value.code == status.WS_1000_NORMAL_CLOSURE\n\n\ndef test_send_response_duplicate_start(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        msg = await websocket.receive()\n        assert msg == {\"type\": \"websocket.connect\"}\n        response = Response(status_code=404, content=\"foo\")\n        await websocket.send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": response.status_code,\n                \"headers\": response.raw_headers,\n            }\n        )\n        await websocket.send(\n            {\n                \"type\": \"websocket.http.response.start\",\n                \"status\": response.status_code,\n                \"headers\": response.raw_headers,\n            }\n        )\n\n    client = test_client_factory(app)\n    with pytest.raises(\n        RuntimeError,\n        match=(\"Expected ASGI message \\\"websocket.http.response.body\\\", but got 'websocket.http.response.start'\"),\n    ):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_subprotocol(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        assert websocket[\"subprotocols\"] == [\"soap\", \"wamp\"]\n        await websocket.accept(subprotocol=\"wamp\")\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\", subprotocols=[\"soap\", \"wamp\"]) as websocket:\n        assert websocket.accepted_subprotocol == \"wamp\"\n\n\ndef test_additional_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept(headers=[(b\"additional\", b\"header\")])\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        assert websocket.extra_headers == [(b\"additional\", b\"header\")]\n\n\ndef test_no_additional_headers(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        assert websocket.extra_headers == []\n\n\ndef test_websocket_exception(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        assert False\n\n    client = test_client_factory(app)\n    with pytest.raises(AssertionError):\n        with client.websocket_connect(\"/123?a=abc\"):\n            pass  # pragma: no cover\n\n\ndef test_duplicate_close(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.close()\n        await websocket.close()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_duplicate_disconnect(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        message = await websocket.receive()\n        assert message[\"type\"] == \"websocket.disconnect\"\n        message = await websocket.receive()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\") as websocket:\n            websocket.close()\n\n\ndef test_websocket_scope_interface() -> None:\n    \"\"\"\n    A WebSocket can be instantiated with a scope, and presents a `Mapping`\n    interface.\n    \"\"\"\n\n    async def mock_receive() -> Message:  # type: ignore\n        ...  # pragma: no cover\n\n    async def mock_send(message: Message) -> None: ...  # pragma: no cover\n\n    websocket = WebSocket({\"type\": \"websocket\", \"path\": \"/abc/\", \"headers\": []}, receive=mock_receive, send=mock_send)\n    assert websocket[\"type\"] == \"websocket\"\n    assert dict(websocket) == {\"type\": \"websocket\", \"path\": \"/abc/\", \"headers\": []}\n    assert len(websocket) == 3\n\n    # check __eq__ and __hash__\n    assert websocket != WebSocket(\n        {\"type\": \"websocket\", \"path\": \"/abc/\", \"headers\": []},\n        receive=mock_receive,\n        send=mock_send,\n    )\n    assert websocket == websocket\n    assert websocket in {websocket}\n    assert {websocket} == {websocket}\n\n\ndef test_websocket_close_reason(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.close(code=status.WS_1001_GOING_AWAY, reason=\"Going Away\")\n\n    client = test_client_factory(app)\n    with client.websocket_connect(\"/\") as websocket:\n        with pytest.raises(WebSocketDisconnect) as exc:\n            websocket.receive_text()\n        assert exc.value.code == status.WS_1001_GOING_AWAY\n        assert exc.value.reason == \"Going Away\"\n\n\ndef test_send_json_invalid_mode(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.send_json({}, mode=\"invalid\")\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_receive_json_invalid_mode(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.receive_json(mode=\"invalid\")\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_receive_text_before_accept(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.receive_text()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_receive_bytes_before_accept(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.receive_bytes()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_receive_json_before_accept(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.receive_json()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_send_before_accept(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.send({\"type\": \"websocket.send\"})\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_send_wrong_message_type(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.send({\"type\": \"websocket.accept\"})\n        await websocket.send({\"type\": \"websocket.accept\"})\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\"):\n            pass  # pragma: no cover\n\n\ndef test_receive_before_accept(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        websocket.client_state = WebSocketState.CONNECTING\n        await websocket.receive()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\") as websocket:\n            websocket.send({\"type\": \"websocket.send\"})\n\n\ndef test_receive_wrong_message_type(test_client_factory: TestClientFactory) -> None:\n    async def app(scope: Scope, receive: Receive, send: Send) -> None:\n        websocket = WebSocket(scope, receive=receive, send=send)\n        await websocket.accept()\n        await websocket.receive()\n\n    client = test_client_factory(app)\n    with pytest.raises(RuntimeError):\n        with client.websocket_connect(\"/\") as websocket:\n            websocket.send({\"type\": \"websocket.connect\"})\n"
  },
  {
    "path": "tests/types.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Protocol\n\nimport httpx\n\nfrom starlette.testclient import TestClient\nfrom starlette.types import ASGIApp\n\nif TYPE_CHECKING:\n\n    class TestClientFactory(Protocol):  # pragma: no cover\n        def __call__(\n            self,\n            app: ASGIApp,\n            base_url: str = \"http://testserver\",\n            raise_server_exceptions: bool = True,\n            root_path: str = \"\",\n            cookies: httpx._types.CookieTypes | None = None,\n            headers: dict[str, str] | None = None,\n            follow_redirects: bool = True,\n            client: tuple[str, int] = (\"testclient\", 50000),\n        ) -> TestClient: ...\nelse:  # pragma: no cover\n\n    class TestClientFactory:\n        __test__ = False\n"
  }
]