vercel-proxy
vercel.proxy lets you write Vercel middleware in Python. It runs before each
request reaches your app and decides whether to let it continue, rewrite it,
redirect it or answer it directly.
from urllib.parse import quote
from vercel.proxy import (
ContinueResponse,
Proxy,
RedirectResponse,
Request,
Response,
RewriteResponse,
)
proxy = Proxy()
@proxy.route("/old-blog/{slug}")
def old_blog(request: Request) -> Response:
slug = quote(request.path_params["slug"])
return RedirectResponse(f"/blog/{slug}", status_code=308)
@proxy.route("/docs/{path:path}")
def docs(request: Request) -> Response:
theme = "dark" if request.cookies.get("theme") == "dark" else "light"
return ContinueResponse(request_headers={"x-theme": theme})
@proxy.route("/api/{path:path}", host="{tenant}.example.com")
async def tenant_api(request: Request) -> Response:
tenant = request.path_params["tenant"]
path = quote(request.path_params["path"])
return RewriteResponse(f"https://{tenant}.api.example.com/{path}")
Setup
Create a proxy module, such as proxy.py, and set the proxy in vercel.json as
module:attribute. A module in a package works too, such as app.proxy:proxy.
{
"proxy": { "entrypoint": "proxy:proxy" }
}
By default the proxy runs for every request. To run it only for some paths, add
a matcher with one path or a list of paths.
{
"proxy": { "entrypoint": "proxy:proxy", "matcher": ["/api/:path*", "/admin"] }
}
The proxy runs separately from your app and gets only the packages in the
vercel.proxy dependency group of pyproject.toml.
[dependency-groups]
"vercel.proxy" = ["vercel-proxy"]
Routes
@proxy.route(path) registers a handler for requests whose path matches. Each
request goes to the first matching route, in the order the routes were
registered.
A path can capture parts of the URL with placeholders. The captured values are
passed to the handler in request.path_params. They are decoded, so quote them
with urllib.parse.quote before building a URL from them, and check any value
that chooses the destination, such as a host or a path containing ...
@proxy.route("/users/{user_id:int}")
def user(request: Request) -> Response:
return RewriteResponse(f"/profiles/{request.path_params['user_id']}")
The following placeholders are available:
{name}captures one path segment as a string.{name:type}also converts the value. The type can bestr,int,floatoruuid.{name:path}captures any part of the path, including/.
Trailing slashes are optional by default. Each request is first matched against
every route as sent, and only then with the trailing slash added or removed. So
with /users/{id} registered before /{path:path}, a request for /users/42/
goes to /{path:path}, which matches it exactly. Pass strict=True to
Proxy() to require exact matches.
A route can also be limited to certain methods or hosts.
@proxy.route("/admin", methods=["GET", "POST"], host="admin.example.com")
def admin(request: Request) -> Response:
return RewriteResponse("/internal/admin")
methodslists the HTTP methods the route accepts. Omit it to accept every method.hostis the hostname the route accepts. It can capture values like the path. Omit it to accept every host.
Handlers can be sync or async. Sync handlers run in a worker thread.
Fallback
Requests that match no route go to the fallback. By default they continue unchanged.
Pass a fallback to the constructor to change it. It can be a response, which is returned for every unmatched request, or a handler.
proxy = Proxy(fallback=JSONResponse({"error": "not found"}, status_code=404))
The fallback can also be set with the @proxy.fallback decorator. It replaces
any fallback passed to the constructor.
@proxy.fallback
def fallback(request: Request) -> Response:
return RewriteResponse("/404")
Requests
Request is a Starlette Request. Use method, url, headers,
query_params, cookies, path_params and client to decide how to route
it.
WebSocket upgrades are routed like GET requests, and the destination accepts
the socket. Check request.headers.get("upgrade") to tell them apart.
Responses
Every handler returns a Response. Responses are based on Starlette's
responses, so headers, set_cookie and
delete_cookie work as in Starlette.
response = ContinueResponse()
response.set_cookie("bucket", "b", max_age=86400)
return response
Header names starting with x-middleware- are reserved. Background tasks are
not supported.
Continuing the request
ContinueResponse() lets the request continue to the original URL.
RewriteResponse(url) serves it from url without changing the URL the client
sees.
For both, request_headers is a dictionary of headers to replace on the
forwarded request. A None value removes the header.
return ContinueResponse(request_headers={"x-region": "eu", "cookie": None})
Answering the request
Response, JSONResponse, PlainTextResponse, HTMLResponse and
RedirectResponse answer the request directly, like the Starlette classes of
the same name. The request does not reach your app.
Metadata
Release files for vercel-proxy 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vercel_proxy-0.1.1.tar.gz | 12.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vercel_proxy-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.3 kB
Release files / vercel_proxy-0.1.1.tar.gz
| Download URL | vercel_proxy-0.1.1.tar.gz |
|---|---|
| Size | 12.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5534230b5c291914193a5751757cff0240faa023332259e79e7b6a73c74dc0a
|
|
BLAKE2b-256 checksum How to use checksums |
8622cb7fc23a4000133319a86a1b4c3b99f644d564baca76c3ea93f41345cc8a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / vercel_proxy-0.1.1-py3-none-any.whl
| Download URL | vercel_proxy-0.1.1-py3-none-any.whl |
|---|---|
| Size | 11.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4467de534bfe18399e486db7114c44c4ecbf7404b72376267d88468e40daee01
|
|
BLAKE2b-256 checksum How to use checksums |
cfc7848d86ee93009de58f9e8889a8ee55d4e05791af56a1d6dbd792004a51bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for vercel-proxy 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.