Flask路由以及视图
url->views,实现原理
Flask中的 路由设置与django的有很大的区别,因为,Flask的轻量级,所以,Flask的用法会更简单:
在views上面加上装饰器,app.route() 即可。url的命名,反向解析,接受的请求的格式的处理,这一切都只需要填写参数即可。
route()的源码:
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 """ 可以看见,这个装饰器,本身执行的是 decorator方法, 在decorator中,调用了 add_url_rule 方法。 从方法的字面意思上理解,就是添加 url 到rule去。 下面看一下 add_url_rule的代码: """ def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): if endpoint is None: endpoint = _endpoint_from_view_func(view_func) options['endpoint'] = endpoint methods = options.pop('methods', None) if methods is None: methods = getattr(view_func, 'methods', None) or ('GET',) if isinstance(methods, string_types): raise TypeError('Allowed methods have to be iterables of strings, ' 'for example: @app.route(..., methods=["POST"])') methods = set(item.upper() for item in methods) required_methods = set(getattr(view_func, 'required_methods', ())) if provide_automatic_options is None: provide_automatic_options = getattr(view_func, 'provide_automatic_options', None) if provide_automatic_options is None: if 'OPTIONS' not in methods: provide_automatic_options = True required_methods.add('OPTIONS') else: provide_automatic_options = False methods |= required_methods rule = self.url_rule_class(rule, methods=methods, **options) rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule) if view_func is not None: old_func = self.view_functions.get(endpoint) if old_func is not None and old_func != view_func: raise AssertionError('View function mapping is overwriting an ' 'existing endpoint function: %s' % endpoint) self.view_functions[endpoint] = view_func
看到上面的代码可以知道,Flask 建立url——》view 路由视图关系 正真的执行的是 add_url_rule() 函数,
并且,将路由(“/index”)这个封装了一个 Rule类,最后全部封装到了 url_map 里面。
所以,如果想要添加一个路由关系, 除了加装饰器之外还可以直接调用这个方法:
示例:
from flask import Flask app = Flask(__name__) app.config["DEBUG"] = True def index(): return "index" app.add_url_rule("/index", f=index) if __name__ == "__main__": app.run()
视图函数详解
- app.route()参数解析:
- rule: 路径; 字符串形式的例如: "/index"
- 路径添加参数:@app.route("/index/<int:id>"):
表示 从将id 传入视图函数, 且必须为int类型;
当有参数的时候, 在反向解析的时候, 需要将参数传入 例:url_for("index", nid=999 )
示例:
from flask import Flask app = Flask(__name__) app.config["DEBUG"] = True @app.route("/index/<int:id>") def index(id): print(id, type(id)) return "index" if __name__ == "__main__": app.run()
- 自定义路由中的参数类型:
- 自定义一个继承 BaseConverter 的类 从werkzeug.routing 中导入。
- app.url_map.converters["reg"] = 自定义的类
- 在app.route("/index/<reg(\d+):nid>")中使用。
- 当请求进来时会先实例化自定义的类,并且将nid的值传入到类中的to_python 函数中并调用
- 当在反向生成url的时候会执行 to_url 的函数,也就是说在CBV中的 url_for() 会先执行to_url 函数。
- 示例:
from flask import Flask from werkzeug.routing import BaseConverter class MyConverter(BaseConverter): def __init__(self, map, regex): super(MyConverter, 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(MyConverter, self).to_url(value) return val app = Flask(__name__) app.url_map.converters['reg'] = MyConverter @app.route('/index/<reg("\d+"):nid>') def index(nid): print(nid, type(nid)) print(url_for('index', nid=987)) return "index" if __name__ == '__main__': app.run()
- methods:请求方式; 列表中放字符串 例如: ["POST", "GET"]
- endpoint:别名;用于反向解析, 如果没有定义的话,默认为被装饰的view函数的名字;
- 注意:不用让endpoint重名;如果重名函数也一定要相同
- redirect_to:重定向;当访问原来的url的时候,重定向到新的视图函数中; 实例中用到的地方,就是当项目跟新后,旧的页面不想在被访问后,可以在用户访问的时候,直接跳转到新的页面。
- strict_slashes:对URL最后的 / 符号是否严格要求; 默认值为None
- view_func:视图函数的名字
- ... 还有几个其他的参数,具体参数,可以去Rule类中查看
class Rule(RuleFactory): def __init__(self, string, defaults=None, subdomain=None, methods=None, build_only=False, endpoint=None, strict_slashes=None, redirect_to=None, alias=False, host=None): if not string.startswith('/'): raise ValueError('urls must start with a leading slash') self.rule = string self.is_leaf = not string.endswith('/') self.map = None self.strict_slashes = strict_slashes self.subdomain = subdomain self.host = host self.defaults = defaults self.build_only = build_only self.alias = alias if methods is None: self.methods = None else: if isinstance(methods, str): raise TypeError('param `methods` should be `Iterable[str]`, not `str`') self.methods = set([x.upper() for x in methods]) if 'HEAD' not in self.methods and 'GET' in self.methods: self.methods.add('HEAD') self.endpoint = endpoint self.redirect_to = redirect_to if defaults: self.arguments = set(map(str, defaults)) else: self.arguments = set() self._trace = self._converters = self._regex = self._argument_weights = None def empty(self): """ Return an unbound copy of this rule. This can be useful if want to reuse an already bound URL for another map. See ``get_empty_kwargs`` to override what keyword arguments are provided to the new copy. """ return type(self)(self.rule, **self.get_empty_kwargs()) def get_empty_kwargs(self): """ Provides kwargs for instantiating empty copy with empty() Use this method to provide custom keyword arguments to the subclass of ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass has custom keyword arguments that are needed at instantiation. Must return a ``dict`` that will be provided as kwargs to the new instance of ``Rule``, following the initial ``self.rule`` value which is always provided as the first, required positional argument. """ defaults = None if self.defaults: defaults = dict(self.defaults) return dict(defaults=defaults, subdomain=self.subdomain, methods=self.methods, build_only=self.build_only, endpoint=self.endpoint, strict_slashes=self.strict_slashes, redirect_to=self.redirect_to, alias=self.alias, host=self.host) def get_rules(self, map): yield self def refresh(self): """Rebinds and refreshes the URL. Call this if you modified the rule in place. :internal: """ self.bind(self.map, rebind=True) def bind(self, map, rebind=False): """Bind the url to a map and create a regular expression based on the information from the rule itself and the defaults from the map. :internal: """ if self.map is not None and not rebind: raise RuntimeError('url rule %r already bound to map %r' % (self, self.map)) self.map = map if self.strict_slashes is None: self.strict_slashes = map.strict_slashes if self.subdomain is None: self.subdomain = map.default_subdomain self.compile() def get_converter(self, variable_name, converter_name, args, kwargs): """Looks up the converter for the given parameter. .. versionadded:: 0.9 """ if converter_name not in self.map.converters: raise LookupError('the converter %r does not exist' % converter_name) return self.map.converters[converter_name](self.map, *args, **kwargs) def compile(self): """Compiles the regular expression and stores it.""" assert self.map is not None, 'rule not bound' if self.map.host_matching: domain_rule = self.host or '' else: domain_rule = self.subdomain or '' self._trace = [] self._converters = {} self._static_weights = [] self._argument_weights = [] regex_parts = [] def _build_regex(rule): index = 0 for converter, arguments, variable in parse_rule(rule): if converter is None: regex_parts.append(re.escape(variable)) self._trace.append((False, variable)) for part in variable.split('/'): if part: self._static_weights.append((index, -len(part))) else: if arguments: c_args, c_kwargs = parse_converter_args(arguments) else: c_args = () c_kwargs = {} convobj = self.get_converter( variable, converter, c_args, c_kwargs) regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex)) self._converters[variable] = convobj self._trace.append((True, variable)) self._argument_weights.append(convobj.weight) self.arguments.add(str(variable)) index = index + 1 _build_regex(domain_rule) regex_parts.append('\\|') self._trace.append((False, '|')) _build_regex(self.is_leaf and self.rule or self.rule.rstrip('/')) if not self.is_leaf: self._trace.append((False, '/')) if self.build_only: return regex = r'^%s%s$' % ( u''.join(regex_parts), (not self.is_leaf or not self.strict_slashes) and '(?<!/)(?P<__suffix__>/?)' or '' ) self._regex = re.compile(regex, re.UNICODE) def match(self, path, method=None): """Check if the rule matches a given path. Path is a string in the form ``"subdomain|/path"`` and is assembled by the map. If the map is doing host matching the subdomain part will be the host instead. If the rule matches a dict with the converted values is returned, otherwise the return value is `None`. :internal: """ if not self.build_only: m = self._regex.search(path) if m is not None: groups = m.groupdict() # we have a folder like part of the url without a trailing # slash and strict slashes enabled. raise an exception that # tells the map to redirect to the same url but with a # trailing slash if self.strict_slashes and not self.is_leaf and \ not groups.pop('__suffix__') and \ (method is None or self.methods is None or method in self.methods): raise RequestSlash() # if we are not in strict slashes mode we have to remove # a __suffix__ elif not self.strict_slashes: del groups['__suffix__'] result = {} for name, value in iteritems(groups): try: value = self._converters[name].to_python(value) except ValidationError: return result[str(name)] = value if self.defaults: result.update(self.defaults) if self.alias and self.map.redirect_defaults: raise RequestAliasRedirect(result) return result def build(self, values, append_unknown=True): """Assembles the relative url for that rule and the subdomain. If building doesn't work for some reasons `None` is returned. :internal: """ tmp = [] add = tmp.append processed = set(self.arguments) for is_dynamic, data in self._trace: if is_dynamic: try: add(self._converters[data].to_url(values[data])) except ValidationError: return processed.add(data) else: add(url_quote(to_bytes(data, self.map.charset), safe='/:|+')) domain_part, url = (u''.join(tmp)).split(u'|', 1) if append_unknown: query_vars = MultiDict(values) for key in processed: if key in query_vars: del query_vars[key] if query_vars: url += u'?' + url_encode(query_vars, charset=self.map.charset, sort=self.map.sort_parameters, key=self.map.sort_key) return domain_part, url def provides_defaults_for(self, rule): """Check if this rule has defaults for a given rule. :internal: """ return not self.build_only and self.defaults and \ self.endpoint == rule.endpoint and self != rule and \ self.arguments == rule.arguments def suitable_for(self, values, method=None): """Check if the dict of values has enough data for url generation. :internal: """ # if a method was given explicitly and that method is not supported # by this rule, this rule is not suitable. if method is not None and self.methods is not None \ and method not in self.methods: return False defaults = self.defaults or () # all arguments required must be either in the defaults dict or # the value dictionary otherwise it's not suitable for key in self.arguments: if key not in defaults and key not in values: return False # in case defaults are given we ensure taht either the value was # skipped or the value is the same as the default value. if defaults: for key, value in iteritems(defaults): if key in values and value != values[key]: return False return True def match_compare_key(self): """The match compare key for sorting. Current implementation: 1. rules without any arguments come first for performance reasons only as we expect them to match faster and some common ones usually don't have any arguments (index pages etc.) 2. rules with more static parts come first so the second argument is the negative length of the number of the static weights. 3. we order by static weights, which is a combination of index and length 4. The more complex rules come first so the next argument is the negative length of the number of argument weights. 5. lastly we order by the actual argument weights. :internal: """ return bool(self.arguments), -len(self._static_weights), self._static_weights,\ -len(self._argument_weights), self._argument_weights def build_compare_key(self): """The build compare key for sorting. :internal: """ return self.alias and 1 or 0, -len(self.arguments), \ -len(self.defaults or ()) def __eq__(self, other): return self.__class__ is other.__class__ and \ self._trace == other._trace __hash__ = None def __ne__(self, other): return not self.__eq__(other) def __str__(self): return self.rule @native_string_result def __repr__(self): if self.map is None: return u'<%s (unbound)>' % self.__class__.__name__ tmp = [] for is_dynamic, data in self._trace: if is_dynamic: tmp.append(u'<%s>' % data) else: tmp.append(data) return u'<%s %s%s -> %s>' % ( self.__class__.__name__, repr((u''.join(tmp)).lstrip(u'|')).lstrip(u'u'), self.methods is not None and u' (%s)' % u', '.join(self.methods) or u'', self.endpoint )
- 视图中的FBV(function base views):
- 这里的FBV的用法和Django基本一样,直接写函数里面的逻辑就行了,不过需要注意的一点是,因为Flask的前后文管理,这里不需要每个函数都接收一个request;
- 使用request,只需要从Flask中导入即可,
- render,redirect 也只需从Flask导入即可,只不过,Flask中render叫做 render_template;
- 视图中的CBV(class base views):
- 自定义一个继承 views.MethodView 的类。
- 绑定url: app.add_url_rule("url", None, Cls.as_view() )
- 限制接受的请求:通过设置静态字段method 就可以限制 例如:methods = ["GET", "POST"]
- 增加装饰器:通过设置静态字段 decorators = [装饰器的函数名字]; 这样会自动在执行视图的时候,提前执行装饰器。
- 示例解析:
import functools from flask import Flask,views app = Flask(__name__) def wrapper(func): @functools.wraps(func) def inner(*args,**kwargs): return func(*args,**kwargs) return inner class UserView(views.MethodView): methods = ['GET',"POST"] decorators = [wrapper,] # 这里会让每次请求进来后都去执行装饰器,不论是什么请求 def get(self,*args,**kwargs): return 'GET' def post(self,*args,**kwargs): return 'POST' app.add_url_rule('/user',None,UserView.as_view('uuuu')) if __name__ == '__main__': app.run()
反向解析
在app.route中的参数,endpoint 是用来给路由起别名的,
在函数中,如果想用到这个别名,需要用 url_for() 来反向解析。
- 当url中有动态传参的时候,在用url_for 的时候,需要先将参数传入
- 示例:
from flask import Flask, url_for, redirect app = Flask(__name__) app.config["DEBUG"] = True @app.route("/index/<int:id>", endpoint="index") def index(id): print(id, type(id)) url_for("index", id=999) return redirect(url_for("index", id=999)) if __name__ == "__main__": app.run()
视图中特殊的装饰器
类似Django中的中间件:
before_request:
- 被该装饰器装饰,会在执行视图函数之前执行
- 当有多个被该装饰器装饰时, 按照位置 谁在前,谁先执行
- before_first_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.route('/index') def index(): print('index') return "Index" @app.route('/order') def order(): print('order') return "order" if __name__ == '__main__': app.run()
after_request:
- 被该装饰器装饰的时候 需要接收response 并且返回response, 且在执行视图函数之后执行。
- 当有多个被该装饰器装饰时, 按照位置 谁在前,谁后执行
- errorhandler (定制错误页面):该装饰器是在报404错误时返回response 会走被这个装饰器装饰的函数。
- 示例:
from flask import Flask app = Flask(__name__) @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()
模板HTML用到的两个装饰器:
template_global:
- 在模板中用到
- 定义全局函数,定义完此函数后,在任何的模板中都可以调用被此装饰器装饰的函数
- 示例:
@app.template_global() def sb(a1, a2): # {{sb(1,9)}} return a1 + a2 # 模板中的用法: {{sb(1,9)}} # 实际页面为: 10
template_filter:
- 在模板中用到
- 也是全局函数, 不过此函数在调用的时候,需要用到管道符 |
- 示例:
""" py文件 """ @app.template_filter() def db(a1, a2, a3): # {{ 1|db(2,3) }} return a1 + a2 + a3 """ HTML """ <p>{{1|db(2,3)}}</p> """ 实际页面: 6 """