flask入门

Flask介绍

Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug(Django使用的是wsgiref) ,模板引擎则使用 Jinja2 。

Flask特点:

  • 短小精悍,可拓展强,第三方组件丰富

与Django的比较:

  • 大而全,内部提供:ORM、Admin、中间件、Form、ModelForm、Session、缓存、信号、CSRF;

tornado:

  • 短小精悍+异步非阻塞

简单的Werkzeug应用

from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple


@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    # 请求一旦到来,执行第三个参数    参数()
    run_simple('localhost', 4000, hello)

Flask版Hello Word

from flask import Flask

app = Flask(__name__) # 一个Flask类的对象

@app.route('/index') # 此乃路由也
def index():
    return 'Hello World'

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

路由系统

路由与函数绑定的两种方式

装饰器方式:

from flask import Flask
app = Flask(__name__)

@app.route('/index',methods=['GET','POST'],endpoint='n1')
def index():
    return "Index"

# 参数介绍 rule=匹配路由,methods=允许请求方式,endpoint=反向解析的名字

直接绑定

from flask import Flask
app = Flask(__name__)

def order():
    return 'Order'

app.add_url_rule('/order',view_func=order)
# route内部就是用了这种方式

动态路由匹配

from flask import Flask
app = Flask(__name__)

@app.route('/index/<int:nid>',methods=['GET','POST'])
def index(nid):
    return "Index"

# <类型:关键字>
# 类型转换器介绍
DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

反向解析

from flask import Flask,url_for
app = Flask(__name__)

@app.route('/index',methods=['GET','POST'],endpoint='n1')
def index():
    v1 = url_for('n1') # 关键字传参
    print(v1)
    return "Index"

  解析的默认名字是函数名,当为视图函数使用装饰器时一定要修复,否则Flask项目会因存在因装饰器装饰多个视图函数,产生相同的endpoint而无法启动

自定义正则匹配

from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
    """
    自定义URL匹配正则表达式
    """

    def __init__(self, map, regex):
        super(RegexConverter, self).__init__(map)
        self.regex = regex

    def to_python(self, value):
        """
        路由匹配时,匹配成功后传递给视图函数中参数的值
        :param value:
        :return:
        """
        return int(value)

    def to_url(self, value):
        """
        使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
        :param value:
        :return:
        """
        val = super(RegexConverter, self).to_url(value)
        return val
# 添加到flask中
app = Flask(import_name=__name__)
app.url_map.converters['regex'] = RegexConverter


@app.route('/index/<regex("\d+"):nid>')
def index(nid):
    print(url_for('index', nid='888'))
    return 'Index'


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

路由重定向

from flask import Flask,render_template,redirect
app = Flask(__name__)

# 当访问此路由时会定位到/new
@app.route('/index',methods=['GET','POST'],redirect_to='/new')
def index():
    return "老功能"

@app.route('/new',methods=['GET','POST'])
def new():
    return '新功能'


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

子域名获取

from flask import Flask,render_template,redirect
app = Flask(__name__)
app.config['SERVER_NAME'] = 'oldboy.com:5000'


@app.route("/dynamic", subdomain="<username>")
def xxxxxxx(username):
    print(username)
    return 'xxxxx'

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

路由的一些参数

@app.route和app.add_url_rule参数:
rule,                       URL规则
view_func,                  视图函数名称
defaults=None,              默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
endpoint=None,              名称,用于反向生成URL,即: url_for('名称')
methods=None,               允许的请求方式,如:["GET","POST"]
strict_slashes=None,        对URL最后的 / 符号是否严格要求,
subdomain=None,             子域名访问

视图

FBV的两种形式

# 方式一
from flask import Flask,render_template,redirect,views
app = Flask(__name__)
class IndexView(views.View):
    methods = ['GET']  # 允许方法
    decorators = [wapper, ] # 装饰器

    def dispatch_request(self):
        print('Index')
        return 'Index!'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index1'))  # name=endpoint


# 方式二
class IndexView(views.MethodView):
    methods = ['GET']
    decorators = [wapper, ]

    def get(self):
        return 'Index.GET'

    def post(self):
        return 'Index.POST'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index2'))  # name=endpoint

请求和响应

# 请求数据获取
from flask import request
# request.method
# request.args
# request.form
# request.values
# request.cookies
# request.headers
# request.path
# request.full_path
# request.script_root
# request.url
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# request.get_data() 请求元数据 # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 返回响应 # return "字符串" from flask import render_template # return render_template('html模板路径',**{}) from flask import redirect # return redirect('/index.html') 重定向 # 设置响应头和cookie from flask import make_response # response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response

有坑

读取文件form表单应设置enctype="multipart/form-data"

模板

Markup等价django的mark_safe

Session

除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。

  • 设置:session['username'] = 'xxx'

  • 删除:session.pop('username', None)
from flask import Flask,render_template,request,redirect,session

app = Flask(__name__) # 一个Flask类的对象
app.secret_key = 'u2jksidjflsduwerjl'  # 秘钥
app.debug = True

@app.route('/login',methods=['GET',"POST"])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    user = request.form.get('user') # 获取POST传过来的值
    pwd = request.form.get('pwd') # 获取POST传过来的值
    if user == 'alex' and pwd == '123':
        # 用户信息放入session
        session['user_info'] = user
        return redirect('/index')
    else:
        return render_template('login.html',msg ='用户名或密码错误')
        # return render_template('login.html',**{'msg':'用户名或密码错误'})


@app.route('/logout')
def logout():
    session.pop('user_info')
    return redirect('/login')

配置文件

flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:

{
'DEBUG':                                get_debug_flag(default=False),  是否开启Debug模式
'TESTING':                              False,                          是否开启测试模式
'PROPAGATE_EXCEPTIONS':                 None,                          
'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
'SECRET_KEY':                           None,
'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
'USE_X_SENDFILE':                       False,
'LOGGER_NAME':                          None,
'LOGGER_HANDLER_POLICY':               'always',
'SERVER_NAME':                          None,
'APPLICATION_ROOT':                     None,
'SESSION_COOKIE_NAME':                  'session',
'SESSION_COOKIE_DOMAIN':                None,
'SESSION_COOKIE_PATH':                  None,
'SESSION_COOKIE_HTTPONLY':              True,
'SESSION_COOKIE_SECURE':                False,
'SESSION_REFRESH_EACH_REQUEST':         True,
'MAX_CONTENT_LENGTH':                   None,
'SEND_FILE_MAX_AGE_DEFAULT':            timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS':              False,
'TRAP_HTTP_EXCEPTIONS':                 False,
'EXPLAIN_TEMPLATE_LOADING':             False,
'PREFERRED_URL_SCHEME':                 'http',
'JSON_AS_ASCII':                        True,
'JSON_SORT_KEYS':                       True,
'JSONIFY_PRETTYPRINT_REGULAR':          True,
'JSONIFY_MIMETYPE':                     'application/json',
'TEMPLATES_AUTO_RELOAD':                None,
    }

设置方式

# 方式一:
app.config['DEBUG'] = True
PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
# 方式二:
app.config.from_object("python类或类的路径")
# 这样写类,
class BaseConfig(object):
    DEBUG = True
    SECRET_KEY = "asudflkjdfadjfakdf"

  

特殊装饰器

from flask import Flask, Request, render_template

app = Flask(__name__, template_folder='templates')
app.debug = True

# 只有第一次请求会执行
@app.before_first_request
def before_first_request1():
    print('before_first_request1')

# 视图函数之前
@app.before_request
def before_request1():
    Request.nnn = 123
    print('before_request1')

# 视图函数之后
@app.after_request
def after_request1(response):
    print('before_request1', response)
    return response

# 自定义错误信息
@app.errorhandler(404)
def page_not_found(error):
    return 'This page does not exist', 404

# 全局过滤函数
@app.template_global()
def sb(a1, a2):
    return a1 + a2

# 可以向模板上下文中自动注入变量或函数(必须是字典),在模板中调用
@app.context_processor 
def inject_user():
      return dict(user=g.user)

# 模板的过滤函数 {{a1|db(a2,a3)}}
@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3


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


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

  before_request的执行顺序是按代码顺序执行的,after_request的执行数顺序是反向执行的,当before_request返回内容的时候,after_request会全部执行,之前版本的Django也是这样做的

闪现威慑,点燃警告

message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除。

from flask import Flask,session,flash,get_flashed_messages
app = Flask(__name__)
app.secret_key = 'asdfasdfasdf'

@app.route('/x1',methods=['GET','POST'])
def login():
    flash('存一个',category='x1') # category是作为关键字,可以不设置
    return "视图函数x1"

@app.route('/x2',methods=['GET','POST'])
def index():
    data = get_flashed_messages(category_filter=['x1']) # category_filter根据关键字取,可以不设置
    return "视图函数x2"

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

中间件

falsk中间件利用了装饰器的性质,他执行的时request,session还没有生成

from flask import Flask
app = Flask(__name__)
app.secret_key = 'asdfasdfasdf'

@app.route('/x2',methods=['GET','POST'])
def index():
    return "x2"


class Middleware(object):
    def __init__(self,old_wsgi_app):
        """
        服务端启动时,自动执行
        :param old_wsgi_app:
        """
        self.old_wsgi_app =old_wsgi_app

    def __call__(self, environ, start_response):
        """
        每次有用户请求道来时
        :param args:
        :param kwargs:
        :return:
        """
        print('before')
        from flask import session,request
        obj = self.old_wsgi_app(environ, start_response)
        print('after')
        return obj

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

蓝图

Flask 用 蓝图(blueprints) 的概念来在一个应用中或跨应用制作应用组件和支持通用的模式。

# 简单的蓝图项目结构
| - projectName  
    | - app  //程序包  
        | - templates //jinjia2模板  
        |- static //css,js 图片等静态文件  
        | - main  //py程序包 ,可以有多个这种包,每个对应不同的功能  
            | - __init__.py  
            |- errors.py  
            |- forms.py  
            |- views.py  
        |- __init__.py  
        |- email.py //邮件处理程序  
        |- models.py //数据库模型  
    |- requirements.txt //列出了所有依赖包以及版本号,方便在其他位置生成相同的虚拟环境以及依赖  
    |- config.py //全局配置文件,配置全局变量  
    |- manage.py //启动程序

在app中的py文件中创建一个蓝图

from flask import Blueprint

us = Blueprint('us',__name__)

@us.route('/info')
def info():
    return 'info'

注册蓝图

创建flask的唯一实例,并且在初始化的时候注册蓝图

在app中的init完成创建实例与注册蓝图的过程

from flask import Flask
from .app import views

app = Flask(__name__)
app.register_blueprint(views.us)

项目的启动文件

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

蓝图的几个作用

  1. 为一个蓝图下的路由设置before_request和after_request
  2. 蓝图URL前缀:xxx = Blueprint('account', __name__,url_prefix='/xxx')
  3. 蓝图子域名:xxx = Blueprint('account', __name__,subdomain='admin')

# 前提需要给配置SERVER_NAME: app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
# 访问时:admin.wupeiqi.com:5000/login.html

信号

Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。

pip3 install blinker

内置信号及执行顺序

flask.signals.py
appcontext_pushed = _signals.signal('appcontext-pushed') #  请求上下文push时执行
request_started = _signals.signal('request-started') #  请求到来前执行
 
如果有render:
	before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
	template_rendered = _signals.signal('template-rendered') # 模板渲染后执行

request_finished = _signals.signal('request-finished') # 请求结束后执行
如果视图函数有异常:
	got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行

request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down') # 请求上下文执行完毕后自动执行(无论成功与否)

appcontext_popped = _signals.signal('appcontext-popped') # 请求上下文pop时执行


如果使用信号:
	message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发

信号的使用:

from flask import signals
def func(*args,**kwargs):
    pass
#信号中注册函数
signals.xxx.connect(func)

自定义信号

from flask import Flask, flash
from flask.signals import _signals

app = Flask(__name__)

xinhao = _signals.signal("xinhao")  # 创建信号


# 定义函数
def func1(*args, **kwargs):
    print("func1", args, kwargs)


def func2(*args, **kwargs):
    print("func2", args, kwargs)


# 将函数注册到信号中,添加到这个列表
xinhao.connect(func1)
xinhao.connect(func2)


@app.route("/zzz")
def zzz():
    xinhao.send(sender='xxx', a1=123, a2=456)  # 触发这个信号,执行注册到列表中的所有函数,这里的参数个上面函数的参数一致
    return "发送信号成功"


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

# 打印结果
# func1 (None,) {'sender': 'xxx', 'a1': 123, 'a2': 456}
# func2 (None,) {'sender': 'xxx', 'a1': 123, 'a2': 456}

问题:信号和before_request区别?

  • before_request,可以控制请求是否可以继续往后执行。
  • 信号,在原来的基础增加额外的操作和值。

多app应用

from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple

app01 = Flask('app01')
app02 = Flask('app02')

@app01.route('/login')
def login():
    return 'app01.login'

@app02.route('/index')
def index():
    return 'app02.index'


dm = DispatcherMiddleware(app01,{
				'/app02':        app02,
			})

if __name__ == '__main__':
	run_simple('localhost', 5000,dm)

源码:

class DispatcherMiddleware(object):

    """Allows one to mount middlewares or applications in a WSGI application.
    This is useful if you want to combine multiple WSGI applications::

        app = DispatcherMiddleware(app, {
            '/app2':        app2,
            '/app3':        app3
        })
    """

    def __init__(self, app, mounts=None):
        self.app = app
        self.mounts = mounts or {}
    # 请求来临时__call__方法被执行
    def __call__(self, environ, start_response):
        script = environ.get('PATH_INFO', '')
        path_info = ''
        while '/' in script:
            if script in self.mounts:  # 如果路由存在与self.mounts中,就说明是其他app的
                app = self.mounts[script]
                break
            script, last_item = script.rsplit('/', 1) # 倒序分割
            path_info = '/%s%s' % (last_item, path_info)# 将分割出去的在拼接起来
        else:
            app = self.mounts.get(script, self.app)
        original_script_name = environ.get('SCRIPT_NAME', '')
        environ['SCRIPT_NAME'] = original_script_name + script
        environ['PATH_INFO'] = path_info
        return app(environ, start_response) # app()执行

离线脚本编写

falsk中数据是在请求来临时通过RequestContext存在Local对象中,但是如果不是通过请求的方式我们要怎样获取数据

with falskobj.app_context():
    pass

源码:

class Flask(_PackageBoundObject):
    def app_context(self):
        return AppContext(self)

with 语法会去执行后面语句返回对象的__enter__方法,with语句结束后会执行__exit__方法
class AppContext(object):
    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.pop(exc_value)

        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
            reraise(exc_type, exc_value, tb)

这也是为什么使用LocalStack对Local对象进行操作?

目的是想要将local中的值维护成一个栈,例如:在多app应用中编写离线脚本时,可以实用上。
from m_app import app01,app02
from flask import current_app
"""
{
	1231: {
		stack: [app01,app02,]
		}
}
"""

with app01.app_context():
    print(current_app)
     with app02.app_context():
          print(current_app)
    print(current_app)

  

posted @ 2018-04-23 19:12  瓜田月夜  阅读(365)  评论(0编辑  收藏  举报