Flask 탐색2(app.route 내부 구현)

16849 단어
최소 flask 애플리케이션
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

전편 블로그는flask의 각 매개 변수의 작용을 탐구하였으며, 본편은 @app를 둘러싸고 있습니다.route('/') flask가 뭘 했는지 탐구해 보세요.
route 방법
route 소스
    def route(self, rule, **options):
        """A decorator that is used to register a view function for a
        given URL rule.  This does the same thing as :meth:`add_url_rule`
        but is intended for decorator usage::

            @app.route('/')
            def index():
                return 'Hello World'

        For more information refer to :ref:`url-route-registrations`.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
                        Starting with Flask 0.6, ``OPTIONS`` is implicitly
                        added and handled by the standard request handling.
        """

        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

route는 사실상 폐쇄적인 패키지입니다. 경로 규칙은route 방법을 통해rule형참에 인용된 다음decorator 방법을 되돌려줍니다. 그래서 @ app입니다.route('/')<==>@decorator, 그래서 helloworld =decorator (hello_world ) <==> hello_world . @app.route('/')의 주작은endpoint = options.pop('endpoint', None) 및 self.add_url_rule(rule,endpoint,f,**options) 두 마디.
add_url_rule
add_url_rule 소스
    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):

        """Connects a URL rule.  Works exactly like the :meth:`route`
        decorator.  If a view_func is provided it will be registered with the
        endpoint.

        Basically this example::

            @app.route('/')
            def index():
                pass

        Is equivalent to the following::

            def index():
                pass
            app.add_url_rule('/', 'index', index)

        If the view_func is not provided you will need to connect the endpoint
        to a view function like so::

            app.view_functions['index'] = index

        Internally :meth:`route` invokes :meth:`add_url_rule` so if you want
        to customize the behavior via subclassing you only need to change
        this method.

        For more information refer to :ref:`url-route-registrations`.

        .. versionchanged:: 0.2
           `view_func` parameter added.

        .. versionchanged:: 0.6
           ``OPTIONS`` is added automatically as method.

        :param rule: the URL rule as string
        :param endpoint: the endpoint for the registered URL rule.  Flask
                         itself assumes the name of the view function as
                         endpoint
        :param view_func: the function to call when serving a request to the
                          provided endpoint
        :param provide_automatic_options: controls whether the ``OPTIONS``
            method should be added automatically. This can also be controlled
            by setting the ``view_func.provide_automatic_options = False``
            before adding the rule.
        :param options: the options to be forwarded to the underlying
                        :class:`~werkzeug.routing.Rule` object.  A change
                        to Werkzeug is handling of method options.  methods
                        is a list of methods this rule should be limited
                        to (``GET``, ``POST`` etc.).  By default a rule
                        just listens for ``GET`` (and implicitly ``HEAD``).
                        Starting with Flask 0.6, ``OPTIONS`` is implicitly
                        added and handled by the standard request handling.
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        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)

        # Methods that should always be added
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        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

        # Add the required methods now.
        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


다른 바인딩 방식
방법 주석에서 url 규칙과 함수를 연결할 수 있는 다른 방식을 볼 수 있다
    @app.route('/')
    def index():
                pass
    
    #    
    def index():
        pass
        
    # add_url_rule(url   ,    ,      )
    app.add_url_rule('/', 'index', index)


분석하다.
     if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

장식기를 통해 경로를 등록하면 일반적으로 endpoint는 None과 같기 때문에 endpoint=endpoint_from_view_func(view_func).
_endpoint_from_view_func
def _endpoint_from_view_func(view_func):
    """Internal helper that returns the default endpoint for a given
    function.  This always is the function name.
    """
    assert view_func is not None, 'expected view func if endpoint is not provided.'
    return view_func.__name__

보기endpoint_from_view_func 방법, endpoint = viewfunc.__name__, 장식기를 통해 경로를 등록할 수 있으며, 일반적인 경우endpoint는 방법명과 같습니다.
분석하다.
  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)

실험 일반상황에서viewfunc는methods 속성이 없습니다. 원본 코드를 수정하여 원본 코드를 실험하기 편리합니다
  #     
  print(endpoint + " #" * 20)
  #        methods
  print(methods)
  
  if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
  
  #        methods
  print(methods)
  #     
  print(endpoint + " #" * 20)

  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)

demo.py
@app.route('/')
def index():
    return 'index'


@app.route('/', methods=["POST"])
def index1():
    return 'index'


@app.route('/', methods=["POST", "GET"])
def index2():
    return 'index'


실험 결과
static # # # # # # # # # # # # # # # # # # # #
None
('GET',)
static # # # # # # # # # # # # # # # # # # # #
index # # # # # # # # # # # # # # # # # # # #
None
('GET',)
index # # # # # # # # # # # # # # # # # # # #
index1 # # # # # # # # # # # # # # # # # # # #
['POST']
['POST']
index1 # # # # # # # # # # # # # # # # # # # #
index2 # # # # # # # # # # # # # # # # # # # #
['POST', 'GET']
['POST', 'GET']
index2 # # # # # # # # # # # # # # # # # # # #

위의 결과를 통해 알 수 있다
  • 기본적으로 @app를 통해route (루트 규칙) 방식으로 보기 함수를 연결합니다. methods는 처음에 None
  • 입니다.
  • if판단을 거쳤을 때getattr를 통해view 획득func의methods 속성, 결합 또는 논리로methods 변수에 값을 부여
  • if를 거친 후methods는 ('GET',)
  • @app를 사용할 경우.route('/', methods=["POST"]) 또는 @app.route('/', methods=["POST", "GET"))는 키 값 쌍을 통해 methods에 값을 부여하고 methods는 None이 아니다.

  • 분석하다.
    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)
    

    이 두 마디를 통해 methods는 키 값을 통해 형식에 값을 부여하고 methods="POST"의 형식을 제외한 모든 교체 가능한 용기는 값으로 할 수 있다는 결론을 얻을 수 있다.
    분석하다.
    원본 코드
    # Methods that should always be added
    required_methods = set(getattr(view_func, 'required_methods', ()))
    
    # starting with Flask 0.8 the view_func object can disable and
    # force-enable the automatic options handling.
    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
    
    # Add the required methods now.
    methods |= required_methods
    
    

    실험 원본 코드 개조
            # Methods that should always be added
            required_methods = set(getattr(view_func, 'required_methods', ()))
    
            # starting with Flask 0.8 the view_func object can disable and
            # force-enable the automatic options handling.
            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
            # --------------------------------------
            print("methods ", methods)
            print("required_methods ", required_methods)
    
            # Add the required methods now.
            methods |= required_methods
    
            print("methods ", methods)
            print("required_methods ", required_methods) 
            print("*" * 20)
    
    

    demo.py
    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def index():
        return 'index'
    
    
    @app.route('/', methods=("POST", "GET"))
    def index2():
        return 'index'
    
    
    if __name__ == '__main__':
        app.run(debug=True)
    
    

    결실
    methods  {'GET'}
    required_methods  {'OPTIONS'}
    methods  {'OPTIONS', 'GET'}
    required_methods  {'OPTIONS'}
    ********************
    methods  {'GET'}
    required_methods  {'OPTIONS'}
    methods  {'OPTIONS', 'GET'}
    required_methods  {'OPTIONS'}
    ********************
    methods  {'POST', 'GET'}
    required_methods  {'OPTIONS'}
    methods  {'POST', 'OPTIONS', 'GET'}
    required_methods  {'OPTIONS'}
    ********************
    

    실험을 통해 알 수 있듯이,
  • 기본적으로 Requiredmethods = set(getattr(view func,'required methods', ())은 None,
  • provide_automatic_options 기본값은 None이며 키 값 쌍을 통과하지 않는 경우provideautomatic_options 전송, provideautomatic_options = getattr(view func,'provide automatic options', None)의 값은 여전히 None
  • 이다.
  • methods 기본값("GET",), "OPTIONS"가 없으므로provideautomatic_options = True , required_methods.add('OPTIONS')
  • methods |= required_methods는 집합을 합쳐서 집합하고 값을 methods에 부여하며 이전의 토대에서OPTIONS를 증가시켰다.

  • 분석하다.
    원본 코드
        #     rule   
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
    
        #  rule    Map  
        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
    

    실험 원본 코드 변경
     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:
    
                print("%-10s %s" % ("endpoint", endpoint))
    
                old_func = self.view_functions.get(endpoint)
    
                print("%-20s %s" % ("old_func is not None", old_func is not None))
                print("%-20s %s" % ("old_func != view_func", old_func != view_func))
                print()
                print("*" * 20)
    
                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
    

    demo.py
    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def index():
        return 'index'
    
    #          
    app.add_url_rule("/", "index", index)
    
    app.add_url_rule("/", "index2", index)
    
      
    if __name__ == '__main__':
        app.run(debug=True)
    
    

    실험 결과
    #       
    endpoint   static
    old_func is not None False
    old_func != view_func True
    
    ********************
    #       
    endpoint   index
    old_func is not None False
    old_func != view_func True
    
    ********************
    #          
    endpoint   index
    old_func is not None True
    old_func != view_func False
    
    ********************
    #       
    endpoint   index2
    old_func is not None False
    old_func != view_func True
    
    ********************
    
  • view_func는 일반적으로 None이 아니며 endpoint는 일반적으로 방법명이기 때문에oldfunc는 방법의 인용입니다.
  • 새로운 보기 함수인 경우 static index index2,oldfunc = self.view_functions.get(endpoint) ,old_func의 값은 None
  • 입니다.
  • 새로운 보기 함수가 아니지만 같은 보기 함수를 사용하면oldfunc != None, 하지만oldfunc = view_func
  • self.view_functions[endpoint] = view_func는 단점을 키로 사용하여 보기 함수의 인용viewfunc를 self에 값으로 추가합니다.view_functions

  • 요약:
  • 귀속시도 함수는 두 가지 방식이 있다
  • endpoint: 단점은 일반적으로 보기 함수 이름
  • methods: 기본값("GET",), 기본값methods에 OPTIONS 요청방식 추가
  • 판단을 통해self.view_functions에 단점을 키로 하는 값이 있는지,viewfunc 보기 함수의 인용으로 보기 함수를self에 추가할 필요가 있는지 판단합니다.view_functions 사전
  • DragonFangQy 2018.6.20

    좋은 웹페이지 즐겨찾기