Restful API 已经大行其道,因为我经常需要需要做基于 Restful API 的测试,为了日常测试的方便,用 Flask 来实现 Restful API 接口。没有使用 flask-restful 插件,手动实现。后期再用 flask-restful 插件来改造。通过这种方式,可以更深刻理解插件解决什么问题,也可以看看插件是怎样更优雅地实现的,而不仅仅是用别人的工具。
数据库表的结构
数据放在 MySQL 数据库中中,创建 emp_master
数据表的脚本如下:
create table EMP_MASTER(
EMP_ID int primary key,
GENDER varchar(10),
AGE int,
EMAIL varchar(50),
PHONE_NR varchar(20),
EDUCATION varchar(20),
MARITAL_STAT varchar(20),
NR_OF_CHILDREN int
) character set utf8;
示例数据可以从这里获取。
数据的增删改查
增删改查用存储过程实现,脚本如下:
获取所有员工主数据的记录
drop procedure if exists getEmployees;
delimiter //
create procedure getEmployees()
begin
select * from emp_master;
end //
delimiter ;
新建员工主数据记录
drop procedure if exists createEmployee;
delimiter //
create procedure createEmployee(
in_empid int,
in_gender varchar(10),
in_age int,
in_email varchar(50),
in_phonenr varchar(20),
in_education varchar(20),
in_maritalstat varchar(20),
in_nrofchildren int
)
begin
insert into emp_master (EMP_ID, GENDER, AGE, EMAIL, PHONE_NR, EDUCATION, MARITAL_STAT, NR_OF_CHILDREN)
VALUES (in_empid, in_gender, in_age, in_email, in_phonenr, in_education, in_maritalstat, in_nrofchildren);
end //
delimiter ;
修改员工主数据
drop procedure if exists updateEmployee;
delimiter //
create procedure updateEmployee(
in_empid int,
in_gender varchar(10),
in_age int,
in_email varchar(50),
in_phonenr varchar(20),
in_education varchar(20),
in_maritalstat varchar(20),
in_nrofchildren int
)
begin
update emp_master set GENDER = in_gender, AGE = in_age, EMAIL = in_email,
PHONE_NR = in_phonenr, EDUCATION = in_education,
MARITAL_STAT = in_maritalstat, NR_OF_CHILDREN = in_nrofchildren
where EMP_ID = in_empid;
end //
delimiter ;
删除员工主数据
drop procedure if exists deleteEmployee;
delimiter //
create procedure deleteEmployee(
empid int
)
begin
delete from emp_master where EMP_ID=empid;
end //
delimiter ;
Flask 工程文档结构
Flask_Restful_MySQL/
app/
__init__.py
config.py
manage.py
main/
__init__.py
controllers.py
routes.py
主要的代码在 controllers.py
和 routes.py
中。controllers.py
实现对数据库的增删改查,结果以dict 类型返回。routes.py
处理请求路径和视图函数的映射,对客户端请求返回 json 格式的响应 (Response)。
系统配置
我们先从配置开始。config.py
源代码如下:
# encoding utf-8
class Config(object):
@staticmethod
def init_app(app):
pass
class DBConfig(Config):
user = 'root'
password = '123456'
db = 'stonetest'
host = 'localhost'
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
config = {
'development': DevelopmentConfig,
'production': ProductionConfig
}
首先定义一个配置 (Configuration) 的基类 Config
, 然后基于 Config
定义三个子类:
-
DBConfig
用于数据库登录的配置; -
DevelopmentConfig
配置用于开发,此时DEBUG = True
; -
ProductionConfig
配置用于生产环境,此时DEBUG = False
创建 app
创建一个名为 app
的 package,也就是创建一个 app
文件夹,文件夹中包含 __init__.py
文件。在 app/__init__.py
中做以下几件事:
- 创建一个
MySQL
的实例db
,flaskext.mysql
是 Flask 插件,其中的MySQL
类用于连接 MySQL 数据库并进行 CRUD 操作 - 创建一个
createApp()
方法,返回app
的实例,同时,建立 app 与 db 的关联
from flask import Flask
from flaskext.mysql import MySQL
from config import config, DBConfig
db = MySQL()
def createApp(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
# MySQL Configurations
app.config['MYSQL_DATABASE_HOST'] = DBConfig.host
app.config['MYSQL_DATABASE_DB'] = DBConfig.db
app.config['MYSQL_DATABASE_USER'] = DBConfig.user
app.config['MYSQL_DATABASE_PASSWORD'] = DBConfig.password
# related db to app
db.init_app(app)
return app
manage.py
作为程序入口
在 manage.py
中输入如下代码:
from app import createApp, db
app = createApp('development')
if __name__ == '__main__':
app.run()
此时,已经可以从 manage.py
启动该 Flask 工程。但因为没有任何视图函数和路由 (route),启动时浏览器提示如下错误:
路由
在 app/main/__init__.py
文件输入如下代码:
from flask import Blueprint
main = Blueprint('main', __name__, template_folder='templates')
from . import routes
以上代码创建一个名为 main
的 Blueprint,然后导入 routes 模块。
接下来在 app/__init__.py
的 createApp()
方法中添加如下几行代码:
def createApp(config_name):
...
# related db to app
db.init_app(app)
# register the blueprint
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
添加的几行代码如下图:
这几行填加的代码,创建一个名称为 main
的蓝图,并在 app
中进行注册。
然后, 在 app/main/routes.py
文件中,定义一个路由:
from . import main
from .controllers import Employee
from flask import jsonify
@main.route('/')
def inext():
return 'Hello, this is Flask Restufl Demo.'
再启动 manage.py
,已经正确地解析到路径,调用正确的视图函数了:
数据库 CRUD 操作
在 app/main/controllers.py
中创建对数据库表 emp_master
进行增删改查的四个。代码非常直观,就不再补充说明了:
from flask import request
from .. import db
class Employee(object):
def listAll(self):
conn = db.connect()
curr = conn.cursor()
curr.callproc('getEmployees')
items = [dict((curr.description[i][0], value) for i, value in enumerate(row)) for row in curr.fetchall()]
curr.close()
conn.close()
return items
def create(self):
payload = request.get_json()
if payload == None:
return {
'desc': 'No payload is provided to create employee.',
'code': 400
}
conn = db.connect();
curr = conn.cursor()
params = {
'EMP_ID': payload['EMP_ID'],
'GENDER': payload['GENDER'],
'AGE': payload['AGE'],
'EMAIL': payload['EMAIL'],
'EDUCATION': payload['EDUCATION'],
'PHONE_NR': payload['PHONE_NR'],
'MARITAL_STAT': payload['MARITAL_STAT'],
'NR_OF_CHILDREN': payload['NR_OF_CHILDREN']
}
# Check for duplication
curr.execute('SELECT EMP_ID FROM emp_master WHERE EMP_ID=%d' % params['EMP_ID'])
curr.fetchall()
if curr.rowcount > 0:
return {
'desc': 'Employee already exists.',
'code': 400
}
curr.callproc('createEmployee', (
params['EMP_ID'],
params['GENDER'],
params['AGE'],
params['EMAIL'],
params['PHONE_NR'],
params['EDUCATION'],
params['MARITAL_STAT'],
params['NR_OF_CHILDREN']
))
conn.commit()
return {
'desc': curr.rowcount,
'code': 201
}
def update(self, empId):
conn = db.connect()
curr = conn.cursor()
payload = request.get_json()
if payload == None:
return {
'desc': 'No payload is provided to update employee.',
'code': 400
}
params = {
'GENDER': payload['GENDER'],
'AGE': payload['AGE'],
'EMAIL': payload['EMAIL'],
'EDUCATION': payload['EDUCATION'],
'PHONE_NR': payload['PHONE_NR'],
'MARITAL_STAT': payload['MARITAL_STAT'],
'NR_OF_CHILDREN': payload['NR_OF_CHILDREN'],
'EMP_ID': empId,
}
curr.callproc('updateEmployee', (
params['EMP_ID'],
params['GENDER'],
params['AGE'],
params['EMAIL'],
params['PHONE_NR'],
params['EDUCATION'],
params['MARITAL_STAT'],
params['NR_OF_CHILDREN']
))
conn.commit()
if curr.rowcount > 0:
return {'desc': curr.rowcount, 'code': 200}
else:
return {
'desc': 'Employee %s does not exists.' % empId,
'code': 400
}
def delete(self, empId):
conn = db.connect()
curr = conn.cursor()
curr.callproc('deleteEmployee', [empId])
conn.commit()
return {'desc': '%d employee(s) deleted.' % curr.rowcount, 'code': 204}
定义对数据表进行 CRUD 的路由
在 app/main/routes.py
添加如下代码:
from . import main
from .controllers import Employee
from flask import jsonify
@main.route('/')
def inext():
return 'Hello, this is Flask Restufl Demo.'
@main.route('/employees')
def listEmployees():
emp = Employee();
employees = emp.listAll()
return jsonify({'rows': employees}), 200;
@main.route('/employees/create', methods=['POST'])
def createEmployee():
emp = Employee()
result = emp.create()
return jsonify({'desc': result['desc']}), result['code']
@main.route('/employees/', methods=['PUT'])
def updateEmployee(empId):
emp = Employee()
result = emp.update(empId)
return jsonify({'desc': result['desc']}), result['code']
@main.route('/employees/', methods=['DELETE'])
def deleteEmployee(empId):
emp = Employee()
result = emp.delete(empId)
return jsonify({'desc': result['desc']}), result['code']
这样,一个完整的、可以对数据库进行 CRUD 的 Restful API就全部完成。后续我将再增加 session,cookie 等内容。