HTTP 的常用请求方法如下:
REST 设计不需要特定的数据格式。在请求中数据可以以 JSON 形式, 或者有时候作为 url 中查询参数项。
RESTful API规划
添加
、修改
图书时,图书信息为 JSON
数据, 数据格式示例:{"title":"Python Web","author":"Tony John","summary":"This is a good book","published":"2020"}
实际部署时,将 URL
中的 127.0.0.1:5000
替换为相应的 主机名
或 IP地址
创建项目 创建名为 flask-book-app
的 flask
项目(步骤略)
项目中需要的包:
flask
: Python 编写的 Web 应用程序框架
python-dotenv
: 从文件中读取键值对,并将其设置为环境变量
flask-sqlalchemy
: 数据库 ORM
flask-restful
: 快速构建 REST APIs 的 Flask 扩展
flask-marshmallow
:序列化和反序列化模块, 用来将复杂的 orm 对象 与 python 原生数据类型之间相互转换的库
flask-cors
: 跨域请求配置模块
marshmallow-sqlalchemy
: 用于 flask-marshmallow
与 flask-sqlalchemy
的集成
添加配置文件 .flaskenv
, 内容如以下代码:
1 2 FLASK_ENV=development FLASK_APP=bookapp
使用包组织项目代码 Flask
对项目结构没有固定要求,可以将所有代码放在 app.py
文件中, 但是这样会导致代码难以阅读和修改,因此这里使用包来组织整个项目的代码。
项目的最终结构如以下图所示:
1 2 3 4 5 6 7 8 . |-- bookapp --- 包名 | |-- __init__.py --- 包构造文件,创建程序实例 | |-- command.py --- 命令函数 | |-- models.py --- 模型类 | `-- views.py --- 视图函数,编写 RESTful API 的方法 |-- venv --- 虚拟环境文件夹 `-- data.db --- Sqlite 数据库文件
添加文件夹 bookapp
, 并在该文件夹中添加两个文件: __init__.py
, views.py
编辑文件 bookapp\__init__.py
, 内容如以下代码:
1 2 3 4 5 6 from flask import Flaskapp = Flask(__name__) from bookapp import views
编辑 bookapp\views.py
, 内容如以下代码:
1 2 3 4 5 6 from bookapp import app@app.route('/' ) def index (): return 'Hello world'
运行项目, 效果如下图:
使用数据库 编辑 bookapp\__init__.py
, 内容如以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import osfrom flask import Flaskfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = 'sqlite:///' + \ os.path.join(os.path.dirname(app.root_path), 'data.db' ) app.config['SQLALCHEMY_TRACK_MODIFICATIONS' ] = False db = SQLAlchemy(app) from bookapp import views, models
添加数据模型类 在 bookapp
文件夹中添加文件 models.py
, 编辑代码如以下:
1 2 3 4 5 6 7 8 9 from bookapp import dbclass Book (db.Model): id = db.Column(db.Integer, primary_key=True ) title = db.Column(db.String(50 )) author = db.Column(db.String(50 )) summary = db.Column(db.String(250 )) published = db.Column(db.String(4 ))
添加的命令实现数据库初始化 在 bookapp
文件夹中添加文件 command.py
, 编辑代码如以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import clickfrom bookapp import app, dbfrom bookapp.models import Book@app.cli.command() def forge (): ''' 用于初始化数据库的指令, 在虚拟环境下运行: flask forge ''' db.create_all() books = [ { 'title' : 'A Fire Upon the Deep' , 'author' : 'Vernor Vinge' , 'summary' : 'The coldsleep itself was dreamless.' , 'published' : '1992' }, { 'title' : 'The Ones Who Walk Away From Omelas' , 'author' : 'Ursula K. Le Guin' , 'summary' : 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the city Omelas, bright-towered by the sea.' , 'published' : '1973' }, { 'title' : 'Dhalgren' , 'author' : 'Samuel R. Delany' , 'summary' : 'to wound the autumnal city.' , 'published' : '1975' }, { 'title' : 'JavaScript Programming' , 'author' : 'Samuel R. Delany' , 'summary' : 'to wound the autumnal city.' , 'published' : '2015' }, { 'title' : 'Flask Web Development' , 'author' : 'Ursula K. Le Guin' , 'summary' : 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the city Omelas, bright-towered by the sea.' , 'published' : '2018' }, ] for item in books: book = Book( title=item['title' ], author=item['author' ], summary=item['summary' ], published=item['published' ] ) db.session.add(book) db.session.commit() click.echo('database initialized.' )
编辑 bookapp\__init__.py
, 在最后一行
添加 command
模块的导入, 如以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import osfrom flask import Flaskfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = 'sqlite:///' + \ os.path.join(os.path.dirname(app.root_path), 'data.db' ) app.config['SQLALCHEMY_TRACK_MODIFICATIONS' ] = False db = SQLAlchemy(app) from bookapp import views, models, command
在项目的虚拟环境下执行 flask forge
命令初始化数据,并添加数据到数据库中:
1 2 (venv) PS D:\sources\python_repos\flask-book-app > flask forge database initialized.
编写 API 方法 ( 组织 Flask-RESTful 资源对象) 编辑 bookapp\views.py
, 内容如以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 from bookapp import app, db, ma, apifrom bookapp.models import Bookfrom flask import jsonify, redirect, requestfrom flask_restful import Resourceclass BookSchema (ma.Schema): class Meta : fields = ('id' , 'title' , 'author' , 'summary' , 'published' ) book_schema = BookSchema() books_schema = BookSchema(many=True ) class BookListResource (Resource ): def get (self ): ''' 查询图书,分页参数的请求格式:http://127.0.0.1:5000/api/v1/books?limit=3&offset=3 使用默认参数的访问路径:http://127.0.0.1:5000/api/v1/books ''' limit = 10 offset = 0 try : if (request.json): limit = request.json['limit' ] offset = request.json['offset' ] except Exception as e: pass query = Book.query.offset(offset).limit(limit) total = Book.query.count() books = books_schema.dump(query) return jsonify({ "offset" : offset, "limit" : limit, "total_count" : total, "books" : books }) def post (self ): ''' 添加一本图书, 每本图书四个属性,不能为缺少 访问路径:http://127.0.0.1:5000/api/v1/books ''' try : new_book = Book( title=request.json['title' ], author=request.json['author' ], summary=request.json['summary' ], published=request.json['published' ] ) db.session.add(new_book) db.session.commit() return book_schema.dump(new_book) except Exception as e: return None class BookResource (Resource ): def get (self, book_id ): ''' 查询指定 id 的图书, 访问路径:http://127.0.0.1:5000/api/v1/books/2 ''' book = Book.query.get_or_404(book_id) return book_schema.dump(book) def patch (self, book_id ): ''' 修改指定 id 的图书 patch 指令是 put 指令的升级,允许局部更新 访问路径:http://127.0.0.1:5000/api/v1/books/2 (同时带上要修改的属性值 ) ''' try : book = Book.query.get_or_404(book_id) if 'title' in request.json: book.title = request.json['title' ] if 'author' in request.json: book.author = request.json['author' ] if 'summary' in request.json: book.summary = request.json['summary' ] if 'published' in request.json: book.published = request.json['published' ] db.session.commit() return book_schema.dump(book) except Exception as e: return None def put (self, book_id ): ''' put 指令 和 patch 指令是一样的 ''' return self .patch(book_id) def delete (self, book_id ): ''' 删除指定 id 的图书 访问路径:http://127.0.0.1:5000/api/v1/books/2 ''' book = Book.query.get_or_404(book_id) db.session.delete(book) db.session.commit() return '' , 204 @app.route('/' , methods=['GET' ] ) def index (): return redirect('/api/v1/books' )
绑定资源与URI 编辑 bookapp\__init__.py
, 加入跨域配置
和 序列化模块
, 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import osfrom flask import Flaskfrom flask_sqlalchemy import SQLAlchemy from flask_cors import CORS from flask_marshmallow import Marshmallowfrom flask_restful import Apiapp = Flask(__name__) CORS(app, resources={r'/*' : {'origins' : '*' }}) app.config['SQLALCHEMY_DATABASE_URI' ] = 'sqlite:///' + \ os.path.join(os.path.dirname(app.root_path), 'data.db' ) app.config['SQLALCHEMY_TRACK_MODIFICATIONS' ] = False db = SQLAlchemy(app) ma = Marshmallow(app) api = Api(app) from bookapp import views, models, command from bookapp.views import BookListResource, BookResource api.add_resource(BookListResource, '/api/v1/books' ) api.add_resource(BookResource, '/api/v1/books/<int:book_id>' )
测试 RESTful API 运行项目 在虚拟环境下执行命令: flask run 运行项目,运行结果如下所示:
1 2 3 4 5 6 7 8 (venv) PS D:\sources\python_repos\flask-book-app > flask run * Serving Flask app 'bookapp' (lazy loading) * Environment: development * Debug mode: on * Restarting with stat * Debugger is active! * Debugger PIN: 129 -008-647 * Running on http://127.0 .0.1 :5000 / (Press CTRL+C to quit)
安装扩展 在 Visual Studio Code
中使用安装名为 PostCode
的扩展, 如下图所示:
无参数的默认分页查询图书测试
带参数的分页查询图书测试
查询指定 id 的图书测试
添加新图书测试
修改图书测试
删除图书测试