flask学习

如何理解wsgi, Werkzeug, flask之间的关系


Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug,它只是工具包,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理。将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

快速入门


最小应用代码实现

from flask import Flask   这个类的实例是我们的 WSGI 应用程序
app = Flask(__name__)  创建一个该类的实例,第一个参数是应用模块或者包的名称。
app.debug = True  如果你启用了调试支持,服务器会在代码修改后自动重新载入,并在发生错误时提供一个相当有用的调试器。

@app.route('/')   使用 route() 装饰器告诉 Flask 什么样的URL 能触发我们的函数。
def hello_world():
    return 'Hello World!'

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

启用调试模式


app.debug = True 如果你启用了调试支持,服务器会在代码修改后自动重新载入,并在发生错误时提供一个相当有用的调试器。

或者: app.run(debug=True)

路由

Flask 的 URL 规则基于 Werkzeug 的路由模块。这个模块背后的思想是基于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。


app.route() 装饰器把一个函数绑定到对应的 URL 上。

 @app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello World'

变量规则

要给 URL 添加变量部分,你可以把这些特殊的字段标记为 <variable_name> , 这个部分将会作为命名参数传递到你的函数。 规则可以用 <converter:variable_name> 指定一个可选的转换器。

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

构造 URL 好处:它允许你一次性修改 URL, 而不是到处边找边改。


你可以用 url_for() 来给指定的函数构造 URL。 它接受函数名作为第一个参数,也接受对应 URL 规则的变量部分的命名参数。未知变量部分会添加到 URL 末尾作为查询参数。

>>> with app.test_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

HTTP 方法


默认情况下,路由只回应 GET 请求,但是通过 route() 装饰器传递 methods 参数可以改变这个行为。

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

静态文件


给静态文件生成地址

在你的包中或是模块的所在目录中创建一个名为 static 的文件夹,在应用中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 'static' 端点名:

url_for('static', filename='style.css')
这个文件应该存储在文件系统上的 static/style.css

模板渲染


可以使用 render_template() 方法来渲染模板 代码实现

from flask import render_template

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

模板文件

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello World!</h1>
{% endif %}

请求对象


当前请求的 HTTP 方法可通过 method 属性来访问。通过:attr:~flask.request.form 属性来访问表单数据( POST 或 PUT 请求提交的数据)

你可以通过 args 属性来访问 URL 中提交的参数 ( ?key=value ):

searchword = request.args.get('q', '')

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':      通过method方法获取请求方法
        if valid_login(request.form['username'],  通过request.form 方法获取表单数据
                       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 invalid
    return render_template('login.html', error=error)

文件上传


用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置 enctype="multipart/form-data" 属性,不然你的浏览器根本不会发送文件。

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']       获取文件
        f.save('/var/www/uploads/uploaded_file.txt')  保存文件
    ...

from flask import request
from werkzeug import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']   获取文件
        f.save('/var/www/uploads/' + secure_filename(f.filename))  安全获取文件的名称

Cookie


设置cookie

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

读取cookie

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

重定向和错误


from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))  重定向

@app.route('/login')
def login():
    abort(401)   错误
    this_is_never_executed()

定制错误页面

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

flask响应方式三种


如果返回的是一个合法的响应对象,它会从视图直接返回。
如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
如果返回的是一个元组,且元组中的元素可以提供额外的信息。这样的元组必须是 (response, status, headers) 的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
如果上述条件均不满足, Flask 会假设返回值是一个合法的 WSGI 应用程序,并转换为一个请求对象。

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

会话session


在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']  登录设置session值
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)          退出登录,删除session值,去掉登录状态
    return redirect(url_for('index'))

# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'   session签名随机密码

消息闪现


消息日志记录


日志记录格式

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

代码实现

import logging
from flask import Flask
app = Flask(__name__)
app.debug = True

@app.route('/')
def hello_world():
    try:
        j = 1/0
    except:
        app.logger.debug("message_info") 记录日志
    print "haha"
    return 'Hello World!'

if __name__ == '__main__':
    handler = logging.FileHandler('flask.log', encoding='UTF-8')
    handler.setLevel(logging.DEBUG)
    logging_format = logging.Formatter(
        '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s')
    handler.setFormatter(logging_format)
    app.logger.addHandler(handler)
    app.run('localhost',9999)

可插拨视图


主要用途:可定制,可插拨的视图来替代部分实现

第一步 :创建一个flask.views.View 的子类,并且引入dispatch_request()方法

第二步:通过使用as_view()方法,把类转换成实际视图函数

from flask.views import View 
class ShowUsers(View):
    def dispatch_request(self):
        users = User.query.all()
        return render_template('users.html', objects=users)
app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))  传递给函数的 字符串是最终视图的名称

也可以基于http方法调度:

from flask.views import MethodView 
class UserAPI(MethodView):
    def get(self):
        users = User.query.all() 
        ...
    def post(self):
        user = User.from_form_data(request.form) 
        ...
app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

蓝图


基本概念:蓝图被注册到应用之后。所要执行的操作的集合,Flask 会把蓝图和视图函数关联起来,并生成两个端点之前的 URL 。

蓝图blueprint,实现应用模块化,使用蓝图让应用层次清晰,开发者可以更容易开发,和维护项目

蓝图,通常作用于相同的URL地址前缀,他们可以放在同一个模块中。

蓝图可以极大简化应用并为扩展提供集中的注册入口。

blueprint对象和flask应用对象的工作方式类似,但不是一个真正的应用。它更像一个用于构建和扩展应用的蓝图

为什么使用蓝图:

  1. 把一个应用分解为一套蓝图。这是针对大型应用的理想方案:一个项目可以实例化一个 应用,初始化多个扩展,并注册许多蓝图。
  2. 在一个应用的 URL 前缀和(或)子域上注册一个蓝图。 URL 前缀和(或)子域的参数 成为蓝图中所有视图的通用视图参数(缺省情况下)。
  3. 使用不同的 URL 规则在应用中多次注册蓝图。
  4. 通过蓝图提供模板过滤器、静态文件、模板和其他工具。蓝图不必执行应用或视图 函数。
  5. 蓝图的缺点是一旦应用被创建后,只有销毁整个应用对象才能注销蓝图。
蓝图实例:show.py

from flask import Blueprint, render_template, abort 
from jinja2 import TemplateNotFound
simple_page = Blueprint('simple_page', __name__, template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})  使用此装饰器时,蓝图会记录下所登记的show函数
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template('pages/%s.html' % page)
    except TemplateNotFound: 
        abort(404)

当以后注册蓝图时,这个函数会被注册到应用中,它会把 构建 Blueprint 时所使用的名称(在本例为 simple_pa ge )作为函数端点 的前缀。

可以这样注册蓝图:

# app.py

from flask import Flask
from user.simple_page import simple_page
app = Flask(__name__) 
app.register_blueprint(simple_page)

注册后形成的url--视图映射规则:
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>, 
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
<Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>]

蓝图还可以挂接到不同的位置:
app.register_blueprint(simple_page, url_prefix='/pages')

这样就会形成如下规则:
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>, 
<Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>]

总之,你可以多次注册蓝图,但是不一定每个蓝图都能正确响应。是否能够多次注册实际 上取决于你的蓝图是如 何编写的,是否能根据不同的位置做出正确的响应。

蓝图资源

静态文件:

蓝图的第三个参数是 static_folder 。
这个参数用以指定蓝图的静态文件所在的文件夹,它可以是一个绝对路径也 可以是相对路径。:
admin = Blueprint('admin', __name__, static_folder='static')

模板:
使用蓝图来暴露模板,那么可以使用 Blueprint 的 template_folder 参数:
admin = Blueprint('admin', __name__, template_folder='templates') 分绝对和相对

理解上下文(上下文是环境的一个快照,是一个用来保存状态的对象)


flask分应用上下文和请求上下文


current_app = LocalProxy(_find_app)   应用上下文,当前app实例对象

g = LocalProxy(partial(_lookup_app_object, 'g')) 应用上下文,用作零时存储的对象
每个请求的g都是独立的,并且在整个请求内都是可访问修改的。
对g对象调用过程如下:
    访问g-->从当前线程的应用上下文栈顶获取应用上下文-->取出其中的g对象-->进行操作。
所以可以通过一个g对象而让所有线程互不干扰的访问自己的g。

request = LocalProxy(partial(_lookup_req_object, 'request'))  请求上下文,封装了客户端发出的http请求
session = LocalProxy(partial(_lookup_req_object, 'session'))  请求上下文,存储用户对话

本地线程

Thread Local 希望不同的线程对于内容的修改只在线程内发挥作用,线程间互不影响

原理:threading.current_thread().dict里添加一个包含对象mydata的id的值,保存不同线程的状态

Werkzeug Local

Werkzeug 使用自定义storage保存不同线程下的状态

​ 提供释放本地线程的release_local方法

​ 获取get_ident函数,获取线程或者协程标识符

Werkzeug 两种数据结构

LocalStack : 基于Werkzeug.local.local 实现的栈结构,可以将对象推入和弹出,可以快速拿到栈顶对象。

LocalProxy:标准代理模式,这个函数执行之后就是通过LocalStack实例化的栈顶的栈顶对象,对于LocalProxy对象的操作都会转发到这个栈顶对象。

理解flask.request

源码globles.py 文件

from functools import partial
from werkzeug.local import LocalStack, LocalProxy

def _lookup_req_object(name):
    top = _request_ctx_stack.top     _request_ctx_stack是底层一个请求上下文栈结构
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

request = LocalProxy(partial(_lookup_req_object, 'request')) 

请求上下文request流程如下:
    1,请求产生
    2,请求过程中向 _request_ctx_stack推入请求上下文对象,这个请求上下文会变成栈顶,request就会成为这个请求上下文,也就包含了这次请求相关的信息和数据。
    3,在视图函数使用request就可以使用request.args.get('name')

flask扩展生态


Flask-Script 添加运行服务器,设置数据库,定制shell等功能的命令。

Flask-WTF 处理web表单

Flask-Sqlalchemy 关系型数据库扩展

Flask-RESTful 实现一个Restful的请求

Flask-Mail 发送邮件

Flask-Babel 国际化I18N和本地化L10N

Flask-PyMongo 使用MongoDB数据库

Flask-Cache 使用缓存

Flask-Login 登录管理

Flask-HTTPAuth 认证

Flask-Admin 是一个简单易用的Flask扩展,让你可以很方便并快速地为Flask应用程序增加管理界面。

Flask-DebugToolbar 内置调试工具

Flask-Migrate 数据库迁移框架

Flask-Security 提供角色管理,权限管理,用户登录,邮箱验证,密码重置,密码加密

flask 钩子函数使用方法


before_first_request

注册一个函数,在处理第一个请求之前运行,只运行一次,后面不再运行

示例:
@app.before_first_request  
def bf_first_request():  
    g.string = 'before_first_request' 

before_request

注册一个函数,在处理每次请求之前运行.每次都运行

@app.before_request和@app.teardown_request的组合可以用于打开一个资源和关闭一个资源,

@app.before_request  
def bf_request():  
    g.string = 'before_request' 

after_request

注册一个函数,在每次请求之后运行.注册的函数至少需要含有一个参数,这个参数实际上为服务器的响应,且函数中需要返回这个响应参数.

装饰器修饰的函数接收一个响应对象,我们拿到了这个响应对象后可以对内容进行修改(虽然很少见)还可以在上面追加COOKIE进行返回。

@app.after_request  
def af_request(param):  
    return param  接收这个参数,然后要返回

 param是服务器的响应 
 示例:
 {'_on_close': [], 'response': ['Hello World!'], 'headers': Headers([('Content-Type', u'text/html; charset=utf-8'), ('Content-Length', u'12')]), '_status_code': 200, '_status': '200 OK', 'direct_passthrough': False}

teardown_request

会在每个请求后执行,请求发生异常时也会被调用,处理函数必须传入参数接收异常信息

@app.teardown_request修饰的函数在非调试模式下才能在出现异常时被执行

@app.teardown_request
def teardown_request(error):
    print("请求之后调用")

app.errorhandler

装饰器用于错误处理

传入装饰器的值为一错误状态码,修饰的函数必须要有一个参数用于接收错误,为了测试500的效果必须关闭调试模式!

@app.errorhandler(404)
def notFound(error):
    return "404"
@app.errorhandler(500)
def notFound(error):
    return "500"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容