组织路由
文章服务逐渐有了列表、详情、创建和后台入口。此时仅靠堆放 Route 很难看清请求会落到哪里。本专题围绕“同一组文章接口如何分发和组织”学习路由规则。
路由要同时考虑路径、请求方法和注册顺序。命名路由解决 URL 反向生成;Mount 用于组织一组子路由。示例代码见下文。
启动 05_article_routes.py 后,可运行 routing_demo.py 查看状态码与响应体。
固定路径与处理函数
Route 把路径与处理函数关联起来。先用固定路径建立最简单的分发关系,再增加文章列表。
先看最基础的路由写法。
示例文件:01_basic_routes.py。运行:uvicorn 01_basic_routes:app --reload。
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("首页")
async def article_list(request):
return PlainTextResponse("文章列表")
routes = [
Route("/", homepage),
Route("/articles", article_list),
]
app = Starlette(routes=routes)
if __name__ == '__main__':
import uvicorn
uvicorn.run("01_basic_routes:app", host='127.0.0.1', port=8000, reload=True)这段代码只做最基础的分发:
/ → homepage
/articles → article_list也就是:
不同路径
对应不同处理函数这里只需要先记住:
Route 负责描述“哪个路径交给谁处理”
routes 列表负责把多条路由放在一起
Starlette(routes=routes) 负责把它们注册到应用中路径参数与请求方法
{article_id:int} 匹配并转换编号,methods 限制允许的 HTTP 方法。固定路径应放在可能与它冲突的参数路径前面。
接下来增加两个常见能力:
- 路径参数
- 请求方法限制
示例文件:02_path_and_methods.py。运行:uvicorn 02_path_and_methods:app --reload。
from starlette.applications import Starlette
from starlette.responses import JSONResponse, PlainTextResponse
from starlette.routing import Route
async def article_list(request):
return PlainTextResponse("GET:文章列表")
async def article_detail(request):
article_id = request.path_params["article_id"]
return JSONResponse({"message": "文章详情", "article_id": article_id})
async def article_create(request):
return PlainTextResponse("POST:模拟新增文章")
routes = [
Route("/articles", article_list, methods=["GET"]),
Route("/articles/{article_id:int}", article_detail, methods=["GET"]),
Route("/articles", article_create, methods=["POST"]),
]
app = Starlette(routes=routes)
if __name__ == '__main__':
import uvicorn
uvicorn.run("02_path_and_methods:app", host='127.0.0.1', port=8000, reload=True)传递路径参数
这一行:
Route("/articles/{article_id:int}", article_detail)表示访问这类地址时:
/articles/1
/articles/2
/articles/100都交给 article_detail,并把数字部分作为 article_id 传进去。
限制请求方法
下面两行:
Route("/articles", article_list, methods=["GET"])
Route("/articles", article_create, methods=["POST"])表示:
GET /articles → article_list
POST /articles → article_create路径一样,但方法不同,处理函数也不同。
重点记三件事:
- 路径参数可以写在路径里。
- 路径参数可以在
request.path_params中读取。 - 同一路径可以按请求方法分流到不同处理函数。
生成命名路由 URL
给路由命名后,request.url_for() 可以按名称生成 URL。修改路径时,使用它的跳转代码不必拼接字符串。
接下来学习给路由命名,以及通过名字生成 URL。
示例文件:03_named_routes.py。运行:uvicorn 03_named_routes:app --reload。
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse, RedirectResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("首页")
async def article_list(request):
return PlainTextResponse("文章列表页面")
async def old_articles(request):
url = request.url_for("article_list")
return RedirectResponse(url)
routes = [
Route("/", homepage, methods=["GET"], name="homepage"),
Route("/articles", article_list, methods=["GET"], name="article_list"),
Route("/old-articles", old_articles, methods=["GET"], name="old_articles"),
]
app = Starlette(routes=routes)
if __name__ == '__main__':
import uvicorn
uvicorn.run("03_named_routes:app", host='127.0.0.1', port=8000, reload=True)这里的核心是:
request.url_for("article_list")它表示:
不要手写 /articles
而是通过路由名字生成对应地址然后:
RedirectResponse(new_url)把旧地址重定向到新地址。
使用 url_for 的注意事项
如果使用 url_for(),要保证:
目标路由已经设置了 name
名字拼写正确用 Mount 分组后台路由
后台或独立功能可以放入 Mount 下。子路由仍按原有规则匹配,只是共享路径前缀。
现在看如何把一组相关路由统一放到某个前缀下。
示例文件:04_admin_mount.py。运行:uvicorn 04_admin_mount:app --reload。
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Mount, Route
async def admin_homepage(request):
return PlainTextResponse("后台首页")
async def admin_articles(request):
return PlainTextResponse("后台文章管理")
admin_routes = [
Route("/", admin_homepage, methods=["GET"], name="homepage"),
Route("/articles", admin_articles, methods=["GET"], name="articles"),
]
routes = [
Mount("/admin", routes=admin_routes, name="admin"),
]
app = Starlette(routes=routes)
if __name__ == '__main__':
import uvicorn
uvicorn.run("04_admin_mount:app", host='127.0.0.1', port=8000, reload=True)这段代码表示:
/admin/ → admin_home
/admin/articles → admin_articles也就是先定义一组后台路由,再统一挂到 /admin 下。
Mount 的适用场景
Mount 适合这种情况:
一组路由有共同前缀
例如 /admin、/api、/static这样能让路由结构更清晰。
路由匹配顺序
路由是按顺序匹配的。
这意味着更具体的路由,通常要放在更前面。
例如下面这种顺序就有风险:
routes = [
Route("/articles/{article_id}", article_detail),
Route("/articles/new", article_new),
]如果没有类型限制,访问 /articles/new 时,new 可能会先被当成 article_id。
更稳妥的写法通常是:
routes = [
Route("/articles/new", article_new),
Route("/articles/{article_id:int}", article_detail),
]可以先记住这个经验:
更具体的路由放前面
更通用的动态路由放后面文章服务路由表
最终路由表把文章与后台入口放在同一个应用中。
目标包括:
/ 首页
/articles 文章列表
/articles/new 新建文章说明
/articles/{article_id:int} 文章详情
POST /articles 模拟新增文章
/old-articles 旧地址跳转
/admin/ 后台首页
/admin/articles 后台文章管理示例文件:05_article_routes.py。运行:uvicorn 05_article_routes:app --reload。
from starlette.applications import Starlette
from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
from starlette.routing import Mount, Route
ARTICLES = [
{"id": 1, "title": "认识 Starlette 应用对象"},
{"id": 2, "title": "学习 Request 请求对象"},
{"id": 3, "title": "学习 Response 响应对象"},
{"id": 4, "title": "学习 Starlette 路由系统"},
]
async def homepage(request):
return PlainTextResponse("首页")
async def article_list(request):
return PlainTextResponse("文章列表")
async def article_detail(request):
article_id = request.path_params["article_id"]
return JSONResponse({"message": "文章详情", "article_id": article_id})
async def article_new(request):
return PlainTextResponse("新建文章说明页面")
async def article_create(request):
return PlainTextResponse("POST:模拟新增文章")
async def old_articles(request):
url = request.url_for("article_list")
return RedirectResponse(url)
async def admin_homepage(request):
return PlainTextResponse("后台首页")
async def admin_articles(request):
return PlainTextResponse("后台文章管理")
article_routes = [
Route("/articles", article_list, methods=["GET"], name="article_list"),
Route("/articles", article_create, methods=["POST"], name="article_create"),
Route("/articles/new", article_new, methods=["GET"], name="article_new"),
Route("/articles/{article_id:int}", article_detail, methods=["GET"], name="article_detail"),
]
admin_routes = [
Route("/", admin_homepage, methods=["GET"], name="homepage"),
Route("/articles", admin_articles, methods=["GET"], name="articles"),
]
routes = [
Route("/", homepage, methods=["GET"], name="homepage"),
*article_routes,
Route("/old-articles", old_articles, methods=["GET"], name="old_articles"),
Mount("/admin", routes=admin_routes, name="admin"),
]
app = Starlette(routes=routes)
if __name__ == '__main__':
import uvicorn
uvicorn.run("05_article_routes:app", host='127.0.0.1', port=8000, reload=True)这个案例把本章几个核心点放到了一起:
普通路径
路径参数
请求方法限制
路由命名
URL 反向生成
重定向
Mount 前缀组织它的重点不是业务复杂,而是帮助你建立一张清晰的路由地图。
案例范围
这个案例已经足够说明:
不同路径如何分发
同一路径不同方法如何分发
动态路径如何处理
相关路由如何按前缀组织但它还没有进入更复杂的项目拆分。 本章先把路由系统本身讲清楚,复杂目录结构以后再说。
案例源码
import requests
BASE_URL = "http://127.0.0.1:8000"
def test_article_paths():
for path in ("/articles", "/articles/new", "/articles/1"):
response = requests.get(f"{BASE_URL}{path}", timeout=5)
print(path, response.status_code, response.text)
def test_method_dispatch():
response = requests.post(f"{BASE_URL}/articles", timeout=5)
print("POST /articles", response.status_code, response.text)
def test_named_redirect():
response = requests.get(
f"{BASE_URL}/old-articles", allow_redirects=False, timeout=5
)
print("GET /old-articles", response.status_code)
print("Location:", response.headers.get("location"))
def test_mounted_routes():
response = requests.get(f"{BASE_URL}/admin/articles", timeout=5)
print("GET /admin/articles", response.status_code, response.text)
if __name__ == "__main__":
test_article_paths()
test_method_dispatch()
test_named_redirect()
test_mounted_routes()是否可以直接帮我修改当前项目的配置文件进行关闭
将默认 ai 的登录也关闭
为何 vscode 里面无法使用中文输入法
是不是系统的原因,按切换输入法没有反应
输入法是 fcitx5 的雾凇输入法
应该是连接问题,怎么解决
问题还是没有解决
好像还与 vs code 的快捷键冲突了