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()
蓝图的几个作用
- 为一个蓝图下的路由设置before_request和after_request
- 蓝图URL前缀:xxx = Blueprint('account', __name__,url_prefix='/xxx')
- 蓝图子域名: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)