如何使用Field对象
Field
对象在Pait
中起到了至关重要的作用, Pait
除了通过Field
对象获取数据来源外, 还可以实现其它的功能, 不过本章中只着重说明参数校验。
1.Field的种类
除了介绍 提到的Body
外, 还有其他不同含义的Field
对象, 它们的名称和作用如下:
Body: 获取当前请求的json数据
Cookie: 获取当前请求的cookie数据(注意, 目前Cookie数据会被转化为Python的dict对象, 这意味着Cookie的Key不能重复。建议当Field为Cookie时,参数的类型为str)
File:获取当前请求的file对象,该对象与原Web框架的file对象一致
Form:获取当前请求的form数据,如果有多个重复Key,只会返回第一个值
Header: 获取当前请求的header数据
Json: 获取当前请求的json数据(与Body一样)
Path: 获取当前请求的path数据,如/api/{version}/test
,则会获取到version的数据
Query: 获取当前请求的Url参数对应的数据,如果有多个重复Key,只会返回第一个值
MultiForm:获取当前请求的form数据, 返回Key对应的数据列表
MultiQuery:获取当前请求的Url参数对应的数据, 返回Key对应的数据列表
Field
使用方法很简单,只要在<name>:<type>=<default>
的default
使用Field
即可,以这段代码为例子:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_demo.py from enum import Enum
from typing import List , Optional
from flask import Flask
from pait.app.flask import pait
from pait.field import Cookie , Form , MultiForm , MultiQuery , Path , Query
class SexEnum ( str , Enum ):
man : str = "man"
woman : str = "woman"
@pait ()
def demo_route (
a : str = Form . t ( description = "form data" ),
b : str = Form . t ( description = "form data" ),
c : List [ str ] = MultiForm . t ( description = "form data" ),
cookie : dict = Cookie . t ( raw_return = True , description = "cookie" ),
multi_user_name : List [ str ] = MultiQuery . t ( description = "user name" , min_length = 2 , max_length = 4 ),
age : int = Path . t ( description = "age" , gt = 1 , lt = 100 ),
uid : int = Query . t ( description = "user id" , gt = 10 , lt = 1000 ),
user_name : str = Query . t ( description = "user name" , min_length = 2 , max_length = 4 ),
email : Optional [ str ] = Query . t ( default = "example@xxx.com" , description = "user email" ),
sex : SexEnum = Query . t ( description = "sex" ),
) -> dict :
return {
"code" : 0 ,
"msg" : "" ,
"data" : {
"form_a" : a ,
"form_b" : b ,
"form_c" : c ,
"cookie" : cookie ,
"multi_user_name" : multi_user_name ,
"age" : age ,
"uid" : uid ,
"user_name" : user_name ,
"email" : email ,
"sex" : sex ,
},
}
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo/<age>" , "demo" , demo_route , methods = [ "POST" ])
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_demo.py from enum import Enum
from typing import List , Optional
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from pait.app.starlette import pait
from pait.field import Cookie , Form , MultiForm , MultiQuery , Path , Query
class SexEnum ( str , Enum ):
man : str = "man"
woman : str = "woman"
@pait ()
async def demo_route (
a : str = Form . t ( description = "form data" ),
b : str = Form . t ( description = "form data" ),
c : List [ str ] = MultiForm . t ( description = "form data" ),
cookie : dict = Cookie . t ( raw_return = True , description = "cookie" ),
multi_user_name : List [ str ] = MultiQuery . t ( description = "user name" , min_length = 2 , max_length = 4 ),
age : int = Path . t ( description = "age" , gt = 1 , lt = 100 ),
uid : int = Query . t ( description = "user id" , gt = 10 , lt = 1000 ),
user_name : str = Query . t ( description = "user name" , min_length = 2 , max_length = 4 ),
email : Optional [ str ] = Query . t ( default = "example@xxx.com" , description = "user email" ),
sex : SexEnum = Query . t ( description = "sex" ),
) -> JSONResponse :
return JSONResponse (
{
"code" : 0 ,
"msg" : "" ,
"data" : {
"form_a" : a ,
"form_b" : b ,
"form_c" : c ,
"cookie" : cookie ,
"multi_user_name" : multi_user_name ,
"age" : age ,
"uid" : uid ,
"user_name" : user_name ,
"email" : email ,
"sex" : sex ,
},
}
)
app : Starlette = Starlette ()
app . add_route ( "/api/demo/ {age} " , demo_route , methods = [ "POST" ])
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_demo.py from enum import Enum
from typing import List , Optional
from sanic import HTTPResponse , Sanic , json
from pait.app.sanic import pait
from pait.field import Cookie , Form , MultiForm , MultiQuery , Path , Query
class SexEnum ( str , Enum ):
man : str = "man"
woman : str = "woman"
@pait ()
async def demo_route (
a : str = Form . t ( description = "form data" ),
b : str = Form . t ( description = "form data" ),
c : List [ str ] = MultiForm . t ( description = "form data" ),
cookie : dict = Cookie . t ( raw_return = True , description = "cookie" ),
multi_user_name : List [ str ] = MultiQuery . t ( description = "user name" , min_length = 2 , max_length = 4 ),
age : int = Path . t ( description = "age" , gt = 1 , lt = 100 ),
uid : int = Query . t ( description = "user id" , gt = 10 , lt = 1000 ),
user_name : str = Query . t ( description = "user name" , min_length = 2 , max_length = 4 ),
email : Optional [ str ] = Query . t ( default = "example@xxx.com" , description = "user email" ),
sex : SexEnum = Query . t ( description = "sex" ),
) -> HTTPResponse :
return json (
{
"code" : 0 ,
"msg" : "" ,
"data" : {
"form_a" : a ,
"form_b" : b ,
"form_c" : c ,
"cookie" : cookie ,
"multi_user_name" : multi_user_name ,
"age" : age ,
"uid" : uid ,
"user_name" : user_name ,
"email" : email ,
"sex" : sex ,
},
}
)
app : Sanic = Sanic ( name = "demo" )
app . add_route ( demo_route , "/api/demo/<age>" , methods = { "POST" })
if __name__ == "__main__" :
app . run ()
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_demo.py from enum import Enum
from typing import List , Optional
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait.app.tornado import pait
from pait.field import Cookie , Form , MultiForm , MultiQuery , Path , Query
from pait.openapi.doc_route import AddDocRoute
class SexEnum ( str , Enum ):
man : str = "man"
woman : str = "woman"
class DemoHandler ( RequestHandler ):
@pait ()
async def post (
self ,
a : str = Form . t ( description = "form data" ),
b : str = Form . t ( description = "form data" ),
c : List [ str ] = MultiForm . t ( description = "form data" ),
cookie : dict = Cookie . t ( raw_return = True , description = "cookie" ),
multi_user_name : List [ str ] = MultiQuery . t ( description = "user name" , min_length = 2 , max_length = 4 ),
age : int = Path . t ( description = "age" , gt = 1 , lt = 100 ),
uid : int = Query . t ( description = "user id" , gt = 10 , lt = 1000 ),
user_name : str = Query . t ( description = "user name" , min_length = 2 , max_length = 4 ),
email : Optional [ str ] = Query . t ( default = "example@xxx.com" , description = "user email" ),
sex : SexEnum = Query . t ( description = "sex" ),
) -> None :
self . write (
{
"code" : 0 ,
"msg" : "" ,
"data" : {
"form_a" : a ,
"form_b" : b ,
"form_c" : c ,
"cookie" : cookie ,
"multi_user_name" : multi_user_name ,
"age" : age ,
"uid" : uid ,
"user_name" : user_name ,
"email" : email ,
"sex" : sex ,
},
}
)
app : Application = Application ([( r "/api/demo/(?P<age>\w+)" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
Note
为了确保演示的代码能够在不同的机器上顺利运行,这里没有演示File
字段的用法,具体用法请参考不同Web框架示例代码中的field_route.py
文件中/api/pait-base-field
对应的路由函数。
示例代码演示了通过不同种类Field
从请求对象获取请求者的参数,并组装成一定的格式返回。
接下来运行示例代码,然后使用curl
命令在终端进行一次请求测试,命令如下:
curl -X 'POST' \
'http://127.0.0.1:8000/api/demo/18?multi_user_name=aaa&multi_user_name=bbb&uid=999&user_name=so1n&sex=man' \
-H 'accept: */*' \
-H 'Cookie: cookie=aaa,aaa' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'a=aaa&b=bbb&c=ccc1&c=ccc2'
正常情况下,会在终端看到如下输出:
{
"code" : 0 ,
"data" : {
"age" : 18 ,
"cookie" : {
"cookie" : "aaa,aaa"
},
"email" : "example@xxx.com" ,
"form_a" : "aaa" ,
"form_b" : "bbb" ,
"form_c" : [
"ccc1" ,
"ccc2"
],
"multi_user_name" : [
"aaa" ,
"bbb"
],
"sex" : "man" ,
"uid" : 999 ,
"user_name" : "so1n"
},
"msg" : ""
}
通过输出结果可以发现,Pait
都能通过Field
的种类准确的从请求对象获取对应的值。
2.Field的功能
从上面的例子可以看到url
没有携带email
参数, 但是接口返回的响应值中的email
却为example@xxx.com
。
这是因为email
字段的Field
的default
属性被设置为example@xx.com
, 这样Pait
会在无法通过请求体获取到email
值的的情况下,把默认值赋给变量。
除了默认值之外, Field
也有很多的功能,这些功能大部分来源于Field
所继承的pydantic.Field
。
2.1.default
Pait
通过读取Field
的default
属性来获取该参数的默认值,当Field
的default
属性不为空且请求体没有对应的值时,Pait
就会把default
的值注入到对应的变量中。
下面是简单的示例代码,示例代码中的两个接口都直接返回获取到的值demo_value
,其中demo
接口带有默认值, 默认值为字符串123,而demo1
接口没有默认值:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_default_demo.py from flask import Flask
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> str :
if isinstance ( exc , TipException ):
exc = exc . exc
return str ( exc )
@pait ()
def demo ( demo_value : str = field . Query . t ( default = "123" )) -> str :
return demo_value
@pait ()
def demo1 ( demo_value : str = field . Query . t ()) -> str :
return demo_value
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . add_url_rule ( "/api/demo1" , view_func = demo1 , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_default_demo.py from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> PlainTextResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
return PlainTextResponse ( str ( exc ))
@pait ()
async def demo ( demo_value : str = field . Query . t ( default = "123" )) -> PlainTextResponse :
return PlainTextResponse ( demo_value )
@pait ()
async def demo1 ( demo_value : str = field . Query . t ()) -> PlainTextResponse :
return PlainTextResponse ( demo_value )
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
Route ( "/api/demo1" , demo1 , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_default_demo.py from sanic import HTTPResponse , Request , Sanic
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
return HTTPResponse ( str ( exc ))
@pait ()
async def demo ( demo_value : str = field . Query . t ( default = "123" )) -> HTTPResponse :
return HTTPResponse ( demo_value )
@pait ()
async def demo1 ( demo_value : str = field . Query . t ()) -> HTTPResponse :
return HTTPResponse ( demo_value )
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . add_route ( demo1 , "/api/demo1" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_default_demo.py from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
self . write ( str ( exc ))
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get ( self , demo_value : str = field . Query . t ( default = "123" )) -> None :
self . write ( demo_value )
class Demo1Handler ( _Handler ):
@pait ()
async def get ( self , demo_value : str = field . Query . t ()) -> None :
self . write ( demo_value )
app : Application = Application ([( r "/api/demo" , DemoHandler ), ( r "/api/demo1" , Demo1Handler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
在运行代码且调用curl
后可以发现,当没有传demo_value
参数时,/api/demo
路由默认返回123, 而/api/demo1
路由会抛出找不到demo_value
值的错误,如下:
curl "http://127.0.0.1:8000/api/demo" 123 curl "http://127.0.0.1:8000/api/demo1" Can not found demo_value value
当传递的demo_value
参数为456时,/api/demo
和/api/demo1
路由都会返回456:
curl "http://127.0.0.1:8000/api/demo?demo_value=456" 456 curl "http://127.0.0.1:8000/api/demo1?demo_value=456" 456
Note
错误处理使用了TipException
,可以通过异常提示 了解TipException
的作用。
2.2.default_factory
default_factory
的作用与default
类似,只不过default_factory
接收的值是函数,只有当请求命中路由函数且Pait
无法从请求对象中找到变量需要的值时才会被执行并将返回值注入到变量中。
示例代码如下,第一个接口的默认值是当前时间, 第二个接口的默认值是uuid,他们每次的返回值都是收到请求时生成的:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_default_factory_demo.py import datetime
import uuid
from flask import Flask
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> str :
if isinstance ( exc , TipException ):
exc = exc . exc
return str ( exc )
@pait ()
def demo ( demo_value : datetime . datetime = field . Query . t ( default_factory = datetime . datetime . now )) -> str :
return str ( demo_value )
@pait ()
def demo1 ( demo_value : str = field . Query . t ( default_factory = lambda : uuid . uuid4 () . hex )) -> str :
return demo_value
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . add_url_rule ( "/api/demo1" , view_func = demo1 , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_default_factory_demo.py import datetime
import uuid
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> PlainTextResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
return PlainTextResponse ( str ( exc ))
@pait ()
async def demo (
demo_value : datetime . datetime = field . Query . t ( default_factory = datetime . datetime . now ),
) -> PlainTextResponse :
return PlainTextResponse ( str ( demo_value ))
@pait ()
async def demo1 ( demo_value : str = field . Query . t ( default_factory = lambda : uuid . uuid4 () . hex )) -> PlainTextResponse :
return PlainTextResponse ( demo_value )
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
Route ( "/api/demo1" , demo1 , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_default_factory_demo.py import datetime
import uuid
from sanic import HTTPResponse , Request , Sanic
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
return HTTPResponse ( str ( exc ))
@pait ()
async def demo ( demo_value : datetime . datetime = field . Query . t ( default_factory = datetime . datetime . now )) -> HTTPResponse :
return HTTPResponse ( str ( demo_value ))
@pait ()
async def demo1 ( demo_value : str = field . Query . t ( default_factory = lambda : uuid . uuid4 () . hex )) -> HTTPResponse :
return HTTPResponse ( demo_value )
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . add_route ( demo1 , "/api/demo1" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_default_factory_demo.py import datetime
import uuid
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
self . write ( str ( exc ))
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get ( self , demo_value : datetime . datetime = field . Query . t ( default_factory = datetime . datetime . now )) -> None :
self . write ( str ( demo_value ))
class Demo1Handler ( _Handler ):
@pait ()
async def get ( self , demo_value : str = field . Query . t ( default_factory = lambda : uuid . uuid4 () . hex )) -> None :
self . write ( demo_value )
app : Application = Application ([( r "/api/demo" , DemoHandler ), ( r "/api/demo1" , Demo1Handler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
在运行代码并使用curl
调用可以发现接口每次返回的结果都是不一样的:
curl "http://127.0.0.1:8000/api/demo" 2022-02-07T14:54:29.127519 curl "http://127.0.0.1:8000/api/demo" 2022-02-07T14:54:33.789994 curl "http://127.0.0.1:8000/api/demo1" 7e4659e18103471da9db91ed4843d962 curl "http://127.0.0.1:8000/api/demo1" ef84f04fa9fc4ea9a8b44449c76146b8
2.3.alias
通常情况下Pait
会以参数名为key从请求体中获取数据,但有一些参数名如Content-Type
是Python不支持的变量命名方式, 此时可以使用alias
来为变量设置别名,如下示例代码:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_alias_demo.py from flask import Flask
from pait import field
from pait.app.flask import pait
@pait ()
def demo ( content_type : str = field . Header . t ( alias = "Content-Type" )) -> str :
return content_type
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_alias_demo.py from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
@pait ()
async def demo ( content_type : str = field . Header . t ( alias = "Content-Type" )) -> PlainTextResponse :
return PlainTextResponse ( content_type )
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
]
)
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_alias_demo.py from sanic import HTTPResponse , Sanic
from pait import field
from pait.app.sanic import pait
@pait ()
async def demo ( content_type : str = field . Header . t ( alias = "Content-Type" )) -> HTTPResponse :
return HTTPResponse ( content_type )
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_alias_demo.py from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.openapi.doc_route import AddDocRoute
class DemoHandler ( RequestHandler ):
@pait ()
async def get ( self , content_type : str = field . Header . t ( alias = "Content-Type" )) -> None :
self . write ( content_type )
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
运行代码并调用curl
命令后可以发现,Pait
正常的从请求体的Header中提取Content-Type
的值并赋给了content_type
变量,所以路由函数能正常返回值123
:
curl "http://127.0.0.1:8000/api/demo" -H "Content-Type:123" 123
2.4.数值类型校验之gt,ge,lt,le,multiple_of
gt
,ge
,lt
,le
,multiple_of
都属于pydantic
的数值类型校验, 仅用于数值的类型,他们的作用各不相同:
gt:仅用于数值的类型,会校验数值是否大于该值,同时也会在OpenAPI添加exclusiveMinimum
属性。
ge:仅用于数值的类型,会校验数值是否大于等于该值,同时也会在OpenAPI添加exclusiveMinimum
属性。
lt:仅用于数值的类型,会校验数值是否小于该值,同时也会在OpenAPI添加exclusiveMaximum
属性。
le:仅用于数值的类型,会校验数值是否小于等于该值,同时也会在OpenAPI添加exclusiveMaximum
属性。
multiple_of:仅用于数字, 会校验该数字是否是指定值得倍数。
使用方法如下:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_num_check_demo.py from flask import Flask , Response , jsonify
from pydantic import ValidationError
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> Response :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return jsonify ({ "data" : exc . errors ()})
return jsonify ({ "data" : str ( exc )})
@pait ()
def demo (
demo_value1 : int = field . Query . i ( gt = 1 , lt = 10 ),
demo_value2 : int = field . Query . i ( ge = 1 , le = 1 ),
demo_value3 : int = field . Query . i ( multiple_of = 3 ),
) -> dict :
return { "data" : [ demo_value1 , demo_value2 , demo_value3 ]}
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_num_check_demo.py from pydantic import ValidationError
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> JSONResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return JSONResponse ({ "data" : exc . errors ()})
return JSONResponse ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value1 : int = field . Query . i ( gt = 1 , lt = 10 ),
demo_value2 : int = field . Query . i ( ge = 1 , le = 1 ),
demo_value3 : int = field . Query . i ( multiple_of = 3 ),
) -> JSONResponse :
return JSONResponse ({ "data" : [ demo_value1 , demo_value2 , demo_value3 ]})
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_num_check_demo.py from pydantic import ValidationError
from sanic import HTTPResponse , Request , Sanic , json
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return json ({ "data" : exc . errors ()})
return json ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value1 : int = field . Query . i ( gt = 1 , lt = 10 ),
demo_value2 : int = field . Query . i ( ge = 1 , le = 1 ),
demo_value3 : int = field . Query . i ( multiple_of = 3 ),
) -> HTTPResponse :
return json ({ "data" : [ demo_value1 , demo_value2 , demo_value3 ]})
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_num_check_demo.py from pydantic import ValidationError
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
self . write ({ "data" : exc . errors ()})
else :
self . write ({ "data" : str ( exc )})
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get (
self ,
demo_value1 : int = field . Query . i ( gt = 1 , lt = 10 ),
demo_value2 : int = field . Query . i ( ge = 1 , le = 1 ),
demo_value3 : int = field . Query . i ( multiple_of = 3 ),
) -> None :
self . write ({ "data" : [ demo_value1 , demo_value2 , demo_value3 ]})
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
这份示例代码只有一个接口,但是接受了三个参数demo_value1
, demo_value2
, demo_value3
,他们分别只接收符合大于1小于10,等于1以及3的倍数的三个参数。
在运行代码并调用curl
命令可以发现第一个请求符合要求并得到了正确的响应结果,
第二,三,四个请求分别是demo_value1
,demo_value2
,demo_value3
的值不在要求的范围内,所以Pait
会生成Pydantic.ValidationError
的错误信息,从错误信息可以简单的看出来三个参数都不符合接口设置的限定条件:
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value1=2&demo_value2=1&demo_value3=3"
{ "data" :[ 2 ,1,3]}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value1=11&demo_value2=1&demo_value3=3"
{
"data" : [
{
"ctx" : { "limit_value" : 10 } ,
"loc" : [ "query" , "demo_value1" ] ,
"msg" : "ensure this value is less than 10" ,
"type" : "value_error.number.not_lt"
}
]
}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value1=2&demo_value2=2&demo_value3=3"
{
"data" : [
{
"ctx" : { "limit_value" : 1 } ,
"loc" : [ "query" , "demo_value2" ] ,
"msg" : "ensure this value is less than or equal to 1" ,
"type" : "value_error.number.not_le"
}
]
}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value1=2&demo_value2=1&demo_value3=4"
{
"data" : [
{
"ctx" : { "multiple_of" : 3 } ,
"loc" : [ "query" , "demo_value3" ] ,
"msg" : "ensure this value is a multiple of 3" ,
"type" : "value_error.number.not_multiple"
}
]
}
2.5.数组校验之min_items,max_items
min_items
,max_items
都属于pydantic
的Sequence
类型校验,仅用于Sequence
的类型,他们的作用各不相同:
min_items:仅用于Sequence
类型,会校验Sequence
长度是否满足大于等于指定的值。
max_items: 仅用于Sequence
类型,会校验Sequence
长度是否满足小于等于指定的值。
Note
如果使用的Pydantic版本大于2.0.0,请使用min_length
和max_length
代替min_items
和max_items
。
示例代码如下,该路由函数通过field.MultiQuery
从请求Url中获取参数demo_value
的数组,并返回给调用端,其中数组的长度限定在1到2之间:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_item_check_demo.py from typing import List
from flask import Flask , Response , jsonify
from pydantic import ValidationError
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> Response :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return jsonify ({ "data" : exc . errors ()})
return jsonify ({ "data" : str ( exc )})
@pait ()
def demo (
demo_value : List [ int ] = field . MultiQuery . i ( min_items = 1 , max_items = 2 ),
) -> dict :
return { "data" : demo_value }
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_item_check_demo.py from typing import List
from pydantic import ValidationError
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> JSONResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return JSONResponse ({ "data" : exc . errors ()})
return JSONResponse ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value : List [ int ] = field . MultiQuery . i ( min_items = 1 , max_items = 2 ),
) -> JSONResponse :
return JSONResponse ({ "data" : demo_value })
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_item_check_demo.py from typing import List
from pydantic import ValidationError
from sanic import HTTPResponse , Request , Sanic , json
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return json ({ "data" : exc . errors ()})
return json ({ "data" : str ( exc )})
@pait ()
async def demo ( demo_value : List [ int ] = field . MultiQuery . i ( min_items = 1 , max_items = 2 )) -> HTTPResponse :
return json ({ "data" : demo_value })
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_item_check_demo.py from typing import List
from pydantic import ValidationError
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
self . write ({ "data" : exc . errors ()})
else :
self . write ({ "data" : str ( exc )})
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get ( self , demo_value : List [ int ] = field . MultiQuery . i ( min_items = 1 , max_items = 2 )) -> None :
self . write ({ "data" : demo_value })
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
与2.4一样,通过curl
调用可以发现合法的参数会放行,不合法的参数则会抛错:
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=1"
{ "data" :[ 1 ]}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=1&demo_value=2"
{ "data" :[ 1 ,2]}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=1&demo_value=2&demo_value=3"
{
"data" : [
{
"loc" : [
"demo_value"
] ,
"msg" : "ensure this value has at most 2 items" ,
"type" : "value_error.list.max_items" ,
"ctx" : {
"limit_value" : 2
}
}
]
}
2.6.字符串校验之min_length,max_length,regex
min_length
,max_length
,regex
都属于pydantic
的字符串类型校验,仅用于字符串的类型,他们的作用各不相同:
min_length:仅用于字符串类型,会校验字符串的长度是否满足大于等于指定的值。
max_length:仅用于字符串类型,会校验字符串的长度是否满足小于等于指定的值。
regex:仅用于字符串类型,会校验字符串是否符合该正则表达式。
Note
如果使用的Pydantic版本大于2.0.0,min_length
和max_length
还可以校验序列类型,并考虑使用pattern
代替regex
。
示例代码如下, 该路由函数需要从Url中获取一个长度大小为6并以英文字母u开头的值:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_string_check_demo.py from flask import Flask , Response , jsonify
from pydantic import ValidationError
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> Response :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return jsonify ({ "data" : exc . errors ()})
return jsonify ({ "data" : str ( exc )})
@pait ()
def demo ( demo_value : str = field . Query . i ( min_length = 6 , max_length = 6 , regex = "^u" )) -> dict :
return { "data" : demo_value }
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_string_check_demo.py from pydantic import ValidationError
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> JSONResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return JSONResponse ({ "data" : exc . errors ()})
return JSONResponse ({ "data" : str ( exc )})
@pait ()
async def demo ( demo_value : str = field . Query . i ( min_length = 6 , max_length = 6 , regex = "^u" )) -> JSONResponse :
return JSONResponse ({ "data" : demo_value })
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_string_check_demo.py from pydantic import ValidationError
from sanic import HTTPResponse , Request , Sanic , json
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return json ({ "data" : exc . errors ()})
return json ({ "data" : str ( exc )})
@pait ()
async def demo ( demo_value : str = field . Query . i ( min_length = 6 , max_length = 6 , regex = "^u" )) -> HTTPResponse :
return json ({ "data" : demo_value })
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_string_check_demo.py from pydantic import ValidationError
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
self . write ({ "data" : exc . errors ()})
else :
self . write ({ "data" : str ( exc )})
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get ( self , demo_value : str = field . Query . i ( min_length = 6 , max_length = 6 , regex = "^u" )) -> None :
self . write ({ "data" : demo_value })
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
运行代码并使用curl
进行三次请求,通过结果可以看出,第一次请求的结果为正常,第二次请求的结果不符合正则表达式,而第三次请求的结果长度不符合要求:
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=u66666"
{ "data" :"u66666" }
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=666666"
{ "data" :[{ "loc" :[ "demo_value" ] ,"msg" :"string does not match regex \"^u\"" ,"type" :"value_error.str.regex" ,"ctx" :{ "pattern" :"^u" }}]}
➜ ~ curl "http://127.0.0.1:8000/api/demo?demo_value=1"
{ "data" :[{ "loc" :[ "demo_value" ] ,"msg" :"ensure this value has at least 6 characters" ,"type" :"value_error.any_str.min_length" ,"ctx" :{ "limit_value" :6}}]}
2.7.raw_return
该参数的默认值为False
,如果为True
,则Pait
不会根据参数名或者alias
为key从请求数据获取值, 而是把整个请求值返回给对应的变量。
示例代码如下, 该接口为一个POST接口, 它需要两个值,第一个值为整个客户端传过来的Json参数, 而第二个值为客户端传过来的Json参数中Key为a的值:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_raw_return_demo.py from flask import Flask , Response , jsonify
from pydantic import ValidationError
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> Response :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return jsonify ({ "data" : exc . errors ()})
return jsonify ({ "data" : str ( exc )})
@pait ()
def demo (
demo_value : dict = field . Json . i ( raw_return = True ),
a : str = field . Json . i (),
) -> dict :
return { "data" : demo_value , "a" : a }
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "POST" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_raw_return_demo.py from pydantic import ValidationError
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> JSONResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return JSONResponse ({ "data" : exc . errors ()})
return JSONResponse ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value : dict = field . Json . i ( raw_return = True ),
a : str = field . Json . i (),
) -> JSONResponse :
return JSONResponse ({ "data" : demo_value , "a" : a })
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "POST" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_raw_return_demo.py from pydantic import ValidationError
from sanic import HTTPResponse , Request , Sanic , json
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return json ({ "data" : exc . errors ()})
return json ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value : dict = field . Json . i ( raw_return = True ),
a : str = field . Json . i (),
) -> HTTPResponse :
return json ({ "data" : demo_value , "a" : a })
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "POST" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn # type: ignore
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_raw_return_demo.py from pydantic import ValidationError
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
self . write ({ "data" : exc . errors ()})
else :
self . write ({ "data" : str ( exc )})
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def post (
self ,
demo_value : dict = field . Json . i ( raw_return = True ),
a : str = field . Json . i (),
) -> None :
self . write ({ "data" : demo_value , "a" : a })
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
运行代码并使用curl
调用, 可以发现结果符合预期:
curl "http://127.0.0.1:8000/api/demo" \
-X POST -d '{"a": "1", "b": "2"}' \
--header "Content-Type: application/json" {"demo_value":{"a":"1","b":"2"},"a":"1"}
2.8.自定义查询不到值的异常
在正常情况下,如果请求对象中没有Pait
需要的数据,那么Pait
会抛出NotFoundValueException
异常。
除此之外,Pait
还支持开发者通过not_value_exception_func
自定义异常处理,
如下代码中路由函数有两个变量,第一个变量demo_value1
没有设置任何Field
的属性,
而第二个变量demo_value2
设置了not_value_exception_func
属性为lambda param: RuntimeError(f"not found {param.name} data")
:
Flask Starlette Sanic Tornado
docs_source_code/docs_source_code/introduction/how_to_use_field/flask_with_not_found_exc_demo.py from flask import Flask , Response , jsonify
from pydantic import ValidationError
from pait import field
from pait.app.flask import pait
from pait.exceptions import TipException
def api_exception ( exc : Exception ) -> Response :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return jsonify ({ "data" : exc . errors ()})
return jsonify ({ "data" : str ( exc )})
@pait ()
def demo (
demo_value1 : str = field . Query . i (),
demo_value2 : str = field . Query . i (
not_value_exception_func = lambda param : RuntimeError ( f "not found { param . name } data" )
),
) -> dict :
return { "data" : { "demo_value1" : demo_value1 , "demo_value2" : demo_value2 }}
app = Flask ( "demo" )
app . add_url_rule ( "/api/demo" , view_func = demo , methods = [ "GET" ])
app . errorhandler ( Exception )( api_exception )
if __name__ == "__main__" :
app . run ( port = 8000 )
docs_source_code/docs_source_code/introduction/how_to_use_field/starlette_with_not_found_exc_demo.py from pydantic import ValidationError
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> JSONResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return JSONResponse ({ "data" : exc . errors ()})
return JSONResponse ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value1 : str = field . Query . i (),
demo_value2 : str = field . Query . i (
not_value_exception_func = lambda param : RuntimeError ( f "not found { param . name } data" )
),
) -> JSONResponse :
return JSONResponse ({ "data" : { "demo_value1" : demo_value1 , "demo_value2" : demo_value2 }})
app = Starlette (
routes = [
Route ( "/api/demo" , demo , methods = [ "GET" ]),
]
)
app . add_exception_handler ( Exception , api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/sanic_with_not_found_exc_demo.py from pydantic import ValidationError
from sanic import HTTPResponse , Request , Sanic , json
from pait import field
from pait.app.sanic import pait
from pait.exceptions import TipException
async def api_exception ( request : Request , exc : Exception ) -> HTTPResponse :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
return json ({ "data" : exc . errors ()})
return json ({ "data" : str ( exc )})
@pait ()
async def demo (
demo_value1 : str = field . Query . i (),
demo_value2 : str = field . Query . i (
not_value_exception_func = lambda param : RuntimeError ( f "not found { param . name } data" )
),
) -> HTTPResponse :
return json ({ "data" : { "demo_value1" : demo_value1 , "demo_value2" : demo_value2 }})
app = Sanic ( "demo" )
app . add_route ( demo , "/api/demo" , methods = { "GET" })
app . exception ( Exception )( api_exception )
if __name__ == "__main__" :
import uvicorn
uvicorn . run ( app )
docs_source_code/docs_source_code/introduction/how_to_use_field/tornado_with_not_found_exc_demo.py from pydantic import ValidationError
from tornado.ioloop import IOLoop
from tornado.web import Application , RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.exceptions import TipException
from pait.openapi.doc_route import AddDocRoute
class _Handler ( RequestHandler ):
def _handle_request_exception ( self , exc : BaseException ) -> None :
if isinstance ( exc , TipException ):
exc = exc . exc
if isinstance ( exc , ValidationError ):
self . write ({ "data" : exc . errors ()})
else :
self . write ({ "data" : str ( exc )})
self . finish ()
class DemoHandler ( _Handler ):
@pait ()
async def get (
self ,
demo_value1 : str = field . Query . i (),
demo_value2 : str = field . Query . i (
not_value_exception_func = lambda param : RuntimeError ( f "not found { param . name } data" )
),
) -> None :
self . write ({ "data" : { "demo_value1" : demo_value1 , "demo_value2" : demo_value2 }})
app : Application = Application ([( r "/api/demo" , DemoHandler )])
AddDocRoute ( app )
if __name__ == "__main__" :
app . listen ( 8000 )
IOLoop . instance () . start ()
接着运行代码,并在终端执行如下curl
命令:
curl "http://127.0.0.1:8000/api/demo?demo_value1=1&demo_value2=2" {"data": {"demo_value1": "1", "demo_value2": "2"}} curl "http://127.0.0.1:8000/api/demo?demo_value2=2" {"data": "Can not found demo_value1 value"} curl "http://127.0.0.1:8000/api/demo?demo_value1=1" {"data":"not found demo_value2 data"}
通过输出结果可以看到demo_value1
缺值和demo_value2
缺值的响应是不同的,其中demo_value2
的缺值异常消息由lambda param: RuntimeError(f"not found {param.name} data")
抛出的。
2.8.其它功能
除了上述功能外,Pait
还有其它功能,可以到对应模块文档了解:
属性
文档
描述
links
OpenAPI
用于支持OpenAPI的link功能
media_type
OpenAPI
Field对应的media_type,用于OpenAPI Scheme的media type。
openapi_serialization
OpenAPI
指定OpenAPI Schema的序列化方式。
example
OpenAPI
用于文档的示例值,Mock请求与响应等Mock功能,参数值支持变量和可调用函数如datetime.datetim.now
,推荐与faker 一起使用。
description
OpenAPI
用于OpenAPI的参数描述
openapi_include
OpenAPI
定义该字段不被OpenAPI读取
extra_param_list
Plugin
定义插件与参数之间的关系