fastapi 请求参数校验机制

"""
第二步:根据类型提示自动转换/校验参数

目标:搞懂「内省(introspection)」—— 程序在运行时读自己函数的
参数名和类型注解,据此自动处理参数。

对应 fastapi 里的:你写 def f(x: int),客户端传 "2",fastapi 自动给你 2(int)。
真实请求里参数都是字符串,框架负责按类型转换 + 校验。
"""

import inspect


class App:
    def __init__(self):
        self.routes = {}

    def get(self, path):
        def decorator(func):
            self.routes[("GET", path)] = func
            return func
        return decorator

    def handle(self, method, path, raw_params: dict):
        """
        模拟一个请求到来。
        raw_params 模拟从 URL / 表单里拿到的原始参数,值全是字符串
        (就像真实 HTTP 里那样)。
        """
        func = self.routes.get((method, path))
        if func is None:
            return "404 Not Found"

        # ---- 这里是本步的核心:按类型提示转换参数 ----
        kwargs = {}
        sig = inspect.signature(func)            # 内省:读出函数签名
        for name, param in sig.parameters.items():
            type_ = param.annotation             # 拿到该参数的类型,比如 int
            raw_value = raw_params.get(name)

            if raw_value is None:
                if param.default is not inspect.Parameter.empty:
                    raw_value = param.default

            if type_ is inspect.Parameter.empty:
                # 没写类型注解,就原样传字符串
                kwargs[name] = raw_value
            else:
                try:
                    kwargs[name] = type_(raw_value)   # 用类型当转换函数:int("2") -> 2
                except (ValueError, TypeError):
                    return f"422 参数 {name}={raw_value!r} 无法转换成 {type_.__name__}"

        return func(**kwargs)                     # 用转换好的参数调用业务函数


app = App()


@app.get("/add")
def add(x: int, y: int):
    return x + y


@app.get("/repeat")
def repeat(text: str, times: int):
    return text * times

@app.get("/devide")
def devide(x: float, y: float = 2):
    return x / y


if __name__ == "__main__":
    # 注意:传进去的值全是字符串(模拟真实请求)
    print('/add  x="2" y="3"      ->', app.handle("GET", "/add", {"x": "2", "y": "3"}))
    print('/repeat text="ab" times="3" ->', app.handle("GET", "/repeat", {"text": "ab", "times": "3"}))
    print()
    # 故意制造错误,看校验生效
    print('/add  x="hello" y="3"  ->', app.handle("GET", "/add", {"x": "hello", "y": "3"}))
    print('/add  缺 y              ->', app.handle("GET", "/add", {"x": "2"}))
    # 我自己加的 /devide path
    print('/devide x="8.6" y="2" ->', app.handle("GET", "/devide", {"x": "8.6", "y": "2"}))
    print('/devide x="8.6" ->', app.handle("GET", "/devide", {"x": "8.6"}))

输出结果:

/add  x="2" y="3"      -> 5
/repeat text="ab" times="3" -> ababab

/add  x="hello" y="3"  -> 422 参数 x='hello' 无法转换成 int
/add  缺 y              -> 422 参数 y=None 无法转换成 int
/devide x="8.6" y="2" -> 4.3
/devide x="8.6" -> 4.3
posted @ 2026-07-03 12:39  RolandHe  阅读(5)  评论(0)    收藏  举报