Flask 文档
flask入门教程及实战:.html
Flask Web 开发入门:.md
开源的 Web 框架哪个快:
GitHub 上 web 框架性能基准测试
:
:/result
Sanic
官方文档:
python web 框架 Sanic 比Flask还好用?:
Sanic 是一个和类Flask 的基于Python3.5+的web框架,它编写的代码速度特别快。
除了像Flask 以外,Sanic 还支持以异步请求的方式处理请求。这意味着你可以使用新的 async/await 语法,编写非阻塞的快速的代码
quart
:
示例:
from quart import Quart, render_template, websocketapp = Quart(__name__)ute("/")
async def hello():return await render_template("index.html")ute("/api")
async def json():return {"hello": "world"}@app.websocket("/ws")
async def ws():while True:await websocket.send("hello")await websocket.send_json({"hello": "world"})if __name__ == "__main__":app.run()
FastAPI
要在Flask应用中使用asyncio,我们需要先创建一个事件循环。在Flask的上下文中,我们可以使用current_app
对象来访问应用实例。我们可以通过在应用实例上调用app.before_first_request
装饰器来创建事件循环。
from flask import Flask, current_appapp = Flask(__name__)@app.before_first_request
def before_first_request():loop = _event_loop()current_app.loop = loop
在Flask应用中创建了事件循环,我们就可以在视图函数中使用它了。让我们看一个简单的例子,在视图函数中进行异步操作。
from flask import Flask, current_app, jsonifyapp = Flask(__name__)ute('/')
def index():loop = current_app.loopasync def async_task():# 异步操作result = await some_async_function()return resultresult = loop.run_until_complete(async_task())return jsonify({'result': result})
使用loop.run_until_complete()
方法来运行异步任务,并等待其完成。
除了在视图函数中使用asyncio事件循环,我们还可以在后台任务中使用它。Flask支持使用APScheduler这样的库来创建后台任务。
安装:pip install apscheduler
from flask import Flask
from apscheduler.schedulers.background import BackgroundSchedulerapp = Flask(__name__)
scheduler = BackgroundScheduler()ute('/')
def index():return 'Hello, Flask!'def scheduled_task():loop = current_app.loopasync def async_task():# 后台任务中的异步操作result = await some_async_function()return resultresult = loop.run_until_complete(async_task())print(result)# 每隔10秒执行一次后台任务
scheduler.add_job(scheduled_task, 'interval', seconds=10)
scheduler.start()if __name__ == '__main__':app.run()
在 Flask 应用中创建了一个后台任务scheduled_task
,它会每隔10秒执行一次。在后台任务中,我们使用和视图函数中一样的方法来使用asyncio事件循环。
示例 :
import threading
import asynciofrom flask import Flask, jsonifyprint(f"In flask global level: {threading.current_thread().name}")
app = Flask(__name__)ute("/toy", methods=["GET"])
def index():print(f"Inside flask function: {threading.current_thread().name}")loop = _event_loop()result = loop.run_until_complete(hello())return jsonify({"result": result})async def hello():await asyncio.sleep(1)return 1if __name__ == "__main__":app.run(host="0.0.0.0", port=4567, debug=False)
安装 Flask:sudo pip install Flask
新建 hello_flask.py。( 文件名不是 flask.py ,因为这将与 Flask 本身冲突 )
from flask import Flaskapp = Flask(__name__)ute("/")
def hello_world():return "Hello World!"if __name__ == "__main__":app.run()
访问 127.0.0.1:5000/ ,你会 看见 Hello World 问候。
按 Ctrl+C 关闭服务器。如果想要外部访问,可以设置 app.run(host='0.0.0.0')
自动加载技术--reload
自动加载技术在web开发中应用很广泛,设想,你正在编写一个服务,每写完一段代码,都需要进行调试,有时仅仅是修改了一行代码,如果为了测试代码,不停的重启服务,着实让人烦恼。
自动加载技术会监测项目里文件的修改情况,一旦发现文件有修改,就会重新加载这个文件,相当于重新import这个模块,这样,你的每一次改动都会在保存后生效而不需要你重启服务,是不是很爽,关于这个技术,我会专门写文章来介绍。
开启 flask 自动加载只需要将 debug 参数设置为 True,flask 将以调试模式启动。然后修改代码,观察服务,一定会重新启动,出现类似下面的提示信息
* Debugger pin code: 194-794-301
* Detected change in '/Users/zhangdongsheng/finup/experiment/studyflask/simple_app/app.py', reloading
* Restarting with stat
准确来说,一个 Flask后端应用,并不等同于一个完整的Web服务,一个完整的Web服务如下:
Web服务器接收浏览器发出的HTTP请求,并经由WSGI标准接口与APP进行通信,APP处理完请求之后,再将响应经由WSGI处理,最终由Web服务器发送给前端。
Flask应用就是APP的角色,而Server通常会由另一个组件来实现,当通过 app.run() 启动 Flask 应用时,其实是 Flask 内置了一个仅用于开发调试的低性能、简易的 Server,这也是为什么不建议直接在生产环境使用 app.run() 来部署 Flask 应用(不建议并不是不能)。
WSGI
Web 服务器网关接口(Python Web Server Gateway Interface,缩写为 WSGI)是为 Python 语言定义的 Web服务器 和 Web应用程序 或 框架 之间的一种简单而通用的接口。
上面的 Web Server 是由单独的组件来充当,那么 Server 在与 APP 交互过程中,就需要遵循一种规范,这个规范就是 WSGI。
通俗的讲:充当 Web Server 角色的可以有很多组件、框架;也有很多框架可以充当 WebApp 的角色,但只要它们双方都遵守WSGI规范,那么编程人员就可以用任意一个WebServer 组件去和任意一种 WebApp 对接。
web server 承担端口监听和接受请求的任务
web framework 主要承担路由,业务逻辑等任务
一般 web framework 库( 比如 flask ),主要部分是 web framework,同时也自带一个性能不咋滴的 web server,这样在开发和调试时可以直接运行起来看看效果,但是在生产环境中自带的 web server 性能就不够用了。
gunicorn 和 uwsgi 是单独实现的性能强劲的 web server,这种单独实现的 web server 和 web framework 配合起来用就可以提高整个应用的性能。
gunicorn、uwsgi 是 web server,flask 或者 bottle 是 web framework。gevent 是 async io。
uwsgi 是 C语言实现,性能更好。gunicorn 是纯 Python 实现。
Flask、Gunicorn、uWSGI、UWSGI、nginx 等关系:
gunicorn / uwsgi 都实现了 wsgi 协议(python web server gateway interface)。它们做的事情是协议转换,协议的一头是web app(如flask, django等framework的app),另一头是web server(如apache, nginx等),gunicore/uwsgi在默认的情况下都是同步模型,但都比通常的web framework实现得好。
gevent是利用协程(greenlet库)实现的一个高性能异步IO的库,它做的事情就是实现异步模型。
gunicorn/uwsgi可以配合gevent实现异步模型,并发能力进一步提高(在没有gevent的情况下,并发能力其实来自于web server)。
以上说的都是他们的本职工作,但实际上它们都做了更多的事,如:
gunicorn/uwsgi都实现了自己的web server,可以不依赖nginx等专业server独立提供web服务;
gevent实现了wsgi,可以不依赖gunicorn/uwsgi等完成协议转换(异步模型下的)。
Flask 自带的 HTTP 服务器能够跑起来,nginx + flask 也没啥问题,关键是如果要提升服务的并发能力,你要怎么处理,部署N个Flask进程,通过nginx代理到N个不同服务端口去,启停更新服务时,操作N个Flask进程?
使用 Gunicorn 最本质的原因就是提升服务处理能力,比如,使用 Gunicorn 部署多个 Flask 应用实例,提升服务并发处理能力;使用 Gunicorn + gevent worker,monkeypatch 掉 Python 原生网路库,进一步提升处理能力,遇到 IO 等待时,挂起当前请求,处理其它请求。
:
部署项目gunicorn、uwsgi性能,测试对比:优缺点:
通常都是用 gunicorn 来解决 flask 后端部署并发的问题, 然而觉得自启多进程是为更优雅的高并发方式。这样就不需要gunicorn了。也没有额外的第三方部署工作,于是有了以下flask + gevent + multiprocess + wsgi的测试
gevent 是一个基于协程的 Python 网络库,它提供了异步 I/O 和协作式多任务处理的功能。pywsgi
模块是 gevent 的一个子模块,它提供了用于创建 WSGI 服务器的功能。WSGI (Web Server Gateway Interface) 是 Python 中定义的一种标准接口,用于将 Web 服务器和 Python Web 应用程序连接起来。
Flask 中使用 gevent 和 pywsgi:
from flask import Flaskapp = Flask(__name__)ute('/')
def hello():return 'Hello, World!'if __name__ == '__main__':from gevent.pywsgi import WSGIServerserver = WSGIServer(('0.0.0.0', 8000), app)server.serve_forever()
程序代码 app.py
from gevent import monkey
from gevent.pywsgi import WSGIServermonkey.patch_all()import datetime
import os
from multiprocessing import cpu_count, Process
from flask import Flask, jsonifyapp = Flask(__name__)ute("/cppla", methods=['GET'])
def function_benchmark():return jsonify({"status": "ok","time": w().strftime('%Y-%m-%d %H:%M'),"pid": os.getpid()}), 200def run(multi_process=None):if not multi_process:WSGIServer(('0.0.0.0', 8080), app).serve_forever()else:multi_server = WSGIServer(('0.0.0.0', 8080), app)multi_server.start()def server_forever():multi_server.start_accepting()multi_server._stop_event.wait()for i in range(cpu_count()):p = Process(target=server_forever)p.start()if __name__ == "__main__":# 单进程 + 协程run(False)# 多进程 + 协程# run(True)
run() 方法适用于启动本地的开发服务器,但是 你每次修改代码后都要手动重启它。这样并不够优雅,而且 Flask 可以做到更 好。如果你启用了调试支持,服务器会在代码修改后自动重新载入,并在发生 错误时提供一个相当有用的调试器。
有两种途径来启用调试模式。一种是直接在应用对象上设置:
app.debug = True
app.run()
另一种是作为 run 方法的一个参数传入:
app.run(debug=True)
两种方法的效果完全相同。
注意:尽管交互式调试器在允许 fork 的环境中无法正常使用(也即在生产服务器 上正常使用几乎是不可能的),但它依然允许执行任意代码。这使它成为一 个巨大的安全隐患,因此它 绝对不能用于生产环境 。
想用其它的调试器? 参见 调试器操作 。
现代 Web 应用的 URL 十分优雅,易于人们辨识记忆
如下,route() 装饰器把一个函数绑定到对应的 URL 上。
from flask import Flaskapp = Flask(__name__)ute("/")
def index():return "Index Page"ute("/hello")
def hello():return "Hello World"if __name__ == "__main__":app.run()
可以构造含有动态部分的 URL,也可以在一个函数上附着 多个规则。
flask 在通过route装饰器添加路由时,endpoint参数默认是所装饰函数的名称。
在 Flask 中,endpoint
是用来给路由函数或者视图函数起一个唯一的名称标识符,以便在其他地方引用该端点。下面是一些 Flask 中 endpoint
用法示例:
from flask import Flask, redirect, url_forapp = Flask(__name__)ute('/', endpoint='endpoint_home')
def home():return 'Home Page'ute('/about', endpoint='endpoint_about')
def about():return 'About Page'# 在其他视图函数中引用端点
ute('/redirect')
def redirect_to_home():return redirect(url_for('endpoint_home'))if __name__ == '__main__':app.run(debug=True)pass
我们为 home
和 about
视图函数分别指定了 endpoint
参数,用于给它们起一个唯一的标识符。然后在 redirect_to_home
视图函数中,使用 url_for
函数结合端点名 'endpoint_home'
来生成对应的 URL。
使用蓝图(Blueprints)时的端点命名空间:
from flask import Flask, redirect, url_for, Blueprintapp = Flask(__name__)bp_1 = Blueprint('users', __name__, url_prefix='/users')@ute('/profile', endpoint='profile')
def profile():return 'User Profile'# 在其他视图函数中引用端点
ute('/redirect')
def redirect_to_profile():return redirect(url_for('users.profile'))ister_blueprint(bp_1)if __name__ == '__main__':app.run(debug=True)pass
这个示例中,我们定义了一个名为 'users'
的蓝图,并为 profile
视图函数指定了 endpoint='profile'
。然后在 redirect_to_profile
视图函数中,使用 'users.profile'
来引用蓝图下的端点。
上述示例中,我们分别使用了单个路由和蓝图来演示 endpoint
的用法。无论是单个路由还是蓝图,都可以通过指定 endpoint
参数来为视图函数提供唯一的标识符,并在其他地方引用它们。这样可以使代码更加具有可读性和可维护性。
要给 URL 添加变量部分,你可以把这些特殊的字段标记为 <variable_name> , 这个部分将会作为命名参数传递到你的函数。规则可以用 <converter:variable_name> 指定一个可选的转换器。
示例:
from flask import Flaskapp = Flask(__name__)ute("/user/<username>")
def show_user_profile(username):# show the user profile for that userreturn f"User {username}" ute("/post/<int:post_id>")
def show_post(post_id):# show the post with the given id, the id is an integerreturn f"Post {post_id}"if __name__ == "__main__":app.run()
转换器有下面几种:
int | 接受整数 |
float | 同 int ,但是接受浮点数 |
path | 和默认的相似,但也接受斜线 |
唯一 URL / 重定向行为
Flask 的 URL 规则基于 Werkzeug 的路由模块。这个模块背后的思想是基 于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。
以这两个规则为例:
ute('/projects/')
def projects():
return 'The project page'ute('/about')
def about():
return 'The about page'
虽然它们看起来着实相似,但它们结尾斜线的使用在 URL 定义 中不同。
flask上下文 request,session ,g的关系及区别(转):
实际上所谓的上下文,就是在该场景下,包括了一次请求相关的信息,包括了从客户(一般是浏览器)发送过来的数据,例如,登陆时使用的用户名密码;以及在中间处理过程中生成的数据,例如,每次请求时我们可能会需要新建一个数据库链接。
Flask 会在接收每次请求的时候将参数自动转换为相应的对象,也就是 request、session,一般来说上下文传递可以通过参数进行,这也就意味这每个需要该上下文的函数都要多增加一个入参,为了解决这一问题,Flask 提供了一个类似于全局变量的实现方式(如下会讲到这一参数是线程安全的)。
在多线程服务器中,通过线程池处理不同客户的不同请求,当收到请求后,会选一个线程进行处理,请求的临时对象(也就是上下文)会保存在该线程对应的全局变量中(通过线程 ID 区分),这样即不干扰其他线程,又使得所有线程都可以访问。
请求上下文:保存客户端和服务器交互的数据。包括 request (请求对象,封装了 HTTP 请求的内容)、session (用于存储请求之间需要记住的值)。
请求上下文对象有:request、session
request
封装了HTTP请求的内容,针对的是http请求。
举例:user = ('user'),获取的是get请求的参数
session
用来记录请求会话中的信息,针对的是用户信息。
举例:session['name'] = user.id,可以记录用户信息。还可以通过('name')获取用户信息。
示例:
from flask import Flask,current_app,url_forapp = Flask(__name__)#应用上下文
#如果在视图函数外部访问,则必须手动推入一个app上下文到app上下文栈中
with app.app_context():print(current_app.name) #context_demoute('/')
def index():# 在视图函数内部可以直接访问current_app.nameprint(current_app.name) #context_demoreturn 'Hello World!'ute('/list/')
def my_list():return 'my_list'# 请求上下文
st_request_context():# 手动推入一个请求上下文到请求上下文栈中# 如果当前应用上下文栈中没有应用上下文# 那么会首先推入一个应用上下文到栈中print(url_for('my_list'))if __name__ == '__main__':app.run(debug=True)
字面意思是 "应用上下文", 但它不是一直存在的,它只是 request context 中的一个对 app 的代理人,所谓 local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app、g
current_app:应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name 打印当前app的名称,也可以在current_app中存储一些变量,例如:
示例:
from flask import Flask,current_appapp = Flask(__name__)#如果在视图函数外部访问,则必须手动推入一个app上下文到app上下文栈中
#第一种方法
# app_context = app.app_context()
# app_context.push()
# print(current_app.name)#第二种方法
with app.app_context():print(current_app.name) #context_demoute('/')
def index():# 在视图函数内部可以直接访问current_app.nameprint(current_app.name) #context_demoreturn 'Hello World!'if __name__ == '__main__':app.run(debug=True)
g 之前是在请求上下文中的,现在被迁移到了应用上下文。
在应用上下文中,通过 flask.current_app、g 会返回当前的应用对象上下文,其中包含了当前的应用对象。
注意:g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的 thread id 区别
g 相当于 "单次请求" 中的 "全局变量",只能在单次请求中调用,和其他请求是互相隔离的
可以参考上下文管理部分,g的创建与销毁流程理解g 能做什么? 可以在单次请求中定义一些值和操作,随着本次请求结束而销毁;如,权限管理
示例 1:
from flask import Flask, gapp = Flask(__name__)ute("/")
def index():# g保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过线程id区别g.test_msg = "g是一个应用上下文,但是只在单次请求中有效"say_hello()return "index page"def say_hello():test_msg = g.test_msgprint(f"Hello {test_msg}")if __name__ == '__main__':app.run()
示例 2
from flask import Flask, gapp = Flask(__name__)ute('/index1')
def index1():g.name = 'tom'print(g.name)return {'data': 'index1页面'}ute('/index2')
def index2():print(g.name)return {'data': 'index2页面'}if __name__ == '__main__':app.run()
访问 index1,正常返回结果
访问 index2,直接报错,因为在 index2 这个请求中, 从来没有设置 g.name 这个属性,所以报错。
示例 3
from flask import Flask, gapp = Flask(__name__)@app.before_request
def bfr():g.name = 'jack'ute('/index1')
def index1():print(g.name)return {'data': 'index1页面'}ute('/index2')
def index2():print(g.name)return {'data': 'index2页面'}if __name__ == '__main__':app.run()
访问 index1、index2 都正常返回,因为每次请求前都会通过 bfr 函数设置 g.name 属性
示例:
import sqlite3
from flask import gdef get_db():db = getattr(g, '_database', None)if db is None:db = g._database = connect_to_database()return dbardown_appcontext
def teardown_db(exception):db = getattr(g, '_database', None)if db is not None:db.close()
"应用上下文" 一般会被认为是一个应用的全局变量,所有请求都可以访问修改其中的内容;
而请求上下文则是请求内可访问的内容。
但事实上,这两者并不是全局与局部的关系,它们都处于一个请求的局部中,每个请求的 g 都是独立的,并且在整个请求内都是可访问修改的。
之所以有应用上下文,是因为 flask 设计之初,就是要支持多个应用。
为什么上下文需要放在栈中?
1.应用上下文:
Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存,如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1那么app应该从栈中删除,方便其他代码使用下面的app。
2.应用上下文:
如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下文,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。
线程隔离的 g对象
g对象是在整个Flask应用运行期间都是可以使用的,并且它也是跟request一样是线程隔离的。这个对象是专门用来存储开发者自定义的一些数据,方便在整个Flask程序中都可以使用。一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不是通过传参的形式,这样更加方便。
首先我们看看 request 是如何实现的,实际上之所以有 request 就是为了在多线程(或者协程)的环境下,各个线程可以使用各自的变量,不至于会混乱,接下来我们看看具体是如何实现的。
Python 提供了同样类似的线程安全变量保存机制,也就是 threading.local() 方法,而在 flask 中,使用的是 werkzeug 中的 Local 实现的,详细可以参考 werkzeug.pocoo/docs/local 。
总体来说,werkzeug 提供了与 threading.local() 相同的机制,不过是 threading 只提供了线程的安全,对于 greenlet 则无效。
flask只是一个框架,到底多线程还是单线程,取决于你如何部署它。
flask内置了一个服务器,它不能用于生产环境,只能用于开发测试环境,这个服务器默认情况下是开启多线程的,下面这段代码可以演示这种情况
import threading
import time
from flask import Flaskapp = Flask(__name__)count = 0ute('/')
def hello_world():global countcount += 1if count % 2 == 1:print(threading.current_thread().ident, 'sleep 10')time.sleep(10)else:print(threading.current_thread().ident, 'no sleep')return 'Hello World!'if __name__ == '__main__':app.run()
hello_world 函数在响应请求时会输出线程的唯一标识,如果是第奇数个请求,则 sleep 10秒钟,使用curl 命令连续两次发出请求。执行结果:
这里千万不要用浏览器来做实验,两次请求都是相同的 url,浏览器可能会进行优化导致两次请求使用相同的 socket 连接。
如果 flask 内置的服务器使用的是多线程,那么第一个请求将延迟10秒钟返回,第二个请求则立马返回结果,不受第一次请求的影响,实验结果与前面的分析是一致的。
如果你希望 flask 用单线程来处理请求,那么需要设置 threaded 参数
if __name__ == '__main__':app.run(threaded=False)
进入 run 函数,在 1181 行可以看到 threaded 默认值是 True
uwsgi 是应用非常广泛的wsgi服务器,它可以指定进程的数量和线程的数量,借用其官方文档上的一个例子
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
这个配置将启动4个进程,每个进程启动2个线程来提供服务,这意味着最多的时候可以同时服务8个请求。如果flask应用希望自己可以启动多线程,那么uwsgi需要配置 enable-threads 等于True,但如果你已经配置了--threads,且大于1,那么enable-threads默认开启。
python 常用的 web 框架,诸如 flask,django,在生产部署时,都会选择多进程的部署方式,选用的中间件多为uwsgi或者gunicorn。
如果项目里使用了数据库,那么就要考虑数据库连接在多进程下的一些问题,这里以mysql数据库为例。
python连接mysql的客户端驱动库有很多种,例如pymysql,它们都提供了数据库连接池,连接池是多线程安全的,多进程下并不安全。
多线程的安全,是通过线程锁解决的,这非常容易做到,而多进程加锁则并不容易。
我所谓的多进程,是由主进程fork出来的子进程,如果主进程里创建了数据库连接池,随后fork出子进程,子进程在获取数据库连接时,两个子进程就有可能获得同一个数据库连接,这样就会引发问题。
一个数据库连接,本质上就是一个socket连接,建立socket连接后,得到一个打开的socket对象,当两个进程都用这同一个socket对象发送和接收数据时,就会引发异常。
不论是用uwsgi还是gunicorn,其原理都是相似的,创建出app后,fork子进程,这样做的目的是提供web服务的响应能力,work的数量需要合理配置。
对于这种部署方式,我一直都担心出现子进程共用同一个数据库连接的问题,直到最近,猛然间找到了问题的本质。
目前所用的数据库连接驱动库,都有一个惰性连接的特性。如果你设置连接池的大小是10,那么当程序启动后,连接池并没有真的被建立,只有当程序进行一次数据库操作时,才会真的去建立连接。当连接数量不够时,才会去新建连接,如果连接池里有空闲的连接,会直接拿来使用。
因此,只要保证在创建出app以后,不使用数据库连接进行任何操作,而是等到有真实的请求到来以后再进行数据库操作即可避免多进程共用数据库连接的问题。
只要父进程没有对数据库进行操作,父进程便不会创建数据库连接池。
当请求真实到达时,已经完成了fork动作,此时的子进程,并没有从父进程那里继承数据库连接池,因为父进程自己也没有创建连接池。
请求打到某个字进程,这个子进程在进行数据库操作时创建只属于自己的数据库连接池,不会受到其他子进程的干扰。
多进程部署模式下,如果你不开启多线程,那么一个子进程便只有一个线程,此时,你设置连接池的大小为1即可。设置的更大,也不会创建出更多的连接,因为对于单个子进程来说,有一个数据库连接就已经足够了。
为了提高响应能力,你开启线程,uwsgi和gunicorn都允许你这样做。那么你要根据线程的数量来设置连接池的大小,与其相等即可,多了也同样不起作用。
如果 Flask 能匹配 URL,那么 Flask 可以生成它们吗?当然可以。用 url_for() 来给指定的函数构造 URL。它接受函数名作为第一个 参数,也接受对应 URL 规则的变量部分的命名参数。未知变量部分会添加到 URL 末尾作为查询参数。
示例:
from flask import Flask, url_forapp = Flask(__name__)ute("/")
def index():passute("/login")
def login():passute("/user/<username>")
def profile(username):st_request_context():print(url_for("index"))print(url_for("login"))print(url_for("login", next="/"))print(url_for("profile", username="John Doe"))
执行结果:
/
/login
/login?next=/
/user/John%20Doe
通过 test_request_context 方法可以和 Python 的 shell 进行交互,它依然会告诉 Flask 要 表现为正在处理一个请求。
从 Shell 创建一个合适的上下文,最简单的方法是使用 test_request_context 方法,此方法 会创建一个 RequestContext 对象:
>>> ctx = st_request_context()
一般来说,您可以使用 with 声明来激活这个请求对象, 但是在终端中,调用 push() 方法和 pop() 方法 会更简单:
>>> ctx.push()
从这里往后,您就可以使用这个请求对象直到您调用 pop 方法为止:
>>> ctx.pop()
仅仅创建一个请求上下文,您仍然不能运行请求发送前通常会运行的代码。 如果您在将连接数据库的任务分配给发送请求前的函数调用,或者在当前 用户并没有被储存在 g 对象里等等情况下,您可能无法 访问到数据库。
您可以很容易的自己完成这件事,仅仅手动调用 preprocess_request() 函数即可:
>>> ctx = st_request_context()
>>> ctx.push()
>>> app.preprocess_request()
请注意, preprocess_request() 函数可能会返回 一个响应对象。这时,忽略它就好了。
要关闭一个请求,您需要在请求后的调用函数(由 process_response() 函数激发)运行之前耍一些小小的把戏:
>>> app.process_sponse_class())
<Response 0 bytes [200 OK]>
>>> ctx.pop()
被注册为 teardown_request() 的函数将会在 上下文环境出栈之后自动执行。所以这是用来销毁请求上下文(如数据库 连接等)资源的最佳地点。
如果您喜欢在 Shell 里实验您的新点子,您可以创建一个包含你想要导入交互式 回话中的东西的的模块。在这里,您也可以定义更多的辅助方法用来完成一些常用的 操作,例如初始化数据库、删除一个数据表等。
把他们放到一个模块里(比如 shelltools 然后在 Shell 中导入它):
>>> from shelltools import *
HTTP (与 Web 应用会话的协议)有许多不同的访问 URL 方法。默认情况下,路 由只回应 GET 请求,但是通过 route() 装饰器传递 methods 参数可以改变这个行为。
ute('/login', methods=['GET', 'POST'])
def login():hod == 'POST':do_the_login()else:show_the_login_form()
HTTP 方法
在 HTML4 和 XHTML1 中,表单只能以 GET 和 POST 方法提交到 服务器。但是 JavaScript 和未来的 HTML 标准允许你使用其它所有的方法。此 外,HTTP 最近变得相当流行,浏览器不再是唯一的 HTTP 客户端。比如,许多版 本控制系统就在使用 HTTP。
动态 web 应用也会需要静态文件,通常是 CSS 和 JavaScript 文件。理想状况下, 你已经配置好 Web 服务器来提供静态文件,但是在开发中,Flask 也可以做到。 只要在你的包中或是模块的所在目录中创建一个名为 static 的文件夹,在应用 中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 'static' 端点名:
url_for('static', filename='style.css')
这个文件应该存储在文件系统上的 static/style.css 。
用 Python 生成 HTML 十分无趣,而且相当繁琐,因为你必须手动对 HTML 做转 义来保证应用的安全。为此,Flask 配备了 Jinja2 模板引擎。
你可以使用 render_template() 方法来渲染模板。你需要做的一 切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何 渲染模板的简例:
from flask import Flask, url_for
from flask import render_templateapp = Flask(__name__)ute("/hello/")
ute("/hello/<name>")
def hello(name=None):return render_template("hello.html", name=name)
Flask 会在 templates 文件夹里寻找模板。
关于模板,你可以发挥 Jinja2 模板的全部实例。更多信息请见 Jinja2 模板文档 。
模板实例:
<!doctype html>
<title>Hello from Flask</title>
{% if name %}<h1>Hello {{ name }}!</h1>
{% else %}<h1>Hello World!</h1>
{% endif %}
在模板里,你也可以访问 request 、 session 和 g [1] 对象, 以及 get_flashed_messages() 函数。
模板继承让模板用起来相当顺手。如欲了解继承的工作机理,请跳转到 模板继承 模式的文档。最起码,模板继承能使特定元素 (比如页眉、导航栏和页脚)可以出现在所有的页面。
自动转义功能默认是开启的,所以如果 name 包含 HTML ,它将会被自动转 义。如果你能信任一个变量,并且你知道它是安全的(例如一个模块把 Wiki 标 记转换为 HTML),你可以用 Markup 类或 |safe 过滤 器在模板中把它标记为安全的。在 Jinja 2 文档中,你会看到更多的例子。
这里是一个 Markup 类如何使用的简单介绍:
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
u'Marked up xbb HTML'
在 0.5 版更改: 自动转义不再在所有模板中启用。下列扩展名的模板会触发自动转义: .html 、 .htm 、.xml 、 .xhtml 。从字符串加载 的模板会禁用自动转义。
不确定 g 对象是什么?它允许你按需存储信息, 查看( g )对象的文档和 在 Flask 中使用 SQLite 3 的文 档以获取更多信息。 |
对于 Web 应用,客户端发送给服务器 的数据交互至关重要。在 Flask 中 由全局的 request 对象来提供这些信息。如果你有一定的 Python 经验,你会好奇,为什么这个对象是全局的,为什么 Flask 还能保证 线程安全。答案是环境作用域:
理解其工作机制及如何利用环境局部变量实现自动化测试
Flask 中的某些对象是全局对象,但却不是通常的那种。这些对象实际上是特定 环境的局部对象的代理。虽然很拗口,但实际上很容易理解。
想象一下处理线程的环境。一个请求传入,Web 服务器决定生成一个新线程( 或者别的什么东西,只要这个底层的对象可以胜任并发系统,而不仅仅是线程)。 当 Flask 开始它内部的请求处理时,它认定当前线程是活动的环境,并绑定当前的应用和 WSGI 环境到那个环境上(线程)。它的实现很巧妙,能保证一个应用调用另一个应用时不会出现问题。
所以,这对你来说意味着什么?除非你要做类似单元测试的东西,否则你基本上 可以完全无视它。你会发现依赖于一段请求对象的代码,因没有请求对象无法正 常运行。解决方案是,自行创建一个请求对象并且把它绑定到环境中。单元测试 的最简单的解决方案是:用 test_request_context() 环 境管理器。结合 with 声明,绑定一个测试请求,这样你才能与之交互。下面 是一个例子:
from flask import Flask
from flask import requestapp = Flask(__name__)st_request_context('/hello', method='POST'):# now you can do something with the request until the# end of the with block, such as basic assertions:assert request.path == '/hello'hod == 'POST'
另一种可能是:传递整个 WSGI 环境给 request_context() 方法:
from flask import Flask
from flask import requestapp = Flask(__name__)quest_context(environ):hod == 'POST'
API 章节对请求对象作了详尽阐述(参见 request ),因此这 里不会赘述。此处宽泛介绍一些最常用的操作。
当前请求的 HTTP 方法可通过 method 属性来访问。通 过:attr:~quest.form 属性来访问表单数据( POST 或 PUT 请求 提交的数据)。这里有一个用到上面提到的那两个属性的完整实例:
from flask import Flask
from flask import request
from flask import render_templateapp = Flask(__name__)ute('/login', methods=['POST', 'GET'])
def login():error = hod == 'POST':if valid_login(request.form['username'],request.form['password']):return log_the_user_in(request.form['username'])else:error = 'Invalid username/password'# the code below is executed if the request method# was GET or the credentials were invalidreturn render_template('login.html', error=error)
当访问 form 属性中的不存在的键会发生什么?会抛出一个特殊的 KeyError 异常。你可以像捕获标准的 KeyError 一样来捕获它。 如果你不这么做,它会显示一个 HTTP 400 Bad Request 错误页面。所以,多数 情况下你并不需要干预这个行为。
你可以通过 args 属性来访问 URL 中提交的参数 ( ?key=value ):
searchword = ('q', '')
推荐用 get 来访问 URL 参数或捕获 KeyError ,因为用户可能会修 改 URL,向他们展现一个 400 bad request 页面会影响用户体验。
欲获取请求对象的完整方法和属性清单,请参阅 request 的 文档。
用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置 enctype="multipart/form-data" 属性,不然你的浏览器根本不会发送文件。
已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象 的 files 属性访问它们。每个上传的文件都会存储在 这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有 一个 save() 方法,这个方法 允许你把文件保存到服务器的文件系统上。这里是一个用它保存文件的例子:
from flask import requestute('/upload', methods=['GET', 'POST'])
def upload_file():hod == 'POST':f = request.files['the_file']f.save('/var/www/uploads/')...
如果你想知道上传前文件在客户端的文件名是什么,你可以访问 filename 属性。但请记住, 永远不要信任这个值,这个值是可以伪造的。如果你要把文件按客户端提供的 文件名存储在服务器上,那么请把它传递给 Werkzeug 提供的 secure_filename() 函数:
from flask import request
from werkzeug import secure_filenameute('/upload', methods=['GET', 'POST'])
def upload_file():hod == 'POST':f = request.files['the_file']f.save('/var/www/uploads/' + secure_filename(f.filename))...
一些更好的例子,见 上传文件 模式。
可以通过 cookies 属性来访问 Cookies,用 响应对象的 set_cookie 方法来设置 Cookies。请 求对象的 cookies 属性是一个内容为客户端提交的 所有 Cookies 的字典。如果你想使用会话,请不要直接使用 Cookies,请参 考 会话 一节。在 Flask 中,已经注意处理了一些 Cookies 安全 细节。
读取 cookies:
from flask import requestute('/')
def index():username = ('username')# (key) instead of cookies[key] to not get a# KeyError if the cookie is missing.
存储 cookies:
from flask import make_responseute('/')
def index():resp = make_response(render_template(...))resp.set_cookie('username', 'the username')return resp
注意:Cookies 是设置在响应对象上的。由于通常视图函数只是返 回字符串,之后 Flask 将字符串转换为响应对象。如果你要显式地转换,你 可以使用 make_response() 函数然后再进行修改。
有时候你想设置 Cookie,但响应对象不能醋在。这可以利用 延迟请求回调 模式实现。
为此,也可以阅读 关于响应 。
可以用 redirect() 函数把用户重定向到其它地方。放弃请 求并返回错误代码,用 abort() 函数。这里是一个它们如何 使用的例子:
from flask import abort, redirect, url_forute('/')
def index():return redirect(url_for('login'))ute('/login')
def login():abort(401)this_is_never_executed()
这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面 (401 意味着禁止访问),但是它展示了重定向是如何工作的。
默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器:
from flask import render_templatehandler(404)
def page_not_found(error):return render_template('page_not_found.html'), 404
注意 render_template() 调用之后的 404 。这告诉 Flask,该页的错误代码是 404 ,即没有找到。默认为 200,也就是一切 正常。
视图函数的返回值会被自动转换为一个响应对象。如果返回值是一个字符串, 它被转换为该字符串为主体的、状态码为 200 OK``的 、 MIME 类型是 ``text/html 的响应对象。Flask 把返回值转换为响应对象的逻辑是这样:
如果你想在视图里操纵上述步骤结果的响应对象,可以使用 make_response() 函数。
例如,你有这样一个视图:
handler(404)
def not_found(error):return render_template('error.html'), 404
你只需要把返回值表达式传递给 make_response() ,获取结果对象并修改,然后再返回它:
handler(404)
def not_found(error):resp = make_response(render_template('error.html'), 404)resp.headers['X-Something'] = 'A value'return resp
除请求对象之外,还有一个 session 对象。它允许你在不 同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名。这意味着用户可以查看你 Cookie 的内容,但却不 能修改它,除非用户知道签名的密钥。
要使用会话,你需要设置一个密钥。这里介绍会话如何工作:
from flask import Flask, session, redirect, url_for, escape, requestapp = Flask(__name__)ute("/")
def index():if "username" in session:return "Logged in as %s" % escape(session["username"])return "You are not logged in"ute("/login", methods=["GET", "POST"])
def login():hod == "POST":session["username"] = request.form["username"]return redirect(url_for("index"))return """<form action="" method="post"><p><input type=text name=username><p><input type=submit value=Login></form>"""ute("/logout")
def logout():# remove the username from the session if it's theresession.pop("username", None)return redirect(url_for("index"))# set the secret key. keep this really secret:
app.secret_key = "A0Zr98j/3yX R~XHH!jmN]LWX/,?RT"
这里提到的 escape() 可以在你模板引擎外做转义(如同本例)。
如何生成强壮的密钥
随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密钥随机生成器来生成漂亮的随机值,这个值可以用来做 密钥:
>>> import os
>>> os.urandom(24)
'xfd{Hxe5<x95xf9xe3x96.5xd1x01O<!xd5xa2xa0x9fR"xa1xa8'
把这个值复制粘贴进你的代码中,你就有了密钥。
使用基于 cookie 的会话需注意: Flask 会将你放进会话对象的值序列化至 Cookies。如果你发现某些值在请求之间并没有持久存在,然而确实已经启用了 Cookies,但也没有得到明确的错误信息。这时,请检查你的页面响应中的 Cookies 的大小,并与 Web 浏览器所支持的大小对比。
在客户端和服务器之间进行交互的过程中,有些准备工作和扫尾工作需要处理,比如:在请求开始的时候,建立数据库连接,在请求结束的时候指定数据交互的格式,为了让每个视图函数避免编写重复的功能代码,Flask 提供了通用设施的过程,即__请求钩子__。
请求钩子是通过装饰器的形式实现的,Flask支持四中请求钩子:
请求钩子与 django 中的中间件很类似。
示例:
from flask import Flask, request, url_for, abortapp = Flask(__name__)ute("/index")
def index():print("index page is running")a = 1 / 0return "Index page"ute("/hello")
def hello():print("hello page is running")return "Hello page"@app.before_first_request
def handle_before_first_request():# 在第一次请求处理之前先执行print("handle_before_first_request is running")@app.before_request
def handle_before_request():# 在每次请求处理之前被执行print("handle_before_request is running")@app.after_request
def handle_after_request(response):# 在每次请求(视图函数)处理之后都被执行,前提是视图函数没有异常# 在请求之后,会接收一个参数,这个参数是前面的请求处理完毕后返回# 的响应数据,如果需要对响应做额外处理,可以在这里进行print(f"handle_after_request is running, the response is {response}")return responseardown_request
def handle_teardown_request(response):# 在每次请求(视图函数)处理之后都会被执行,无论视图函数是否异常# 每一次请求之后都会调用,会接收一个参数,这个参数是服务器出现的# 错误信息,工作在非调试模式下,当时图函数出现异常时,才会被执行print(f"handle_teardown_request is running, the response is {response}")if request.path == url_for("index"):print("在请求钩子中判断请求的视图逻辑:index")elif request.path == url_for("hello"):print("在请求钩子中判断请求的视图逻辑:hello")return responseif __name__ == '__main__':app.run()
导包:
使用:
在终端中使用:python 文件名.py --help 查看可用的命令:
from flask import Flask
from flask_script import Managerapp = Flask(__name__)
manager = Manager(app)ute("/index")
def index():return "Index page"if __name__ == '__main__':manager.run()
在Flask中使用render_template渲染模板
模板变量与django中类似,其中操作字典有两种方式:{{my_dict.键名}}或{{my_dict[“键名”]}}
render_template(“需要渲染的模板.html”, xx=value, xxx=value, …) 其中value可以是字典、列表、int、str…
过滤器(支持链式使用过滤器):
1、字符串过滤器:
2、列表过滤器:
3、自定义过滤器:自定义的过滤器名称如果与内置的过滤器重名,会覆盖掉内置的过滤器,自定义过滤器有两种方式。
plate_filter("过滤器名") # 如果不传过滤器名,默认函数名
def 自定义的过滤器函数:
...
from flask import Flask, render_templateapp = Flask(__name__)# 渲染模板
ute("/test")
def test():data = {"name": "zhaosi","age": 20,"my_dict": {"city": "beijing"},"my_list": [1, 2, 3, 4, 5, 6, 7],"my_int": 3}return render_template("test.html", **data)# 自定义过滤器
# 方式一
def li_step_1(li):# 列表元素隔一个取一个return li[::2]
app.add_template_filter(li_step_1, "li_1")# 方式二
plate_filter("li_2")
def li_step_2(li):# 列表元素各两个取一个return li[::3]if __name__ == '__main__':app.run()# html文件
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>模板</title></head><body><p>name:{{ name }}</p><p>age:{{ age }}</p><p>字典类型数据:</p><p>my_dict: city:{{ my_dict.city }}</p><p>my_dict: city:{{ my_dict["city"] }}</p><p>列表类型数据:</p><p>my_list:{{ my_list }}</p><p>int类型数据:</p><p>my_int:{{ my_int }}</p><p>my_list[my_int]:{{ my_list[my_int] }}</p><p>模板变量相加减</p><p>my_list[0] + my_list[1] = {{ my_list[0] + my_list[1] }}</p><p>字符串变量相加</p><p>name + city = {{ name + my_dict["city"] }}</p><p>过滤器的使用,支持链式操作,即:xxx|过滤器1|过滤器2|...</p><p>‘ hello flask ’去首尾空格,并首字母大写:{{ " hello flask "|trim|title }}</p><p>自定义的转换器的使用:</p><p>列表元素隔一个取一个:{{ my_list|li_1 }}</p><p>列表元素隔两个取一个:{{ my_list|li_2 }}</p></body>
</html>
xss攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器执行,达到攻击的目的,形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨大的,是web安全的头号大敌。
XSS攻击是在用户的浏览器上执行的,其形成过程则是在服务器端页面渲染时,注入了恶意的HTML代码导致的。
from flask import Flask, request, render_templateapp = Flask(__name__)ute("/xss", methods={"GET", "POST"})
def xss():text = ""hod == "POST":text = ("text")return render_template("xss攻击.html", text=text)if __name__ == '__main__':app.run()# html文件
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>XSS攻击</title></head><body><form method="post"><textarea name="text"></textarea><input type="submit" value="提交"></form>{# 转义:默认是开启转义的,即:你输入什么,最后返回什么,这是为了防止xss攻击 #}{#比如:输入<script>alert("xss attack")</script>转义成html语言后为: <script>alert("xss attack")</script>那么在浏览器中显示也是:<script>alert("xss attack")</script>#}{{ text }} {# 默认是开启转义了,渲染模板时,执行的是 <script>alert("xss attack")</script> #}{{ text|safe }} {# 关闭转义,渲染模板时,执行的是 <script>alert("xss attack")</script> ,这是就会出现一个弹窗 #}</body>
</html>
反馈,是良好的应用和用户界面的重要构成。如果用户得不到足够的反馈,他们 很可能开始厌恶这个应用。 Flask 提供了消息闪现系统,可以简单地给用户反馈。 消息闪现系统通常会在请求结束时记录信息,并在下一个(且仅在下一个)请求 中访问记录的信息。展现这些消息通常结合要模板布局。
使用 flash() 方法可以闪现一条消息。要操作消息本身,请使用 get_flashed_messages() 函数,并且在模板中也可以使用。完整 的例子见 消息闪现 部分。
有时候你会处于这样一种境地,你处理的数据本应该是正确的,但实际上不是。 比如,你会有一些向服务器发送请求的客户端代码,但请求显然是畸形的。这可 能是用户篡改了数据,或是客户端代码的粗制滥造。大多数情况下,正常地返回 400 Bad Request 就可以了,但是有时候不能这么做,并且要让代码继续运 行。
你可能依然想要记录下,是什么不对劲。这时日志记录就派上了用场。从 Flask 0.3 开始,Flask 就已经预置了日志系统。
这里有一些调用日志记录的例子:
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
('An error occurred')
附带的 logger 是一个标准日志类 Logger ,所以更多信息请查阅 logging 的文档 。
如果你想给你的应用添加 WSGI 中间件,你可以封装内部 WSGI 应用。例如若 是你想用 Werkzeug 包中的某个中间件来应付 lighttpd 中的 bugs ,可以这 样做:
ib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
部署 Flask 应用
托管 Flask 应用的其它选择:
如果你有自己的主机,并且准备自己托管,参见 部署选择 章节。
Flask 介绍 Flaskr
Flask 创建文件夹
Flask 数据库模式
Flask 应用设置代码
Flask 数据库连接
Flask 创建数据库
Flask 视图函数
Flask 模板
Flask 添加样式
Flask 应用测试
Flask Jinja 配置
Flask 标准上下文
Flask 标准过滤器
Flask 控制自转义
Flask 注册过滤器
Flask 上下文处理器
Flask 应用程序
Flask 测试的大框架
Flask 第一个测试
Flask 登录和登出
Flask 测试消息的添加
Flask 其他测试技巧
Flask 伪造资源和上下文
Flask 保存上下文
Flask 访问和修改 Sessions
Flask 错误邮件
Flask 记录带文件
Flask 控制日志格式
Flask 其他的库
Flask 配置基础
Flask 内置的配置值
Flask 从文件配置
Flask 配置的最佳实践
Flask 开发/生产
Flask 实例文件夹
Flask 订阅信号
Flask 创建信号
Flask 发送信号
Flask 信号与 Flask 的请求上下文
Flask 基于装饰器的信号订阅
Flask 核心信号
Flask 基本原则
Flask 方法提示
Flask 基于调度的方法
Flask 装饰视图
Flask 用于 API 的方法视图
Flask 应用上下文的作用
Flask 创建应用上下文
Flask 应用上下文局部变量
Flask 上下文用法
Flask 深入上下文作用域
Flask 上下文如何工作
Flask 回调和错误
Flask 销毁回调
Flask 留意代理
Flask 错误是的上下文保护
Flask 为什么使用蓝图?
Flask 蓝图的设想
Flask 我的第一个蓝图
Flask 注册蓝图
Flask 蓝图资源
Flask 构造 URL
随着 flask 程序越来越复杂,我们需要对程序进行模块化的处理。
举例来说:假设有一个博客程序,前台界面需要的路由为:首页、列表、详情等页面
源程序app.py文件:
from flask import Flaskapp=Flask(__name__)ute('/')
def index():return 'index'ute('/list')
def list():return 'list'ute('/detail')
def detail():return 'detail'if __name__=='__main__':app.run()
如果博主需要编辑博客,要进入后台进行处理:后台主页,编辑,创建,发布博客
改进后程序:
from flask import Flaskapp=Flask(__name__)ute('/')
def index():return 'index'ute('/list')
def list():return 'list'ute('/detail')
def detail():return 'detail'ute('/')
def admin_home():return 'admin_home'ute('/new')
def new():return 'new'ute('/edit')
def edit():return 'edit'ute('/publish')
def publish():return 'publish'if __name__=='__main__':app.run()
这样就使得我们在一个py文件中写入了很多路由,将来维护代码会非常麻烦,此时,同学们就考虑到了模块化的处理方式,将admin相关的路由写到一个admin.py文件中,那我们就顺着这个思路走下去
修改后的代码:
app.py
from flask import Flaskapp=Flask(__name__)ute('/')
def index():return 'index'ute('/list')
def list():return 'list'ute('/detail')
def detail():return 'detail'if __name__=='__main__':app.run()admin.pyute('/')
def admin_home():return 'admin_home'ute('/new')
def new():return 'new'ute('/edit')
def edit():return 'edit'ute('/publish')
def publish():return 'publish'
发现app.py文件中的app直接报错,代码无法继续写下去,所以在flask程序中,使用传统的模块化是行不通的,需要flask提供一个特有的模块化处理方式,flask内置了一个模块化处理的类,即Blueprint
简单来说,Blueprint 是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效
使用蓝图可以分为三个步骤
1,创建一个蓝图对象
admin=Blueprint('admin',__name__)
2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
ute('/')
def admin_home():
return 'admin_home'
3,在应用对象上注册这个蓝图对象
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
url_for('admin.index') # /admin/
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
下面的示例将蓝图所在目录下的static_admin目录设置为静态目录
admin = Blueprint("admin",__name__,static_folder='static_admin')
ister_blueprint(admin,url_prefix='/admin')
现在就可以使用/admin/static_admin/ 访问static_admin目录下的静态文件了 定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。下面的示例将为 static_admin 文件夹的路由设置为 /lib
admin = Blueprint("admin",__name__,static_folder='static_admin',static_url_path='/lib')
ister_blueprint(admin,url_prefix='/admin')
蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录
admin
=
Blueprint(
'admin'
,__name__,template_folder
=
'my_templates'
)
注:如果在 templates 中存在和 my_templates 同名文件,则系统会优先使用 templates 中的文件 参考链接:python - flask blueprint template folder - Stack Overflow
Flask 寻找扩展
Flask 使用扩展
Flask 0.8 以前
某些东西非常通用,以至于你有很大的机会在绝大部分 Web 应用中,都能找到 他们的身影。例如相当多的应用在使用关系数据库而且包含用户注册和认证模块。 在这种情况下,请求开始之前,他们会打开数据库连接、获得当前已经登陆的用户 信息。在请求结束的时候,数据库连接又会被关闭。
这章提供了一些由用户贡献的代码片段和模板来加速开发 Flask Snippet Archives.
Flask 阅读源码
Flask 钩子,继承
Flask 继承
Flask 用中间件包装
Flask 分支
Flask 像专家一样扩大规模
Flask 与社区对话
Flask 应用对象
Flask 会话接口
Flask 消息闪现
Flask 模板渲染
Flask 信号
Flask 基于类的视图
Flask 视图函数选项
Flask 显式的应用对象
Flask 路由系统
Flask 某个模板引擎
Flask 微与依赖
Flask 线程局域变量
Flask 是什么,不是什么?
Flask XHTML 的历史
Flask HTML5 的历史
Flask HTML vs. XHTML
Flask “严格”意味着什么?
Flask HTML5 中的新技术
Flask 应该使用什么?
Flask 跨站脚本攻击(XSS)
Flask 跨站请求伪造(CSRF)
Flask JSON 安全
Flask 自动转换
Flask 金科玉律
Flask 自行编解码
Flask 配置编辑器
本文发布于:2024-02-02 10:48:12,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170684208943295.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |