Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
flask 是轻量级的,并且可扩展性强,可定制性强 的框架。适用于开发小型的网站。开发大型的网站也行,因为flask提供了很多第三方的组件,我们把flask与第三方的组件结合起来,也可以搭建一个与Django类似的,集成了很多功能的一个框架。
一. 基本使用
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run()
二、配置文件
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类或类的路径")
# 按照不同的情况 ;修改配置
app.config.from_object('settings.DevelopmentConfig')
#settings.py文件
#基础的配置类
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
#用于上线的配置类
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
#用于开发的配置类
class DevelopmentConfig(Config):
DEBUG = True
PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
写一个配置文件的字符串怎么找到配置信息的原理
# 'settings.DevelopmentConfig'通过切割,在importlib 加载模块,然后反射得到这个类的对象。dir(obj) 拿到这个类下面的所有属性和方法 # 再判断是大写的,这样就可以拿到这个配置下面的配置信息了。
原理的代码
import importlib
path = "settings.Foo"
p,c = path.rsplit('.',maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m,c)
# 如果找到这个类? #dir(cls)找到这个类下面的所有属性的字符串,不包含__init__下面的对象属性
for key in dir(cls):
if key.isupper():
print(key,getattr(cls,key)) #类.字符串
三、路由系统(带参数的装饰器)
@app.route('/index/<int:nid>',methods=['GET','POST'])
def index(nid):
print(nid)
return "Index"
- @app.route('/user/<username>')
- @app.route('/post/<int:post_id>')
- @app.route('/post/<float:post_id>')
- @app.route('/post/<path:path>')
- @app.route('/login', methods=['GET', 'POST'])
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
路由系统原理(带参数的装饰器原理)
'''
带参数的装饰器的原理
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
'''
'''
@app.route('/index', methods=["GET", "POST"])
执行的过程:
1.先执行
decorator = app.route('/index', methods=["GET", "POST"])
2.
@decorator
def index():
pass
decorator(index)
'''
路由系统里面的参数:
@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最后的 / 符号是否严格要求,
如:
@app.route('/index',strict_slashes=False),
访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可
@app.route('/index',strict_slashes=True)
仅访问 http://www.xx.com/index
redirect_to=None, 重定向到指定地址
如:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
或
def func(adapter, nid):
return "/home/888"
@app.route('/index/<int:nid>', redirect_to=func)
subdomain=None, 子域名访问
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
@app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "static.your-domain.tld"
@app.route("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return username + ".your-domain.tld"
if __name__ == '__main__':
app.run()
#子域名
from flask import Blueprint
public = Blueprint('public', __name__)
@public.route('/')
def home():
return 'hello flask'
app.py:
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='public')
现在可以通过public.example.com/来访问public模块了。
自定制正则路由匹配
from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter
app = Flask(import_name=__name__)
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.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()
b. 自定制正则路由匹配
# -*- coding: utf-8 -*-
# @Author: 曾辉
#1.定制类
from werkzeug.routing import BaseConverter
from flask import Flask,request,url_for,session
from flask_session import Session
from flask_session import RedisSessionInterface
import redis
app = Flask(__name__)
#在session数据存放在redis中, 存放的方式 和 Django 中session 是差不多的 , 把随机生成的特殊字符串放在cookies中,
#下次访问那着这个字符串 找redis里面去取值。
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.Redis(host='127.0.0.1',port=6379,password="zh4350697")
Session(app)
'''
自定义url匹配正则表达式
'''
# BaseConverter 父类里面有 to_python ;to_url
class MyurlRegex(BaseConverter):
def __init__(self,map,regex):
super().__init__(map)
self.regex = regex
# def to_python(self, value):
# return value
# def to_url(self, value):
#
# value = super().to_url(value)
#
# return value
#2.添加到转换器中
app.url_map.converters["reg"] = MyurlRegex
'''
执行的过程:
1.用户发送请求
2.flask内部进行正则匹配
3.调用to_python(返回匹配正则表达的结果)的方法
4. to_python方法的返回值会交给视图函数作为参数传入
5. to_url 方法是反向生成url的时候调用的,返回的就是url
'''
#3.使用自定义正则
@app.route("/index/<reg('d+'):nid>",methods=["GET"])
def index(nid):
#6 <class 'str'>
# nid =int(nid)
# print(nid)
#
#传入参数的反向生成url
# url = url_for("index", nid=987)
# print(url)
# <SecureCookieSession {'xxx': 123}> ---> session
session["xxx"]=123
print(session["xxx"])
return "..."
if __name__ == "__main__":
# app.__call__
#self.wsgi_app(environ, start_response)
app.run()
四、请求和响应
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
app = Flask(__name__)
@app.route('/login.html', methods=['GET', "POST"])
def login():
# 请求相关信息
# request.method 请求的方式
# request.args url的参数(GET请求的数据)
# request.form form表单(以字典的形式),ajax (POST请求的数据)
# request.values 返回GET和POST请求数据
# request.get_json() 接收json数据,并且返回 json反序列化 的数据
# request.cookies
# request.headers
# request.path /index ;是 不包含 ip 和 端口
# request.full_path
# request.script_root
# request.url 是全包含的;
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(f.filename))
# 响应相关信息
# return "字符串"
# return render_template('html模板路径',**{})
# return redirect('/index.html')
# 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
return "内容"
if __name__ == '__main__':
app.run()
五、Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
# 设置session,是建立在cookies上的,存放到浏览器上的,因此要对其加密后在存放。 # 设置session值时的加密 # app.secret_key = "asdasdsad" # session["user"] = "123"
设置:session['username'] = 'xxx'
删除:session.pop('username', None)
flask-session组件,把session放入redis里面
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
pip3 install redis
pip3 install flask-session
"""
from flask import Flask, session, redirect
from flask.ext.session import Session
app = Flask(__name__)
app.debug = True
app.secret_key = 'asdfasdfasd'
app.config['SESSION_TYPE'] = 'redis'
from redis import Redis
app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')
Session(app)
@app.route('/login')
def login():
session['username'] = 'alex'
return redirect('/index')
@app.route('/index')
def index():
name = session['username']
return name
if __name__ == '__main__':
app.run()
-闪现
- 基于session.pop 实现的,实现了 只能取一次的效果
-视图
-FBV
-CBV
from flask import Flask, redirect, request, url_for,views
app = Flask(__name__)
import functools
def authwarppe(func):
@functools.wraps(func)
def innder(*args,**kwargs):
#执行原函数
ret = func(*args,**kwargs)
return ret
return innder
# CBV
class UserView(views.MethodView):
#限制访问的方式
methods = ["GET"]
#批量的加装饰器
decorators = [authwarppe]
def get(self,*args,**kwargs):
# self.dispatch_request
return "get"
def post(self, *args, **kwargs):
return "post"
#路由与视图的关系
app.add_url_rule("/user",None,UserView.as_view("user")) #反向生成url -- user
#UserView.as_view -- view --- self.dispatch_request
if __name__ == "__main__":
app.run()
六、模板
1、模板的使用
Flask使用的是Jinja2模板,所以其语法和Django无差别
- 基本数据类型:可以执行python语法,如:dict.get() list['xx']
- 在模板中传入函数
- django,自动执行 {{func}}
- flask,不自动执行 {{func()}} 必须自己加括号才会执行函数,不然返回的是函数名字
- 全局定义函数
@app.template_global()
def sb(a1, a2):
# 在模板里面调用的方式 {{sb(1,9)}}
return a1 + a2
@app.template_filter()
def db(a1, a2, a3):
# 在模板里面调用的方式 {{ 1|db(2,3) }}
return a1 + a2 + a3
- 模板继承
layout.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>模板</h1>
{% block content %}{% endblock %}
</body>
</html>
tpl.html
{% extends "layout.html"%}
{% block content %}
{{users.0}}
{% endblock %}
- include
{% include "form.html" %}
form.html
<form>
asdfasdf
asdfasdf
asdf
asdf
</form>
- 宏
{% macro ccccc(name, type='text', value='') %}
<h1>宏</h1>
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
<input type="submit" value="提交">
{% endmacro %}
{{ ccccc('n1') }}
{{ ccccc('n2') }}
#以后在模板可以多次执行; 每调用一次就 执行一次;
- 安全
- 前端: {{u|safe}}
- 后端: MarkUp("asdf")
#全局的模板渲染
#全局定义函数
@app.template_global()
def add(x,y):
# 使用{{add(3,7)}}
return x*y
@app.template_global()
def template():
# 使用{{add(3,7)}}
return Markup('<input type="text">')
# Markup 就相当于 Django 中的 make_safe
@app.template_filter()
def pow(a,b,c,d):
# 使用{{6|pow(3,7,10)}}
return a+b+c+d
七、蓝图
蓝图用于为应用提供目录划分:
小型应用程序:示例
大型应用程序:示例
from flask import Blueprint
ac = Blueprint("ac",__name__)
其他: - 自定义的模板,静态文件 - 给某一类的Url添加前缀 app.register_blueprint(ac,url_prefix='/api') - 给某一类的url添加before_request @ac.before_request def xxx(): print("ac.before_request") @ac.route('/login') def login(): return render_template("login.html")
八、 特殊的装饰器:
1. before_request
2. after_request
示例:
from flask import Flask
app = Flask(__name__)
@app.before_request
def x1():
print('before:x1')
return '滚'
@app.before_request
def xx1():
print('before:xx1')
@app.after_request
def x2(response):
print('after:x2')
return response
@app.after_request
def xx2(response):
print('after:xx2')
return response
@app.route('/index')
def index():
print('index')
return "Index"
@app.route('/order')
def order():
print('order')
return "order"
if __name__ == '__main__':
app.run()
3. before_first_request
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def x1():
print('123123')
@app.route('/index')
def index():
print('index')
return "Index"
@app.route('/order')
def order():
print('order')
return "order"
if __name__ == '__main__':
app.run()
4. template_global
5. template_filter
6. errorhandler 自定制报错
@app.errorhandler(404)
def not_found(arg):
print(arg)
return "没找到"
#!/usr/bin/env python
# -*- coding:utf-8 -*-
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_first_request
def before_first_request2():
print('before_first_request2')
@app.before_request
def before_request1():
Request.nnn = 123
print('before_request1')
@app.before_request
def before_request2():
print('before_request2')
@app.after_request
def after_request1(response):
print('before_request1', response)
return response
@app.after_request
def after_request2(response):
print('before_request2', 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.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()
九、中间件
from flask import Flask
app = Flask(__name__)
@app.route('/index')
def index():
print('index')
return "Index"
class Middleware(object):
def __init__(self,old):
self.old = old
def __call__(self, *args, **kwargs):
print('请求前')
ret = self.old(*args, **kwargs)
print('请求后')
return ret
# return self.wsgi_app(environ, start_response)
if __name__ == '__main__':
app.wsgi_app = Middleware(app.wsgi_app)
app.run()
app.__call__
#当请求进来 第三个参数自动的帮你加括号;run_simple(host, port, self, **options)
# 对象() 执行 ---> 对象.__call__方法 (类里面的__call__);
十、Flask插件
总结:
视图函数中使用:request/session/g/current_app
注意:请求上下文和应用上下文需要先放入Local中,才能获取到。
from flask import Flask,current_app,request,session,g
app = Flask(__name__)
# 错误
# print(current_app.config)
@app.route('/index')
def index():
# 正确
#在视图函数中才把ctx,app_ctx放入对应的local中,才能取到。
print(current_app.config)
return "Index"
if __name__ == '__main__':
app.run()
为什么要把上下文管理分成:
- 请求上下文: request_ctx (request/session)
- 应用上下文: app_ctx(app/g)
主要是因为在写离线脚本的时候,没有请求,因此没有请求上下文,只有应用上下文。所有为了方便,要拆开。
正常情况下,stack的值的栈只有一个ctx
__restage__ = {
123:{"stack":[ctx]}
}
但是在多app离线脚本的时候,stack的值的栈 可能有多个app1_ctx,app2_ctx。
离线脚本:
#要应用到app上文管理 from crm import db,create_app app = create_app() app_ctx = app.app_context() with app_ctx: # with 对象 的时候就会自动的触发类的__enter__ 方法,然后执行下面的代码,最后执行__exit__ #__enter__是将app_ctx通过 LocalStack放入Local中, db.create_all() #会调用LocalStack 从Local中获取app,然后再从app中获取配置。 #__exit__ 是将当前的app_ctx对象从Local中移除掉
多app离线脚本;栈有多个app_ctx
from crm import db,create_app app = create_app() app_ctx = app.app_context() app_ctx2 = app.app_context() with app_ctx: # with 对象 的时候就会自动的触发类的__enter__ 方法,然后执行下面的代码,最后执行__exit__ with app_ctx2: #__enter__是将app_ctx通过 LocalStack放入Local中, #里面就是先用的app_ctx2。因为是同时把app_ctx2和app_ctx放入栈中,后进先出。 db.create_all() #会调用LocalStack 从Local中获取app,然后再从app中获取配置。 #__exit__ 是将当前的app_ctx对象从Local中移除掉