好风凭借力,送我上青云。
Flask-SQLAlchemy
安装
pip install flask-sqlalchemy -i https://pypi.doubanio.com/simple
定义使用
Flask-SQLAlchemy和原生的SQLAlchemy基本一致,首先想让我们定义一下数据库并进行连接
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) # type:Flask
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8"
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app=app)
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(length=50), nullable=False)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(length=50), nullable=False)
uid = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
author = db.relationship("User", backref="articles")
# db.drop_all()
# db.create_all()
# user = User(username="ydy")
# article = Article(title="xxxxxxaaaa")
#
# article.author = user
#
# db.session.add(article)
# db.session.commit()
users = User.query.all()
print(users)
if __name__ == '__main__':
app.run(debug=True)
使用Flask-SQLAlchemy需要配置一下几个部分:
- 导入
from flask_sqlalchemy import SQLAlchemy
- 配置相关数据库参数用于连接,最终形成一个URI:
DB_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8"
- 把URI添加到app的配置,
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
,app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
为了防止结合alembic
生成迁移数据库提示错误信息 - 创建SQLAlchemy对象,
db = SQLAlchemy(app=app)
,这一步必须放在配置之后否则无效。
结合alembic迁移
在对应文件目录执行命令,初始化一个alembic的文件夹(名称随便取,一般为alembic)
alembic init alembic
文件目录如下:
.
├── alembic
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
├── alembic.ini
└── demo.py
修改alembic/env.py文件配置
目的是导入我们app所在文件的模块路径。需要根据个人情况,每个人的目录结构不同,所以查找路径不同。例如:我的文件就是上面的demo.py,结合目录结构。之后还需要导入app所在的文件,并且修改target_metadata
的值
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import demo
target_metadata = demo.db.Model.metadata
修改alembic.ini配置
sqlalchemy.url = mysql+pymysql://root:root@localhost/demo?charset=utf8
生成迁移脚本
alembic revision --autogenerate -m 'message'
成功之后会在versions文件夹下创建一个py文件。
基本的Flask-SQLALchemy配置完成,其他的操作基本和SQLAlchemy一致。
Flask-Script
安装
pip install flask-script -i https://pypi.doubanio.com/simple
定义使用
新建一个文件manager.py
,输入一下格式代码
from flask_script import Manager
from xxx import app # 从其他文件导入app
manager = Manager(app) # 接受用一个app
if __name__ == '__main__':
manager.run()
添加脚本
@manager.option("-u", "--name", dest="name")
def add_user(name):
print("hello %s" % name)
添加一个函数,接受一个个参数,并且添加了一个个装饰器,接着我们通过命令执行文件,其中dest
是接受传递的值:
python manager.py add_user -u "ydy"
或者
python manager.py add_user --name "ydy"
使用子命令
我们可以新建一个db_script.py来管理我们操作数据库的命令文件
from flask_script import Manager
db_manager = Manager()
@db_manager.command
def init():
pass
@db_manager.command
def makemigrations():
pass
@db_manager.command
def migrate():
pass
之后我们在script主文件注册子命令:
from flask_script import Manager
from xxx import app # 从其他文件导入app
from db_script import db_manager
manager = Manager(app) # 接受用一个app
manager.add_command("db",db_manager) # 添加子命令,名称为db,随意取
if __name__ == '__main__':
manager.run()
使用子命令:
python manager.py db init
python manager.py db makemigrations
python manager.py db migrate
使用类定义
举一个非常简单的例子,我们想创建一个hello仅打印出“ hello world” 的命令。它不需要任何参数,因此非常简单:
from flask_script import Command
class Hello(Command):
"prints hello world"
def run(self):
print "hello world"
manager.add_command('hello', Hello())
使用类定义:
- 必须继承Command
- 必须实现run方法
- 必须通过add_command方法添加命令
通过类定义接受参数的形式:
from flask_script import Command,Option
class Hello(Command):
option_list = (
Option("-u", "--name", dest="name"),
)
def run(self, name):
print("hello {}".format(name))
manager.add_command("hello", Hello())
#========================方式二=======================
class Hello2(Command):
def __init__(self):
# 一般用来设置默认值
pass
def get_options(self):
return [
Option("-u", "--name", dest="name"),
]
def run(self, name):
print("hello {}".format(name))
manager.add_command("hello2", Hello2())
Flask-Migrate
安装
pip install flask-migrate -i https://pypi.doubanio.com/simple
在manage.py代码中使用
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from xxx import app
from xxx import models # 需要导入模型,产生映射关系,否则无法生成迁移文件
manager = Manager(app)
# 用来绑定app和db到flask_migrate的
Migrate(app,db)
# 添加Migrate的所有子命令到db下
manager.add_command("db",MigrateCommand)
if __name__ == '__main__':
manager.run()
flask_migrate常用命令
- 初始化一个环境:python manage.py db init
- 自动检测模型,生成迁移脚本:python manage.py db migrate
- 将迁移脚本映射到数据库中:python manage.py db upgrade
- 更多命令:python manage.py db --help
Flask-RESTful
Flask-RESTful适用于前后端分离项目的接口设计,设计符合RESTful风格的代码,使用简单方便。Flask-RESTful要求Python版本为 2.6, 2.7, 或者 3.3及以上。
安装
pip install flask-restful -i https://pypi.doubanio.com/simple
定义使用
下面结合Flask-RESTful设计一个接口,新建一个manage.py
的文件:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app=app)
class Index(Resource):
def get(self):
return {'username': "ydongy"}
api.add_resource(Index, "/", endpoint="index")
if __name__ == '__main__':
app.run(debug=True)
分析:
- 导入模块
from flask_restful import Api, Resource
,Api
接受一个app生成一个对象,相当于把app包装。Resource
是需要继承的类,替换以前的views.MethodView
api = Api(app=app)
,把app包装class Index(Resource):
:定义的类视图继承Resource
,此时返回的响应内容就是Json格式api.add_resource(Index, "/", endpoint="index")
:添加路由和端点,endpoint用于url_for反转url使用,如果不写endpoint,那么将会使用视图的名字的小写来作为endpoint。
参数解析
Flask-RESTful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。以下是基本用法:
from flask_restful import reqparse
class Login(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('username', type=str, help="用户名", required=True)
parser.add_argument('password', type=str, help="密码", required=True)
args = parser.parse_args()
......
add_argument
可以指定这个字段的名字,这个字段的数据类型等,也就是接受前台传递过来的请求体数据。以下将对这个方法的一些参数做详细讲解:
常用参数:
default
:默认值,如果这个参数没有值,那么将使用这个参数指定的值。required
:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。type
:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。choices
:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。help
:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。trim
:是否要去掉前后的空格。
扩展type
其中的type,可以使用python自带的一些数据类型,也可以使用flask_restful.inputs
下的一些特定的数据类型来强制转换。比如一些常用的:
url
:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
from flask_restful import inputs
parser.add_argument('urlpath', type=inputs.url, help="url地址", required=True)
regex
:正则表达式。
from flask_restful import inputs
parser.add_argument('phone', type=inputs.regex(r'1[3-9]\d{9}'), help="手机号")
date
:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常。
from flask_restful import inputs
parser.add_argument('birthday', type=inputs.date, help="生日", required=True)
数据格式化
对于一个视图函数,你可以指定好一些字段用于返回。以后可以使用ORM模型或者自定义的模型的时候,他会自动的获取模型中的相应的字段,生成json数据,然后再返回给客户端。这其中需要导入flask_restful.marshal_with
装饰器。并且需要写一个字典,来指示需要返回的字段,以及该字段的数据类型。示例代码如下:
from flask_restful import marshal_with,fields
class ProfileView(Resource):
resource_fields = {
'username': fields.String,
'age': fields.Integer,
'gender': fields.Integer
}
@marshal_with(resource_fields)
def get(self,user_id):
user = User.query.get(user_id)
return user
分析:
resource_fields
:定义一个需要返回的json格式的数据变量,名称随便取@marshal_with(resource_fields)
:给方法增加一个装饰器,并把定义的变量传入
在get方法中,返回user的时候,flask_restful会自动的读取user模型上的username以及age还有gender属性。组装成一个json格式的字符串返回给客户端,因此不需要我们手动调用各个字段,组织成json数据返回。但是注意定义的字典中的键需要和模型字段名一致,如果想要不一样可以增加一个参数,如下重命名属性。
重命名属性
有时候返回给前台的的字段名称的不同于模型的属性名。此时可以使用attribute
配置这种映射。比如现在想要返回user.username中的值,但是在返回给前台的时候,想要以name返回回去,那么可以这样写:
resource_fields = {
'name': fields.String(attribute='username')
}
attribute:对应模型字段属性名称,name是返回给前端的字段名称。
还可以定义一个default
默认值,当字段没有值的时候可以指定一个默认值。
关联关系
首先我们定义几个模型类,表示之间的关联关系:
class User(db.Model):
__tablename__ = "tb_user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50))
email = db.Column(db.String(200))
article_tag_table = db.Table(
'tb_tag_article',
db.Column('article_id', db.Integer, db.ForeignKey("tb_article.id"), primary_key=True),
db.Column('tag_id', db.Integer, db.ForeignKey("tb_tag.id"), primary_key=True)
)
class Article(db.Model):
__tablename__ = "tb_article"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
content = db.Column(db.String(200), nullable=True)
author_id = db.Column(db.Integer, db.ForeignKey('tb_user.id')) # 表名.id
author = db.relationship("User", backref="articles")
tags = db.relationship("Tag", backref="articles", secondary=article_tag_table)
class Tag(db.Model):
__tablename__ = "tb_tag"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
user-article
:一对多的关系article-tag
:多对多的关系
此时我们想要查询一个文章,但是查询结果中还要包含该文章的作者、所属标签的信息,而不是单单的外键id。此时需要如下定义,一对多的关系可以使用fields.Nested
,多对多的关系可以使用fields.List
。
class ArticleView(Resource):
resource_fields = {
'title': fields.String,
'content': fields.String,
'author': fields.Nested({
"id": fields.Integer,
"username": fields.String,
"email": fields.String
}),
'tags': fields.List(fields.Nested({
"id": fields.Integer,
"name": fields.String
}))
}
@marshal_with(resource_fields)
def get(self, article_id):
article = Article.query.get(article_id)
return article
author
:就是Article关联字段的属性,表示单个,所以使用fields.Nested,参数是想要获取的关联对象的属性tags
:也是Article关联字段的属性,表示多个,所以使用fields.List,而每一个tag
又是一个关联对象,所以嵌套一层fields.Nested
,参数是想要获取的关联对象的属性
返回结果如下:
{
"title": "mysql",
"content": "入门到删库",
"author": {
"id": 1,
"username": "jack",
"email": "[email protected]"
},
"tags": [
{
"id": 1,
"name": "database"
},
{
"id": 2,
"name": "language"
}
]
}
蓝图及模板使用
- 在蓝图中,如果使用
flask-restful
,那么在创建Api
对象的时候,就不要再使用app
了,而是使用蓝图app。 - 如果在
flask-restful
的视图中想要返回html
代码,或者是模版,那么就应该使用api.representation
这个装饰器来定义一个函数,在这个函数中,应该对html
代码进行一个封装,再返回。
from flask import render_template, make_response
from flask import Blueprint
from flask_restful import Api
api_bp = Blueprint(name="v1", import_name=__name__, url_prefix="/v1")
api = Api(app=api_bp)
@api.representation('text/html')
def html(data, code, headers):
resp = make_response(data)
return resp
class ListView(Resource):
def get(self):
return render_template("index.html")
api.add_resource(ListView, '/article/', endpoint="list_view")
分析:
api_bp = Blueprint(name="v1", import_name=__name__, url_prefix="/v1")
:定义蓝图api = Api(app=api_bp)
:传入蓝图app创建Api对象@api.representation('text/html')
:装饰一个函数,指明返回的content-type类型,该方法自动执行,所以需要返回一个response对象return render_template("index.html")
:restful类视图返回模板,会自动执行上面被装饰器定义过的函数