Skip to main content

restful(and auto route) support for Django2/3

Project description

restful-dj

基于 Django2/3 的 restful 自动路由框架。

此包解决的问题:

  • 告别 Django 繁锁的路由配置
  • 更便捷的 restful 编码体验
  • 自动解析/校验请求参数,填充到路由处理函数

安装

pip install restful-dj

使用

此组件提供的包(package)名称为 restful_dj,所有用到的模块都在此包下引入。

示例:

from restful_dj import route
from enums import RouteTypes

@route('module_name', 'route_name', route_type = RouteTypes.TEST)
def test(req):
    pass

装饰器 @route 用于标记路由处理函数。RouteTypes 是自定义的路由数据。

restful-dj 包含以下几个部分:

  • 路由映射

    为了避免在客户端暴露代码路径,从设计上使用了映射的方式来处理请求。

  • 中间件

    在处理请求/响应过程中,可以对 request/response 以及其参数进行处理。

  • 全局类型

    在路由装饰器的参数中包含的全局类型,如 RouteTypes

  • 路由收集与持久化

    为了提高线上性能的工具。

restful-dj 的使用流程如下:

  1. 注册请求路径前缀
  2. 注册路由映射
  3. 注册中间件
  4. 编写路由处理函数
  5. 发布

注册请求路径前缀

在项目的根 urls.py 文件中,使用以下配置

from django.urls import path
import restful_dj

urlpatterns = [
    path('any/prefix', restful_dj.dispatch)
]

其中,any/prefix 是用户自定义的url前缀,可以使用空串 '',表示没有特定前缀。 restful_dj.dispatch 中路由的分发入口。

注:可以通过部署地址 any/prefix 访问接口列表 (仅在开发模式时可用), 如:http://localhost:8000/any/prefix

注册路由映射

为了避免在客户端暴露代码路径(同时避免意外访问未授权的代码),从设计上使用了映射的方式来处理请求。

注册方式如下:

import restful_dj

restful_dj.map_routes({
    'path.prefix': 'path.to',
})
  • path.prefix 为请求的路径
  • path.to 为请求路径时,应将其定向到的 python 包/模块。

所有的路由目录(顶层,不包含已经映射过目录的子目录)均需要被映射,未在映射表中的路径请求,不会被处理。 restful-dj 会自动查找 包/模块 path.to 下的所有路由。

路径应为基于项目根目录的相对路径。

注册中间件

中间件用于在处理请求/响应过程中,对 request/response 以及其参数进行处理。

注册方式如下:

import restful_dj
from path.to import FooMiddleware
from path.to import BarMiddleware

restful_dj.register_middlewares(
    FooMiddleware,
    BarMiddleware,
)

当注册了多个中间件时,它们会按被注册的顺序执行。

需要注意: 每一个中间件类型在程序运行期间共享一个实例。

如何开发中间件?参见 中间件类结构

编写路由处理函数

路由文件位置没有要求,只要配置好就可以了。

import restful_dj

restful_dj.map_routes({
    'test': 'test.api'
})

此配置表示,所有请求中以 test 开头的地址,都会交由 test.api 下的模块进行处理。

使用装饰器 route 标记路由处理函数。

test/api/demo.py

from django.http import HttpRequest
from restful_dj.decorator import route

@route(module='module-name', name='name')
def get(request, param1, param2=None, param3: int =5):
    # request 会是 HttpRequest
    return {
        'param1': param1, 
        'param2': param2, 
        'param3': param3, 
    }

@route(module='module-name', name='name')
def get_param(param1, req: HttpRequest, from_=None, param3 =5):
    # req 会是 HttpRequest
    return {
        'param1': param1, 
        'from': from_, 
        'param3': param3,
    } 

@route(module='module-name', name='name')
def get_param(request: str, param1, from_=None, param3 =5):
    # request 会是请求参数,参数列表中没有 HttpRequest
    return {
        'request': request,
        'param1': param1, 
        'from': from_, 
        'param3': param3,
    } 

@route(module='module-name', name='name')
def get_param(request, param1, from_=None, param3 =5, **kwargs):
    # 未在函数的参数列表中声明的请求参数,会出现在 kwargs 中
    return {
        'param1': param1,
        'from': from_,
        'param3': param3,
        'variable_args': kwargs
    }

一些需要注意的地方:

  • 当代码中需要使用关键字作为名称时,请在名称后添加 _,此时前端请求时,_ 符号可省略, 如: from_ 在请求时可写作 from=testfrom_=test 亦可)。
  • 对于语言间的命令差异,可以自动兼容 下划线命名法驼峰命名法,如:请求参数中的 pageIndex,在处理函数中可以写作 page_index_page_index_ , 也就是说,在前后添加 _ 符号都不会影响参数的解析。
  • 路由处理函数可以添加一个可变参数(如:**kwargs**),用于接收未在参数列表中列出的请求项。 当然,kwargs和普通函数一样,可以是任何其它名称。
  • request 参数(与参数位置无关),可能被解析成三种结果(12均会将其作为 HttpRequest 参数处理):
    1. 参数名称为 request,并且未指定参数类型(或指定类型为 HttpRequest)
    2. 参数类型为 HttpRequest,参数名称可以是任何合法的标识符
    3. 参数名称为 request,声明了不是 HttpRequest 的类型,此时会被解析成一般的请求参数

前端调用:

// 请求 get 函数
ajax.get('test.demo?param1=1&param2=2&param3=3')

// 请求 get_param 函数
ajax.get('test.demo/param?param1=1&param2=2&param3=3')

路由可以返回任何类型的数据。路由会自动根据函数定义来判断传入参数的类型是否合法。

比如前面示例中的 param3: int =5,会根据声明类型 int 去判断传入类型

  • 如果传入了字符串类型的数值,路由会自动转换成数值类型
  • 另外,如果设置了 None 以外的默认值,那么路由会根据默认值的类型自动去判断, 此时可以省略参数类型,如: param3: int =5 省略为 param3=5

装饰器

装饰器 route 用于声明某个函数可以被路由使用。通过添加此装饰器以限制非路由函数被非法访问。

声明为:

def route(module=None, name=None, **kwargs):
    pass
  • module 此路由所属的业务/功能模块名称
  • name 此路由的名称
  • **kwargs 额外参数

这些参数都会被传递给中间件的各个函数的参数 meta。详细见 RouteMeta

同时,此装饰器会自动尝试将 request.bodyrequest.GETrequest.POST 处理成 JSON 格式(仅在 content-type=application/json 时), 并且分别添加到 request.Brequest.Grequest.P 属性上。

注意:一般情况下,使用路由处理函数就能完全操作请求参数,应该尽量减少使用 B/P/G,以避免代码的不明确性。

发布

发布 指将 Django 项目发布到服务器上运行(线上环境)。

一般来说,发布时只需要调用 生成路由映射文件 的接口就可以了, 路由收集在其中会自动调用。

路由收集

路由收集器用于收集项目中的所有路由,通过以下方式调用:

import restful_dj
routes = restful_dj.collect()

routes 是一个可以直接迭代的路由数组

其每一个路由项的结构如下:

  • module
  • name
  • kwargs
  • id
  • pkg # 路由所在包名称
  • file # 路由所在文件的完整路径
  • handler # 路由请求的处理函数
  • method # 路由的请求方法
  • path # 路由的请求路径

生成路由映射文件

since 1.0.3

restful-dj 导出了一个工具函数 persist,用于将路由收集起来,并持久化,其用法如下:

import os
from django.conf import settings
import restful_dj

restful_map = os.path.join(settings.BASE_DIR, 'path/to/restful_map.py')
# restful_map 参数是可选的,当不传时,调用会返回生成的代码内容
# encoding 参数是可选的,默认值为 utf-8。
restful_dj.persist(restful_map, encoding='utf-8')

此处还需要调用路由的映射注册,以及全局类型注册等。 因此,最佳方法就是,将这些注册写一个单独的 python 文件,在启动和发布时均调用即可。

最终生成的路由代码会写入文件 restful_map.py,此文件会暴露一个数据项 routes,其中是所有的路由映射。 一般来说,应该在系统启动时 (在主应用的 urls.py 文件中) 调用此函数:

import restful_dj
from path.to import restful_map
restful_dj.register_routes(restful_map.routes)

综上,发布以及线上运行流程为

  1. 发布时调用 restful_dj.persist 生成路由映射文件
  2. 程序启动时,判断 settings.DEBUG=False,执行 from path.to import restful_map 并调用 restful_dj.register_routes(restful_map.routes) 注册路由。

高级用法

分发前的处理

有的时候,需要在分发前对请求参数进行处理。此时可以使用 restful.set_before_dispatch_handler 来进行一些预处理。

函数签名:

def set_before_dispatch_handler(handler):
    pass

用法:

import restful_dj

def before_dispatch_handler(request, entry, name):
    # 可以在此处修改 request 的数据
    # 也可以重新定义 entry 和 name
    return entry, name

restful_dj.set_before_dispatch_handler(before_dispatch_handler)

注册全局类型

当在路由装饰器参数中使用了自定义的值类型时(比如枚举或类),应该当将其注册到 restful-dj,否则无法正确收集到路由。

例:

test.py

from restful_dj 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

注册自定义日志记录器

from restful_dj import set_logger

def my_logger(level: str, message: str, e: Exception):
    pass

set_logger(my_logger)

其中,level表示日志级别,会有以下值:

  • debug
  • info
  • success
  • warning
  • error

中间件类结构

path.to.MiddlewareClass

from django.http import HttpRequest
from restful_dj import RouteMeta 

class MiddlewareClass:
    """
    路由中间件
    """

    def process_request(self, request: HttpRequest, meta: RouteMeta, **kwargs):
        """
        对 request 对象进行预处理。一般用于请求的数据的解码,此时路由组件尚水进行请求数据的解析(B,P,G 尚不可用)
        :param request:
        :param meta:
        :return: 返回 HttpResponse 以终止请求,返回 False 以停止执行后续的中间件(表示访问未授权),返回 None 或不返回任何值继续执行后续中间件
        """
        pass

    def process_invoke(self, request: HttpRequest, meta: RouteMeta, **kwargs):
        """
        在路由函数调用前,对其参数等进行处理,此时路由组件已经完成了请求数据的解析(B,P,G 已可用)
        此时可以对解析后的参数进行变更
        :param request:
        :param meta:
        :return: 返回 HttpResponse 以终止请求,返回 False 以停止执行后续的中间件(表示访问未授权),返回 None 或不返回任何值继续执行后续中间件
        """
        pass

    def process_return(self, request: HttpRequest, meta: RouteMeta, **kwargs):
        """
        在路由函数调用后,对其返回值进行处理
        :param request:
        :param meta:
        :param kwargs: 始终会有一个 'data' 的项,表示返回的原始数据
        :return: 返回 HttpResponse 以终止执行,否则返回新的 return value
        """
        assert 'data' in kwargs
        return kwargs['data']

    def process_response(self, request: HttpRequest, meta: RouteMeta, **kwargs):
        """
        对 response 数据进行预处理。一般用于响应的数据的编码
        :rtype: HttpResponse
        :param meta:
        :param request:
        :param kwargs: 始终会有一个 'response' 的项,表示返回的 HttpResponse
        :return: 无论何种情况,应该始终返回一个  HttpResponse
        """
        assert 'response' in kwargs
        return kwargs['response']

RouteMeta

路由元数据,中间件中勾子函数的参数 meta 结构。

from types import FunctionType
from typing import OrderedDict

class RouteMeta:  
    @property
    def handler(self) -> FunctionType:
        """
        路由处理函数对象
        :return:
        """
        return self._handler

    @property
    def func_args(self) -> OrderedDict:
        """
        路由处理函数参数列表
        :return:
        """
        return self._func_args

    @property
    def id(self) -> str:
        """
        路由ID,此ID由路由相关信息组合而成
        :return:
        """
        return self._id

    @property
    def module(self) -> str:
        """
        装饰器上指定的 module 值
        :return:
        """
        return self._module

    @property
    def name(self) -> str:
        """
        装饰器上指定的 name 值
        :return:
        """
        return self._name

    @property
    def kwargs(self) -> dict:
        """
        装饰器上指定的其它参数
        :return:
        :rtype: Dict
        """
        return self._kwargs

另外,meta 还提供了 hasget 两个方法,其描述如下:

  • has(arg_name) 判断是否指定了额外参数
  • get(arg_name, default_value=None) 若存在指定名称的额外参数,则返回值,否则返回指定的默认值

额外参数: 除 namemodule 外的参数

待办事项

  • 添加严格模式支持。在严格模式下,不允许传入未声明的参数。
  • 参数类型支持上传文件

常见问题

403 Forbidden. CSRF verification failed. Request aborted.

移除或注释掉 settings.py 文件中的中间件 'django.middleware.csrf.CsrfViewMiddleware'

restful 请求时,前端页面并没有 Django 生成的 form 以及对应的 csrf 校验字段,此配置实际上对 restful 是没有意义的。

更新记录

2.0.0

  • 新的 restful-dj 启动方式
  • 修复 api 列表页面可能导致的异常问题
  • 移除 DotDict 支持
  • 移除 @route 的保留参数

1.0.3

  • 优化 线上路由加载方式,提升启动和响应速度
  • 优化 路由处理函数被用户直接调用的支持
  • 优化 将兼容性调整为 Python 3.5

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

restful-dj-2.0.1.tar.gz (28.3 kB view hashes)

Uploaded Source

Built Distribution

restful_dj-2.0.1-py3-none-any.whl (27.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page