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中移除掉