Flask入门

文章目录

简介

Flask 是一款基于 Werkzeug 和 Jinja2 实现的轻量级 Web 框架,灵活轻便易上手,有非常多的扩展插件。




安装

pip install Flask

本文 Flask==2.1.2




初试

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return '<h1>Hello World!</h1>'


@app.route('/user/<name>')
def user(name):
    return f'<h1>Hello, {name}!</h1>'


@app.route('/number/<int:id>')
def number(id):
    return f'<h1>Hello, {id}!</h1>'


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/

访问 http://127.0.0.1:5000/user/XerCis

访问 http://127.0.0.1:5000/number/1


Ctrl + C 退出程序




路由

处理 URL 到 Python 函数的映射程序称为路由,如 @app.route('/'),将触发视图函数 index()

创建路由方式有以下两种:



装饰器

装饰器 app.route() 相当于函数 app.add_url_rule()

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return '<h1>Hello World!</h1>'


def user(name):
    return f'<h1>Hello, {name}!</h1>'


app.add_url_rule('/user/<name>', view_func=user)
print(app.url_map)
# Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
#  <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
#  <Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])



蓝图

实现代码分层


auth/__init__.py

from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import views

auth/views.py

from . import auth


@auth.route('/login')
def login():
    return 'success'

main/__init__.py

from flask import Blueprint

main = Blueprint('main', __name__)

from . import views

main/views.py

from . import main


@main.route('/')
def index():
    return '<h1>Hello World!</h1>'

app.py

from flask import Flask

from main import main as main_blueprint
from auth import auth as auth_blueprint

app = Flask(__name__)
app.register_blueprint(main_blueprint)
app.register_blueprint(auth_blueprint, url_prefix='/auth')
print(app.url_map)
# Map([<Rule '/auth/login' (OPTIONS, GET, HEAD) -> auth.login>,
#  <Rule '/' (OPTIONS, GET, HEAD) -> main.index>,
#  <Rule '/static/<filename>' (OPTIONS, GET, HEAD) -> static>])



注册蓝图标准流程:

  1. 创建蓝图对象
  2. 使用蓝图对象注册路由
  3. app 对象注册蓝图对象
from flask import Flask
from flask import Blueprint

app = Flask(__name__)

blueprint = Blueprint('main', __name__)  # 1. 创建蓝图对象


@blueprint.route('/')  # 2. 使用蓝图对象注册路由
def ping():
    return 'pong'


app.register_blueprint(blueprint)  # 3. app 对象注册蓝图对象

if __name__ == '__main__':
    print(app.url_map)
    app.run()



自动注册蓝图

按业务划分代码

app/blueprints.py,蓝图的统一定义

from flask import Blueprint

common = Blueprint('common', __name__)

app/common/views.py

from app.blueprints import common


@common.route('/')
def ping():
    return 'pong'

main.py

import importlib

from flask import Flask
from flask import Blueprint

app = Flask(__name__)

module = importlib.import_module('app.blueprints')
for name, blueprint in module.__dict__.items():
    if isinstance(blueprint, Blueprint):
        importlib.import_module('app.{}.views'.format(name))
        app.register_blueprint(blueprint)

if __name__ == '__main__':
    print(app.url_map)
    app.run()




请求上下文

Flask 使用上下文临时把某些对象变为全局可访问,如参数 request

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return f'<p>Your browser is {user_agent}</p>'


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/

变量名上下文说明
current_app程序上下文当前激活程序的实例
g程序上下文处理请求时的临时存储对象
request请求上下文请求对象,封装了客户端的 HTTP 请求内容
session请求上下文用户会话,存储请求间的键值对字典




请求钩子

在请求前或后执行代码,如请求前创建数据库连接或认证发起请求的用户

为避免在每个视图函数中使用重复代码,Flask 提供了注册通用函数的功能

请求钩子用装饰器实现:




响应

Flask 响应的状态码默认为 200,表示成功处理

如果需要不同状态码,,可作为第二个返回值,如状态码 400,表示请求无效

或返回 Response 对象,可用 make_response() 函数构造

重定向的响应码为 302,可使用 redirect() 替代返回三个值或 Response 对象的形式

网页或页面没找到的响应码为 404,常通过 abort() 处理错误

from flask import Flask, make_response, redirect, abort

app = Flask(__name__)


@app.route('/')
def index():
    return '<h1>Bad Request</h1>', 400


@app.route('/error')
def error():
    response = make_response('<h1>This document carries a cookie!</h1>')
    response.set_cookie('answer', '42')
    return response


@app.route('/user/<int:id>')
def get_user(id):
    if id % 2 == 0:
        abort(404)
    else:
        return '<h1>Hello</h1>'


@app.route('/redirect')
def _redirect():
    return redirect('http://www.baidu.com')


if __name__ == '__main__':
    app.run(debug=True)

访问:




模板

为避免混乱,业务逻辑和显示逻辑应分开

模板是一个包含响应文本的文件,占位符表示动态部分,Flask 使用 Jinja2 这个强大的模板引擎进行渲染

项目结构,Flask 默认在 templates 子文件夹下寻找模板

templates/index.html

<h1>Hello World!</h1>

templates/user.html

<h1>Hello, {{ name }}!</h1>

main.py

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)


if __name__ == '__main__':
    app.run(debug=True)

访问:




变量

  • 变量要用空格隔开,如 {{ x }}
  • 能识别多种类型,如列表、字典、对象,如 {{ d['key'] }}{{ obj.f() }}
  • 能用过滤器修改变量,如首字母大写 Hello, {{ name|capitalize }}

templates/index.html

<p>{{ d['key'] }}</p>
<p>{{ l[3] }}</p>
<p>{{ l[index] }}</p>
<p>{{ obj.f() }}</p>
<p>Hello, {{ name|capitalize }}</p>

main.py

from flask import Flask, render_template

app = Flask(__name__)


class A():
    def f(self):
        return 'a'


@app.route('/')
def index():
    d = {'key': 'Hello'}
    l = [0, 1, 2, 3, 4]
    index = 4
    obj = A()
    name = 'xercis'
    return render_template('index.html', d=d, l=l, index=index, obj=obj, name=name)


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/



流程控制

条件 templates/condition.html

{% if x > 0 %}
    x is positive
{% elif x == 0 %}
    x is zero
{% else %}
    x is negative
{% endif %}

循环 templates/loop.html

<ul>
    {% for comment in comments %}
    <li>{{ comment }}</li>
    {% endfor %}
</ul>

宏,类似于 Python 中的函数,templates/macro.html

{% macro render_comment(comment) %}
    <li>{{ comment }}</li>
{% endmacro %}

<ul>
    {% for comment in comments %}
        {{ render_comment(comment) }}
    {% endfor %}
</ul>

还有模板继承,此处略。

main.py

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/condition/<x>')
def condition(x):
    x = int(x)
    return render_template('condition.html', x=x)


@app.route('/loop')
def loop():
    comments = 'abcde'
    return render_template('loop.html', comments=comments)


@app.route('/macro')
def macro():
    comments = 'abcde'
    return render_template('macro.html', comments=comments)


if __name__ == '__main__':
    app.run(debug=True)

访问:




自定义错误页面

@app.errorhandler()

from flask import Flask, abort

app = Flask(__name__)


@app.route('/a')
def a():
    abort(404)


@app.route('/b')
def b():
    abort(500)


@app.errorhandler(404)
def page_not_found(e):
    return '<h1>Page not found</h1>', 404


@app.errorhandler(500)
def internal_server_error(e):
    return '<h1>internal server error</h1>', 500


if __name__ == '__main__':
    app.run(debug=True)

访问:




Web表单

安装

pip install flask-bootstrap
pip install flask-wtf

快速渲染表单 templates/index.html

{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block content %}
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
    <button type="button" class="close" data-dismiss="alert">&times;</button>
    {{ message }}
</div>
{% endfor %}
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{{ wtf.quick_form(form) }}
{% endblock %}

main.py

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask import Flask, render_template, session, redirect, url_for, flash

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'  # 生成加密令牌的密钥
from flask_bootstrap import Bootstrap

bootstrap = Bootstrap(app)


class NameForm(FlaskForm):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')


@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        old_name = session.get('name')
        if old_name is not None and old_name != form.name.data:
            flash('Looks like you have changed your name!')
        session['name'] = form.name.data
        return redirect(url_for('index'))
    return render_template('index.html', form=form, name=session.get('name'))


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/




SQL数据库

订单管理程序有数据表如下

  • customers:
  • products:
  • orders:

安装

pip install flask-sqlalchemy



数据库URL

数据库引擎URL
MySQLmysql://scott:tiger@localhost/foo
mysql+mysqldb://scott:tiger@localhost/foo
mysql+pymysql://scott:tiger@localhost/foo
PostgreSQLpostgresql://scott:tiger@localhost/mydatabase
postgresql+psycopg2://scott:tiger@localhost/mydatabase
SQLitesqlite:///C:\path\to\foo.db



定义模型

SQLAlchemy 列类型

类型Python 类型描述
BigIntegerint大整数
Booleanbool布尔类型
Datedatetime.date日期
DateTimedatetime.datetime日期时间
Enumstr枚举
Floatfloat浮点数
Integerint整数
Intervaldatetime.timedelta时间间隔
LargeBinarystr二进制文件
MatchTypeMATCH操作符的返回类型
Numericdecimal.Decimal数值的基类
PickleType任意 Python 对象pickle序列化的Python对象
SchemaTypeMark a type as possibly requiring schema-level DDL for usage.
SmallIntegerint较小整数
Stringstr字符串的基类
Textstr可变大小字符串
Timedatetime.time时间
Unicodestr可变大小Unicode字符串
UnicodeTextstr长度无界的Unicode字符串

列选项

选项明描述
name数据库中的列名
type_列类型
autoincrement整数主键列自动递增
default默认值
index索引
nullable是否允许为空
unique是否为唯一值

列关系选项



增删改查

SQLAlchemy 查询过滤器

过滤器描述
filter()筛选条件
filter_by()关键字形式的筛选条件
limit()数量限制
offset()偏移量
order_by()排序
group_by()分组

SQLAlchemy 查询执行函数

执行函数描述
all()以列表的形式返回
first()第一条结果
first_or_404()第一条结果或404
get()主键对应的行
get_or_404()主键对应的行或404
count()结果数量
paginate()返回一个 Paginate 对象,包含指定范围的结果
from pathlib import Path

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + str(Path(__file__).parent / 'data.sqlite')  # 数据库连接
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 不跟踪对象修改

db = SQLAlchemy(app)


class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role', lazy='dynamic')

    def __repr__(self):
        return '<Role %r>' % self.name


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '<User %r>' % self.username


if __name__ == '__main__':
    db.drop_all()  # 删除旧数据

    db.create_all()  # 创建数据库和表

    # 插入行
    admin_role = Role(name='Admin')
    mod_role = Role(name='Moderator')
    user_role = Role(name='User')
    user_john = User(username='john', role=admin_role)
    user_susan = User(username='susan', role=user_role)
    user_david = User(username='david', role=user_role)
    # db.session.add(admin_role)
    # db.session.add(mod_role)
    # db.session.add(user_role)
    # db.session.add(user_john)
    # db.session.add(user_susan)
    # db.session.add(user_david)
    db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
    db.session.commit()

    print(admin_role.id)  # 1
    print(mod_role.id)  # 2
    print(user_role.id)  # 3

    # 修改行
    print(admin_role.name)  # Admin
    admin_role.name = 'Administrator'
    db.session.add(admin_role)
    db.session.commit()
    print(admin_role.name)  # Administrator

    # 删除行
    db.session.delete(mod_role)
    db.session.commit()
    print(mod_role.id)  # 2

    # 查询行
    print(Role.query.all())  # [<Role 'Administrator'>, <Role 'User'>]
    print(User.query.all())  # [<User 'john'>, <User 'susan'>, <User 'david'>]
    print(User.query.filter_by(role=user_role).all())  # [<User 'susan'>, <User 'david'>]
    print(str(User.query.filter_by(role=user_role)))
    # SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users

    users = user_role.users
    print(users)  # [<User 'susan'>, <User 'david'>]
    print(users[0].role)  # <Role 'User'>

    print(user_role.users.order_by(User.username).all())  # [<User 'david'>, <User 'susan'>]
    print(user_role.users.count())  # 2




电子邮件

安装

pip install flask-mail

推荐阅读:Python通过163和QQ收发邮件

异步发邮件

from threading import Thread

from flask import Flask
from flask_mail import Mail, Message

app = Flask(__name__)
app.config['MAIL_SERVER'] = 'smtp.163.com'  # 服务器
app.config['MAIL_PORT'] = 465  # 启用SSL发信,端口一般是465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = 'abc@163.com'  # 用户名
app.config['MAIL_PASSWORD'] = 'MXHQPFWUFEXCVMOQ'  # 授权密码

mail = Mail(app)  # 这句不能在app配置初始化前


def send_async_email(app, message):
    """异步发送电子邮件"""
    with app.app_context():
        mail.send(message)


@app.route('/')
def index():
    subject = '关于Python日志库Loguru库的问题请教'
    message = Message(
        subject,
        recipients=['12345678@qq.com'],
        html='<h1>请问如何轻松记录日志?</h1>',
        sender=app.config['MAIL_USERNAME']
    )
    # mail.send(message)  # 这句会卡几秒,影响用户体验
    thr = Thread(target=send_async_email, args=[app, message])
    thr.start()
    return '发送成功\n'


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/

生产环境中最好用 Celery




TODO:用户认证

大多数用户在不同的网站中使用相同的密码,想保证数据库中用户密码的安全,不能存储密码本身,而要存储密码的散列值,通常使用散列值加盐的方法。

推荐阅读:

使用 Werkzeug 实现密码散列值加盐

from werkzeug.security import generate_password_hash, check_password_hash

password = '123456'
password_hash = generate_password_hash(password)
print(password_hash)
print(check_password_hash(password_hash, password))
# pbkdf2:sha256:260000$mcvf9pIsr5Dg625I$f9060c13862c0b2570407eb27fba8a59d68b8d56aaf6845d46b64d8788894768
# True

安装

pip install flask-login
pip install email_validator

Flask-Login 要求实现的用户模型方法

方法描述
is_authenticated()用户是否已登录
is_active()是否允许用户登录
is_anonymous()普通用户返回 False
get_id()

用户认证

from . import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash


class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    password_hash = db.Column(db.String(128))

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User %r>' % self.username


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

具体代码

git checkout 8e

Flask用户认证




大型程序结构

PythonFlask快速入门与进阶



FlaskWeb开发:基于Python的Web应用开发实战

git clone https://github.com/miguelgrinberg/flasky.git
cd flasky
git checkout 7a



阿里巴巴Java开发手册

工程结构部分



cookiecutter-flask



笔者项目结构

前端能对状态码 404 和 500 进行路由,前后端分离的话,应由前端渲染,对应 static 和 templates 文件夹就不需要了



开源项目




配置管理

JSON排序

JSON_SORT_KEYS 默认为 True,自动根据键名排序,能提高缓存性能。

from flask import Flask

app = Flask(__name__)
app.config['JSON_SORT_KEYS'] = False  # 禁用JSON自动排序


@app.route('/')
def index():
    data = {'b': 5, 'a': 4, 'c': 3}
    return data


if __name__ == '__main__':
    app.run()

访问:http://127.0.0.1:5000/




REST Web服务

版本

区分版本很重要,因为无法强制更新手机 APP,可让客户端的参数带上当前版本。



HTTP状态码

状态码描述
200OK成功
201Created成功并创建了新资源
400Bad request请求不可用
401Unauthorized未授权
403Forbidden无权访问
404Not found对应资源不存在
405Method not allowed不支持该方法
500Internal server error服务器内部错误

404 和 500 状态码可能会让客户端困惑,因此可以在错误处理程序中改写响应,这种技术称为内容协商,发送 JSON 格式响应。



认证

  • 基于密令认证:Flask-HTTPAuth,将密令包含在请求的 Authorization 中。
  • 基于令牌认证:发送登录密令获取认证令牌,有过期时间,通过令牌代替登录密令。




测试




记录慢查询

实现方案




部署

  1. 用 500 错误页面取代 Flask 交互式调试器,即错误栈跟踪,同时记录日志,发送邮件(SMTPHandler)




日志

Python 日志级别由小到大分别是:DEBUG < INFO < WARNING < ERROR < CRITICAL

Flask 默认日志级别同 Python,为 warning()

LogRecord 属性

记录接口请求参数,便于 Postman 请求,此处只解析常见的 mimetype

import json
import logging

from flask.logging import default_handler
from flask import Flask, request, has_request_context

app = Flask(__name__)
app.logger.setLevel(logging.INFO)


class RequestFormatter(logging.Formatter):
    def format(self, record):
        record.method = ''
        record.url = ''
        record.request_data = ''
        record.cookie = ''
        if has_request_context():
            record.method = request.method
            record.url = request.url

            mimetype = request.mimetype
            if mimetype == 'multipart/form-data':
                request_data = request.form
                request_data = '\n'.join(['{}:{}'.format(k, v) for k, v in request_data.items()])
            elif mimetype == 'application/x-www-form-urlencoded':
                request_data = json.loads(next(iter(request.form.keys())))
                request_data = '\n'.join(['{}:{}'.format(k, v) for k, v in request_data.items()])
            elif mimetype == 'application/json':
                request_data = request.data.decode()
            else:
                request_data = request.data.decode()
            record.request_data = request_data
            if record.request_data:
                record.request_data = '\n{}\n{}'.format(request.mimetype, record.request_data)

            record.cookie = ';'.join(['{}={}'.format(k, v) for k, v in request.cookies.items()])
            if record.cookie:
                record.cookie = '\nCookie:{}'.format(record.cookie)
        return super().format(record)


formatter = RequestFormatter('[%(asctime)s|%(method)s|%(url)s]%(request_data)s%(cookie)s')
default_handler.setFormatter(formatter)


@app.before_request
def before_request():
    """打印请求参数"""
    app.logger.info('')


@app.route('/', methods=['GET', 'POST'])
def ping():
    return 'pong\n'


if __name__ == '__main__':
    app.run()

测试

请求方法mimetype命令
GETcurl http://127.0.0.1:5000/
GETcurl "http://127.0.0.1:5000/?a=1&b=2"
POSTmultipart/form-datacurl -X POST "http://127.0.0.1:5000/" -F c=3 -F d=4
POSTapplication/x-www-form-urlencodedcurl -X POST "http://127.0.0.1:5000/" --data "{\"b\":2, \"c\":3}"
POSTapplication/jsoncurl -X POST -H "Content-Type: application/json" "http://127.0.0.1:5000/" --data "{\"b\":2, \"c\":3}"
POSTtext/plaincurl -X POST -H "Content-Type: text/plain" "http://127.0.0.1:5000/" --data "Hello"

效果

2022-06-30 22:36:25,597|GET|http://127.0.0.1:5000/
2022-06-30 22:36:28,488|GET|http://127.0.0.1:5000/?a=1&b=2
2022-06-30 22:36:31,235|POST|http://127.0.0.1:5000/
multipart/form-data
c:3
d:4
2022-06-30 22:36:33,731|POST|http://127.0.0.1:5000/
application/x-www-form-urlencoded
b:2
c:3
2022-06-30 22:36:36,931|POST|http://127.0.0.1:5000/
application/json
{"b":2, "c":3}
2022-06-30 22:36:40,723|POST|http://127.0.0.1:5000/
text/plain
Hello

普通日志

import logging

logger = logging.getLogger()


def f():
    logger.warning('hello')




下载文件

from flask import Flask, send_file

app = Flask(__name__)


@app.route('/download/')
def download():
    file = '测试.txt'
    with open(file, mode='w') as f:
        f.write('Hello World!')
    return send_file(file, as_attachment=True)


if __name__ == '__main__':
    app.run()

访问 http://127.0.0.1:5000/download/




上传文件




获取参数

POST:request.form.get(key, default=None, type=None)

GET:request.args.get(key, default=None, type=None)

所有:request.values.get(key, default=None, type=None)




重定向

from werkzeug.utils import redirect




性能分析




Python高并发服务部署——Nginx+Gunicorn+gevent+Flask+Supervisor




Flask 扩展

Flask-Bootstrap好看的页面

安装

pip install flask-bootstrap

templates/user.html

{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"
                    data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}
{% block content %}
<div class="container">
    <div class="page-header">
        <h1>Hello, {{ name }}!</h1>
    </div>
</div>
{% endblock %}

main.py

from flask import Flask, render_template
from flask_bootstrap import Bootstrap

app = Flask(__name__)
Bootstrap(app)


@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)


if __name__ == '__main__':
    app.run(debug=True)

访问 http://127.0.0.1:5000/user/XerCis




Flask-Moment本地化日期和时间

如果用户来自世界各地,需要统一时间单位,一般使用地理位置无关的协调世界时( Coordinated Universal Time, UTC),在渲染的时候转换为当地时间。

安装

pip install flask-moment

templates/index.html

{{ moment.include_moment() }}
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>

main.py

from datetime import datetime

from flask import Flask, render_template
from flask_moment import Moment

app = Flask(__name__)
moment = Moment(app)


@app.route('/')
def index():
    return render_template('index.html', current_time=datetime.utcnow())


if __name__ == '__main__':
    app.run(debug=True)




Flask-WTF表单验证和渲染

安装

pip install flask-wtf




Flask-SQLAlchemy管理数据库

安装

pip install flask-sqlalchemy




Flask-Migrate迁移数据库

安装

pip install flask-migrate




Flask-Mail电子邮件

安装

pip install flask-mail




Flask-Login用户认证

安装

pip install flask-login




Flask-HTTPAuth接口认证

REST Web 服务要求无状态,服务器再两次请求间不能记住客户端的任何信息。

可通过 HTTP 认证发送密令,将密令包含在请求的 Authorization 中。

安装

pip install flask-httpauth

可结合装饰器 auth.login_required()api.before_request() 进行所有路由的自动认证,甚至能认证角色。




Flask-Talisman安全HTTP

安装

pip install flask-talisman




Flask-RESTful构建RESTAPI

安装

pip install flask-restful




Flask-OAuth第三方登录

Flask-OAuthlib




Flask-OpenID开放式认证

安装

pip install flask-openid




Flask-WhooshAlchemy全文搜索

安装

pip install flask_whooshalchemy




Flask-Admin后台管理

安装

pip install flask-admin




Flask-Security安全机制

安装

pip install flask-security




Flask-JWT-Extended用户认证

安装

pip install flask-jwt-extended




Flask-Limiter接口频率限制

安装

pip install flask-limiter




Flask-Babel国际化

安装

pip install flask-babel




Flask-Caching缓存

安装

pip install flask-caching




Flask-DebugToolbar调试工具

安装

pip install flask-debugtoolbar




Flask-Redis

安装

pip install flask-redis




Flask-SQLAcodegen自动生成模型

安装

pip install flask-sqlacodegen




Flask-SSE向前端发数据

安装

pip install flask-sse




Flask-SocketIO实时双向通讯

安装

pip install flask-socketio




Flask-Uploads文件上传

安装

pip install flask-uploads




Flask-Dropzone拖拽上传

安装

pip install flask-dropzone




Flask-CKEditor富文本编辑器

安装

pip install flask-ckeditor




Flask-FlatPages代码高亮

安装

pip install flask-flatPages




Flask-Opentracing分布式跟踪

安装

pip install flask-opentracing




Flasgger文档生成

安装

pip install flasgger




更多扩展




自动化部署




自动化测试




日志处理




高级框架

APIFlask

安装

pip install apiflask

代码

from apiflask import APIFlask, Schema, abort
from apiflask.fields import Integer, String
from apiflask.validators import Length, OneOf

app = APIFlask(__name__)

pets = [
    {'id': 0, 'name': 'Kitty', 'category': 'cat'},
    {'id': 1, 'name': 'Coco', 'category': 'dog'}
]


class PetInSchema(Schema):
    """入参"""
    name = String(required=True, validate=Length(0, 10))
    category = String(required=True, validate=OneOf(['dog', 'cat']))


class PetOutSchema(Schema):
    """出参"""
    id = Integer()
    name = String()
    category = String()


@app.get('/')
def say_hello():
    # 返回dict相当于使用jsonify()
    return {'message': 'Hello!'}


@app.get('/pets/<int:pet_id>')
@app.output(PetOutSchema)
def get_pet(pet_id):
    if pet_id > len(pets) - 1:
        abort(404)
    # 也可以直接返回ORM/ODM实例,会自动将对象序列化为JSON格式
    return pets[pet_id]


@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True), location='json_or_form')
@app.output(PetOutSchema)
def update_pet(pet_id, data):
    # 经过验证和解析的输入数据将作为dict注入视图函数
    if pet_id > len(pets) - 1:
        abort(404)
    for attr, value in data.items():
        pets[pet_id][attr] = value
    return pets[pet_id]


if __name__ == '__main__':
    app.run()

测试

curl http://127.0.0.1:5000/
curl http://127.0.0.1:5000/pets/0
curl -X PATCH http://127.0.0.1:5000/pets/0 --data "{'name':'Butterfly', 'category':'cat'}"

文档





推荐阅读

  1. Flask Web开发实战番外
  2. HelloFlask - Flask 资源集合地
  3. 设计好接口的36个锦囊




后台任务

import time
import threading

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return '<h1>Hello World!</h1>'


def start_runner():
    def start_loop():
        while True:
            print('执行后台任务...')
            time.sleep(3)

    thread = threading.Thread(target=start_loop)
    thread.start()


if __name__ == '__main__':
    start_runner()
    app.run()




解决循环引用




Flask+RocketMQ

不建议 Flask 和 RocketMQ 放在一起

消费者

extension.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

model.py

from extension import db


class Record(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    message_id = db.Column(db.String, unique=True)  # 唯一索引用于消费消息幂等性
    message_body = db.Column(db.String)

mq.py

import time
import logging

from rocketmq.client import PushConsumer, ConsumeStatus

from model import Record
from extension import db

logger = logging.getLogger()


def callback(message):
    """消费逻辑"""
    print(message.id, message.body)

    from app import app
    with app.app_context():
        try:
            record = Record(message_id=message.id, message_body=message.body.decode('utf-8'))
            db.session.add(record)
            db.session.commit()
        except Exception as e:  # 可能会重复消费
            print(e)
            db.session.rollback()
        n = db.session.query(Record.id).count()
        print(f'现在有{n}条记录')
    return ConsumeStatus.CONSUME_SUCCESS  # 一定要返回


def run():
    from uuid import uuid4
    logger.warning('开始消费')
    consumer = PushConsumer(str(uuid4()))
    # consumer = PushConsumer('CID_XXX')
    consumer.set_name_server_address('127.0.0.1:9876')
    consumer.subscribe('TestTopic', callback)
    consumer.start()
    while True:  # 一定要加这两句,否则Segmentation fault
        time.sleep(30)
    consumer.shutdown()

app.py

import os
import threading

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from mq import run
from model import Record
from extension import db

if os.path.exists('test.db'):
    os.remove('test.db')


def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
    # app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
    db.init_app(app)
    return app


app = create_app()

with app.app_context():
    db.create_all()

threading.Thread(target=run).start()


@app.route('/')
def ping():
    return 'pong\n'


if __name__ == '__main__':
    app.run()




遇到的坑

1. The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a list.

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return [1, 2, 3]


if __name__ == '__main__':
    app.run()

Flask 不支持返回 list

from flask import jsonify
return jsonify([1, 2, 3])

2. 报错RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context().  See the
documentation for more information.

循环引用

3. 前端报错Access to XMLHttpRequest at ‘xxx/xxx’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: Redirect is not allowed for a preflight request.

请求 url 加上 /

4. 跨域请求下前端获取不到 Response Headers

response.headers['Access-Control-Expose-Headers'] = 'Header名1,Header名2'

跨源资源共享(CORS) - HTTP | MDN

5. 设置了跨域后,Chrome浏览器依然报错Access to XMLHttpRequest at from origin has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space private

Chrome 浏览器访问 chrome://flags/#block-insecure-private-network-requests

Edge 浏览器访问 edge://flags/#block-insecure-private-network-requests

设置 Block insecure private network requests. 为 Disabled

Flask, add Access-Control-Allow-Private-Network true to CORS preflight

该问题本人在测试环境中遇到,将 http 全部换成 https 即可解决

6. 其他系统调用本系统报错514 Frequency capped,后端日志没有收到请求,且Postman可正常请求

找运维帮忙看,可能是域名解析出了问题




参考文献

  1. Flask Documentation
  2. Flask 中文文档
  3. Python Flask快速入门与进阶
  4. Flask Web 开发:基于 Python 的 Web 应用开发实战
  5. Flask Web Development GitHub
  6. Flask-Bootstrap Documentation
  7. Python数据库ORM框架——SQLAlchemy
  8. Python通过163和QQ收发邮件
  9. flask mail ConnectionRefusedError: WinError 10061 由于目标计算机积极拒绝,无法连接。
  10. 有哪些使用Python和Flask搭建的博客、论坛等开源项目推荐?
  11. Python Flask request获取参数几种方式
  12. 请不要把 Flask 和 FastAPI 放到一起比较
  13. Flask 中最受欢迎的扩展插件
  14. postman接口测试工具的基本使用
  15. HelloFlask - Flask 资源集合地
  16. JavaWeb 分层结构 总结
  17. 分层明确高度定制化的 Python Flask MVC
  18. Python:改造flask接口服务提供统一格式的返回数据
  19. Change Flask logs from INFO to DEBUG
  20. 玩转Python日志模块(logging)
  21. Flask request 属性详解
  22. flask获取post请求参数
  23. Flask - How do I read the raw body in a POST request
  24. tenable/flask-logging-demo
  25. flask踩坑记录:flask的响应结果总是按首字母排序
  26. 在Flask启动时启动后台任务
  27. Circular import of db reference using Flask-SQLAlchemy and Blueprints
  28. 一个Flask应用运行过程剖析
  29. 跨域资源共享 CORS 详解
  30. 跨源资源共享(CORS) - HTTP | MDN
  31. 如何解决Chrome禁止发送不安全的内网网络请求
  32. CORS跨域请求中Preflight Request
  33. Flask, add Access-Control-Allow-Private-Network true to CORS preflight
  34. How to fix a website with blocked mixed content
  35. 后端工程师需要了解的跨域知识
  36. 浏览器踩坑:浏览器访问已发布服务器的网址显示跨域,但微信浏览器可以正常访问,别人的浏览器也可以正常访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XerCis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值