Showing preview only (603K chars total). Download the full file or copy to clipboard to get everything.
Repository: Kludex/uvicorn
Branch: main
Commit: 02bed6f8c38e
Files: 118
Total size: 570.2 KB
Directory structure:
gitextract_nb8gwzdb/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── 1-issue.yml
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── benchmark.yml
│ ├── main.yml
│ └── publish.yml
├── .gitignore
├── CITATION.cff
├── LICENSE.md
├── README.md
├── docs/
│ ├── CNAME
│ ├── concepts/
│ │ ├── asgi.md
│ │ ├── event-loop.md
│ │ ├── lifespan.md
│ │ └── websockets.md
│ ├── contributing.md
│ ├── deployment/
│ │ ├── docker.md
│ │ └── index.md
│ ├── index.md
│ ├── installation.md
│ ├── overrides/
│ │ ├── main.html
│ │ └── partials/
│ │ ├── nav.html
│ │ └── toc-item.html
│ ├── plugins/
│ │ └── main.py
│ ├── release-notes.md
│ ├── server-behavior.md
│ ├── settings.md
│ └── sponsorship.md
├── mkdocs.yml
├── pyproject.toml
├── scripts/
│ ├── build
│ ├── check
│ ├── coverage
│ ├── docs
│ ├── install
│ ├── lint
│ ├── sync-version
│ └── test
├── tests/
│ ├── __init__.py
│ ├── benchmarks/
│ │ ├── __init__.py
│ │ ├── http.py
│ │ ├── test_http.py
│ │ ├── test_ws.py
│ │ └── ws.py
│ ├── conftest.py
│ ├── custom_loop_utils.py
│ ├── importer/
│ │ ├── __init__.py
│ │ ├── circular_import_a.py
│ │ ├── circular_import_b.py
│ │ ├── raise_import_error.py
│ │ └── test_importer.py
│ ├── middleware/
│ │ ├── __init__.py
│ │ ├── test_logging.py
│ │ ├── test_message_logger.py
│ │ ├── test_proxy_headers.py
│ │ └── test_wsgi.py
│ ├── protocols/
│ │ ├── __init__.py
│ │ ├── test_http.py
│ │ ├── test_utils.py
│ │ └── test_websocket.py
│ ├── response.py
│ ├── supervisors/
│ │ ├── __init__.py
│ │ ├── test_multiprocess.py
│ │ ├── test_reload.py
│ │ └── test_signal.py
│ ├── test_auto_detection.py
│ ├── test_cli.py
│ ├── test_compat.py
│ ├── test_config.py
│ ├── test_default_headers.py
│ ├── test_lifespan.py
│ ├── test_main.py
│ ├── test_server.py
│ ├── test_ssl.py
│ ├── test_subprocess.py
│ └── utils.py
└── uvicorn/
├── __init__.py
├── __main__.py
├── _compat.py
├── _subprocess.py
├── _types.py
├── config.py
├── importer.py
├── lifespan/
│ ├── __init__.py
│ ├── off.py
│ └── on.py
├── logging.py
├── loops/
│ ├── __init__.py
│ ├── asyncio.py
│ ├── auto.py
│ └── uvloop.py
├── main.py
├── middleware/
│ ├── __init__.py
│ ├── asgi2.py
│ ├── message_logger.py
│ ├── proxy_headers.py
│ └── wsgi.py
├── protocols/
│ ├── __init__.py
│ ├── http/
│ │ ├── __init__.py
│ │ ├── auto.py
│ │ ├── flow_control.py
│ │ ├── h11_impl.py
│ │ └── httptools_impl.py
│ ├── utils.py
│ └── websockets/
│ ├── __init__.py
│ ├── auto.py
│ ├── websockets_impl.py
│ ├── websockets_sansio_impl.py
│ └── wsproto_impl.py
├── py.typed
├── server.py
├── supervisors/
│ ├── __init__.py
│ ├── basereload.py
│ ├── multiprocess.py
│ ├── statreload.py
│ └── watchfilesreload.py
└── workers.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/1-issue.yml
================================================
name: Issue
description: Report a bug or unexpected behavior. 🙏
body:
- type: markdown
attributes:
value: Thank you for contributing to Uvicorn! ✊
- type: checkboxes
id: checks
attributes:
label: Initial Checks
description: Just making sure you open a discussion first. 🙏
options:
- label: I confirm this was discussed, and the maintainers suggest I open an issue.
required: true
- label: I'm aware that if I created this issue without a discussion, it may be closed without a response.
required: true
- type: textarea
id: discussion
attributes:
label: Discussion Link
description: |
Please link to the discussion that led to this issue.
If you haven't discussed this issue yet, please do so before opening an issue.
render: Text
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
Please explain what you're seeing and what you would expect to see.
Please provide as much detail as possible to make understanding and solving your problem as quick as possible. 🙏
validations:
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: >
If applicable, please add a self-contained,
[minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example)
demonstrating the bug.
render: Python
- type: textarea
id: version
attributes:
label: Python, Uvicorn & OS Version
description: |
Which version of Python & Uvicorn are you using, and which Operating System?
Please run the following command and copy the output below:
```bash
python -m uvicorn --version
```
render: Text
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: false
contact_links:
- name: Discussions
url: https://github.com/Kludex/uvicorn/discussions
about: The "Discussions" forum is where you want to start. 💖
- name: Chat
url: https://discord.com/invite/SWU73HffbV
about: Our community Discord server. 💬
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thanks for contributing to Uvicorn! 💚
Given this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->
# Summary
<!-- Write a small summary about what is happening here. -->
# Checklist
- [ ] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
- [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
- [ ] I've updated the documentation accordingly.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"
groups:
python-packages:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
groups:
github-actions:
patterns:
- "*"
================================================
FILE: .github/workflows/benchmark.yml
================================================
---
name: CodSpeed
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
id-token: write
contents: read
jobs:
benchmarks:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
with:
python-version: "3.13"
- name: Install dependencies
run: scripts/install
shell: bash
- name: Run the benchmarks
uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4
with:
mode: instrumentation
run: uv run pytest tests/benchmarks/ --codspeed -n 0
================================================
FILE: .github/workflows/main.yml
================================================
---
name: Test Suite
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
tests:
name: "Python ${{ matrix.python-version }} ${{ matrix.os }}"
runs-on: "${{ matrix.os }}"
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
with:
python-version: ${{ matrix.python-version }}
enable-cache: ${{ matrix.os != 'windows-latest' }}
- name: Install dependencies
run: scripts/install
shell: bash
- name: Run linting checks
run: scripts/check
if: "${{ matrix.os == 'ubuntu-latest'}}"
- name: "Build package & docs"
run: scripts/build
shell: bash
- name: "Run tests"
run: scripts/test
shell: bash
- name: "Enforce coverage"
run: scripts/coverage
shell: bash
# https://github.com/marketplace/actions/alls-green#why
check:
if: always()
needs: [tests]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
with:
python-version: "3.11"
enable-cache: true
- name: Install dependencies
run: scripts/install
- name: Build package & docs
run: scripts/build
- name: Upload package distributions
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: package-distributions
path: dist/
- name: Upload documentation
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: documentation
path: site/
pypi-publish:
runs-on: ubuntu-latest
needs: build
if: success() && startsWith(github.ref, 'refs/tags/')
permissions:
id-token: write
environment:
name: pypi
url: https://pypi.org/project/uvicorn
steps:
- name: Download artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: package-distributions
path: dist/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
docs-publish:
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: documentation
path: site/
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- name: Install uv
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
with:
python-version: "3.12"
enable-cache: true
- name: Install dependencies
run: scripts/install
- name: Publish documentation 📚 to GitHub Pages
run: uv run mkdocs gh-deploy --force
docs-cloudflare:
runs-on: ubuntu-latest
needs: build
environment:
name: cloudflare
url: https://uvicorn.dev
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download artifacts
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: documentation
path: site/
- uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >
pages deploy ./site
--project-name uvicorn
--commit-hash ${{ github.sha }}
--branch main
================================================
FILE: .gitignore
================================================
.cache
.coverage
.coverage.*
.mypy_cache/
__pycache__/
uvicorn.egg-info/
venv/
htmlcov/
site/
dist/
.codspeed/
================================================
FILE: CITATION.cff
================================================
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: Uvicorn
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Marcelo
family-names: Trylesinski
email: marcelotryle@gmail.com
- given-names: Tom
family-names: Christie
email: tom@tomchristie.com
repository-code: "https://github.com/Kludex/uvicorn"
url: "https://uvicorn.dev/"
abstract: Uvicorn is an ASGI web server implementation for Python.
keywords:
- asgi
- server
license: BSD-3-Clause
================================================
FILE: LICENSE.md
================================================
Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
<p align="center">
<img width="320" height="320" src="https://raw.githubusercontent.com/tomchristie/uvicorn/main/docs/uvicorn.png" alt='uvicorn'>
</p>
<p align="center">
<em>An ASGI web server, for Python.</em>
</p>
---
[](https://github.com/Kludex/uvicorn/actions)
[](https://pypi.python.org/pypi/uvicorn)
[](https://pypi.org/project/uvicorn)
[](https://discord.gg/RxKUF5JuHs)
---
**Documentation**: [https://uvicorn.dev](https://uvicorn.dev)
**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn)
---
Uvicorn is an ASGI web server implementation for Python.
Until recently Python has lacked a minimal low-level server/application interface for
async frameworks. The [ASGI specification][asgi] fills this gap, and means we're now able to
start building a common set of tooling usable across all async frameworks.
Uvicorn supports HTTP/1.1 and WebSockets.
## Quickstart
Install using `pip`:
```shell
$ pip install uvicorn
```
This will install uvicorn with minimal (pure Python) dependencies.
```shell
$ pip install 'uvicorn[standard]'
```
This will install uvicorn with "Cython-based" dependencies (where possible) and other "optional extras".
In this context, "Cython-based" means the following:
- the event loop `uvloop` will be installed and used if possible.
- the http protocol will be handled by `httptools` if possible.
Moreover, "optional extras" means that:
- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible.
- the `--reload` flag in development mode will use `watchfiles`.
- windows users will have `colorama` installed for the colored logs.
- `python-dotenv` will be installed should you want to use the `--env-file` option.
- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired.
Create an application, in `example.py`:
```python
async def app(scope, receive, send):
assert scope['type'] == 'http'
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
(b'content-type', b'text/plain'),
],
})
await send({
'type': 'http.response.body',
'body': b'Hello, world!',
})
```
Run the server:
```shell
$ uvicorn example:app
```
---
## Why ASGI?
Most well established Python Web frameworks started out as WSGI-based frameworks.
WSGI applications are a single, synchronous callable that takes a request and returns a response.
This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections,
which WSGI doesn't support well.
Having an async concurrency model also allows for options such as lightweight background tasks,
and can be less of a limiting factor for endpoints that have long periods being blocked on network
I/O such as dealing with slow HTTP requests.
---
## Alternative ASGI servers
A strength of the ASGI protocol is that it decouples the server implementation
from the application framework. This allows for an ecosystem of interoperating
webservers and application frameworks.
### Daphne
The first ASGI server implementation, originally developed to power Django Channels, is [the Daphne webserver][daphne].
It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets.
Any of the example applications given here can equally well be run using `daphne` instead.
```
$ pip install daphne
$ daphne app:App
```
### Hypercorn
[Hypercorn][hypercorn] was initially part of the Quart web framework, before
being separated out into a standalone ASGI server.
Hypercorn supports HTTP/1.1, HTTP/2, and WebSockets.
It also supports [the excellent `trio` async framework][trio], as an alternative to `asyncio`.
```
$ pip install hypercorn
$ hypercorn app:App
```
### Mangum
[Mangum][mangum] is an adapter for using ASGI applications with AWS Lambda & API Gateway.
### Granian
[Granian][granian] is an ASGI compatible Rust HTTP server which supports HTTP/2, TLS and WebSockets.
---
<p align="center"><i>Uvicorn is <a href="https://github.com/Kludex/uvicorn/blob/main/LICENSE.md">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>— 🦄 —</p>
[asgi]: https://asgi.readthedocs.io/en/latest/
[daphne]: https://github.com/django/daphne
[hypercorn]: https://github.com/pgjones/hypercorn
[trio]: https://trio.readthedocs.io
[mangum]: https://github.com/jordaneremieff/mangum
[granian]: https://github.com/emmett-framework/granian
================================================
FILE: docs/CNAME
================================================
www.uvicorn.org
================================================
FILE: docs/concepts/asgi.md
================================================
## ASGI
**Uvicorn** uses the [ASGI specification](https://asgi.readthedocs.io/en/latest/) for interacting with an application.
The application should expose an async callable which takes three arguments:
* `scope` - A dictionary containing information about the incoming connection.
* `receive` - A channel on which to receive incoming messages from the server.
* `send` - A channel on which to send outgoing messages to the server.
Two common patterns you might use are either function-based applications:
```python
async def app(scope, receive, send):
assert scope['type'] == 'http'
...
```
Or instance-based applications:
```python
class App:
async def __call__(self, scope, receive, send):
assert scope['type'] == 'http'
...
app = App()
```
It's good practice for applications to raise an exception on scope types
that they do not handle.
The content of the `scope` argument, and the messages expected by `receive` and `send` depend on the protocol being used.
The format for HTTP messages is described in the [ASGI HTTP Message format](https://asgi.readthedocs.io/en/latest/specs/www.html).
### HTTP Scope
An incoming HTTP request might have a connection `scope` like this:
```python
{
'type': 'http',
'scheme': 'http',
'root_path': '',
'server': ('127.0.0.1', 8000),
'http_version': '1.1',
'method': 'GET',
'path': '/',
'headers': [
(b'host', b'127.0.0.1:8000'),
(b'user-agent', b'curl/7.51.0'),
(b'accept', b'*/*')
]
}
```
### HTTP Messages
The instance coroutine communicates back to the server by sending messages to the `send` coroutine.
```python
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
]
})
await send({
'type': 'http.response.body',
'body': b'Hello, world!',
})
```
### Requests & responses
Here's an example that displays the method and path used in the incoming request:
```python
async def app(scope, receive, send):
"""
Echo the method and path back in an HTTP response.
"""
assert scope['type'] == 'http'
body = f'Received {scope["method"]} request to {scope["path"]}'
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
]
})
await send({
'type': 'http.response.body',
'body': body.encode('utf-8'),
})
```
### Reading the request body
You can stream the request body without blocking the asyncio task pool,
by fetching messages from the `receive` coroutine.
```python
async def read_body(receive):
"""
Read and return the entire body from an incoming ASGI message.
"""
body = b''
more_body = True
while more_body:
message = await receive()
body += message.get('body', b'')
more_body = message.get('more_body', False)
return body
async def app(scope, receive, send):
"""
Echo the request body back in an HTTP response.
"""
body = await read_body(receive)
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
(b'content-type', b'text/plain'),
(b'content-length', str(len(body)).encode())
]
})
await send({
'type': 'http.response.body',
'body': body,
})
```
### Streaming responses
You can stream responses by sending multiple `http.response.body` messages to
the `send` coroutine.
```python
import asyncio
async def app(scope, receive, send):
"""
Send a slowly streaming HTTP response back to the client.
"""
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
]
})
for chunk in [b'Hello', b', ', b'world!']:
await send({
'type': 'http.response.body',
'body': chunk,
'more_body': True
})
await asyncio.sleep(1)
await send({
'type': 'http.response.body',
'body': b'',
})
```
---
## Why ASGI?
Most well established Python Web frameworks started out as WSGI-based frameworks.
WSGI applications are a single, synchronous callable that takes a request and returns a response.
This doesn’t allow for long-lived connections, like you get with long-poll HTTP or WebSocket connections,
which WSGI doesn't support well.
Having an async concurrency model also allows for options such as lightweight background tasks,
and can be less of a limiting factor for endpoints that have long periods being blocked on network
I/O such as dealing with slow HTTP requests.
---
## Alternative ASGI servers
A strength of the ASGI protocol is that it decouples the server implementation
from the application framework. This allows for an ecosystem of interoperating
webservers and application frameworks.
### Daphne
The first ASGI server implementation, originally developed to power Django Channels, is
[the Daphne webserver](https://github.com/django/daphne).
It is run widely in production, and supports HTTP/1.1, HTTP/2, and WebSockets.
Any of the example applications given here can equally well be run using `daphne` instead.
```shell
pip install daphne
daphne app:App
```
### Hypercorn
[Hypercorn](https://github.com/pgjones/hypercorn) was initially part of the Quart web framework,
before being separated out into a standalone ASGI server.
Hypercorn supports HTTP/1.1, HTTP/2, HTTP/3 and WebSockets.
```shell
pip install hypercorn
hypercorn app:App
```
---
## ASGI frameworks
You can use Uvicorn, Daphne, or Hypercorn to run any ASGI framework.
For small services you can also write ASGI applications directly.
### Starlette
[Starlette](https://github.com/Kludex/starlette) is a lightweight ASGI framework/toolkit.
It is ideal for building high performance asyncio services, and supports both HTTP and WebSockets.
### Django Channels
The ASGI specification was originally designed for use with [Django Channels](https://channels.readthedocs.io/en/latest/).
Channels is a little different to other ASGI frameworks in that it provides
an asynchronous frontend onto a threaded-framework backend. It allows Django
to support WebSockets, background tasks, and long-running connections,
with application code still running in a standard threaded context.
### Quart
[Quart](https://pgjones.gitlab.io/quart/) is a Flask-like ASGI web framework.
### FastAPI
[**FastAPI**](https://github.com/tiangolo/fastapi) is an API framework based on **Starlette** and **Pydantic**, heavily inspired by previous server versions of **APIStar**.
You write your API function parameters with Python 3.6+ type declarations and get automatic data conversion, data validation, OpenAPI schemas (with JSON Schemas) and interactive API documentation UIs.
### BlackSheep
[BlackSheep](https://www.neoteroi.dev/blacksheep/) is a web framework based on ASGI, inspired by Flask and ASP.NET Core.
Its most distinctive features are built-in support for dependency injection, automatic binding of parameters by request handler's type annotations, and automatic generation of OpenAPI documentation and Swagger UI.
### Falcon
[Falcon](https://falconframework.org) is a minimalist REST and app backend framework for Python, with a focus on reliability, correctness, and performance at scale.
### Muffin
[Muffin](https://github.com/klen/muffin) is a fast, lightweight and asynchronous ASGI web-framework for Python 3.
### Litestar
[Litestar](https://litestar.dev) is a powerful, lightweight and flexible ASGI framework.
It includes everything that's needed to build modern APIs - from data serialization and validation to websockets, ORM integration, session management, authentication and more.
### Panther
[Panther](https://PantherPy.github.io/) is a fast & friendly web framework for building async APIs with Python 3.10+.
It has built-in Document-oriented Database, Caching System, Authentication and Permission Classes, Visual API Monitoring and also supports Websocket, Throttling, Middlewares.
================================================
FILE: docs/concepts/event-loop.md
================================================
# Event Loop
Uvicorn provides two event loop implementations that you can choose from using the [`--loop`](../settings.md#implementation) option:
```bash
uvicorn main:app --loop <auto|asyncio|uvloop>
```
By default, Uvicorn uses `--loop auto`, which automatically selects:
1. **uvloop** - If [uvloop](https://github.com/MagicStack/uvloop) is installed, Uvicorn will use it for maximum performance
2. **asyncio** - If uvloop is not available, Uvicorn falls back to Python's built-in asyncio event loop
Since `uvloop` is not compatible with Windows or PyPy, it is not available on these platforms.
On Windows, the asyncio implementation uses the standard [`ProactorEventLoop`][asyncio.ProactorEventLoop] in single-process mode.
When running with `--reload` or multiple workers, it uses [`SelectorEventLoop`][asyncio.SelectorEventLoop] instead.
??? info "Why can `ProactorEventLoop` fail with multiple processes on Windows?"
If you want to know more about it, you can read the issue [#cpython/122240](https://github.com/python/cpython/issues/122240).
## Custom Event Loop
You can use custom event loop implementations by specifying a module path and function name using the colon notation:
```bash
uvicorn main:app --loop <module>:<function>
```
The function should return a callable that creates a new event loop instance.
### rloop
[rloop](https://github.com/gi0baro/rloop) is an experimental AsyncIO event loop implemented in Rust on top of the [mio](https://github.com/tokio-rs/mio) crate. It aims to provide high performance through Rust's systems programming capabilities.
You can install it with:
=== "pip"
```bash
pip install rloop
```
=== "uv"
```bash
uv add rloop
```
You can run `uvicorn` with `rloop` with the following command:
```bash
uvicorn main:app --loop rloop:new_event_loop
```
!!! warning "Experimental"
rloop is currently **experimental** and **not suited for production usage**. It is only available on **Unix systems**.
### Winloop
[Winloop](https://github.com/Vizonex/Winloop) is an alternative library that brings uvloop-like performance to Windows. Since uvloop is based on libuv and doesn't support Windows, Winloop provides a Windows-compatible implementation with significant performance improvements over the standard Windows event loop policies.
You can install it with:
=== "pip"
```bash
pip install winloop
```
=== "uv"
```bash
uv add winloop
```
You can run `uvicorn` with `Winloop` with the following command:
```bash
uvicorn main:app --loop winloop:new_event_loop
```
================================================
FILE: docs/concepts/lifespan.md
================================================
Since Uvicorn is an ASGI server, it supports the
[ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html).
This allows you to run **startup** and **shutdown** events for your application.
The lifespan protocol is useful for initializing resources that need to be available throughout
the lifetime of the application, such as database connections, caches, or other services.
Keep in mind that the lifespan is executed **only once per application instance**. If you have
multiple workers, each worker will execute the lifespan independently.
## Lifespan Architecture
The lifespan protocol runs as a sibling task alongside your main application, allowing both to execute concurrently.
Let's see how Uvicorn handles the lifespan and main application tasks:
```mermaid
sequenceDiagram
participant Server as Uvicorn Server
participant LifespanTask as Lifespan Task
participant AppTask as Application Task
participant UserApp as User Application
Note over Server: ✅ Server starts
Server->>+LifespanTask: spawn_task(lifespan_handler)
LifespanTask->>UserApp: {"type": "lifespan.startup"}
Note over UserApp: Initialize databases, caches, etc.
UserApp-->>LifespanTask: {"type": "lifespan.startup.complete"}
LifespanTask->>Server: ✅ Startup complete
Server->>+AppTask: spawn_task(application_handler)
Note over AppTask: ✅ Ready for requests
rect rgb(240, 248, 255)
Note over LifespanTask, AppTask: Both tasks running concurrently
par Lifespan maintains state
LifespanTask->>LifespanTask: Keep lifespan connection alive
and Application serves requests
AppTask->>UserApp: HTTP/WebSocket requests
UserApp-->>AppTask: Responses
end
end
Note over Server: Shutdown signal received
Server->>AppTask: Stop accepting new connections
AppTask->>AppTask: Complete pending requests
LifespanTask->>UserApp: {"type": "lifespan.shutdown"}
Note over UserApp: Cleanup databases, caches, etc.
UserApp-->>LifespanTask: {"type": "lifespan.shutdown.complete"}
LifespanTask->>-Server: Lifespan task complete
AppTask->>-Server: Application task complete
Note over Server: ✅ Server stopped
```
Having the lifespan task run as a sibling task is a deliberate design choice. It could have been implemented as a parent task that spawns the
application task. This decision has the implication that if you create a [`ContextVar`][contextvars.ContextVar]
in the lifespan task, it will not be available in the application task.
## Usage
Let's see an example of a minimal (but complete) ASGI application that implements the lifespan protocol:
```python title="ASGI application with lifespan" hl_lines="3-11"
async def app(scope, receive, send):
if scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
print("Application is starting up...")
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
print("Application is shutting down...")
await send({'type': 'lifespan.shutdown.complete'})
return
elif scope['type'] == 'http':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')],
})
await send({'type': 'http.response.body', 'body': b'Hello, World!'})
else:
raise RuntimeError("This server doesn't support WebSocket.")
```
You can run the above application with `uvicorn main:app`. Then you'll see the print statements when the
application starts. You can also try to send some HTTP requests to it, and it will respond with "Hello, World!".
And if you stop the server (`CTRL + C`), it will print `"Application is shutting down..."`.
## Disabling Lifespan
If you want to disable the lifespan protocol, you can do so by setting the `lifespan` option to `off` when running Uvicorn:
```bash
uvicorn main:app --lifespan off
```
By default, Uvicorn will automatically enable the lifespan protocol if the application supports it.
================================================
FILE: docs/concepts/websockets.md
================================================
**Uvicorn** supports the WebSocket protocol as defined in [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455).
## Upgrade Process
The WebSocket protocol starts as an HTTP connection that gets "upgraded" to a WebSocket connection
through a handshake process. Here's how it works:
```mermaid
sequenceDiagram
participant Client
participant Server
participant ASGI App
Note over Client,ASGI App: WebSocket Handshake Process
Client->>Server: HTTP GET Request
Note right of Client: Headers:<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Key: [key]<br/>Sec-WebSocket-Version: 13
Server->>ASGI App: websocket.connect event
Note right of Server: Scope type: "websocket"
alt Connection Accepted
ASGI App->>Server: {"type": "websocket.accept"}
Server->>Client: HTTP 101 Switching Protocols
Note right of Server: Headers:<br/>Upgrade: websocket<br/>Connection: Upgrade<br/>Sec-WebSocket-Accept: [hash]
Note over Client,ASGI App: WebSocket Connection Established
loop Message Exchange
Client->>Server: WebSocket Frame
Server->>ASGI App: websocket.receive event
ASGI App->>Server: {"type": "websocket.send", "text": "..."}
Server->>Client: WebSocket Frame
end
alt Client Closes
Client->>Server: Close Frame
Server->>ASGI App: websocket.disconnect event
else Server Closes
ASGI App->>Server: {"type": "websocket.close"}
Server->>Client: Close Frame
end
else Connection Rejected
ASGI App->>Server: {"type": "websocket.http.response.start", "status": 403}
Server->>Client: HTTP 403 Forbidden
end
```
1. **Initial HTTP Request**: The client sends a regular HTTP GET request with special headers indicating it wants to upgrade to WebSocket:
- `Upgrade: websocket`
- `Connection: Upgrade`
- `Sec-WebSocket-Key`: A base64-encoded random key
- `Sec-WebSocket-Version: 13`
2. **Server Processing**: Uvicorn receives the request and creates a WebSocket scope, sending a `websocket.connect` event to the ASGI application.
3. **Application Decision**: The ASGI app decides whether to accept or reject the connection based on authentication, authorization, or other logic.
4. **Handshake Completion**: If accepted, the server responds with HTTP 101 status and the computed `Sec-WebSocket-Accept` header.
5. **Full-Duplex Communication**: Once upgraded, both client and server can send messages at any time using WebSocket frames.
6. **Connection Termination**: Either side can initiate closing the connection with a close frame.
## ASGI WebSocket Events
**Uvicorn** translates WebSocket protocol messages into ASGI events:
- `websocket.connect`: Sent when a client requests a WebSocket upgrade
- `websocket.receive`: Sent when a message is received from the client
- `websocket.disconnect`: Sent when the connection is closed
The ASGI app can respond with:
- `websocket.accept`: Accept the connection upgrade with an optional subprotocol
- `websocket.send`: Send a message to the client
- `websocket.close`: Close the connection with an optional status code
You can read more about it on the [ASGI documentation](https://asgi.readthedocs.io/en/latest/specs/www.html#websocket).
## Protocol Implementations
**Uvicorn** has three implementations of the WebSocket protocol.
### WSProto Protocol
This implementation was the first implemented. It uses the
[`wsproto`](https://python-hyper.org/projects/wsproto/en/stable/) package underneath.
You can choose this protocol by setting the `--ws` option to `wsproto`.
### WebSocket Protocol
This implementation uses the [`websockets`](https://websockets.readthedocs.io/) package as dependency.
By default, if you have `websockets` installed, Uvicorn will use this protocol.
### WebSockets SansIO Protocol
Since `websockets` deprecated the API Uvicorn uses to run the previous protocol, we had to create this new
protocol that uses the `websockets` SansIO API.
You can choose this protocol by setting the `--ws` option to `websockets-sansio`.
!!! note
The SansIO implementation was released in Uvicorn version 0.35.0 in June 2025.
================================================
FILE: docs/contributing.md
================================================
# Contributing
Thank you for being interested in contributing to Uvicorn.
There are many ways you can contribute to the project:
- Using Uvicorn on your stack and [reporting bugs/issues you find](https://github.com/Kludex/uvicorn/issues/new)
- [Implementing new features and fixing bugs](https://github.com/Kludex/uvicorn/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
- [Review Pull Requests of others](https://github.com/Kludex/uvicorn/pulls)
- Write documentation
- Participate in discussions
## Reporting Bugs, Issues or Feature Requests
Found something that Uvicorn should support?
Stumbled upon some unexpected behaviour?
Need a missing functionality?
Contributions should generally start out from a previous discussion.
You can reach out someone at the [community chat](https://discord.com/invite/SWU73HffbV)
or at the [github discussions tab](https://github.com/Kludex/uvicorn/discussions).
When creating a new topic in the discussions tab, possible 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, or if we'd consider a pull request.
Try to be more descriptive as you can and in case of a bug report,
provide as much information as possible like:
- OS platform
- Python version
- Installed dependencies and versions (`python -m pip freeze`)
- Code snippet
- Error traceback
You should always try to reduce any examples to the *simplest possible case*
that demonstrates the issue.
Some possibly useful tips for narrowing down potential issues...
- Does the issue exist with a specific supervisor like `Multiprocess` or more than one?
- Does the issue exist on asgi, or wsgi, or both?
- Are you running Uvicorn in conjunction with Gunicorn, others, or standalone?
## Development
To start developing Uvicorn create a **fork** of the
[Uvicorn repository](https://github.com/Kludex/uvicorn) on GitHub.
Then clone your fork with the following command replacing `YOUR-USERNAME` with
your GitHub username:
```shell
$ git clone https://github.com/YOUR-USERNAME/uvicorn
```
You can now install the project and its dependencies using:
```shell
$ cd uvicorn
$ scripts/install
```
## Testing and Linting
We use custom shell scripts to automate testing, linting,
and documentation building workflow.
To run the tests, use:
```shell
$ scripts/test
```
Any additional arguments will be passed to `pytest`. See the [pytest documentation](https://docs.pytest.org/en/latest/how-to/usage.html) for more information.
For example, to run a single test script:
```shell
$ scripts/test tests/test_cli.py
```
To run the code auto-formatting:
```shell
$ scripts/lint
```
Lastly, to run code checks separately (they are also run as part of `scripts/test`), run:
```shell
$ scripts/check
```
## Documenting
Documentation pages are located under the `docs/` folder.
To run the documentation site locally (useful for previewing changes), use:
```shell
$ scripts/docs serve
```
## Resolving Build / CI Failures
Once you've submitted your pull request, the test suite will
automatically run, and the results will show up in GitHub.
If the test suite fails, you'll want to click through to the
"Details" link, and try to identify why the test suite failed.
<p align="center" style="margin: 0 0 10px">
<img src="https://raw.githubusercontent.com/Kludex/uvicorn/main/docs/img/gh-actions-fail.png" alt='Failing PR commit status'>
</p>
Here are some common ways the test suite can fail:
### Check Job Failed
<p align="center" style="margin: 0 0 10px">
<img src="https://raw.githubusercontent.com/Kludex/uvicorn/main/docs/img/gh-actions-fail-check.png" alt='Failing GitHub action lint job'>
</p>
This job failing means there is either a code formatting issue or type-annotation issue.
You can look at the job output to figure out why it's failed or within a shell run:
```shell
$ scripts/check
```
It may be worth it to run `$ scripts/lint` to attempt auto-formatting the code
and if that job succeeds commit the changes.
### Docs Job Failed
This job failing means the documentation failed to build. This can happen for
a variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`.
### Python 3.X Job Failed
This job failing means the unit tests failed or not all code paths are covered by unit tests.
If tests are failing you will see this message under the coverage report:
`=== 1 failed, 354 passed, 1 skipped, 1 xfailed in 37.08s ===`
If tests succeed but coverage doesn't reach 100%, you will see this
message under the coverage report:
`Coverage failure: total of 98 is less than fail-under=100`
## Releasing
*This section is targeted at Uvicorn maintainers.*
Before releasing a new version, create a pull request that includes:
- **An update to the changelog**:
- We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/).
- [Compare](https://github.com/Kludex/uvicorn/compare/) `main` with the tag of the latest release, and list all entries that are of interest to our users:
- Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes.
- Things that **should not** go in the changelog: changes to documentation, tests or tooling.
- Try sorting entries in descending order of impact / importance.
- Keep it concise and to-the-point. 🎯
- **A version bump**: see `__init__.py`.
For an example, see [#1006](https://github.com/Kludex/uvicorn/pull/1107).
Once the release PR is merged, create a
[new release](https://github.com/Kludex/uvicorn/releases/new) including:
- Tag version like `0.13.3`.
- Release title `Version 0.13.3`
- Description copied from the changelog.
Once created this release will be automatically uploaded to PyPI.
================================================
FILE: docs/deployment/docker.md
================================================
# Dockerfile
**Docker** is a popular choice for modern application deployment. However, creating a good Dockerfile from scratch can be challenging. This guide provides a **solid foundation** that works well for most Python projects.
While the example below won't fit every use case, it offers an excellent starting point that you can adapt to your specific needs.
## Quickstart
For this example, we'll need to install [`docker`](https://docs.docker.com/get-docker/),
[docker-compose](https://docs.docker.com/compose/install/) and
[`uv`](https://docs.astral.sh/uv/getting-started/installation/).
Then, let's create a new project with `uv`:
```bash
uv init app
```
This will create a new project with a basic structure:
```bash
app/
├── main.py
├── pyproject.toml
└── README.md
```
On `main.py`, let's create a simple ASGI application:
```python title="main.py"
async def app(scope, receive, send):
body = "Hello, world!"
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [
[b"content-type", b"text/plain"],
[b"content-length", len(body)],
],
}
)
await send(
{
"type": "http.response.body",
"body": body.encode("utf-8"),
}
)
```
We need to include `uvicorn` in the dependencies:
```bash
uv add uvicorn
```
This will also create a `uv.lock` file. :sunglasses:
??? tip "What is `uv.lock`?"
`uv.lock` is a `uv` specific lockfile. A lockfile is a file that contains the exact versions of the dependencies
that were installed when the `uv.lock` file was created.
This allows for deterministic builds and consistent deployments.
Just to make sure everything is working, let's run the application:
```bash
uv run uvicorn main:app
```
You should see the following output:
```bash
INFO: Started server process [62727]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
## Dockerfile
We'll create a **cache-aware Dockerfile** that optimizes build times. The key strategy is to install dependencies first, then copy the project files. This approach leverages Docker's caching mechanism to significantly speed up rebuilds.
```dockerfile title="Dockerfile"
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Change the working directory to the `app` directory
WORKDIR /app
# Install dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project
# Copy the project into the image
ADD . /app
# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
# Run with uvicorn
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
A common question is **"how many workers should I run?"**. The image above uses a single Uvicorn worker.
The recommended approach is to let your orchestration system manage the number of deployed containers rather than
relying on the process manager inside the container.
You can read more about this in the
[Decouple applications](https://docs.docker.com/build/building/best-practices/#decouple-applications) section
of the Docker documentation.
!!! warning "For production, create a non-root user!"
When running in production, you should create a non-root user and run the container as that user.
To make sure it works, let's build the image and run it:
```bash
docker build -t my-app .
docker run -p 8000:8000 my-app
```
For more information on using uv with Docker, refer to the
[official uv Docker integration guide](https://docs.astral.sh/uv/guides/integration/docker/).
## Docker Compose
When running in development, it's often useful to have a way to hot-reload the application when code changes.
Let's create a `docker-compose.yml` file to run the application:
```yaml title="docker-compose.yml"
services:
backend:
build: .
ports:
- "8000:8000"
environment:
- UVICORN_RELOAD=true
volumes:
- .:/app
tty: true
```
You can run the application with `docker compose up` and it will automatically rebuild the image when code changes.
Now you have a fully working development environment! :tada:
================================================
FILE: docs/deployment/index.md
================================================
Server deployment is a complex area, that will depend on what kind of service you're deploying Uvicorn onto.
As a general rule, you probably want to:
* Run `uvicorn --reload` from the command line for local development.
* Run `gunicorn -k uvicorn.workers.UvicornWorker` for production.
* Additionally run behind Nginx for self-hosted deployments.
* Finally, run everything behind a CDN for caching support, and serious DDOS protection.
## Running from the command line
Typically you'll run `uvicorn` from the command line.
```bash
$ uvicorn main:app --reload --port 5000
```
The ASGI application should be specified in the form `path.to.module:instance.path`.
When running locally, use `--reload` to turn on auto-reloading.
The `--reload` and `--workers` arguments are **mutually exclusive**.
To see the complete set of available options, use `uvicorn --help`:
```bash
{{ uvicorn_help }}
```
See the [settings documentation](../settings.md) for more details on the supported options for running uvicorn.
## Running programmatically
To run directly from within a Python program, you should use `uvicorn.run(app, **config)`. For example:
```py title="main.py"
import uvicorn
class App:
...
app = App()
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=5000, log_level="info")
```
The set of configuration options is the same as for the command line tool.
Note that the application instance itself *can* be passed instead of the app
import string.
```python
uvicorn.run(app, host="127.0.0.1", port=5000, log_level="info")
```
However, this style only works if you are not using multiprocessing (`workers=NUM`)
or reloading (`reload=True`), so we recommend using the import string style.
Also note that in this case, you should put `uvicorn.run` into `if __name__ == '__main__'` clause in the main module.
!!! note
The `reload` and `workers` parameters are **mutually exclusive**.
## Using a process manager
Running Uvicorn using a process manager ensures that you can run multiple processes in a resilient manner, and allows you to perform server upgrades without dropping requests.
A process manager will handle the socket setup, start-up multiple server processes, monitor process aliveness, and listen for signals to provide for processes restarts, shutdowns, or dialing up and down the number of running processes.
### Built-in
Uvicorn includes a `--workers` option that allows you to run multiple worker processes.
```bash
$ uvicorn main:app --workers 4
```
Unlike gunicorn, uvicorn does not use pre-fork, but uses [`spawn`](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods), which allows uvicorn's multiprocess manager to still work well on Windows.
The default process manager monitors the status of child processes and automatically restarts child processes that die unexpectedly. Not only that, it will also monitor the status of the child process through the pipeline. When the child process is accidentally stuck, the corresponding child process will be killed through an unstoppable system signal or interface.
You can also manage child processes by sending specific signals to the main process. (Not supported on Windows.)
- `SIGHUP`: Work processeses are graceful restarted one after another. If you update the code, the new worker process will use the new code.
- `SIGTTIN`: Increase the number of worker processes by one.
- `SIGTTOU`: Decrease the number of worker processes by one.
### Gunicorn
!!! warning
The `uvicorn.workers` module is deprecated and will be removed in a future release.
You should use the [`uvicorn-worker`](https://github.com/Kludex/uvicorn-worker) package instead.
```bash
python -m pip install uvicorn-worker
```
Gunicorn is probably the simplest way to run and manage Uvicorn in a production setting. Uvicorn includes a gunicorn worker class that means you can get set up with very little configuration.
The following will start Gunicorn with four worker processes:
`gunicorn -w 4 -k uvicorn.workers.UvicornWorker`
The `UvicornWorker` implementation uses the `uvloop` and `httptools` implementations. To run under PyPy you'll want to use pure-python implementation instead. You can do this by using the `UvicornH11Worker` class.
`gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker`
Gunicorn provides a different set of configuration options to Uvicorn, so some options such as `--limit-concurrency` are not yet supported when running with Gunicorn.
If you need to pass uvicorn's config arguments to gunicorn workers then you'll have to subclass `UvicornWorker`:
```python
from uvicorn.workers import UvicornWorker
class MyUvicornWorker(UvicornWorker):
CONFIG_KWARGS = {"loop": "asyncio", "http": "h11", "lifespan": "off"}
```
### Supervisor
To use `supervisor` as a process manager you should either:
* Hand over the socket to uvicorn using its file descriptor, which supervisor always makes available as `0`, and which must be set in the `fcgi-program` section.
* Or use a UNIX domain socket for each `uvicorn` process.
A simple supervisor configuration might look something like this:
```ini title="supervisord.conf"
[supervisord]
[fcgi-program:uvicorn]
socket=tcp://localhost:8000
command=venv/bin/uvicorn --fd 0 main:App
numprocs=4
process_name=uvicorn-%(process_num)d
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
```
Then run with `supervisord -n`.
## Running behind Nginx
Using Nginx as a proxy in front of your Uvicorn processes may not be necessary, but is recommended for additional resilience. Nginx can deal with serving your static media and buffering slow requests, leaving your application servers free from load as much as possible.
In managed environments such as `Heroku`, you won't typically need to configure Nginx, as your server processes will already be running behind load balancing proxies.
The recommended configuration for proxying from Nginx is to use a UNIX domain socket between Nginx and whatever the process manager that is being used to run Uvicorn. If using Uvicorn directly you can bind it to a UNIX domain socket using `uvicorn --uds /path/to/socket.sock <...>`.
When running your application behind one or more proxies you will want to make sure that each proxy sets appropriate headers to ensure that your application can properly determine the client address of the incoming connection, and if the connection was over `http` or `https`. For more information see [Proxies and Forwarded Headers](#proxies-and-forwarded-headers) below.
Here's how a simple Nginx configuration might look. This example includes setting proxy headers, and using a UNIX domain socket to communicate with the application server.
It also includes some basic configuration to forward websocket connections.
For more info on this, check [Nginx recommendations](https://nginx.org/en/docs/http/websocket.html).
```conf
http {
server {
listen 80;
client_max_body_size 4G;
server_name example.com;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://uvicorn;
}
location /static {
# path for static files
root /path/to/app/static;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream uvicorn {
server unix:/tmp/uvicorn.sock;
}
}
```
Uvicorn's `--proxy-headers` behavior may not be sufficient for more complex proxy configurations that use different combinations of headers, or where the application is running behind more than one intermediary proxying service.
In those cases, you might want to use an ASGI middleware to set the `client` and `scheme` dependant on the request headers.
## Running behind a CDN
Running behind a content delivery network, such as Cloudflare or Cloud Front, provides a serious layer of protection against DDoS attacks. Your service will be running behind huge clusters of proxies and load balancers that are designed for handling huge amounts of traffic, and have capabilities for detecting and closing off connections from DDoS attacks.
Proper usage of cache control headers can mean that a CDN is able to serve large amounts of data without always having to forward the request on to your server.
Content Delivery Networks can also be a low-effort way to provide HTTPS termination.
## Running with HTTPS
To run uvicorn with https, a certificate and a private key are required.
The recommended way to get them is using [Let's Encrypt](https://letsencrypt.org/).
For local development with https, it's possible to use [mkcert](https://github.com/FiloSottile/mkcert)
to generate a valid certificate and private key.
```bash
$ uvicorn main:app --port 5000 --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem
```
### Running gunicorn worker
It's also possible to use certificates with uvicorn's worker for gunicorn.
```bash
$ gunicorn --keyfile=./key.pem --certfile=./cert.pem -k uvicorn.workers.UvicornWorker main:app
```
## Proxies and Forwarded Headers
When running an application behind one or more proxies, certain information about the request is lost.
To avoid this most proxies will add headers containing this information for downstream servers to read.
Uvicorn currently supports the following headers:
- `X-Forwarded-For` ([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For))
- `X-Forwarded-Proto`([MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto))
Uvicorn can use these headers to correctly set the client and protocol in the request.
However as anyone can set these headers you must configure which "clients" you will trust to have set them correctly.
Uvicorn can be configured to trust IP Addresses (e.g. `127.0.0.1`), IP Networks (e.g. `10.100.0.0/16`),
or Literals (e.g. `/path/to/socket.sock`). When running from CLI these are configured using `--forwarded-allow-ips`.
!!! Warning "Only trust clients you can actually trust!"
Incorrectly trusting other clients can lead to malicious actors spoofing their apparent client address to your application.
For more information, check [`ProxyHeadersMiddleware`](https://github.com/Kludex/uvicorn/blob/main/uvicorn/middleware/proxy_headers.py).
### Client Port
Currently if the `ProxyHeadersMiddleware` is able to retrieve a trusted client value then the client's port will be set to `0`.
This is because port information is lost when using these headers.
### UNIX Domain Sockets (UDS)
Although it is common for UNIX Domain Sockets to be used for communicating between various HTTP servers, they can mess with some of the expected received values as they will be various non-address strings or missing values.
For example:
- when NGINX itself is running behind a UDS it will add the literal `unix:` as the client in the `X-Forwarded-For` header.
- When Uvicorn is running behind a UDS the initial client will be `None`.
### Trust Everything
Rather than specifying what to trust, you can instruct Uvicorn to trust all clients using the literal `"*"`.
You should only set this when you know you can trust all values within the forwarded headers (e.g. because
your proxies remove the existing headers before setting their own).
================================================
FILE: docs/index.md
================================================
<style>
.md-typeset h1,
.md-content__button {
display: none;
}
</style>
<p align="center">
<img width="320" height="320" src="../../uvicorn.png" alt='uvicorn'>
</p>
<p align="center">
<em>An ASGI web server, for Python.</em>
</p>
<p align="center">
<a href="https://github.com/Kludex/uvicorn/actions">
<img src="https://github.com/Kludex/uvicorn/workflows/Test%20Suite/badge.svg" alt="Test Suite">
</a>
<a href="https://pypi.org/project/uvicorn/">
<img src="https://badge.fury.io/py/uvicorn.svg" alt="Package version">
</a>
<a href="https://pypi.org/project/uvicorn" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/uvicorn.svg?color=%2334D058" alt="Supported Python versions">
</a>
<a href="https://discord.gg/RxKUF5JuHs">
<img src="https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" alt="Discord">
</a>
</p>
---
**Documentation**: [https://uvicorn.dev](https://uvicorn.dev)
**Source Code**: [https://www.github.com/Kludex/uvicorn](https://www.github.com/Kludex/uvicorn)
---
**Uvicorn** is an [ASGI](concepts/asgi.md) web server implementation for Python.
Until recently Python has lacked a minimal low-level server/application interface for
async frameworks. The [ASGI specification](https://asgi.readthedocs.io/en/latest/) fills this gap,
and means we're now able to start building a common set of tooling usable across all async frameworks.
Uvicorn currently supports **HTTP/1.1** and **WebSockets**.
## Quickstart
**Uvicorn** is available on [PyPI](https://pypi.org/project/uvicorn/) so installation is as simple as:
=== "pip"
```bash
pip install uvicorn
```
=== "uv"
```bash
uv add uvicorn
```
See the [installation documentation](installation.md) for more information.
---
Let's create a simple ASGI application to run with Uvicorn:
```python title="main.py"
async def app(scope, receive, send):
assert scope['type'] == 'http'
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
(b'content-type', b'text/plain'),
(b'content-length', b'13'),
],
})
await send({
'type': 'http.response.body',
'body': b'Hello, world!',
})
```
Then we can run it with Uvicorn:
```shell
uvicorn main:app
```
---
## Usage
The uvicorn command line tool is the easiest way to run your application.
### Command line options
```bash
{{ uvicorn_help }}
```
For more information, see the [settings documentation](settings.md).
### Running programmatically
There are several ways to run uvicorn directly from your application.
#### `uvicorn.run`
If you're looking for a programmatic equivalent of the `uvicorn` command line interface, use `uvicorn.run()`:
```py title="main.py"
import uvicorn
async def app(scope, receive, send):
...
if __name__ == "__main__":
uvicorn.run("main:app", port=5000, log_level="info")
```
#### `Config` and `Server` instances
For more control over configuration and server lifecycle, use `uvicorn.Config` and `uvicorn.Server`:
```py title="main.py"
import uvicorn
async def app(scope, receive, send):
...
if __name__ == "__main__":
config = uvicorn.Config("main:app", port=5000, log_level="info")
server = uvicorn.Server(config)
server.run()
```
If you'd like to run Uvicorn from an already running async environment, use `uvicorn.Server.serve()` instead:
```py title="main.py"
import asyncio
import uvicorn
async def app(scope, receive, send):
...
async def main():
config = uvicorn.Config("main:app", port=5000, log_level="info")
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
asyncio.run(main())
```
### Running with Gunicorn
!!! warning
The `uvicorn.workers` module is deprecated and will be removed in a future release.
You should use the [`uvicorn-worker`](https://github.com/Kludex/uvicorn-worker) package instead.
```bash
python -m pip install uvicorn-worker
```
[Gunicorn](https://gunicorn.org/) is a mature, fully featured server and process manager.
Uvicorn includes a Gunicorn worker class allowing you to run ASGI applications,
with all of Uvicorn's performance benefits, while also giving you Gunicorn's
fully-featured process management.
This allows you to increase or decrease the number of worker processes on the
fly, restart worker processes gracefully, or perform server upgrades without downtime.
For production deployments we recommend using gunicorn with the uvicorn worker class.
```
gunicorn example:app -w 4 -k uvicorn.workers.UvicornWorker
```
For a [PyPy](https://pypy.org/) compatible configuration use `uvicorn.workers.UvicornH11Worker`.
For more information, see the [deployment documentation](deployment/index.md).
### Application factories
The `--factory` flag allows loading the application from a factory function, rather than an application instance directly. The factory will be called with no arguments and should return an ASGI application.
```py title="main.py"
def create_app():
app = ...
return app
```
```shell
uvicorn --factory main:create_app
```
================================================
FILE: docs/installation.md
================================================
**Uvicorn** is available on [PyPI](https://pypi.org/project/uvicorn/) so installation is as simple as:
=== "pip"
```bash
pip install uvicorn
```
=== "uv"
```bash
uv add uvicorn
```
The above will install Uvicorn with the minimal set of dependencies:
- [`h11`](https://github.com/python-hyper/h11) — Pure Python sans-io HTTP/1.1 implementation.
- [`click`](https://github.com/pallets/click) — Command line interface library.
If you are running on Python 3.10 or early versions,
[`typing_extensions`](https://github.com/python/typing_extensions) will also be installed.
## Optional Dependencies
There are many optional dependencies that can be installed to add support for various features.
If you just want to install all of them at once, you can use the `standard` extra:
=== "pip"
```bash
pip install 'uvicorn[standard]'
```
=== "uv"
```bash
uv add 'uvicorn[standard]'
```
The `standard` extra installs the following dependencies:
- **[`uvloop`](https://github.com/MagicStack/uvloop) — Fast, drop-in replacement of the built-in asyncio event loop.**
When `uvloop` is installed, Uvicorn will use it by default.
- **[`httptools`](https://github.com/MagicStack/httptools) — Python binding for the Node.js HTTP parser.**
When `httptools` is installed, Uvicorn will use it by default for HTTP/1.1 parsing.
You can read this issue to understand how it compares with `h11`: [h11/issues/9](https://github.com/python-hyper/h11/issues/9).
- **[`websockets`](https://websockets.readthedocs.io/en/stable/) — WebSocket library for Python.**
When `websockets` is installed, Uvicorn will use it by default for WebSocket handling.
You can alternatively install **[`wsproto`](https://github.com/python-hyper/wsproto)** and set the `--ws`
option to `wsproto` to use it instead.
- **[`watchfiles`](https://github.com/samuelcolvin/watchfiles) — Simple, modern and high performance file
watching and code reload in python.**
When `watchfiles` is installed, Uvicorn will use it by default for the `--reload` option.
- **[`colorama`](https://github.com/tartley/colorama) — Cross-platform support for ANSI terminal
colors.**
This is installed only on Windows, to provide colored logs.
- **[`python-dotenv`](https://github.com/theskumar/python-dotenv) — Reads key-value pairs from a `.env` file
and adds them to the environment.**
This is installed to allow you to use the `--env-file` option.
- **[`PyYAML`](https://github.com/yaml/pyyaml) — YAML parser and emitter for Python.**
This is installed to allow you to provide a `.yaml` file to the `--log-config` option.
================================================
FILE: docs/overrides/main.html
================================================
{% extends "base.html" %}
{% block extrahead %}
{{ super() }}
<script>
// Redirect starlette.io to starlette.dev
if (window.location.hostname === 'www.uvicorn.org' || window.location.hostname === 'uvicorn.org') {
const newUrl = window.location.href.replace(/^https?:\/\/(www\.)?uvicorn\.org/, 'https://uvicorn.dev');
window.location.replace(newUrl);
}
</script>
{% endblock %}
================================================
FILE: docs/overrides/partials/nav.html
================================================
{% import "partials/nav-item.html" as item with context %}
<!-- Determine class according to configuration -->
{% set class = "md-nav md-nav--primary" %}
{% if "navigation.tabs" in features %}
{% set class = class ~ " md-nav--lifted" %}
{% endif %}
{% if "toc.integrate" in features %}
{% set class = class ~ " md-nav--integrated" %}
{% endif %}
<!-- Main navigation -->
<nav
class="{{ class }}"
aria-label="{{ lang.t('nav.title') }}"
data-md-level="0"
>
<!-- Site title -->
<label class="md-nav__title" for="__drawer">
<a
href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
title="{{ config.site_name | e }}"
class="md-nav__button md-logo"
aria-label="{{ config.site_name }}"
data-md-component="logo"
>
{% include "partials/logo.html" %}
</a>
{{ config.site_name }}
</label>
<!-- Repository information -->
{% if config.repo_url %}
<div class="md-nav__source">
{% include "partials/source.html" %}
</div>
{% endif %}
<!-- Navigation list -->
<ul class="md-nav__list" data-md-scrollfix>
{% for nav_item in nav %}
{% set path = "__nav_" ~ loop.index %}
{{ item.render(nav_item, path, 1) }}
{% endfor %}
</ul>
</nav>
================================================
FILE: docs/overrides/partials/toc-item.html
================================================
<!-- Copied from https://github.com/squidfunk/mkdocs-material/issues/4827#issuecomment-1869812019 -->
<li class="md-nav__item"></li>
<a href="{{ toc_item.url }}" class="md-nav__link">
<span class="md-ellipsis">
{{ toc_item.title }}
</span>
</a>
<!-- Table of contents list -->
{% if toc_item.children %}
<nav class="md-nav" aria-label="{{ toc_item.title | striptags }}">
<ul class="md-nav__list">
{% for toc_item in toc_item.children %}
{% if not page.meta.toc_depth or toc_item.level <= page.meta.toc_depth %} {% include "partials/toc-item.html" %}
{% endif %} {% endfor %} </ul>
</nav>
{% endif %}
</li>
================================================
FILE: docs/plugins/main.py
================================================
from __future__ import annotations as _annotations
import re
import subprocess
from functools import lru_cache
from mkdocs.config import Config
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
def on_page_content(html: str, page: Page, config: Config, files: Files) -> str:
"""Called on each page after the markdown is converted to HTML."""
html = add_hyperlink_to_pull_request(html, page, config, files)
return html
def add_hyperlink_to_pull_request(html: str, page: Page, config: Config, files: Files) -> str:
"""Add hyperlink on PRs mentioned on the release notes page.
If we find "(#\\d+)" it will be added an hyperlink to https://github.com/Kludex/uvicorn/pull/$1.
"""
if not page.file.name == "release-notes":
return html
return re.sub(r"\(#(\d+)\)", r"(<a href='https://github.com/Kludex/uvicorn/pull/\1'>#\1</a>)", html)
def on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str:
"""Called on each file after it is read and before it is converted to HTML."""
markdown = uvicorn_print_help(markdown, page)
return markdown
def uvicorn_print_help(markdown: str, page: Page) -> str:
return re.sub(r"{{ *uvicorn_help *}}", get_uvicorn_help(), markdown)
@lru_cache
def get_uvicorn_help():
output = subprocess.run(["uvicorn", "--help"], capture_output=True, check=True)
return output.stdout.decode()
================================================
FILE: docs/release-notes.md
================================================
---
toc_depth: 2
---
## 0.42.0 (March 16, 2026)
### Changed
* Use `bytearray` for request body accumulation to avoid O(n^2) allocation on fragmented bodies (#2845)
### Fixed
* Escape brackets and backslash in httptools `HEADER_RE` regex (#2824)
* Fix multiple issues in websockets sans-io implementation (#2825)
## 0.41.0 (February 16, 2026)
### Added
* Add `--limit-max-requests-jitter` to stagger worker restarts (#2707)
* Add socket path to `scope["server"]` (#2561)
### Changed
* Rename `LifespanOn.error_occured` to `error_occurred` (#2776)
### Fixed
* Ignore permission denied errors in watchfiles reloader (#2817)
* Ensure lifespan shutdown runs when `should_exit` is set during startup (#2812)
* Reduce the log level of 'request limit exceeded' messages (#2788)
## 0.40.0 (December 21, 2025)
### Remove
* Drop support for Python 3.9 (#2772)
## 0.39.0 (December 21, 2025)
### Fixed
* Send close frame on ASGI return for WebSockets (#2769)
* Explicitly start ASGI run with empty context (#2742)
## 0.38.0 (October 18, 2025)
### Added
* Support Python 3.14 (#2723)
## 0.37.0 (September 23, 2025)
### Added
* Add `--timeout-worker-healthcheck` option (#2711)
* Add `os.PathLike[str]` type to `ssl_ca_certs` (#2676)
## 0.36.1 (September 23, 2025)
### Fixed
* Raise an exception when calling removed `Config.setup_event_loop()` (#2709)
## 0.36.0 (September 20, 2025)
### Added
* Support custom IOLOOPs (#2435)
* Allow to provide importable string in `--http`, `--ws` and `--loop` (#2658)
## 0.35.0 (June 28, 2025)
### Added
* Add `WebSocketsSansIOProtocol` (#2540)
### Changed
* Refine help message for option `--proxy-headers` (#2653)
## 0.34.3 (June 1, 2025)
### Fixed
* Don't include `cwd()` when non-empty `--reload-dirs` is passed (#2598)
* Apply `get_client_addr` formatting to WebSocket logging (#2636)
## 0.34.2 (April 19, 2025)
### Fixed
* Flush stdout buffer on Windows to trigger reload (#2604)
## 0.34.1 (April 13, 2025)
### Deprecated
* Deprecate `ServerState` in the main module (#2581)
## 0.34.0 (December 15, 2024)
### Added
* Add `content-length` to 500 response in `wsproto` implementation (#2542)
### Removed
* Drop support for Python 3.8 (#2543)
## 0.33.0 (December 14, 2024)
### Removed
* Remove `WatchGod` support for `--reload` (#2536)
## 0.32.1 (November 20, 2024)
### Fixed
* Drop ASGI spec version to 2.3 on HTTP scope (#2513)
* Enable httptools lenient data on `httptools >= 0.6.3` (#2488)
## 0.32.0 (October 15, 2024)
### Added
* Officially support Python 3.13 (#2482)
* Warn when `max_request_limit` is exceeded (#2430)
## 0.31.1 (October 9, 2024)
### Fixed
* Support WebSockets 0.13.1 (#2471)
* Restore support for `[*]` in trusted hosts (#2480)
* Add `PathLike[str]` type hint for `ssl_keyfile` (#2481)
## 0.31.0 (September 27, 2024)
### Added
Improve `ProxyHeadersMiddleware` (#2468) and (#2231):
- Fix the host for requests from clients running on the proxy server itself.
- Fallback to host that was already set for empty x-forwarded-for headers.
- Also allow to specify IP Networks as trusted hosts. This greatly simplifies deployments
on docker swarm/kubernetes, where the reverse proxy might have a dynamic IP.
- This includes support for IPv6 Address/Networks.
## 0.30.6 (August 13, 2024)
### Fixed
- Don't warn when upgrade is not WebSocket and dependencies are installed (#2360)
## 0.30.5 (August 2, 2024)
### Fixed
- Don't close connection before receiving body on H11 (#2408)
## 0.30.4 (July 31, 2024)
### Fixed
- Close connection when `h11` sets client state to `MUST_CLOSE` (#2375)
## 0.30.3 (July 20, 2024)
### Fixed
- Suppress `KeyboardInterrupt` from CLI and programmatic usage (#2384)
- `ClientDisconnect` inherits from `OSError` instead of `IOError` (#2393)
## 0.30.2 (July 20, 2024)
### Added
- Add `reason` support to [`websocket.disconnect`](https://asgi.readthedocs.io/en/latest/specs/www.html#disconnect-receive-event-ws) event (#2324)
### Fixed
- Iterate subprocesses in-place on the process manager (#2373)
## 0.30.1 (June 2, 2024)
### Fixed
- Allow horizontal tabs `\t` in response header values (#2345)
## 0.30.0 (May 28, 2024)
### Added
- New multiprocess manager (#2183)
- Allow `ConfigParser` or a `io.IO[Any]` on `log_config` (#1976)
### Fixed
- Suppress side-effects of signal propagation (#2317)
- Send `content-length` header on 5xx (#2304)
### Deprecated
- Deprecate the `uvicorn.workers` module (#2302)
## 0.29.0 (March 19, 2024)
### Added
- Cooperative signal handling (#1600)
## 0.28.1 (March 19, 2024)
### Fixed
- Revert raise `ClientDisconnected` on HTTP (#2276)
## 0.28.0 (March 9, 2024)
### Added
- Raise `ClientDisconnected` on `send()` when client disconnected (#2220)
### Fixed
- Except `AttributeError` on `sys.stdin.fileno()` for Windows IIS10 (#1947)
- Use `X-Forwarded-Proto` for WebSockets scheme when the proxy provides it (#2258)
## 0.27.1 (February 10, 2024)
- Fix spurious LocalProtocolError errors when processing pipelined requests (#2243)
## 0.27.0.post1 (January 29, 2024)
### Fixed
- Fix nav overrides for newer version of Mkdocs Material (#2233)
## 0.27.0 (January 22, 2024)
### Added
- Raise `ClientDisconnect(IOError)` on `send()` when client disconnected (#2218)
- Bump ASGI WebSocket spec version to 2.4 (#2221)
## 0.26.0 (January 16, 2024)
### Changed
- Update `--root-path` to include the root path prefix in the full ASGI `path` as per the ASGI spec (#2213)
- Use `__future__.annotations` on some internal modules (#2199)
## 0.25.0 (December 17, 2023)
### Added
- Support the WebSocket Denial Response ASGI extension (#1916)
### Fixed
- Allow explicit hidden file paths on `--reload-include` (#2176)
- Properly annotate `uvicorn.run()` (#2158)
## 0.24.0.post1 (November 6, 2023)
### Fixed
- Revert mkdocs-material from 9.1.21 to 9.2.6 (#2148)
## 0.24.0 (November 4, 2023)
### Added
- Support Python 3.12 (#2145)
- Allow setting `app` via environment variable `UVICORN_APP` (#2106)
## 0.23.2 (July 31, 2023)
### Fixed
- Maintain the same behavior of `websockets` from 10.4 on 11.0 (#2061)
## 0.23.1 (July 18, 2023)
### Fixed
- Add `typing_extensions` for Python 3.10 and lower (#2053)
## 0.23.0 (July 10, 2023)
### Added
- Add `--ws-max-queue` parameter WebSockets (#2033)
### Removed
- Drop support for Python 3.7 (#1996)
- Remove `asgiref` as typing dependency (#1999)
### Fixed
- Set `scope["scheme"]` to `ws` or `wss` instead of `http` or `https` on `ProxyHeadersMiddleware` for WebSockets (#2043)
### Changed
- Raise `ImportError` on circular import (#2040)
- Use `logger.getEffectiveLevel()` instead of `logger.level` to check if log level is `TRACE` (#1966)
## 0.22.0 (April 28, 2023)
### Added
- Add `--timeout-graceful-shutdown` parameter (#1950)
- Handle `SIGBREAK` on Windows (#1909)
### Fixed
- Shutdown event is now being triggered on Windows when using hot reload (#1584)
- `--reload-delay` is effectively used on the `watchfiles` reloader (#1930)
## 0.21.1 (March 16, 2023)
### Fixed
- Reset lifespan state on each request (#1903)
## 0.21.0 (March 9, 2023)
### Added
- Introduce lifespan state (#1818)
- Allow headers to be sent as iterables on H11 implementation (#1782)
- Improve discoverability when --port=0 is used (#1890)
### Changed
- Avoid importing `h11` and `pyyaml` when not needed to improve import time (#1846)
- Replace current native `WSGIMiddleware` implementation by `a2wsgi` (#1825)
- Change default `--app-dir` from "." (dot) to "" (empty string) (#1835)
### Fixed
- Send code 1012 on shutdown for WebSockets (#1816)
- Use `surrogateescape` to encode headers on `websockets` implementation (#1005)
- Fix warning message on reload failure (#1784)
## 0.20.0 (November 20, 2022)
### Added
- Check if handshake is completed before sending frame on `wsproto` shutdown (#1737)
- Add default headers to WebSockets implementations (#1606 & #1747)
- Warn user when `reload` and `workers` flag are used together (#1731)
### Fixed
- Use correct `WebSocket` error codes on `close` (#1753)
- Send disconnect event on connection lost for `wsproto` (#996)
- Add `SIGQUIT` handler to `UvicornWorker` (#1710)
- Fix crash on exist with "--uds" if socket doesn't exist (#1725)
- Annotate `CONFIG_KWARGS` in `UvicornWorker` class (#1746)
### Removed
- Remove conditional on `RemoteProtocolError.event_hint` on `wsproto` (#1486)
- Remove unused `handle_no_connect` on `wsproto` implementation (#1759)
## 0.19.0 (October 19, 2022)
### Added
- Support Python 3.11 (#1652)
- Bump minimal `httptools` version to `0.5.0` (#1645)
- Ignore HTTP/2 upgrade and optionally ignore WebSocket upgrade (#1661)
- Add `py.typed` to comply with PEP 561 (#1687)
### Fixed
- Set `propagate` to `False` on "uvicorn" logger (#1288)
- USR1 signal is now handled correctly on `UvicornWorker`. (#1565)
- Use path with query string on `WebSockets` logs (#1385)
- Fix behavior on which "Date" headers were not updated on the same connection (#1706)
### Removed
- Remove the `--debug` flag (#1640)
- Remove the `DebugMiddleware` (#1697)
## 0.18.3 (August 24, 2022)
### Fixed
- Remove cyclic references on HTTP implementations. (#1604)
### Changed
- `reload_delay` default changed from `None` to `0.25` on `uvicorn.run()` and `Config`. `None` is not an acceptable value anymore. (#1545)
## 0.18.2 (June 27, 2022)
### Fixed
- Add default `log_config` on `uvicorn.run()` (#1541)
- Revert `logging` file name modification (#1543)
## 0.18.1 (June 23, 2022)
### Fixed
- Use `DEFAULT_MAX_INCOMPLETE_EVENT_SIZE` as default to `h11_max_incomplete_event_size` on the CLI (#1534)
## 0.18.0 (June 23, 2022)
### Added
- The `reload` flag prioritizes `watchfiles` instead of the deprecated `watchgod` (#1437)
- Annotate `uvicorn.run()` function (#1423)
- Allow configuring `max_incomplete_event_size` for `h11` implementation (#1514)
### Removed
- Remove `asgiref` dependency (#1532)
### Fixed
- Turn `raw_path` into bytes on both websockets implementations (#1487)
- Revert log exception traceback in case of invalid HTTP request (#1518)
- Set `asyncio.WindowsSelectorEventLoopPolicy()` when using multiple workers to avoid "WinError 87" (#1454)
## 0.17.6 (March 11, 2022)
### Changed
- Change `httptools` range to `>=0.4.0` (#1400)
## 0.17.5 (February 16, 2022)
### Fixed
- Fix case where url is fragmented in httptools protocol (#1263)
- Fix WSGI middleware not to explode quadratically in the case of a larger body (#1329)
### Changed
- Send HTTP 400 response for invalid request (#1352)
## 0.17.4 (February 4, 2022)
### Fixed
- Replace `create_server` by `create_unix_server` (#1362)
## 0.17.3 (February 3, 2022)
### Fixed
- Drop wsproto version checking. (#1359)
## 0.17.2 (February 3, 2022)
### Fixed
- Revert #1332. While trying to solve the memory leak, it introduced an issue (#1345) when the server receives big chunks of data using the `httptools` implementation. (#1354)
- Revert stream interface changes. This was introduced on 0.14.0, and caused an issue (#1226), which caused a memory leak when sending TCP pings. (#1355)
- Fix wsproto version check expression (#1342)
## 0.17.1 (January 28, 2022)
### Fixed
- Move all data handling logic to protocol and ensure connection is closed. (#1332)
- Change `spec_version` field from "2.1" to "2.3", as Uvicorn is compliant with that version of the ASGI specifications. (#1337)
## 0.17.0.post1 (January 24, 2022)
### Fixed
- Add the `python_requires` version specifier (#1328)
## 0.17.0 (January 14, 2022)
### Added
- Allow configurable websocket per-message-deflate setting (#1300)
- Support extra_headers for WS accept message (#1293)
- Add missing http version on websockets scope (#1309)
### Fixed/Removed
- Drop Python 3.6 support (#1261)
- Fix reload process behavior when exception is raised (#1313)
- Remove `root_path` from logs (#1294)
## 0.16.0 (December 8, 2021)
### Added
- Enable read of uvicorn settings from environment variables (#1279)
- Bump `websockets` to 10.0. (#1180)
- Ensure non-zero exit code when startup fails (#1278)
- Increase `httptools` version range from "==0.2.*" to ">=0.2.0,<0.4.0". (#1243)
- Override default asyncio event loop with reload only on Windows (#1257)
- Replace `HttpToolsProtocol.pipeline` type from `list` to `deque`. (#1213)
- Replace `WSGIResponder.send_queue` type from `list` to `deque`. (#1214)
### Fixed
- Main process exit after startup failure on reloader classes (#1177)
- Fix the need of `httptools` on minimal installation (#1135)
- Fix ping parameters annotation in Config class (#1127)
## 0.15.0 (August 13, 2021)
### Added
- Change reload to be configurable with glob patterns. Currently only `.py` files are watched, which is different from the previous default behavior. (#820)
- Add Python 3.10-rc.1 support. Now the server uses `asyncio.run` which will: start a fresh asyncio event loop, on shutdown cancel any background tasks rather than aborting them, `aexit` any remaining async generators, and shutdown the default `ThreadPoolExecutor`. (#1070)
- Exit with status 3 when worker starts failed (#1077)
- Add option to set websocket ping interval and timeout (#1048)
- Adapt bind_socket to make it usable with multiple processes (#1009)
- Add existence check to the reload directory(ies) (#1089)
- Add missing trace log for websocket protocols (#1083)
- Support disabling default Server and Date headers (#818)
### Changed
- Add PEP440 compliant version of click (#1099)
- Bump asgiref to 3.4.0 (#1100)
### Fixed
- When receiving a `SIGTERM` supervisors now terminate their processes before joining them (#1069)
- Fix `httptools` range to `>=0.4.0` (#1400)
## 0.14.0 (June 1, 2021)
### Added
- Defaults ws max_size on server to 16MB (#995)
- Improve user feedback if no ws library installed (#926 and #1023)
- Support 'reason' field in 'websocket.close' messages (#957)
- Implemented lifespan.shutdown.failed (#755)
### Changed
- Upgraded websockets requirements (#1065)
- Switch to asyncio streams API (#869)
- Update httptools from 0.1.* to 0.2.* (#1024)
- Allow Click 8.0, refs #1016 (#1042)
- Add search for a trusted host in ProxyHeadersMiddleware (#591)
- Up wsproto to 1.0.0 (#892)
### Fixed
- Force reload_dirs to be a list (#978)
- Fix gunicorn worker not running if extras not installed (#901)
- Fix socket port 0 (#975)
- Prevent garbage collection of main lifespan task (#972)
## 0.13.4 (February 20, 2021)
### Fixed
- Fixed wsgi middleware PATH_INFO encoding (#962)
- Fixed uvloop dependency (#952) then (#959)
- Relax watchgod up bound (#946)
- Return 'connection: close' header in response (#721)
### Added
- Docs: Nginx + websockets (#948)
- Document the default value of 1 for workers (#940) (#943)
- Enabled permessage-deflate extension in websockets (#764)
## 0.13.3 (December 29, 2020)
### Fixed
- Prevent swallowing of return codes from `subprocess` when running with Gunicorn by properly resetting signals. (#895)
- Tweak detection of app factories to be more robust. A warning is now logged when passing a factory without the `--factory` flag. (#914)
- Properly clean tasks when handshake is aborted when running with `--ws websockets`. (#921)
## 0.13.2 (December 12, 2020)
### Fixed
- Log full exception traceback in case of invalid HTTP request. (#886 and #888)
## 0.13.1 (December 12, 2020)
### Fixed
- Prevent exceptions when the ASGI application rejects a connection during the WebSocket handshake, when running on both `--ws wsproto` or `--ws websockets`. (#704 and #881)
- Ensure connection `scope` doesn't leak in logs when using JSON log formatters. (#859 and #884)
## 0.13.0 (December 8, 2020)
### Added
- Add `--factory` flag to support factory-style application imports. (#875)
- Skip installation of signal handlers when not in the main thread. Allows using `Server` in multithreaded contexts without having to override `.install_signal_handlers()`. (#871)
## 0.12.3 (November 21, 2020)
### Fixed
- Fix race condition that leads Quart to hang with uvicorn (#848)
- Use latin1 when decoding X-Forwarded-* headers (#701)
- Rework IPv6 support (#837)
- Cancel old keepalive-trigger before setting new one. (#832)
## 0.12.2 (October 19, 2020)
### Added
- Adding ability to decrypt ssl key file (#808)
- Support .yml log config files (#799)
- Added python 3.9 support (#804)
### Fixed
- Fixes watchgod with common prefixes (#817)
- Fix reload with ipv6 host (#803)
- Added cli support for headers containing colon (#813)
- Sharing socket across workers on windows (#802)
- Note the need to configure trusted "ips" when using unix sockets (#796)
## 0.12.1 (September 30, 2020)
### Changed
- Pinning h11 and python-dotenv to min versions (#789)
- Get docs/index.md in sync with README.md (#784)
### Fixed
- Improve changelog by pointing out breaking changes (#792)
## 0.12.0 (September 28, 2020)
### Added
- Make reload delay configurable (#774)
- Upgrade maximum h11 dependency version to 0.10 (#772)
- Allow .json or .yaml --log-config files (#665)
- Add ASGI dict to the lifespan scope (#754)
- Upgrade wsproto to 0.15.0 (#750)
- Use optional package installs (#666)
### Changed
- Don't set log level for root logger (#767) 8/28/20 df81b168
- Uvicorn no longer ships extra dependencies `uvloop`, `websockets` and `httptools` as default.
To install these dependencies use `uvicorn[standard]`.
### Fixed
- Revert "Improve shutdown robustness when using `--reload` or multiprocessing (#620)" (#756)
- Fix terminate error in windows (#744)
- Fix bug where --log-config disables uvicorn loggers (#512)
## 0.11.8 (July 30, 2020)
* Fix a regression that caused Uvicorn to crash when using `--interface=wsgi`. (#730)
* Fix a regression that caused Uvicorn to crash when using unix domain sockets. (#729)
## 0.11.7 (July 28, 2020)
* SECURITY FIX: Prevent sending invalid HTTP header names and values. (#725)
* SECURITY FIX: Ensure path value is escaped before logging to the console. (#724)
* Fix `--proxy-headers` client IP and host when using a Unix socket. (#636)
## 0.11.6 (July 17, 2020)
* Fix overriding the root logger.
## 0.11.5 (April 29, 2020)
* Revert "Watch all files, not just .py" due to unexpected side effects.
* Revert "Pass through gunicorn timeout config." due to unexpected side effects.
## 0.11.4 (April 28, 2020)
* Use `watchgod`, if installed, for watching code changes.
* Watch all files, not just .py.
* Pass through gunicorn timeout config.
## 0.11.3 (February 17, 2020)
* Update dependencies.
## 0.11.2 (January 20, 2020)
* Don't open socket until after application startup.
* Support `--backlog`.
## 0.11.1 (December 20, 2019)
* Use a more liberal `h11` dependency. Either `0.8.*` or `0.9.*``.
## 0.11.0 (December 20, 2019)
* Fix reload/multiprocessing on Windows with Python 3.8.
* Drop IOCP support. (Required for fix above.)
* Add `uvicorn --version` flag.
* Add `--use-colors` and `--no-use-colors` flags.
* Display port correctly, when auto port selection isused with `--port=0`.
## 0.10.8 (November 12, 2019)
* Fix reload/multiprocessing error.
## 0.10.7 (November 12, 2019)
* Use resource_sharer.DupSocket to resolve socket sharing on Windows.
## 0.10.6 (November 12, 2019)
* Exit if `workers` or `reload` are use without an app import string style.
* Reorganise supervisor processes to properly hand over sockets on windows.
## 0.10.5 (November 12, 2019)
* Update uvloop dependency to 0.14+
## 0.10.4 (November 9, 2019)
* Error clearly when `workers=<NUM>` is used with app instance, instead of an app import string.
* Switch `--reload-dir` to current working directory by default.
## 0.10.3 (November 1, 2019)
* Add ``--log-level trace`
## 0.10.2 (October 31, 2019)
* Enable --proxy-headers by default.
## 0.10.1 (October 31, 2019)
* Resolve issues with logging when using `--reload` or `--workers`.
* Setup up root logger to capture output for all logger instances, not just `uvicorn.error` and `uvicorn.access`.
## 0.10.0 (October 29, 2019)
* Support for Python 3.8
* Separated out `uvicorn.error` and `uvicorn.access` logs.
* Coloured log output when connected to a terminal.
* Dropped `logger=` config setting.
* Added `--log-config [FILE]` and `log_config=[str|dict]`. May either be a Python logging config dictionary or the file name of a logging configuration.
* Added `--forwarded_allow_ips` and `forwarded_allow_ips`. Defaults to the value of the `$FORWARDED_ALLOW_IPS` environment variable or "127.0.0.1". The `--proxy-headers` flag now defaults to `True`, but only trusted IPs are used to populate forwarding info.
* The `--workers` setting now defaults to the value of the `$WEB_CONCURRENCY` environment variable.
* Added support for `--env-file`. Requires `python-dotenv`.
================================================
FILE: docs/server-behavior.md
================================================
# Server Behavior
Uvicorn is designed with particular attention to connection and resource management, in order to provide a robust server implementation. It aims to ensure graceful behavior to either server or client errors, and resilience to poor client behavior or denial of service attacks.
## HTTP Headers
The `Server` and `Date` headers are added to all outgoing requests.
If a `Connection: Close` header is included then Uvicorn will close the connection after the response. Otherwise connections will stay open, pending the keep-alive timeout.
If a `Content-Length` header is included then Uvicorn will ensure that the content length of the response body matches the value in the header, and raise an error otherwise.
If no `Content-Length` header is included then Uvicorn will use chunked encoding for the response body, and will set a `Transfer-Encoding` header if required.
If a `Transfer-Encoding` header is included then any `Content-Length` header will be ignored.
HTTP headers are mandated to be case-insensitive. Uvicorn will always send response headers strictly in lowercase.
---
## Flow Control
Proper flow control ensures that large amounts of data do not become buffered on the transport when either side of a connection is sending data faster than its counterpart is able to handle.
### Write flow control
If the write buffer passes a high water mark, then Uvicorn ensures the ASGI `send` messages will only return once the write buffer has been drained below the low water mark.
### Read flow control
Uvicorn will pause reading from a transport once the buffered request body hits a high water mark, and will only resume once `receive` has been called, or once the response has been sent.
---
## Request and Response bodies
### Response completion
Once a response has been sent, Uvicorn will no longer buffer any remaining request body. Any later calls to `receive` will return an `http.disconnect` message.
Together with the read flow control, this behavior ensures that responses that return without reading the request body will not stream any substantial amounts of data into memory.
### Expect: 100-Continue
The `Expect: 100-Continue` header may be sent by clients to require a confirmation from the server before uploading the request body. This can be used to ensure that large request bodies are only sent once the client has confirmation that the server is willing to accept the request.
Uvicorn ensures that any required `100 Continue` confirmations are only sent if the ASGI application calls `receive` to read the request body.
Note that proxy configurations may not necessarily forward on `Expect: 100-Continue` headers. In particular, Nginx defaults to buffering request bodies, and automatically sends `100 Continues` rather than passing the header on to the upstream server.
### HEAD requests
Uvicorn will strip any response body from HTTP requests with the `HEAD` method.
Applications should generally treat `HEAD` requests in the same manner as `GET` requests, in order to ensure that identical headers are sent in both cases, and that any ASGI middleware that modifies the headers will operate identically in either case.
One exception to this might be if your application serves large file downloads, in which case you might wish to only generate the response headers.
---
## Timeouts
Uvicorn provides the following timeouts:
* Keep-Alive. Defaults to 5 seconds. Between requests, connections must receive new data within this period or be disconnected.
---
## Resource Limits
Uvicorn provides the following resource limiting:
* Concurrency. Defaults to `None`. If set, this provides a maximum number of concurrent tasks *or* open connections that should be allowed. Any new requests or connections that occur once this limit has been reached will result in a "503 Service Unavailable" response. Setting this value to a limit that you know your servers are able to support will help ensure reliable resource usage, even against significantly over-resourced servers.
* Max requests. Defaults to `None`. If set, this provides a maximum number of HTTP requests that will be serviced before terminating a process. Together with a process manager this can be used to prevent memory leaks from impacting long running processes.
---
## Server Errors
Server errors will be logged at the `error` log level. All logging defaults to being written to `stdout`.
### Exceptions
If an exception is raised by an ASGI application, and a response has not yet been sent on the connection, then a `500 Server Error` HTTP response will be sent.
Uvicorn sends the headers and the status code as soon as it receives from the ASGI application. This means that if the application sends a [Response Start](https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event)
message with a status code of `200 OK`, and then an exception is raised, the response will still be sent with a status code of `200 OK`.
### Invalid responses
Uvicorn will ensure that ASGI applications send the correct sequence of messages, and will raise errors otherwise. This includes checking for no response sent, partial response sent, or invalid message sequences being sent.
---
## Graceful Process Shutdown
Graceful process shutdowns are particularly important during a restart period. During this period you want to:
* Start a number of new server processes to handle incoming requests, listening on the existing socket.
* Stop the previous server processes from listening on the existing socket.
* Close any connections that are not currently waiting on an HTTP response, and wait for any other connections to finalize their HTTP responses.
* Wait for any background tasks to run to completion, such as occurs when the ASGI application has sent the HTTP response, but the asyncio task has not yet run to completion.
Uvicorn handles process shutdown gracefully, ensuring that connections are properly finalized, and all tasks have run to completion. During a shutdown period Uvicorn will ensure that responses and tasks must still complete within the configured timeout periods.
---
## HTTP Pipelining
HTTP/1.1 provides support for sending multiple requests on a single connection, before having received each corresponding response. Servers are required to support HTTP pipelining, but it is now generally accepted to lead to implementation issues. It is not enabled on browsers, and may not necessarily be enabled on any proxies that the HTTP request passes through.
Uvicorn supports pipelining pragmatically. It will queue up any pipelined HTTP requests, and pause reading from the underlying transport. It will not start processing pipelined requests until each response has been dealt with in turn.
================================================
FILE: docs/settings.md
================================================
# Settings
Use the following options to configure Uvicorn, when running from the command line.
## Configuration Methods
There are three ways to configure Uvicorn:
1. **Command Line**: Use command line options when running Uvicorn directly.
```bash
uvicorn main:app --host 0.0.0.0 --port 8000
```
2. **Programmatic**: Use keyword arguments when running programmatically with `uvicorn.run()`.
```python
uvicorn.run("main:app", host="0.0.0.0", port=8000)
```
!!! note
When using `reload=True` or `workers=NUM`, you should put `uvicorn.run` into
an `if __name__ == '__main__'` clause in the main module.
3. **Environment Variables**: Use environment variables with the prefix `UVICORN_`.
```bash
export UVICORN_HOST="0.0.0.0"
export UVICORN_PORT="8000"
uvicorn main:app
```
CLI options and the arguments for `uvicorn.run()` take precedence over environment variables.
Also note that `UVICORN_*` prefixed settings cannot be used from within an environment
configuration file. Using an environment configuration file with the `--env-file` flag is
intended for configuring the ASGI application that uvicorn runs, rather than configuring
uvicorn itself.
## Application
* `APP` - The ASGI application to run, in the format `"<module>:<attribute>"`.
* `--factory` - Treat `APP` as an application factory, i.e. a `() -> <ASGI app>` callable.
* `--app-dir <path>` - Look for APP in the specified directory by adding it to the PYTHONPATH. **Default:** *Current working directory*.
## Socket Binding
* `--host <str>` - Bind socket to this host. Use `--host 0.0.0.0` to make the application available on your local network. IPv6 addresses are supported, for example: `--host '::'`. **Default:** *'127.0.0.1'*.
* `--port <int>` - Bind to a socket with this port. If set to 0, an available port will be picked. **Default:** *8000*.
* `--uds <path>` - Bind to a UNIX domain socket, for example `--uds /tmp/uvicorn.sock`. Useful if you want to run Uvicorn behind a reverse proxy.
* `--fd <int>` - Bind to socket from this file descriptor. Useful if you want to run Uvicorn within a process manager.
## Development
* `--reload` - Enable auto-reload. Uvicorn supports two versions of auto-reloading behavior enabled by this option. **Default:** *False*.
* `--reload-dir <path>` - Specify which directories to watch for python file changes. May be used multiple times. If unused, then by default the whole current directory will be watched. If you are running programmatically use `reload_dirs=[]` and pass a list of strings.
* `--reload-delay <float>` - Delay between previous and next check if application needs to be reloaded. **Default:** *0.25*.
### Reloading without watchfiles
If Uvicorn _cannot_ load [watchfiles](https://pypi.org/project/watchfiles/) at runtime, it will periodically look for changes in modification times to all `*.py` files (and only `*.py` files) inside of its monitored directories. See the `--reload-dir` option. Specifying other file extensions is not supported unless watchfiles is installed. See the `--reload-include` and `--reload-exclude` options for details.
### Reloading with watchfiles
For more nuanced control over which file modifications trigger reloads, install `uvicorn[standard]`, which includes watchfiles as a dependency. Alternatively, install [watchfiles](https://pypi.org/project/watchfiles/) where Uvicorn can see it.
Using Uvicorn with watchfiles will enable the following options (which are otherwise ignored):
* `--reload-include <glob-pattern>` - Specify a glob pattern to match files or directories which will be watched. May be used multiple times. By default the following patterns are included: `*.py`. These defaults can be overwritten by including them in `--reload-exclude`.
* `--reload-exclude <glob-pattern>` - Specify a glob pattern to match files or directories which will excluded from watching. May be used multiple times. By default the following patterns are excluded: `.*, .py[cod], .sw.*, ~*`. These defaults can be overwritten by including them in `--reload-include`.
!!! tip
When using Uvicorn through [WSL](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux), you might
have to set the `WATCHFILES_FORCE_POLLING` environment variable, for file changes to trigger a reload.
See [watchfiles documentation](https://watchfiles.helpmanual.io/api/watch/) for further details.
## Production
* `--workers <int>` - Number of worker processes. Defaults to the `$WEB_CONCURRENCY` environment variable if available, or 1. Not valid with `--reload`.
* `--env-file <path>` - Environment configuration file for the ASGI application. **Default:** *None*.
* `--timeout-worker-healthcheck <int>` - Maximum number of seconds to wait for a worker to respond to a healthcheck. **Default:** *5*.
!!! note
The `--reload` and `--workers` arguments are mutually exclusive. You cannot use both at the same time.
## Logging
* `--log-config <path>` - Logging configuration file. **Options:** *`dictConfig()` formats: .json, .yaml*. Any other format will be processed with `fileConfig()`. Set the `formatters.default.use_colors` and `formatters.access.use_colors` values to override the auto-detected behavior.
* If you wish to use a YAML file for your logging config, you will need to include PyYAML as a dependency for your project or install uvicorn with the `[standard]` optional extras.
* `--log-level <str>` - Set the log level. **Options:** *'critical', 'error', 'warning', 'info', 'debug', 'trace'.* **Default:** *'info'*.
* `--no-access-log` - Disable access log only, without changing log level.
* `--use-colors / --no-use-colors` - Enable / disable colorized formatting of the log records. If not set, colors will be auto-detected. This option is ignored if the `--log-config` CLI option is used.
## Implementation
* `--loop <str>` - Set the event loop implementation. The uvloop implementation provides greater performance, but is not compatible with Windows or PyPy. **Options:** *'auto', 'asyncio', 'uvloop'.* **Default:** *'auto'*.
* `--http <str>` - Set the HTTP protocol implementation. The httptools implementation provides greater performance, but it not compatible with PyPy. **Options:** *'auto', 'h11', 'httptools'.* **Default:** *'auto'*.
* `--ws <str>` - Set the WebSockets protocol implementation. Either of the `websockets` and `wsproto` packages are supported. There are two versions of `websockets` supported: `websockets` and `websockets-sansio`. Use `'none'` to ignore all websocket requests. **Options:** *'auto', 'none', 'websockets', 'websockets-sansio', 'wsproto'.* **Default:** *'auto'*.
* `--ws-max-size <int>` - Set the WebSockets max message size, in bytes. Only available with the `websockets` protocol. **Default:** *16777216* (16 MB).
* `--ws-max-queue <int>` - Set the maximum length of the WebSocket incoming message queue. Only available with the `websockets` protocol. **Default:** *32*.
* `--ws-ping-interval <float>` - Set the WebSockets ping interval, in seconds. Only available with the `websockets` protocol. **Default:** *20.0*.
* `--ws-ping-timeout <float>` - Set the WebSockets ping timeout, in seconds. Only available with the `websockets` protocol. **Default:** *20.0*.
* `--ws-per-message-deflate <bool>` - Enable/disable WebSocket per-message-deflate compression. Only available with the `websockets` protocol. **Default:** *True*.
* `--lifespan <str>` - Set the Lifespan protocol implementation. **Options:** *'auto', 'on', 'off'.* **Default:** *'auto'*.
* `--h11-max-incomplete-event-size <int>` - Set the maximum number of bytes to buffer of an incomplete event. Only available for `h11` HTTP protocol implementation. **Default:** *16384* (16 KB).
## Application Interface
* `--interface <str>` - Select ASGI3, ASGI2, or WSGI as the application interface.
Note that WSGI mode always disables WebSocket support, as it is not supported by the WSGI interface.
**Options:** *'auto', 'asgi3', 'asgi2', 'wsgi'.* **Default:** *'auto'*.
!!! warning
Uvicorn's native WSGI implementation is deprecated, you should switch
to [a2wsgi](https://github.com/abersheeran/a2wsgi) (`pip install a2wsgi`).
## HTTP
* `--root-path <str>` - Set the ASGI `root_path` for applications submounted below a given URL path. **Default:** *""*.
* `--proxy-headers / --no-proxy-headers` - Enable/Disable X-Forwarded-Proto, X-Forwarded-For to populate remote address info. Defaults to enabled, but is restricted to only trusting connecting IPs in the `forwarded-allow-ips` configuration.
* `--forwarded-allow-ips <comma-separated-list>` - Comma separated list of IP Addresses, IP Networks, or literals (e.g. UNIX Socket path) to trust with proxy headers. Defaults to the `$FORWARDED_ALLOW_IPS` environment variable if available, or '127.0.0.1'. The literal `'*'` means trust everything.
* `--server-header / --no-server-header` - Enable/Disable default `Server` header. **Default:** *True*.
* `--date-header / --no-date-header` - Enable/Disable default `Date` header. **Default:** *True*.
* `--header <name:value>` - Specify custom default HTTP response headers as a Name:Value pair. May be used multiple times.
!!! note
The `--no-date-header` flag doesn't have effect on the `websockets` implementation.
## HTTPS
The [SSL context](https://docs.python.org/3/library/ssl.html#ssl.SSLContext) can be configured with the following options:
* `--ssl-keyfile <path>` - The SSL key file.
* `--ssl-keyfile-password <str>` - The password to decrypt the ssl key.
* `--ssl-certfile <path>` - The SSL certificate file.
* `--ssl-version <int>` - The SSL version to use. **Default:** *ssl.PROTOCOL_TLS_SERVER*.
* `--ssl-cert-reqs <int>` - Whether client certificate is required. **Default:** *ssl.CERT_NONE*.
* `--ssl-ca-certs <str>` - The CA certificates file.
* `--ssl-ciphers <str>` - The ciphers to use. **Default:** *"TLSv1"*.
To understand more about the SSL context options, please refer to the [Python documentation](https://docs.python.org/3/library/ssl.html).
## Resource Limits
* `--limit-concurrency <int>` - Maximum number of concurrent connections or tasks to allow, before issuing HTTP 503 responses. Useful for ensuring known memory usage patterns even under over-resourced loads.
* `--limit-max-requests <int>` - Maximum number of requests to service before terminating the process. Useful when running together with a process manager, for preventing memory leaks from impacting long-running processes.
* `--limit-max-requests-jitter <int>` - Maximum jitter to add to `limit-max-requests`. Each worker adds a random number in the range `[0, jitter]`, staggering restarts to avoid all workers restarting simultaneously. **Default:** *0*.
* `--backlog <int>` - Maximum number of connections to hold in backlog. Relevant for heavy incoming traffic. **Default:** *2048*.
## Timeouts
* `--timeout-keep-alive <int>` - Close Keep-Alive connections if no new data is received within this timeout (in seconds). **Default:** *5*.
* `--timeout-graceful-shutdown <int>` - Maximum number of seconds to wait for graceful shutdown. After this timeout, the server will start terminating requests.
================================================
FILE: docs/sponsorship.md
================================================
# ✨ Sponsor Starlette & Uvicorn ✨
Thank you for your interest in sponsoring Starlette and Uvicorn! ❤️
Your support *directly* contributes to the ongoing development, maintenance, and long-term sustainability of both projects.
<div style="display: flex; justify-content: center; gap: 4rem; margin: 2rem 0; text-align: center;">
<div style="padding: 1rem;">
<h3 style="color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;">67M+</h3>
<p>Starlette Downloads/Month</p>
</div>
<div style="padding: 1rem;">
<h3 style="color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;">57M+</h3>
<p>Uvicorn Downloads/Month</p>
</div>
<div style="padding: 1rem;">
<h3 style="color: #6e5494; font-size: 2em; margin-bottom: 0.5rem;">19K+</h3>
<p>Combined GitHub Stars</p>
</div>
</div>
## Why Sponsor?
While Starlette and Uvicorn are part of the [Encode](https://github.com/encode) organization,
they have been primarily maintained by [**Marcelo Trylesinski (Kludex)**](https://github.com/Kludex)
for the past several years. His dedication and consistent work have been instrumental in keeping
these projects robust, secure, and up-to-date.
This sponsorship page was created to give the community an opportunity to support Marcelo's continued
efforts in maintaining and improving both projects. Your sponsorship directly enables him to
dedicate more time and resources to maintaining and improving these essential tools:
- [x] **Active Development:** Developing new features, enhancing existing ones, and
keeping both projects aligned with the latest developments in the Python and ASGI ecosystems. 💻
- [x] **Community Support:** Providing better support, addressing user issues,
and cultivating a welcoming environment for contributors. 🤝
- [x] **Long-Term Stability:** Ensuring the long-term viability of both projects through strategic
planning and addressing technical debt. 🌳
- [x] **Bug Fixes & Maintenance:** Providing prompt attention to bug reports and
general maintenance to keep the projects reliable. 🔨
- [x] **Security:** Ensuring robust security practices, conducting regular security audits, and
promptly addressing vulnerabilities to protect millions of production deployments. 🔒
- [x] **Documentation:** Creating comprehensive guides, tutorials, and examples to help users of all skill levels. 📖
## How Sponsorship Works
We currently manage sponsorships *exclusively* through **GitHub Sponsors**. This platform integrates seamlessly with the GitHub ecosystem, making it easy for organizations to contribute.
<div style="text-align: center; padding: 2rem; margin: 2rem 0; background: linear-gradient(135deg, #6e5494, #24292e); border-radius: 10px; color: white;">
<h2 style="color: white; margin-bottom: 1rem;">🌟 Become a Sponsor Today! 🌟</h2>
<p style="margin-bottom: 1.5rem; font-size: 1.1em;">Your support helps keep Starlette and Uvicorn growing stronger!</p>
<a href="https://github.com/sponsors/Kludex"
style="display: inline-block; padding: 1rem 2rem; background-color: #238636; color: white; text-decoration: none; border-radius: 6px; font-size: 1.2em; font-weight: bold; transition: all 0.3s ease-in-out;"
onmouseover="this.style.backgroundColor='#2ea043';this.style.transform='translateY(-2px)'"
onmouseout="this.style.backgroundColor='#238636';this.style.transform='translateY(0)'">
❤️ Sponsor on GitHub
</a>
</div>
## Sponsorship Tiers 🎁
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin: 2rem 0;">
<div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; display: flex; flex-direction: column;">
<h3 style="color: #cd7f32;">🥉 Bronze Sponsor</h3>
<div style="font-size: 1.5em; margin: 1rem 0;">$100<span style="font-size: 0.6em;">/month</span></div>
<ul style="list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;">
<li>✓ Company name on Sponsors page</li>
<li>✓ Small logo with link</li>
<li>✓ Our eternal gratitude</li>
</ul>
<div style="text-align: center; margin-top: auto;">
<a href="https://github.com/sponsors/Kludex" style="display: inline-block; padding: 0.5rem 1rem; background-color: #cd7f32; color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">
Become a Bronze Sponsor
</a>
</div>
</div>
<div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; display: flex; flex-direction: column;">
<h3 style="color: #c0c0c0;">🥈 Silver Sponsor</h3>
<div style="font-size: 1.5em; margin: 1rem 0;">$250<span style="font-size: 0.6em;">/month</span></div>
<ul style="list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;">
<li>✓ All Bronze benefits</li>
<li>✓ Medium-sized logo</li>
<li>✓ Release notes mention</li>
</ul>
<div style="text-align: center; margin-top: auto;">
<a href="https://github.com/sponsors/Kludex" style="display: inline-block; padding: 0.5rem 1rem; background-color: #c0c0c0; color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">
Become a Silver Sponsor
</a>
</div>
</div>
<div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius: 6px; background: #fff; position: relative; overflow: hidden; display: flex; flex-direction: column;">
<div style="position: absolute; top: 10px; right: -25px; background: #238636; color: white; padding: 5px 30px; transform: rotate(45deg);">
Popular
</div>
<h3 style="color: #ffd700;">🥇 Gold Sponsor</h3>
<div style="font-size: 1.5em; margin: 1rem 0;">$500<span style="font-size: 0.6em;">/month</span></div>
<ul style="list-style: none; padding: 0; margin-bottom: 1rem; min-height: 90px;">
<li>✓ All Silver benefits</li>
<li>✓ Large logo on main pages</li>
<li>✓ Priority support</li>
</ul>
<div style="text-align: center; margin-top: auto;">
<a href="https://github.com/sponsors/Kludex" style="display: inline-block; padding: 0.5rem 1rem; background-color: #ffd700; color: black; text-decoration: none; border-radius: 6px; font-weight: bold; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">
Become a Gold Sponsor
</a>
</div>
</div>
</div>
<div style="text-align: center; margin: 2rem 0;">
<h3>🤝 Custom Sponsor</h3>
<p>Looking for something different? <a href="mailto:marcelotryle@gmail.com">Contact us</a> to discuss custom sponsorship options!</p>
</div>
## Current Sponsors
**Thank you to our generous sponsors!** 🙏
<div style="display: flex; flex-direction: column; gap: 3rem; margin: 2rem 0;">
<div>
<h3 style="text-align: center; color: #ffd700; margin-bottom: 1.5rem;">🏆 Gold Sponsors</h3>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;">
<a href="https://fastapi.tiangolo.com" style="text-decoration: none;">
<div style="width: 200px; background: #f6f8fa; border-radius: 8px; padding: 1rem; text-align: center;">
<div style="height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 0.75rem;">
<img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI" style="max-width: 100%; max-height: 100%; object-fit: contain;">
</div>
<p style="margin: 0; color: #57606a; font-size: 0.9em;">Modern, fast web framework for building APIs with Python 3.8+</p>
</div>
</a>
</div>
</div>
<div>
<h3 style="text-align: center; color: #c0c0c0; margin-bottom: 1.5rem;">🥈 Silver Sponsors</h3>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;">
<!-- Add Silver Sponsors here -->
</div>
</div>
<div>
<h3 style="text-align: center; color: #cd7f32; margin-bottom: 1.5rem;">🥉 Bronze Sponsors</h3>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; align-items: center;">
<!-- Add Bronze Sponsors here -->
</div>
</div>
</div>
## Alternative Sponsorship Platforms
<div style="background: #f6f8fa; padding: 1.5rem; border-radius: 8px; margin: 2rem 0;">
<h3>📢 We Want Your Input!</h3>
<p>We are currently evaluating whether to expand our sponsorship options beyond GitHub Sponsors. If your company would be interested in sponsoring Starlette and Uvicorn but prefers to use a different platform (e.g., Open Collective, direct invoicing), please let us know!</p>
<p>Your feedback is invaluable in helping us make sponsorship as accessible as possible. Share your thoughts by:</p>
<ul>
<li>Opening a discussion on our <a href="https://github.com/Kludex/starlette/discussions">GitHub repository</a></li>
<li>Contacting us directly at <a href="mailto:marcelotryle@gmail.com">marcelotryle@gmail.com</a></li>
</ul>
</div>
<a id="acknowledgments"></a>
## Community & Future Plans 🌟
We want to express our deepest gratitude to all the contributors who have helped shape Starlette and
Uvicorn over the years. These projects wouldn't be what they are today without the incredible work of
every single contributor.
Special thanks to some of our most impactful contributors:
- **Tom Christie** ([@tomchristie](https://github.com/tomchristie)) - The original creator of Starlette and Uvicorn.
- **Adrian Garcia Badaracco** ([@adriangb](https://github.com/adriangb)) - Major contributor to Starlette.
- **Thomas Grainger** ([@graingert](https://github.com/graingert)) - Major contributor to AnyIO, and significant contributions to Starlette and Uvicorn.
- **Alex Grönholm** ([@agronholm](https://github.com/agronholm)) - Creator of AnyIO.
- **Florimond Manca** ([@florimondmanca](https://github.com/florimondmanca)) - Important contributions to Starlette and Uvicorn.
If you want your name removed from the list above, or if I forgot a significant contributor, please let me know.
You can view all contributors on GitHub:
[Starlette Contributors](https://github.com/Kludex/starlette/graphs/contributors) / [Uvicorn Contributors](https://github.com/Kludex/uvicorn/graphs/contributors).
While the current sponsorship program directly supports Marcelo's maintenance work, we are exploring ways
to distribute funding to other key contributors in the future. This initiative is still in early planning
stages, as we want to ensure a fair and sustainable model that recognizes the valuable contributions of
our community members.
================================================
FILE: mkdocs.yml
================================================
site_name: Uvicorn
site_description: The lightning-fast ASGI server.
site_url: https://uvicorn.dev
repo_name: Kludex/uvicorn
repo_url: https://github.com/Kludex/uvicorn
edit_uri: edit/main/docs/
strict: true
theme:
name: material
custom_dir: docs/overrides
logo: uvicorn.png
favicon: uvicorn.png
palette:
- scheme: "default"
media: "(prefers-color-scheme: light)"
toggle:
icon: "material/lightbulb"
name: "Switch to dark mode"
- scheme: "slate"
media: "(prefers-color-scheme: dark)"
primary: "blue"
toggle:
icon: "material/lightbulb-outline"
name: "Switch to light mode"
icon:
repo: fontawesome/brands/github
features:
- content.code.annotate
- content.code.copy # https://squidfunk.github.io/mkdocs-material/upgrade/?h=content+copy#contentcodecopy
- content.tabs.link
- navigation.footer # https://squidfunk.github.io/mkdocs-material/upgrade/?h=content+copy#navigationfooter
- navigation.path
- navigation.sections # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation
- navigation.top # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#back-to-top-button
- navigation.tracking
- search.suggest
- search.highlight
- toc.follow # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#anchor-following
# https://www.mkdocs.org/user-guide/configuration/#validation
validation:
omitted_files: warn
absolute_links: warn
unrecognized_links: warn
nav:
- Welcome: index.md
- Installation: installation.md
- Settings: settings.md
- Server Behavior: server-behavior.md
- Concepts:
- ASGI: concepts/asgi.md
- Lifespan: concepts/lifespan.md
- WebSockets: concepts/websockets.md
- Event Loop: concepts/event-loop.md
- Deployment:
- Deployment: deployment/index.md
- Docker: deployment/docker.md
- Release Notes: release-notes.md
- Contributing: contributing.md
- Sponsorship: sponsorship.md
extra:
analytics:
provider: google
property: G-KTS6TXPD85
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/Kludex/uvicorn
- icon: fontawesome/brands/discord
link: https://discord.com/invite/RxKUF5JuHs
- icon: fontawesome/brands/twitter
link: https://x.com/marcelotryle
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/marcelotryle
- icon: fontawesome/solid/globe
link: https://fastapiexpert.com
markdown_extensions:
- attr_list
- admonition
- codehilite:
css_class: highlight
- toc:
permalink: true
- pymdownx.details
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.extra:
pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
plugins:
- search
- mkdocstrings:
handlers:
python:
inventories:
- https://docs.python.org/3/objects.inv
- llmstxt:
full_output: llms-full.txt
markdown_description: |-
Uvicorn is a lightning-fast ASGI server implementation, designed to run asynchronous web applications.
It supports the ASGI specification, which allows for both HTTP/1.1 and WebSocket protocols.
sections:
Sections:
- index.md
- settings.md
- deployment/*.md
- server-behavior.md
Concepts:
- concepts/*.md
hooks:
- docs/plugins/main.py
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "uvicorn"
dynamic = ["version"]
description = "The lightning-fast ASGI server."
readme = "README.md"
license = "BSD-3-Clause"
license-files = ["LICENSE.md"]
requires-python = ">=3.10"
authors = [
{ name = "Tom Christie", email = "tom@tomchristie.com" },
]
maintainers = [
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
"click>=7.0",
"h11>=0.8",
"typing_extensions>=4.0; python_version < '3.11'",
]
[project.optional-dependencies]
standard = [
"colorama>=0.4; sys_platform == 'win32'",
"httptools>=0.6.3",
"python-dotenv>=0.13",
"PyYAML>=5.1",
"uvloop>=0.15.1; sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')",
"watchfiles>=0.20",
"websockets>=10.4",
]
[dependency-groups]
dev = [
# We add uvicorn[standard] so `uv sync` considers the extras.
"uvicorn[standard]",
"ruff==0.15.1",
"pytest==9.0.2",
"pytest-mock==3.15.1",
"pytest-xdist[psutil]==3.8.0",
"pytest-codspeed>=4.1.1",
"mypy==1.19.1",
"types-click==7.1.8",
"types-pyyaml==6.0.12.20250915",
"trustme==1.2.1",
"cryptography>=44.0.3",
"coverage==7.13.4",
"coverage-conditional-plugin==0.9.0",
"coverage-enable-subprocess==1.0",
"httpx==0.28.1",
# check dist
"twine==6.2.0",
# Explicit optionals,
"a2wsgi==1.10.10",
"wsproto==1.3.2",
"websockets==13.1",
]
docs = [
"mkdocs==1.6.1",
"mkdocs-material==9.7.1",
"mkdocstrings-python==2.0.2",
"mkdocs-llmstxt==0.5.0",
]
[tool.uv]
default-groups = ["dev", "docs"]
required-version = ">=0.8.6"
[project.scripts]
uvicorn = "uvicorn.main:main"
[project.urls]
Changelog = "https://uvicorn.dev/release-notes"
Funding = "https://github.com/sponsors/encode"
Homepage = "https://uvicorn.dev/"
Source = "https://github.com/Kludex/uvicorn"
[tool.hatch.version]
path = "uvicorn/__init__.py"
[tool.hatch.build.targets.sdist]
include = ["/uvicorn", "/tests"]
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = ["E", "F", "I", "FA", "UP"]
ignore = ["B904", "B028", "UP031"]
[tool.ruff.lint.isort]
combine-as-imports = true
[tool.mypy]
warn_unused_ignores = true
warn_redundant_casts = true
show_error_codes = true
disallow_untyped_defs = false
ignore_missing_imports = true
follow_imports = "silent"
[tool.pytest.ini_options]
addopts = "-rxXs --strict-config --strict-markers -n 8"
xfail_strict = true
filterwarnings = [
"error",
"ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning",
"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
"ignore: remove second argument of ws_handler:DeprecationWarning:websockets",
"ignore: websockets.legacy is deprecated.*:DeprecationWarning",
"ignore: websockets.server.WebSocketServerProtocol is deprecated.*:DeprecationWarning",
"ignore: websockets.client.connect is deprecated.*:DeprecationWarning",
# httptools in Python 3.14t needs the `PYTHON_GIL=0` environment variable, or raises a RuntimeWarning.
"ignore: The global interpreter lock (GIL)*:RuntimeWarning"
]
[tool.coverage.run]
parallel = true
source_pkgs = ["uvicorn", "tests"]
plugins = ["coverage_conditional_plugin"]
omit = ["uvicorn/workers.py", "uvicorn/__main__.py", "uvicorn/_compat.py", "tests/benchmarks/*"]
[tool.coverage.report]
precision = 2
fail_under = 100
show_missing = true
skip_covered = true
exclude_lines = [
"pragma: no cover",
"pragma: nocover",
"pragma: full coverage",
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:",
"raise NotImplementedError",
]
[tool.coverage.coverage_conditional_plugin.omit]
"sys_platform == 'win32'" = [
"uvicorn/loops/uvloop.py",
"uvicorn/supervisors/multiprocess.py",
"tests/supervisors/test_multiprocess.py",
]
"sys_platform != 'win32'" = ["uvicorn/loops/asyncio.py"]
[tool.coverage.coverage_conditional_plugin.rules]
py-win32 = "sys_platform == 'win32'"
py-not-win32 = "sys_platform != 'win32'"
py-linux = "sys_platform == 'linux'"
py-not-linux = "sys_platform != 'linux'"
py-darwin = "sys_platform == 'darwin'"
py-gte-39 = "sys_version_info >= (3, 9)"
py-lt-39 = "sys_version_info < (3, 9)"
py-gte-310 = "sys_version_info >= (3, 10)"
py-lt-310 = "sys_version_info < (3, 10)"
py-gte-311 = "sys_version_info >= (3, 11)"
py-lt-311 = "sys_version_info < (3, 11)"
================================================
FILE: scripts/build
================================================
#!/bin/sh -e
set -x
uv build
uv run twine check dist/*
uv run mkdocs build
================================================
FILE: scripts/check
================================================
#!/bin/sh -e
export SOURCE_FILES="uvicorn tests"
set -x
./scripts/sync-version
uv run ruff format --check --diff $SOURCE_FILES
uv run mypy $SOURCE_FILES
uv run ruff check $SOURCE_FILES
================================================
FILE: scripts/coverage
================================================
#!/bin/sh -e
export SOURCE_FILES="uvicorn tests"
set -x
uv run coverage combine
uv run coverage report
================================================
FILE: scripts/docs
================================================
#!/bin/sh -e
set -x
uv run mkdocs "$@"
================================================
FILE: scripts/install
================================================
#!/bin/sh -e
set -x
uv sync --frozen
================================================
FILE: scripts/lint
================================================
#!/bin/sh -e
export SOURCE_FILES="uvicorn tests"
set -x
uv run ruff format $SOURCE_FILES
uv run ruff check --fix $SOURCE_FILES
================================================
FILE: scripts/sync-version
================================================
#!/bin/sh -e
SEMVER_REGEX="([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(\+[0-9A-Za-z-]+)?"
CHANGELOG_VERSION=$(grep -o -E $SEMVER_REGEX docs/release-notes.md | head -1)
VERSION=$(grep -o -E $SEMVER_REGEX uvicorn/__init__.py | head -1)
if [ "$CHANGELOG_VERSION" != "$VERSION" ]; then
echo "Version in changelog does not match version in uvicorn/__init__.py!"
exit 1
fi
================================================
FILE: scripts/test
================================================
#!/bin/sh
set -ex
if [ -z $GITHUB_ACTIONS ]; then
scripts/check
fi
export COVERAGE_PROCESS_START=$(pwd)/pyproject.toml
uv run coverage run --debug config -m pytest "$@"
if [ -z $GITHUB_ACTIONS ]; then
scripts/coverage
fi
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/benchmarks/__init__.py
================================================
================================================
FILE: tests/benchmarks/http.py
================================================
from __future__ import annotations
import asyncio
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, TypeAlias
from uvicorn._types import ASGIApplication, Scope
from uvicorn.config import Config
from uvicorn.lifespan.off import LifespanOff
from uvicorn.protocols.http.h11_impl import H11Protocol
from uvicorn.server import ServerState
if TYPE_CHECKING:
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol
WSProtocol: TypeAlias = WebSocketProtocol | _WSProtocol
HTTPProtocol: TypeAlias = H11Protocol | HttpToolsProtocol
SIMPLE_GET_REQUEST = b"\r\n".join([b"GET / HTTP/1.1", b"Host: example.org", b"", b""])
SIMPLE_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: application/json",
b"Content-Length: 18",
b"",
b'{"hello": "world"}',
]
)
LARGE_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: text/plain",
b"Content-Length: 100000",
b"",
b"x" * 100000,
]
)
HTTP10_GET_REQUEST = b"\r\n".join([b"GET / HTTP/1.0", b"Host: example.org", b"", b""])
CONNECTION_CLOSE_REQUEST = b"\r\n".join([b"GET / HTTP/1.1", b"Host: example.org", b"Connection: close", b"", b""])
START_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: application/json",
b"Content-Length: 18",
b"",
b"",
]
)
FINISH_POST_REQUEST = b'{"hello": "world"}'
BODY_CHUNK_SIZE = 256
FRAGMENTED_BODY_SIZE = 100_000
FRAGMENTED_POST_HEADERS = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: application/octet-stream",
b"Content-Length: " + str(FRAGMENTED_BODY_SIZE).encode(),
b"",
b"",
]
)
FRAGMENTED_BODY_CHUNKS = [b"x" * BODY_CHUNK_SIZE] * (FRAGMENTED_BODY_SIZE // BODY_CHUNK_SIZE)
class MockTransport:
def __init__(self) -> None:
self.buffer = b""
self.closed = False
self.read_paused = False
def get_extra_info(self, key: Any) -> Any:
return {
"sockname": ("127.0.0.1", 8000),
"peername": ("127.0.0.1", 8001),
"sslcontext": False,
}.get(key)
def write(self, data: bytes) -> None:
self.buffer += data
def close(self) -> None:
self.closed = True
def pause_reading(self) -> None:
self.read_paused = True
def resume_reading(self) -> None:
self.read_paused = False
def is_closing(self) -> bool:
return self.closed
def clear_buffer(self) -> None:
self.buffer = b""
def set_protocol(self, protocol: asyncio.Protocol) -> None:
pass
class MockTimerHandle:
def __init__(
self, loop_later_list: list[MockTimerHandle], delay: float, callback: Callable[[], None], args: tuple[Any, ...]
) -> None:
self.loop_later_list = loop_later_list
self.delay = delay
self.callback = callback
self.args = args
self.cancelled = False
def cancel(self) -> None:
if not self.cancelled:
self.cancelled = True
self.loop_later_list.remove(self)
class MockLoop:
def __init__(self) -> None:
self._tasks: list[asyncio.Task[Any]] = []
self._later: list[MockTimerHandle] = []
def create_task(self, coroutine: Any) -> Any:
self._tasks.insert(0, coroutine)
return MockTask()
def call_later(self, delay: float, callback: Callable[[], None], *args: Any) -> MockTimerHandle:
handle = MockTimerHandle(self._later, delay, callback, args)
self._later.insert(0, handle)
return handle
async def run_one(self) -> Any:
return await self._tasks.pop()
class MockTask:
def add_done_callback(self, callback: Callable[[], None]) -> None:
pass
class MockProtocol(asyncio.Protocol):
loop: MockLoop
transport: MockTransport
timeout_keep_alive_task: asyncio.TimerHandle | None
ws_protocol_class: type[WSProtocol] | None
scope: Scope
def make_config(app: ASGIApplication, **kwargs: Any) -> Config:
return Config(app=app, **kwargs)
def get_connected_protocol(
config: Config,
http_protocol_cls: type[HTTPProtocol],
) -> MockProtocol:
loop = MockLoop()
transport = MockTransport()
lifespan = LifespanOff(config)
server_state = ServerState()
protocol = http_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop) # type: ignore
protocol.connection_made(transport) # type: ignore[arg-type]
return protocol # type: ignore[return-value]
================================================
FILE: tests/benchmarks/test_http.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from tests.benchmarks.http import (
CONNECTION_CLOSE_REQUEST,
FINISH_POST_REQUEST,
FRAGMENTED_BODY_CHUNKS,
FRAGMENTED_POST_HEADERS,
HTTP10_GET_REQUEST,
LARGE_POST_REQUEST,
SIMPLE_GET_REQUEST,
SIMPLE_POST_REQUEST,
START_POST_REQUEST,
get_connected_protocol,
make_config,
)
from tests.response import Response
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
if TYPE_CHECKING:
from tests.benchmarks.http import HTTPProtocol
pytestmark = [pytest.mark.anyio, pytest.mark.benchmark]
_plain_text_app = Response("Hello, world", media_type="text/plain")
_no_content_app = Response(b"", status_code=204)
_chunked_app = Response(b"Hello, world!", status_code=200, headers={"transfer-encoding": "chunked"})
_plain_text_config = make_config(_plain_text_app)
_chunked_config = make_config(_chunked_app)
async def _body_echo_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
body = b""
while True:
message = await receive()
body += message.get("body", b"") # type: ignore[operator]
if not message.get("more_body", False):
break
headers = [(b"content-length", str(len(body)).encode())]
await send({"type": "http.response.start", "status": 200, "headers": headers})
await send({"type": "http.response.body", "body": body})
_body_echo_config = make_config(_body_echo_app)
async def test_bench_simple_get(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(SIMPLE_GET_REQUEST)
await protocol.loop.run_one()
async def test_bench_simple_post(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(SIMPLE_POST_REQUEST)
await protocol.loop.run_one()
async def test_bench_large_post(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(LARGE_POST_REQUEST)
await protocol.loop.run_one()
async def test_bench_pipelined_requests(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(SIMPLE_GET_REQUEST * 3)
await protocol.loop.run_one()
await protocol.loop.run_one()
await protocol.loop.run_one()
async def test_bench_keepalive_reuse(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(SIMPLE_GET_REQUEST)
await protocol.loop.run_one()
protocol.data_received(SIMPLE_GET_REQUEST)
await protocol.loop.run_one()
async def test_bench_chunked_response(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_chunked_config, http_protocol_cls)
protocol.data_received(SIMPLE_GET_REQUEST)
await protocol.loop.run_one()
async def test_bench_http10(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(HTTP10_GET_REQUEST)
await protocol.loop.run_one()
async def test_bench_connection_close(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(CONNECTION_CLOSE_REQUEST)
await protocol.loop.run_one()
async def test_bench_fragmented_body(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_plain_text_config, http_protocol_cls)
protocol.data_received(FRAGMENTED_POST_HEADERS)
for chunk in FRAGMENTED_BODY_CHUNKS:
protocol.data_received(chunk)
await protocol.loop.run_one()
async def test_bench_post_body_receive(http_protocol_cls: type[HTTPProtocol]) -> None:
protocol = get_connected_protocol(_body_echo_config, http_protocol_cls)
protocol.data_received(START_POST_REQUEST)
protocol.data_received(FINISH_POST_REQUEST)
await protocol.loop.run_one()
================================================
FILE: tests/benchmarks/test_ws.py
================================================
from __future__ import annotations
import importlib.util
from typing import TYPE_CHECKING
import pytest
from tests.benchmarks.http import make_config
from tests.benchmarks.ws import WS_UPGRADE, get_connected_ws_protocol
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
if TYPE_CHECKING:
from tests.benchmarks.ws import WSProtocolClass
pytestmark = [pytest.mark.anyio, pytest.mark.benchmark]
@pytest.fixture(
params=[
pytest.param(
"wsproto",
marks=pytest.mark.skipif(not importlib.util.find_spec("wsproto"), reason="wsproto not installed."),
id="wsproto",
),
pytest.param("websockets-sansio", id="websockets-sansio"),
]
)
def ws_cls(request: pytest.FixtureRequest) -> WSProtocolClass:
if request.param == "wsproto":
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol
return WSProtocol
from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol
return WebSocketsSansIOProtocol
async def _ws_accept_close_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
await receive()
await send({"type": "websocket.accept"})
await send({"type": "websocket.close", "code": 1000})
async def _ws_send_text_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
await receive()
await send({"type": "websocket.accept"})
await send({"type": "websocket.send", "text": "Hello, world!"})
await send({"type": "websocket.close", "code": 1000})
_ws_accept_close_config = make_config(_ws_accept_close_app, access_log=False)
_ws_send_text_config = make_config(_ws_send_text_app, access_log=False)
async def test_bench_ws_handshake(ws_cls: WSProtocolClass) -> None:
protocol = get_connected_ws_protocol(_ws_accept_close_config, ws_cls)
protocol.data_received(WS_UPGRADE)
await protocol.loop.run_one()
async def test_bench_ws_send_text(ws_cls: WSProtocolClass) -> None:
protocol = get_connected_ws_protocol(_ws_send_text_config, ws_cls)
protocol.data_received(WS_UPGRADE)
await protocol.loop.run_one()
================================================
FILE: tests/benchmarks/ws.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Any, TypeAlias
from tests.benchmarks.http import MockLoop, MockTransport
from uvicorn.config import Config
from uvicorn.lifespan.off import LifespanOff
from uvicorn.server import ServerState
if TYPE_CHECKING:
from uvicorn.protocols.websockets.websockets_sansio_impl import WebSocketsSansIOProtocol
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol
WSProtocolClass: TypeAlias = type[WSProtocol] | type[WebSocketsSansIOProtocol]
WS_UPGRADE = (
b"GET / HTTP/1.1\r\n"
b"Host: example.org\r\n"
b"Upgrade: websocket\r\n"
b"Connection: Upgrade\r\n"
b"Sec-WebSocket-Key: YmVuY2htYXJra2V5MTIzNA==\r\n"
b"Sec-WebSocket-Version: 13\r\n"
b"\r\n"
)
# Masked text frame: "Hello, world!" (13 bytes) with zero mask key
WS_TEXT_FRAME = b"\x81\x8d\x00\x00\x00\x00Hello, world!"
# Masked close frame: code 1000 with zero mask key
WS_CLOSE_FRAME = b"\x88\x82\x00\x00\x00\x00\x03\xe8"
def get_connected_ws_protocol(config: Config, ws_protocol_cls: WSProtocolClass) -> Any:
loop = MockLoop()
transport = MockTransport()
lifespan = LifespanOff(config)
server_state = ServerState()
protocol = ws_protocol_cls(config=config, server_state=server_state, app_state=lifespan.state, _loop=loop) # type: ignore[arg-type]
protocol.connection_made(transport) # type: ignore[arg-type]
return protocol
================================================
FILE: tests/conftest.py
================================================
from __future__ import annotations
import contextlib
import importlib.util
import os
import socket
import ssl
from copy import deepcopy
from hashlib import md5
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
from uuid import uuid4
import pytest
try:
import trustme
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
HAVE_TRUSTME = True
except ImportError: # pragma: no cover
HAVE_TRUSTME = False
from uvicorn.config import LOGGING_CONFIG
from uvicorn.importer import import_from_string
# Note: We explicitly turn the propagate on just for tests, because pytest
# caplog not able to capture no-propagate loggers.
#
# And the caplog_for_logger helper also not work on test config cases, because
# when create Config object, Config.configure_logging will remove caplog.handler.
#
# The simple solution is set propagate=True before execute tests.
#
# See also: https://github.com/pytest-dev/pytest/issues/3697
LOGGING_CONFIG["loggers"]["uvicorn"]["propagate"] = True
@pytest.fixture
def tls_certificate_authority() -> trustme.CA:
if not HAVE_TRUSTME:
pytest.skip("trustme not installed") # pragma: no cover
return trustme.CA()
@pytest.fixture
def tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.LeafCert:
return tls_certificate_authority.issue_cert(
"localhost",
"127.0.0.1",
"::1",
)
@pytest.fixture
def tls_ca_certificate_pem_path(tls_certificate_authority: trustme.CA):
with tls_certificate_authority.cert_pem.tempfile() as ca_cert_pem:
yield ca_cert_pem
@pytest.fixture
def tls_ca_certificate_private_key_path(tls_certificate_authority: trustme.CA):
with tls_certificate_authority.private_key_pem.tempfile() as private_key:
yield private_key
@pytest.fixture
def tls_certificate_private_key_encrypted_path(tls_certificate):
private_key = serialization.load_pem_private_key(
tls_certificate.private_key_pem.bytes(),
password=None,
backend=default_backend(),
)
encrypted_key = private_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.BestAvailableEncryption(b"uvicorn password for the win"),
)
with trustme.Blob(encrypted_key).tempfile() as private_encrypted_key:
yield private_encrypted_key
@pytest.fixture
def tls_certificate_private_key_path(tls_certificate: trustme.CA):
with tls_certificate.private_key_pem.tempfile() as private_key:
yield private_key
@pytest.fixture
def tls_certificate_key_and_chain_path(tls_certificate: trustme.LeafCert):
with tls_certificate.private_key_and_cert_chain_pem.tempfile() as cert_pem:
yield cert_pem
@pytest.fixture
def tls_certificate_server_cert_path(tls_certificate: trustme.LeafCert):
with tls_certificate.cert_chain_pems[0].tempfile() as cert_pem:
yield cert_pem
@pytest.fixture
def tls_ca_ssl_context(tls_certificate_authority: trustme.CA) -> ssl.SSLContext:
ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
tls_certificate_authority.configure_trust(ssl_ctx)
return ssl_ctx
@pytest.fixture(scope="package")
def reload_directory_structure(tmp_path_factory: pytest.TempPathFactory):
"""
This fixture creates a directory structure to enable reload parameter tests
The fixture has the following structure:
root
├── [app, app_first, app_second, app_third]
│ ├── css
│ │ └── main.css
│ ├── js
│ │ └── main.js
│ ├── src
│ │ └── main.py
│ └── sub
│ └── sub.py
├── ext
│ └── ext.jpg
├── .dotted
├── .dotted_dir
│ └── file.txt
└── main.py
"""
root = tmp_path_factory.mktemp("reload_directory")
apps = ["app", "app_first", "app_second", "app_third"]
root_file = root / "main.py"
root_file.touch()
dotted_file = root / ".dotted"
dotted_file.touch()
dotted_dir = root / ".dotted_dir"
dotted_dir.mkdir()
dotted_dir_file = dotted_dir / "file.txt"
dotted_dir_file.touch()
for app in apps:
app_path = root / app
app_path.mkdir()
dir_files = [
("src", ["main.py"]),
("js", ["main.js"]),
("css", ["main.css"]),
("sub", ["sub.py"]),
]
for directory, files in dir_files:
directory_path = app_path / directory
directory_path.mkdir()
for file in files:
file_path = directory_path / file
file_path.touch()
ext_dir = root / "ext"
ext_dir.mkdir()
ext_file = ext_dir / "ext.jpg"
ext_file.touch()
yield root
@pytest.fixture
def anyio_backend() -> str:
return "asyncio"
@pytest.fixture(scope="function")
def logging_config() -> dict[str, Any]:
return deepcopy(LOGGING_CONFIG)
@pytest.fixture
def short_socket_name(tmp_path, tmp_path_factory): # pragma: py-win32
max_sock_len = 100
socket_filename = "my.sock"
identifier = f"{uuid4()}-"
identifier_len = len(identifier.encode())
tmp_dir = Path("/tmp").resolve()
os_tmp_dir = Path(os.getenv("TMPDIR", "/tmp")).resolve()
basetemp = Path(
str(tmp_path_factory.getbasetemp()),
).resolve()
hash_basetemp = md5(
str(basetemp).encode(),
).hexdigest()
def make_tmp_dir(base_dir):
return TemporaryDirectory(
dir=str(base_dir),
prefix="p-",
suffix=f"-{hash_basetemp}",
)
paths = basetemp, os_tmp_dir, tmp_dir
for _num, tmp_dir_path in enumerate(paths, 1):
with make_tmp_dir(tmp_dir_path) as tmpd:
tmpd = Path(tmpd).resolve()
sock_path = str(tmpd / socket_filename)
sock_path_len = len(sock_path.encode())
if sock_path_len <= max_sock_len:
if max_sock_len - sock_path_len >= identifier_len: # pragma: no cover
sock_path = str(tmpd / "".join((identifier, socket_filename)))
yield sock_path
return
def _unused_port(socket_type: int) -> int:
"""Find an unused localhost port from 1024-65535 and return it."""
with contextlib.closing(socket.socket(type=socket_type)) as sock:
sock.bind(("127.0.0.1", 0))
return sock.getsockname()[1]
# This was copied from pytest-asyncio.
# Ref.: https://github.com/pytest-dev/pytest-asyncio/blob/25d9592286682bc6dbfbf291028ff7a9594cf283/pytest_asyncio/plugin.py#L525-L527 # noqa: E501
@pytest.fixture
def unused_tcp_port() -> int:
return _unused_port(socket.SOCK_STREAM)
@pytest.fixture(
params=[
pytest.param(
"uvicorn.protocols.websockets.wsproto_impl:WSProtocol",
marks=pytest.mark.skipif(not importlib.util.find_spec("wsproto"), reason="wsproto not installed."),
id="wsproto",
),
pytest.param("uvicorn.protocols.websockets.websockets_impl:WebSocketProtocol", id="websockets"),
pytest.param(
"uvicorn.protocols.websockets.websockets_sansio_impl:WebSocketsSansIOProtocol", id="websockets-sansio"
),
]
)
def ws_protocol_cls(request: pytest.FixtureRequest):
return import_from_string(request.param)
@pytest.fixture(
params=[
pytest.param(
"uvicorn.protocols.http.httptools_impl:HttpToolsProtocol",
marks=pytest.mark.skipif(
not importlib.util.find_spec("httptools"),
reason="httptools not installed.",
),
id="httptools",
),
pytest.param("uvicorn.protocols.http.h11_impl:H11Protocol", id="h11"),
]
)
def http_protocol_cls(request: pytest.FixtureRequest):
return import_from_string(request.param)
================================================
FILE: tests/custom_loop_utils.py
================================================
from __future__ import annotations
import asyncio
class CustomLoop(asyncio.SelectorEventLoop):
pass
================================================
FILE: tests/importer/__init__.py
================================================
================================================
FILE: tests/importer/circular_import_a.py
================================================
# Used by test_importer.py
from .circular_import_b import foo # noqa
bar = 123 # pragma: no cover
================================================
FILE: tests/importer/circular_import_b.py
================================================
# Used by test_importer.py
from .circular_import_a import bar # noqa
foo = 123 # pragma: no cover
================================================
FILE: tests/importer/raise_import_error.py
================================================
# Used by test_importer.py
myattr = 123
import does_not_exist # noqa
================================================
FILE: tests/importer/test_importer.py
================================================
import pytest
from uvicorn.importer import ImportFromStringError, import_from_string
def test_invalid_format() -> None:
with pytest.raises(ImportFromStringError) as exc_info:
import_from_string("example:")
expected = 'Import string "example:" must be in format "<module>:<attribute>".'
assert expected in str(exc_info.value)
def test_invalid_module() -> None:
with pytest.raises(ImportFromStringError) as exc_info:
import_from_string("module_does_not_exist:myattr")
expected = 'Could not import module "module_does_not_exist".'
assert expected in str(exc_info.value)
def test_invalid_attr() -> None:
with pytest.raises(ImportFromStringError) as exc_info:
import_from_string("tempfile:attr_does_not_exist")
expected = 'Attribute "attr_does_not_exist" not found in module "tempfile".'
assert expected in str(exc_info.value)
def test_internal_import_error() -> None:
with pytest.raises(ImportError):
import_from_string("tests.importer.raise_import_error:myattr")
def test_valid_import() -> None:
instance = import_from_string("tempfile:TemporaryFile")
from tempfile import TemporaryFile
assert instance == TemporaryFile
def test_no_import_needed() -> None:
from tempfile import TemporaryFile
instance = import_from_string(TemporaryFile)
assert instance == TemporaryFile
def test_circular_import_error() -> None:
with pytest.raises(ImportError) as exc_info:
import_from_string("tests.importer.circular_import_a:bar")
expected = (
"cannot import name 'bar' from partially initialized module "
"'tests.importer.circular_import_a' (most likely due to a circular import)"
)
assert expected in str(exc_info.value)
================================================
FILE: tests/middleware/__init__.py
================================================
================================================
FILE: tests/middleware/test_logging.py
================================================
from __future__ import annotations
import contextlib
import logging
import socket
import sys
from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, TypeAlias
import httpx
import pytest
import websockets.client
from websockets.protocol import State
from tests.utils import run_server
from uvicorn import Config
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
if TYPE_CHECKING:
import sys
from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol
WSProtocol: TypeAlias = "type[WebSocketProtocol | _WSProtocol]"
pytestmark = pytest.mark.anyio
@contextlib.contextmanager
def caplog_for_logger(caplog: pytest.LogCaptureFixture, logger_name: str) -> Iterator[pytest.LogCaptureFixture]:
logger = logging.getLogger(logger_name)
logger.propagate, old_propagate = False, logger.propagate
logger.addHandler(caplog.handler)
try:
yield caplog
finally:
logger.removeHandler(caplog.handler)
logger.propagate = old_propagate
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
assert scope["type"] == "http"
await send({"type": "http.response.start", "status": 204, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
async def test_trace_logging(caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int):
config = Config(
app=app,
log_level="trace",
log_config=logging_config,
lifespan="auto",
port=unused_tcp_port,
)
with caplog_for_logger(caplog, "uvicorn.asgi"):
async with run_server(config):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://127.0.0.1:{unused_tcp_port}")
assert response.status_code == 204
messages = [record.message for record in caplog.records if record.name == "uvicorn.asgi"]
assert "ASGI [1] Started scope=" in messages.pop(0)
assert "ASGI [1] Raised exception" in messages.pop(0)
assert "ASGI [2] Started scope=" in messages.pop(0)
assert "ASGI [2] Send " in messages.pop(0)
assert "ASGI [2] Send " in messages.pop(0)
assert "ASGI [2] Completed" in messages.pop(0)
async def test_trace_logging_on_http_protocol(http_protocol_cls, caplog, logging_config, unused_tcp_port: int):
config = Config(
app=app,
log_level="trace",
http=http_protocol_cls,
log_config=logging_config,
port=unused_tcp_port,
)
with caplog_for_logger(caplog, "uvicorn.error"):
async with run_server(config):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://127.0.0.1:{unused_tcp_port}")
assert response.status_code == 204
messages = [record.message for record in caplog.records if record.name == "uvicorn.error"]
assert any(" - HTTP connection made" in message for message in messages)
assert any(" - HTTP connection lost" in message for message in messages)
async def test_trace_logging_on_ws_protocol(
ws_protocol_cls: WSProtocol,
caplog: pytest.LogCaptureFixture,
logging_config: dict[str, Any],
unused_tcp_port: int,
):
async def websocket_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
assert scope["type"] == "websocket"
while True:
message = await receive()
if message["type"] == "websocket.connect":
await send({"type": "websocket.accept"})
elif message["type"] == "websocket.disconnect":
break
async def open_connection(url: str):
async with websockets.client.connect(url) as websocket:
return websocket.state is State.OPEN
config = Config(
app=websocket_app,
log_level="trace",
log_config=logging_config,
ws=ws_protocol_cls,
port=unused_tcp_port,
)
with caplog_for_logger(caplog, "uvicorn.error"):
async with run_server(config):
is_open = await open_connection(f"ws://127.0.0.1:{unused_tcp_port}")
assert is_open
messages = [record.message for record in caplog.records if record.name == "uvicorn.error"]
assert any(" - Upgrading to WebSocket" in message for message in messages)
assert any(" - WebSocket connection made" in message for message in messages)
assert any(" - WebSocket connection lost" in message for message in messages)
@pytest.mark.parametrize("use_colors", [(True), (False), (None)])
async def test_access_logging(
use_colors: bool, caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int
):
config = Config(app=app, use_colors=use_colors, log_config=logging_config, port=unused_tcp_port)
with caplog_for_logger(caplog, "uvicorn.access"):
async with run_server(config):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://127.0.0.1:{unused_tcp_port}")
assert response.status_code == 204
messages = [record.message for record in caplog.records if record.name == "uvicorn.access"]
assert '"GET / HTTP/1.1" 204' in messages.pop()
@pytest.mark.parametrize("use_colors", [(True), (False)])
async def test_default_logging(
use_colors: bool, caplog: pytest.LogCaptureFixture, logging_config: dict[str, Any], unused_tcp_port: int
):
config = Config(app=app, use_colors=use_colors, log_config=logging_config, port=unused_tcp_port)
with caplog_for_logger(caplog, "uvicorn.access"):
async with run_server(config):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://127.0.0.1:{unused_tcp_port}")
assert response.status_code == 204
messages = [record.message for record in caplog.records if "uvicorn" in record.name]
assert "Started server process" in messages.pop(0)
assert "Waiting for application startup" in messages.pop(0)
assert "ASGI 'lifespan' protocol appears unsupported" in messages.pop(0)
assert "Application startup complete" in messages.pop(0)
assert "Uvicorn running on http://127.0.0.1" in messages.pop(0)
assert '"GET / HTTP/1.1" 204' in messages.pop(0)
assert "Shutting down" in messages.pop(0)
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
async def test_running_log_using_uds(
caplog: pytest.LogCaptureFixture, short_socket_name: str, unused_tcp_port: int
): # pragma: py-win32
config = Config(app=app, uds=short_socket_name, port=unused_tcp_port)
with caplog_for_logger(caplog, "uvicorn.access"):
async with run_server(config):
...
messages = [record.message for record in caplog.records if "uvicorn" in record.name]
assert f"Uvicorn running on unix socket {short_socket_name} (Press CTRL+C to quit)" in messages
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
async def test_running_log_using_fd(caplog: pytest.LogCaptureFixture, unused_tcp_port: int): # pragma: py-win32
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
fd = sock.fileno()
config = Config(app=app, fd=fd, port=unused_tcp_port)
with caplog_for_logger(caplog, "uvicorn.access"):
async with run_server(config):
...
sockname = sock.getsockname()
messages = [record.message for record in caplog.records if "uvicorn" in record.name]
assert f"Uvicorn running on socket {sockname} (Press CTRL+C to quit)" in messages
async def test_unknown_status_code(caplog: pytest.LogCaptureFixture, unused_tcp_port: int):
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable):
assert scope["type"] == "http"
await send({"type": "http.response.start", "status": 599, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
config = Config(app=app, port=unused_tcp_port)
with caplog_for_logger(caplog, "uvicorn.access"):
async with run_server(config):
async with httpx.AsyncClient() as client:
response = await client.get(f"http://127.0.0.1:{unused_tcp_port}")
assert response.status_code == 599
messages = [record.message for record in caplog.records if record.name == "uvicorn.access"]
assert '"GET / HTTP/1.1" 599' in messages.pop()
async def test_server_start_with_port_zero(caplog: pytest.LogCaptureFixture):
config = Config(app=app, port=0)
async with run_server(config) as _server:
server = _server.servers[0]
sock = server.sockets[0]
host, port = sock.getsockname()
messages = [record.message for record in caplog.records if "uvicorn" in record.name]
assert f"Uvicorn running on http://{host}:{port} (Press CTRL+C to quit)" in messages
================================================
FILE: tests/middleware/test_message_logger.py
================================================
import httpx
import pytest
from tests.middleware.test_logging import caplog_for_logger
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
from uvicorn.logging import TRACE_LOG_LEVEL
from uvicorn.middleware.message_logger import MessageLoggerMiddleware
@pytest.mark.anyio
async def test_message_logger(caplog: pytest.LogCaptureFixture) -> None:
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
await receive()
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
with caplog_for_logger(caplog, "uvicorn.asgi"):
caplog.set_level(TRACE_LOG_LEVEL, logger="uvicorn.asgi")
caplog.set_level(TRACE_LOG_LEVEL)
transport = httpx.ASGITransport(MessageLoggerMiddleware(app)) # type: ignore
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.get("/")
assert response.status_code == 200
messages = [record.msg % record.args for record in caplog.records]
assert sum(["ASGI [1] Started" in message for message in messages]) == 1
assert sum(["ASGI [1] Send" in message for message in messages]) == 2
assert sum(["ASGI [1] Receive" in message for message in messages]) == 1
assert sum(["ASGI [1] Completed" in message for message in messages]) == 1
assert sum(["ASGI [1] Raised exception" in message for message in messages]) == 0
@pytest.mark.anyio
async def test_message_logger_exc(caplog: pytest.LogCaptureFixture) -> None:
async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
raise RuntimeError()
with caplog_for_logger(caplog, "uvicorn.asgi"):
caplog.set_level(TRACE_LOG_LEVEL, logger="uvicorn.asgi")
caplog.set_level(TRACE_LOG_LEVEL)
transport = httpx.ASGITransport(MessageLoggerMiddleware(app)) # type: ignore
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
with pytest.raises(RuntimeError):
await client.get("/")
messages = [record.msg % record.args for record in caplog.records]
assert sum(["ASGI [1] Started" in message for message in messages]) == 1
assert sum(["ASGI [1] Send" in message for message in messages]) == 0
assert sum(["ASGI [1] Receive" in message for message in messages]) == 0
assert sum(["ASGI [1] Completed" in message for message in messages]) == 0
assert sum(["ASGI [1] Raised exception" in message for message in messages]) == 1
================================================
FILE: tests/middleware/test_proxy_headers.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
import httpx
import httpx._transports.asgi
import pytest
import websockets.client
from tests.response import Response
from tests.utils import run_server
from uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope
from uvicorn.config import Config
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware, _TrustedHosts
if TYPE_CHECKING:
from uvicorn.protocols.http.h11_impl import H11Protocol
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol
X_FORWARDED_FOR = "X-Forwarded-For"
X_FORWARDED_PROTO = "X-Forwarded-Proto"
async def default_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
scheme = scope["scheme"] # type: ignore
if (client := scope["client"]) is None: # type: ignore
client_addr = "NONE" # pragma: no cover
else:
host, port = client
client_addr = f"{host}:{port}"
response = Response(f"{scheme}://{client_addr}", media_type="text/plain")
await response(scope, receive, send)
def make_httpx_client(
trusted_hosts: str | list[str],
client: tuple[str, int] = ("127.0.0.1", 123),
) -> httpx.AsyncClient:
"""Create async client for use in test cases.
Args:
trusted_hosts: trusted_hosts for proxy middleware
client: transport client to use
"""
app = ProxyHeadersMiddleware(default_app, trusted_hosts)
transport = httpx.ASGITransport(app=app, client=client) # type: ignore
return httpx.AsyncClient(transport=transport, base_url="http://testserver")
# Note: we vary the format here to also test some of the functionality
# of the _TrustedHosts.__init__ method.
_TRUSTED_NOTHING: list[str] = []
_TRUSTED_EVERYTHING = "*"
_TRUSTED_EVERYTHING_LIST = ["*"]
_TRUSTED_IPv4_ADDRESSES = "127.0.0.1, 10.0.0.1"
_TRUSTED_IPv4_NETWORKS = ["127.0.0.0/8", "10.0.0.0/8"]
_TRUSTED_IPv6_ADDRESSES = [
"2001:db8::",
"2001:0db8:0001:0000:0000:0ab9:C0A8:0102",
"2001:db8:3333:4444:5555:6666:1.2.3.4", # This is a dual address
"::11.22.33.44", # This is a dual address
]
_TRUSTED_IPv6_NETWORKS = "2001:db8:abcd:0012::0/64"
_TRUSTED_LITERALS = "some-literal , unix:///foo/bar , /foo/bar, garba*gewith*"
@pytest.mark.parametrize(
("init_hosts", "test_host", "expected"),
[
## Never Trust trust
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_NOTHING, "127.0.0.0", False),
(_TRUSTED_NOTHING, "127.0.0.1", False),
(_TRUSTED_NOTHING, "127.1.1.1", False),
(_TRUSTED_NOTHING, "127.255.255.255", False),
(_TRUSTED_NOTHING, "10.0.0.0", False),
(_TRUSTED_NOTHING, "10.0.0.1", False),
(_TRUSTED_NOTHING, "10.1.1.1", False),
(_TRUSTED_NOTHING, "10.255.255.255", False),
(_TRUSTED_NOTHING, "192.168.0.0", False),
(_TRUSTED_NOTHING, "192.168.0.1", False),
(_TRUSTED_NOTHING, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_NOTHING, "2001:db8::", False),
(_TRUSTED_NOTHING, "2001:db8:abcd:0012::0", False),
(_TRUSTED_NOTHING, "2001:db8:abcd:0012::1:1", False),
(_TRUSTED_NOTHING, "::", False),
(_TRUSTED_NOTHING, "::1", False),
(
_TRUSTED_NOTHING,
"2001:db8:3333:4444:5555:6666:102:304",
False,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_NOTHING, "::b16:212c", False), # aka ::11.22.33.44
(_TRUSTED_NOTHING, "a:b:c:d::", False),
(_TRUSTED_NOTHING, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_NOTHING, "some-literal", False),
(_TRUSTED_NOTHING, "unix:///foo/bar", False),
(_TRUSTED_NOTHING, "/foo/bar", False),
(_TRUSTED_NOTHING, "*", False),
(_TRUSTED_NOTHING, "another-literal", False),
(_TRUSTED_NOTHING, "unix:///another/path", False),
(_TRUSTED_NOTHING, "/another/path", False),
(_TRUSTED_NOTHING, "", False),
## Always trust
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_EVERYTHING, "127.0.0.0", True),
(_TRUSTED_EVERYTHING, "127.0.0.1", True),
(_TRUSTED_EVERYTHING, "127.1.1.1", True),
(_TRUSTED_EVERYTHING, "127.255.255.255", True),
(_TRUSTED_EVERYTHING, "10.0.0.0", True),
(_TRUSTED_EVERYTHING, "10.0.0.1", True),
(_TRUSTED_EVERYTHING, "10.1.1.1", True),
(_TRUSTED_EVERYTHING, "10.255.255.255", True),
(_TRUSTED_EVERYTHING, "192.168.0.0", True),
(_TRUSTED_EVERYTHING, "192.168.0.1", True),
(_TRUSTED_EVERYTHING, "1.1.1.1", True),
(_TRUSTED_EVERYTHING_LIST, "1.1.1.1", True),
# Test IPv6 Addresses
(_TRUSTED_EVERYTHING, "2001:db8::", True),
(_TRUSTED_EVERYTHING, "2001:db8:abcd:0012::0", True),
(_TRUSTED_EVERYTHING, "2001:db8:abcd:0012::1:1", True),
(_TRUSTED_EVERYTHING, "::", True),
(_TRUSTED_EVERYTHING, "::1", True),
(
_TRUSTED_EVERYTHING,
"2001:db8:3333:4444:5555:6666:102:304",
True,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_EVERYTHING, "::b16:212c", True), # aka ::11.22.33.44
(_TRUSTED_EVERYTHING, "a:b:c:d::", True),
(_TRUSTED_EVERYTHING, "::a:b:c:d", True),
(_TRUSTED_EVERYTHING_LIST, "::a:b:c:d", True),
# Test Literals
(_TRUSTED_EVERYTHING, "some-literal", True),
(_TRUSTED_EVERYTHING, "unix:///foo/bar", True),
(_TRUSTED_EVERYTHING, "/foo/bar", True),
(_TRUSTED_EVERYTHING, "*", True),
(_TRUSTED_EVERYTHING, "another-literal", True),
(_TRUSTED_EVERYTHING, "unix:///another/path", True),
(_TRUSTED_EVERYTHING, "/another/path", True),
(_TRUSTED_EVERYTHING, "", True),
(_TRUSTED_EVERYTHING_LIST, "", True),
## Trust IPv4 Addresses
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_IPv4_ADDRESSES, "127.0.0.0", False),
(_TRUSTED_IPv4_ADDRESSES, "127.0.0.1", True),
(_TRUSTED_IPv4_ADDRESSES, "127.1.1.1", False),
(_TRUSTED_IPv4_ADDRESSES, "127.255.255.255", False),
(_TRUSTED_IPv4_ADDRESSES, "10.0.0.0", False),
(_TRUSTED_IPv4_ADDRESSES, "10.0.0.1", True),
(_TRUSTED_IPv4_ADDRESSES, "10.1.1.1", False),
(_TRUSTED_IPv4_ADDRESSES, "10.255.255.255", False),
(_TRUSTED_IPv4_ADDRESSES, "192.168.0.0", False),
(_TRUSTED_IPv4_ADDRESSES, "192.168.0.1", False),
(_TRUSTED_IPv4_ADDRESSES, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_IPv4_ADDRESSES, "2001:db8::", False),
(_TRUSTED_IPv4_ADDRESSES, "2001:db8:abcd:0012::0", False),
(_TRUSTED_IPv4_ADDRESSES, "2001:db8:abcd:0012::1:1", False),
(_TRUSTED_IPv4_ADDRESSES, "::", False),
(_TRUSTED_IPv4_ADDRESSES, "::1", False),
(
_TRUSTED_IPv4_ADDRESSES,
"2001:db8:3333:4444:5555:6666:102:304",
False,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_IPv4_ADDRESSES, "::b16:212c", False), # aka ::11.22.33.44
(_TRUSTED_IPv4_ADDRESSES, "a:b:c:d::", False),
(_TRUSTED_IPv4_ADDRESSES, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_IPv4_ADDRESSES, "some-literal", False),
(_TRUSTED_IPv4_ADDRESSES, "unix:///foo/bar", False),
(_TRUSTED_IPv4_ADDRESSES, "*", False),
(_TRUSTED_IPv4_ADDRESSES, "/foo/bar", False),
(_TRUSTED_IPv4_ADDRESSES, "another-literal", False),
(_TRUSTED_IPv4_ADDRESSES, "unix:///another/path", False),
(_TRUSTED_IPv4_ADDRESSES, "/another/path", False),
(_TRUSTED_IPv4_ADDRESSES, "", False),
## Trust IPv6 Addresses
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_IPv6_ADDRESSES, "127.0.0.0", False),
(_TRUSTED_IPv6_ADDRESSES, "127.0.0.1", False),
(_TRUSTED_IPv6_ADDRESSES, "127.1.1.1", False),
(_TRUSTED_IPv6_ADDRESSES, "127.255.255.255", False),
(_TRUSTED_IPv6_ADDRESSES, "10.0.0.0", False),
(_TRUSTED_IPv6_ADDRESSES, "10.0.0.1", False),
(_TRUSTED_IPv6_ADDRESSES, "10.1.1.1", False),
(_TRUSTED_IPv6_ADDRESSES, "10.255.255.255", False),
(_TRUSTED_IPv6_ADDRESSES, "192.168.0.0", False),
(_TRUSTED_IPv6_ADDRESSES, "192.168.0.1", False),
(_TRUSTED_IPv6_ADDRESSES, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_IPv6_ADDRESSES, "2001:db8::", True),
(_TRUSTED_IPv6_ADDRESSES, "2001:db8:abcd:0012::0", False),
(_TRUSTED_IPv6_ADDRESSES, "2001:db8:abcd:0012::1:1", False),
(_TRUSTED_IPv6_ADDRESSES, "::", False),
(_TRUSTED_IPv6_ADDRESSES, "::1", False),
(
_TRUSTED_IPv6_ADDRESSES,
"2001:db8:3333:4444:5555:6666:102:304",
True,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_IPv6_ADDRESSES, "::b16:212c", True), # aka ::11.22.33.44
(_TRUSTED_IPv6_ADDRESSES, "a:b:c:d::", False),
(_TRUSTED_IPv6_ADDRESSES, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_IPv6_ADDRESSES, "some-literal", False),
(_TRUSTED_IPv6_ADDRESSES, "unix:///foo/bar", False),
(_TRUSTED_IPv6_ADDRESSES, "*", False),
(_TRUSTED_IPv6_ADDRESSES, "/foo/bar", False),
(_TRUSTED_IPv6_ADDRESSES, "another-literal", False),
(_TRUSTED_IPv6_ADDRESSES, "unix:///another/path", False),
(_TRUSTED_IPv6_ADDRESSES, "/another/path", False),
(_TRUSTED_IPv6_ADDRESSES, "", False),
## Trust IPv4 Networks
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_IPv4_NETWORKS, "127.0.0.0", True),
(_TRUSTED_IPv4_NETWORKS, "127.0.0.1", True),
(_TRUSTED_IPv4_NETWORKS, "127.1.1.1", True),
(_TRUSTED_IPv4_NETWORKS, "127.255.255.255", True),
(_TRUSTED_IPv4_NETWORKS, "10.0.0.0", True),
(_TRUSTED_IPv4_NETWORKS, "10.0.0.1", True),
(_TRUSTED_IPv4_NETWORKS, "10.1.1.1", True),
(_TRUSTED_IPv4_NETWORKS, "10.255.255.255", True),
(_TRUSTED_IPv4_NETWORKS, "192.168.0.0", False),
(_TRUSTED_IPv4_NETWORKS, "192.168.0.1", False),
(_TRUSTED_IPv4_NETWORKS, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_IPv4_NETWORKS, "2001:db8::", False),
(_TRUSTED_IPv4_NETWORKS, "2001:db8:abcd:0012::0", False),
(_TRUSTED_IPv4_NETWORKS, "2001:db8:abcd:0012::1:1", False),
(_TRUSTED_IPv4_NETWORKS, "::", False),
(_TRUSTED_IPv4_NETWORKS, "::1", False),
(
_TRUSTED_IPv4_NETWORKS,
"2001:db8:3333:4444:5555:6666:102:304",
False,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_IPv4_NETWORKS, "::b16:212c", False), # aka ::11.22.33.44
(_TRUSTED_IPv4_NETWORKS, "a:b:c:d::", False),
(_TRUSTED_IPv4_NETWORKS, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_IPv4_NETWORKS, "some-literal", False),
(_TRUSTED_IPv4_NETWORKS, "unix:///foo/bar", False),
(_TRUSTED_IPv4_NETWORKS, "*", False),
(_TRUSTED_IPv4_NETWORKS, "/foo/bar", False),
(_TRUSTED_IPv4_NETWORKS, "another-literal", False),
(_TRUSTED_IPv4_NETWORKS, "unix:///another/path", False),
(_TRUSTED_IPv4_NETWORKS, "/another/path", False),
(_TRUSTED_IPv4_NETWORKS, "", False),
## Trust IPv6 Networks
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_IPv6_NETWORKS, "127.0.0.0", False),
(_TRUSTED_IPv6_NETWORKS, "127.0.0.1", False),
(_TRUSTED_IPv6_NETWORKS, "127.1.1.1", False),
(_TRUSTED_IPv6_NETWORKS, "127.255.255.255", False),
(_TRUSTED_IPv6_NETWORKS, "10.0.0.0", False),
(_TRUSTED_IPv6_NETWORKS, "10.0.0.1", False),
(_TRUSTED_IPv6_NETWORKS, "10.1.1.1", False),
(_TRUSTED_IPv6_NETWORKS, "10.255.255.255", False),
(_TRUSTED_IPv6_NETWORKS, "192.168.0.0", False),
(_TRUSTED_IPv6_NETWORKS, "192.168.0.1", False),
(_TRUSTED_IPv6_NETWORKS, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_IPv6_NETWORKS, "2001:db8::", False),
(_TRUSTED_IPv6_NETWORKS, "2001:db8:abcd:0012::0", True),
(_TRUSTED_IPv6_NETWORKS, "2001:db8:abcd:0012::1:1", True),
(_TRUSTED_IPv6_NETWORKS, "::", False),
(_TRUSTED_IPv6_NETWORKS, "::1", False),
(
_TRUSTED_IPv6_NETWORKS,
"2001:db8:3333:4444:5555:6666:102:304",
False,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_IPv6_NETWORKS, "::b16:212c", False), # aka ::11.22.33.44
(_TRUSTED_IPv6_NETWORKS, "a:b:c:d::", False),
(_TRUSTED_IPv6_NETWORKS, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_IPv6_NETWORKS, "some-literal", False),
(_TRUSTED_IPv6_NETWORKS, "unix:///foo/bar", False),
(_TRUSTED_IPv6_NETWORKS, "*", False),
(_TRUSTED_IPv6_NETWORKS, "/foo/bar", False),
(_TRUSTED_IPv6_NETWORKS, "another-literal", False),
(_TRUSTED_IPv6_NETWORKS, "unix:///another/path", False),
(_TRUSTED_IPv6_NETWORKS, "/another/path", False),
(_TRUSTED_IPv6_NETWORKS, "", False),
## Trust Literals
## -----------------------------
# Test IPv4 Addresses
(_TRUSTED_LITERALS, "127.0.0.0", False),
(_TRUSTED_LITERALS, "127.0.0.1", False),
(_TRUSTED_LITERALS, "127.1.1.1", False),
(_TRUSTED_LITERALS, "127.255.255.255", False),
(_TRUSTED_LITERALS, "10.0.0.0", False),
(_TRUSTED_LITERALS, "10.0.0.1", False),
(_TRUSTED_LITERALS, "10.1.1.1", False),
(_TRUSTED_LITERALS, "10.255.255.255", False),
(_TRUSTED_LITERALS, "192.168.0.0", False),
(_TRUSTED_LITERALS, "192.168.0.1", False),
(_TRUSTED_LITERALS, "1.1.1.1", False),
# Test IPv6 Addresses
(_TRUSTED_LITERALS, "2001:db8::", False),
(_TRUSTED_LITERALS, "2001:db8:abcd:0012::0", False),
(_TRUSTED_LITERALS, "2001:db8:abcd:0012::1:1", False),
(_TRUSTED_LITERALS, "::", False),
(_TRUSTED_LITERALS, "::1", False),
(
_TRUSTED_LITERALS,
"2001:db8:3333:4444:5555:6666:102:304",
False,
), # aka 2001:db8:3333:4444:5555:6666:1.2.3.4
(_TRUSTED_LITERALS, "::b16:212c", False), # aka ::11.22.33.44
(_TRUSTED_LITERALS, "a:b:c:d::", False),
(_TRUSTED_LITERALS, "::a:b:c:d", False),
# Test Literals
(_TRUSTED_LITERALS, "some-literal", True),
(_TRUSTED_LITERALS, "unix:///foo/bar", True),
(_TRUSTED_LITERALS, "*", False),
(_TRUSTED_LITERALS, "/foo/bar", True),
(_TRUSTED_LITERALS, "another-literal", False),
(_TRUSTED_LITERALS, "unix:///another/path", False),
(_TRUSTED_LITERALS, "/another/path", False),
(_TRUSTED_LITERALS, "", False),
],
)
def test_forwarded_hosts(init_hosts: str | list[str], test_host: str, expected: bool) -> None:
trusted_hosts = _TrustedHosts(init_hosts)
assert (test_host in trusted_hosts) is expected
@pytest.mark.anyio
@pytest.mark.parametrize(
("trusted_hosts", "expected"),
[
# always trust
("*", "https://1.2.3.4:0"),
# trusted proxy
("127.0.0.1", "https://1.2.3.4:0"),
(["127.0.0.1"], "https://1.2.3.4:0"),
# trusted proxy list
(["127.0.0.1", "10.0.0.1"], "https://1.2.3.4:0"),
("127.0.0.1, 10.0.0.1", "https://1.2.3.4:0"),
# trusted proxy network
# https://github.com/Kludex/uvicorn/issues/1068#issuecomment-1004813267
("127.0.0.0/24, 10.0.0.1", "https://1.2.3.4:0"),
# request from untrusted proxy
("192.168.0.1", "http://127.0.0.1:123"),
# request from untrusted proxy network
("192.168.0.0/16", "http://127.0.0.1:123"),
# request from client running on proxy server itself
# https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576
(["127.0.0.1", "1.2.3.4"], "https://1.2.3.4:0"),
],
)
async def test_proxy_headers_trusted_hosts(trusted_hosts: str | list[str], expected: str) -> None:
async with make_httpx_client(trusted_hosts) as client:
headers = {X_FORWARDED_FOR: "1.2.3.4", X_FORWARDED_PROTO: "https"}
response = await client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == expected
@pytest.mark.anyio
@pytest.mark.parametrize(
("forwarded_for", "forwarded_proto", "expected"),
[
("", "", "http://127.0.0.1:123"),
("", None, "http://127.0.0.1:123"),
("", "asdf", "http://127.0.0.1:123"),
(" , ", "https", "https://127.0.0.1:123"),
(", , ", "https", "https://127.0.0.1:123"),
(" , 10.0.0.1", "https", "https://127.0.0.1:123"),
("9.9.9.9 , , , 10.0.0.1", "https", "https://127.0.0.1:123"),
(", , 9.9.9.9", "https", "https://9.9.9.9:0"),
(", , 9.9.9.9, , ", "https", "https://127.0.0.1:123"),
],
)
async def test_proxy_headers_trusted_hosts_malformed(
forwarded_for: str,
forwarded_proto: str | None,
expected: str,
) -> None:
async with make_httpx_client("127.0.0.1, 10.0.0.0/8") as client:
headers = {X_FORWARDED_FOR: forwarded_for}
if forwarded_proto is not None:
headers[X_FORWARDED_PROTO] = forwarded_proto
response = await client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == expected
@pytest.mark.anyio
@pytest.mark.parametrize(
("trusted_hosts", "expected"),
[
# always trust
("*", "https://1.2.3.4:0"),
# all proxies are trusted
(["127.0.0.1", "10.0.2.1", "192.168.0.2"], "https://1.2.3.4:0"),
# order doesn't matter
(["10.0.2.1", "192.168.0.2", "127.0.0.1"], "https://1.2.3.4:0"),
# should set first untrusted as remote address
(["192.168.0.2", "127.0.0.1"], "https://10.0.2.1:0"),
# Mixed literals and networks
(["127.0.0.1", "10.0.0.0/8", "192.168.0.2"], "https://1.2.3.4:0"),
],
)
async def test_proxy_headers_multiple_proxies(trusted_hosts: str | list[str], expected: str) -> None:
async with make_httpx_client(trusted_hosts) as client:
headers = {X_FORWARDED_FOR: "1.2.3.4, 10.0.2.1, 192.168.0.2", X_FORWARDED_PROTO: "https"}
response = await client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == expected
@pytest.mark.anyio
async def test_proxy_headers_invalid_x_forwarded_for() -> None:
async with make_httpx_client("*") as client:
headers = httpx.Headers(
{
X_FORWARDED_FOR: "1.2.3.4, \xf0\xfd\xfd\xfd, unix:, ::1",
X_FORWARDED_PROTO: "https",
},
encoding="latin-1",
)
response = await client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == "https://1.2.3.4:0"
@pytest.mark.anyio
@pytest.mark.parametrize(
"forwarded_proto,expected",
[
("http", "ws://1.2.3.4:0"),
("https", "wss://1.2.3.4:0"),
("ws", "ws://1.2.3.4:0"),
("wss", "wss://1.2.3.4:0"),
],
)
async def test_proxy_headers_websocket_x_forwarded_proto(
forwarded_proto: str,
expected: str,
ws_protocol_cls: type[WSProtocol | WebSocketProtocol],
http_protocol_cls: type[H11Protocol | HttpToolsProtocol],
unused_tcp_port: int,
) -> None:
async def websocket_app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
assert scope["type"] == "websocket"
scheme = scope["scheme"]
assert scope["client"] is not None
host, port = scope["client"]
await send({"type": "websocket.accept"})
await send({"type": "websocket.send", "text": f"{scheme}://{host}:{port}"})
await send({"type": "websocket.close"})
app_with_middleware = ProxyHeadersMiddleware(websocket_app, trusted_hosts="*")
config = Config(
app=app_with_middleware,
ws=ws_protocol_cls,
http=http_protocol_cls,
lifespan="off",
port=unused_tcp_port,
)
async with run_server(config):
url = f"ws://127.0.0.1:{unused_tcp_port}"
headers = {X_FORWARDED_FOR: "1.2.3.4", X_FORWARDED_PROTO: forwarded_proto}
async with websockets.client.connect(url, extra_headers=headers) as websocket:
data = await websocket.recv()
assert data == expected
@pytest.mark.anyio
async def test_proxy_headers_empty_x_forwarded_for() -> None:
# fallback to the default behavior if x-forwarded-for is an empty list
# https://github.com/Kludex/uvicorn/issues/1068#issuecomment-855371576
async with make_httpx_client("*") as client:
headers = {X_FORWARDED_FOR: "", X_FORWARDED_PROTO: "https"}
response = await client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == "https://127.0.0.1:123"
================================================
FILE: tests/middleware/test_wsgi.py
================================================
from __future__ import annotations
import io
import sys
from collections.abc import AsyncGenerator, Callable
import a2wsgi
import httpx
import pytest
from uvicorn._types import Environ, HTTPRequestEvent, HTTPScope, StartResponse
from uvicorn.middleware import wsgi
def hello_world(environ: Environ, start_response: StartResponse) -> list[bytes]:
status = "200 OK"
output = b"Hello World!\n"
headers = [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(output))),
]
start_response(status, headers, None)
return [output]
def echo_body(environ: Environ, start_response: StartResponse) -> list[bytes]:
status = "200 OK"
output = environ["wsgi.input"].read()
headers = [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(output))),
]
start_response(status, headers, None)
return [output]
def raise_exception(environ: Environ, start_response: StartResponse) -> list[bytes]:
raise RuntimeError("Something went wrong")
def return_exc_info(environ: Environ, start_response: StartResponse) -> list[bytes]:
try:
raise RuntimeError("Something went wrong")
except RuntimeError:
status = "500 Internal Server Error"
output = b"Internal Server Error"
headers = [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(output))),
]
start_response(status, headers, sys.exc_info()) # type: ignore[arg-type]
return [output]
@pytest.fixture(params=[wsgi._WSGIMiddleware, a2wsgi.WSGIMiddleware])
def wsgi_middleware(request: pytest.FixtureRequest) -> Callable:
return request.param
@pytest.mark.anyio
async def test_wsgi_get(wsgi_middleware: Callable) -> None:
transport = httpx.ASGITransport(wsgi_middleware(hello_world))
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.get("/")
assert response.status_code == 200
assert response.text == "Hello World!\n"
@pytest.mark.anyio
async def test_wsgi_post(wsgi_middleware: Callable) -> None:
transport = httpx.ASGITransport(wsgi_middleware(echo_body))
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.post("/", json={"example": 123})
assert response.status_code == 200
assert response.text == '{"example":123}'
@pytest.mark.anyio
async def test_wsgi_put_more_body(wsgi_middleware: Callable) -> None:
async def generate_body() -> AsyncGenerator[bytes, None]:
for _ in range(1024):
yield b"123456789abcdef\n" * 64
transport = httpx.ASGITransport(wsgi_middleware(echo_body))
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.put("/", content=generate_body())
assert response.status_code == 200
assert response.text == "123456789abcdef\n" * 64 * 1024
@pytest.mark.anyio
async def test_wsgi_exception(wsgi_middleware: Callable) -> None:
# Note that we're testing the WSGI app directly here.
# The HTTP protocol implementations would catch this error and return 500.
transport = httpx.ASGITransport(wsgi_middleware(raise_exception))
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
with pytest.raises(RuntimeError):
await client.get("/")
@pytest.mark.anyio
async def test_wsgi_exc_info(wsgi_middleware: Callable) -> None:
app = wsgi_middleware(return_exc_info)
transport = httpx.ASGITransport(
app=app,
raise_app_exceptions=False,
)
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.get("/")
assert response.status_code == 500
assert response.text == "Internal Server Error"
def test_build_environ_encoding() -> None:
scope: HTTPScope = {
"asgi": {"version": "3.0", "spec_version": "2.0"},
"scheme": "http",
"raw_path": b"/\xe6\x96\x87%2Fall",
"type": "http",
"http_version": "1.1",
"method": "GET",
"path": "/文/all",
"root_path": "/文",
"client": None,
"server": None,
"query_string": b"a=123&b=456",
"headers": [(b"key", b"value1"), (b"key", b"value2")],
"extensions": {},
}
message: HTTPRequestEvent = {
"type": "http.request",
"body": b"",
"more_body": False,
}
environ = wsgi.build_environ(scope, message, io.BytesIO(b""))
assert environ["SCRIPT_NAME"] == "/文".encode().decode("latin-1")
assert environ["PATH_INFO"] == b"/all".decode("latin-1")
assert environ["HTTP_KEY"] == "value1,value2"
================================================
FILE: tests/protocols/__init__.py
================================================
================================================
FILE: tests/protocols/test_http.py
================================================
from __future__ import annotations
import asyncio
import logging
import socket
import threading
import time
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, TypeAlias
import pytest
from tests.response import Response
from uvicorn import Server
from uvicorn._types import ASGIApplication, ASGIReceiveCallable, ASGISendCallable, Scope
from uvicorn.config import WS_PROTOCOLS, Config
from uvicorn.lifespan.off import LifespanOff
from uvicorn.lifespan.on import LifespanOn
from uvicorn.protocols.http.h11_impl import H11Protocol
from uvicorn.server import ServerState
try:
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
skip_if_no_httptools = pytest.mark.skipif(False, reason="httptools is installed")
except ModuleNotFoundError: # pragma: no cover
skip_if_no_httptools = pytest.mark.skipif(True, reason="httptools is not installed")
if TYPE_CHECKING:
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
from uvicorn.protocols.websockets.websockets_impl import WebSocketProtocol
from uvicorn.protocols.websockets.wsproto_impl import WSProtocol as _WSProtocol
WSProtocol: TypeAlias = WebSocketProtocol | _WSProtocol
HTTPProtocol: TypeAlias = H11Protocol | HttpToolsProtocol
pytestmark = pytest.mark.anyio
WEBSOCKET_PROTOCOLS = WS_PROTOCOLS.keys()
SIMPLE_GET_REQUEST = b"\r\n".join([b"GET / HTTP/1.1", b"Host: example.org", b"", b""])
SIMPLE_HEAD_REQUEST = b"\r\n".join([b"HEAD / HTTP/1.1", b"Host: example.org", b"", b""])
SIMPLE_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: application/json",
b"Content-Length: 18",
b"",
b'{"hello": "world"}',
]
)
CONNECTION_CLOSE_REQUEST = b"\r\n".join([b"GET / HTTP/1.1", b"Host: example.org", b"Connection: close", b"", b""])
CONNECTION_CLOSE_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Connection: close",
b"Content-Type: application/json",
b"Content-Length: 18",
b"",
b"{'hello': 'world'}",
]
)
REQUEST_AFTER_CONNECTION_CLOSE = b"\r\n".join(
[
b"GET / HTTP/1.1",
b"Host: example.org",
b"Connection: close",
b"",
b"",
b"GET / HTTP/1.1",
b"Host: example.org",
b"",
b"",
]
)
LARGE_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: text/plain",
b"Content-Length: 100000",
b"",
b"x" * 100000,
]
)
START_POST_REQUEST = b"\r\n".join(
[
b"POST / HTTP/1.1",
b"Host: example.org",
b"Content-Type: application/json",
b"Content-Length: 18",
b"",
b"",
]
)
FINISH_POST_REQUEST = b'{"hell
gitextract_nb8gwzdb/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── 1-issue.yml
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── benchmark.yml
│ ├── main.yml
│ └── publish.yml
├── .gitignore
├── CITATION.cff
├── LICENSE.md
├── README.md
├── docs/
│ ├── CNAME
│ ├── concepts/
│ │ ├── asgi.md
│ │ ├── event-loop.md
│ │ ├── lifespan.md
│ │ └── websockets.md
│ ├── contributing.md
│ ├── deployment/
│ │ ├── docker.md
│ │ └── index.md
│ ├── index.md
│ ├── installation.md
│ ├── overrides/
│ │ ├── main.html
│ │ └── partials/
│ │ ├── nav.html
│ │ └── toc-item.html
│ ├── plugins/
│ │ └── main.py
│ ├── release-notes.md
│ ├── server-behavior.md
│ ├── settings.md
│ └── sponsorship.md
├── mkdocs.yml
├── pyproject.toml
├── scripts/
│ ├── build
│ ├── check
│ ├── coverage
│ ├── docs
│ ├── install
│ ├── lint
│ ├── sync-version
│ └── test
├── tests/
│ ├── __init__.py
│ ├── benchmarks/
│ │ ├── __init__.py
│ │ ├── http.py
│ │ ├── test_http.py
│ │ ├── test_ws.py
│ │ └── ws.py
│ ├── conftest.py
│ ├── custom_loop_utils.py
│ ├── importer/
│ │ ├── __init__.py
│ │ ├── circular_import_a.py
│ │ ├── circular_import_b.py
│ │ ├── raise_import_error.py
│ │ └── test_importer.py
│ ├── middleware/
│ │ ├── __init__.py
│ │ ├── test_logging.py
│ │ ├── test_message_logger.py
│ │ ├── test_proxy_headers.py
│ │ └── test_wsgi.py
│ ├── protocols/
│ │ ├── __init__.py
│ │ ├── test_http.py
│ │ ├── test_utils.py
│ │ └── test_websocket.py
│ ├── response.py
│ ├── supervisors/
│ │ ├── __init__.py
│ │ ├── test_multiprocess.py
│ │ ├── test_reload.py
│ │ └── test_signal.py
│ ├── test_auto_detection.py
│ ├── test_cli.py
│ ├── test_compat.py
│ ├── test_config.py
│ ├── test_default_headers.py
│ ├── test_lifespan.py
│ ├── test_main.py
│ ├── test_server.py
│ ├── test_ssl.py
│ ├── test_subprocess.py
│ └── utils.py
└── uvicorn/
├── __init__.py
├── __main__.py
├── _compat.py
├── _subprocess.py
├── _types.py
├── config.py
├── importer.py
├── lifespan/
│ ├── __init__.py
│ ├── off.py
│ └── on.py
├── logging.py
├── loops/
│ ├── __init__.py
│ ├── asyncio.py
│ ├── auto.py
│ └── uvloop.py
├── main.py
├── middleware/
│ ├── __init__.py
│ ├── asgi2.py
│ ├── message_logger.py
│ ├── proxy_headers.py
│ └── wsgi.py
├── protocols/
│ ├── __init__.py
│ ├── http/
│ │ ├── __init__.py
│ │ ├── auto.py
│ │ ├── flow_control.py
│ │ ├── h11_impl.py
│ │ └── httptools_impl.py
│ ├── utils.py
│ └── websockets/
│ ├── __init__.py
│ ├── auto.py
│ ├── websockets_impl.py
│ ├── websockets_sansio_impl.py
│ └── wsproto_impl.py
├── py.typed
├── server.py
├── supervisors/
│ ├── __init__.py
│ ├── basereload.py
│ ├── multiprocess.py
│ ├── statreload.py
│ └── watchfilesreload.py
└── workers.py
SYMBOL INDEX (719 symbols across 59 files)
FILE: docs/plugins/main.py
function on_page_content (line 12) | def on_page_content(html: str, page: Page, config: Config, files: Files)...
function add_hyperlink_to_pull_request (line 18) | def add_hyperlink_to_pull_request(html: str, page: Page, config: Config,...
function on_page_markdown (line 29) | def on_page_markdown(markdown: str, page: Page, config: Config, files: F...
function uvicorn_print_help (line 35) | def uvicorn_print_help(markdown: str, page: Page) -> str:
function get_uvicorn_help (line 40) | def get_uvicorn_help():
FILE: tests/benchmarks/http.py
class MockTransport (line 78) | class MockTransport:
method __init__ (line 79) | def __init__(self) -> None:
method get_extra_info (line 84) | def get_extra_info(self, key: Any) -> Any:
method write (line 91) | def write(self, data: bytes) -> None:
method close (line 94) | def close(self) -> None:
method pause_reading (line 97) | def pause_reading(self) -> None:
method resume_reading (line 100) | def resume_reading(self) -> None:
method is_closing (line 103) | def is_closing(self) -> bool:
method clear_buffer (line 106) | def clear_buffer(self) -> None:
method set_protocol (line 109) | def set_protocol(self, protocol: asyncio.Protocol) -> None:
class MockTimerHandle (line 113) | class MockTimerHandle:
method __init__ (line 114) | def __init__(
method cancel (line 123) | def cancel(self) -> None:
class MockLoop (line 129) | class MockLoop:
method __init__ (line 130) | def __init__(self) -> None:
method create_task (line 134) | def create_task(self, coroutine: Any) -> Any:
method call_later (line 138) | def call_later(self, delay: float, callback: Callable[[], None], *args...
method run_one (line 143) | async def run_one(self) -> Any:
class MockTask (line 147) | class MockTask:
method add_done_callback (line 148) | def add_done_callback(self, callback: Callable[[], None]) -> None:
class MockProtocol (line 152) | class MockProtocol(asyncio.Protocol):
function make_config (line 160) | def make_config(app: ASGIApplication, **kwargs: Any) -> Config:
function get_connected_protocol (line 164) | def get_connected_protocol(
FILE: tests/benchmarks/test_http.py
function _body_echo_app (line 36) | async def _body_echo_app(scope: Scope, receive: ASGIReceiveCallable, sen...
function test_bench_simple_get (line 51) | async def test_bench_simple_get(http_protocol_cls: type[HTTPProtocol]) -...
function test_bench_simple_post (line 57) | async def test_bench_simple_post(http_protocol_cls: type[HTTPProtocol]) ...
function test_bench_large_post (line 63) | async def test_bench_large_post(http_protocol_cls: type[HTTPProtocol]) -...
function test_bench_pipelined_requests (line 69) | async def test_bench_pipelined_requests(http_protocol_cls: type[HTTPProt...
function test_bench_keepalive_reuse (line 77) | async def test_bench_keepalive_reuse(http_protocol_cls: type[HTTPProtoco...
function test_bench_chunked_response (line 85) | async def test_bench_chunked_response(http_protocol_cls: type[HTTPProtoc...
function test_bench_http10 (line 91) | async def test_bench_http10(http_protocol_cls: type[HTTPProtocol]) -> None:
function test_bench_connection_close (line 97) | async def test_bench_connection_close(http_protocol_cls: type[HTTPProtoc...
function test_bench_fragmented_body (line 103) | async def test_bench_fragmented_body(http_protocol_cls: type[HTTPProtoco...
function test_bench_post_body_receive (line 111) | async def test_bench_post_body_receive(http_protocol_cls: type[HTTPProto...
FILE: tests/benchmarks/test_ws.py
function ws_cls (line 28) | def ws_cls(request: pytest.FixtureRequest) -> WSProtocolClass:
function _ws_accept_close_app (line 38) | async def _ws_accept_close_app(scope: Scope, receive: ASGIReceiveCallabl...
function _ws_send_text_app (line 44) | async def _ws_send_text_app(scope: Scope, receive: ASGIReceiveCallable, ...
function test_bench_ws_handshake (line 55) | async def test_bench_ws_handshake(ws_cls: WSProtocolClass) -> None:
function test_bench_ws_send_text (line 61) | async def test_bench_ws_send_text(ws_cls: WSProtocolClass) -> None:
FILE: tests/benchmarks/ws.py
function get_connected_ws_protocol (line 33) | def get_connected_ws_protocol(config: Config, ws_protocol_cls: WSProtoco...
FILE: tests/conftest.py
function tls_certificate_authority (line 42) | def tls_certificate_authority() -> trustme.CA:
function tls_certificate (line 49) | def tls_certificate(tls_certificate_authority: trustme.CA) -> trustme.Le...
function tls_ca_certificate_pem_path (line 58) | def tls_ca_certificate_pem_path(tls_certificate_authority: trustme.CA):
function tls_ca_certificate_private_key_path (line 64) | def tls_ca_certificate_private_key_path(tls_certificate_authority: trust...
function tls_certificate_private_key_encrypted_path (line 70) | def tls_certificate_private_key_encrypted_path(tls_certificate):
function tls_certificate_private_key_path (line 86) | def tls_certificate_private_key_path(tls_certificate: trustme.CA):
function tls_certificate_key_and_chain_path (line 92) | def tls_certificate_key_and_chain_path(tls_certificate: trustme.LeafCert):
function tls_certificate_server_cert_path (line 98) | def tls_certificate_server_cert_path(tls_certificate: trustme.LeafCert):
function tls_ca_ssl_context (line 104) | def tls_ca_ssl_context(tls_certificate_authority: trustme.CA) -> ssl.SSL...
function reload_directory_structure (line 111) | def reload_directory_structure(tmp_path_factory: pytest.TempPathFactory):
function anyio_backend (line 171) | def anyio_backend() -> str:
function logging_config (line 176) | def logging_config() -> dict[str, Any]:
function short_socket_name (line 181) | def short_socket_name(tmp_path, tmp_path_factory): # pragma: py-win32
function _unused_port (line 215) | def _unused_port(socket_type: int) -> int:
function unused_tcp_port (line 225) | def unused_tcp_port() -> int:
function ws_protocol_cls (line 242) | def ws_protocol_cls(request: pytest.FixtureRequest):
function http_protocol_cls (line 259) | def http_protocol_cls(request: pytest.FixtureRequest):
FILE: tests/custom_loop_utils.py
class CustomLoop (line 6) | class CustomLoop(asyncio.SelectorEventLoop):
FILE: tests/importer/test_importer.py
function test_invalid_format (line 6) | def test_invalid_format() -> None:
function test_invalid_module (line 13) | def test_invalid_module() -> None:
function test_invalid_attr (line 20) | def test_invalid_attr() -> None:
function test_internal_import_error (line 27) | def test_internal_import_error() -> None:
function test_valid_import (line 32) | def test_valid_import() -> None:
function test_no_import_needed (line 39) | def test_no_import_needed() -> None:
function test_circular_import_error (line 46) | def test_circular_import_error() -> None:
FILE: tests/middleware/test_logging.py
function caplog_for_logger (line 31) | def caplog_for_logger(caplog: pytest.LogCaptureFixture, logger_name: str...
function app (line 42) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function test_trace_logging (line 48) | async def test_trace_logging(caplog: pytest.LogCaptureFixture, logging_c...
function test_trace_logging_on_http_protocol (line 70) | async def test_trace_logging_on_http_protocol(http_protocol_cls, caplog,...
function test_trace_logging_on_ws_protocol (line 88) | async def test_trace_logging_on_ws_protocol(
function test_access_logging (line 125) | async def test_access_logging(
function test_default_logging (line 140) | async def test_default_logging(
function test_running_log_using_uds (line 160) | async def test_running_log_using_uds(
function test_running_log_using_fd (line 173) | async def test_running_log_using_fd(caplog: pytest.LogCaptureFixture, un...
function test_unknown_status_code (line 185) | async def test_unknown_status_code(caplog: pytest.LogCaptureFixture, unu...
function test_server_start_with_port_zero (line 202) | async def test_server_start_with_port_zero(caplog: pytest.LogCaptureFixt...
FILE: tests/middleware/test_message_logger.py
function test_message_logger (line 11) | async def test_message_logger(caplog: pytest.LogCaptureFixture) -> None:
function test_message_logger_exc (line 34) | async def test_message_logger_exc(caplog: pytest.LogCaptureFixture) -> N...
FILE: tests/middleware/test_proxy_headers.py
function default_app (line 27) | async def default_app(scope: Scope, receive: ASGIReceiveCallable, send: ...
function make_httpx_client (line 39) | def make_httpx_client(
function test_forwarded_hosts (line 339) | def test_forwarded_hosts(init_hosts: str | list[str], test_host: str, ex...
function test_proxy_headers_trusted_hosts (line 368) | async def test_proxy_headers_trusted_hosts(trusted_hosts: str | list[str...
function test_proxy_headers_trusted_hosts_malformed (line 391) | async def test_proxy_headers_trusted_hosts_malformed(
function test_proxy_headers_multiple_proxies (line 421) | async def test_proxy_headers_multiple_proxies(trusted_hosts: str | list[...
function test_proxy_headers_invalid_x_forwarded_for (line 430) | async def test_proxy_headers_invalid_x_forwarded_for() -> None:
function test_proxy_headers_websocket_x_forwarded_proto (line 454) | async def test_proxy_headers_websocket_x_forwarded_proto(
function test_proxy_headers_empty_x_forwarded_for (line 488) | async def test_proxy_headers_empty_x_forwarded_for() -> None:
FILE: tests/middleware/test_wsgi.py
function hello_world (line 15) | def hello_world(environ: Environ, start_response: StartResponse) -> list...
function echo_body (line 26) | def echo_body(environ: Environ, start_response: StartResponse) -> list[b...
function raise_exception (line 37) | def raise_exception(environ: Environ, start_response: StartResponse) -> ...
function return_exc_info (line 41) | def return_exc_info(environ: Environ, start_response: StartResponse) -> ...
function wsgi_middleware (line 56) | def wsgi_middleware(request: pytest.FixtureRequest) -> Callable:
function test_wsgi_get (line 61) | async def test_wsgi_get(wsgi_middleware: Callable) -> None:
function test_wsgi_post (line 70) | async def test_wsgi_post(wsgi_middleware: Callable) -> None:
function test_wsgi_put_more_body (line 79) | async def test_wsgi_put_more_body(wsgi_middleware: Callable) -> None:
function test_wsgi_exception (line 92) | async def test_wsgi_exception(wsgi_middleware: Callable) -> None:
function test_wsgi_exc_info (line 102) | async def test_wsgi_exc_info(wsgi_middleware: Callable) -> None:
function test_build_environ_encoding (line 114) | def test_build_environ_encoding() -> None:
FILE: tests/protocols/test_http.py
class MockTransport (line 170) | class MockTransport:
method __init__ (line 171) | def __init__(
method get_extra_info (line 181) | def get_extra_info(self, key: Any):
method write (line 184) | def write(self, data: bytes):
method close (line 188) | def close(self):
method pause_reading (line 192) | def pause_reading(self):
method resume_reading (line 195) | def resume_reading(self):
method is_closing (line 198) | def is_closing(self):
method clear_buffer (line 201) | def clear_buffer(self):
method set_protocol (line 204) | def set_protocol(self, protocol: asyncio.Protocol):
class MockTimerHandle (line 208) | class MockTimerHandle:
method __init__ (line 209) | def __init__(
method cancel (line 218) | def cancel(self):
class MockLoop (line 224) | class MockLoop:
method __init__ (line 225) | def __init__(self):
method create_task (line 229) | def create_task(self, coroutine: Any) -> Any:
method call_later (line 233) | def call_later(self, delay: float, callback: Callable[[], None], *args...
method run_one (line 238) | async def run_one(self):
method run_later (line 241) | def run_later(self, with_delay: float) -> None:
class MockTask (line 251) | class MockTask:
method add_done_callback (line 252) | def add_done_callback(self, callback: Callable[[], None]):
class MockProtocol (line 256) | class MockProtocol(asyncio.Protocol):
function get_connected_protocol (line 264) | def get_connected_protocol(
function test_get_request (line 280) | async def test_get_request(http_protocol_cls: type[HTTPProtocol]):
function test_header_value_allowed_characters (line 299) | async def test_header_value_allowed_characters(http_protocol_cls: type[H...
function test_invalid_header_name (line 333) | async def test_invalid_header_name(http_protocol_cls: type[HTTPProtocol]...
function test_request_logging (line 346) | async def test_request_logging(path: str, http_protocol_cls: type[HTTPPr...
function test_head_request (line 361) | async def test_head_request(http_protocol_cls: type[HTTPProtocol]):
function test_post_request (line 371) | async def test_post_request(http_protocol_cls: type[HTTPProtocol]):
function test_keepalive (line 390) | async def test_keepalive(http_protocol_cls: type[HTTPProtocol]):
function test_keepalive_timeout (line 401) | async def test_keepalive_timeout(http_protocol_cls: type[HTTPProtocol]):
function test_keepalive_timeout_with_pipelined_requests (line 415) | async def test_keepalive_timeout_with_pipelined_requests(http_protocol_c...
function test_close (line 438) | async def test_close(http_protocol_cls: type[HTTPProtocol]):
function test_chunked_encoding (line 448) | async def test_chunked_encoding(http_protocol_cls: type[HTTPProtocol]):
function test_chunked_encoding_empty_body (line 459) | async def test_chunked_encoding_empty_body(http_protocol_cls: type[HTTPP...
function test_chunked_encoding_head_request (line 470) | async def test_chunked_encoding_head_request(http_protocol_cls: type[HTT...
function test_pipelined_requests (line 480) | async def test_pipelined_requests(http_protocol_cls: type[HTTPProtocol]):
function test_undersized_request (line 501) | async def test_undersized_request(http_protocol_cls: type[HTTPProtocol]):
function test_oversized_request (line 510) | async def test_oversized_request(http_protocol_cls: type[HTTPProtocol]):
function test_large_post_request (line 519) | async def test_large_post_request(http_protocol_cls: type[HTTPProtocol]):
function test_invalid_http (line 529) | async def test_invalid_http(http_protocol_cls: type[HTTPProtocol]):
function test_app_exception (line 537) | async def test_app_exception(http_protocol_cls: type[HTTPProtocol]):
function test_exception_during_response (line 548) | async def test_exception_during_response(http_protocol_cls: type[HTTPPro...
function test_no_response_returned (line 561) | async def test_no_response_returned(http_protocol_cls: type[HTTPProtocol]):
function test_partial_response_returned (line 571) | async def test_partial_response_returned(http_protocol_cls: type[HTTPPro...
function test_response_header_splitting (line 582) | async def test_response_header_splitting(http_protocol_cls: type[HTTPPro...
function test_duplicate_start_message (line 593) | async def test_duplicate_start_message(http_protocol_cls: type[HTTPProto...
function test_missing_start_message (line 605) | async def test_missing_start_message(http_protocol_cls: type[HTTPProtoco...
function test_message_after_body_complete (line 616) | async def test_message_after_body_complete(http_protocol_cls: type[HTTPP...
function test_value_returned (line 629) | async def test_value_returned(http_protocol_cls: type[HTTPProtocol]):
function test_early_disconnect (line 642) | async def test_early_disconnect(http_protocol_cls: type[HTTPProtocol]):
function test_early_response (line 663) | async def test_early_response(http_protocol_cls: type[HTTPProtocol]):
function test_read_after_response (line 674) | async def test_read_after_response(http_protocol_cls: type[HTTPProtocol]):
function test_http10_request (line 691) | async def test_http10_request(http_protocol_cls: type[HTTPProtocol]):
function test_root_path (line 705) | async def test_root_path(http_protocol_cls: type[HTTPProtocol]):
function test_raw_path (line 720) | async def test_raw_path(http_protocol_cls: type[HTTPProtocol]):
function test_max_concurrency (line 737) | async def test_max_concurrency(http_protocol_cls: type[HTTPProtocol]):
function test_shutdown_during_request (line 758) | async def test_shutdown_during_request(http_protocol_cls: type[HTTPProto...
function test_shutdown_during_idle (line 769) | async def test_shutdown_during_idle(http_protocol_cls: type[HTTPProtocol]):
function test_100_continue_sent_when_body_consumed (line 778) | async def test_100_continue_sent_when_body_consumed(http_protocol_cls: t...
function test_100_continue_not_sent_when_body_not_consumed (line 809) | async def test_100_continue_not_sent_when_body_not_consumed(
function test_supported_upgrade_request (line 832) | async def test_supported_upgrade_request(http_protocol_cls: type[HTTPPro...
function test_unsupported_ws_upgrade_request (line 842) | async def test_unsupported_ws_upgrade_request(http_protocol_cls: type[HT...
function test_unsupported_ws_upgrade_request_warn_on_auto (line 852) | async def test_unsupported_ws_upgrade_request_warn_on_auto(
function test_http2_upgrade_request (line 869) | async def test_http2_upgrade_request(http_protocol_cls: type[HTTPProtoco...
function asgi3app (line 879) | async def asgi3app(scope: Scope, receive: ASGIReceiveCallable, send: ASG...
function asgi2app (line 883) | def asgi2app(scope: Scope):
function test_scopes (line 897) | async def test_scopes(
function test_invalid_http_request (line 916) | async def test_invalid_http_request(
function test_fragmentation (line 932) | def test_fragmentation(unused_tcp_port: int):
function test_huge_headers_h11protocol_failure (line 977) | async def test_huge_headers_h11protocol_failure():
function test_huge_headers_httptools_will_pass (line 990) | async def test_huge_headers_httptools_will_pass():
function test_huge_headers_h11protocol_failure_with_setting (line 1003) | async def test_huge_headers_h11protocol_failure_with_setting():
function test_huge_headers_httptools (line 1016) | async def test_huge_headers_httptools():
function test_huge_headers_h11_max_incomplete (line 1029) | async def test_huge_headers_h11_max_incomplete():
function test_return_close_header (line 1040) | async def test_return_close_header(http_protocol_cls: type[HTTPProtocol]):
function test_close_connection_with_multiple_requests (line 1054) | async def test_close_connection_with_multiple_requests(http_protocol_cls...
function test_close_connection_with_post_request (line 1068) | async def test_close_connection_with_post_request(http_protocol_cls: typ...
function test_iterator_headers (line 1087) | async def test_iterator_headers(http_protocol_cls: type[HTTPProtocol]):
function test_lifespan_state (line 1099) | async def test_lifespan_state(http_protocol_cls: type[HTTPProtocol]):
function test_header_upgrade_is_not_websocket_depend_installed (line 1127) | async def test_header_upgrade_is_not_websocket_depend_installed(
function test_header_upgrade_is_websocket_depend_not_installed (line 1143) | async def test_header_upgrade_is_websocket_depend_not_installed(
FILE: tests/protocols/test_utils.py
class MockSocket (line 12) | class MockSocket:
method __init__ (line 13) | def __init__(
method getpeername (line 23) | def getpeername(self):
method getsockname (line 26) | def getsockname(self):
class MockTransport (line 30) | class MockTransport(Transport):
method __init__ (line 31) | def __init__(self, info: dict[str, Any]) -> None:
method get_extra_info (line 34) | def get_extra_info(self, name: str, default: Any = None) -> Any:
function test_get_local_addr_with_socket (line 38) | def test_get_local_addr_with_socket():
function test_get_remote_addr_with_socket (line 52) | def test_get_remote_addr_with_socket():
function test_get_local_addr (line 67) | def test_get_local_addr():
function test_get_remote_addr (line 78) | def test_get_remote_addr():
function test_get_client_addr (line 91) | def test_get_client_addr(scope: Any, expected_client: str):
FILE: tests/protocols/test_websocket.py
class WebSocketResponse (line 49) | class WebSocketResponse:
method __init__ (line 50) | def __init__(self, scope: Scope, receive: ASGIReceiveCallable, send: A...
method __await__ (line 55) | def __await__(self):
method asgi (line 58) | async def asgi(self):
function wsresponse (line 69) | async def wsresponse(url: str):
function test_invalid_upgrade (line 84) | async def test_invalid_upgrade(ws_protocol_cls: WSProtocol, http_protoco...
function test_accept_connection (line 114) | async def test_accept_connection(ws_protocol_cls: WSProtocol, http_proto...
function test_shutdown (line 129) | async def test_shutdown(ws_protocol_cls: WSProtocol, http_protocol_cls: ...
function test_supports_permessage_deflate_extension (line 141) | async def test_supports_permessage_deflate_extension(
function test_can_disable_permessage_deflate_extension (line 159) | async def test_can_disable_permessage_deflate_extension(
function test_close_connection (line 186) | async def test_close_connection(ws_protocol_cls: WSProtocol, http_protoc...
function test_headers (line 204) | async def test_headers(ws_protocol_cls: WSProtocol, http_protocol_cls: H...
function test_extra_headers (line 223) | async def test_extra_headers(ws_protocol_cls: WSProtocol, http_protocol_...
function test_path_and_raw_path (line 238) | async def test_path_and_raw_path(ws_protocol_cls: WSProtocol, http_proto...
function test_send_text_data_to_client (line 257) | async def test_send_text_data_to_client(
function test_send_binary_data_to_client (line 275) | async def test_send_binary_data_to_client(
function test_send_and_close_connection (line 293) | async def test_send_and_close_connection(
function test_send_text_data_to_server (line 319) | async def test_send_text_data_to_server(
function test_send_binary_data_to_server (line 342) | async def test_send_binary_data_to_server(
function test_send_after_protocol_close (line 365) | async def test_send_after_protocol_close(
function test_missing_handshake (line 393) | async def test_missing_handshake(ws_protocol_cls: WSProtocol, http_proto...
function test_send_before_handshake (line 407) | async def test_send_before_handshake(
function test_duplicate_handshake (line 423) | async def test_duplicate_handshake(ws_protocol_cls: WSProtocol, http_pro...
function test_asgi_return_value (line 436) | async def test_asgi_return_value(ws_protocol_cls: WSProtocol, http_proto...
function test_close_transport_on_asgi_return (line 454) | async def test_close_transport_on_asgi_return(
function test_app_close (line 477) | async def test_app_close(
function test_client_close (line 513) | async def test_client_close(ws_protocol_cls: WSProtocol, http_protocol_c...
function test_client_connection_lost (line 541) | async def test_client_connection_lost(
function test_client_connection_lost_on_send (line 574) | async def test_client_connection_lost_on_send(
function test_connection_lost_before_handshake_complete (line 601) | async def test_connection_lost_before_handshake_complete(
function test_send_close_on_server_shutdown (line 637) | async def test_send_close_on_server_shutdown(
function test_subprotocols (line 676) | async def test_subprotocols(
function test_send_binary_data_to_server_bigger_than_default_on_websockets (line 717) | async def test_send_binary_data_to_server_bigger_than_default_on_websock...
function test_server_reject_connection (line 753) | async def test_server_reject_connection(
class EmptyDict (line 787) | class EmptyDict(TypedDict): ...
function test_server_reject_connection_with_response (line 790) | async def test_server_reject_connection_with_response(
function test_server_reject_connection_with_multibody_response (line 821) | async def test_server_reject_connection_with_multibody_response(
function test_server_reject_connection_with_invalid_status (line 861) | async def test_server_reject_connection_with_invalid_status(
function test_server_reject_connection_with_body_nolength (line 892) | async def test_server_reject_connection_with_body_nolength(
function test_server_reject_connection_with_invalid_msg (line 924) | async def test_server_reject_connection_with_invalid_msg(
function test_server_reject_connection_with_missing_body (line 958) | async def test_server_reject_connection_with_missing_body(
function test_server_multiple_websocket_http_response_start_events (line 992) | async def test_server_multiple_websocket_http_response_start_events(
function test_server_can_read_messages_in_buffer_after_close (line 1039) | async def test_server_can_read_messages_in_buffer_after_close(
function test_default_server_headers (line 1073) | async def test_default_server_headers(
function test_no_server_headers (line 1090) | async def test_no_server_headers(ws_protocol_cls: WSProtocol, http_proto...
function test_no_date_header_on_wsproto (line 1113) | async def test_no_date_header_on_wsproto(http_protocol_cls: HTTPProtocol...
function test_multiple_server_header (line 1135) | async def test_multiple_server_header(
function test_lifespan_state (line 1160) | async def test_lifespan_state(ws_protocol_cls: WSProtocol, http_protocol...
FILE: tests/response.py
class Response (line 1) | class Response:
method __init__ (line 4) | def __init__(self, content, status_code=200, headers=None, media_type=...
method __call__ (line 12) | async def __call__(self, scope, receive, send) -> None:
method render (line 23) | def render(self, content) -> bytes:
method set_content_length (line 28) | def set_content_length(self):
method set_content_type (line 32) | def set_content_type(self):
FILE: tests/supervisors/test_multiprocess.py
function new_console_in_windows (line 20) | def new_console_in_windows(test_function: Callable[[], Any]) -> Callable...
function app (line 40) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function run (line 44) | def run(sockets: list[socket.socket] | None) -> None:
function test_process_ping_pong (line 49) | def test_process_ping_pong() -> None:
function test_process_ping_pong_timeout (line 55) | def test_process_ping_pong_timeout() -> None:
function test_multiprocess_run (line 61) | def test_multiprocess_run() -> None:
function test_multiprocess_health_check (line 76) | def test_multiprocess_health_check() -> None:
function test_multiprocess_sigterm (line 96) | def test_multiprocess_sigterm() -> None:
function test_multiprocess_sigbreak (line 110) | def test_multiprocess_sigbreak() -> None: # pragma: py-not-win32
function test_multiprocess_sighup (line 123) | def test_multiprocess_sighup() -> None:
function test_multiprocess_sigttin (line 146) | def test_multiprocess_sigttin() -> None:
function test_multiprocess_sigttou (line 161) | def test_multiprocess_sigttou() -> None:
FILE: tests/supervisors/test_reload.py
function run (line 29) | def run(sockets: list[socket.socket] | None) -> None:
function sleep_touch (line 33) | def sleep_touch(*paths: Path):
function touch_soon (line 40) | def touch_soon() -> Generator[Callable[[Path], None]]:
class TestBaseReload (line 54) | class TestBaseReload:
method setup (line 56) | def setup(self, reload_directory_structure: Path, reloader_class: type...
method _setup_reloader (line 62) | def _setup_reloader(self, config: Config) -> BaseReload:
method _reload_tester (line 71) | def _reload_tester(
method test_reloader_should_initialize (line 85) | def test_reloader_should_initialize(self) -> None:
method test_reload_when_python_file_is_changed (line 98) | def test_reload_when_python_file_is_changed(self, touch_soon: Callable...
method test_should_reload_when_python_file_in_subdir_is_changed (line 111) | def test_should_reload_when_python_file_in_subdir_is_changed(self, tou...
method test_should_not_reload_when_python_file_in_excluded_subdir_is_changed (line 123) | def test_should_not_reload_when_python_file_in_excluded_subdir_is_chan...
method test_reload_when_pattern_matched_file_is_changed (line 142) | def test_reload_when_pattern_matched_file_is_changed(
method test_should_not_reload_when_exclude_pattern_match_file_is_changed (line 156) | def test_should_not_reload_when_exclude_pattern_match_file_is_changed(
method test_should_not_reload_when_dot_file_is_changed (line 179) | def test_should_not_reload_when_dot_file_is_changed(self, touch_soon: ...
method test_should_reload_when_directories_have_same_prefix (line 191) | def test_should_reload_when_directories_have_same_prefix(
method test_should_not_reload_when_only_subdirectory_is_watched (line 216) | def test_should_not_reload_when_only_subdirectory_is_watched(
method test_override_defaults (line 236) | def test_override_defaults(self, touch_soon: Callable[[Path], None]) -...
method test_explicit_paths (line 258) | def test_explicit_paths(self, touch_soon: Callable[[Path], None]) -> N...
method test_watchfiles_no_changes (line 279) | def test_watchfiles_no_changes(self) -> None:
function test_should_watch_cwd (line 308) | def test_should_watch_cwd(mocker: MockerFixture, reload_directory_struct...
function test_should_watch_multiple_dirs (line 318) | def test_should_watch_multiple_dirs(mocker: MockerFixture, reload_direct...
function test_display_path_relative (line 335) | def test_display_path_relative(tmp_path: Path):
function test_display_path_non_relative (line 342) | def test_display_path_non_relative():
function test_base_reloader_run (line 347) | def test_base_reloader_run(tmp_path: Path):
function test_base_reloader_should_exit (line 378) | def test_base_reloader_should_exit(tmp_path: Path):
function test_base_reloader_closes_sockets_on_shutdown (line 394) | def test_base_reloader_closes_sockets_on_shutdown():
FILE: tests/supervisors/test_signal.py
function test_sigint_finish_req (line 14) | async def test_sigint_finish_req(unused_tcp_port: int):
function test_sigint_abort_req (line 47) | async def test_sigint_abort_req(unused_tcp_port: int, caplog):
function test_sigint_deny_request_after_triggered (line 82) | async def test_sigint_deny_request_after_triggered(unused_tcp_port: int,...
FILE: tests/test_auto_detection.py
function app (line 32) | async def app(scope, receive, send):
function test_loop_auto (line 36) | def test_loop_auto():
function test_http_auto (line 44) | async def test_http_auto():
function test_websocket_auto (line 52) | async def test_websocket_auto():
FILE: tests/test_cli.py
function load_env_var (line 25) | def load_env_var(key: str, value: str) -> Iterator[None]:
class App (line 33) | class App:
function test_cli_print_version (line 37) | def test_cli_print_version() -> None:
function test_cli_headers (line 53) | def test_cli_headers() -> None:
function test_cli_call_server_run (line 70) | def test_cli_call_server_run() -> None:
function test_cli_call_change_reload_run (line 80) | def test_cli_call_change_reload_run() -> None:
function test_cli_call_multiprocess_run (line 92) | def test_cli_call_multiprocess_run() -> None:
function uds_file (line 105) | def uds_file(tmp_path: Path, request: pytest.FixtureRequest) -> Path: #...
function test_cli_uds (line 114) | def test_cli_uds(uds_file: Path) -> None: # pragma: py-win32
function test_cli_incomplete_app_parameter (line 128) | def test_cli_incomplete_app_parameter() -> None:
function test_cli_event_size (line 139) | def test_cli_event_size() -> None:
function test_env_variables (line 155) | def test_env_variables(http_protocol: str):
function test_ignore_environment_variable_when_set_on_cli (line 164) | def test_ignore_environment_variable_when_set_on_cli():
function test_app_dir (line 173) | def test_app_dir(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
function test_set_app_via_environment_variable (line 195) | def test_set_app_via_environment_variable():
FILE: tests/test_compat.py
function assert_event_loop (line 13) | async def assert_event_loop(expected_loop_class: type[AbstractEventLoop]):
function test_asyncio_run__default_loop_factory (line 17) | def test_asyncio_run__default_loop_factory() -> None:
function test_asyncio_run__custom_loop_factory (line 21) | def test_asyncio_run__custom_loop_factory() -> None:
function test_asyncio_run__passing_a_non_awaitable_callback_should_throw_error (line 25) | def test_asyncio_run__passing_a_non_awaitable_callback_should_throw_erro...
FILE: tests/test_config.py
function mocked_logging_config_module (line 30) | def mocked_logging_config_module(mocker: MockerFixture) -> MagicMock:
function json_logging_config (line 35) | def json_logging_config(logging_config: dict) -> str:
function yaml_logging_config (line 40) | def yaml_logging_config(logging_config: dict) -> str:
function asgi_app (line 44) | async def asgi_app(scope: Scope, receive: ASGIReceiveCallable, send: ASG...
function wsgi_app (line 48) | def wsgi_app(environ: Environ, start_response: StartResponse) -> None:
function test_config_should_reload_is_set (line 56) | def test_config_should_reload_is_set(app: ASGIApplication, expected_shou...
function test_should_warn_on_invalid_reload_configuration (line 62) | def test_should_warn_on_invalid_reload_configuration(tmp_path: Path, cap...
function test_reload_dir_is_set (line 80) | def test_reload_dir_is_set(reload_directory_structure: Path, caplog: pyt...
function test_non_existant_reload_dir_is_not_set (line 91) | def test_non_existant_reload_dir_is_not_set(reload_directory_structure: ...
function test_reload_subdir_removal (line 102) | def test_reload_subdir_removal(reload_directory_structure: Path) -> None:
function test_reload_included_dir_is_added_to_reload_dirs (line 112) | def test_reload_included_dir_is_added_to_reload_dirs(
function test_reload_dir_subdirectories_are_removed (line 129) | def test_reload_dir_subdirectories_are_removed(
function test_reload_excluded_subdirectories_are_removed (line 151) | def test_reload_excluded_subdirectories_are_removed(
function test_reload_includes_exclude_dir_patterns_are_matched (line 168) | def test_reload_includes_exclude_dir_patterns_are_matched(
function test_wsgi_app (line 191) | def test_wsgi_app() -> None:
function test_proxy_headers (line 200) | def test_proxy_headers() -> None:
function test_app_unimportable_module (line 208) | def test_app_unimportable_module() -> None:
function test_app_unimportable_other (line 214) | def test_app_unimportable_other(caplog: pytest.LogCaptureFixture) -> None:
function test_app_factory (line 227) | def test_app_factory(caplog: pytest.LogCaptureFixture) -> None:
function test_concrete_http_class (line 251) | def test_concrete_http_class() -> None:
function test_socket_bind (line 257) | def test_socket_bind() -> None:
function test_ssl_config (line 265) | def test_ssl_config(
function test_ssl_config_combined (line 279) | def test_ssl_config_combined(tls_certificate_key_and_chain_path: str) ->...
function asgi2_app (line 289) | def asgi2_app(scope: Scope) -> Callable:
function test_asgi_version (line 297) | def test_asgi_version(app: ASGIApplication, expected_interface: Literal[...
function test_log_config_default (line 312) | def test_log_config_default(
function test_log_config_json (line 331) | def test_log_config_json(
function test_log_config_yaml (line 350) | def test_log_config_yaml(
function test_log_config_file (line 370) | def test_log_config_file(
function web_concurrency (line 384) | def web_concurrency(request: pytest.FixtureRequest) -> Iterator[int]:
function forwarded_allow_ips (line 391) | def forwarded_allow_ips(request: pytest.FixtureRequest) -> Iterator[str]:
function test_env_file (line 397) | def test_env_file(
function test_config_access_log (line 426) | def test_config_access_log(access_log: bool, handlers: int) -> None:
function test_config_log_level (line 435) | def test_config_log_level(log_level: int) -> None:
function test_config_log_effective_level (line 447) | def test_config_log_effective_level(log_level: int, uvicorn_logger_level...
function test_ws_max_size (line 465) | def test_ws_max_size() -> None:
function test_ws_max_queue (line 471) | def test_ws_max_queue() -> None:
function test_bind_unix_socket_works_with_reload_or_workers (line 486) | def test_bind_unix_socket_works_with_reload_or_workers(
function test_bind_fd_works_with_reload_or_workers (line 507) | def test_bind_fd_works_with_reload_or_workers(reload: bool, workers: int...
function test_config_use_subprocess (line 533) | def test_config_use_subprocess(reload: bool, workers: int, expected: bool):
function test_warn_when_using_reload_and_workers (line 539) | def test_warn_when_using_reload_and_workers(caplog: pytest.LogCaptureFix...
function test_get_loop_factory (line 552) | def test_get_loop_factory(loop_type: LoopFactoryType, expected_loop_fact...
function test_custom_loop__importable_custom_loop_setup_function (line 564) | def test_custom_loop__importable_custom_loop_setup_function() -> None:
function test_custom_loop__not_importable_custom_loop_setup_function (line 576) | def test_custom_loop__not_importable_custom_loop_setup_function(caplog: ...
function test_setup_event_loop_is_removed (line 590) | def test_setup_event_loop_is_removed(caplog: pytest.LogCaptureFixture) -...
FILE: tests/test_default_headers.py
function app (line 13) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function test_default_default_headers (line 19) | async def test_default_default_headers(unused_tcp_port: int):
function test_override_server_header (line 27) | async def test_override_server_header(unused_tcp_port: int):
function test_disable_default_server_header (line 36) | async def test_disable_default_server_header(unused_tcp_port: int):
function test_override_server_header_multiple_times (line 44) | async def test_override_server_header_multiple_times(unused_tcp_port: int):
function test_add_additional_header (line 53) | async def test_add_additional_header(unused_tcp_port: int):
function test_disable_default_date_header (line 64) | async def test_disable_default_date_header(unused_tcp_port: int):
FILE: tests/test_lifespan.py
function test_lifespan_on (line 10) | def test_lifespan_on():
function test_lifespan_off (line 43) | def test_lifespan_off():
function test_lifespan_auto (line 59) | def test_lifespan_auto():
function test_lifespan_auto_with_error (line 92) | def test_lifespan_auto_with_error():
function test_lifespan_on_with_error (line 110) | def test_lifespan_on_with_error():
function test_lifespan_with_failed_startup (line 131) | def test_lifespan_with_failed_startup(mode, raise_exception, caplog):
function test_lifespan_scope_asgi3app (line 160) | def test_lifespan_scope_asgi3app():
function test_lifespan_scope_asgi2app (line 183) | def test_lifespan_scope_asgi2app():
function test_lifespan_with_failed_shutdown (line 210) | def test_lifespan_with_failed_shutdown(mode, raise_exception, caplog):
function test_lifespan_state (line 244) | def test_lifespan_state():
FILE: tests/test_main.py
function app (line 19) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function _has_ipv6 (line 25) | def _has_ipv6(host: str):
function test_run (line 53) | async def test_run(host, url: str, unused_tcp_port: int):
function test_run_multiprocess (line 61) | async def test_run_multiprocess(unused_tcp_port: int):
function test_run_reload (line 69) | async def test_run_reload(unused_tcp_port: int):
function test_run_invalid_app_config_combination (line 77) | def test_run_invalid_app_config_combination(caplog: pytest.LogCaptureFix...
function test_run_startup_failure (line 88) | def test_run_startup_failure(caplog: pytest.LogCaptureFixture) -> None:
function test_run_match_config_params (line 100) | def test_run_match_config_params() -> None:
function test_exit_on_create_server_with_invalid_host (line 112) | async def test_exit_on_create_server_with_invalid_host() -> None:
function test_deprecated_server_state_from_main (line 120) | def test_deprecated_server_state_from_main() -> None:
FILE: tests/test_server.py
function capture_signal_sync (line 31) | def capture_signal_sync(sig: signal.Signals) -> Generator[list[int], Non...
function capture_signal_async (line 40) | def capture_signal_async(sig: signal.Signals) -> Generator[list[int], No...
function dummy_app (line 49) | async def dummy_app(scope, receive, send): # pragma: py-win32
function app (line 53) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function test_server_interrupt (line 69) | async def test_server_interrupt(
function test_shutdown_on_early_exit_during_startup (line 90) | async def test_shutdown_on_early_exit_during_startup(unused_tcp_port: int):
function test_request_than_limit_max_requests_warn_log (line 125) | async def test_request_than_limit_max_requests_warn_log(
function test_limit_max_requests_jitter (line 138) | async def test_limit_max_requests_jitter(
function server (line 160) | async def server(*, app: ASGIApplication, port: int, http_protocol_cls: ...
function test_no_contextvars_pollution_asyncio (line 189) | async def test_no_contextvars_pollution_asyncio(
FILE: tests/test_ssl.py
function app (line 8) | async def app(scope, receive, send):
function test_run (line 15) | async def test_run(
function test_run_chain (line 38) | async def test_run_chain(
function test_run_chain_only (line 59) | async def test_run_chain_only(tls_ca_ssl_context, tls_certificate_key_an...
function test_run_password (line 74) | async def test_run_password(
FILE: tests/test_subprocess.py
function server_run (line 11) | def server_run(sockets: list[socket.socket]): # pragma: no cover
function app (line 15) | async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISend...
function test_get_subprocess (line 19) | def test_get_subprocess() -> None:
function test_subprocess_started (line 31) | def test_subprocess_started() -> None:
FILE: tests/utils.py
function run_server (line 16) | async def run_server(config: Config, sockets: list[socket] | None = None...
function assert_signal (line 29) | def assert_signal(sig: signal.Signals):
function as_cwd (line 41) | def as_cwd(path: Path):
function get_asyncio_default_loop_per_os (line 51) | def get_asyncio_default_loop_per_os() -> type[asyncio.AbstractEventLoop]:
FILE: uvicorn/_compat.py
function asyncio_run (line 21) | def asyncio_run(
function asyncio_run (line 35) | def asyncio_run(
function _cancel_all_tasks (line 71) | def _cancel_all_tasks(loop: asyncio.AbstractEventLoop) -> None:
FILE: uvicorn/_subprocess.py
function get_subprocess (line 21) | def get_subprocess(
function subprocess_started (line 54) | def subprocess_started(
FILE: uvicorn/_types.py
class ASGIVersions (line 51) | class ASGIVersions(TypedDict):
class HTTPScope (line 56) | class HTTPScope(TypedDict):
class WebSocketScope (line 73) | class WebSocketScope(TypedDict):
class LifespanScope (line 90) | class LifespanScope(TypedDict):
class HTTPRequestEvent (line 100) | class HTTPRequestEvent(TypedDict):
class HTTPResponseDebugEvent (line 106) | class HTTPResponseDebugEvent(TypedDict):
class HTTPResponseStartEvent (line 111) | class HTTPResponseStartEvent(TypedDict):
class HTTPResponseBodyEvent (line 118) | class HTTPResponseBodyEvent(TypedDict):
class HTTPResponseTrailersEvent (line 124) | class HTTPResponseTrailersEvent(TypedDict):
class HTTPServerPushEvent (line 130) | class HTTPServerPushEvent(TypedDict):
class HTTPDisconnectEvent (line 136) | class HTTPDisconnectEvent(TypedDict):
class WebSocketConnectEvent (line 140) | class WebSocketConnectEvent(TypedDict):
class WebSocketAcceptEvent (line 144) | class WebSocketAcceptEvent(TypedDict):
class _WebSocketReceiveEventBytes (line 150) | class _WebSocketReceiveEventBytes(TypedDict):
class _WebSocketReceiveEventText (line 156) | class _WebSocketReceiveEventText(TypedDict):
class _WebSocketSendEventBytes (line 165) | class _WebSocketSendEventBytes(TypedDict):
class _WebSocketSendEventText (line 171) | class _WebSocketSendEventText(TypedDict):
class WebSocketResponseStartEvent (line 180) | class WebSocketResponseStartEvent(TypedDict):
class WebSocketResponseBodyEvent (line 186) | class WebSocketResponseBodyEvent(TypedDict):
class WebSocketDisconnectEvent (line 192) | class WebSocketDisconnectEvent(TypedDict):
class WebSocketCloseEvent (line 198) | class WebSocketCloseEvent(TypedDict):
class LifespanStartupEvent (line 204) | class LifespanStartupEvent(TypedDict):
class LifespanShutdownEvent (line 208) | class LifespanShutdownEvent(TypedDict):
class LifespanStartupCompleteEvent (line 212) | class LifespanStartupCompleteEvent(TypedDict):
class LifespanStartupFailedEvent (line 216) | class LifespanStartupFailedEvent(TypedDict):
class LifespanShutdownCompleteEvent (line 221) | class LifespanShutdownCompleteEvent(TypedDict):
class LifespanShutdownFailedEvent (line 225) | class LifespanShutdownFailedEvent(TypedDict):
class ASGI2Protocol (line 266) | class ASGI2Protocol(Protocol):
method __init__ (line 267) | def __init__(self, scope: Scope) -> None: ... # pragma: no cover
method __call__ (line 269) | async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendC...
FILE: uvicorn/config.py
function create_ssl_context (line 105) | def create_ssl_context(
function is_dir (line 125) | def is_dir(path: Path) -> bool:
function resolve_reload_patterns (line 134) | def resolve_reload_patterns(patterns_list: list[str], directories_list: ...
function _normalize_dirs (line 170) | def _normalize_dirs(dirs: list[str] | str | None) -> list[str]:
class Config (line 178) | class Config:
method __init__ (line 179) | def __init__(
method asgi_version (line 348) | def asgi_version(self) -> Literal["2.0", "3.0"]:
method is_ssl (line 357) | def is_ssl(self) -> bool:
method use_subprocess (line 361) | def use_subprocess(self) -> bool:
method configure_logging (line 364) | def configure_logging(self) -> None:
method load (line 402) | def load(self) -> None:
method setup_event_loop (line 481) | def setup_event_loop(self) -> None:
method get_loop_factory (line 488) | def get_loop_factory(self) -> Callable[[], asyncio.AbstractEventLoop] ...
method bind_socket (line 501) | def bind_socket(self) -> socket.socket:
method should_reload (line 550) | def should_reload(self) -> bool:
FILE: uvicorn/importer.py
class ImportFromStringError (line 5) | class ImportFromStringError(Exception):
function import_from_string (line 9) | def import_from_string(import_str: Any) -> Any:
FILE: uvicorn/lifespan/off.py
class LifespanOff (line 8) | class LifespanOff:
method __init__ (line 9) | def __init__(self, config: Config) -> None:
method startup (line 13) | async def startup(self) -> None:
method shutdown (line 16) | async def shutdown(self) -> None:
FILE: uvicorn/lifespan/on.py
class LifespanOn (line 31) | class LifespanOn:
method __init__ (line 32) | def __init__(self, config: Config) -> None:
method startup (line 47) | async def startup(self) -> None:
method shutdown (line 64) | async def shutdown(self) -> None:
method main (line 78) | async def main(self) -> None:
method send (line 102) | async def send(self, message: LifespanSendMessage) -> None:
method receive (line 136) | async def receive(self) -> LifespanReceiveMessage:
FILE: uvicorn/logging.py
class ColourizedFormatter (line 14) | class ColourizedFormatter(logging.Formatter):
method __init__ (line 32) | def __init__(
method color_level_name (line 45) | def color_level_name(self, level_name: str, level_no: int) -> str:
method should_use_colors (line 52) | def should_use_colors(self) -> bool:
method formatMessage (line 55) | def formatMessage(self, record: logging.LogRecord) -> str:
class DefaultFormatter (line 68) | class DefaultFormatter(ColourizedFormatter):
method should_use_colors (line 69) | def should_use_colors(self) -> bool:
class AccessFormatter (line 73) | class AccessFormatter(ColourizedFormatter):
method get_status_code (line 82) | def get_status_code(self, status_code: int) -> str:
method formatMessage (line 97) | def formatMessage(self, record: logging.LogRecord) -> str:
FILE: uvicorn/loops/asyncio.py
function asyncio_loop_factory (line 8) | def asyncio_loop_factory(use_subprocess: bool = False) -> Callable[[], a...
FILE: uvicorn/loops/auto.py
function auto_loop_factory (line 7) | def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyn...
FILE: uvicorn/loops/uvloop.py
function uvloop_loop_factory (line 9) | def uvloop_loop_factory(use_subprocess: bool = False) -> Callable[[], as...
FILE: uvicorn/main.py
function _metavar_from_type (line 39) | def _metavar_from_type(_type: Any) -> str:
function print_version (line 48) | def print_version(ctx: click.Context, param: click.Parameter, value: boo...
function main (line 382) | def main(
function run (line 486) | def run(
function __getattr__ (line 617) | def __getattr__(name: str) -> Any:
FILE: uvicorn/middleware/asgi2.py
class ASGI2Middleware (line 9) | class ASGI2Middleware:
method __init__ (line 10) | def __init__(self, app: "ASGI2Application"):
method __call__ (line 13) | async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable...
FILE: uvicorn/middleware/message_logger.py
function message_with_placeholders (line 22) | def message_with_placeholders(message: Any) -> Any:
class MessageLoggerMiddleware (line 36) | class MessageLoggerMiddleware:
method __init__ (line 37) | def __init__(self, app: "ASGI3Application"):
method __call__ (line 47) | async def __call__(
FILE: uvicorn/middleware/proxy_headers.py
class ProxyHeadersMiddleware (line 8) | class ProxyHeadersMiddleware:
method __init__ (line 23) | def __init__(self, app: ASGI3Application, trusted_hosts: list[str] | s...
method __call__ (line 27) | async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, s...
function _parse_raw_hosts (line 63) | def _parse_raw_hosts(value: str) -> list[str]:
class _TrustedHosts (line 67) | class _TrustedHosts:
method __init__ (line 70) | def __init__(self, trusted_hosts: list[str] | str) -> None:
method __contains__ (line 109) | def __contains__(self, host: str | None) -> bool:
method get_trusted_client_host (line 125) | def get_trusted_client_host(self, x_forwarded_for: str) -> str:
FILE: uvicorn/middleware/wsgi.py
function build_environ (line 27) | def build_environ(scope: HTTPScope, message: ASGIReceiveEvent, body: io....
class _WSGIMiddleware (line 82) | class _WSGIMiddleware:
method __init__ (line 83) | def __init__(self, app: WSGIApp, workers: int = 10):
method __call__ (line 91) | async def __call__(
class WSGIResponder (line 102) | class WSGIResponder:
method __init__ (line 103) | def __init__(
method __call__ (line 120) | async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendC...
method sender (line 146) | async def sender(self, send: ASGISendCallable) -> None:
method start_response (line 157) | def start_response(
method wsgi (line 177) | def wsgi(self, environ: Environ, start_response: StartResponse) -> None:
FILE: uvicorn/protocols/http/flow_control.py
class FlowControl (line 10) | class FlowControl:
method __init__ (line 11) | def __init__(self, transport: asyncio.Transport) -> None:
method drain (line 18) | async def drain(self) -> None:
method pause_reading (line 21) | def pause_reading(self) -> None:
method resume_reading (line 26) | def resume_reading(self) -> None:
method pause_writing (line 31) | def pause_writing(self) -> None:
method resume_writing (line 36) | def resume_writing(self) -> None:
function service_unavailable (line 42) | async def service_unavailable(scope: Scope, receive: ASGIReceiveCallable...
FILE: uvicorn/protocols/http/h11_impl.py
function _get_status_phrase (line 30) | def _get_status_phrase(status_code: int) -> bytes:
class H11Protocol (line 40) | class H11Protocol(asyncio.Protocol):
method __init__ (line 41) | def __init__(
method connection_made (line 90) | def connection_made( # type: ignore[override]
method connection_lost (line 105) | def connection_lost(self, exc: Exception | None) -> None:
method eof_received (line 130) | def eof_received(self) -> None:
method _unset_keepalive_if_required (line 133) | def _unset_keepalive_if_required(self) -> None:
method _get_upgrade (line 138) | def _get_upgrade(self) -> bytes | None:
method _should_upgrade_to_ws (line 150) | def _should_upgrade_to_ws(self) -> bool:
method _unsupported_upgrade_warning (line 155) | def _unsupported_upgrade_warning(self) -> None:
method _should_upgrade (line 162) | def _should_upgrade(self) -> bool:
method data_received (line 170) | def data_received(self, data: bytes) -> None:
method handle_events (line 176) | def handle_events(self) -> None:
method handle_websocket_upgrade (line 279) | def handle_websocket_upgrade(self, event: h11.Request) -> None:
method send_400_response (line 298) | def send_400_response(self, msg: str) -> None:
method on_response_complete (line 316) | def on_response_complete(self) -> None:
method shutdown (line 335) | def shutdown(self) -> None:
method pause_writing (line 346) | def pause_writing(self) -> None:
method resume_writing (line 352) | def resume_writing(self) -> None:
method timeout_keep_alive_handler (line 358) | def timeout_keep_alive_handler(self) -> None:
class RequestResponseCycle (line 369) | class RequestResponseCycle:
method __init__ (line 370) | def __init__(
method run_asgi (line 408) | async def run_asgi(self, app: ASGI3Application) -> None:
method send_500_response (line 436) | async def send_500_response(self) -> None:
method send (line 454) | async def send(self, message: ASGISendEvent) -> None:
method receive (line 528) | async def receive(self) -> ASGIReceiveEvent:
FILE: uvicorn/protocols/http/httptools_impl.py
function _get_status_line (line 34) | def _get_status_line(status_code: int) -> bytes:
class HttpToolsProtocol (line 45) | class HttpToolsProtocol(asyncio.Protocol):
method __init__ (line 46) | def __init__(
method connection_made (line 100) | def connection_made( # type: ignore[override]
method connection_lost (line 115) | def connection_lost(self, exc: Exception | None) -> None:
method eof_received (line 134) | def eof_received(self) -> None:
method _unset_keepalive_if_required (line 137) | def _unset_keepalive_if_required(self) -> None:
method _get_upgrade (line 142) | def _get_upgrade(self) -> bytes | None:
method _should_upgrade_to_ws (line 154) | def _should_upgrade_to_ws(self) -> bool:
method _unsupported_upgrade_warning (line 159) | def _unsupported_upgrade_warning(self) -> None:
method _should_upgrade (line 165) | def _should_upgrade(self) -> bool:
method data_received (line 169) | def data_received(self, data: bytes) -> None:
method handle_websocket_upgrade (line 185) | def handle_websocket_upgrade(self) -> None:
method send_400_response (line 205) | def send_400_response(self, msg: str) -> None:
method on_message_begin (line 221) | def on_message_begin(self) -> None:
method on_url (line 238) | def on_url(self, url: bytes) -> None:
method on_header (line 241) | def on_header(self, name: bytes, value: bytes) -> None:
method on_headers_complete (line 247) | def on_headers_complete(self) -> None:
method on_body (line 305) | def on_body(self, body: bytes) -> None:
method on_message_complete (line 313) | def on_message_complete(self) -> None:
method on_response_complete (line 319) | def on_response_complete(self) -> None:
method shutdown (line 343) | def shutdown(self) -> None:
method pause_writing (line 352) | def pause_writing(self) -> None:
method resume_writing (line 358) | def resume_writing(self) -> None:
method timeout_keep_alive_handler (line 364) | def timeout_keep_alive_handler(self) -> None:
class RequestResponseCycle (line 373) | class RequestResponseCycle:
method __init__ (line 374) | def __init__(
method run_asgi (line 414) | async def run_asgi(self, app: ASGI3Application) -> None:
method send_500_response (line 442) | async def send_500_response(self) -> None:
method send (line 457) | async def send(self, message: ASGISendEvent) -> None:
method receive (line 563) | async def receive(self) -> ASGIReceiveEvent:
FILE: uvicorn/protocols/utils.py
class ClientDisconnected (line 10) | class ClientDisconnected(OSError): ...
function get_remote_addr (line 13) | def get_remote_addr(transport: asyncio.Transport) -> tuple[str, int] | N...
function get_local_addr (line 30) | def get_local_addr(transport: asyncio.Transport) -> tuple[str, int | Non...
function is_ssl (line 47) | def is_ssl(transport: asyncio.Transport) -> bool:
function get_client_addr (line 51) | def get_client_addr(scope: WWWScope) -> str:
function get_path_with_query_string (line 58) | def get_path_with_query_string(scope: WWWScope) -> str:
FILE: uvicorn/protocols/websockets/websockets_impl.py
class Server (line 46) | class Server:
method register (line 49) | def register(self, ws: WebSocketServerProtocol) -> None:
method unregister (line 52) | def unregister(self, ws: WebSocketServerProtocol) -> None:
method is_serving (line 55) | def is_serving(self) -> bool:
class WebSocketProtocol (line 59) | class WebSocketProtocol(WebSocketServerProtocol):
method __init__ (line 63) | def __init__(
method connection_made (line 120) | def connection_made( # type: ignore[override]
method connection_lost (line 135) | def connection_lost(self, exc: Exception | None) -> None:
method shutdown (line 148) | def shutdown(self) -> None:
method on_task_complete (line 156) | def on_task_complete(self, task: asyncio.Task[None]) -> None:
method process_request (line 159) | async def process_request(self, path: str, request_headers: Headers) -...
method process_subprotocol (line 206) | def process_subprotocol(
method send_500_response (line 215) | def send_500_response(self) -> None:
method ws_handler (line 229) | async def ws_handler(self, protocol: WebSocketServerProtocol, path: st...
method run_asgi (line 238) | async def run_asgi(self) -> None:
method asgi_send (line 264) | async def asgi_send(self, message: ASGISendEvent) -> None:
method asgi_receive (line 359) | async def asgi_receive(self) -> WebSocketDisconnectEvent | WebSocketCo...
FILE: uvicorn/protocols/websockets/websockets_sansio_impl.py
class WebSocketsSansIOProtocol (line 45) | class WebSocketsSansIOProtocol(asyncio.Protocol):
method __init__ (line 46) | def __init__(
method connection_made (line 103) | def connection_made(self, transport: BaseTransport) -> None:
method connection_lost (line 116) | def connection_lost(self, exc: Exception | None) -> None:
method eof_received (line 129) | def eof_received(self) -> None:
method shutdown (line 132) | def shutdown(self) -> None:
method data_received (line 142) | def data_received(self, data: bytes) -> None:
method handle_events (line 149) | def handle_events(self) -> None:
method handle_connect (line 171) | def handle_connect(self, event: Request) -> None:
method handle_cont (line 210) | def handle_cont(self, event: Frame) -> None: # pragma: no cover
method handle_text (line 215) | def handle_text(self, event: Frame) -> None:
method handle_bytes (line 221) | def handle_bytes(self, event: Frame) -> None:
method send_receive_event_to_app (line 227) | def send_receive_event_to_app(self) -> None:
method handle_ping (line 242) | def handle_ping(self) -> None:
method handle_close (line 246) | def handle_close(self, event: Frame) -> None:
method handle_parser_exception (line 257) | def handle_parser_exception(self) -> None: # pragma: no cover
method on_task_complete (line 268) | def on_task_complete(self, task: asyncio.Task[None]) -> None:
method run_asgi (line 271) | async def run_asgi(self) -> None:
method send_500_response (line 287) | def send_500_response(self) -> None:
method send (line 295) | async def send(self, message: ASGISendEvent) -> None:
method receive (line 412) | async def receive(self) -> ASGIReceiveEvent:
FILE: uvicorn/protocols/websockets/wsproto_impl.py
class WSProtocol (line 38) | class WSProtocol(asyncio.Protocol):
method __init__ (line 39) | def __init__(
method connection_made (line 87) | def connection_made( # type: ignore[override]
method connection_lost (line 100) | def connection_lost(self, exc: Exception | None) -> None:
method eof_received (line 113) | def eof_received(self) -> None:
method data_received (line 116) | def data_received(self, data: bytes) -> None:
method handle_events (line 126) | def handle_events(self) -> None:
method pause_writing (line 139) | def pause_writing(self) -> None:
method resume_writing (line 145) | def resume_writing(self) -> None:
method shutdown (line 151) | def shutdown(self) -> None:
method on_task_complete (line 160) | def on_task_complete(self, task: asyncio.Task[None]) -> None:
method handle_connect (line 165) | def handle_connect(self, event: events.Request) -> None:
method handle_text (line 193) | def handle_text(self, event: events.TextMessage) -> None:
method handle_bytes (line 202) | def handle_bytes(self, event: events.BytesMessage) -> None:
method handle_close (line 212) | def handle_close(self, event: events.CloseConnection) -> None:
method handle_ping (line 218) | def handle_ping(self, event: events.Ping) -> None:
method send_500_response (line 221) | def send_500_response(self) -> None:
method run_asgi (line 233) | async def run_asgi(self) -> None:
method send (line 249) | async def send(self, message: ASGISendEvent) -> None:
method receive (line 370) | async def receive(self) -> WebSocketEvent:
FILE: uvicorn/server.py
class ServerState (line 44) | class ServerState:
method __init__ (line 49) | def __init__(self) -> None:
class Server (line 56) | class Server:
method __init__ (line 57) | def __init__(self, config: Config) -> None:
method limit_max_requests (line 69) | def limit_max_requests(self) -> int | None:
method run (line 74) | def run(self, sockets: list[socket.socket] | None = None) -> None:
method serve (line 77) | async def serve(self, sockets: list[socket.socket] | None = None) -> N...
method _serve (line 81) | async def _serve(self, sockets: list[socket.socket] | None = None) -> ...
method startup (line 104) | async def startup(self, sockets: list[socket.socket] | None = None) ->...
method _log_started_message (line 197) | def _log_started_message(self, listeners: Sequence[socket.SocketType])...
method main_loop (line 232) | async def main_loop(self) -> None:
method on_tick (line 241) | async def on_tick(self, counter: int) -> bool:
method shutdown (line 271) | async def shutdown(self, sockets: list[socket.socket] | None = None) -...
method _wait_tasks_to_complete (line 303) | async def _wait_tasks_to_complete(self) -> None:
method capture_signals (line 322) | def capture_signals(self) -> Generator[None, None, None]:
method handle_exit (line 341) | def handle_exit(self, sig: int, frame: FrameType | None) -> None:
FILE: uvicorn/supervisors/basereload.py
class BaseReload (line 26) | class BaseReload:
method __init__ (line 27) | def __init__(
method signal_handler (line 41) | def signal_handler(self, sig: int, frame: FrameType | None) -> None: ...
method run (line 50) | def run(self) -> None:
method pause (line 63) | def pause(self) -> None:
method __iter__ (line 67) | def __iter__(self) -> Iterator[list[Path] | None]:
method __next__ (line 70) | def __next__(self) -> list[Path] | None:
method startup (line 73) | def startup(self) -> None:
method restart (line 87) | def restart(self) -> None:
method shutdown (line 103) | def shutdown(self) -> None:
method should_restart (line 117) | def should_restart(self) -> list[Path] | None:
function _display_path (line 121) | def _display_path(path: Path) -> str:
FILE: uvicorn/supervisors/multiprocess.py
class Process (line 26) | class Process:
method __init__ (line 27) | def __init__(
method ping (line 38) | def ping(self, timeout: float = 5) -> bool:
method pong (line 45) | def pong(self) -> None:
method always_pong (line 49) | def always_pong(self) -> None:
method target (line 53) | def target(self, sockets: list[socket] | None = None) -> Any: # pragm...
method is_alive (line 66) | def is_alive(self, timeout: float = 5) -> bool:
method start (line 72) | def start(self) -> None:
method terminate (line 75) | def terminate(self) -> None:
method kill (line 89) | def kill(self) -> None:
method join (line 94) | def join(self) -> None:
method pid (line 99) | def pid(self) -> int | None:
class Multiprocess (line 103) | class Multiprocess:
method __init__ (line 104) | def __init__(
method init_processes (line 123) | def init_processes(self) -> None:
method terminate_all (line 129) | def terminate_all(self) -> None:
method join_all (line 133) | def join_all(self) -> None:
method restart_all (line 137) | def restart_all(self) -> None:
method run (line 145) | def run(self) -> None:
method keep_subprocess_alive (line 163) | def keep_subprocess_alive(self) -> None:
method handle_signals (line 182) | def handle_signals(self) -> None:
method handle_int (line 192) | def handle_int(self) -> None:
method handle_term (line 196) | def handle_term(self) -> None:
method handle_break (line 200) | def handle_break(self) -> None: # pragma: py-not-win32
method handle_hup (line 204) | def handle_hup(self) -> None: # pragma: py-win32
method handle_ttin (line 208) | def handle_ttin(self) -> None: # pragma: py-win32
method handle_ttou (line 215) | def handle_ttou(self) -> None: # pragma: py-win32
FILE: uvicorn/supervisors/statreload.py
class StatReload (line 14) | class StatReload(BaseReload):
method __init__ (line 15) | def __init__(
method should_restart (line 28) | def should_restart(self) -> list[Path] | None:
method restart (line 45) | def restart(self) -> None:
method iter_py_files (line 49) | def iter_py_files(self) -> Iterator[Path]:
FILE: uvicorn/supervisors/watchfilesreload.py
class FileFilter (line 13) | class FileFilter:
method __init__ (line 14) | def __init__(self, config: Config):
method __call__ (line 37) | def __call__(self, path: Path) -> bool:
class WatchFilesReload (line 55) | class WatchFilesReload(BaseReload):
method __init__ (line 56) | def __init__(
method should_restart (line 79) | def should_restart(self) -> list[Path] | None:
FILE: uvicorn/workers.py
class UvicornWorker (line 24) | class UvicornWorker(Worker):
method __init__ (line 32) | def __init__(self, *args: Any, **kwargs: Any) -> None:
method init_signals (line 74) | def init_signals(self) -> None:
method _install_sigquit_handler (line 85) | def _install_sigquit_handler(self) -> None:
method _serve (line 95) | async def _serve(self) -> None:
method run (line 103) | def run(self) -> None:
method callback_notify (line 106) | async def callback_notify(self) -> None:
class UvicornH11Worker (line 110) | class UvicornH11Worker(UvicornWorker):
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (615K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/1-issue.yml",
"chars": 1928,
"preview": "name: Issue\ndescription: Report a bug or unexpected behavior. 🙏\n\nbody:\n - type: markdown\n attributes:\n value: "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 440,
"preview": "# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#con"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 539,
"preview": "<!-- Thanks for contributing to Uvicorn! 💚\nGiven this is a project maintained by volunteers, please read this template t"
},
{
"path": ".github/dependabot.yml",
"chars": 341,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"uv\"\n directory: \"/\"\n schedule:\n interval: \"monthly\"\n groups:\n "
},
{
"path": ".github/workflows/benchmark.yml",
"chars": 772,
"preview": "---\nname: CodSpeed\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"main\"]\n\npermissions:\n id-token: "
},
{
"path": ".github/workflows/main.yml",
"chars": 1531,
"preview": "---\nname: Test Suite\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"main\"]\n\njobs:\n tests:\n name"
},
{
"path": ".github/workflows/publish.yml",
"chars": 3166,
"preview": "name: Publish\n\non:\n push:\n tags:\n - '*'\n workflow_dispatch:\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n st"
},
{
"path": ".gitignore",
"chars": 111,
"preview": ".cache\n.coverage\n.coverage.*\n.mypy_cache/\n__pycache__/\nuvicorn.egg-info/\nvenv/\nhtmlcov/\nsite/\ndist/\n.codspeed/\n"
},
{
"path": "CITATION.cff",
"chars": 628,
"preview": "# This CITATION.cff file was generated with cffinit.\n# Visit https://bit.ly/cffinit to generate yours today!\n\ncff-versio"
},
{
"path": "LICENSE.md",
"chars": 1525,
"preview": "Copyright © 2017-present, [Encode OSS Ltd](https://www.encode.io/).\nAll rights reserved.\n\nRedistribution and use in sour"
},
{
"path": "README.md",
"chars": 4890,
"preview": "<p align=\"center\">\n <img width=\"320\" height=\"320\" src=\"https://raw.githubusercontent.com/tomchristie/uvicorn/main/docs/"
},
{
"path": "docs/CNAME",
"chars": 16,
"preview": "www.uvicorn.org\n"
},
{
"path": "docs/concepts/asgi.md",
"chars": 8150,
"preview": "## ASGI\n\n**Uvicorn** uses the [ASGI specification](https://asgi.readthedocs.io/en/latest/) for interacting with an appli"
},
{
"path": "docs/concepts/event-loop.md",
"chars": 2582,
"preview": "# Event Loop\n\nUvicorn provides two event loop implementations that you can choose from using the [`--loop`](../settings."
},
{
"path": "docs/concepts/lifespan.md",
"chars": 4244,
"preview": "Since Uvicorn is an ASGI server, it supports the\n[ASGI lifespan protocol](https://asgi.readthedocs.io/en/latest/specs/li"
},
{
"path": "docs/concepts/websockets.md",
"chars": 4252,
"preview": "**Uvicorn** supports the WebSocket protocol as defined in [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455).\n\n##"
},
{
"path": "docs/contributing.md",
"chars": 5867,
"preview": "# Contributing\n\nThank you for being interested in contributing to Uvicorn.\nThere are many ways you can contribute to the"
},
{
"path": "docs/deployment/docker.md",
"chars": 4499,
"preview": "# Dockerfile\n\n**Docker** is a popular choice for modern application deployment. However, creating a good Dockerfile from"
},
{
"path": "docs/deployment/index.md",
"chars": 11526,
"preview": "Server deployment is a complex area, that will depend on what kind of service you're deploying Uvicorn onto.\n\nAs a gener"
},
{
"path": "docs/index.md",
"chars": 5205,
"preview": "<style>\n .md-typeset h1,\n .md-content__button {\n display: none;\n }\n</style>\n\n<p align=\"center\">\n <img width=\"320\""
},
{
"path": "docs/installation.md",
"chars": 2673,
"preview": "**Uvicorn** is available on [PyPI](https://pypi.org/project/uvicorn/) so installation is as simple as:\n\n=== \"pip\"\n\n `"
},
{
"path": "docs/overrides/main.html",
"chars": 408,
"preview": "{% extends \"base.html\" %}\n\n{% block extrahead %}\n {{ super() }}\n <script>\n // Redirect starlette.io to starlette.de"
},
{
"path": "docs/overrides/partials/nav.html",
"chars": 1291,
"preview": "{% import \"partials/nav-item.html\" as item with context %}\n\n<!-- Determine class according to configuration -->\n {% set "
},
{
"path": "docs/overrides/partials/toc-item.html",
"chars": 655,
"preview": "<!-- Copied from https://github.com/squidfunk/mkdocs-material/issues/4827#issuecomment-1869812019 -->\n<li class=\"md-nav_"
},
{
"path": "docs/plugins/main.py",
"chars": 1438,
"preview": "from __future__ import annotations as _annotations\n\nimport re\nimport subprocess\nfrom functools import lru_cache\n\nfrom mk"
},
{
"path": "docs/release-notes.md",
"chars": 20755,
"preview": "---\ntoc_depth: 2\n---\n\n## 0.42.0 (March 16, 2026)\n\n### Changed\n\n* Use `bytearray` for request body accumulation to avoid "
},
{
"path": "docs/server-behavior.md",
"chars": 6770,
"preview": "# Server Behavior\n\nUvicorn is designed with particular attention to connection and resource management, in order to prov"
},
{
"path": "docs/settings.md",
"chars": 11173,
"preview": "# Settings\n\nUse the following options to configure Uvicorn, when running from the command line.\n\n## Configuration Method"
},
{
"path": "docs/sponsorship.md",
"chars": 11199,
"preview": "# ✨ Sponsor Starlette & Uvicorn ✨\n\nThank you for your interest in sponsoring Starlette and Uvicorn! ❤️\n\nYour support *di"
},
{
"path": "mkdocs.yml",
"chars": 3862,
"preview": "site_name: Uvicorn\nsite_description: The lightning-fast ASGI server.\nsite_url: https://uvicorn.dev\n\nrepo_name: Kludex/uv"
},
{
"path": "pyproject.toml",
"chars": 5100,
"preview": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"uvicorn\"\ndynamic = [\"versio"
},
{
"path": "scripts/build",
"chars": 77,
"preview": "#!/bin/sh -e\n\nset -x\n\nuv build\nuv run twine check dist/*\nuv run mkdocs build\n"
},
{
"path": "scripts/check",
"chars": 188,
"preview": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\n./scripts/sync-version\nuv run ruff format --check --diff $SOU"
},
{
"path": "scripts/coverage",
"chars": 106,
"preview": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\nuv run coverage combine\nuv run coverage report\n"
},
{
"path": "scripts/docs",
"chars": 41,
"preview": "#!/bin/sh -e\n\nset -x\n\nuv run mkdocs \"$@\"\n"
},
{
"path": "scripts/install",
"chars": 39,
"preview": "#!/bin/sh -e\n\nset -x\n\nuv sync --frozen\n"
},
{
"path": "scripts/lint",
"chars": 130,
"preview": "#!/bin/sh -e\n\nexport SOURCE_FILES=\"uvicorn tests\"\n\nset -x\n\nuv run ruff format $SOURCE_FILES\nuv run ruff check --fix $SOU"
},
{
"path": "scripts/sync-version",
"chars": 398,
"preview": "#!/bin/sh -e\n\nSEMVER_REGEX=\"([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*))?(\\+[0-9A-Za-z-]+)?\"\nCHANGEL"
},
{
"path": "scripts/test",
"chars": 234,
"preview": "#!/bin/sh\n\nset -ex\n\nif [ -z $GITHUB_ACTIONS ]; then\n scripts/check\nfi\n\nexport COVERAGE_PROCESS_START=$(pwd)/pyproject"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/benchmarks/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/benchmarks/http.py",
"chars": 4901,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\nfrom typing import TYPE_CHECKING"
},
{
"path": "tests/benchmarks/test_http.py",
"chars": 4229,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport pytest\n\nfrom tests.benchmarks.http import ("
},
{
"path": "tests/benchmarks/test_ws.py",
"chars": 2166,
"preview": "from __future__ import annotations\n\nimport importlib.util\nfrom typing import TYPE_CHECKING\n\nimport pytest\n\nfrom tests.be"
},
{
"path": "tests/benchmarks/ws.py",
"chars": 1430,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, TypeAlias\n\nfrom tests.benchmarks.http import "
},
{
"path": "tests/conftest.py",
"chars": 7865,
"preview": "from __future__ import annotations\n\nimport contextlib\nimport importlib.util\nimport os\nimport socket\nimport ssl\nfrom copy"
},
{
"path": "tests/custom_loop_utils.py",
"chars": 107,
"preview": "from __future__ import annotations\n\nimport asyncio\n\n\nclass CustomLoop(asyncio.SelectorEventLoop):\n pass\n"
},
{
"path": "tests/importer/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/importer/circular_import_a.py",
"chars": 101,
"preview": "# Used by test_importer.py\nfrom .circular_import_b import foo # noqa\n\nbar = 123 # pragma: no cover\n"
},
{
"path": "tests/importer/circular_import_b.py",
"chars": 101,
"preview": "# Used by test_importer.py\nfrom .circular_import_a import bar # noqa\n\nfoo = 123 # pragma: no cover\n"
},
{
"path": "tests/importer/raise_import_error.py",
"chars": 72,
"preview": "# Used by test_importer.py\n\nmyattr = 123\n\nimport does_not_exist # noqa\n"
},
{
"path": "tests/importer/test_importer.py",
"chars": 1758,
"preview": "import pytest\n\nfrom uvicorn.importer import ImportFromStringError, import_from_string\n\n\ndef test_invalid_format() -> Non"
},
{
"path": "tests/middleware/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/middleware/test_logging.py",
"chars": 9146,
"preview": "from __future__ import annotations\n\nimport contextlib\nimport logging\nimport socket\nimport sys\nfrom collections.abc impor"
},
{
"path": "tests/middleware/test_message_logger.py",
"chars": 2714,
"preview": "import httpx\nimport pytest\n\nfrom tests.middleware.test_logging import caplog_for_logger\nfrom uvicorn._types import ASGIR"
},
{
"path": "tests/middleware/test_proxy_headers.py",
"chars": 21309,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nimport httpx\nimport httpx._transports.asgi\nimport "
},
{
"path": "tests/middleware/test_wsgi.py",
"chars": 4828,
"preview": "from __future__ import annotations\n\nimport io\nimport sys\nfrom collections.abc import AsyncGenerator, Callable\n\nimport a2"
},
{
"path": "tests/protocols/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/protocols/test_http.py",
"chars": 43717,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport socket\nimport threading\nimport time\nfrom collec"
},
{
"path": "tests/protocols/test_utils.py",
"chars": 3148,
"preview": "from __future__ import annotations\n\nimport socket\nfrom asyncio import Transport\nfrom typing import Any\n\nimport pytest\n\nf"
},
{
"path": "tests/protocols/test_websocket.py",
"chars": 49142,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom copy import deepcopy\nfrom typing import TYPE_CHECKING, Any, Type"
},
{
"path": "tests/response.py",
"chars": 1459,
"preview": "class Response:\n charset = \"utf-8\"\n\n def __init__(self, content, status_code=200, headers=None, media_type=None):\n"
},
{
"path": "tests/supervisors/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/supervisors/test_multiprocess.py",
"chars": 5885,
"preview": "from __future__ import annotations\n\nimport functools\nimport os\nimport signal\nimport socket\nimport threading\nimport time\n"
},
{
"path": "tests/supervisors/test_reload.py",
"chars": 14647,
"preview": "from __future__ import annotations\n\nimport signal\nimport socket\nimport sys\nfrom collections.abc import Callable, Generat"
},
{
"path": "tests/supervisors/test_signal.py",
"chars": 4183,
"preview": "import asyncio\nimport signal\nfrom asyncio import Event\n\nimport httpx\nimport pytest\n\nfrom tests.utils import assert_signa"
},
{
"path": "tests/test_auto_detection.py",
"chars": 1706,
"preview": "import asyncio\nimport contextlib\nimport importlib\n\nimport pytest\n\nfrom uvicorn.config import Config\nfrom uvicorn.loops.a"
},
{
"path": "tests/test_cli.py",
"chars": 6229,
"preview": "import contextlib\nimport importlib\nimport os\nimport platform\nimport sys\nfrom collections.abc import Iterator\nfrom pathli"
},
{
"path": "tests/test_compat.py",
"chars": 913,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom asyncio import AbstractEventLoop\n\nimport pytest\n\nfrom tests.cust"
},
{
"path": "tests/test_config.py",
"chars": 20552,
"preview": "from __future__ import annotations\n\nimport configparser\nimport io\nimport json\nimport logging\nimport os\nimport socket\nimp"
},
{
"path": "tests/test_default_headers.py",
"chars": 3317,
"preview": "from __future__ import annotations\n\nimport httpx\nimport pytest\n\nfrom tests.utils import run_server\nfrom uvicorn import C"
},
{
"path": "tests/test_lifespan.py",
"chars": 8073,
"preview": "import asyncio\n\nimport pytest\n\nfrom uvicorn.config import Config\nfrom uvicorn.lifespan.off import LifespanOff\nfrom uvico"
},
{
"path": "tests/test_main.py",
"chars": 4356,
"preview": "import importlib\nimport inspect\nimport socket\nfrom logging import WARNING\n\nimport httpx\nimport pytest\n\nimport uvicorn.se"
},
{
"path": "tests/test_server.py",
"chars": 9137,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport contextlib\nimport contextvars\nimport json\nimport logging\nimpor"
},
{
"path": "tests/test_ssl.py",
"chars": 3038,
"preview": "import httpx\nimport pytest\n\nfrom tests.utils import run_server\nfrom uvicorn.config import Config\n\n\nasync def app(scope, "
},
{
"path": "tests/test_subprocess.py",
"chars": 1280,
"preview": "from __future__ import annotations\n\nimport socket\nfrom unittest.mock import patch\n\nfrom uvicorn._subprocess import Spawn"
},
{
"path": "tests/utils.py",
"chars": 1573,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport os\nimport signal\nimport sys\nfrom collections.abc import AsyncI"
},
{
"path": "uvicorn/__init__.py",
"chars": 147,
"preview": "from uvicorn.config import Config\nfrom uvicorn.main import Server, main, run\n\n__version__ = \"0.42.0\"\n__all__ = [\"main\", "
},
{
"path": "uvicorn/__main__.py",
"chars": 62,
"preview": "import uvicorn\n\nif __name__ == \"__main__\":\n uvicorn.main()\n"
},
{
"path": "uvicorn/_compat.py",
"chars": 2916,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable, Coroutine\nfrom typin"
},
{
"path": "uvicorn/_subprocess.py",
"chars": 2776,
"preview": "\"\"\"\nSome light wrappers around Python's multiprocessing, to deal with cleanly\nstarting child processes.\n\"\"\"\n\nfrom __futu"
},
{
"path": "uvicorn/_types.py",
"chars": 7677,
"preview": "\"\"\"\nCopyright (c) Django Software Foundation and individual contributors.\nAll rights reserved.\n\nRedistribution and use i"
},
{
"path": "uvicorn/config.py",
"chars": 22095,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport inspect\nimport json\nimport logging\nimport logging.config\nimpor"
},
{
"path": "uvicorn/importer.py",
"chars": 1128,
"preview": "import importlib\nfrom typing import Any\n\n\nclass ImportFromStringError(Exception):\n pass\n\n\ndef import_from_string(impo"
},
{
"path": "uvicorn/lifespan/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/lifespan/off.py",
"chars": 332,
"preview": "from __future__ import annotations\n\nfrom typing import Any\n\nfrom uvicorn import Config\n\n\nclass LifespanOff:\n def __in"
},
{
"path": "uvicorn/lifespan/on.py",
"chars": 5173,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nfrom asyncio import Queue\nfrom typing import Any\n\nfrom"
},
{
"path": "uvicorn/logging.py",
"chars": 4235,
"preview": "from __future__ import annotations\n\nimport http\nimport logging\nimport sys\nfrom copy import copy\nfrom typing import Liter"
},
{
"path": "uvicorn/loops/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/loops/asyncio.py",
"chars": 333,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport sys\nfrom collections.abc import Callable\n\n\ndef asyncio_loop_fa"
},
{
"path": "uvicorn/loops/auto.py",
"chars": 566,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\n\ndef auto_loop_factory(use_subp"
},
{
"path": "uvicorn/loops/uvloop.py",
"chars": 236,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\nimport uvloop\n\n\ndef uvloop_loop"
},
{
"path": "uvicorn/main.py",
"chars": 18183,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport os\nimport platform\nimport ssl\nimport sys\nimport"
},
{
"path": "uvicorn/middleware/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/middleware/asgi2.py",
"chars": 394,
"preview": "from uvicorn._types import (\n ASGI2Application,\n ASGIReceiveCallable,\n ASGISendCallable,\n Scope,\n)\n\n\nclass A"
},
{
"path": "uvicorn/middleware/message_logger.py",
"chars": 2859,
"preview": "import logging\nfrom typing import Any\n\nfrom uvicorn._types import (\n ASGI3Application,\n ASGIReceiveCallable,\n A"
},
{
"path": "uvicorn/middleware/proxy_headers.py",
"chars": 5790,
"preview": "from __future__ import annotations\n\nimport ipaddress\n\nfrom uvicorn._types import ASGI3Application, ASGIReceiveCallable, "
},
{
"path": "uvicorn/middleware/wsgi.py",
"chars": 7125,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport concurrent.futures\nimport io\nimport sys\nimport warnings\nfrom c"
},
{
"path": "uvicorn/protocols/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/protocols/http/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/protocols/http/auto.py",
"chars": 403,
"preview": "from __future__ import annotations\n\nimport asyncio\n\nAutoHTTPProtocol: type[asyncio.Protocol]\ntry:\n import httptools "
},
{
"path": "uvicorn/protocols/http/flow_control.py",
"chars": 1701,
"preview": "import asyncio\n\nfrom uvicorn._types import ASGIReceiveCallable, ASGISendCallable, Scope\n\nCLOSE_HEADER = (b\"connection\", "
},
{
"path": "uvicorn/protocols/http/h11_impl.py",
"chars": 21225,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport contextvars\nimport http\nimport logging\nfrom collections.abc im"
},
{
"path": "uvicorn/protocols/http/httptools_impl.py",
"chars": 22322,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport contextvars\nimport http\nimport logging\nimport re\nimport urllib"
},
{
"path": "uvicorn/protocols/utils.py",
"chars": 2058,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport socket\nimport urllib.parse\n\nfrom uvicorn._types import WWWScop"
},
{
"path": "uvicorn/protocols/websockets/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "uvicorn/protocols/websockets/auto.py",
"chars": 590,
"preview": "from __future__ import annotations\n\nimport asyncio\nfrom collections.abc import Callable\n\nAutoWebSocketsProtocol: Callabl"
},
{
"path": "uvicorn/protocols/websockets/websockets_impl.py",
"chars": 15419,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport http\nimport logging\nfrom collections.abc import Sequence\nfrom "
},
{
"path": "uvicorn/protocols/websockets/websockets_sansio_impl.py",
"chars": 17221,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport sys\nfrom asyncio.transports import BaseTranspor"
},
{
"path": "uvicorn/protocols/websockets/wsproto_impl.py",
"chars": 15273,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nfrom typing import Any, Literal, cast\nfrom urllib.pars"
},
{
"path": "uvicorn/py.typed",
"chars": 1,
"preview": "\n"
},
{
"path": "uvicorn/server.py",
"chars": 13368,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport contextlib\nimport functools\nimport logging\nimport os\nimport pl"
},
{
"path": "uvicorn/supervisors/__init__.py",
"chars": 507,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom uvicorn.supervisors.basereload import BaseRel"
},
{
"path": "uvicorn/supervisors/basereload.py",
"chars": 4018,
"preview": "from __future__ import annotations\n\nimport logging\nimport os\nimport signal\nimport sys\nimport threading\nfrom collections."
},
{
"path": "uvicorn/supervisors/multiprocess.py",
"chars": 7580,
"preview": "from __future__ import annotations\n\nimport logging\nimport os\nimport signal\nimport threading\nfrom collections.abc import "
},
{
"path": "uvicorn/supervisors/statreload.py",
"chars": 1548,
"preview": "from __future__ import annotations\n\nimport logging\nfrom collections.abc import Callable, Iterator\nfrom pathlib import Pa"
},
{
"path": "uvicorn/supervisors/watchfilesreload.py",
"chars": 2923,
"preview": "from __future__ import annotations\n\nfrom collections.abc import Callable\nfrom pathlib import Path\nfrom socket import soc"
},
{
"path": "uvicorn/workers.py",
"chars": 3873,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport signal\nimport sys\nimport warnings\nfrom typing i"
}
]
About this extraction
This page contains the full source code of the Kludex/uvicorn GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (570.2 KB), approximately 141.3k tokens, and a symbol index with 719 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.