快快快——200行代码带你入门Python后台开发(基于fastApi+peewee)

2,943 阅读8分钟

背景说明

随着轻应用的蓬勃发展,开发人员都要学习了解一点后台开发技术,但是如果想要一个前台或者运维小伙伴,直接上手基于Java的那套Web框架,效果肯定是不理想的,原因很明显啊,直接上手既不利于项目的快速上线,更容易在使用学习时因为入门的难过高,导致自信心受到不小的打击,那么问题就来了,画重点,就没有一个快速上手的web框架可以供选择么?,当然有了,而且可选择性还不算少,不过我们今天只介绍这一款组合,就是fastApi和peegee组合

简介

看标题我们大家也知道这两款是属于哪家的了,对了,就是Python,为什么选择Python呢,基本原因就是Python上手容易,而且该语言的使用范围非常广泛,哪怕不是用来做web开发,我个人建议也可以学习下Python,当然了,Python的学习不是我们这里的重点。

前置条件

我们这个虽然是200行,但是却没有说零基础,因为如果零基础的话,的确不是很好进行一些实战开发,所以这篇文章设置了一下前置条件

  • Python,这个不用说了,最好要有一些Python的基础知识,对于常见的字典啊,数组啊,类啊有一些了解,否则不要说写代码了,就是看代码也比较费劲
  • SQL,目前的Web开发必然是包括SQL的,当然了,既然我们这是200行代码,那肯定实现的业务功能非常有限,用到的数据库的相关功能也非常有限,不过还是最好建议有一定的SQL基础,别的不说,最基础的增删改查还是要了解的吧,因为我们这200行里就是做的这几件事
  • ORM,对象关系映射这个是老生常谈了,个人倾向于用ORM来和数据库打交道,也就是为什么选择peegee的一个原因,最好对这个ORM有个概要的了解
  • 其他,比如说redis啊,也了解下,我们为了简化用户登录逻辑,使用redis缓存了用户信息

有了以上这些,我们现在可以入手fastApt和Peewee了

fastApi

fastApi
官网对该框架的介绍也是比较精简,FastAPI framework, high performance, easy to learn, fast to code, ready for production,翻译过来就是性能高,好上手,编码快,准生产。一听这四个介绍,那还想什么呢,肯定是入坑啊(这里至于为什么性能高,为啥准生产这些我们后续可以单独开一篇再详细的讲解下)。既然官方的介绍这么诱惑,我们再犹豫,那就太对不起官方的描述了,两个字,入坑

Peewee

peewee
以前项目中也用过SQLAlchemy,但是用起来感觉就是可用,但是不好用,不好用也说不出来哪里不好用,直到看到peewee,这个框架感觉就一个特点,简单,用来不用太多的配置,Model直接继承,然后数据操作就是ORM方式

码起来

首先参考fastApi和Peewee的要求,讲需要的包本地安装,此处建议大家使用requirement的形式,具体内容如下

peewee==3.13.1 
uvicorn==0.11.3
fastapi==0.52.0
PyMySQL>=0.9.3
redis>=3.4.1
PyJWT>=1.7.1

其中uvicorn为运行server,PyMySQL为mysql驱动(此处使用mysql存储数据),PyJWT为鉴权的token的工具包。导入后参考如下进行目录创建

功能分析

参考实际开发功能,我们此处稍作简化处理,目标实现一个记事本功能,用户登录后,然后可以进行记事本内容的增加、删除以及内容的查询。经过以上分析,我们实现四个接口,分别是登录接口(POST)、增加接口(POST)、删除接口(POST)、查询接口(GET),参考fastApi官网实例

@app.post('/')
def hello():
    return {"info":"hello World"}

接口说明

那么我们的接口参考如下,基本代码可以是


@app.post('/lee/user/login')
def login(user: LoginUser):
    '''登录'''
    pass
    
@app.post('/lee/item/save', dependencies=[Depends(verify_token)])
def save_item(param: dict):
    '''内容保存'''
    pass

@app.post('/lee/item/all', dependencies=[Depends(verify_token)])
def items(param: dict):
    '''查询'''
    pass
@app.post('/lee/item/delete', dependencies=[Depends(verify_token)])
def delete_item(param: dict):
    '''删除'''
    pass

以上就是四个接口的基本样子,其中dependencies=[Depends(verify_token)]可以暂时不用管

token相关

目前前后台分离的形式下,我们的访问控制,也使用token的形式,我在这里再赘述下token的简单流程,使用token的话,基本就需要做两件事情,一件是产生token(登录接口中验证用户名和密码通过后返回token),另一件就是校验token(在每次的Http请求中增加token字段,一般放在Header中,然后系统获取token),既然事情确认了,那我就代码实现

def app_create_token(username, password):
    '''创建token'''
    conn = redis.Redis(connection_pool=POOL) #使用redis存放用户信息
    # print(conn.get('app_dev_password_' + username).decode('utf-8'))
    if conn.get('app_dev_username_' + username) is None:
        raise Exception('用户暂时无法登陆')
    if conn.get('app_dev_password_' + username).decode('utf-8') != password:
        raise Exception('密码错误')
    #以上为判断用户是否在redis中存在
    token_name = conn.get('app_username_' + username)
    if token_name is not None:
        #如果token已经在redis中了,直接返回,不新生成,当然了此处也可以做超时等相关设置
        return token_name.decode()
    token = jwt.encode({'user': username}, JWT_SECRET,
                       algorithm='HS256') #调用JWT的包生成token
    token_name = token.decode('utf-8')
    conn.set(name='app_username_' + username, value=token_name)
    return token_name #返回token字符串

校验token

def app_verify_token(token):
    '''验证token'''
    infos = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) #根据传递过来的token进行解析,如果正确解析,那就是正确的token(此处也可以增加超时等设置)
    return infos['user']

以上就完成了基本的token生成和token的校验,但是又一个问题摆在我们面前,就是如何校验?我们刚才已经陈述过,需要获取header中的token信息来判断,那么此处如何在每个请求获取header呢?flask中可以使用python修饰器的特征来实现对方法的修饰,增加额外信息,fastApi引入了一个新的东西,就是dependencies

fastApi的dependencies

dependencies,这个大家可能都非常的陌生,但是看到官网介绍中的 Dependency Injection这几个字的时候,Java开发的同学是不是感到分外的亲切呢?🐶🐶🐶,DI相关的内容我们不在展开介绍,此处我们把官网上对于合适使用dependencies的的介绍再罗列过来

  • 当有重复的逻辑代码时
  • 共享数据库链接时
  • 安全、认证、角色相关时
  • 其他...
    除了第四个,其他我想对于我们来说都不陌生,我们的token基本就属于第三类情况了
#token.py
class TokenException(Exception):
    code: int
    msg: str
    detail: str

    def __init__(self, code, msg, detail=None):
        self.code = code
        self.msg = msg
        self.detail = detail
#我们首先定义一个异常,用途后面再说

#然后我们定义一个方法(verify_token),该方法即为文档中描述的Depends
from fastapi import Header


def verify_token(token=Header(None)):
    if token is None:
        raise TokenException(701, '没有token') #定义TokenException类是为了方便后续统一化处理
    try:
        app_verify_token(token)
    except Exception:
        raise TokenException(702, token + '无效,请确认token合法性')
#定义好该方法后,在每一个请求中如下编写即可
@app.post('/lee/item/all', dependencies=[Depends(verify_token)])
def items(param: dict):
    '''查询'''
    pass

至于方法verify_token中的参数header为什么这样编写,参考fastApi的文档fastapi.tiangolo.com/tutorial/he…,然后增加对于TokenException的格式化处理

#main.py
class StatusInfo():
    def __init__(self, code: int, msg: str, data: dict = None):
        self.code = code
        self.msg = msg
        self.data = data


@app.exception_handler(TokenException)
def error_handler(request, exc: TokenException):
    return JSONResponse(content=jsonable_encoder(StatusInfo(exc.code, exc.msg), include_none=False),
                        media_type="application/json")

此时就实现了访问接口的token认证,对于没有token的请求,统一返回

{
    "code": 701,
    "msg": "没有token"
}

对于token错误或者失效(增加失效逻辑处理),统一返回

业务逻辑

经过以上的代码编写,我们现在就可以安心的写我们的业务逻辑代码了

#sys_conf.py
from peewee import *

db = MySQLDatabase('day_todo', user='username', password='password',
                         host='localhost', port=3306)

首先参考以上使用peewee定义数据库的链接,然后定义表对应的Model

# item.py
from conf.sys_conf import db
from peewee import *
from datetime import datetime

#记事本类
class Item(Model):
    id = IntegerField(primary_key=True)
    info = CharField()
    insert_time = DateTimeField()
    last_update_time = DateTimeField()
    is_finish = BooleanField()
    is_delete = BooleanField()
    date_batch = CharField()
    user_id = CharField()

    @staticmethod
    def default_create(info):
        '''静态默认创建对象方法'''
        item = Item()
        item.is_finish = False
        item.is_delete = False
        item.insert_time = datetime.now()
        item.info = info
        item.last_update_time = datetime.now()
        item.date_batch = datetime.strftime(datetime.now(), '%Y%m%d')
        return item

    def to_json(self):
        '''bean转json'''
        json = {}
        json['id'] = self.id
        json['info'] = self.info
        json['insert_time'] = self.insert_time
        json['last_update_time'] = str(self.last_update_time)
        json['is_finish'] = self.is_finish
        json['is_delete'] = self.is_delete
        json['date_batch'] = self.date_batch
        json['user_id'] = self.user_id
        return json

    class Meta:
        database = db #对应的数据库链接名称
        table_name = 'lee_item' #自定义映射的表名

我们以其中一个删除接口为例

# main.py
@app.post('/lee/item/delete', dependencies=[Depends(verify_token)])
def delete_item(param: dict): #定义传递的json参数使用param来接受,类型为dict
    try:
        id = param['id']
        user = param['user']
        db.connect()

        list = Item.select().where(Item.id == id, Item.user_id == user) #使用peewee来查询符合条件的记事本信息
        if len(list) < 1:
            return create_response(200, '没有需要更改的数据')
        item = list[0]
        item.is_delete = True #将数据的是否删除字段修改为删除
        item.save()  #保存
        return create_response(200, '成功') #格式化返回
    except Exception as e:
        return create_response(201, '删除记录失败' + str(e))
    finally:
        db.commit()
        db.close()
      
# 格式化返回  
def create_response(code: int = 200, msg: str = 'success', data: dict = None):
    return JSONResponse(content=jsonable_encoder(StatusInfo(code=code, msg=msg, data=data), include_none=False),
                        media_type="application/json")

其他接口参考此接口开发即可

总结

通过上面的讲解大家应该对使用fastApi和peewee快速开发后台API有了一个基本的了解,项目详细的代码参考源码。文中难免存在谬误,欢迎大家多交流,多指点。