The little ASGI library that shines.
Starlette provides specialized data structures for handling web-specific data types like URLs, headers, query parameters, form data, and file uploads, with convenient APIs for manipulation and access.
from starlette.datastructures import URL
from urllib.parse import SplitResult
from typing import Any, Dict, Union
class URL(str):
"""
URL manipulation class with convenient methods.
Immutable URL class that provides easy access to URL components
and methods for creating modified URLs.
"""
def __new__(
cls,
url: str = "",
scope: Dict[str, Any] = None,
**components: Any
) -> "URL":
"""
Create URL from string or ASGI scope.
Args:
url: URL string
scope: ASGI scope dict
**components: URL components to override
"""
@property
def scheme(self) -> str:
"""URL scheme (http, https, ws, wss)."""
@property
def netloc(self) -> str:
"""Network location (host:port)."""
@property
def hostname(self) -> str:
"""Hostname without port."""
@property
def port(self) -> int | None:
"""Port number or None if not specified."""
@property
def path(self) -> str:
"""URL path."""
@property
def query(self) -> str:
"""Query string."""
@property
def fragment(self) -> str:
"""URL fragment (after #)."""
@property
def username(self) -> str | None:
"""Username from URL."""
@property
def password(self) -> str | None:
"""Password from URL."""
@property
def is_secure(self) -> bool:
"""True if scheme is https or wss."""
@property
def components(self) -> SplitResult:
"""urllib.parse.SplitResult object."""
def replace(self, **kwargs: Any) -> "URL":
"""
Create new URL with replaced components.
Args:
**kwargs: Components to replace (scheme, netloc, path, query, fragment)
Returns:
New URL with replaced components
"""
def include_query_params(self, **params: Any) -> "URL":
"""
Create new URL with additional query parameters.
Args:
**params: Query parameters to add
Returns:
New URL with added parameters
"""
def replace_query_params(self, **params: Any) -> "URL":
"""
Create new URL with replaced query parameters.
Args:
**params: Query parameters to set (replaces all existing)
Returns:
New URL with new query parameters
"""
def remove_query_params(
self,
keys: Union[str, list[str]]
) -> "URL":
"""
Create new URL with specified query parameters removed.
Args:
keys: Parameter key(s) to remove
Returns:
New URL without specified parameters
"""from starlette.datastructures import URLPath
class URLPath(str):
"""
URL path with protocol and host information.
Used for generating absolute URLs from relative paths.
"""
def __new__(
cls,
path: str,
protocol: str = "",
host: str = ""
) -> "URLPath":
"""
Create URLPath.
Args:
path: URL path
protocol: Protocol ("http" or "websocket")
host: Host name
"""
@property
def protocol(self) -> str:
"""Protocol type."""
@property
def host(self) -> str:
"""Host name."""
def make_absolute_url(self, base_url: str) -> str:
"""
Create absolute URL from base URL.
Args:
base_url: Base URL to combine with path
Returns:
Complete absolute URL
"""from starlette.datastructures import Headers, MutableHeaders
from typing import Iterator, List, Tuple
class Headers:
"""
Immutable case-insensitive HTTP headers.
Provides dictionary-like access to HTTP headers with
case-insensitive matching and multi-value support.
"""
def __init__(
self,
headers: Union[
Dict[str, str],
List[Tuple[str, str]],
List[Tuple[bytes, bytes]]
] = None,
raw: List[Tuple[bytes, bytes]] = None,
scope: Dict[str, Any] = None,
) -> None:
"""
Initialize headers.
Args:
headers: Headers as dict or list of tuples
raw: Raw header bytes (ASGI format)
scope: ASGI scope containing headers
"""
@property
def raw(self) -> List[Tuple[bytes, bytes]]:
"""Raw header list in ASGI format."""
def keys(self) -> List[str]:
"""Get all header names."""
def values(self) -> List[str]:
"""Get all header values."""
def items(self) -> List[Tuple[str, str]]:
"""Get header name-value pairs."""
def get(self, key: str, default: str = None) -> str | None:
"""
Get header value (case-insensitive).
Args:
key: Header name
default: Default value if header not found
Returns:
Header value or default
"""
def getlist(self, key: str) -> List[str]:
"""
Get all values for header (case-insensitive).
Args:
key: Header name
Returns:
List of all values for the header
"""
def mutablecopy(self) -> "MutableHeaders":
"""Create mutable copy of headers."""
# Dictionary-like access
def __getitem__(self, key: str) -> str:
"""Get header value (raises KeyError if not found)."""
def __contains__(self, key: str) -> bool:
"""Check if header exists (case-insensitive)."""
def __iter__(self) -> Iterator[str]:
"""Iterate over header names."""
def __len__(self) -> int:
"""Number of headers."""
class MutableHeaders(Headers):
"""
Mutable case-insensitive HTTP headers.
Extends Headers with methods for modifying header values.
"""
def __setitem__(self, key: str, value: str) -> None:
"""Set header value."""
def __delitem__(self, key: str) -> None:
"""Delete header."""
def setdefault(self, key: str, value: str) -> str:
"""Set header if not already present."""
def update(self, other: Union[Headers, Dict[str, str]]) -> None:
"""Update headers from another headers object or dict."""
def append(self, key: str, value: str) -> None:
"""Append value to existing header."""
def add_vary_header(self, vary: str) -> None:
"""Add value to Vary header."""from starlette.datastructures import QueryParams, ImmutableMultiDict
class QueryParams(ImmutableMultiDict):
"""
URL query parameters with multi-value support.
Immutable dictionary-like container for URL query parameters
that supports multiple values per key.
"""
def __init__(
self,
query: Union[
str,
bytes,
Dict[str, str],
Dict[str, List[str]],
List[Tuple[str, str]]
] = None,
**kwargs: str
) -> None:
"""
Initialize query parameters.
Args:
query: Query string, dict, or list of tuples
**kwargs: Additional query parameters
"""
# Inherited from ImmutableMultiDict
def get(self, key: str, default: str = None) -> str | None:
"""Get first value for key."""
def getlist(self, key: str) -> List[str]:
"""Get all values for key."""
def keys(self) -> List[str]:
"""Get all parameter names."""
def values(self) -> List[str]:
"""Get all parameter values."""
def items(self) -> List[Tuple[str, str]]:
"""Get key-value pairs."""
def multi_items(self) -> List[Tuple[str, str]]:
"""Get all key-value pairs including duplicates."""
# Dictionary-like access
def __getitem__(self, key: str) -> str:
"""Get first value for key."""
def __contains__(self, key: str) -> bool:
"""Check if parameter exists."""
def __iter__(self) -> Iterator[str]:
"""Iterate over parameter names."""from starlette.datastructures import FormData, UploadFile, MultiDict
class FormData(ImmutableMultiDict):
"""
Form data container supporting files and fields.
Immutable container for form data that can contain
both regular form fields and file uploads.
"""
def get(self, key: str, default: Any = None) -> Union[str, UploadFile, None]:
"""Get form field or file."""
def getlist(self, key: str) -> List[Union[str, UploadFile]]:
"""Get all values for form field."""
async def close(self) -> None:
"""Close all uploaded files to free resources."""
class UploadFile:
"""
Uploaded file wrapper with async file operations.
Represents a file uploaded through multipart form data
with methods for reading and manipulating file content.
"""
def __init__(
self,
file: BinaryIO,
size: int = None,
filename: str = None,
headers: Headers = None,
) -> None:
"""
Initialize upload file.
Args:
file: File-like object containing uploaded data
size: File size in bytes
filename: Original filename from client
headers: HTTP headers for this file part
"""
@property
def filename(self) -> str | None:
"""Original filename from client."""
@property
def file(self) -> BinaryIO:
"""Underlying file object."""
@property
def size(self) -> int | None:
"""File size in bytes."""
@property
def headers(self) -> Headers:
"""Headers for this file part."""
@property
def content_type(self) -> str | None:
"""MIME content type."""
async def read(self, size: int = -1) -> bytes:
"""
Read file content.
Args:
size: Number of bytes to read (-1 for all)
Returns:
File content as bytes
"""
async def seek(self, offset: int) -> None:
"""
Seek to position in file.
Args:
offset: Byte offset to seek to
"""
async def write(self, data: Union[str, bytes]) -> None:
"""
Write data to file.
Args:
data: Data to write to file
"""
async def close(self) -> None:
"""Close the file and free resources."""from starlette.datastructures import ImmutableMultiDict, MultiDict
class ImmutableMultiDict:
"""
Immutable multi-value dictionary.
Dictionary-like container that supports multiple values
per key while maintaining immutability.
"""
def __init__(
self,
items: Union[
Dict[str, str],
Dict[str, List[str]],
List[Tuple[str, str]]
] = None
) -> None:
"""Initialize from various input formats."""
def get(self, key: str, default: Any = None) -> Any:
"""Get first value for key."""
def getlist(self, key: str) -> List[Any]:
"""Get all values for key."""
def keys(self) -> List[str]:
"""Get all keys."""
def values(self) -> List[Any]:
"""Get all values (first value per key)."""
def items(self) -> List[Tuple[str, Any]]:
"""Get key-value pairs (first value per key)."""
def multi_items(self) -> List[Tuple[str, Any]]:
"""Get all key-value pairs including duplicates."""
# Dictionary-like interface
def __getitem__(self, key: str) -> Any:
"""Get first value for key."""
def __contains__(self, key: str) -> bool:
"""Check if key exists."""
def __iter__(self) -> Iterator[str]:
"""Iterate over keys."""
class MultiDict(ImmutableMultiDict):
"""
Mutable multi-value dictionary.
Extends ImmutableMultiDict with methods for modifying content.
"""
def __setitem__(self, key: str, value: Any) -> None:
"""Set single value for key (replaces existing)."""
def __delitem__(self, key: str) -> None:
"""Delete all values for key."""
def setlist(self, key: str, values: List[Any]) -> None:
"""Set multiple values for key."""
def append(self, key: str, value: Any) -> None:
"""Append value to existing values for key."""
def pop(self, key: str, default: Any = None) -> Any:
"""Remove and return first value for key."""
def popitem(self) -> Tuple[str, Any]:
"""Remove and return arbitrary key-value pair."""
def poplist(self, key: str) -> List[Any]:
"""Remove and return all values for key."""
def clear(self) -> None:
"""Remove all items."""
def setdefault(self, key: str, default: Any = None) -> Any:
"""Set key to default if not present."""
def update(self, *args, **kwargs) -> None:
"""Update with items from another mapping."""from starlette.datastructures import Address
from typing import NamedTuple
class Address(NamedTuple):
"""
Network address (host, port) tuple.
Represents client or server address information.
"""
host: str
port: intfrom starlette.datastructures import Secret
class Secret(str):
"""
String that hides its value in repr.
Used for sensitive data like passwords and API keys
to prevent accidental exposure in logs or debug output.
"""
def __new__(cls, value: str) -> "Secret":
"""Create secret string."""
def __repr__(self) -> str:
"""Hide value in string representation."""
return f"{self.__class__.__name__}('**********')"
def __str__(self) -> str:
"""Return actual value when converted to string."""from starlette.datastructures import CommaSeparatedStrings
class CommaSeparatedStrings(list[str]):
"""
Comma-separated string sequence.
Parses comma-separated strings into a list while
maintaining list interface.
"""
def __init__(self, value: Union[str, List[str]]) -> None:
"""
Initialize from string or sequence.
Args:
value: Comma-separated string or list of strings
"""
def __str__(self) -> str:
"""Convert back to comma-separated string."""from starlette.datastructures import State
class State:
"""
Arbitrary state container with attribute access.
Simple container for storing application or request state
with convenient attribute-based access.
"""
def __init__(self, state: Dict[str, Any] = None) -> None:
"""
Initialize state container.
Args:
state: Initial state dictionary
"""
def __setattr__(self, name: str, value: Any) -> None:
"""Set state attribute."""
def __getattr__(self, name: str) -> Any:
"""Get state attribute."""
def __delattr__(self, name: str) -> None:
"""Delete state attribute."""# Creating and manipulating URLs
url = URL("https://example.com/path?param=value")
print(url.scheme) # "https"
print(url.hostname) # "example.com"
print(url.path) # "/path"
print(url.query) # "param=value"
print(url.is_secure) # True
# Creating modified URLs
new_url = url.replace(scheme="http", path="/new-path")
# "http://example.com/new-path?param=value"
# Adding query parameters
url_with_params = url.include_query_params(new_param="new_value")
# "https://example.com/path?param=value&new_param=new_value"
# Replacing query parameters
url_new_params = url.replace_query_params(param="new_value", other="param")
# "https://example.com/path?param=new_value&other=param"
# Removing query parameters
url_no_param = url.remove_query_params("param")
# "https://example.com/path"# Creating headers
headers = Headers({
"content-type": "application/json",
"authorization": "Bearer token123"
})
# Case-insensitive access
print(headers["Content-Type"]) # "application/json"
print(headers.get("AUTHORIZATION")) # "Bearer token123"
# Multiple values for same header
headers_multi = Headers([
("accept", "text/html"),
("accept", "application/json"),
("accept", "*/*")
])
accept_values = headers_multi.getlist("accept")
# ["text/html", "application/json", "*/*"]
# Mutable headers
mutable = headers.mutablecopy()
mutable["x-custom"] = "custom-value"
mutable.append("cache-control", "no-cache")
mutable.add_vary_header("Accept-Encoding")# From query string
params = QueryParams("name=john&age=30&tags=python&tags=web")
print(params["name"]) # "john"
print(params.get("age")) # "30"
print(params.getlist("tags")) # ["python", "web"]
# From dictionary
params = QueryParams({
"search": "starlette",
"page": "2",
"per_page": "10"
})
# Iterate over parameters
for key in params:
print(f"{key}: {params[key]}")async def handle_form(request):
form = await request.form()
# Access form fields
name = form.get("name", "")
email = form.get("email", "")
# Handle file uploads
avatar = form.get("avatar")
if avatar and isinstance(avatar, UploadFile):
# Read file content
content = await avatar.read()
# Get file info
filename = avatar.filename
content_type = avatar.content_type
size = avatar.size
# Save file
with open(f"uploads/{filename}", "wb") as f:
await avatar.seek(0) # Reset file position
while chunk := await avatar.read(1024):
f.write(chunk)
# Clean up
await avatar.close()
# Close form to clean up all files
await form.close()
return JSONResponse({
"name": name,
"email": email,
"file_uploaded": bool(avatar and avatar.filename)
})# Create multi-dict from form data
form_data = MultiDict([
("name", "John"),
("interests", "python"),
("interests", "web"),
("interests", "api")
])
# Access values
name = form_data["name"] # "John"
interests = form_data.getlist("interests") # ["python", "web", "api"]
# Modify (mutable version)
form_data.append("interests", "database")
form_data.setlist("skills", ["programming", "design"])
# All items including duplicates
all_items = form_data.multi_items()
# [("name", "John"), ("interests", "python"), ("interests", "web"), ...]# Secret values
api_key = Secret("sk_live_abc123")
print(api_key) # Shows actual value
print(repr(api_key)) # Secret('**********')
# Comma-separated strings
allowed_hosts = CommaSeparatedStrings("localhost,127.0.0.1,example.com")
print(allowed_hosts) # ["localhost", "127.0.0.1", "example.com"]
# Application state
app.state.database_url = "postgresql://..."
app.state.cache = {}
app.state.config = {"debug": True}
# Request state
request.state.user_id = 123
request.state.start_time = time.time()
# Network address
client_addr = Address("192.168.1.100", 54321)
print(client_addr.host) # "192.168.1.100"
print(client_addr.port) # 54321Starlette's data structures provide convenient, type-safe handling of web-specific data with immutable defaults, comprehensive APIs, and proper resource management for files and other resources.
Install with Tessl CLI
npx tessl i tessl/pypi-starlette