其他:
- web.py
- bottle.py
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
官方 官方文档(中文)
django提供了:
而这些,flask都没有,都需要扩展包来提供。
Flask-SQLalchemy:操作数据库,ORM. 号称操作数据库最快的框架SQLalchemy;
Flask-script:终端脚本工具,脚手架;
Flask-migrate:管理迁移数据库. 比Django的更加强大, 迁移数据的过程中还可以回滚;
Flask-Session:Session存储方式指定;
Flask-WTF:表单;
Flask-Mail:邮件;
Flask-Bable:提供国际化和本地化支持,翻译;
Flask-Login:认证用户状态;
Flask-OpenID:认证, OAuth;
Flask-RESTful:开发REST API的工具;
Flask JSON-RPC: 开发rpc远程服务[过程]调用
Flask-Bootstrap:集成前端Twitter Bootstrap框架
Flask-Moment:本地化日期和时间
Flask-Admin:简单而可扩展的管理接口的框架
可以通过 https://pypi.org/search/?c=Framework+%3A%3A+Flask 查看更多flask官方推荐的扩展
pip install flask
介绍: Werkzeug是一个WSGI工具包,他可以作为一个Web框架的底层库。
补充: werkzeug 不是一个web服务器,也不是一个web框架,而是一个工具包,官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等
示例一:
from werkzeug.wrappers import Request, Response
@Request.application
def hello(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, hello)
示例二:
from werkzeug.wrappers import Request, Response
class Flask(object):
def __call__(self.serving,run_simple)
return '请求来了'
if __name__== 'main':
run_simple('localhost', 4000, hello)
示例三:
from werkzeug.wrappers import Request, Response
class Flask(object):
def __call__(self.serving,self):
return '请求来了'
def run(self):
run_simple('127.0.0.1',5000,self)
app =Flask()
if __name__== 'main':
app.run()
与django不同,flask不会提供任何的自动操作,所以需要手动创建项目目录,需要手动创建启动项目的管理文件
例如,创建项目目录 flaskdemo,在目录中创建manage.py.在pycharm中打开项目并指定上面创建的虚拟环境
名字可随便更改,可以是app.py/run.py/main.py/index.py
from flask import Flask
# 创建flask的应用对象
# __name__表示当前模块的名字
# 模块名,Flask这个模块所在的目录为总目录,默认这个目录中的static为静态目录,templates为模板目录
app = Flask(__name__)
# @app.route('/')
def hello_world():
'''定义的视图函数'''
return 'Hello World!'
if __name__ == '__main__':
# 注意:flask默认端口5000
app.run()
# 导入Flask类
from flask import Flask
"""
import_name Flask程序所在的包(模块),传 __name__ 就可以
其可以决定 Flask 在访问静态文件时查找的路径
static_path 静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path 静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder 静态文件存储的文件夹,可以不传,默认为 static
template_folder 模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)
# 编写路由视图
# flask的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中。
# flask的视图函数,flask中默认允许通过return返回html格式数据给客户端。
@app.route(rule='/')
def index():
return 'Hello Word!'
# 加载项目配置
class Config(object):
# 开启调试模式
DEBUG = True
# flask中支持多种配置方式,通过app.config来进行加载,我们会这里常用的是配置类
app.config.from_object( Config )
# 指定服务器IP和端口
if __name__ == '__main__':
# 运行flask
app.run(host="0.0.0.0", port=5000)
werkzeug
的wsgi实现,flask自己没有wsgi__call__
方法werkzeug
main.py
from flask import Flask,render_template,request,redirect,session,url_for
app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfsdfsdfsdf'
USERS = {
1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"},
2:{'name':'李四','age':28,'gender':'男','text':"搞钱第一条"},
3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"},
}
@app.route('/detail/' ,methods=['GET'])
def detail(nid):
user = session.get('user_info')
if not user:
return redirect('/login')
info = USERS.get(nid)
return render_template('detail.html',info=info)
@app.route('/index',methods=['GET'])
def index():
user = session.get('user_info')
if not user:
# return redirect('/login')
url = url_for('l1')
return redirect(url)
return render_template('index.html',user_dict=USERS)
@app.route('/login',methods=['GET','POST'],endpoint='l1')
def login():
if request.method == "GET":
return render_template('login.html')
else:
# request.query_string
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'lqz' and pwd == '123':
session['user_info'] = user
return redirect('http://www.baidu.com')
return render_template('login.html',error='用户名或密码错误')
if __name__ == '__main__':
app.run()
detail.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>详细信息 {{info.name}}h1>
<div>
{{info.text}}
div>
body>
html>
index.html
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1>用户列表h1> <table> {% for k,v in user_dict.items() %} <tr> <td>{{k}}td> <td>{{v.name}}td> <td>{{v['name']}}td> <td>{{v.get('name')}}td> <td><a href="/detail/{{k}}">查看详细a>td> tr> {% endfor %} table>body>html>
login.html
DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Titletitle>head><body> <h1>用户登录h1> <form method="post"> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="登录"><span style="color:red">{{error}}span> form>body>html>
flask路由
@app.route('/login')def login(): pass
路由的参数
@app.route('/login',methods=['GET','POST'],endpoint='l1')def login(): pass'''第一个参数:url第二个参数:支持的请求方法第三个参数:endpoint反向解析,不写默认为函数名,并且不能重名,重名就报错'''
动态路由
@app.route('/index')def login(): pass@app.route('/index/' )def index(name): pass@app.route('/index/int:' )def index(nid): pass
获取提交的数据
from flask import request@app.route('/index')def login(): request.args # GET请求传递的参数 request.form # POST请求传递的参数
返回数据
@app.route('/index')def login(): return render_template('模板文件') return jsonify(...) # 传递json格式的数据 return redirect('/index/') # 传递url return redirect(url_for('别名')) # 别名 return '字符串'
模板处理
{{值}}{%for item in list %} {{item}}{% endfor %}{# #} #注释
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'] = TruePS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
#通过py文件配置app.config.from_pyfile("python文件名称")如:settings.pyDEBUG = Trueapp.config.from_pyfile("settings.py")#通过环境变量配置app.config.from_envvar("环境变量名称")#app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])环境变量的值为python文件名称名称,内部调用from_pyfile方法app.config.from_json("json文件名称")JSON文件名称,必须是json格式,因为内部会执行json.loadsapp.config.from_mapping({'DEBUG': True})字典格式app.config.from_object("python类或类的路径")
settings.py
(用的最多)
app.config.from_object('pro_flask.settings.TestingConfig')class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:'class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo'class DevelopmentConfig(Config): DEBUG = Trueclass TestingConfig(Config): TESTING = TruePS: 从sys.path中已经存在路径开始写PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录(Flask对象init方法的参数)
# 指定访问路径为 [email protected](rule='/demo')def demo(): return "demo"
@app.route('/detail/' ,methods=['GET'],endpoint='detail')
# 不限定类型的路由参数传递@app.route(rule='/user/')def user(id): # 接受参数 return "id=%s的用户中心" % id# 路由参数理论上可以存在多个# 参数名建议不要使用单字母,因为有些单字母在框架中默认被占用了。@app.route(rule='/user1//')def user1(id, page): # 接受参数 return "id=%s,page=%s" % (id, page)
系统自带的转换器具体使用方式在每种转换器的注释代码中有写,请留意每种转换器初始化的参数。
转换器名称 | 描述 |
---|---|
string | 默认类型,接受不带斜杠的任何文本 |
int | 接受正整数 |
float | 接受正浮点值 |
path | 接收string 但也接受斜线 |
uuid | 接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx |
# 限定类型的路由参数传递
# 路由格式:<类型:参数名>
# 路由参数的类型,flask支持 int整型,float浮点数,path路径,uuid唯一识别码
@app.route(rule='/user2/' )
def user2(id):
print('type(id), id:', type(id), id) # type(id), id: 1
return f'Hello {id}!'
@app.route(rule='/user3/' )
def user3(id):
print('type(id), id:', type(id), id) # type(id), id: zcdsb123
return f'Hello {id}!'
@app.route(rule='/user4/' )
def user4(id):
print('type(id), id:', type(id), id) # type(id), id: 1.1
return f'Hello {id}!'
@app.route(rule='/user5/' )
def user5(id):
print('type(id), id:', type(id), id) # type(id), id: lxdsb/zcdsb
return f'Hello {id}!'
@app.route(rule='/user6/' )
def user6(id):
print('type(id), id:', type(id), id) # type(id), id: 95db2e6c-e7a7-11ea-9ca3-48ba4e4e6384
return f'Hello {id}!'
限定路由参数的类型,flask系统自带转换器编写在werkzeug.routing.py文件中。底部可以看到以下字典:
DEFAULT_CONVERTERS = { "default": UnicodeConverter, "string": UnicodeConverter, "any": AnyConverter, "path": PathConverter, "int": IntegerConverter, "float": FloatConverter, "uuid": UUIDConverter,}
app.add_url_rule()
"""1. decorator = app.route('/',methods=['GET','POST'],endpoint='n1') def route(self, rule, **options): # app对象 # rule= / # options = {methods=['GET','POST'],endpoint='n1'} def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator2. @decorator decorator(index)"""#同理def login(): return '登录'app.add_url_rule('/login', 'n2', login, methods=['GET',"POST"])#与django路由类似#django与flask路由:flask路由基于装饰器,本质是基于:add_url_rule#add_url_rule 源码中,endpoint如果为空,endpoint = _endpoint_from_view_func(view_func),最终取view_func.__name__(函数名)
简单示例:
from flask import Flaskapp = Flask(__name__)app.config['DEBUG'] = Falsedef index(): return 'index'# Flask路由本质就是`app.add_url_rule()`app.add_url_rule('/', endpoint='index', view_func=index)if __name__ == '__main__': app.run()
# 提示: 即是@app.route的参数, 本质也是app.add_url_rule的参数rule='/index' # 符合URL规则的路由view_func=index # 视图函数名称defaults=None # 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}可以为函数提供默认参数endpoint=None # 名称,用于反向生成URL,即: url_for('名称')methods=None # 允许的请求方式,如:["GET", "post"] 大小写都可以, 内部会执行upper()strict_slashes=None # 对URL最后的 / 符号是否严格要求, 默认严格. False就是不严格 @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/', redirect_to='/home/') subdomain = None # 子域名访问 # C:\Windows\System32\drivers\etc\hosts 127.0.0.1 www.liuqingzheng.com 127.0.0.1 admin.liuqingzheng.com 127.0.0.1 buy.liuqingzheng.com # 示例 from flask import Flask, views, url_for app = Flask(import_name=__name__) app.config['SERVER_NAME'] = 'liuqingzheng.com:5000' @app.route("/", subdomain="admin") def static_index(): return "static.your-domain.tld" # 可以传入任意的字符串,如传入的字符串为aa,显示为 aa.liuqingzheng.com @app.route("/dynamic", subdomain="") def username_index(username): return username + ".your-domain.tld" if __name__ == '__main__': app.run() # 支持访问的路由需要包含如下的域名访问格式:www dynamic admin http://www.liuqingzheng.com:5000/dynamic http://admin.liuqingzheng.com:5000/dynamic http://buy.liuqingzheng.com:5000/dynamic
基本步骤:
from flask import Flask, request# 初始化app = Flask(import_name=__name__)# 编写路由视图@app.route(rule='/')def index(): return "hello world!
"# 关于路由参数的限制,flask内置的类型不够具体,在开发中,我们经常接受参数,需要更加精确的限制# 这时候,可以使用正则匹配路由参数# 正则匹配路由参数,其实就是扩展flask内置的路由限定类型,需要完成4个步骤# 1. 引入flask的路由转换器from werkzeug.routing import BaseConverter# 2. 创建自定义路由转换器class MobileConverter(BaseConverter): """手机号码类型限制""" def __init__(self, map, *args): super().__init__(map) self.regex = "1[3-9]\d{9}"# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块app.url_map.converters['mob'] = MobileConverter# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可@app.route(rule='/user/')def user(mobile): return mobile# 1. 引入flask的路由转换器from werkzeug.routing import BaseConverter# 2. 创建自定义路由转换器class RegexConverter(BaseConverter): """根据正则进行参数限制""" def __init__(self, map, *args): super().__init__(map) self.regex = args[0] # args=(\w+@\w+\.\w+, )# 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块app.url_map.converters['re'] = RegexConverter# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可@app.route(rule='/user/')def user2(email): print(app.url_map) # 获取所有的路由列表 return email# 声明和加载配置class Config(): DEBUG = Trueapp.config.from_object(Config)if __name__ == '__main__': # 运行flask app.run(port=8000)
# 流程:
"""
1. 写类,继承BaseConverter
2. 注册:app.url_map.converters['regex'] = RegexConverter
3. 使用:@app.route('/index/') 正则表达式会当作第二个参数传递到类中
"""
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):
"""
路由匹配时,匹配成功后传递给视图函数中参数的值
"""
return int(value)
def to_url(self, value):
"""
使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
"""
val = super(RegexConverter, self).to_url(value)
return val
# 添加到flask中
app.url_map.converters['regex'] = RegexConverter
@app.route('/index/' )
def index(nid):
print(url_for('index', nid='888'))
return 'Index'
if __name__ == '__main__':
app.run()
django中的三件套:
Httpresponse ''
render render_template
redirect redirect
JsonResponse jsonify
def auth(func):
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result
return inner
class IndexView(views.View):
methods = ['GET']
decorators = [auth, ]
def dispatch_request(self):
print('Index')
return 'Index!'
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
#或者,通常用此方式
class IndexView(views.MethodView):
methods = ['GET']
decorators = [auth, ]
def get(self):
return 'Index.GET'
def post(self):
return 'Index.POST'
app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <h1>用户列表</h1> <table> {% for k,v in user_dict.items() %} <tr> <td>{{k}}</td> <td>{{v.name}}</td> <td>{{v['name']}}</td> <td>{{v.get('name')}}</td> <td><a href="/detail/{{k}}">查看详细</a></td> </tr> {% endfor %} </table></body></html>
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body> <h1>用户列表</h1> <table> {% for k,v in user_dict.items() %} <tr> <td>{{k}}</td> <td>{{v.name}}</td> <td>{{v['name']}}</td> <td>{{v.get('name')}}</td> <td><a href="/detail/{{k}}">查看详细</a></td> </tr> {% endfor %} </table></body></html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
</table>
</body>
</html>
app.py
from flask import Flask, Markup, render_template
app = Flask(__name__)
def func1(arg):
# Markup 类似于 Django中的make_save
return Markup("" % (arg,))
@app.route('/')
def index():
return render_template('xss.html', ff=func1)
if __name__ == '__main__':
app.run()
xss.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ff('六五')}}
<!-- safe和Django中过滤器中的safe方法一致 -->
{{ff('六五')|safe}}
</body>
</html>
注意:
Markup等价django的mark_safe ,
extends,include一模一样
from flask import Flask, request, jsonify, make_response
app = Flask(__name__)
app.debug = True
@app.route('/', methods=['GET', 'POST'])
def index():
from werkzeug.datastructures import CombinedMultiDict
# --------------------------- 请求对象 ---------------------------
# 获取当前请求方法
print(request.method) # POST
# 获取get请求提交的数据
print(request.args) # ImmutableMultiDict([('name', 'yang'), ('age', '18')])
print(type(request.args)) #
print(request.args.get('name')) # yang
from werkzeug.datastructures import ImmutableMultiDict
# 获取get形式提交的数据(提示: 需要自己转)
print(request.query_string) # b'name=yang&age=18'
# 获取post请求提交的数据
print(request.form) # ImmutableMultiDict([('name', 'yang666'), ('age', '22')])
# 获取文件
print(
request.files) # ImmutableMultiDict([('avatar', ), ('sql_file', )])
print(request.files.get('avatar')) # FileStorage: 'default.jpg' ('image/jpeg')>
# 获取post和get提交的数据总和
print(
request.values) # CombinedMultiDict([ImmutableMultiDict([('name', 'yang'), ('age', '18')]), ImmutableMultiDict([('name', 'yang666'), ('age', '22')])])
print(request.values.get('name')) # yang
print(request.values.get('age')) # 18
print(request.values.getlist('name')) # ['yang', 'yang666']
# 获取客户端所带的cookie
print(request.cookies) # {'key': 'woshicookies', 'user': 'yang'}
# 获取请求头中所携带的数据
print(request.headers)
'''
Yang: xiudetianhualuanzui
Cookie: key=woshicookies; xxx=lqz
User-Agent: PostmanRuntime/7.26.3
Accept: */*
Postman-Token: 40bb7ca6-ba5b-4d1a-9732-1f49cfef954f
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: multipart/form-data; boundary=--------------------------487226677414449536798986
Content-Length: 65491
'''
# 获取不带域名的请求路径
print(request.full_path) # /?name=yang&age=18
# 获取不带域名只带参数的请求路径
print(request.path) # /
# 获取带域名带参数的请求路径
print(request.url) # http://127.0.0.1:8080/?name=yang&age=18
# 获取带域名请求路径
print(request.base_url) # http://www.yang1333.com:8080/
# 获取域名
print(request.url_root) # http://www.yang1333.com:8080/
# 获取域名
print(request.host_url) # http://www.yang1333.com:8080/
# 获取当前请求用户IP: 127.0.0.1:500
print(request.host) # www.yang1333.com:8080
# --------------------------- 响应对象 ---------------------------
# 返回三板斧: 字符串, render_template, redirect
return "字符串"
return render_template('html模板路径', **{})
return redirect('/index.html')
# 返回JSON格式数据: 类似于Django中的JsonResponse对象
return jsonify({'k1': 'v1'})
# 返回response对象
string = 'hello world'
response = make_response(string)
# 设置cookie操作
response.set_cookie('key', 'value')
response.delete_cookie('key')
# 设置响应头
response.headers['X-Something'] = 'A value'
# response是flask.wrappers.Response类型
print(type(response))
from flask.wrappers import Response
return response
if __name__ == '__main__':
print(app.config)
app.run(port=8080)