Auto RESTful framework for Python3.
Project description
restfx
Python3 的 restful 多应用自动路由框架。
此框架解决的问题:
- 没有繁锁的路由配置
- 便捷的 restful 编码体验
- 自动解析/校验请求参数,并填充到路由处理函数
安装
- Gitee: https://gitee.com/hyjiacan/restfx
- Github: https://github.com/hyjiacan/restfx
- PyPI: https://pypi.org/project/restfx/
pip install restfx
安装后,可以通过 CLI 工具 (0.7.1
) restfx
命令创建基本项目结构:
restfx create projectname
使用此命令,可能需要将
restfx
安装到全局环境中。
使用
此组件提供的包(package)名称为 restfx
。
此框架遵循约定优于编码,我们作出以下约定:
- 路由处理函数名称均使用 小写
- 路由处理函数名称使用 下划线风格
- 当使用路径
GET /path/to/route
的时候, 如果to.py
和route.py
文件同时存在, 则会加载to.py
中的get_route
而不是route.py
名词说明
- 应用 使用
App()
初始化得到的实例 - 装饰器 类型
restfx.route
,这是一个装饰器@route
- 路由处理函数 由装饰器
@route
装饰的函数,用于处理请求 - 中间件 继承
restfx.middleware.MiddlewareBase
的类,用于对请求和响应进行自定义处理 - 扩展路由 一般的路由处理函数名称为请求的
method
,如:get/post
,扩展指具有扩展名称的路由:get_test/post_test
- 全局类型 当在装饰器
@route()
的参数中使用的自定义数据类型,需要通过app.register_globals()
进行注册
创建应用
import os
import restfx
if __name__ == '__main__':
root = os.path.dirname(__file__)
app = restfx.App(root, api_prefix='any/prefix', debug_mode=True)
app.map_routes({
'x': 'test'
}).map_static(static_map={}).startup(host='127.0.0.1', port=9127, **kwargs)
api_prefix
用于指定 api 接口 url 的根路径,即所有接口都是以此项指定的值开始(默认值为api
)。map_static
用于指定静态资源与目录映射关系。import os static_map = { '/static': os.path.join(root, 'path/to/static') }
此配置会允许通过url/static
访问静态目录{root}/path/to/static
。 指定的静态目录可以是绝对路径,也可以是相对root
的相对路径。
如:http://127.0.0.1:8000/any/prefix 。
多应用模式:每次调用
restfx.App(...)
都会启动一个新的应用服务器。 每个应用中的路由/中间件等都是独立的。
app.startup
有一个参数 kwargs
,
其可选的参数见 werkzeug.serving.run_simple
应用 app
暴露了以下接口:
app.startup(host: str, port: int, **kwargs)
启动调试服务器app.update_debug_mode(debug_mode: bool)->App
改变当前的 debug 状态app.set_intercepter(intercepter: FunctionType)->App
指定请求拦截器,其会在分发路由前调用app.set_logger(logger: FunctionType)->App
指定日志记录函数,不指定时仅仅会在控制台输出日志app.map_routes(routes_map: dict)->App
指定路由映射表,此表用于重写请求路径app.map_static(static_map: dict)->App
指定静态资源映射表,此表用于描述静态资源路径app.register_routes(routes: list)->App
注册路由列表,此函数应该在线上模式时被调用, 其参数为通过 persist 生成的文件中的routes
字段app.register(method: str, path: str, handler: FunctionType)->App
手动注册一个路由app.register_globals(*global_classes)->App
注册全局类型app.register_middleware(*middlewares)->App
注册中间件app.collect(*global_classes)->list
收集路由信息。通过register_globals
指定过的全局类型,此处不用重新指定。app.persist(filename: str = '', encoding='utf8', *global_classes)->str
获取持久化的路由串(生成的 python 代码), 用于写入持久化文件。通过register_globals
指定过的全局类型,此处不用重新指定。app.set_dev_options(**kwargs)->App
设置一些开发选项。见 开发选项
->App
表示返回了实例本身,也就是说这些接口可以通过链式调用
编写路由
模块 test.a
-> 文件 test/a.py
from restfx import route
from enums import RouteTypes
@route('module_name', 'route_name', route_type = RouteTypes.TEST)
def get(req):
pass
装饰器 @route 用于标记路由处理函数。RouteTypes
是自定义的路由数据(可选项)。
restfx
包含以下几个部分:
-
路由映射
为了避免在客户端暴露代码路径,从设计上使用了映射的方式来处理请求。
-
在处理请求/响应过程中,可以对
request
/response
以及其参数进行处理。 -
在路由装饰器的参数中包含的全局类型,如
RouteTypes
-
为了提高线上性能的工具。
restfx
的使用流程如下:
注册路由映射
为了避免在客户端暴露代码路径(同时避免意外访问未授权的代码),从设计上使用了映射的方式来处理请求。
注册方式如下:
app.map_routes({
'path/prefix': 'path.to',
})
path/prefix
为请求的路径path.to
为请求路径时,框架会将prefix
指定的路径定向到对应的 python 包/模块。
所有的路由目录(顶层,不包含已经映射过目录的子目录)均需要被映射,未在映射表中的路径请求,不会被处理。
restfx
会自动查找 path.to
包/模块下的所有路由。
路径应为基于项目根目录的相对路径。
编写路由处理函数
路由文件位置没有要求,只要配置好就可以了。
app.map_routes({
'test': 'test.api'
})
此配置表示,所有请求中以 test
开头的地址,都会交由 test.api
下的模块进行处理。
使用装饰器 route 标记路由处理函数。
test/api/demo.py
from restfx import route
from restfx.http import HttpRequest
@route(module='测试名称-模块', name='测试名称-GET')
def get(request, param1, param2=None, param3: int = 5):
# request 会是 HttpRequest
return {
'param1': param1,
'param2': param2,
'param3': param3,
}
@route(module='测试名称-模块', name='测试名称-POST_PARAM')
def get_param(param1, req: HttpRequest, from_=None, param3=5):
# req 会是 HttpRequest
return {
'param1': param1,
'from': from_,
'param3': param3,
}
@route(module='测试名称-模块', name='测试名称-PUT_PARAM')
def put(request: str, param1, from_=None, param3=5):
# request 会是请求参数,参数列表中没有 HttpRequest
return {
'request': request,
'param1': param1,
'from': from_,
'param3': param3,
}
@route(module='测试名称-模块', name='测试名称-DELETE_PARAM')
def delete(request, param1, from_=None, param3=5, **kwargs):
# 未在函数的参数列表中声明的请求参数,会出现在 kwargs 中
return {
'param1': param1,
'from': from_,
'param3': param3,
'variable_args': kwargs
}
一些需要注意的地方:
- 当代码中需要使用关键字作为名称时,请在名称后添加
_
,此时前端请求时,_
符号可省略, 如:from_
在请求时可写作from=test
(from_=test
亦可)。 - 对于语言间的命令差异,可以自动兼容
下划线命名法
与驼峰命名法
,如:请求参数中的pageIndex
, 在处理函数中可以写作page_index
或_page_index_
, 也就是说,在前后添加_
符号都不会影响参数的解析。 - 路由处理函数可以添加一个可变参数(如:
**kwargs**
),用于接收未在参数列表中列出的请求项。 当然,kwargs
和普通函数一样,可以是任何其它名称。 request
参数(与参数位置无关),可能被解析成三种结果(1
和2
均会将其作为HttpRequest
参数处理):- 参数名称为
request
,并且未指定参数类型(或指定类型为HttpRequest
) - 参数类型为
HttpRequest
,参数名称可以是任何合法的标识符 - 参数名称为
request
,声明了不是HttpRequest
的类型,此时会被解析成一般的请求参数
- 参数名称为
URL格式
http://127.0.0.1:9127/api/test/demo/param?param1=1¶m2=2¶m3=3
127.0.0.1
startup
的host
参数9127
startup
的port
参数api
App
初始化时的api_prefix
参数test/demo
app.map_routes
参数中定义的'test': 'test.api'
,最终会访问到包test.api.demo
/param
get_param()
中的_param
名称匹配符扩展路由名称param1/2/3
分别会填充到处理函数的对应参数中
前端调用
// 请求 get 函数
ajax.get('/test/demo?param1=1¶m2=2¶m3=3')
// 请求 get_param 函数
ajax.get('/test/demo/param?param1=1&from_=2¶m3=3')
// 请求 post 函数
ajax.post('/test/demo', {
param1: 1,
from_: 2,
param3: 3
})
// 请求 delete 函数
ajax.delete('/test/demo?param1=1&from_=2¶m3=3¶m4=4')
路由可以返回任何类型的数据。路由会自动根据函数定义来判断传入参数的类型是否合法。
比如前面示例中的 param3: int =5
,会根据声明类型 int
去判断传入类型
- 如果传入了字符串类型的数值,路由会自动转换成数值类型
- 另外,如果设置了
None
以外的默认值,那么路由会根据默认值的类型自动去判断, 此时可以省略参数类型,如:param3: int =5
省略为param3=5
param4
会出现在delete
请求的kwargs
中
装饰器
装饰器 route
用于声明某个函数是一个路由处理函数。通过添加此装饰器以限制非路由函数被非法访问。
声明为:
def route(module=None, name=None, **kwargs):
pass
module
此路由所属的业务/功能模块名称name
此路由的名称**kwargs
用户自定义参数
这些参数都会被传递给中间件的各个函数的参数
meta
。详细见 RouteMeta
同时,此装饰器会自动尝试将 request.data
,request.args
和 request.form
处理成 JSON 格式(仅在 content-type=application/json
时),
并且分别添加到 request.BODY
,request.GET
和 request.POST
属性上。
注意:一般情况下,使用路由处理函数就能完全操作请求参数,应该尽量减少使用 BODY/POST/GET
,以避免代码的不明确性。
注册中间件
中间件用于在处理请求/响应过程中,对 request
/response
以及其参数进行处理。
注册方式如下:
from path.to import FooMiddleware
from path.to import BarMiddleware
app.register_middlewares(
FooMiddleware(),
BarMiddleware(),
)
当注册了多个中间件时,它们会按被注册的顺序执行。
注意: 注册的是中间件实例,每一个中间件类型在程序运行期间共享一个实例。
如何开发中间件?参见 中间件类结构
restfx
内置了一些中间件,参见 内置中间件
Session
框架以中间件的方式提供了简单的 session 支持。
若不指定,不启用 session 支持。
from restfx import App
from restfx.session.providers import MemorySessionProvider
app = App(...)
app.register_middleware(MemorySessionProvider(20))
内置了以下几种数据源:
MemorySessionProvider
基于内存的 session 实现,在使用多进程模型时,请勿使用此类型FileSessionProvider
基于文件的 session 实现MysqlSessionProvider
基于 mysql 数据库的 session 实现
也可以自定义数据源的实现:
from restfx.session.interfaces import ISessionProvider, IDbSessionProvider
class CustomSessionProvider(ISessionProvider):
pass
class CustomDbSessionProvider(IDbSessionProvider):
pass
接口 ISessionProvider
和 IDbSessionProvider
实现其中一个即可。
对于自定义的数据库源,建议使用 IDbSessionProvider
。
IDbSessionProvider
中使用了 DBUtils
包中的 PooledDB。
在初始化时,应该传入一个 PooledDB
实例。
在使用时,应该通过 self.connect(shared=True)
获取新的连接。
在应用内,可以通过 request.session
来访问 session 对象。
from restfx import route
from restfx.http import HttpRequest
@route('test', 'test')
def get(request: HttpRequest):
session = request.session
发布
发布 指将 restfx
项目发布到服务器上运行(线上环境)。
一般来说,发布时只需要调用 生成路由映射文件 的接口就可以了, 路由收集在其中会自动调用。
路由收集
此功能一般仅用于辅助功能,在开发中若不会显式用到路由列表,不需要调用此函数
路由收集器用于收集项目中的所有路由,通过以下方式调用:
routes = app.collect()
routes
是一个可以直接迭代的路由数组
其每一个路由项的结构如下:
- module
- name
- kwargs
- id
- pkg # 路由所在包名称
- file # 路由所在文件的完整路径
- handler # 路由请求的处理函数
- method # 路由的请求方法
- path # 路由的请求路径
- handler_info # 路由处理函数描述,这些信息从参数和注释收集而来
- is_package # 路由处理函数是否存在于包
__init__.py
文件中 - ext_mode # 是否为扩展路由。扩展路由处理函数的命名方式为
get_xxx/post_xxx
生成路由映射文件
restfx
导出了一个工具函数 persist
,用于将路由收集起来,并持久化,其用法如下:
import os
restfx_map = os.path.join(os.path.dirname(__file__), 'path/to/restfx_map.py')
# restfx_map 参数是可选的,当不传时,调用会返回生成的代码内容
# encoding 参数是可选的,默认值为 utf-8。
app.persist(restfx_map, encoding='utf-8')
此处还需要调用路由的映射注册,以及全局类型注册等。 因此,最佳方法就是,将这些注册写一个单独的 python 文件,在启动和发布时均调用即可。
最终生成的路由代码会写入文件 restfx_map.py,此文件会暴露一个数据项 routes
,其中是所有的路由映射。
一般来说,在线上环境中,应该在系统启动时调用此函数:
from path.to import restfx_map
app.register_routes(restfx_map.routes)
综上,发布以及线上运行流程为:
- 发布时调用
restfx.persist
生成路由映射文件 - 程序启动时,判断
app.DEBUG=False
,执行from path.to import restfx_map
并调用restfx.register_routes(restfx_map.routes)
注册路由。
高级用法
wsgi
App
实例本身即是 wsgi
入口。
main.py
from restfx import App
# app 就是 wsgi 入口
app = App(...)
在部署到 wsgi 容器时,将 main:app
暴露给容器作为入口。
分发前的处理
有的时候,需要在分发前对请求参数进行处理。此时可以使用 restfx.set_intercepter
来进行一些预处理。
def dispatch_intercepter(request, entry):
# 可以在此处修改 request 的数据
# 也可以重新定义 entry
return entry
app.set_intercepter(dispatch_intercepter)
注册全局类型
当在路由装饰器参数中使用了自定义的值类型时(比如枚举或类),应该当将其注册到 restfx
,否则无法正确收集到路由。
例:
test.py
from restfx import route
from enums import RouteTypes
@route('module_name', 'route_name', route_type = RouteTypes.TEST)
def test(req):
pass
enums.py
from enum import Enum
class RouteTypes(Enum):
TEST = 1
注册自定义日志记录器
def my_logger(level: str, message: str, e: Exception):
pass
app.set_logger(my_logger)
其中,level
表示日志级别,会有以下值:
- debug
- info
- warning
- error
中间件类结构
path.to.MiddlewareClass
from restfx.routes import RouteMeta
from restfx.middleware import MiddlewareBase
class MiddlewareClass(MiddlewareBase):
"""
路由中间件
"""
def process_request(self, request, meta, **kwargs):
"""
对 request 对象进行预处理。一般用于请求的数据的解码,此时路由组件尚水进行请求数据的解析(B,P,G 尚不可用)
:param request:
:type request: HttpRequest
:param meta:
:type meta: RouteMeta
:return: 返回 HttpResponse 以终止请求,返回非 None 以停止执行后续的中间件,返回 None 或不返回任何值继续执行后续中间件
"""
pass
def process_invoke(self, request, meta, **kwargs):
"""
在路由函数调用前,对其参数等进行处理,此时路由组件已经完成了请求数据的解析(B,P,G 已可用)
此时可以对解析后的参数进行变更
:param request:
:type request: HttpRequest
:param meta:
:type meta: RouteMeta
:return: 返回 HttpResponse 以终止请求,返回非 None 以停止执行后续的中间件,返回 None 或不返回任何值继续执行后续中间件
"""
pass
def process_return(self, request, meta, data, **kwargs):
"""
在路由函数调用后,对其返回值进行处理
:param request:
:type request: HttpRequest
:param meta:
:type meta: RouteMeta
:param data: 表示路由返回的原始数据
:param kwargs: 保留参数
:return: 返回 HttpResponse 以终止执行,否则返回新的数据
"""
pass
def process_response(self, request, meta, response, **kwargs):
"""
对 response 数据进行预处理。一般用于响应的数据的编码
:param request:
:type request: HttpRequest
:param meta:
:type meta: RouteMeta
:param response: 表示路由返回的原始 HttpResponse
:type response: HttpResponse
:param kwargs: 始终会有一个 'response' 的项,
:return: 返回类型可以是 HttpResponse 或 None(保留原来的 response)
:rtype: Union[HttpResponse, None]
"""
pass
其调用顺序为:
process_request
process_invoke
process_return
process_response
其中,process_request
和 process_invoke
按中间件注册的顺序 顺序 执行;
process_return
和 process_response
按中间件注册的顺序 倒序 执行。
在 process_request
和 process_invoke
中返回了 HttpResponse
后,
会中止后续所有中间件执行 process_request
, process_invoke
, process_return
,
并开始执行 process_response
在 process_request
和 process_invoke
中返回了非 None/HttpResponse
后,
会中止后续所有中间件执行 process_request
, process_invoke
,
并开始执行 process_return
, process_response
在 process_return
中返回了 HttpResponse
后,会中止持续所有中间件执行 process_return
,
并开始执行 process_response
; 返回 None
表示不改变返回值(的引用); 返回其它值会作为新的返回值
在 process_response
中返回了 HttpResponse
后,会中止持续所有中间件执行 process_response
,
并结束请求。
在处理函数中,返回
None
不会改变执行顺序。
例如,注册了三个中间件, MiddlewareA, MiddlewareB, MiddlewareC
,以在 MiddlewareB
中为例,
以下是几种会改变中间件执行顺序的情形说明:
MiddlewareB.process_request
返回 非None/HttpResponse
数据- MiddlewareA.process_request
- MiddlewareB.process_request
MiddlewareC.process_requestMiddlewareA.process_invokeMiddlewareB.process_invokeMiddlewareC.process_invoke- MiddlewareC.process_return
- MiddlewareB.process_return
- MiddlewareA.process_return
- MiddlewareC.process_response
- MiddlewareB.process_response
- MiddlewareA.process_response
MiddlewareB.process_request
返回HttpResponse
- MiddlewareA.process_request
- MiddlewareB.process_request
MiddlewareC.process_requestMiddlewareA.process_invokeMiddlewareB.process_invokeMiddlewareC.process_invokeMiddlewareC.process_returnMiddlewareB.process_returnMiddlewareA.process_return- MiddlewareC.process_response
- MiddlewareB.process_response
- MiddlewareA.process_response
MiddlewareB.process_invoke
返回 非None/HttpResponse
数据- MiddlewareA.process_request
- MiddlewareB.process_request
- MiddlewareC.process_request
- MiddlewareA.process_invoke
- MiddlewareB.process_invoke
MiddlewareC.process_invoke- MiddlewareC.process_return
- MiddlewareB.process_return
- MiddlewareA.process_return
- MiddlewareC.process_response
- MiddlewareB.process_response
- MiddlewareA.process_response
MiddlewareB.process_invoke
返回HttpResponse
- MiddlewareA.process_request
- MiddlewareB.process_request
- MiddlewareC.process_request
- MiddlewareA.process_invoke
- MiddlewareB.process_invoke
MiddlewareC.process_invokeMiddlewareC.process_returnMiddlewareB.process_returnMiddlewareA.process_return- MiddlewareC.process_response
- MiddlewareB.process_response
- MiddlewareA.process_response
MiddlewareB.process_return
返回HttpResponse
- MiddlewareA.process_request
- MiddlewareB.process_request
- MiddlewareC.process_request
- MiddlewareA.process_invoke
- MiddlewareB.process_invoke
- MiddlewareC.process_invoke
- MiddlewareC.process_return
- MiddlewareB.process_return
MiddlewareA.process_return- MiddlewareC.process_response
- MiddlewareB.process_response
- MiddlewareA.process_response
MiddlewareB.process_response
返回HttpResponse
- MiddlewareA.process_request
- MiddlewareB.process_request
- MiddlewareC.process_request
- MiddlewareA.process_invoke
- MiddlewareB.process_invoke
- MiddlewareC.process_invoke
- MiddlewareC.process_return
- MiddlewareB.process_return
- MiddlewareA.process_return
- MiddlewareC.process_response
- MiddlewareB.process_response
MiddlewareA.process_response
RouteMeta
路由元数据,中间件中勾子函数的参数 meta
结构。
id: str
路由ID,此ID由路由相关信息组合而成module: str
装饰器上指定的 module 值name: str
装饰器上指定的 name 值method: str
路由的请求方法 大写格式path: str
路由的请求路径handler: FunctionType
路由处理函数对象func_args: OrderedDict
路由处理函数参数列表kwargs: dict
装饰器上指定的其它参数
另外,meta 还提供了 has
和 get
两个方法,其描述如下:
has(arg_name)
判断是否指定了额外参数get(arg_name, default_value=None)
若存在指定名称的额外参数,则返回值,否则返回指定的默认值
额外参数: 除
name
和module
外的参数
内置中间件
SessionMiddleware
此中间件用于提供 session 支持。
from restfx import App
from restfx.middleware.middlewares import SessionMiddleware
from restfx.session.providers import MemorySessionProvider
app = App()
app.register_middleware(
SessionMiddleware(MemorySessionProvider(20),
session_name='sessionid',
cookie_max_age=None,
cookie_expires=None,
cookie_path="/",
cookie_domain=None,
cookie_secure=False,
cookie_samesite=None,
cookie_httponly=True)
)
HttpAuthMiddleware
此中间件提供 HTTP 身份校验 Http Authentication
支持,其基于 WWW-Authenticate
实现。
from restfx import App
from restfx.middleware.middlewares import HttpAuthMiddleware
from restfx.routes import RouteMeta
from restfx.http import HttpRequest
def on_http_auth(request: HttpRequest, meta: RouteMeta):
if not meta.get('auth', True):
"""
当 @route 中指定了 auth=False 时,表示此路由不需要身份验证
auth 为开发者自己定义的名称,并未内置。
"""
return True
authorization = request.authorization
if authorization is not None and authorization.username == 'admin' and authorization.password == '123456':
return True
else:
return False
app = App()
app.register_middleware(
HttpAuthMiddleware(on_auth = on_http_auth)
)
一般来说,此中间件应该被第一个注册,以防止越权操作。
开发选项
通过 app.set_dev_options(**kwargs)->App
设置一些开发选项。
支持的配置项:
app_name: str
在 API 列表上展示的应用名称。api_list_addition: FunctionType
指定API列表的附加项。一般用于展示一些自定义的路由字段。api_list_expanded: bool
在API列表加载后,是否默认展开所有项。
api_list_addition
用于自定义API列表中的附加信息渲染。其声明如下:
def api_list_addition(route_info):
pass
其返回值将作为附加信息(支持使用 html)。
此功能一般在 @route
上有自定义的参数时,给用户提示使用,如:
from restfx import App, route
def api_list_addition(route_info):
if 'auth' not in route_info['kwargs'] or route_info['kwargs'] is False:
return '<span style="color: #ff7d7d">[需要身份校验]</span>'
return '<span style="color: #66c0de">[不需要身份校验]</span>'
App(...).set_dev_options(api_list_addition=api_list_addition)
@route('module', 'name', auth=False)
def get():
pass
以上代码演示了在装饰器 @route
上添加了自定义参数 auth
,用于表示接口是否需要校验。
截图
路由声明
API列表
待办事项
- 添加严格模式支持。在严格模式下,不允许传入未声明的参数。
- 参数类型支持上传文件
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.