starlette-0.46.1/mkdocs.yml 0000644 0000000 0000000 00000005151 13615410400 012536 0 ustar 00 site_name: Starlette
site_description: The little ASGI library that shines.
site_url: https://www.starlette.io
theme:
name: "material"
custom_dir: docs/overrides
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.copy
- toc.follow
repo_name: encode/starlette
repo_url: https://github.com/encode/starlette
edit_uri: edit/master/docs/
nav:
- Introduction: "index.md"
- Features:
- Applications: "applications.md"
- Requests: "requests.md"
- Responses: "responses.md"
- WebSockets: "websockets.md"
- Routing: "routing.md"
- Endpoints: "endpoints.md"
- Middleware: "middleware.md"
- Static Files: "staticfiles.md"
- Templates: "templates.md"
- Database: "database.md"
- GraphQL: "graphql.md"
- Authentication: "authentication.md"
- API Schemas: "schemas.md"
- Lifespan: "lifespan.md"
- Background Tasks: "background.md"
- Server Push: "server-push.md"
- Exceptions: "exceptions.md"
- Configuration: "config.md"
- Thread Pool: "threadpool.md"
- Test Client: "testclient.md"
- Release Notes: "release-notes.md"
- Community:
- Third Party Packages: "third-party-packages.md"
- Contributing: "contributing.md"
- Sponsorship: "sponsorship.md"
markdown_extensions:
- attr_list
- admonition
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.details
- 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
watch:
- starlette
plugins:
- search
- mkdocstrings:
handlers:
python:
options:
docstring_section_style: list
show_root_toc_entry: false
members_order: source
separate_signature: true
filters: ["!^_"]
docstring_options:
ignore_init_summary: true
merge_init_into_class: true
parameter_headings: true
show_signature_annotations: true
show_source: false
signature_crossrefs: true
import:
- url: https://docs.python.org/3/objects.inv
starlette-0.46.1/requirements.txt 0000644 0000000 0000000 00000000572 13615410400 014021 0 ustar 00 # Optionals
-e .[full]
# Testing
coverage==7.6.12
importlib-metadata==8.6.1
mypy==1.15.0
ruff==0.9.9
typing_extensions==4.12.2
types-contextvars==2.4.7.3
types-PyYAML==6.0.12.20241230
types-dataclasses==0.6.6
pytest==8.3.4
trio==0.29.0
# Documentation
black==25.1.0
mkdocs==1.6.1
mkdocs-material==9.6.6
mkdocstrings-python==1.16.2
# Packaging
build==1.2.2.post1
twine==6.1.0
starlette-0.46.1/.github/FUNDING.yml 0000644 0000000 0000000 00000000017 13615410400 013704 0 ustar 00 github: Kludex
starlette-0.46.1/.github/dependabot.yml 0000644 0000000 0000000 00000000422 13615410400 014717 0 ustar 00 version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
groups:
python-packages:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
starlette-0.46.1/.github/pull_request_template.md 0000644 0000000 0000000 00000001043 13615410400 017030 0 ustar 00
# Summary
# 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.
starlette-0.46.1/.github/ISSUE_TEMPLATE/1-issue.md 0000644 0000000 0000000 00000001120 13615410400 016056 0 ustar 00 ---
name: Issue
about: Please only raise an issue if you've been advised to do so after discussion. Thanks! π
---
The starting point for issues should usually be a discussion...
https://github.com/encode/starlette/discussions
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.
This will help us ensure that the "Issues" list properly reflects ongoing or needed work on the project.
---
- [ ] Initially raised as discussion #...
starlette-0.46.1/.github/ISSUE_TEMPLATE/config.yml 0000644 0000000 0000000 00000000657 13615410400 016254 0 ustar 00 # 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/encode/starlette/discussions
about: >
The "Discussions" forum is where you want to start. π
- name: Chat
url: https://discord.gg/SWU73HffbV
about: >
Our community chat forum.
starlette-0.46.1/.github/workflows/publish.yml 0000644 0000000 0000000 00000001140 13615410400 016313 0 ustar 00 name: Publish
on:
push:
tags:
- '*'
jobs:
publish:
name: "Publish release"
runs-on: "ubuntu-latest"
environment:
name: deploy
steps:
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v5"
with:
python-version: "3.11"
- name: "Install dependencies"
run: "scripts/install"
- name: "Build package & docs"
run: "scripts/build"
- name: "Publish to PyPI & deploy docs"
run: "scripts/publish"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
starlette-0.46.1/.github/workflows/test-suite.yml 0000644 0000000 0000000 00000001434 13615410400 016761 0 ustar 00 ---
name: Test Suite
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
tests:
name: "Python ${{ matrix.python-version }}"
runs-on: "ubuntu-latest"
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: "actions/checkout@v4"
- uses: "actions/setup-python@v5"
with:
python-version: "${{ matrix.python-version }}"
allow-prereleases: true
- name: "Install dependencies"
run: "scripts/install"
- name: "Run linting checks"
run: "scripts/check"
- name: "Build package & docs"
run: "scripts/build"
- name: "Run tests"
run: "scripts/test"
- name: "Enforce coverage"
run: "scripts/coverage"
starlette-0.46.1/docs/CNAME 0000644 0000000 0000000 00000000021 13615410400 012220 0 ustar 00 www.starlette.io
starlette-0.46.1/docs/applications.md 0000644 0000000 0000000 00000003372 13615410400 014476 0 ustar 00
??? abstract "API Reference"
::: starlette.applications.Starlette
options:
parameter_headings: false
show_root_heading: true
heading_level: 3
filters:
- "__init__"
Starlette includes an application class `Starlette` that nicely ties together all of
its other functionality.
```python
from contextlib import asynccontextmanager
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route, Mount, WebSocketRoute
from starlette.staticfiles import StaticFiles
def homepage(request):
return PlainTextResponse('Hello, world!')
def user_me(request):
username = "John Doe"
return PlainTextResponse('Hello, %s!' % username)
def user(request):
username = request.path_params['username']
return PlainTextResponse('Hello, %s!' % username)
async def websocket_endpoint(websocket):
await websocket.accept()
await websocket.send_text('Hello, websocket!')
await websocket.close()
@asynccontextmanager
async def lifespan(app):
print('Startup')
yield
print('Shutdown')
routes = [
Route('/', homepage),
Route('/user/me', user_me),
Route('/user/{username}', user),
WebSocketRoute('/ws', websocket_endpoint),
Mount('/static', StaticFiles(directory="static")),
]
app = Starlette(debug=True, routes=routes, lifespan=lifespan)
```
### Storing state on the app instance
You can store arbitrary extra state on the application instance, using the
generic `app.state` attribute.
For example:
```python
app.state.ADMIN_EMAIL = 'admin@example.org'
```
### Accessing the app instance
Where a `request` is available (i.e. endpoints and middleware), the app is available on `request.app`.
starlette-0.46.1/docs/authentication.md 0000644 0000000 0000000 00000012546 13615410400 015032 0 ustar 00 Starlette offers a simple but powerful interface for handling authentication
and permissions. Once you've installed `AuthenticationMiddleware` with an
appropriate authentication backend the `request.user` and `request.auth`
interfaces will be available in your endpoints.
```python
from starlette.applications import Starlette
from starlette.authentication import (
AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import base64
import binascii
class BasicAuthBackend(AuthenticationBackend):
async def authenticate(self, conn):
if "Authorization" not in conn.headers:
return
auth = conn.headers["Authorization"]
try:
scheme, credentials = auth.split()
if scheme.lower() != 'basic':
return
decoded = base64.b64decode(credentials).decode("ascii")
except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
raise AuthenticationError('Invalid basic auth credentials')
username, _, password = decoded.partition(":")
# TODO: You'd want to verify the username and password here.
return AuthCredentials(["authenticated"]), SimpleUser(username)
async def homepage(request):
if request.user.is_authenticated:
return PlainTextResponse('Hello, ' + request.user.display_name)
return PlainTextResponse('Hello, you')
routes = [
Route("/", endpoint=homepage)
]
middleware = [
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]
app = Starlette(routes=routes, middleware=middleware)
```
## Users
Once `AuthenticationMiddleware` is installed the `request.user` interface
will be available to endpoints or other middleware.
This interface should subclass `BaseUser`, which provides two properties,
as well as whatever other information your user model includes.
* `.is_authenticated`
* `.display_name`
Starlette provides two built-in user implementations: `UnauthenticatedUser()`,
and `SimpleUser(username)`.
## AuthCredentials
It is important that authentication credentials are treated as separate concept
from users. An authentication scheme should be able to restrict or grant
particular privileges independently of the user identity.
The `AuthCredentials` class provides the basic interface that `request.auth`
exposes:
* `.scopes`
## Permissions
Permissions are implemented as an endpoint decorator, that enforces that the
incoming request includes the required authentication scopes.
```python
from starlette.authentication import requires
@requires('authenticated')
async def dashboard(request):
...
```
You can include either one or multiple required scopes:
```python
from starlette.authentication import requires
@requires(['authenticated', 'admin'])
async def dashboard(request):
...
```
By default 403 responses will be returned when permissions are not granted.
In some cases you might want to customize this, for example to hide information
about the URL layout from unauthenticated users.
```python
from starlette.authentication import requires
@requires(['authenticated', 'admin'], status_code=404)
async def dashboard(request):
...
```
!!! note
The `status_code` parameter is not supported with WebSockets. The 403 (Forbidden)
status code will always be used for those.
Alternatively you might want to redirect unauthenticated users to a different
page.
```python
from starlette.authentication import requires
async def homepage(request):
...
@requires('authenticated', redirect='homepage')
async def dashboard(request):
...
```
When redirecting users, the page you redirect them to will include URL they originally requested at the `next` query param:
```python
from starlette.authentication import requires
from starlette.responses import RedirectResponse
@requires('authenticated', redirect='login')
async def admin(request):
...
async def login(request):
if request.method == "POST":
# Now that the user is authenticated,
# we can send them to their original request destination
if request.user.is_authenticated:
next_url = request.query_params.get("next")
if next_url:
return RedirectResponse(next_url)
return RedirectResponse("/")
```
For class-based endpoints, you should wrap the decorator
around a method on the class.
```python
from starlette.authentication import requires
from starlette.endpoints import HTTPEndpoint
class Dashboard(HTTPEndpoint):
@requires("authenticated")
async def get(self, request):
...
```
## Custom authentication error responses
You can customise the error response sent when a `AuthenticationError` is
raised by an auth backend:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
def on_auth_error(request: Request, exc: Exception):
return JSONResponse({"error": str(exc)}, status_code=401)
app = Starlette(
middleware=[
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend(), on_error=on_auth_error),
],
)
```
starlette-0.46.1/docs/background.md 0000644 0000000 0000000 00000003651 13615410400 014127 0 ustar 00
Starlette includes a `BackgroundTask` class for in-process background tasks.
A background task should be attached to a response, and will run only once
the response has been sent.
### Background Task
Used to add a single background task to a response.
Signature: `BackgroundTask(func, *args, **kwargs)`
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette.background import BackgroundTask
...
async def signup(request):
data = await request.json()
username = data['username']
email = data['email']
task = BackgroundTask(send_welcome_email, to_address=email)
message = {'status': 'Signup successful'}
return JSONResponse(message, background=task)
async def send_welcome_email(to_address):
...
routes = [
...
Route('/user/signup', endpoint=signup, methods=['POST'])
]
app = Starlette(routes=routes)
```
### BackgroundTasks
Used to add multiple background tasks to a response.
Signature: `BackgroundTasks(tasks=[])`
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.background import BackgroundTasks
async def signup(request):
data = await request.json()
username = data['username']
email = data['email']
tasks = BackgroundTasks()
tasks.add_task(send_welcome_email, to_address=email)
tasks.add_task(send_admin_notification, username=username)
message = {'status': 'Signup successful'}
return JSONResponse(message, background=tasks)
async def send_welcome_email(to_address):
...
async def send_admin_notification(username):
...
routes = [
Route('/user/signup', endpoint=signup, methods=['POST'])
]
app = Starlette(routes=routes)
```
!!! important
The tasks are executed in order. In case one of the tasks raises
an exception, the following tasks will not get the opportunity to be executed.
starlette-0.46.1/docs/config.md 0000644 0000000 0000000 00000014726 13615410400 013262 0 ustar 00 Starlette encourages a strict separation of configuration from code,
following [the twelve-factor pattern][twelve-factor].
Configuration should be stored in environment variables, or in a `.env` file
that is not committed to source control.
```python title="main.py"
from sqlalchemy import create_engine
from starlette.applications import Starlette
from starlette.config import Config
from starlette.datastructures import CommaSeparatedStrings, Secret
# Config will be read from environment variables and/or ".env" files.
config = Config(".env")
DEBUG = config('DEBUG', cast=bool, default=False)
DATABASE_URL = config('DATABASE_URL')
SECRET_KEY = config('SECRET_KEY', cast=Secret)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)
app = Starlette(debug=DEBUG)
engine = create_engine(DATABASE_URL)
...
```
```shell title=".env"
# Don't commit this to source control.
# Eg. Include ".env" in your `.gitignore` file.
DEBUG=True
DATABASE_URL=postgresql://user:password@localhost:5432/database
SECRET_KEY=43n080musdfjt54t-09sdgr
ALLOWED_HOSTS=127.0.0.1, localhost
```
## Configuration precedence
The order in which configuration values are read is:
* From an environment variable.
* From the `.env` file.
* The default value given in `config`.
If none of those match, then `config(...)` will raise an error.
## Secrets
For sensitive keys, the `Secret` class is useful, since it helps minimize
occasions where the value it holds could leak out into tracebacks or
other code introspection.
To get the value of a `Secret` instance, you must explicitly cast it to a string.
You should only do this at the point at which the value is used.
```python
>>> from myproject import settings
>>> settings.SECRET_KEY
Secret('**********')
>>> str(settings.SECRET_KEY)
'98n349$%8b8-7yjn0n8y93T$23r'
```
!!! tip
You can use `DatabaseURL` from `databases`
package [here](https://github.com/encode/databases/blob/ab5eb718a78a27afe18775754e9c0fa2ad9cd211/databases/core.py#L420)
to store database URLs and avoid leaking them in the logs.
## CommaSeparatedStrings
For holding multiple inside a single config key, the `CommaSeparatedStrings`
type is useful.
```python
>>> from myproject import settings
>>> print(settings.ALLOWED_HOSTS)
CommaSeparatedStrings(['127.0.0.1', 'localhost'])
>>> print(list(settings.ALLOWED_HOSTS))
['127.0.0.1', 'localhost']
>>> print(len(settings.ALLOWED_HOSTS))
2
>>> print(settings.ALLOWED_HOSTS[0])
'127.0.0.1'
```
## Reading or modifying the environment
In some cases you might want to read or modify the environment variables programmatically.
This is particularly useful in testing, where you may want to override particular
keys in the environment.
Rather than reading or writing from `os.environ`, you should use Starlette's
`environ` instance. This instance is a mapping onto the standard `os.environ`
that additionally protects you by raising an error if any environment variable
is set *after* the point that it has already been read by the configuration.
If you're using `pytest`, then you can setup any initial environment in
`tests/conftest.py`.
```python title="tests/conftest.py"
from starlette.config import environ
environ['DEBUG'] = 'TRUE'
```
## Reading prefixed environment variables
You can namespace the environment variables by setting `env_prefix` argument.
```python title="myproject/settings.py"
import os
from starlette.config import Config
os.environ['APP_DEBUG'] = 'yes'
os.environ['ENVIRONMENT'] = 'dev'
config = Config(env_prefix='APP_')
DEBUG = config('DEBUG') # lookups APP_DEBUG, returns "yes"
ENVIRONMENT = config('ENVIRONMENT') # lookups APP_ENVIRONMENT, raises KeyError as variable is not defined
```
## A full example
Structuring large applications can be complex. You need proper separation of
configuration and code, database isolation during tests, separate test and
production databases, etc...
Here we'll take a look at a complete example, that demonstrates how
we can start to structure an application.
First, let's keep our settings, our database table definitions, and our
application logic separated:
```python title="myproject/settings.py"
from starlette.config import Config
from starlette.datastructures import Secret
config = Config(".env")
DEBUG = config('DEBUG', cast=bool, default=False)
SECRET_KEY = config('SECRET_KEY', cast=Secret)
DATABASE_URL = config('DATABASE_URL')
```
```python title="myproject/tables.py"
import sqlalchemy
# Database table definitions.
metadata = sqlalchemy.MetaData()
organisations = sqlalchemy.Table(
...
)
```
```python title="myproject/app.py"
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
from starlette.routing import Route
from myproject import settings
async def homepage(request):
...
routes = [
Route("/", endpoint=homepage)
]
middleware = [
Middleware(
SessionMiddleware,
secret_key=settings.SECRET_KEY,
)
]
app = Starlette(debug=settings.DEBUG, routes=routes, middleware=middleware)
```
Now let's deal with our test configuration.
We'd like to create a new test database every time the test suite runs,
and drop it once the tests complete. We'd also like to ensure
```python title="tests/conftest.py"
from starlette.config import environ
from starlette.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy_utils import create_database, database_exists, drop_database
# This line would raise an error if we use it after 'settings' has been imported.
environ['DEBUG'] = 'TRUE'
from myproject import settings
from myproject.app import app
from myproject.tables import metadata
@pytest.fixture(autouse=True, scope="session")
def setup_test_database():
"""
Create a clean test database every time the tests are run.
"""
url = settings.DATABASE_URL
engine = create_engine(url)
assert not database_exists(url), 'Test database already exists. Aborting tests.'
create_database(url) # Create the test database.
metadata.create_all(engine) # Create the tables.
yield # Run the tests.
drop_database(url) # Drop the test database.
@pytest.fixture()
def client():
"""
Make a 'client' fixture available to test cases.
"""
# Our fixture is created within a context manager. This ensures that
# application lifespan runs for every test case.
with TestClient(app) as test_client:
yield test_client
```
[twelve-factor]: https://12factor.net/config
starlette-0.46.1/docs/contributing.md 0000644 0000000 0000000 00000013114 13615410400 014512 0 ustar 00 # Contributing
Thank you for being interested in contributing to Starlette.
There are many ways you can contribute to the project:
- Try Starlette and [report bugs/issues you find](https://github.com/encode/starlette/issues/new)
- [Implement new features](https://github.com/encode/starlette/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
- [Review Pull Requests of others](https://github.com/encode/starlette/pulls)
- Write documentation
- Participate in discussions
## Reporting Bugs or Other Issues
Found something that Starlette should support?
Stumbled upon some unexpected behaviour?
Contributions should generally start out with [a discussion](https://github.com/encode/starlette/discussions).
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.
## Development
To start developing Starlette, create a **fork** of the
[Starlette repository](https://github.com/encode/starlette) 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/starlette
```
You can now install the project and its dependencies using:
```shell
$ cd starlette
$ 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_application.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
```
## 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.
Here are some common ways the test suite can fail:
### Check Job Failed
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, 435 passed, 1 skipped, 1 xfailed in 11.09s ===`
If tests succeed but coverage doesn't reach our current threshold, you will see this
message under the coverage report:
`FAIL Required test coverage of 100% not reached. Total coverage: 99.00%`
## Releasing
*This section is targeted at Starlette 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/encode/starlette/compare/) `master` 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 `__version__.py`.
For an example, see [#1600](https://github.com/encode/starlette/pull/1600).
Once the release PR is merged, create a
[new release](https://github.com/encode/starlette/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.
If something goes wrong with the PyPI job the release can be published using the
`scripts/publish` script.
starlette-0.46.1/docs/database.md 0000644 0000000 0000000 00000022452 13615410400 013554 0 ustar 00 Starlette is not strictly tied to any particular database implementation.
You can use it with an asynchronous ORM, such as [GINO](https://python-gino.org/),
or use regular non-async endpoints, and integrate with [SQLAlchemy](https://www.sqlalchemy.org/).
In this documentation we'll demonstrate how to integrate against [the `databases` package](https://github.com/encode/databases),
which provides SQLAlchemy core support against a range of different database drivers.
Here's a complete example, that includes table definitions, configuring a `database.Database`
instance, and a couple of endpoints that interact with the database.
```ini title=".env"
DATABASE_URL=sqlite:///test.db
```
```python title="app.py"
import contextlib
import databases
import sqlalchemy
from starlette.applications import Starlette
from starlette.config import Config
from starlette.responses import JSONResponse
from starlette.routing import Route
# Configuration from environment variables or '.env' file.
config = Config('.env')
DATABASE_URL = config('DATABASE_URL')
# Database table definitions.
metadata = sqlalchemy.MetaData()
notes = sqlalchemy.Table(
"notes",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("text", sqlalchemy.String),
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)
database = databases.Database(DATABASE_URL)
@contextlib.asynccontextmanager
async def lifespan(app):
await database.connect()
yield
await database.disconnect()
# Main application code.
async def list_notes(request):
query = notes.select()
results = await database.fetch_all(query)
content = [
{
"text": result["text"],
"completed": result["completed"]
}
for result in results
]
return JSONResponse(content)
async def add_note(request):
data = await request.json()
query = notes.insert().values(
text=data["text"],
completed=data["completed"]
)
await database.execute(query)
return JSONResponse({
"text": data["text"],
"completed": data["completed"]
})
routes = [
Route("/notes", endpoint=list_notes, methods=["GET"]),
Route("/notes", endpoint=add_note, methods=["POST"]),
]
app = Starlette(
routes=routes,
lifespan=lifespan,
)
```
Finally, you will need to create the database tables. It is recommended to use
Alembic, which we briefly go over in [Migrations](#migrations)
## Queries
Queries may be made with as [SQLAlchemy Core queries][sqlalchemy-core].
The following methods are supported:
* `rows = await database.fetch_all(query)`
* `row = await database.fetch_one(query)`
* `async for row in database.iterate(query)`
* `await database.execute(query)`
* `await database.execute_many(query)`
## Transactions
Database transactions are available either as a decorator, as a
context manager, or as a low-level API.
Using a decorator on an endpoint:
```python
@database.transaction()
async def populate_note(request):
# This database insert occurs within a transaction.
# It will be rolled back by the `RuntimeError`.
query = notes.insert().values(text="you won't see me", completed=True)
await database.execute(query)
raise RuntimeError()
```
Using a context manager:
```python
async def populate_note(request):
async with database.transaction():
# This database insert occurs within a transaction.
# It will be rolled back by the `RuntimeError`.
query = notes.insert().values(text="you won't see me", completed=True)
await request.database.execute(query)
raise RuntimeError()
```
Using the low-level API:
```python
async def populate_note(request):
transaction = await database.transaction()
try:
# This database insert occurs within a transaction.
# It will be rolled back by the `RuntimeError`.
query = notes.insert().values(text="you won't see me", completed=True)
await database.execute(query)
raise RuntimeError()
except:
await transaction.rollback()
raise
else:
await transaction.commit()
```
## Test isolation
There are a few things that we want to ensure when running tests against
a service that uses a database. Our requirements should be:
* Use a separate database for testing.
* Create a new test database every time we run the tests.
* Ensure that the database state is isolated between each test case.
Here's how we need to structure our application and tests in order to
meet those requirements:
```python
from starlette.applications import Starlette
from starlette.config import Config
import databases
config = Config(".env")
TESTING = config('TESTING', cast=bool, default=False)
DATABASE_URL = config('DATABASE_URL', cast=databases.DatabaseURL)
TEST_DATABASE_URL = DATABASE_URL.replace(database='test_' + DATABASE_URL.database)
# Use 'force_rollback' during testing, to ensure we do not persist database changes
# between each test case.
if TESTING:
database = databases.Database(TEST_DATABASE_URL, force_rollback=True)
else:
database = databases.Database(DATABASE_URL)
```
We still need to set `TESTING` during a test run, and setup the test database.
Assuming we're using `py.test`, here's how our `conftest.py` might look:
```python
import pytest
from starlette.config import environ
from starlette.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy_utils import database_exists, create_database, drop_database
# This sets `os.environ`, but provides some additional protection.
# If we placed it below the application import, it would raise an error
# informing us that 'TESTING' had already been read from the environment.
environ['TESTING'] = 'True'
import app
@pytest.fixture(scope="session", autouse=True)
def create_test_database():
"""
Create a clean database on every test case.
For safety, we should abort if a database already exists.
We use the `sqlalchemy_utils` package here for a few helpers in consistently
creating and dropping the database.
"""
url = str(app.TEST_DATABASE_URL)
engine = create_engine(url)
assert not database_exists(url), 'Test database already exists. Aborting tests.'
create_database(url) # Create the test database.
metadata.create_all(engine) # Create the tables.
yield # Run the tests.
drop_database(url) # Drop the test database.
@pytest.fixture()
def client():
"""
When using the 'client' fixture in test cases, we'll get full database
rollbacks between test cases:
def test_homepage(client):
url = app.url_path_for('homepage')
response = client.get(url)
assert response.status_code == 200
"""
with TestClient(app) as client:
yield client
```
## Migrations
You'll almost certainly need to be using database migrations in order to manage
incremental changes to the database. For this we'd strongly recommend
[Alembic][alembic], which is written by the author of SQLAlchemy.
```shell
$ pip install alembic
$ alembic init migrations
```
Now, you'll want to set things up so that Alembic references the configured
DATABASE_URL, and uses your table metadata.
In `alembic.ini` remove the following line:
```shell
sqlalchemy.url = driver://user:pass@localhost/dbname
```
In `migrations/env.py`, you need to set the ``'sqlalchemy.url'`` configuration key,
and the `target_metadata` variable. You'll want something like this:
```python
# The Alembic Config object.
config = context.config
# Configure Alembic to use our DATABASE_URL and our table definitions...
import app
config.set_main_option('sqlalchemy.url', str(app.DATABASE_URL))
target_metadata = app.metadata
...
```
Then, using our notes example above, create an initial revision:
```shell
alembic revision -m "Create notes table"
```
And populate the new file (within `migrations/versions`) with the necessary directives:
```python
def upgrade():
op.create_table(
'notes',
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("text", sqlalchemy.String),
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)
def downgrade():
op.drop_table('notes')
```
And run your first migration. Our notes app can now run!
```shell
alembic upgrade head
```
**Running migrations during testing**
It is good practice to ensure that your test suite runs the database migrations
every time it creates the test database. This will help catch any issues in your
migration scripts, and will help ensure that the tests are running against
a database that's in a consistent state with your live database.
We can adjust the `create_test_database` fixture slightly:
```python
from alembic import command
from alembic.config import Config
import app
...
@pytest.fixture(scope="session", autouse=True)
def create_test_database():
url = str(app.DATABASE_URL)
engine = create_engine(url)
assert not database_exists(url), 'Test database already exists. Aborting tests.'
create_database(url) # Create the test database.
config = Config("alembic.ini") # Run the migrations.
command.upgrade(config, "head")
yield # Run the tests.
drop_database(url) # Drop the test database.
```
[sqlalchemy-core]: https://docs.sqlalchemy.org/en/latest/core/
[alembic]: https://alembic.sqlalchemy.org/en/latest/
starlette-0.46.1/docs/endpoints.md 0000644 0000000 0000000 00000010077 13615410400 014013 0 ustar 00
Starlette includes the classes `HTTPEndpoint` and `WebSocketEndpoint` that provide a class-based view pattern for
handling HTTP method dispatching and WebSocket sessions.
### HTTPEndpoint
The `HTTPEndpoint` class can be used as an ASGI application:
```python
from starlette.responses import PlainTextResponse
from starlette.endpoints import HTTPEndpoint
class App(HTTPEndpoint):
async def get(self, request):
return PlainTextResponse(f"Hello, world!")
```
If you're using a Starlette application instance to handle routing, you can
dispatch to an `HTTPEndpoint` class. Make sure to dispatch to the class itself,
rather than to an instance of the class:
```python
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.endpoints import HTTPEndpoint
from starlette.routing import Route
class Homepage(HTTPEndpoint):
async def get(self, request):
return PlainTextResponse(f"Hello, world!")
class User(HTTPEndpoint):
async def get(self, request):
username = request.path_params['username']
return PlainTextResponse(f"Hello, {username}")
routes = [
Route("/", Homepage),
Route("/{username}", User)
]
app = Starlette(routes=routes)
```
HTTP endpoint classes will respond with "405 Method not allowed" responses for any
request methods which do not map to a corresponding handler.
### WebSocketEndpoint
The `WebSocketEndpoint` class is an ASGI application that presents a wrapper around
the functionality of a `WebSocket` instance.
The ASGI connection scope is accessible on the endpoint instance via `.scope` and
has an attribute `encoding` which may optionally be set, in order to validate the expected websocket data in the `on_receive` method.
The encoding types are:
* `'json'`
* `'bytes'`
* `'text'`
There are three overridable methods for handling specific ASGI websocket message types:
* `async def on_connect(websocket, **kwargs)`
* `async def on_receive(websocket, data)`
* `async def on_disconnect(websocket, close_code)`
```python
from starlette.endpoints import WebSocketEndpoint
class App(WebSocketEndpoint):
encoding = 'bytes'
async def on_connect(self, websocket):
await websocket.accept()
async def on_receive(self, websocket, data):
await websocket.send_bytes(b"Message: " + data)
async def on_disconnect(self, websocket, close_code):
pass
```
The `WebSocketEndpoint` can also be used with the `Starlette` application class:
```python
import uvicorn
from starlette.applications import Starlette
from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route, WebSocketRoute
html = """
Chat
WebSocket Chat
"""
class Homepage(HTTPEndpoint):
async def get(self, request):
return HTMLResponse(html)
class Echo(WebSocketEndpoint):
encoding = "text"
async def on_receive(self, websocket, data):
await websocket.send_text(f"Message text was: {data}")
routes = [
Route("/", Homepage),
WebSocketRoute("/ws", Echo)
]
app = Starlette(routes=routes)
```
starlette-0.46.1/docs/exceptions.md 0000644 0000000 0000000 00000012077 13615410400 014173 0 ustar 00
Starlette allows you to install custom exception handlers to deal with
how you return responses when errors or handled exceptions occur.
```python
from starlette.applications import Starlette
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse
HTML_404_PAGE = ...
HTML_500_PAGE = ...
async def not_found(request: Request, exc: HTTPException):
return HTMLResponse(content=HTML_404_PAGE, status_code=exc.status_code)
async def server_error(request: Request, exc: HTTPException):
return HTMLResponse(content=HTML_500_PAGE, status_code=exc.status_code)
exception_handlers = {
404: not_found,
500: server_error
}
app = Starlette(routes=routes, exception_handlers=exception_handlers)
```
If `debug` is enabled and an error occurs, then instead of using the installed
500 handler, Starlette will respond with a traceback response.
```python
app = Starlette(debug=True, routes=routes, exception_handlers=exception_handlers)
```
As well as registering handlers for specific status codes, you can also
register handlers for classes of exceptions.
In particular you might want to override how the built-in `HTTPException` class
is handled. For example, to use JSON style responses:
```python
async def http_exception(request: Request, exc: HTTPException):
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
exception_handlers = {
HTTPException: http_exception
}
```
The `HTTPException` is also equipped with the `headers` argument. Which allows the propagation
of the headers to the response class:
```python
async def http_exception(request: Request, exc: HTTPException):
return JSONResponse(
{"detail": exc.detail},
status_code=exc.status_code,
headers=exc.headers
)
```
You might also want to override how `WebSocketException` is handled:
```python
async def websocket_exception(websocket: WebSocket, exc: WebSocketException):
await websocket.close(code=1008)
exception_handlers = {
WebSocketException: websocket_exception
}
```
## Errors and handled exceptions
It is important to differentiate between handled exceptions and errors.
Handled exceptions do not represent error cases. They are coerced into appropriate
HTTP responses, which are then sent through the standard middleware stack. By default
the `HTTPException` class is used to manage any handled exceptions.
Errors are any other exception that occurs within the application. These cases
should bubble through the entire middleware stack as exceptions. Any error
logging middleware should ensure that it re-raises the exception all the
way up to the server.
In practical terms, the error handled used is `exception_handler[500]` or `exception_handler[Exception]`.
Both keys `500` and `Exception` can be used. See below:
```python
async def handle_error(request: Request, exc: HTTPException):
# Perform some logic
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
exception_handlers = {
Exception: handle_error # or "500: handle_error"
}
```
It's important to notice that in case a [`BackgroundTask`](https://www.starlette.io/background/) raises an exception,
it will be handled by the `handle_error` function, but at that point, the response was already sent. In other words,
the response created by `handle_error` will be discarded. In case the error happens before the response was sent, then
it will use the response object - in the above example, the returned `JSONResponse`.
In order to deal with this behaviour correctly, the middleware stack of a
`Starlette` application is configured like this:
* `ServerErrorMiddleware` - Returns 500 responses when server errors occur.
* Installed middleware
* `ExceptionMiddleware` - Deals with handled exceptions, and returns responses.
* Router
* Endpoints
## HTTPException
The `HTTPException` class provides a base class that you can use for any handled exceptions.
The `ExceptionMiddleware` implementation defaults to returning plain-text HTTP responses for any `HTTPException`.
* `HTTPException(status_code, detail=None, headers=None)`
You should only raise `HTTPException` inside routing or endpoints.
Middleware classes should instead just return appropriate responses directly.
You can use an `HTTPException` on a WebSocket endpoint. In case it's raised before `websocket.accept()`
the connection is not upgraded to a WebSocket connection, and the proper HTTP response is returned.
```python
from starlette.applications import Starlette
from starlette.exceptions import HTTPException
from starlette.routing import WebSocketRoute
from starlette.websockets import WebSocket
async def websocket_endpoint(websocket: WebSocket):
raise HTTPException(status_code=400, detail="Bad request")
app = Starlette(routes=[WebSocketRoute("/ws", websocket_endpoint)])
```
## WebSocketException
You can use the `WebSocketException` class to raise errors inside of WebSocket endpoints.
* `WebSocketException(code=1008, reason=None)`
You can set any code valid as defined [in the specification](https://tools.ietf.org/html/rfc6455#section-7.4.1).
starlette-0.46.1/docs/graphql.md 0000644 0000000 0000000 00000001144 13615410400 013441 0 ustar 00 GraphQL support in Starlette was deprecated in version 0.15.0, and removed in version 0.17.0.
Although GraphQL support is no longer built in to Starlette, you can still use GraphQL with Starlette via 3rd party libraries. These libraries all have Starlette-specific guides to help you do just that:
- [Ariadne](https://ariadnegraphql.org/docs/starlette-integration.html)
- [`starlette-graphene3`](https://github.com/ciscorn/starlette-graphene3#example)
- [Strawberry](https://strawberry.rocks/docs/integrations/starlette)
- [`tartiflette-asgi`](https://tartiflette.github.io/tartiflette-asgi/usage/#starlette)
starlette-0.46.1/docs/index.md 0000644 0000000 0000000 00000012476 13615410400 013124 0 ustar 00
β¨ The little ASGI framework that shines. β¨
---
**Documentation**: https://www.starlette.io
**Source Code**: https://github.com/encode/starlette
---
# Introduction
Starlette is a lightweight [ASGI][asgi] framework/toolkit,
which is ideal for building async web services in Python.
It is production-ready, and gives you the following:
* A lightweight, low-complexity HTTP web framework.
* WebSocket support.
* In-process background tasks.
* Startup and shutdown events.
* Test client built on `httpx`.
* CORS, GZip, Static Files, Streaming responses.
* Session and Cookie support.
* 100% test coverage.
* 100% type annotated codebase.
* Few hard dependencies.
* Compatible with `asyncio` and `trio` backends.
* Great overall performance [against independent benchmarks][techempower].
## Sponsorship
Starlette is an open-source project that relies on community support. You can help us maintain and improve the framework by [becoming a sponsor](/sponsorship).
## Installation
```shell
pip install starlette
```
You'll also want to install an ASGI server, such as [uvicorn](https://www.uvicorn.org/), [daphne](https://github.com/django/daphne/), or [hypercorn](https://hypercorn.readthedocs.io/en/latest/).
```shell
pip install uvicorn
```
## Example
```python title="main.py"
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
async def homepage(request):
return JSONResponse({'hello': 'world'})
app = Starlette(debug=True, routes=[
Route('/', homepage),
])
```
Then run the application...
```shell
uvicorn main:app
```
## Dependencies
Starlette only requires `anyio`, and the following dependencies are optional:
* [`httpx`][httpx] - Required if you want to use the `TestClient`.
* [`jinja2`][jinja2] - Required if you want to use `Jinja2Templates`.
* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`.
* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.
* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.
You can install all of these with `pip install starlette[full]`.
## Framework or Toolkit
Starlette is designed to be used either as a complete framework, or as
an ASGI toolkit. You can use any of its components independently.
```python title="main.py"
from starlette.responses import PlainTextResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = PlainTextResponse('Hello, world!')
await response(scope, receive, send)
```
Run the `app` application in `main.py`:
```shell
$ uvicorn main:app
INFO: Started server process [11509]
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Run uvicorn with `--reload` to enable auto-reloading on code changes.
## Modularity
The modularity that Starlette is designed on promotes building re-usable
components that can be shared between any ASGI framework. This should enable
an ecosystem of shared middleware and mountable applications.
The clean API separation also means it's easier to understand each component
in isolation.
---
Starlette is BSD licensed code. Designed & crafted with care.— βοΈ —
[asgi]: https://asgi.readthedocs.io/en/latest/
[httpx]: https://www.python-httpx.org/
[jinja2]: https://jinja.palletsprojects.com/
[python-multipart]: https://multipart.fastapiexpert.com/
[itsdangerous]: https://itsdangerous.palletsprojects.com/
[sqlalchemy]: https://www.sqlalchemy.org
[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation
[techempower]: https://www.techempower.com/benchmarks/#hw=ph&test=fortune&l=zijzen-sf
starlette-0.46.1/docs/lifespan.md 0000644 0000000 0000000 00000004476 13615410400 013617 0 ustar 00
Starlette applications can register a lifespan handler for dealing with
code that needs to run before the application starts up, or when the application
is shutting down.
```python
import contextlib
from starlette.applications import Starlette
@contextlib.asynccontextmanager
async def lifespan(app):
async with some_async_resource():
print("Run at startup!")
yield
print("Run on shutdown!")
routes = [
...
]
app = Starlette(routes=routes, lifespan=lifespan)
```
Starlette will not start serving any incoming requests until the lifespan has been run.
The lifespan teardown will run once all connections have been closed, and
any in-process background tasks have completed.
Consider using [`anyio.create_task_group()`](https://anyio.readthedocs.io/en/stable/tasks.html)
for managing asynchronous tasks.
## Lifespan State
The lifespan has the concept of `state`, which is a dictionary that
can be used to share the objects between the lifespan, and the requests.
```python
import contextlib
from typing import AsyncIterator, TypedDict
import httpx
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
class State(TypedDict):
http_client: httpx.AsyncClient
@contextlib.asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[State]:
async with httpx.AsyncClient() as client:
yield {"http_client": client}
async def homepage(request: Request) -> PlainTextResponse:
client = request.state.http_client
response = await client.get("https://www.example.com")
return PlainTextResponse(response.text)
app = Starlette(
lifespan=lifespan,
routes=[Route("/", homepage)]
)
```
The `state` received on the requests is a **shallow** copy of the state received on the
lifespan handler.
## Running lifespan in tests
You should use `TestClient` as a context manager, to ensure that the lifespan is called.
```python
from example import app
from starlette.testclient import TestClient
def test_homepage():
with TestClient(app) as client:
# Application's lifespan is called on entering the block.
response = client.get("/")
assert response.status_code == 200
# And the lifespan's teardown is run when exiting the block.
```
starlette-0.46.1/docs/middleware.md 0000644 0000000 0000000 00000104230 13615410400 014120 0 ustar 00
Starlette includes several middleware classes for adding behavior that is applied across
your entire application. These are all implemented as standard ASGI
middleware classes, and can be applied either to Starlette or to any other ASGI application.
## Using middleware
The Starlette application class allows you to include the ASGI middleware
in a way that ensures that it remains wrapped by the exception handler.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
routes = ...
# Ensure that all requests include an 'example.com' or
# '*.example.com' host header, and strictly enforce https-only access.
middleware = [
Middleware(
TrustedHostMiddleware,
allowed_hosts=['example.com', '*.example.com'],
),
Middleware(HTTPSRedirectMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
```
Every Starlette application automatically includes two pieces of middleware by default:
* `ServerErrorMiddleware` - Ensures that application exceptions may return a custom 500 page, or display an application traceback in DEBUG mode. This is *always* the outermost middleware layer.
* `ExceptionMiddleware` - Adds exception handlers, so that particular types of expected exception cases can be associated with handler functions. For example raising `HTTPException(status_code=404)` within an endpoint will end up rendering a custom 404 page.
Middleware is evaluated from top-to-bottom, so the flow of execution in our example
application would look like this:
* Middleware
* `ServerErrorMiddleware`
* `TrustedHostMiddleware`
* `HTTPSRedirectMiddleware`
* `ExceptionMiddleware`
* Routing
* Endpoint
The following middleware implementations are available in the Starlette package:
## CORSMiddleware
Adds appropriate [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to outgoing responses in order to allow cross-origin requests from browsers.
The default parameters used by the CORSMiddleware implementation are restrictive by default,
so you'll need to explicitly enable particular origins, methods, or headers, in order
for browsers to be permitted to use them in a Cross-Domain context.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
routes = ...
middleware = [
Middleware(CORSMiddleware, allow_origins=['*'])
]
app = Starlette(routes=routes, middleware=middleware)
```
The following arguments are supported:
* `allow_origins` - A list of origins that should be permitted to make cross-origin requests. eg. `['https://example.org', 'https://www.example.org']`. You can use `['*']` to allow any origin.
* `allow_origin_regex` - A regex string to match against origins that should be permitted to make cross-origin requests. eg. `'https://.*\.example\.org'`.
* `allow_methods` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use `['*']` to allow all standard methods.
* `allow_headers` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to `[]`. You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for CORS requests.
* `allow_credentials` - Indicate that cookies should be supported for cross-origin requests. Defaults to `False`. Also, `allow_origins`, `allow_methods` and `allow_headers` cannot be set to `['*']` for credentials to be allowed, all of them must be explicitly specified.
* `expose_headers` - Indicate any response headers that should be made accessible to the browser. Defaults to `[]`.
* `max_age` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to `600`.
The middleware responds to two particular types of HTTP request...
#### CORS preflight requests
These are any `OPTIONS` request with `Origin` and `Access-Control-Request-Method` headers.
In this case the middleware will intercept the incoming request and respond with
appropriate CORS headers, and either a 200 or 400 response for informational purposes.
#### Simple requests
Any request with an `Origin` header. In this case the middleware will pass the
request through as normal, but will include appropriate CORS headers on the response.
### CORSMiddleware Global Enforcement
When using CORSMiddleware with your Starlette application, it's important to ensure that CORS headers are applied even to error responses generated by unhandled exceptions. The recommended solution is to wrap the entire Starlette application with CORSMiddleware. This approach guarantees that even if an exception is caught by ServerErrorMiddleware (or other outer error-handling middleware), the response will still include the proper `Access-Control-Allow-Origin` header.
For example, instead of adding CORSMiddleware as an inner `middleware` via the Starlette middleware parameter, you can wrap your application as follows:
```python
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
import uvicorn
app = Starlette()
app = CORSMiddleware(app=app, allow_origins=["*"])
# ... your routes and middleware configuration ...
if __name__ == '__main__':
uvicorn.run(
app,
host='0.0.0.0',
port=8000
)
```
## SessionMiddleware
Adds signed cookie-based HTTP sessions. Session information is readable but not modifiable.
Access or modify the session data using the `request.session` dictionary interface.
The following arguments are supported:
* `secret_key` - Should be a random string.
* `session_cookie` - Defaults to "session".
* `max_age` - Session expiry time in seconds. Defaults to 2 weeks. If set to `None` then the cookie will last as long as the browser session.
* `same_site` - SameSite flag prevents the browser from sending session cookie along with cross-site requests. Defaults to `'lax'`.
* `path` - The path set for the session cookie. Defaults to `'/'`.
* `https_only` - Indicate that Secure flag should be set (can be used with HTTPS only). Defaults to `False`.
* `domain` - Domain of the cookie used to share cookie between subdomains or cross-domains. The browser defaults the domain to the same host that set the cookie, excluding subdomains ([reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#domain_attribute)).
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
routes = ...
middleware = [
Middleware(SessionMiddleware, secret_key=..., https_only=True)
]
app = Starlette(routes=routes, middleware=middleware)
```
## HTTPSRedirectMiddleware
Enforces that all incoming requests must either be `https` or `wss`. Any incoming
requests to `http` or `ws` will be redirected to the secure scheme instead.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
routes = ...
middleware = [
Middleware(HTTPSRedirectMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
```
There are no configuration options for this middleware class.
## TrustedHostMiddleware
Enforces that all incoming requests have a correctly set `Host` header, in order
to guard against HTTP Host Header attacks.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
routes = ...
middleware = [
Middleware(TrustedHostMiddleware, allowed_hosts=['example.com', '*.example.com'])
]
app = Starlette(routes=routes, middleware=middleware)
```
The following arguments are supported:
* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard
domains such as `*.example.com` are supported for matching subdomains. To allow any
hostname either use `allowed_hosts=["*"]` or omit the middleware.
* `www_redirect` - If set to True, requests to non-www versions of the allowed hosts will be redirected to their www counterparts. Defaults to `True`.
If an incoming request does not validate correctly then a 400 response will be sent.
## GZipMiddleware
Handles GZip responses for any request that includes `"gzip"` in the `Accept-Encoding` header.
The middleware will handle both standard and streaming responses.
??? info "Buffer on streaming responses"
On streaming responses, the middleware will buffer the response before compressing it.
The idea is that we don't want to compress every small chunk of data, as it would be inefficient.
Instead, we buffer the response until it reaches a certain size, and then compress it.
This may cause a delay in the response, as the middleware waits for the buffer to fill up before compressing it.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
routes = ...
middleware = [
Middleware(GZipMiddleware, minimum_size=1000, compresslevel=9)
]
app = Starlette(routes=routes, middleware=middleware)
```
The following arguments are supported:
* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`.
* `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes.
The middleware won't GZip responses that already have either a `Content-Encoding` set, to prevent them from
being encoded twice, or a `Content-Type` set to `text/event-stream`, to avoid compressing server-sent events.
## BaseHTTPMiddleware
An abstract class that allows you to write ASGI middleware against a request/response
interface.
### Usage
To implement a middleware class using `BaseHTTPMiddleware`, you must override the
`async def dispatch(request, call_next)` method.
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Custom'] = 'Example'
return response
routes = ...
middleware = [
Middleware(CustomHeaderMiddleware)
]
app = Starlette(routes=routes, middleware=middleware)
```
If you want to provide configuration options to the middleware class you should
override the `__init__` method, ensuring that the first argument is `app`, and
any remaining arguments are optional keyword arguments. Make sure to set the `app`
attribute on the instance if you do this.
```python
class CustomHeaderMiddleware(BaseHTTPMiddleware):
def __init__(self, app, header_value='Example'):
super().__init__(app)
self.header_value = header_value
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Custom'] = self.header_value
return response
middleware = [
Middleware(CustomHeaderMiddleware, header_value='Customized')
]
app = Starlette(routes=routes, middleware=middleware)
```
Middleware classes should not modify their state outside of the `__init__` method.
Instead you should keep any state local to the `dispatch` method, or pass it
around explicitly, rather than mutating the middleware instance.
### Limitations
Currently, the `BaseHTTPMiddleware` has some known limitations:
- Using `BaseHTTPMiddleware` will prevent changes to [`contextlib.ContextVar`](https://docs.python.org/3/library/contextvars.html#contextvars.ContextVar)s from propagating upwards. That is, if you set a value for a `ContextVar` in your endpoint and try to read it from a middleware you will find that the value is not the same value you set in your endpoint (see [this test](https://github.com/encode/starlette/blob/621abc747a6604825190b93467918a0ec6456a24/tests/middleware/test_base.py#L192-L223) for an example of this behavior).
To overcome these limitations, use [pure ASGI middleware](#pure-asgi-middleware), as shown below.
## Pure ASGI Middleware
The [ASGI spec](https://asgi.readthedocs.io/en/latest/) makes it possible to implement ASGI middleware using the ASGI interface directly, as a chain of ASGI applications that call into the next one. In fact, this is how middleware classes shipped with Starlette are implemented.
This lower-level approach provides greater control over behavior and enhanced interoperability across frameworks and servers. It also overcomes the [limitations of `BaseHTTPMiddleware`](#limitations).
### Writing pure ASGI middleware
The most common way to create an ASGI middleware is with a class.
```python
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
await self.app(scope, receive, send)
```
The middleware above is the most basic ASGI middleware. It receives a parent ASGI application as an argument for its constructor, and implements an `async __call__` method which calls into that parent application.
Some implementations such as [`asgi-cors`](https://github.com/simonw/asgi-cors/blob/10ef64bfcc6cd8d16f3014077f20a0fb8544ec39/asgi_cors.py) use an alternative style, using functions:
```python
import functools
def asgi_middleware():
def asgi_decorator(app):
@functools.wraps(app)
async def wrapped_app(scope, receive, send):
await app(scope, receive, send)
return wrapped_app
return asgi_decorator
```
In any case, ASGI middleware must be callables that accept three arguments: `scope`, `receive`, and `send`.
* `scope` is a dict holding information about the connection, where `scope["type"]` may be:
* [`"http"`](https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope): for HTTP requests.
* [`"websocket"`](https://asgi.readthedocs.io/en/latest/specs/www.html#websocket-connection-scope): for WebSocket connections.
* [`"lifespan"`](https://asgi.readthedocs.io/en/latest/specs/lifespan.html#scope): for ASGI lifespan messages.
* `receive` and `send` can be used to exchange ASGI event messages with the ASGI server β more on this below. The type and contents of these messages depend on the scope type. Learn more in the [ASGI specification](https://asgi.readthedocs.io/en/latest/specs/index.html).
### Using pure ASGI middleware
Pure ASGI middleware can be used like any other middleware:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from .middleware import ASGIMiddleware
routes = ...
middleware = [
Middleware(ASGIMiddleware),
]
app = Starlette(..., middleware=middleware)
```
See also [Using middleware](#using-middleware).
### Type annotations
There are two ways of annotating a middleware: using Starlette itself or [`asgiref`](https://github.com/django/asgiref).
* Using Starlette: for most common use cases.
```python
from starlette.types import ASGIApp, Message, Scope, Receive, Send
class ASGIMiddleware:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
return await self.app(scope, receive, send)
async def send_wrapper(message: Message) -> None:
# ... Do something
await send(message)
await self.app(scope, receive, send_wrapper)
```
* Using [`asgiref`](https://github.com/django/asgiref): for more rigorous type hinting.
```python
from asgiref.typing import ASGI3Application, ASGIReceiveCallable, ASGISendCallable, Scope
from asgiref.typing import ASGIReceiveEvent, ASGISendEvent
class ASGIMiddleware:
def __init__(self, app: ASGI3Application) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def send_wrapper(message: ASGISendEvent) -> None:
# ... Do something
await send(message)
return await self.app(scope, receive, send_wrapper)
```
### Common patterns
#### Processing certain requests only
ASGI middleware can apply specific behavior according to the contents of `scope`.
For example, to only process HTTP requests, write this...
```python
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
... # Do something here!
await self.app(scope, receive, send)
```
Likewise, WebSocket-only middleware would guard on `scope["type"] != "websocket"`.
The middleware may also act differently based on the request method, URL, headers, etc.
#### Reusing Starlette components
Starlette provides several data structures that accept the ASGI `scope`, `receive` and/or `send` arguments, allowing you to work at a higher level of abstraction. Such data structures include [`Request`](requests.md#request), [`Headers`](requests.md#headers), [`QueryParams`](requests.md#query-parameters), [`URL`](requests.md#url), etc.
For example, you can instantiate a `Request` to more easily inspect an HTTP request:
```python
from starlette.requests import Request
class ASGIMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] == "http":
request = Request(scope)
... # Use `request.method`, `request.url`, `request.headers`, etc.
await self.app(scope, receive, send)
```
You can also reuse [responses](responses.md), which are ASGI applications as well.
#### Sending eager responses
Inspecting the connection `scope` allows you to conditionally call into a different ASGI app. One use case might be sending a response without calling into the app.
As an example, this middleware uses a dictionary to perform permanent redirects based on the requested path. This could be used to implement ongoing support of legacy URLs in case you need to refactor route URL patterns.
```python
from starlette.datastructures import URL
from starlette.responses import RedirectResponse
class RedirectsMiddleware:
def __init__(self, app, path_mapping: dict):
self.app = app
self.path_mapping = path_mapping
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
url = URL(scope=scope)
if url.path in self.path_mapping:
url = url.replace(path=self.path_mapping[url.path])
response = RedirectResponse(url, status_code=301)
await response(scope, receive, send)
return
await self.app(scope, receive, send)
```
Example usage would look like this:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
routes = ...
redirections = {
"/v1/resource/": "/v2/resource/",
# ...
}
middleware = [
Middleware(RedirectsMiddleware, path_mapping=redirections),
]
app = Starlette(routes=routes, middleware=middleware)
```
#### Inspecting or modifying the request
Request information can be accessed or changed by manipulating the `scope`. For a full example of this pattern, see Uvicorn's [`ProxyHeadersMiddleware`](https://github.com/encode/uvicorn/blob/fd4386fefb8fe8a4568831a7d8b2930d5fb61455/uvicorn/middleware/proxy_headers.py) which inspects and tweaks the `scope` when serving behind a frontend proxy.
Besides, wrapping the `receive` ASGI callable allows you to access or modify the HTTP request body by manipulating [`http.request`](https://asgi.readthedocs.io/en/latest/specs/www.html#request-receive-event) ASGI event messages.
As an example, this middleware computes and logs the size of the incoming request body...
```python
class LoggedRequestBodySizeMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
body_size = 0
async def receive_logging_request_body_size():
nonlocal body_size
message = await receive()
assert message["type"] == "http.request"
body_size += len(message.get("body", b""))
if not message.get("more_body", False):
print(f"Size of request body was: {body_size} bytes")
return message
await self.app(scope, receive_logging_request_body_size, send)
```
Likewise, WebSocket middleware may manipulate [`websocket.receive`](https://asgi.readthedocs.io/en/latest/specs/www.html#receive-receive-event) ASGI event messages to inspect or alter incoming WebSocket data.
For an example that changes the HTTP request body, see [`msgpack-asgi`](https://github.com/florimondmanca/msgpack-asgi).
#### Inspecting or modifying the response
Wrapping the `send` ASGI callable allows you to inspect or modify the HTTP response sent by the underlying application. To do so, react to [`http.response.start`](https://asgi.readthedocs.io/en/latest/specs/www.html#response-start-send-event) or [`http.response.body`](https://asgi.readthedocs.io/en/latest/specs/www.html#response-body-send-event) ASGI event messages.
As an example, this middleware adds some fixed extra response headers:
```python
from starlette.datastructures import MutableHeaders
class ExtraResponseHeadersMiddleware:
def __init__(self, app, headers):
self.app = app
self.headers = headers
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
return await self.app(scope, receive, send)
async def send_with_extra_headers(message):
if message["type"] == "http.response.start":
headers = MutableHeaders(scope=message)
for key, value in self.headers:
headers.append(key, value)
await send(message)
await self.app(scope, receive, send_with_extra_headers)
```
See also [`asgi-logger`](https://github.com/Kludex/asgi-logger/blob/main/asgi_logger/middleware.py) for an example that inspects the HTTP response and logs a configurable HTTP access log line.
Likewise, WebSocket middleware may manipulate [`websocket.send`](https://asgi.readthedocs.io/en/latest/specs/www.html#send-send-event) ASGI event messages to inspect or alter outgoing WebSocket data.
Note that if you change the response body, you will need to update the response `Content-Length` header to match the new response body length. See [`brotli-asgi`](https://github.com/fullonic/brotli-asgi) for a complete example.
#### Passing information to endpoints
If you need to share information with the underlying app or endpoints, you may store it into the `scope` dictionary. Note that this is a convention -- for example, Starlette uses this to share routing information with endpoints -- but it is not part of the ASGI specification. If you do so, be sure to avoid conflicts by using keys that have low chances of being used by other middleware or applications.
For example, when including the middleware below, endpoints would be able to access `request.scope["asgi_transaction_id"]`.
```python
import uuid
class TransactionIDMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
scope["asgi_transaction_id"] = uuid.uuid4()
await self.app(scope, receive, send)
```
#### Cleanup and error handling
You can wrap the application in a `try/except/finally` block or a context manager to perform cleanup operations or do error handling.
For example, the following middleware might collect metrics and process application exceptions...
```python
import time
class MonitoringMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
start = time.time()
try:
await self.app(scope, receive, send)
except Exception as exc:
... # Process the exception
raise
finally:
end = time.time()
elapsed = end - start
... # Submit `elapsed` as a metric to a monitoring backend
```
See also [`timing-asgi`](https://github.com/steinnes/timing-asgi) for a full example of this pattern.
### Gotchas
#### ASGI middleware should be stateless
Because ASGI is designed to handle concurrent requests, any connection-specific state should be scoped to the `__call__` implementation. Not doing so would typically lead to conflicting variable reads/writes across requests, and most likely bugs.
As an example, this would conditionally replace the response body, if an `X-Mock` header is present in the response...
=== "β Do"
```python
from starlette.datastructures import Headers
class MockResponseBodyMiddleware:
def __init__(self, app, content):
self.app = app
self.content = content
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
# A flag that we will turn `True` if the HTTP response
# has the 'X-Mock' header.
# β : Scoped to this function.
should_mock = False
async def maybe_send_with_mock_content(message):
nonlocal should_mock
if message["type"] == "http.response.start":
headers = Headers(raw=message["headers"])
should_mock = headers.get("X-Mock") == "1"
await send(message)
elif message["type"] == "http.response.body":
if should_mock:
message = {"type": "http.response.body", "body": self.content}
await send(message)
await self.app(scope, receive, maybe_send_with_mock_content)
```
=== "β Don't"
```python hl_lines="7-8"
from starlette.datastructures import Headers
class MockResponseBodyMiddleware:
def __init__(self, app, content):
self.app = app
self.content = content
# β: This variable would be read and written across requests!
self.should_mock = False
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def maybe_send_with_mock_content(message):
if message["type"] == "http.response.start":
headers = Headers(raw=message["headers"])
self.should_mock = headers.get("X-Mock") == "1"
await send(message)
elif message["type"] == "http.response.body":
if self.should_mock:
message = {"type": "http.response.body", "body": self.content}
await send(message)
await self.app(scope, receive, maybe_send_with_mock_content)
```
See also [`GZipMiddleware`](https://github.com/encode/starlette/blob/9ef1b91c9c043197da6c3f38aa153fd874b95527/starlette/middleware/gzip.py) for a full example implementation that navigates this potential gotcha.
### Further reading
This documentation should be enough to have a good basis on how to create an ASGI middleware.
Nonetheless, there are great articles about the subject:
- [Introduction to ASGI: Emergence of an Async Python Web Ecosystem](https://florimond.dev/en/posts/2019/08/introduction-to-asgi-async-python-web/)
- [How to write ASGI middleware](https://pgjones.dev/blog/how-to-write-asgi-middleware-2021/)
## Using middleware in other frameworks
To wrap ASGI middleware around other ASGI applications, you should use the
more general pattern of wrapping the application instance:
```python
app = TrustedHostMiddleware(app, allowed_hosts=['example.com'])
```
You can do this with a Starlette application instance too, but it is preferable
to use the `middleware=` style, as it will:
* Ensure that everything remains wrapped in a single outermost `ServerErrorMiddleware`.
* Preserves the top-level `app` instance.
## Applying middleware to groups of routes
Middleware can also be added to `Mount` instances, which allows you to apply middleware to a group of routes or a sub-application:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Mount, Route
routes = [
Mount(
"/",
routes=[
Route(
"/example",
endpoint=...,
)
],
middleware=[Middleware(GZipMiddleware)]
)
]
app = Starlette(routes=routes)
```
Note that middleware used in this way is *not* wrapped in exception handling middleware like the middleware applied to the `Starlette` application is.
This is often not a problem because it only applies to middleware that inspect or modify the `Response`, and even then you probably don't want to apply this logic to error responses.
If you do want to apply the middleware logic to error responses only on some routes you have a couple of options:
* Add an `ExceptionMiddleware` onto the `Mount`
* Add a `try/except` block to your middleware and return an error response from there
* Split up marking and processing into two middlewares, one that gets put on `Mount` which marks the response as needing processing (for example by setting `scope["log-response"] = True`) and another applied to the `Starlette` application that does the heavy lifting.
The `Route`/`WebSocket` class also accepts a `middleware` argument, which allows you to apply middleware to a single route:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Route
routes = [
Route(
"/example",
endpoint=...,
middleware=[Middleware(GZipMiddleware)]
)
]
app = Starlette(routes=routes)
```
You can also apply middleware to the `Router` class, which allows you to apply middleware to a group of routes:
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.routing import Route, Router
routes = [
Route("/example", endpoint=...),
Route("/another", endpoint=...),
]
router = Router(routes=routes, middleware=[Middleware(GZipMiddleware)])
```
## Third party middleware
#### [asgi-auth-github](https://github.com/simonw/asgi-auth-github)
This middleware adds authentication to any ASGI application, requiring users to sign in
using their GitHub account (via [OAuth](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)).
Access can be restricted to specific users or to members of specific GitHub organizations or teams.
#### [asgi-csrf](https://github.com/simonw/asgi-csrf)
Middleware for protecting against CSRF attacks. This middleware implements the Double Submit Cookie pattern, where a cookie is set, then it is compared to a csrftoken hidden form field or an `x-csrftoken` HTTP header.
#### [AuthlibMiddleware](https://github.com/aogier/starlette-authlib)
A drop-in replacement for Starlette session middleware, using [authlib's jwt](https://docs.authlib.org/en/latest/jose/jwt.html)
module.
#### [BugsnagMiddleware](https://github.com/ashinabraham/starlette-bugsnag)
A middleware class for logging exceptions to [Bugsnag](https://www.bugsnag.com/).
#### [CSRFMiddleware](https://github.com/frankie567/starlette-csrf)
Middleware for protecting against CSRF attacks. This middleware implements the Double Submit Cookie pattern, where a cookie is set, then it is compared to an `x-csrftoken` HTTP header.
#### [EarlyDataMiddleware](https://github.com/HarrySky/starlette-early-data)
Middleware and decorator for detecting and denying [TLSv1.3 early data](https://tools.ietf.org/html/rfc8470) requests.
#### [PrometheusMiddleware](https://github.com/perdy/starlette-prometheus)
A middleware class for capturing Prometheus metrics related to requests and responses, including in progress requests, timing...
#### [ProxyHeadersMiddleware](https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py)
Uvicorn includes a middleware class for determining the client IP address,
when proxy servers are being used, based on the `X-Forwarded-Proto` and `X-Forwarded-For` headers. For more complex proxy configurations, you might want to adapt this middleware.
#### [RateLimitMiddleware](https://github.com/abersheeran/asgi-ratelimit)
A rate limit middleware. Regular expression matches url; flexible rules; highly customizable. Very easy to use.
#### [RequestIdMiddleware](https://github.com/snok/asgi-correlation-id)
A middleware class for reading/generating request IDs and attaching them to application logs.
#### [RollbarMiddleware](https://docs.rollbar.com/docs/starlette)
A middleware class for logging exceptions, errors, and log messages to [Rollbar](https://www.rollbar.com).
#### [StarletteOpentracing](https://github.com/acidjunk/starlette-opentracing)
A middleware class that emits tracing info to [OpenTracing.io](https://opentracing.io/) compatible tracers and
can be used to profile and monitor distributed applications.
#### [SecureCookiesMiddleware](https://github.com/thearchitector/starlette-securecookies)
Customizable middleware for adding automatic cookie encryption and decryption to Starlette applications, with
extra support for existing cookie-based middleware.
#### [TimingMiddleware](https://github.com/steinnes/timing-asgi)
A middleware class to emit timing information (cpu and wall time) for each request which
passes through it. Includes examples for how to emit these timings as statsd metrics.
#### [WSGIMiddleware](https://github.com/abersheeran/a2wsgi)
A middleware class in charge of converting a WSGI application into an ASGI one.
starlette-0.46.1/docs/release-notes.md 0000644 0000000 0000000 00000123075 13615410400 014561 0 ustar 00 ---
toc_depth: 2
---
## 0.46.1 (March 8, 2025)
#### Fixed
* Allow relative directory path when `follow_symlinks=True` [#2896](https://github.com/encode/starlette/pull/2896).
## 0.46.0 (February 22, 2025)
#### Added
* `GZipMiddleware`: Make sure `Vary` header is always added if a response can be compressed [#2865](https://github.com/encode/starlette/pull/2865).
#### Fixed
* Raise exception from background task on BaseHTTPMiddleware [#2812](https://github.com/encode/starlette/pull/2812).
* `GZipMiddleware`: Don't compress on server sent events [#2871](https://github.com/encode/starlette/pull/2871).
#### Changed
* `MultiPartParser`: Rename `max_file_size` to `spool_max_size` [#2780](https://github.com/encode/starlette/pull/2780).
#### Deprecated
* Add deprecated warning to `TestClient(timeout=...)` [#2840](https://github.com/encode/starlette/pull/2840).
## 0.45.3 (January 24, 2025)
#### Fixed
* Turn directory into string on `lookup_path` on commonpath comparison [#2851](https://github.com/encode/starlette/pull/2851).
## 0.45.2 (January 4, 2025)
#### Fixed
* Make `create_memory_object_stream` compatible with old anyio versions once again, and bump anyio minimum version to 3.6.2 [#2833](https://github.com/encode/starlette/pull/2833).
## 0.45.1 (December 30, 2024)
#### Fixed
* Close `MemoryObjectReceiveStream` left unclosed upon exception in `BaseHTTPMiddleware` children [#2813](https://github.com/encode/starlette/pull/2813).
* Collect errors more reliably from the WebSocket logic on the `TestClient` [#2814](https://github.com/encode/starlette/pull/2814).
#### Refactor
* Use a pair of memory object streams instead of two queues on the `TestClient` [#2829](https://github.com/encode/starlette/pull/2829).
## 0.45.0 (December 29, 2024)
#### Removed
* Drop Python 3.8 support [#2823](https://github.com/encode/starlette/pull/2823).
* Remove `ExceptionMiddleware` import proxy from `starlette.exceptions` module [#2826](https://github.com/encode/starlette/pull/2826).
* Remove deprecated `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` [#2827](https://github.com/encode/starlette/pull/2827).
## 0.44.0 (December 28, 2024)
#### Added
* Add `client` parameter to `TestClient` [#2810](https://github.com/encode/starlette/pull/2810).
* Add `max_part_size` parameter to `Request.form()` [#2815](https://github.com/encode/starlette/pull/2815).
## 0.43.0 (December 25, 2024)
#### Removed
* Remove deprecated `allow_redirects` argument from `TestClient` [#2808](https://github.com/encode/starlette/pull/2808).
#### Added
* Make UUID path parameter conversion more flexible [#2806](https://github.com/encode/starlette/pull/2806).
## 0.42.0 (December 14, 2024)
#### Added
* Raise `ClientDisconnect` on `StreamingResponse` [#2732](https://github.com/encode/starlette/pull/2732).
#### Fixed
* Use ETag from headers when parsing If-Range in FileResponse [#2761](https://github.com/encode/starlette/pull/2761).
* Follow directory symlinks in `StaticFiles` when `follow_symlinks=True` [#2711](https://github.com/encode/starlette/pull/2711).
* Bump minimum `python-multipart` version to `0.0.18` [0ba8395](https://github.com/encode/starlette/commit/0ba83959e609bbd460966f092287df1bbd564cc6).
* Bump minimum `httpx` version to `0.27.0` [#2773](https://github.com/encode/starlette/pull/2773).
## 0.41.3 (November 18, 2024)
#### Fixed
* Exclude the query parameters from the `scope[raw_path]` on the `TestClient` [#2716](https://github.com/encode/starlette/pull/2716).
* Replace `dict` by `Mapping` on `HTTPException.headers` [#2749](https://github.com/encode/starlette/pull/2749).
* Correct middleware argument passing and improve factory pattern [#2752](https://github.com/encode/starlette/pull/2752).
## 0.41.2 (October 27, 2024)
#### Fixed
* Revert bump on `python-multipart` on `starlette[full]` extras [#2737](https://github.com/encode/starlette/pull/2737).
## 0.41.1 (October 24, 2024)
#### Fixed
* Bump minimum `python-multipart` version to `0.0.13` [#2734](https://github.com/encode/starlette/pull/2734).
* Change `python-multipart` import to `python_multipart` [#2733](https://github.com/encode/starlette/pull/2733).
## 0.41.0 (October 15, 2024)
#### Added
- Allow to raise `HTTPException` before `websocket.accept()` [#2725](https://github.com/encode/starlette/pull/2725).
## 0.40.0 (October 15, 2024)
This release fixes a Denial of service (DoS) via `multipart/form-data` requests.
You can view the full security advisory:
[GHSA-f96h-pmfr-66vw](https://github.com/encode/starlette/security/advisories/GHSA-f96h-pmfr-66vw)
#### Fixed
- Add `max_part_size` to `MultiPartParser` to limit the size of parts in `multipart/form-data`
requests [fd038f3](https://github.com/encode/starlette/commit/fd038f3070c302bff17ef7d173dbb0b007617733).
## 0.39.2 (September 29, 2024)
#### Fixed
- Allow use of `request.url_for` when only "app" scope is available [#2672](https://github.com/encode/starlette/pull/2672).
- Fix internal type hints to support `python-multipart==0.0.12` [#2708](https://github.com/encode/starlette/pull/2708).
## 0.39.1 (September 25, 2024)
#### Fixed
- Avoid regex re-compilation in `responses.py` and `schemas.py` [#2700](https://github.com/encode/starlette/pull/2700).
- Improve performance of `get_route_path` by removing regular expression usage
[#2701](https://github.com/encode/starlette/pull/2701).
- Consider `FileResponse.chunk_size` when handling multiple ranges [#2703](https://github.com/encode/starlette/pull/2703).
- Use `token_hex` for generating multipart boundary strings [#2702](https://github.com/encode/starlette/pull/2702).
## 0.39.0 (September 23, 2024)
#### Added
* Add support for [HTTP Range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests) to
`FileResponse` [#2697](https://github.com/encode/starlette/pull/2697).
## 0.38.6 (September 22, 2024)
#### Fixed
* Close unclosed `MemoryObjectReceiveStream` in `TestClient` [#2693](https://github.com/encode/starlette/pull/2693).
## 0.38.5 (September 7, 2024)
#### Fixed
* Schedule `BackgroundTasks` from within `BaseHTTPMiddleware` [#2688](https://github.com/encode/starlette/pull/2688).
This behavior was removed in 0.38.3, and is now restored.
## 0.38.4 (September 1, 2024)
#### Fixed
* Ensure accurate `root_path` removal in `get_route_path` function [#2600](https://github.com/encode/starlette/pull/2600).
## 0.38.3 (September 1, 2024)
#### Added
* Support for Python 3.13 [#2662](https://github.com/encode/starlette/pull/2662).
#### Fixed
* Don't poll for disconnects in `BaseHTTPMiddleware` via `StreamingResponse` [#2620](https://github.com/encode/starlette/pull/2620).
## 0.38.2 (July 27, 2024)
#### Fixed
* Not assume all routines have `__name__` on `routing.get_name()` [#2648](https://github.com/encode/starlette/pull/2648).
## 0.38.1 (July 23, 2024)
#### Removed
* Revert "Add support for ASGI pathsend extension" [#2649](https://github.com/encode/starlette/pull/2649).
## 0.38.0 (July 20, 2024)
#### Added
* Allow use of `memoryview` in `StreamingResponse` and `Response` [#2576](https://github.com/encode/starlette/pull/2576)
and [#2577](https://github.com/encode/starlette/pull/2577).
* Send 404 instead of 500 when filename requested is too long on `StaticFiles` [#2583](https://github.com/encode/starlette/pull/2583).
#### Changed
* Fail fast on invalid `Jinja2Template` instantiation parameters [#2568](https://github.com/encode/starlette/pull/2568).
* Check endpoint handler is async only once [#2536](https://github.com/encode/starlette/pull/2536).
#### Fixed
* Add proper synchronization to `WebSocketTestSession` [#2597](https://github.com/encode/starlette/pull/2597).
## 0.37.2 (March 5, 2024)
#### Added
* Add `bytes` to `_RequestData` type [#2510](https://github.com/encode/starlette/pull/2510).
#### Fixed
* Revert "Turn `scope["client"]` to `None` on `TestClient` (#2377)" [#2525](https://github.com/encode/starlette/pull/2525).
* Remove deprecated `app` argument passed to `httpx.Client` on the `TestClient` [#2526](https://github.com/encode/starlette/pull/2526).
## 0.37.1 (February 9, 2024)
#### Fixed
* Warn instead of raise for missing env file on `Config` [#2485](https://github.com/encode/starlette/pull/2485).
## 0.37.0 (February 5, 2024)
#### Added
* Support the WebSocket Denial Response ASGI extension [#2041](https://github.com/encode/starlette/pull/2041).
## 0.36.3 (February 4, 2024)
#### Fixed
* Create `anyio.Event` on async context [#2459](https://github.com/encode/starlette/pull/2459).
## 0.36.2 (February 3, 2024)
#### Fixed
* Upgrade `python-multipart` to `0.0.7` [13e5c26](http://github.com/encode/starlette/commit/13e5c26a27f4903924624736abd6131b2da80cc5).
* Avoid duplicate charset on `Content-Type` [#2443](https://github.com/encode/starlette/2443).
## 0.36.1 (January 23, 2024)
#### Fixed
* Check if "extensions" in scope before checking the extension [#2438](http://github.com/encode/starlette/pull/2438).
## 0.36.0 (January 22, 2024)
#### Added
* Add support for ASGI `pathsend` extension [#2435](http://github.com/encode/starlette/pull/2435).
* Cancel `WebSocketTestSession` on close [#2427](http://github.com/encode/starlette/pull/2427).
* Raise `WebSocketDisconnect` when `WebSocket.send()` excepts `IOError` [#2425](http://github.com/encode/starlette/pull/2425).
* Raise `FileNotFoundError` when the `env_file` parameter on `Config` is not valid [#2422](http://github.com/encode/starlette/pull/2422).
## 0.35.1 (January 11, 2024)
#### Fixed
* Stop using the deprecated "method" parameter in `FileResponse` inside of `StaticFiles` [#2406](https://github.com/encode/starlette/pull/2406).
* Make `typing-extensions` optional again [#2409](https://github.com/encode/starlette/pull/2409).
## 0.35.0 (January 11, 2024)
#### Added
* Add `*args` to `Middleware` and improve its type hints [#2381](https://github.com/encode/starlette/pull/2381).
#### Fixed
* Use `Iterable` instead `Iterator` on `iterate_in_threadpool` [#2362](https://github.com/encode/starlette/pull/2362).
#### Changes
* Handle `root_path` to keep compatibility with mounted ASGI applications and WSGI [#2400](https://github.com/encode/starlette/pull/2400).
* Turn `scope["client"]` to `None` on `TestClient` [#2377](https://github.com/encode/starlette/pull/2377).
## 0.34.0 (December 16, 2023)
### Added
* Use `ParamSpec` for `run_in_threadpool` [#2375](https://github.com/encode/starlette/pull/2375).
* Add `UploadFile.__repr__` [#2360](https://github.com/encode/starlette/pull/2360).
### Fixed
* Merge URLs properly on `TestClient` [#2376](https://github.com/encode/starlette/pull/2376).
* Take weak ETags in consideration on `StaticFiles` [#2334](https://github.com/encode/starlette/pull/2334).
### Deprecated
* Deprecate `FileResponse(method=...)` parameter [#2366](https://github.com/encode/starlette/pull/2366).
## 0.33.0 (December 1, 2023)
### Added
* Add `middleware` per `Route`/`WebSocketRoute` [#2349](https://github.com/encode/starlette/pull/2349).
* Add `middleware` per `Router` [#2351](https://github.com/encode/starlette/pull/2351).
### Fixed
* Do not overwrite `"path"` and `"root_path"` scope keys [#2352](https://github.com/encode/starlette/pull/2352).
* Set `ensure_ascii=False` on `json.dumps()` for `WebSocket.send_json()` [#2341](https://github.com/encode/starlette/pull/2341).
## 0.32.0.post1 (November 5, 2023)
### Fixed
* Revert mkdocs-material from 9.1.17 to 9.4.7 [#2326](https://github.com/encode/starlette/pull/2326).
## 0.32.0 (November 4, 2023)
### Added
* Send `reason` on `WebSocketDisconnect` [#2309](https://github.com/encode/starlette/pull/2309).
* Add `domain` parameter to `SessionMiddleware` [#2280](https://github.com/encode/starlette/pull/2280).
### Changed
* Inherit from `HTMLResponse` instead of `Response` on `_TemplateResponse` [#2274](https://github.com/encode/starlette/pull/2274).
* Restore the `Response.render` type annotation to its pre-0.31.0 state [#2264](https://github.com/encode/starlette/pull/2264).
## 0.31.1 (August 26, 2023)
### Fixed
* Fix import error when `exceptiongroup` isn't available [#2231](https://github.com/encode/starlette/pull/2231).
* Set `url_for` global for custom Jinja environments [#2230](https://github.com/encode/starlette/pull/2230).
## 0.31.0 (July 24, 2023)
### Added
* Officially support Python 3.12 [#2214](https://github.com/encode/starlette/pull/2214).
* Support AnyIO 4.0 [#2211](https://github.com/encode/starlette/pull/2211).
* Strictly type annotate Starlette (strict mode on mypy) [#2180](https://github.com/encode/starlette/pull/2180).
### Fixed
* Don't group duplicated headers on a single string when using the `TestClient` [#2219](https://github.com/encode/starlette/pull/2219).
## 0.30.0 (July 13, 2023)
### Removed
* Drop Python 3.7 support [#2178](https://github.com/encode/starlette/pull/2178).
## 0.29.0 (July 13, 2023)
### Added
* Add `follow_redirects` parameter to `TestClient` [#2207](https://github.com/encode/starlette/pull/2207).
* Add `__str__` to `HTTPException` and `WebSocketException` [#2181](https://github.com/encode/starlette/pull/2181).
* Warn users when using `lifespan` together with `on_startup`/`on_shutdown` [#2193](https://github.com/encode/starlette/pull/2193).
* Collect routes from `Host` to generate the OpenAPI schema [#2183](https://github.com/encode/starlette/pull/2183).
* Add `request` argument to `TemplateResponse` [#2191](https://github.com/encode/starlette/pull/2191).
### Fixed
* Stop `body_stream` in case `more_body=False` on `BaseHTTPMiddleware` [#2194](https://github.com/encode/starlette/pull/2194).
## 0.28.0 (June 7, 2023)
### Changed
* Reuse `Request`'s body buffer for call_next in `BaseHTTPMiddleware` [#1692](https://github.com/encode/starlette/pull/1692).
* Move exception handling logic to `Route` [#2026](https://github.com/encode/starlette/pull/2026).
### Added
* Add `env` parameter to `Jinja2Templates`, and deprecate `**env_options` [#2159](https://github.com/encode/starlette/pull/2159).
* Add clear error message when `httpx` is not installed [#2177](https://github.com/encode/starlette/pull/2177).
### Fixed
* Allow "name" argument on `templates url_for()` [#2127](https://github.com/encode/starlette/pull/2127).
## 0.27.0 (May 16, 2023)
This release fixes a path traversal vulnerability in `StaticFiles`. You can view the full security advisory:
https://github.com/encode/starlette/security/advisories/GHSA-v5gw-mw7f-84px
### Added
* Minify JSON websocket data via `send_json` https://github.com/encode/starlette/pull/2128
### Fixed
* Replace `commonprefix` by `commonpath` on `StaticFiles` [1797de4](https://github.com/encode/starlette/commit/1797de464124b090f10cf570441e8292936d63e3).
* Convert ImportErrors into ModuleNotFoundError [#2135](https://github.com/encode/starlette/pull/2135).
* Correct the RuntimeError message content in websockets [#2141](https://github.com/encode/starlette/pull/2141).
## 0.26.1 (March 13, 2023)
### Fixed
* Fix typing of Lifespan to allow subclasses of Starlette [#2077](https://github.com/encode/starlette/pull/2077).
## 0.26.0.post1 (March 9, 2023)
### Fixed
* Replace reference from Events to Lifespan on the mkdocs.yml [#2072](https://github.com/encode/starlette/pull/2072).
## 0.26.0 (March 9, 2023)
### Added
* Support [lifespan state](lifespan.md) [#2060](https://github.com/encode/starlette/pull/2060),
[#2065](https://github.com/encode/starlette/pull/2065) and [#2064](https://github.com/encode/starlette/pull/2064).
### Changed
* Change `url_for` signature to return a `URL` instance [#1385](https://github.com/encode/starlette/pull/1385).
### Fixed
* Allow "name" argument on `url_for()` and `url_path_for()` [#2050](https://github.com/encode/starlette/pull/2050).
### Deprecated
* Deprecate `on_startup` and `on_shutdown` events [#2070](https://github.com/encode/starlette/pull/2070).
## 0.25.0 (February 14, 2023)
### Fix
* Limit the number of fields and files when parsing `multipart/form-data` on the `MultipartParser` [8c74c2c](https://github.com/encode/starlette/commit/8c74c2c8dba7030154f8af18e016136bea1938fa) and [#2036](https://github.com/encode/starlette/pull/2036).
## 0.24.0 (February 6, 2023)
### Added
* Allow `StaticFiles` to follow symlinks [#1683](https://github.com/encode/starlette/pull/1683).
* Allow `Request.form()` as a context manager [#1903](https://github.com/encode/starlette/pull/1903).
* Add `size` attribute to `UploadFile` [#1405](https://github.com/encode/starlette/pull/1405).
* Add `env_prefix` argument to `Config` [#1990](https://github.com/encode/starlette/pull/1990).
* Add template context processors [#1904](https://github.com/encode/starlette/pull/1904).
* Support `str` and `datetime` on `expires` parameter on the `Response.set_cookie` method [#1908](https://github.com/encode/starlette/pull/1908).
### Changed
* Lazily build the middleware stack [#2017](https://github.com/encode/starlette/pull/2017).
* Make the `file` argument required on `UploadFile` [#1413](https://github.com/encode/starlette/pull/1413).
* Use debug extension instead of custom response template extension [#1991](https://github.com/encode/starlette/pull/1991).
### Fixed
* Fix url parsing of ipv6 urls on `URL.replace` [#1965](https://github.com/encode/starlette/pull/1965).
## 0.23.1 (December 9, 2022)
### Fixed
* Only stop receiving stream on `body_stream` if body is empty on the `BaseHTTPMiddleware` [#1940](https://github.com/encode/starlette/pull/1940).
## 0.23.0 (December 5, 2022)
### Added
* Add `headers` parameter to the `TestClient` [#1966](https://github.com/encode/starlette/pull/1966).
### Deprecated
* Deprecate `Starlette` and `Router` decorators [#1897](https://github.com/encode/starlette/pull/1897).
### Fixed
* Fix bug on `FloatConvertor` regex [#1973](https://github.com/encode/starlette/pull/1973).
## 0.22.0 (November 17, 2022)
### Changed
* Bypass `GZipMiddleware` when response includes `Content-Encoding` [#1901](https://github.com/encode/starlette/pull/1901).
### Fixed
* Remove unneeded `unquote()` from query parameters on the `TestClient` [#1953](https://github.com/encode/starlette/pull/1953).
* Make sure `MutableHeaders._list` is actually a `list` [#1917](https://github.com/encode/starlette/pull/1917).
* Import compatibility with the next version of `AnyIO` [#1936](https://github.com/encode/starlette/pull/1936).
## 0.21.0 (September 26, 2022)
This release replaces the underlying HTTP client used on the `TestClient` (`requests` :arrow_right: `httpx`), and as those clients [differ _a bit_ on their API](https://www.python-httpx.org/compatibility/), your test suite will likely break. To make the migration smoother, you can use the [`bump-testclient`](https://github.com/Kludex/bump-testclient) tool.
### Changed
* Replace `requests` with `httpx` in `TestClient` [#1376](https://github.com/encode/starlette/pull/1376).
### Added
* Add `WebSocketException` and support for WebSocket exception handlers [#1263](https://github.com/encode/starlette/pull/1263).
* Add `middleware` parameter to `Mount` class [#1649](https://github.com/encode/starlette/pull/1649).
* Officially support Python 3.11 [#1863](https://github.com/encode/starlette/pull/1863).
* Implement `__repr__` for route classes [#1864](https://github.com/encode/starlette/pull/1864).
### Fixed
* Fix bug on which `BackgroundTasks` were cancelled when using `BaseHTTPMiddleware` and client disconnected [#1715](https://github.com/encode/starlette/pull/1715).
## 0.20.4 (June 28, 2022)
### Fixed
* Remove converter from path when generating OpenAPI schema [#1648](https://github.com/encode/starlette/pull/1648).
## 0.20.3 (June 10, 2022)
### Fixed
* Revert "Allow `StaticFiles` to follow symlinks" [#1681](https://github.com/encode/starlette/pull/1681).
## 0.20.2 (June 7, 2022)
### Fixed
* Fix regression on route paths with colons [#1675](https://github.com/encode/starlette/pull/1675).
* Allow `StaticFiles` to follow symlinks [#1337](https://github.com/encode/starlette/pull/1377).
## 0.20.1 (May 28, 2022)
### Fixed
* Improve detection of async callables [#1444](https://github.com/encode/starlette/pull/1444).
* Send 400 (Bad Request) when `boundary` is missing [#1617](https://github.com/encode/starlette/pull/1617).
* Send 400 (Bad Request) when missing "name" field on `Content-Disposition` header [#1643](https://github.com/encode/starlette/pull/1643).
* Do not send empty data to `StreamingResponse` on `BaseHTTPMiddleware` [#1609](https://github.com/encode/starlette/pull/1609).
* Add `__bool__` dunder for `Secret` [#1625](https://github.com/encode/starlette/pull/1625).
## 0.20.0 (May 3, 2022)
### Removed
* Drop Python 3.6 support [#1357](https://github.com/encode/starlette/pull/1357) and [#1616](https://github.com/encode/starlette/pull/1616).
## 0.19.1 (April 22, 2022)
### Fixed
* Fix inference of `Route.name` when created from methods [#1553](https://github.com/encode/starlette/pull/1553).
* Avoid `TypeError` on `websocket.disconnect` when code is `None` [#1574](https://github.com/encode/starlette/pull/1574).
### Deprecated
* Deprecate `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` in favor of `WS_1005_NO_STATUS_RCVD` and `WS_1006_ABNORMAL_CLOSURE`, as the previous constants didn't match the [WebSockets specs](https://www.iana.org/assignments/websocket/websocket.xhtml) [#1580](https://github.com/encode/starlette/pull/1580).
## 0.19.0 (March 9, 2022)
### Added
* Error handler will always run, even if the error happens on a background task [#761](https://github.com/encode/starlette/pull/761).
* Add `headers` parameter to `HTTPException` [#1435](https://github.com/encode/starlette/pull/1435).
* Internal responses with `405` status code insert an `Allow` header, as described by [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.5) [#1436](https://github.com/encode/starlette/pull/1436).
* The `content` argument in `JSONResponse` is now required [#1431](https://github.com/encode/starlette/pull/1431).
* Add custom URL convertor register [#1437](https://github.com/encode/starlette/pull/1437).
* Add content disposition type parameter to `FileResponse` [#1266](https://github.com/encode/starlette/pull/1266).
* Add next query param with original request URL in requires decorator [#920](https://github.com/encode/starlette/pull/920).
* Add `raw_path` to `TestClient` scope [#1445](https://github.com/encode/starlette/pull/1445).
* Add union operators to `MutableHeaders` [#1240](https://github.com/encode/starlette/pull/1240).
* Display missing route details on debug page [#1363](https://github.com/encode/starlette/pull/1363).
* Change `anyio` required version range to `>=3.4.0,<5.0` [#1421](https://github.com/encode/starlette/pull/1421) and [#1460](https://github.com/encode/starlette/pull/1460).
* Add `typing-extensions>=3.10` requirement - used only on lower versions than Python 3.10 [#1475](https://github.com/encode/starlette/pull/1475).
### Fixed
* Prevent `BaseHTTPMiddleware` from hiding errors of `StreamingResponse` and mounted applications [#1459](https://github.com/encode/starlette/pull/1459).
* `SessionMiddleware` uses an explicit `path=...`, instead of defaulting to the ASGI 'root_path' [#1512](https://github.com/encode/starlette/pull/1512).
* `Request.client` is now compliant with the ASGI specifications [#1462](https://github.com/encode/starlette/pull/1462).
* Raise `KeyError` at early stage for missing boundary [#1349](https://github.com/encode/starlette/pull/1349).
### Deprecated
* Deprecate WSGIMiddleware in favor of a2wsgi [#1504](https://github.com/encode/starlette/pull/1504).
* Deprecate `run_until_first_complete` [#1443](https://github.com/encode/starlette/pull/1443).
## 0.18.0 (January 23, 2022)
### Added
* Change default chunk size from 4Kb to 64Kb on `FileResponse` [#1345](https://github.com/encode/starlette/pull/1345).
* Add support for `functools.partial` in `WebSocketRoute` [#1356](https://github.com/encode/starlette/pull/1356).
* Add `StaticFiles` packages with directory [#1350](https://github.com/encode/starlette/pull/1350).
* Allow environment options in `Jinja2Templates` [#1401](https://github.com/encode/starlette/pull/1401).
* Allow HEAD method on `HttpEndpoint` [#1346](https://github.com/encode/starlette/pull/1346).
* Accept additional headers on `websocket.accept` message [#1361](https://github.com/encode/starlette/pull/1361) and [#1422](https://github.com/encode/starlette/pull/1422).
* Add `reason` to `WebSocket` close ASGI event [#1417](https://github.com/encode/starlette/pull/1417).
* Add headers attribute to `UploadFile` [#1382](https://github.com/encode/starlette/pull/1382).
* Don't omit `Content-Length` header for `Content-Length: 0` cases [#1395](https://github.com/encode/starlette/pull/1395).
* Don't set headers for responses with 1xx, 204 and 304 status code [#1397](https://github.com/encode/starlette/pull/1397).
* `SessionMiddleware.max_age` now accepts `None`, so cookie can last as long as the browser session [#1387](https://github.com/encode/starlette/pull/1387).
### Fixed
* Tweak `hashlib.md5()` function on `FileResponse`s ETag generation. The parameter [`usedforsecurity`](https://bugs.python.org/issue9216) flag is set to `False`, if the flag is available on the system. This fixes an error raised on systems with [FIPS](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/FIPS_Mode_-_an_explanation) enabled [#1366](https://github.com/encode/starlette/pull/1366) and [#1410](https://github.com/encode/starlette/pull/1410).
* Fix `path_params` type on `url_path_for()` method i.e. turn `str` into `Any` [#1341](https://github.com/encode/starlette/pull/1341).
* `Host` now ignores `port` on routing [#1322](https://github.com/encode/starlette/pull/1322).
## 0.17.1 (November 17, 2021)
### Fixed
* Fix `IndexError` in authentication `requires` when wrapped function arguments are distributed between `*args` and `**kwargs` [#1335](https://github.com/encode/starlette/pull/1335).
## 0.17.0 (November 4, 2021)
### Added
* `Response.delete_cookie` now accepts the same parameters as `Response.set_cookie` [#1228](https://github.com/encode/starlette/pull/1228).
* Update the `Jinja2Templates` constructor to allow `PathLike` [#1292](https://github.com/encode/starlette/pull/1292).
### Fixed
* Fix BadSignature exception handling in SessionMiddleware [#1264](https://github.com/encode/starlette/pull/1264).
* Change `HTTPConnection.__getitem__` return type from `str` to `typing.Any` [#1118](https://github.com/encode/starlette/pull/1118).
* Change `ImmutableMultiDict.getlist` return type from `typing.List[str]` to `typing.List[typing.Any]` [#1235](https://github.com/encode/starlette/pull/1235).
* Handle `OSError` exceptions on `StaticFiles` [#1220](https://github.com/encode/starlette/pull/1220).
* Fix `StaticFiles` 404.html in HTML mode [#1314](https://github.com/encode/starlette/pull/1314).
* Prevent anyio.ExceptionGroup in error views under a BaseHTTPMiddleware [#1262](https://github.com/encode/starlette/pull/1262).
### Removed
* Remove GraphQL support [#1198](https://github.com/encode/starlette/pull/1198).
## 0.16.0 (July 19, 2021)
### Added
* Added [Encode](https://github.com/sponsors/encode) funding option
[#1219](https://github.com/encode/starlette/pull/1219)
### Fixed
* `starlette.websockets.WebSocket` instances are now hashable and compare by identity
[#1039](https://github.com/encode/starlette/pull/1039)
* A number of fixes related to running task groups in lifespan
[#1213](https://github.com/encode/starlette/pull/1213),
[#1227](https://github.com/encode/starlette/pull/1227)
### Deprecated/removed
* The method `starlette.templates.Jinja2Templates.get_env` was removed
[#1218](https://github.com/encode/starlette/pull/1218)
* The ClassVar `starlette.testclient.TestClient.async_backend` was removed,
the backend is now configured using constructor kwargs
[#1211](https://github.com/encode/starlette/pull/1211)
* Passing an Async Generator Function or a Generator Function to `starlette.routing.Router(lifespan=)` is deprecated. You should wrap your lifespan in `@contextlib.asynccontextmanager`.
[#1227](https://github.com/encode/starlette/pull/1227)
[#1110](https://github.com/encode/starlette/pull/1110)
## 0.15.0 (June 23, 2021)
This release includes major changes to the low-level asynchronous parts of Starlette. As a result,
**Starlette now depends on [AnyIO](https://anyio.readthedocs.io/en/stable/)** and some minor API
changes have occurred. Another significant change with this release is the
**deprecation of built-in GraphQL support**.
### Added
* Starlette now supports [Trio](https://trio.readthedocs.io/en/stable/) as an async runtime via
AnyIO - [#1157](https://github.com/encode/starlette/pull/1157).
* `TestClient.websocket_connect()` now must be used as a context manager.
* Initial support for Python 3.10 - [#1201](https://github.com/encode/starlette/pull/1201).
* The compression level used in `GZipMiddleware` is now adjustable -
[#1128](https://github.com/encode/starlette/pull/1128).
### Fixed
* Several fixes to `CORSMiddleware`. See [#1111](https://github.com/encode/starlette/pull/1111),
[#1112](https://github.com/encode/starlette/pull/1112),
[#1113](https://github.com/encode/starlette/pull/1113),
[#1199](https://github.com/encode/starlette/pull/1199).
* Improved exception messages in the case of duplicated path parameter names -
[#1177](https://github.com/encode/starlette/pull/1177).
* `RedirectResponse` now uses `quote` instead of `quote_plus` encoding for the `Location` header
to better match the behaviour in other frameworks such as Django -
[#1164](https://github.com/encode/starlette/pull/1164).
* Exception causes are now preserved in more cases -
[#1158](https://github.com/encode/starlette/pull/1158).
* Session cookies now use the ASGI root path in the case of mounted applications -
[#1147](https://github.com/encode/starlette/pull/1147).
* Fixed a cache invalidation bug when static files were deleted in certain circumstances -
[#1023](https://github.com/encode/starlette/pull/1023).
* Improved memory usage of `BaseHTTPMiddleware` when handling large responses -
[#1012](https://github.com/encode/starlette/issues/1012) fixed via #1157
### Deprecated/removed
* Built-in GraphQL support via the `GraphQLApp` class has been deprecated and will be removed in a
future release. Please see [#619](https://github.com/encode/starlette/issues/619). GraphQL is not
supported on Python 3.10.
* The `executor` parameter to `GraphQLApp` was removed. Use `executor_class` instead.
* The `workers` parameter to `WSGIMiddleware` was removed. This hasn't had any effect since
Starlette v0.6.3.
## 0.14.2 (February 2, 2021)
### Fixed
* Fixed `ServerErrorMiddleware` compatibility with Python 3.9.1/3.8.7 when debug mode is enabled -
[#1132](https://github.com/encode/starlette/pull/1132).
* Fixed unclosed socket `ResourceWarning`s when using the `TestClient` with WebSocket endpoints -
#1132.
* Improved detection of `async` endpoints wrapped in `functools.partial` on Python 3.8+ -
[#1106](https://github.com/encode/starlette/pull/1106).
## 0.14.1 (November 9th, 2020)
### Removed
* `UJSONResponse` was removed (this change was intended to be included in 0.14.0). Please see the
[documentation](https://www.starlette.io/responses/#custom-json-serialization) for how to
implement responses using custom JSON serialization -
[#1074](https://github.com/encode/starlette/pull/1047).
## 0.14.0 (November 8th, 2020)
### Added
* Starlette now officially supports Python3.9.
* In `StreamingResponse`, allow custom async iterator such as objects from classes implementing `__aiter__`.
* Allow usage of `functools.partial` async handlers in Python versions 3.6 and 3.7.
* Add 418 I'm A Teapot status code.
### Changed
* Create tasks from handler coroutines before sending them to `asyncio.wait`.
* Use `format_exception` instead of `format_tb` in `ServerErrorMiddleware`'s `debug` responses.
* Be more lenient with handler arguments when using the `requires` decorator.
## 0.13.8
* Revert `Queue(maxsize=1)` fix for `BaseHTTPMiddleware` middleware classes and streaming responses.
* The `StaticFiles` constructor now allows `pathlib.Path` in addition to strings for its `directory` argument.
## 0.13.7
* Fix high memory usage when using `BaseHTTPMiddleware` middleware classes and streaming responses.
## 0.13.6
* Fix 404 errors with `StaticFiles`.
## 0.13.5
* Add support for `Starlette(lifespan=...)` functions.
* More robust path-traversal check in StaticFiles app.
* Fix WSGI PATH_INFO encoding.
* RedirectResponse now accepts optional background parameter
* Allow path routes to contain regex meta characters
* Treat ASGI HTTP 'body' as an optional key.
* Don't use thread pooling for writing to in-memory upload files.
## 0.13.0
* Switch to promoting application configuration on init style everywhere.
This means dropping the decorator style in favour of declarative routing
tables and middleware definitions.
## 0.12.12
* Fix `request.url_for()` for the Mount-within-a-Mount case.
## 0.12.11
* Fix `request.url_for()` when an ASGI `root_path` is being used.
## 0.12.1
* Add `URL.include_query_params(**kwargs)`
* Add `URL.replace_query_params(**kwargs)`
* Add `URL.remove_query_params(param_names)`
* `request.state` properly persisting across middleware.
* Added `request.scope` interface.
## 0.12.0
* Switch to ASGI 3.0.
* Fixes to CORS middleware.
* Add `StaticFiles(html=True)` support.
* Fix path quoting in redirect responses.
## 0.11.1
* Add `request.state` interface, for storing arbitrary additional information.
* Support disabling GraphiQL with `GraphQLApp(..., graphiql=False)`.
## 0.11.0
* `DatabaseMiddleware` is now dropped in favour of `databases`
* Templates are no longer configured on the application instance. Use `templates = Jinja2Templates(directory=...)` and `return templates.TemplateResponse('index.html', {"request": request})`
* Schema generation is no longer attached to the application instance. Use `schemas = SchemaGenerator(...)` and `return schemas.OpenAPIResponse(request=request)`
* `LifespanMiddleware` is dropped in favor of router-based lifespan handling.
* Application instances now accept a `routes` argument, `Starlette(routes=[...])`
* Schema generation now includes mounted routes.
## 0.10.6
* Add `Lifespan` routing component.
## 0.10.5
* Ensure `templating` does not strictly require `jinja2` to be installed.
## 0.10.4
* Templates are now configured independently from the application instance. `templates = Jinja2Templates(directory=...)`. Existing API remains in place, but is no longer documented,
and will be deprecated in due course. See the template documentation for more details.
## 0.10.3
* Move to independent `databases` package instead of `DatabaseMiddleware`. Existing API
remains in place, but is no longer documented, and will be deprecated in due course.
## 0.10.2
* Don't drop explicit port numbers on redirects from `HTTPSRedirectMiddleware`.
## 0.10.1
* Add MySQL database support.
* Add host-based routing.
## 0.10.0
* WebSockets now default to sending/receiving JSON over text data frames. Use `.send_json(data, mode="binary")` and `.receive_json(mode="binary")` for binary framing.
* `GraphQLApp` now takes an `executor_class` argument, which should be used in preference to the existing `executor` argument. Resolves an issue with async executors being instantiated before the event loop was setup. The `executor` argument is expected to be deprecated in the next median or major release.
* Authentication and the `@requires` decorator now support WebSocket endpoints.
* `MultiDict` and `ImmutableMultiDict` classes are available in `uvicorn.datastructures`.
* `QueryParams` is now instantiated with standard dict-style `*args, **kwargs` arguments.
## 0.9.11
* Session cookies now include browser 'expires', in addition to the existing signed expiry.
* `request.form()` now returns a multi-dict interface.
* The query parameter multi-dict implementation now mirrors `dict` more correctly for the
behavior of `.keys()`, `.values()`, and `.items()` when multiple same-key items occur.
* Use `urlsplit` throughout in favor of `urlparse`.
## 0.9.10
* Support `@requires(...)` on class methods.
* Apply URL escaping to form data.
* Support `HEAD` requests automatically.
* Add `await request.is_disconnected()`.
* Pass operationName to GraphQL executor.
## 0.9.9
* Add `TemplateResponse`.
* Add `CommaSeparatedStrings` datatype.
* Add `BackgroundTasks` for multiple tasks.
* Common subclass for `Request` and `WebSocket`, to eg. share `session` functionality.
* Expose remote address with `request.client`.
## 0.9.8
* Add `request.database.executemany`.
## 0.9.7
* Ensure that `AuthenticationMiddleware` handles lifespan messages correctly.
## 0.9.6
* Add `AuthenticationMiddleware`, and `@requires()` decorator.
## 0.9.5
* Support either `str` or `Secret` for `SessionMiddleware(secret_key=...)`.
## 0.9.4
* Add `config.environ`.
* Add `datastructures.Secret`.
* Add `datastructures.DatabaseURL`.
## 0.9.3
* Add `config.Config(".env")`
## 0.9.2
* Add optional database support.
* Add `request` to GraphQL context.
* Hide any password component in `URL.__repr__`.
## 0.9.1
* Handle startup/shutdown errors properly.
## 0.9.0
* `TestClient` can now be used as a context manager, instead of `LifespanContext`.
* Lifespan is now handled as middleware. Startup and Shutdown events are
visible throughout the middleware stack.
## 0.8.8
* Better support for third-party API schema generators.
## 0.8.7
* Support chunked requests with TestClient.
* Cleanup asyncio tasks properly with WSGIMiddleware.
* Support using TestClient within endpoints, for service mocking.
## 0.8.6
* Session cookies are now set on the root path.
## 0.8.5
* Support URL convertors.
* Support HTTP 304 cache responses from `StaticFiles`.
* Resolve character escaping issue with form data.
## 0.8.4
* Default to empty body on responses.
## 0.8.3
* Add 'name' argument to `@app.route()`.
* Use 'Host' header for URL reconstruction.
## 0.8.2
### StaticFiles
* StaticFiles no longer reads the file for responses to `HEAD` requests.
## 0.8.1
### Templating
* Add a default templating configuration with Jinja2.
Allows the following:
```python
app = Starlette(template_directory="templates")
@app.route('/')
async def homepage(request):
# `url_for` is available inside the template.
template = app.get_template('index.html')
content = template.render(request=request)
return HTMLResponse(content)
```
## 0.8.0
### Exceptions
* Add support for `@app.exception_handler(404)`.
* Ensure handled exceptions are not seen as errors by the middleware stack.
### SessionMiddleware
* Add `max_age`, and use timestamp-signed cookies. Defaults to two weeks.
### Cookies
* Ensure cookies are strictly HTTP correct.
### StaticFiles
* Check directory exists on instantiation.
## 0.7.4
### Concurrency
* Add `starlette.concurrency.run_in_threadpool`. Now handles `contextvar` support.
## 0.7.3
### Routing
* Add `name=` support to `app.mount()`. This allows eg: `app.mount('/static', StaticFiles(directory='static'), name='static')`.
## 0.7.2
### Middleware
* Add support for `@app.middleware("http")` decorator.
### Routing
* Add "endpoint" to ASGI scope.
## 0.7.1
### Debug tracebacks
* Improve debug traceback information & styling.
### URL routing
* Support mounted URL lookups with "path=", eg. `url_for('static', path=...)`.
* Support nested URL lookups, eg. `url_for('admin:user', username=...)`.
* Add redirect slashes support.
* Add www redirect support.
### Background tasks
* Add background task support to `FileResponse` and `StreamingResponse`.
## 0.7.0
### API Schema support
* Add `app.schema_generator = SchemaGenerator(...)`.
* Add `app.schema` property.
* Add `OpenAPIResponse(...)`.
### GraphQL routing
* Drop `app.add_graphql_route("/", ...)` in favor of more consistent `app.add_route("/", GraphQLApp(...))`.
## 0.6.3
### Routing API
* Support routing to methods.
* Ensure `url_path_for` works with Mount('/{some_path_params}').
* Fix Router(default=) argument.
* Support repeated paths, like: `@app.route("/", methods=["GET"])`, `@app.route("/", methods=["POST"])`
* Use the default ThreadPoolExecutor for all sync endpoints.
## 0.6.2
### SessionMiddleware
Added support for `request.session`, with `SessionMiddleware`.
## 0.6.1
### BaseHTTPMiddleware
Added support for `BaseHTTPMiddleware`, which provides a standard
request/response interface over a regular ASGI middleware.
This means you can write ASGI middleware while still working at
a request/response level, rather than handling ASGI messages directly.
```python
from starlette.applications import Starlette
from starlette.middleware.base import BaseHTTPMiddleware
class CustomMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers['Custom-Header'] = 'Example'
return response
app = Starlette()
app.add_middleware(CustomMiddleware)
```
## 0.6.0
### request.path_params
The biggest change in 0.6 is that endpoint signatures are no longer:
```python
async def func(request: Request, **kwargs) -> Response
```
Instead we just use:
```python
async def func(request: Request) -> Response
```
The path parameters are available on the request as `request.path_params`.
This is different to most Python webframeworks, but I think it actually ends up
being much more nicely consistent all the way through.
### request.url_for()
Request and WebSocketSession now support URL reversing with `request.url_for(name, **path_params)`.
This method returns a fully qualified `URL` instance.
The URL instance is a string-like object.
### app.url_path_for()
Applications now support URL path reversing with `app.url_path_for(name, **path_params)`.
This method returns a `URL` instance with the path and scheme set.
The URL instance is a string-like object, and will return only the path if coerced to a string.
### app.routes
Applications now support a `.routes` parameter, which returns a list of `[Route|WebSocketRoute|Mount]`.
### Route, WebSocketRoute, Mount
The low level components to `Router` now match the `@app.route()`, `@app.websocket_route()`, and `app.mount()` signatures.
starlette-0.46.1/docs/requests.md 0000644 0000000 0000000 00000015150 13615410400 013660 0 ustar 00
Starlette includes a `Request` class that gives you a nicer interface onto
the incoming request, rather than accessing the ASGI scope and receive channel directly.
### Request
Signature: `Request(scope, receive=None)`
```python
from starlette.requests import Request
from starlette.responses import Response
async def app(scope, receive, send):
assert scope['type'] == 'http'
request = Request(scope, receive)
content = '%s %s' % (request.method, request.url.path)
response = Response(content, media_type='text/plain')
await response(scope, receive, send)
```
Requests present a mapping interface, so you can use them in the same
way as a `scope`.
For instance: `request['path']` will return the ASGI path.
If you don't need to access the request body you can instantiate a request
without providing an argument to `receive`.
#### Method
The request method is accessed as `request.method`.
#### URL
The request URL is accessed as `request.url`.
The property is a string-like object that exposes all the
components that can be parsed out of the URL.
For example: `request.url.path`, `request.url.port`, `request.url.scheme`.
#### Headers
Headers are exposed as an immutable, case-insensitive, multi-dict.
For example: `request.headers['content-type']`
#### Query Parameters
Query parameters are exposed as an immutable multi-dict.
For example: `request.query_params['search']`
#### Path Parameters
Router path parameters are exposed as a dictionary interface.
For example: `request.path_params['username']`
#### Client Address
The client's remote address is exposed as a named two-tuple `request.client` (or `None`).
The hostname or IP address: `request.client.host`
The port number from which the client is connecting: `request.client.port`
#### Cookies
Cookies are exposed as a regular dictionary interface.
For example: `request.cookies.get('mycookie')`
Cookies are ignored in case of an invalid cookie. (RFC2109)
#### Body
There are a few different interfaces for returning the body of the request:
The request body as bytes: `await request.body()`
The request body, parsed as form data or multipart: `async with request.form() as form:`
The request body, parsed as JSON: `await request.json()`
You can also access the request body as a stream, using the `async for` syntax:
```python
from starlette.requests import Request
from starlette.responses import Response
async def app(scope, receive, send):
assert scope['type'] == 'http'
request = Request(scope, receive)
body = b''
async for chunk in request.stream():
body += chunk
response = Response(body, media_type='text/plain')
await response(scope, receive, send)
```
If you access `.stream()` then the byte chunks are provided without storing
the entire body to memory. Any subsequent calls to `.body()`, `.form()`, or `.json()`
will raise an error.
In some cases such as long-polling, or streaming responses you might need to
determine if the client has dropped the connection. You can determine this
state with `disconnected = await request.is_disconnected()`.
#### Request Files
Request files are normally sent as multipart form data (`multipart/form-data`).
Signature: `request.form(max_files=1000, max_fields=1000, max_part_size=1024*1024)`
You can configure the number of maximum fields or files with the parameters `max_files` and `max_fields`; and part size using `max_part_size`:
```python
async with request.form(max_files=1000, max_fields=1000, max_part_size=1024*1024):
...
```
!!! info
These limits are for security reasons, allowing an unlimited number of fields or files could lead to a denial of service attack by consuming a lot of CPU and memory parsing too many empty fields.
When you call `async with request.form() as form` you receive a `starlette.datastructures.FormData` which is an immutable
multidict, containing both file uploads and text input. File upload items are represented as instances of `starlette.datastructures.UploadFile`.
`UploadFile` has the following attributes:
* `filename`: An `str` with the original file name that was uploaded or `None` if its not available (e.g. `myimage.jpg`).
* `content_type`: An `str` with the content type (MIME type / media type) or `None` if it's not available (e.g. `image/jpeg`).
* `file`: A `SpooledTemporaryFile` (a file-like object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.
* `headers`: A `Headers` object. Often this will only be the `Content-Type` header, but if additional headers were included in the multipart field they will be included here. Note that these headers have no relationship with the headers in `Request.headers`.
* `size`: An `int` with uploaded file's size in bytes. This value is calculated from request's contents, making it better choice to find uploaded file's size than `Content-Length` header. `None` if not set.
`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`).
* `async write(data)`: Writes `data` (`bytes`) to the file.
* `async read(size)`: Reads `size` (`int`) bytes of the file.
* `async seek(offset)`: Goes to the byte position `offset` (`int`) in the file.
* E.g., `await myfile.seek(0)` would go to the start of the file.
* `async close()`: Closes the file.
As all these methods are `async` methods, you need to "await" them.
For example, you can get the file name and the contents with:
```python
async with request.form() as form:
filename = form["upload_file"].filename
contents = await form["upload_file"].read()
```
!!! info
As settled in [RFC-7578: 4.2](https://www.ietf.org/rfc/rfc7578.txt), form-data content part that contains file
assumed to have `name` and `filename` fields in `Content-Disposition` header: `Content-Disposition: form-data;
name="user"; filename="somefile"`. Though `filename` field is optional according to RFC-7578, it helps
Starlette to differentiate which data should be treated as file. If `filename` field was supplied, `UploadFile`
object will be created to access underlying file, otherwise form-data part will be parsed and available as a raw
string.
#### Application
The originating Starlette application can be accessed via `request.app`.
#### Other state
If you want to store additional information on the request you can do so
using `request.state`.
For example:
`request.state.time_started = time.time()`
starlette-0.46.1/docs/responses.md 0000644 0000000 0000000 00000015666 13615410400 014042 0 ustar 00
Starlette includes a few response classes that handle sending back the
appropriate ASGI messages on the `send` channel.
### Response
Signature: `Response(content, status_code=200, headers=None, media_type=None)`
* `content` - A string or bytestring.
* `status_code` - An integer HTTP status code.
* `headers` - A dictionary of strings.
* `media_type` - A string giving the media type. eg. "text/html"
Starlette will automatically include a Content-Length header. It will also
include a Content-Type header, based on the media_type and appending a charset
for text types, unless a charset has already been specified in the `media_type`.
Once you've instantiated a response, you can send it by calling it as an
ASGI application instance.
```python
from starlette.responses import Response
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = Response('Hello, world!', media_type='text/plain')
await response(scope, receive, send)
```
#### Set Cookie
Starlette provides a `set_cookie` method to allow you to set cookies on the response object.
Signature: `Response.set_cookie(key, value, max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite="lax")`
* `key` - A string that will be the cookie's key.
* `value` - A string that will be the cookie's value.
* `max_age` - An integer that defines the lifetime of the cookie in seconds. A negative integer or a value of `0` will discard the cookie immediately. `Optional`
* `expires` - Either an integer that defines the number of seconds until the cookie expires, or a datetime. `Optional`
* `path` - A string that specifies the subset of routes to which the cookie will apply. `Optional`
* `domain` - A string that specifies the domain for which the cookie is valid. `Optional`
* `secure` - A bool indicating that the cookie will only be sent to the server if request is made using SSL and the HTTPS protocol. `Optional`
* `httponly` - A bool indicating that the cookie cannot be accessed via JavaScript through `Document.cookie` property, the `XMLHttpRequest` or `Request` APIs. `Optional`
* `samesite` - A string that specifies the samesite strategy for the cookie. Valid values are `'lax'`, `'strict'` and `'none'`. Defaults to `'lax'`. `Optional`
#### Delete Cookie
Conversely, Starlette also provides a `delete_cookie` method to manually expire a set cookie.
Signature: `Response.delete_cookie(key, path='/', domain=None)`
### HTMLResponse
Takes some text or bytes and returns an HTML response.
```python
from starlette.responses import HTMLResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = HTMLResponse('
Hello, world!
')
await response(scope, receive, send)
```
### PlainTextResponse
Takes some text or bytes and returns a plain text response.
```python
from starlette.responses import PlainTextResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = PlainTextResponse('Hello, world!')
await response(scope, receive, send)
```
### JSONResponse
Takes some data and returns an `application/json` encoded response.
```python
from starlette.responses import JSONResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = JSONResponse({'hello': 'world'})
await response(scope, receive, send)
```
#### Custom JSON serialization
If you need fine-grained control over JSON serialization, you can subclass
`JSONResponse` and override the `render` method.
For example, if you wanted to use a third-party JSON library such as
[orjson](https://pypi.org/project/orjson/):
```python
from typing import Any
import orjson
from starlette.responses import JSONResponse
class OrjsonResponse(JSONResponse):
def render(self, content: Any) -> bytes:
return orjson.dumps(content)
```
In general you *probably* want to stick with `JSONResponse` by default unless
you are micro-optimising a particular endpoint or need to serialize non-standard
object types.
### RedirectResponse
Returns an HTTP redirect. Uses a 307 status code by default.
```python
from starlette.responses import PlainTextResponse, RedirectResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
if scope['path'] != '/':
response = RedirectResponse(url='/')
else:
response = PlainTextResponse('Hello, world!')
await response(scope, receive, send)
```
### StreamingResponse
Takes an async generator or a normal generator/iterator and streams the response body.
```python
from starlette.responses import StreamingResponse
import asyncio
async def slow_numbers(minimum, maximum):
yield '
'
for number in range(minimum, maximum + 1):
yield '
%d
' % number
await asyncio.sleep(0.5)
yield '
'
async def app(scope, receive, send):
assert scope['type'] == 'http'
generator = slow_numbers(1, 10)
response = StreamingResponse(generator, media_type='text/html')
await response(scope, receive, send)
```
Have in mind that file-like objects (like those created by `open()`) are normal iterators. So, you can return them directly in a `StreamingResponse`.
### FileResponse
Asynchronously streams a file as the response.
Takes a different set of arguments to instantiate than the other response types:
* `path` - The filepath to the file to stream.
* `headers` - Any custom headers to include, as a dictionary.
* `media_type` - A string giving the media type. If unset, the filename or path will be used to infer a media type.
* `filename` - If set, this will be included in the response `Content-Disposition`.
* `content_disposition_type` - will be included in the response `Content-Disposition`. Can be set to "attachment" (default) or "inline".
File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers.
```python
from starlette.responses import FileResponse
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = FileResponse('statics/favicon.ico')
await response(scope, receive, send)
```
File responses also supports [HTTP range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests).
The `Accept-Ranges: bytes` header will be included in the response if the file exists. For now, only the `bytes`
range unit is supported.
If the request includes a `Range` header, and the file exists, the response will be a `206 Partial Content` response
with the requested range of bytes. If the range is invalid, the response will be a `416 Range Not Satisfiable` response.
## Third party responses
#### [EventSourceResponse](https://github.com/sysid/sse-starlette)
A response class that implements [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html). It enables event streaming from the server to the client without the complexity of websockets.
starlette-0.46.1/docs/routing.md 0000644 0000000 0000000 00000021312 13615410400 013471 0 ustar 00 ## HTTP Routing
Starlette has a simple but capable request routing system. A routing table
is defined as a list of routes, and passed when instantiating the application.
```python
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("Homepage")
async def about(request):
return PlainTextResponse("About")
routes = [
Route("/", endpoint=homepage),
Route("/about", endpoint=about),
]
app = Starlette(routes=routes)
```
The `endpoint` argument can be one of:
* A regular function or async function, which accepts a single `request`
argument and which should return a response.
* A class that implements the ASGI interface, such as Starlette's [HTTPEndpoint](endpoints.md#httpendpoint).
## Path Parameters
Paths can use URI templating style to capture path components.
```python
Route('/users/{username}', user)
```
By default this will capture characters up to the end of the path or the next `/`.
You can use convertors to modify what is captured. The available convertors are:
* `str` returns a string, and is the default.
* `int` returns a Python integer.
* `float` returns a Python float.
* `uuid` return a Python `uuid.UUID` instance.
* `path` returns the rest of the path, including any additional `/` characters.
Convertors are used by prefixing them with a colon, like so:
```python
Route('/users/{user_id:int}', user)
Route('/floating-point/{number:float}', floating_point)
Route('/uploaded/{rest_of_path:path}', uploaded)
```
If you need a different converter that is not defined, you can create your own.
See below an example on how to create a `datetime` convertor, and how to register it:
```python
from datetime import datetime
from starlette.convertors import Convertor, register_url_convertor
class DateTimeConvertor(Convertor):
regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?"
def convert(self, value: str) -> datetime:
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")
def to_string(self, value: datetime) -> str:
return value.strftime("%Y-%m-%dT%H:%M:%S")
register_url_convertor("datetime", DateTimeConvertor())
```
After registering it, you'll be able to use it as:
```python
Route('/history/{date:datetime}', history)
```
Path parameters are made available in the request, as the `request.path_params`
dictionary.
```python
async def user(request):
user_id = request.path_params['user_id']
...
```
## Handling HTTP methods
Routes can also specify which HTTP methods are handled by an endpoint:
```python
Route('/users/{user_id:int}', user, methods=["GET", "POST"])
```
By default function endpoints will only accept `GET` requests, unless specified.
## Submounting routes
In large applications you might find that you want to break out parts of the
routing table, based on a common path prefix.
```python
routes = [
Route('/', homepage),
Mount('/users', routes=[
Route('/', users, methods=['GET', 'POST']),
Route('/{username}', user),
])
]
```
This style allows you to define different subsets of the routing table in
different parts of your project.
```python
from myproject import users, auth
routes = [
Route('/', homepage),
Mount('/users', routes=users.routes),
Mount('/auth', routes=auth.routes),
]
```
You can also use mounting to include sub-applications within your Starlette
application. For example...
```python
# This is a standalone static files server:
app = StaticFiles(directory="static")
# This is a static files server mounted within a Starlette application,
# underneath the "/static" path.
routes = [
...
Mount("/static", app=StaticFiles(directory="static"), name="static")
]
app = Starlette(routes=routes)
```
## Reverse URL lookups
You'll often want to be able to generate the URL for a particular route,
such as in cases where you need to return a redirect response.
* Signature: `url_for(name, **path_params) -> URL`
```python
routes = [
Route("/", homepage, name="homepage")
]
# We can use the following to return a URL...
url = request.url_for("homepage")
```
URL lookups can include path parameters...
```python
routes = [
Route("/users/{username}", user, name="user_detail")
]
# We can use the following to return a URL...
url = request.url_for("user_detail", username=...)
```
If a `Mount` includes a `name`, then submounts should use a `{prefix}:{name}`
style for reverse URL lookups.
```python
routes = [
Mount("/users", name="users", routes=[
Route("/", user, name="user_list"),
Route("/{username}", user, name="user_detail")
])
]
# We can use the following to return URLs...
url = request.url_for("users:user_list")
url = request.url_for("users:user_detail", username=...)
```
Mounted applications may include a `path=...` parameter.
```python
routes = [
...
Mount("/static", app=StaticFiles(directory="static"), name="static")
]
# We can use the following to return URLs...
url = request.url_for("static", path="/css/base.css")
```
For cases where there is no `request` instance, you can make reverse lookups
against the application, although these will only return the URL path.
```python
url = app.url_path_for("user_detail", username=...)
```
## Host-based routing
If you want to use different routes for the same path based on the `Host` header.
Note that port is removed from the `Host` header when matching.
For example, `Host (host='example.org:3600', ...)` will be processed
even if the `Host` header contains or does not contain a port other than `3600`
(`example.org:5600`, `example.org`).
Therefore, you can specify the port if you need it for use in `url_for`.
There are several ways to connect host-based routes to your application
```python
site = Router() # Use eg. `@site.route()` to configure this.
api = Router() # Use eg. `@api.route()` to configure this.
news = Router() # Use eg. `@news.route()` to configure this.
routes = [
Host('api.example.org', api, name="site_api")
]
app = Starlette(routes=routes)
app.host('www.example.org', site, name="main_site")
news_host = Host('news.example.org', news)
app.router.routes.append(news_host)
```
URL lookups can include host parameters just like path parameters
```python
routes = [
Host("{subdomain}.example.org", name="sub", app=Router(routes=[
Mount("/users", name="users", routes=[
Route("/", user, name="user_list"),
Route("/{username}", user, name="user_detail")
])
]))
]
...
url = request.url_for("sub:users:user_detail", username=..., subdomain=...)
url = request.url_for("sub:users:user_list", subdomain=...)
```
## Route priority
Incoming paths are matched against each `Route` in order.
In cases where more that one route could match an incoming path, you should
take care to ensure that more specific routes are listed before general cases.
For example:
```python
# Don't do this: `/users/me` will never match incoming requests.
routes = [
Route('/users/{username}', user),
Route('/users/me', current_user),
]
# Do this: `/users/me` is tested first.
routes = [
Route('/users/me', current_user),
Route('/users/{username}', user),
]
```
## Working with Router instances
If you're working at a low-level you might want to use a plain `Router`
instance, rather that creating a `Starlette` application. This gives you
a lightweight ASGI application that just provides the application routing,
without wrapping it up in any middleware.
```python
app = Router(routes=[
Route('/', homepage),
Mount('/users', routes=[
Route('/', users, methods=['GET', 'POST']),
Route('/{username}', user),
])
])
```
## WebSocket Routing
When working with WebSocket endpoints, you should use `WebSocketRoute`
instead of the usual `Route`.
Path parameters, and reverse URL lookups for `WebSocketRoute` work the the same
as HTTP `Route`, which can be found in the HTTP [Route](#http-routing) section above.
```python
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute
async def websocket_index(websocket):
await websocket.accept()
await websocket.send_text("Hello, websocket!")
await websocket.close()
async def websocket_user(websocket):
name = websocket.path_params["name"]
await websocket.accept()
await websocket.send_text(f"Hello, {name}")
await websocket.close()
routes = [
WebSocketRoute("/", endpoint=websocket_index),
WebSocketRoute("/{name}", endpoint=websocket_user),
]
app = Starlette(routes=routes)
```
The `endpoint` argument can be one of:
* An async function, which accepts a single `websocket` argument.
* A class that implements the ASGI interface, such as Starlette's [WebSocketEndpoint](endpoints.md#websocketendpoint).
starlette-0.46.1/docs/schemas.md 0000644 0000000 0000000 00000005733 13615410400 013436 0 ustar 00 Starlette supports generating API schemas, such as the widely used [OpenAPI
specification][openapi]. (Formerly known as "Swagger".)
Schema generation works by inspecting the routes on the application through
`app.routes`, and using the docstrings or other attributes on the endpoints
in order to determine a complete API schema.
Starlette is not tied to any particular schema generation or validation tooling,
but includes a simple implementation that generates OpenAPI schemas based on
the docstrings.
```python
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.schemas import SchemaGenerator
schemas = SchemaGenerator(
{"openapi": "3.0.0", "info": {"title": "Example API", "version": "1.0"}}
)
def list_users(request):
"""
responses:
200:
description: A list of users.
examples:
[{"username": "tom"}, {"username": "lucy"}]
"""
raise NotImplementedError()
def create_user(request):
"""
responses:
200:
description: A user.
examples:
{"username": "tom"}
"""
raise NotImplementedError()
def openapi_schema(request):
return schemas.OpenAPIResponse(request=request)
routes = [
Route("/users", endpoint=list_users, methods=["GET"]),
Route("/users", endpoint=create_user, methods=["POST"]),
Route("/schema", endpoint=openapi_schema, include_in_schema=False)
]
app = Starlette(routes=routes)
```
We can now access an OpenAPI schema at the "/schema" endpoint.
You can generate the API Schema directly with `.get_schema(routes)`:
```python
schema = schemas.get_schema(routes=app.routes)
assert schema == {
"openapi": "3.0.0",
"info": {"title": "Example API", "version": "1.0"},
"paths": {
"/users": {
"get": {
"responses": {
200: {
"description": "A list of users.",
"examples": [{"username": "tom"}, {"username": "lucy"}],
}
}
},
"post": {
"responses": {
200: {"description": "A user.", "examples": {"username": "tom"}}
}
},
},
},
}
```
You might also want to be able to print out the API schema, so that you can
use tooling such as generating API documentation.
```python
if __name__ == '__main__':
assert sys.argv[-1] in ("run", "schema"), "Usage: example.py [run|schema]"
if sys.argv[-1] == "run":
uvicorn.run("example:app", host='0.0.0.0', port=8000)
elif sys.argv[-1] == "schema":
schema = schemas.get_schema(routes=app.routes)
print(yaml.dump(schema, default_flow_style=False))
```
### Third party packages
#### [starlette-apispec][starlette-apispec]
Easy APISpec integration for Starlette, which supports some object serialization libraries.
[openapi]: https://github.com/OAI/OpenAPI-Specification
[starlette-apispec]: https://github.com/Woile/starlette-apispec
starlette-0.46.1/docs/server-push.md 0000644 0000000 0000000 00000001754 13615410400 014275 0 ustar 00
Starlette includes support for HTTP/2 and HTTP/3 server push, making it
possible to push resources to the client to speed up page load times.
### `Request.send_push_promise`
Used to initiate a server push for a resource. If server push is not available
this method does nothing.
Signature: `send_push_promise(path)`
* `path` - A string denoting the path of the resource.
```python
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles
async def homepage(request):
"""
Homepage which uses server push to deliver the stylesheet.
"""
await request.send_push_promise("/static/style.css")
return HTMLResponse(
''
)
routes = [
Route("/", endpoint=homepage),
Mount("/static", StaticFiles(directory="static"), name="static")
]
app = Starlette(routes=routes)
```
starlette-0.46.1/docs/sponsorship.md 0000644 0000000 0000000 00000026035 13615410400 014400 0 ustar 00 # β¨ 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.
67M+
Starlette Downloads/Month
57M+
Uvicorn Downloads/Month
19K+
Combined GitHub Stars
## 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.
π Become a Sponsor Today! π
Your support helps keep Starlette and Uvicorn growing stronger!
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!
Your feedback is invaluable in helping us make sponsorship as accessible as possible. Share your thoughts by:
## 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** ([@AlexGrΓΆnholm](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/encode/starlette/graphs/contributors) / [Uvicorn Contributors](https://github.com/encode/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.
starlette-0.46.1/docs/staticfiles.md 0000644 0000000 0000000 00000004310 13615410400 014313 0 ustar 00
Starlette also includes a `StaticFiles` class for serving files in a given directory:
### StaticFiles
Signature: `StaticFiles(directory=None, packages=None, html=False, check_dir=True, follow_symlink=False)`
* `directory` - A string or [os.PathLike][pathlike] denoting a directory path.
* `packages` - A list of strings or list of tuples of strings of python packages.
* `html` - Run in HTML mode. Automatically loads `index.html` for directories if such file exist.
* `check_dir` - Ensure that the directory exists upon instantiation. Defaults to `True`.
* `follow_symlink` - A boolean indicating if symbolic links for files and directories should be followed. Defaults to `False`.
You can combine this ASGI application with Starlette's routing to provide
comprehensive static file serving.
```python
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
routes = [
...
Mount('/static', app=StaticFiles(directory='static'), name="static"),
]
app = Starlette(routes=routes)
```
Static files will respond with "404 Not found" or "405 Method not allowed"
responses for requests which do not match. In HTML mode if `404.html` file
exists it will be shown as 404 response.
The `packages` option can be used to include "static" directories contained within
a python package. The Python "bootstrap4" package is an example of this.
```python
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
routes=[
...
Mount('/static', app=StaticFiles(directory='static', packages=['bootstrap4']), name="static"),
]
app = Starlette(routes=routes)
```
By default `StaticFiles` will look for `statics` directory in each package,
you can change the default directory by specifying a tuple of strings.
```python
routes=[
...
Mount('/static', app=StaticFiles(packages=[('bootstrap4', 'static')]), name="static"),
]
```
You may prefer to include static files directly inside the "static" directory
rather than using Python packaging to include static files, but it can be useful
for bundling up reusable components.
[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike
starlette-0.46.1/docs/templates.md 0000644 0000000 0000000 00000011165 13615410400 014005 0 ustar 00 Starlette is not _strictly_ coupled to any particular templating engine, but
Jinja2 provides an excellent choice.
### Jinja2Templates
Signature: `Jinja2Templates(directory, context_processors=None, **env_options)`
* `directory` - A string, [os.Pathlike][pathlike] or a list of strings or [os.Pathlike][pathlike] denoting a directory path.
* `context_processors` - A list of functions that return a dictionary to add to the template context.
* `**env_options` - Additional keyword arguments to pass to the Jinja2 environment.
Starlette provides a simple way to get `jinja2` configured. This is probably
what you want to use by default.
```python
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.templating import Jinja2Templates
from starlette.staticfiles import StaticFiles
templates = Jinja2Templates(directory='templates')
async def homepage(request):
return templates.TemplateResponse(request, 'index.html')
routes = [
Route('/', endpoint=homepage),
Mount('/static', StaticFiles(directory='static'), name='static')
]
app = Starlette(debug=True, routes=routes)
```
Note that the incoming `request` instance must be included as part of the
template context.
The Jinja2 template context will automatically include a `url_for` function,
so we can correctly hyperlink to other pages within the application.
For example, we can link to static files from within our HTML templates:
```html
```
If you want to use [custom filters][jinja2], you will need to update the `env`
property of `Jinja2Templates`:
```python
from commonmark import commonmark
from starlette.templating import Jinja2Templates
def marked_filter(text):
return commonmark(text)
templates = Jinja2Templates(directory='templates')
templates.env.filters['marked'] = marked_filter
```
## Using custom jinja2.Environment instance
Starlette also accepts a preconfigured [`jinja2.Environment`](https://jinja.palletsprojects.com/en/3.0.x/api/#api) instance.
```python
import jinja2
from starlette.templating import Jinja2Templates
env = jinja2.Environment(...)
templates = Jinja2Templates(env=env)
```
## Context processors
A context processor is a function that returns a dictionary to be merged into a template context.
Every function takes only one argument `request` and must return a dictionary to add to the context.
A common use case of template processors is to extend the template context with shared variables.
```python
import typing
from starlette.requests import Request
def app_context(request: Request) -> typing.Dict[str, typing.Any]:
return {'app': request.app}
```
### Registering context templates
Pass context processors to `context_processors` argument of the `Jinja2Templates` class.
```python
import typing
from starlette.requests import Request
from starlette.templating import Jinja2Templates
def app_context(request: Request) -> typing.Dict[str, typing.Any]:
return {'app': request.app}
templates = Jinja2Templates(
directory='templates', context_processors=[app_context]
)
```
!!! info
Asynchronous functions as context processors are not supported.
## Testing template responses
When using the test client, template responses include `.template` and `.context`
attributes.
```python
from starlette.testclient import TestClient
def test_homepage():
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.template.name == 'index.html'
assert "request" in response.context
```
## Customizing Jinja2 Environment
`Jinja2Templates` accepts all options supported by Jinja2 `Environment`.
This will allow more control over the `Environment` instance created by Starlette.
For the list of options available to `Environment` you can check Jinja2 documentation [here](https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment)
```python
from starlette.templating import Jinja2Templates
templates = Jinja2Templates(directory='templates', autoescape=False, auto_reload=True)
```
## Asynchronous template rendering
Jinja2 supports async template rendering, however as a general rule
we'd recommend that you keep your templates free from logic that invokes
database lookups, or other I/O operations.
Instead we'd recommend that you ensure that your endpoints perform all I/O,
for example, strictly evaluate any database queries within the view and
include the final results in the context.
[jinja2]: https://jinja.palletsprojects.com/en/3.0.x/api/?highlight=environment#writing-filters
[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike
starlette-0.46.1/docs/testclient.md 0000644 0000000 0000000 00000017014 13615410400 014164 0 ustar 00
??? abstract "API Reference"
::: starlette.testclient.TestClient
options:
parameter_headings: false
show_bases: false
show_root_heading: true
heading_level: 3
filters:
- "__init__"
The test client allows you to make requests against your ASGI application,
using the `httpx` library.
```python
from starlette.responses import HTMLResponse
from starlette.testclient import TestClient
async def app(scope, receive, send):
assert scope['type'] == 'http'
response = HTMLResponse('Hello, world!')
await response(scope, receive, send)
def test_app():
client = TestClient(app)
response = client.get('/')
assert response.status_code == 200
```
The test client exposes the same interface as any other `httpx` session.
In particular, note that the calls to make a request are just standard
function calls, not awaitables.
You can use any of `httpx` standard API, such as authentication, session
cookies handling, or file uploads.
For example, to set headers on the TestClient you can do:
```python
client = TestClient(app)
# Set headers on the client for future requests
client.headers = {"Authorization": "..."}
response = client.get("/")
# Set headers for each request separately
response = client.get("/", headers={"Authorization": "..."})
```
And for example to send files with the TestClient:
```python
client = TestClient(app)
# Send a single file
with open("example.txt", "rb") as f:
response = client.post("/form", files={"file": f})
# Send multiple files
with open("example.txt", "rb") as f1:
with open("example.png", "rb") as f2:
files = {"file1": f1, "file2": ("filename", f2, "image/png")}
response = client.post("/form", files=files)
```
For more information you can check the `httpx` [documentation](https://www.python-httpx.org/advanced/).
By default the `TestClient` will raise any exceptions that occur in the
application. Occasionally you might want to test the content of 500 error
responses, rather than allowing client to raise the server exception. In this
case you should use `client = TestClient(app, raise_server_exceptions=False)`.
!!! note
If you want the `TestClient` to run the `lifespan` handler,
you will need to use the `TestClient` as a context manager. It will
not be triggered when the `TestClient` is instantiated. You can learn more about it
[here](lifespan.md#running-lifespan-in-tests).
### Change client address
By default, the TestClient will set the client host to `"testserver"` and the port to `50000`.
You can change the client address by setting the `client` attribute of the `TestClient` instance:
```python
client = TestClient(app, client=('localhost', 8000))
```
### Selecting the Async backend
`TestClient` takes arguments `backend` (a string) and `backend_options` (a dictionary).
These options are passed to `anyio.start_blocking_portal()`.
See the [anyio documentation](https://anyio.readthedocs.io/en/stable/basics.html#backend-options)
for more information about the accepted backend options.
By default, `asyncio` is used with default options.
To run `Trio`, pass `backend="trio"`. For example:
```python
def test_app()
with TestClient(app, backend="trio") as client:
...
```
To run `asyncio` with `uvloop`, pass `backend_options={"use_uvloop": True}`. For example:
```python
def test_app()
with TestClient(app, backend_options={"use_uvloop": True}) as client:
...
```
### Testing WebSocket sessions
You can also test websocket sessions with the test client.
The `httpx` library will be used to build the initial handshake, meaning you
can use the same authentication options and other headers between both http and
websocket testing.
```python
from starlette.testclient import TestClient
from starlette.websockets import WebSocket
async def app(scope, receive, send):
assert scope['type'] == 'websocket'
websocket = WebSocket(scope, receive=receive, send=send)
await websocket.accept()
await websocket.send_text('Hello, world!')
await websocket.close()
def test_app():
client = TestClient(app)
with client.websocket_connect('/') as websocket:
data = websocket.receive_text()
assert data == 'Hello, world!'
```
The operations on session are standard function calls, not awaitables.
It's important to use the session within a context-managed `with` block. This
ensure that the background thread on which the ASGI application is properly
terminated, and that any exceptions that occur within the application are
always raised by the test client.
#### Establishing a test session
* `.websocket_connect(url, subprotocols=None, **options)` - Takes the same set of arguments as `httpx.get()`.
May raise `starlette.websockets.WebSocketDisconnect` if the application does not accept the websocket connection.
`websocket_connect()` must be used as a context manager (in a `with` block).
!!! note
The `params` argument is not supported by `websocket_connect`. If you need to pass query arguments, hard code it
directly in the URL.
```python
with client.websocket_connect('/path?foo=bar') as websocket:
...
```
#### Sending data
* `.send_text(data)` - Send the given text to the application.
* `.send_bytes(data)` - Send the given bytes to the application.
* `.send_json(data, mode="text")` - Send the given data to the application. Use `mode="binary"` to send JSON over binary data frames.
#### Receiving data
* `.receive_text()` - Wait for incoming text sent by the application and return it.
* `.receive_bytes()` - Wait for incoming bytestring sent by the application and return it.
* `.receive_json(mode="text")` - Wait for incoming json data sent by the application and return it. Use `mode="binary"` to receive JSON over binary data frames.
May raise `starlette.websockets.WebSocketDisconnect`.
#### Closing the connection
* `.close(code=1000)` - Perform a client-side close of the websocket connection.
### Asynchronous tests
Sometimes you will want to do async things outside of your application.
For example, you might want to check the state of your database after calling your app
using your existing async database client/infrastructure.
For these situations, using `TestClient` is difficult because it creates it's own event loop and async
resources (like a database connection) often cannot be shared across event loops.
The simplest way to work around this is to just make your entire test async and use an async client, like [httpx.AsyncClient].
Here is an example of such a test:
```python
from httpx import AsyncClient, ASGITransport
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import PlainTextResponse
def hello(request: Request) -> PlainTextResponse:
return PlainTextResponse("Hello World!")
app = Starlette(routes=[Route("/", hello)])
# if you're using pytest, you'll need to to add an async marker like:
# @pytest.mark.anyio # using https://github.com/agronholm/anyio
# or install and configure pytest-asyncio (https://github.com/pytest-dev/pytest-asyncio)
async def test_app() -> None:
# note: you _must_ set `base_url` for relative urls like "/" to work
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
r = await client.get("/")
assert r.status_code == 200
assert r.text == "Hello World!"
```
[httpx.AsyncClient]: https://www.python-httpx.org/advanced/#calling-into-python-web-apps
starlette-0.46.1/docs/third-party-packages.md 0000644 0000000 0000000 00000030024 13615410400 016025 0 ustar 00
Starlette has a rapidly growing community of developers, building tools that integrate into Starlette, tools that depend on Starlette, etc.
Here are some of those third party packages:
## Plugins
### Apitally
GitHub |
Documentation
Analytics, request logging and monitoring for REST APIs built with Starlette (and other frameworks).
### Authlib
GitHub |
Documentation
The ultimate Python library in building OAuth and OpenID Connect clients and servers. Check out how to integrate with [Starlette](https://docs.authlib.org/en/latest/client/starlette.html).
### ChannelBox
GitHub
Another solution for websocket broadcast. Send messages to channel groups from any part of your code.
Checkout MySimpleChat, a simple chat application built using `channel-box` and `starlette`.
### Imia
GitHub
An authentication framework for Starlette with pluggable authenticators and login/logout flow.
### Mangum
GitHub
Serverless ASGI adapter for AWS Lambda & API Gateway.
### Nejma
GitHub
Manage and send messages to groups of channels using websockets.
Checkout nejma-chat, a simple chat application built using `nejma` and `starlette`.
### Scout APM
GitHub
An APM (Application Performance Monitoring) solution that can
instrument your application to find performance bottlenecks.
### SpecTree
GitHub
Generate OpenAPI spec document and validate request & response with Python annotations. Less boilerplate code(no need for YAML).
### Starlette APISpec
GitHub
Simple APISpec integration for Starlette.
Document your REST API built with Starlette by declaring OpenAPI (Swagger)
schemas in YAML format in your endpoint's docstrings.
### Starlette Compress
GitHub
Starlette-Compress is a fast and simple middleware for compressing responses in Starlette.
It adds ZStd, Brotli, and GZip compression support with sensible default configuration.
### Starlette Context
GitHub
Middleware for Starlette that allows you to store and access the context data of a request.
Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.
### Starlette Cramjam
GitHub
A Starlette middleware that allows **brotli**, **gzip** and **deflate** compression algorithm with a minimal requirements.
### Starlette OAuth2 API
GitLab
A starlette middleware to add authentication and authorization through JWTs.
It relies solely on an auth provider to issue access and/or id tokens to clients.
### Starlette Prometheus
GitHub
A plugin for providing an endpoint that exposes [Prometheus](https://prometheus.io/) metrics based on its [official python client](https://github.com/prometheus/client_python).
### Starlette WTF
GitHub
A simple tool for integrating Starlette and WTForms. It is modeled on the excellent Flask-WTF library.
### Starlette-Login
GitHub |
Documentation
User session management for Starlette.
It handles the common tasks of logging in, logging out, and remembering your users' sessions over extended periods of time.
### Starsessions
GitHub
An alternate session support implementation with customizable storage backends.
### webargs-starlette
GitHub
Declarative request parsing and validation for Starlette, built on top
of [webargs](https://github.com/marshmallow-code/webargs).
Allows you to parse querystring, JSON, form, headers, and cookies using
type annotations.
### DecoRouter
GitHub
FastAPI style routing for Starlette.
Allows you to use decorators to generate routing tables.
### Starception
GitHub
Beautiful exception page for Starlette apps.
### Starlette-Admin
GitHub |
Documentation
Simple and extensible admin interface framework.
Built with [Tabler](https://tabler.io/) and [Datatables](https://datatables.net/), it allows you
to quickly generate fully customizable admin interface for your models. You can export your data to many formats (*CSV*, *PDF*,
*Excel*, etc), filter your data with complex query including `AND` and `OR` conditions, upload files, ...
### Vellox
GitHub
Serverless ASGI adapter for GCP Cloud Functions.
## Starlette Bridge
GitHub |
Documentation
With the deprecation of `on_startup` and `on_shutdown`, Starlette Bridge makes sure you can still
use the old ways of declaring events with a particularity that internally, in fact, creates the
`lifespan` for you. This way backwards compatibility is assured for the existing packages out there
while maintaining the integrity of the newly `lifespan` events of `Starlette`.
## Frameworks
### FastAPI
GitHub |
Documentation
High performance, easy to learn, fast to code, ready for production web API framework.
Inspired by **APIStar**'s previous server system with type declarations for route parameters, based on the OpenAPI specification version 3.0.0+ (with JSON Schema), powered by **Pydantic** for the data handling.
### Flama
GitHub |
Documentation
Flama is a **data-science oriented framework** to rapidly build modern and robust **machine learning** (ML) APIs. The main aim of the framework is to make ridiculously simple the deployment of ML APIs. With Flama, data scientists can now quickly turn their ML models into asynchronous, auto-documented APIs with just a single line of code. All in just few seconds!
Flama comes with an intuitive CLI, and provides an easy-to-learn philosophy to speed up the building of **highly performant** GraphQL, REST, and ML APIs. Besides, it comprises an ideal solution for the development of asynchronous and **production-ready** services, offering **automatic deployment** for ML models.
### Greppo
GitHub |
Documentation
A Python framework for building geospatial dashboards and web-applications.
Greppo is an open-source Python framework that makes it easy to build geospatial dashboards and web-applications. It provides a toolkit to quickly integrate data, algorithms, visualizations and UI for interactivity. It provides APIs to the update the variables in the backend, recompute the logic, and reflect the changes in the frontend (data mutation hook).
### Responder
GitHub |
Documentation
Async web service framework. Some Features: flask-style route expression,
yaml support, OpenAPI schema generation, background tasks, graphql.
### Starlette-apps
Roll your own framework with a simple app system, like [Django-GDAPS](https://gdaps.readthedocs.io/en/latest/) or [CakePHP](https://cakephp.org/).
GitHub
### Dark Star
A simple framework to help minimise the code needed to get HTML to the browser. Changes your file paths into Starlette routes and puts your view code right next to your template. Includes support for [htmx](https://htmx.org) to help enhance your frontend.
DocsGitHub
### Xpresso
A flexible and extendable web framework built on top of Starlette, Pydantic and [di](https://github.com/adriangb/di).
GitHub |
Documentation
### Ellar
GitHub |
Documentation
Ellar is an ASGI web framework for building fast, efficient and scalable RESTAPIs and server-side applications. It offers a high level of abstraction in building server-side applications and combines elements of OOP (Object Oriented Programming), and FP (Functional Programming) - Inspired by Nestjs.
It is built on 3 core libraries **Starlette**, **Pydantic**, and **injector**.
### Apiman
An extension to integrate Swagger/OpenAPI document easily for Starlette project and provide [SwaggerUI](http://swagger.io/swagger-ui/) and [RedocUI](https://rebilly.github.io/ReDoc/).
GitHub
### Starlette-Babel
Provides translations, localization, and timezone support via Babel integration.
GitHub
### Starlette-StaticResources
GitHub
Allows mounting [package resources](https://docs.python.org/3/library/importlib.resources.html#module-importlib.resources) for static data, similar to [StaticFiles](https://www.starlette.io/staticfiles/).
### Sentry
GitHub |
Documentation
Sentry is a software error detection tool. It offers actionable insights for resolving performance issues and errors, allowing users to diagnose, fix, and optimize Python debugging. Additionally, it integrates seamlessly with Starlette for Python application development. Sentry's capabilities include error tracking, performance insights, contextual information, and alerts/notifications.
### Shiny
GitHub |
Documentation
Leveraging Starlette and asyncio, Shiny allows developers to create effortless Python web applications using the power of reactive programming. Shiny eliminates the hassle of manual state management, automatically determining the best execution path for your app at runtime while simultaneously minimizing re-rendering. This means that Shiny can support everything from the simplest dashboard to full-featured web apps. starlette-0.46.1/docs/threadpool.md 0000644 0000000 0000000 00000001474 13615410400 014152 0 ustar 00 # Thread Pool
When you're using `def` instead of `async def`, Starlette will run your code in a thread pool to avoid
blocking the event loop. This applies for endpoint functions and background tasks you create, but also
for internal Starlette code.
To be more precise, Starlette uses `anyio.to_thread.run_sync` to run the synchronous code.
## Limitation
The default thread pool size is only 40 _tokens_. This means that only 40 threads can run at the same time.
If you need to run more threads, you can increase the number of _tokens_:
```py
import anyio.to_thread
limiter = anyio.to_thread.current_default_thread_limiter()
limiter.total_tokens = 100
```
The above code will increase the number of _tokens_ to 100.
Increasing the number of threads may have a performance and memory impact, so be careful when doing so.
starlette-0.46.1/docs/websockets.md 0000644 0000000 0000000 00000011324 13615410400 014155 0 ustar 00
Starlette includes a `WebSocket` class that fulfils a similar role
to the HTTP request, but that allows sending and receiving data on a websocket.
### WebSocket
Signature: `WebSocket(scope, receive=None, send=None)`
```python
from starlette.websockets import WebSocket
async def app(scope, receive, send):
websocket = WebSocket(scope=scope, receive=receive, send=send)
await websocket.accept()
await websocket.send_text('Hello, world!')
await websocket.close()
```
WebSockets present a mapping interface, so you can use them in the same
way as a `scope`.
For instance: `websocket['path']` will return the ASGI path.
#### URL
The websocket URL is accessed as `websocket.url`.
The property is actually a subclass of `str`, and also exposes all the
components that can be parsed out of the URL.
For example: `websocket.url.path`, `websocket.url.port`, `websocket.url.scheme`.
#### Headers
Headers are exposed as an immutable, case-insensitive, multi-dict.
For example: `websocket.headers['sec-websocket-version']`
#### Query Parameters
Query parameters are exposed as an immutable multi-dict.
For example: `websocket.query_params['search']`
#### Path Parameters
Router path parameters are exposed as a dictionary interface.
For example: `websocket.path_params['username']`
### Accepting the connection
* `await websocket.accept(subprotocol=None, headers=None)`
### Sending data
* `await websocket.send_text(data)`
* `await websocket.send_bytes(data)`
* `await websocket.send_json(data)`
JSON messages default to being sent over text data frames, from version 0.10.0 onwards.
Use `websocket.send_json(data, mode="binary")` to send JSON over binary data frames.
### Receiving data
* `await websocket.receive_text()`
* `await websocket.receive_bytes()`
* `await websocket.receive_json()`
May raise `starlette.websockets.WebSocketDisconnect()`.
JSON messages default to being received over text data frames, from version 0.10.0 onwards.
Use `websocket.receive_json(data, mode="binary")` to receive JSON over binary data frames.
### Iterating data
* `websocket.iter_text()`
* `websocket.iter_bytes()`
* `websocket.iter_json()`
Similar to `receive_text`, `receive_bytes`, and `receive_json` but returns an
async iterator.
```python hl_lines="7-8"
from starlette.websockets import WebSocket
async def app(scope, receive, send):
websocket = WebSocket(scope=scope, receive=receive, send=send)
await websocket.accept()
async for message in websocket.iter_text():
await websocket.send_text(f"Message text was: {message}")
await websocket.close()
```
When `starlette.websockets.WebSocketDisconnect` is raised, the iterator will exit.
### Closing the connection
* `await websocket.close(code=1000, reason=None)`
### Sending and receiving messages
If you need to send or receive raw ASGI messages then you should use
`websocket.send()` and `websocket.receive()` rather than using the raw `send` and
`receive` callables. This will ensure that the websocket's state is kept
correctly updated.
* `await websocket.send(message)`
* `await websocket.receive()`
### Send Denial Response
If you call `websocket.close()` before calling `websocket.accept()` then
the server will automatically send a HTTP 403 error to the client.
If you want to send a different error response, you can use the
`websocket.send_denial_response()` method. This will send the response
and then close the connection.
* `await websocket.send_denial_response(response)`
This requires the ASGI server to support the WebSocket Denial Response
extension. If it is not supported a `RuntimeError` will be raised.
In the context of `Starlette`, you can also use the `HTTPException` to achieve the same result.
```python
from starlette.applications import Starlette
from starlette.exceptions import HTTPException
from starlette.routing import WebSocketRoute
from starlette.websockets import WebSocket
def is_authorized(subprotocols: list[str]):
if len(subprotocols) != 2:
return False
if subprotocols[0] != "Authorization":
return False
# Here we are hard coding the token, in a real application you would validate the token
# against a database or an external service.
if subprotocols[1] != "token":
return False
return True
async def websocket_endpoint(websocket: WebSocket):
subprotocols = websocket.scope["subprotocols"]
if not is_authorized(subprotocols):
raise HTTPException(status_code=401, detail="Unauthorized")
await websocket.accept("Authorization")
await websocket.send_text("Hello, world!")
await websocket.close()
app = Starlette(debug=True, routes=[WebSocketRoute("/ws", websocket_endpoint)])
```
starlette-0.46.1/docs/img/gh-actions-fail-check.png 0000644 0000000 0000000 00003550635 13615410400 017004 0 ustar 00 PNG
IHDR
> Ζ ¬Q miCCPICC Profile HWXSΙ[ΠBzDj )!΄ `#$cBP±£
Α(Ά;veQμ}± ’¬ΊΨPyΠu_ωήωΎΉχΟ3)w&χ 4?p%