
👉 项目官网:https://www.python-office.com/ 👈
👉 本开源项目的交流群 👈


大家好,这里是程序员晚枫,正在all in AI编程实战,全网同名。
(4 h 直播 / 录播可拆 2×2 h)
目标
• 把元类、描述符、异步上下文管理器、数据类全部串烧
• 实现「路由 + 中间件 + 异步视图 + 自动 Swagger」的完整闭环
• 最终产出可 pip install 的框架包,并在启动 banner 植入「程序员晚枫」彩蛋
──────────────────
10.0 开场 3 min
“今天不写业务,而是写‘能写业务的工具’;300 行代码,跑出一个 FastAPI 的 80 % 能力。”
──────────────────
10.1 需求澄清(5 min)
| 功能 | 技术点 |
|--------------|--------------------------------|
| 路由 | 元类自动注册 |
| 依赖注入 | 描述符 + 数据类 |
| 中间件 | 异步上下文管理器 |
| 文档 | dataclass → JSON Schema → Swagger |
| CLI | typer 一键启动 |
──────────────────
10.2 项目骨架 10 min
1 2 3 4 5 6 7 8 9 10 11
| miniweb/ ├── src/miniweb/ │ ├── __init__.py │ ├── app.py # 核心应用 │ ├── routing.py # 元类注册 │ ├── middleware.py # 洋葱中间件 │ ├── schema.py # 数据类转 Schema │ └── cli.py # typer CLI ├── tests/ ├── pyproject.toml └── README.md
|
──────────────────
10.3 路由元类 25 min
1 2 3 4
| from miniweb.routing import RouterMeta
class App(metaclass=RouterMeta): prefix = "/api"
|
实现 RouterMeta:
1 2 3 4 5 6 7 8 9 10 11 12
| class RouterMeta(type): registry = {}
def __new__(mcls, name, bases, ns): cls = super().__new__(mcls, name, bases, ns) prefix = getattr(cls, "prefix", "") for k, v in list(ns.items()): if callable(v) and hasattr(v, "__route__"): method, path = v.__route__ key = f"{method}:{prefix}{path}" cls.registry[key] = v return cls
|
装饰器语法糖:
1 2 3 4 5 6 7
| from functools import wraps
def get(path: str): def decorator(func): func.__route__ = ("GET", path) return func return decorator
|
──────────────────
10.4 异步视图 + 依赖注入 30 min
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from dataclasses import dataclass from miniweb.app import App, get from miniweb.middleware import JSONResponse
@dataclass class PageQuery: page: int = 1 size: int = 10
@app.get("/users") async def list_users(query: PageQuery): return JSONResponse({ "items": [{"id": i, "name": f"user{i}"} for i in range(query.size)], "page": query.page, "total": 100 })
|
依赖注入实现:
1 2 3 4 5 6 7 8 9 10
| from inspect import signature from typing import get_type_hints
async def inject_params(func, request): hints = get_type_hints(func) kwargs = {} for name, cls in hints.items(): if cls is PageQuery: kwargs[name] = PageQuery(**request["query"]) return await func(**kwargs)
|
──────────────────
10.5 洋葱中间件 25 min
1 2 3 4 5 6 7
| from contextlib import asynccontextmanager
@asynccontextmanager async def cors_middleware(request): print("程序员晚枫 CORS 前置") response = yield response.headers["Access-Control-Allow-Origin"] = "*"
|
注册方式:
1
| app.use(cors_middleware)
|
──────────────────
10.6 数据类 → Swagger 自动生成 20 min
1 2 3 4 5
| from miniweb.schema import schema_from_dataclass
spec = schema_from_dataclass(PageQuery) print(spec)
|
实现:
1 2 3 4 5 6 7 8 9 10 11
| from typing import get_type_hints, get_origin, get_args import json
def schema_from_dataclass(cls): fields = {} for k, v in get_type_hints(cls).items(): if v is int: fields[k] = {"type": "integer"} elif get_origin(v) is int: fields[k] = {"type": "integer", "default": getattr(cls, k, None)} return {"type": "object", "properties": fields}
|
──────────────────
10.7 CLI 启动 & 彩蛋 Banner 15 min
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import typer, uvicorn, textwrap
cli = typer.Typer()
@cli.command() def dev(host: str = "0.0.0.0", port: int = 8000): banner = textwrap.dedent(""" ╭─────────────────────────────╮ │ MiniWeb 已启动 🚀 │ │ Made with ❤ by 程序员晚枫 │ │ Swagger: http://{host}:{port}/docs │ ╰─────────────────────────────╯ """).format(host=host, port=port) print(banner) uvicorn.run("miniweb.app:app", host=host, port=port, reload=True)
if __name__ == "__main__": cli()
|
──────────────────
10.8 打包 & 发布 10 min
1 2
| poetry build poetry publish
|
──────────────────
10.9 单元测试 & 基准 15 min
1 2 3 4 5 6 7 8 9
| import pytest, httpx, asyncio
@pytest.mark.asyncio async def test_list_users(): from miniweb.app import app async with httpx.AsyncClient(app=app, base_url="http://test") as ac: res = await ac.get("/api/users", params={"page": 2, "size": 5}) assert res.status_code == 200 assert len(res.json()["items"]) == 5
|
──────────────────
10.10 一键脚手架 10 min
1 2 3
| pip install miniweb miniweb new mysite cd mysite && miniweb dev
|
──────────────────
10.11 小结 & 彩蛋彩蛋(5 min)
300 行代码 ≈ FastAPI 20 % 功能,但 100 % 覆盖了课程所有知识点。
──────────────────
10.12 终极作业
- 必做:给框架加
@cache 装饰器,底层用 functools.lru_cache,并在缓存命中时打印「程序员晚枫的缓存命中 +1」。 - 选做:支持 WebSocket 路由
/ws,使用 anyio 兼容 trio/asyncio。 - 彩蛋:启动时随机输出 3 条「程序员晚枫」段子,来自内置 JSON。
提交:
• 仓库 fork 至 feat/lesson10
• CI 通过后自动打 tag v1.0.0 并发 Tweet:
“🎉 MiniWeb 1.0 发布!300 行代码带你飞,感谢 程序员晚枫 全程陪跑。”
(课程完结 🎉)
大家在学习课程中有任何问题,欢迎+微信和我交流👉我的联系方式:微信、读者群、1对1、福利



程序员晚枫专注AI编程培训,小白看完他和图灵社区合作的教程《30讲 · AI编程训练营》就能上手做AI项目。
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!