Flask의 컨텍스트 스택의 목적은 무엇입니까?


158

나는 그것이 어떻게 작동하는지 또는 그것이 원래의 방식으로 설계된 이유를 완전히 이해하지 않고 한동안 요청 / 응용 프로그램 컨텍스트를 사용하고 있습니다. "스택"의 목적은 요청 또는 애플리케이션 컨텍스트와 관련하여 무엇입니까? 이 두 개의 별도 스택입니까, 아니면 둘 다 한 스택의 일부입니까? 요청 컨텍스트가 스택으로 푸시됩니까, 아니면 스택 자체입니까? 여러 컨텍스트를 서로 밀거나 팝할 수 있습니까? 그렇다면 왜 그렇게하고 싶습니까?

모든 질문에 대해 죄송하지만 요청 컨텍스트 및 응용 프로그램 컨텍스트에 대한 설명서를 읽은 후에도 여전히 혼란 스럽습니다.


5
kronosapiens.github.io/blog/2014/08/14/… IMO,이 블로그 게시물은 플라스크 컨텍스트에 대한 가장 이해하기 쉬운 설명을 제공합니다.
mission.liao

답변:


243

여러 앱

Flask에 여러 개의 앱이있을 수 있다는 사실을 알기 전까지는 애플리케이션 컨텍스트와 그 목적이 혼란 스럽습니다. 단일 WSGI Python 인터프리터가 여러 Flask 응용 프로그램을 실행하려는 상황을 상상해보십시오. 여기서는 블루 프린트를 말하는 것이 아니라 완전히 다른 플라스크 애플리케이션을 말하는 것입니다.

"Application Dispatching" 예제 의 Flask documentation 섹션 과 유사하게이를 설정할 수 있습니다 .

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

"프론트 엔드"와 "백엔드"라는 두 개의 완전히 다른 Flask 응용 프로그램이 있습니다. 즉, Flask(...)애플리케이션 생성자가 두 번 호출되어 Flask 애플리케이션의 두 인스턴스가 작성되었습니다.

문맥

Flask로 작업 할 때 종종 전역 변수를 사용하여 다양한 기능에 액세스하게됩니다. 예를 들어, 아마도 다음과 같은 코드가있을 것입니다.

from flask import request

그런 다음보기 중에 request현재 요청 정보에 액세스하는 데 사용할 수 있습니다. 분명히 request일반적인 전역 변수는 아닙니다. 실제로는 컨텍스트 로컬 값입니다. 다시 말해, "내가 호출 할 때 CURRENT 요청 의 객체 로부터 속성을 request.path얻는다 "는 등의 마술이있다 . 두 개의 다른 요청은에 대해 다른 결과를 갖습니다 .pathrequestrequest.path

실제로 여러 스레드로 Flask를 실행하더라도 Flask는 요청 객체를 격리 할 수있을 정도로 똑똑합니다. 그렇게하면 각각 다른 요청을 처리하는 두 스레드가 동시에 request.path해당 요청에 대한 올바른 정보를 호출 하고 얻을 수 있습니다.

그것을 함께 넣어

따라서 우리는 이미 Flask가 동일한 인터프리터에서 여러 응용 프로그램을 처리 할 수 ​​있으며 Flask를 사용하여 "컨텍스트 로컬"글로벌을 사용할 수 있기 때문에 "현재" 요청 이 무엇인지 결정하는 메커니즘이 있어야합니다 ( request.path) 와 같은 작업을 수행 합니다.

이러한 아이디어를 종합하면 Flask는 "현재"응용 프로그램이 무엇인지 확인할 방법이 있어야합니다.

다음과 유사한 코드가있을 수도 있습니다.

from flask import url_for

request예와 같이 url_for함수에는 현재 환경에 따라 다른 논리가 있습니다. 그러나이 경우 논리가 어떤 앱이 "현재"앱으로 간주되는지에 크게 의존한다는 것을 알 수 있습니다. 위에 표시된 프런트 엔드 / 백엔드 예제에서 "프론트 엔드"및 "백엔드"앱은 모두 "/ 로그인"경로를 가질 수 있으므로 url_for('/login')뷰가 프런트 엔드 또는 백엔드 앱에 대한 요청을 처리하는지에 따라 다른 것을 반환해야합니다.

질문에 대답하려면 ...

"스택"의 목적은 요청 또는 애플리케이션 컨텍스트와 관련하여 무엇입니까?

요청 컨텍스트 문서에서 :

요청 컨텍스트는 내부적으로 스택으로 유지 관리되므로 여러 번 푸시 및 팝할 수 있습니다. 내부 리디렉션과 같은 것을 구현하는 데 매우 편리합니다.

다시 말해, 일반적으로 이러한 "현재"요청 또는 "현재"응용 프로그램 스택에 0 개 또는 1 개의 항목이 있지만 더 많은 것을 가질 수 있습니다.

주어진 예는 요청이 "내부 리디렉션"의 결과를 반환하도록하는 위치입니다. 사용자가 A를 요청하지만 사용자 B에게 되돌아 가고 싶다고 가정 해 보겠습니다. 대부분의 경우 사용자에게 리디렉션을 발행하고 사용자를 리소스 B로 지정하면 사용자가 B를 가져 오기 위해 두 번째 요청을 실행하게됩니다. 이를 처리하는 약간 다른 방법은 내부 경로 재 지정을 수행하는 것입니다. 즉, A를 처리하는 동안 Flask는 자원 B에 대한 새 요청을 작성하고이 두 번째 요청의 결과를 사용자의 원래 요청 결과로 사용합니다.

이 두 개의 별도 스택입니까, 아니면 둘 다 한 스택의 일부입니까?

그것들은 두 개의 분리 된 스택 입니다. 그러나 이것은 구현 세부 사항입니다. 더 중요한 것은 스택이 너무 많지 않지만 언제든지 "현재"앱 또는 요청을 얻을 수 있다는 사실입니다 (스택 상단).

요청 컨텍스트가 스택으로 푸시됩니까, 아니면 스택 자체입니까?

"요청 컨텍스트"는 "요청 컨텍스트 스택"의 한 항목입니다. "앱 컨텍스트"및 "앱 컨텍스트 스택"과 유사합니다.

여러 컨텍스트를 서로 밀거나 팝할 수 있습니까? 그렇다면 왜 그렇게하고 싶습니까?

Flask 응용 프로그램에서는 일반적으로이 작업을 수행하지 않습니다. 내부 리디렉션의 예는 위에서 설명한 것입니다. 그러나이 경우에도 Flask가 새로운 요청을 처리하게되므로 Flask는 모든 푸시 / 팝핑을 수행하게됩니다.

그러나 스택을 직접 조작하려는 경우가 있습니다.

요청 외부에서 코드 실행

사람들이 가지고있는 전형적인 문제 중 하나는 Flask-SQLAlchemy 확장을 사용하여 아래에 표시된 것과 같은 코드를 사용하여 SQL 데이터베이스 및 모델 정의를 설정한다는 것입니다.

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

그런 다음 셸에서 실행해야하는 스크립트에서 appdb값 을 사용합니다 . 예를 들어 "setup_tables.py"스크립트는 ...

from myapp import app, db

# Set up models
db.create_all()

이 경우 Flask-SQLAlchemy 확장 app프로그램 은 응용 프로그램 에 대해 알고 있지만 create_all()응용 프로그램 컨텍스트가 없다는 불평 오류가 발생합니다. 이 오류는 정당화됩니다. create_all메소드를 실행할 때 어떤 애플리케이션을 처리해야하는지 Flask에게 말한 적이 없습니다 .

with app.app_context()뷰에서 유사한 함수를 실행할 때 왜이 호출 이 필요하지 않은지 궁금 할 것 입니다. Flask는 실제 웹 요청을 처리 할 때 이미 응용 프로그램 컨텍스트 관리를 처리하기 때문입니다. 문제는 실제로 일회용 스크립트에서 모델을 사용할 때와 같이 이러한 뷰 함수 (또는 다른 콜백) 외부에서만 발생합니다.

해결 방법은 응용 프로그램 컨텍스트를 직접 푸시하는 것입니다.

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

이렇게하면 새로운 응용 프로그램 컨텍스트가 적용됩니다 (의 응용 프로그램을 사용하면 app둘 이상의 응용 프로그램이있을 수 있음을 기억하십시오).

테스팅

스택을 조작하려는 또 다른 경우는 테스트입니다. 요청을 처리하는 단위 테스트를 작성하고 결과를 확인할 수 있습니다.

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty

3
이것은 여전히 ​​나에게 혼란입니다! 하나의 단일 요청 컨텍스트가없고 내부 리디렉션을 수행하려는 경우 대체하십시오. 분명한 디자인 인 것 같습니다.
Maarten

@Maarten 요청 A를 처리하는 동안 요청 B를 만들고 요청 B가 스택에서 요청 A를 대체하면 요청 A에 대한 처리를 완료 할 수 없습니다. 그러나 제안한대로 대체 전략을 수행하고 스택을 가지고 있지 않은 경우에도 (내부 리디렉션이 더 어려울 수 있음) 이것은 요청 처리를 분리하기 위해 앱 및 요청 컨텍스트가 필요하다는 사실을 실제로 변경하지는 않습니다.
Mark Hildreth

좋은 설명입니다! 그러나 나는 여전히 약간 혼란스러워한다. "필요에 따라 애플리케이션 컨텍스트가 생성되고 파괴된다. 스레드 간에는 절대 이동하지 않으며 요청간에 공유되지 않는다." 플라스크 문서에서. "응용 프로그램 컨텍스트"가 앱과 함께 유지되지 않는 이유는 무엇입니까?
jayven

1
Flask의 내부 리디렉션 예제는 도움이 될 것입니다. 그렇지 않다면 request = Local()global.py에 대한 단순한 디자인으로는 충분 하지 않습니까? 내가 생각하지 않는 유스 케이스가있을 수 있습니다.
QuadrupleA 5

뷰를 가져올 때 팩토리 메소드 내에서 앱 컨텍스트를 푸시하는 것이 좋습니까? 뷰에는 current_app를 참조하는 경로가 포함되어 있으므로 컨텍스트가 필요합니다.
변수

48

이전 답변은 이미 요청하는 동안 플라스크 배경에서 진행되는 작업에 대한 훌륭한 개요를 제공합니다. 아직 읽지 않았다면 이것을 읽기 전에 @MarkHildreth의 답변을 권장합니다. 요컨대, 각각의 http 요청에 대해 새로운 컨텍스트 (스레드)가 생성 Local되므로 requestand와 같은 객체를 허용 하는 스레드 기능이 필요합니다.g요청 특정 컨텍스트를 유지하면서 스레드 전체에서 액세스 할 수 있습니다. 또한 http 요청을 처리하는 동안 Flask는 내부에서 추가 요청을 에뮬레이트 할 수 있으므로 해당 컨텍스트를 스택에 저장해야합니다. 또한 Flask를 사용하면 단일 프로세스 내에서 여러 wsgi 응용 프로그램을 서로 실행할 수 있으며 요청하는 동안 하나 이상의 작업을 호출하여 호출 할 수 있습니다 (각 요청은 새로운 응용 프로그램 컨텍스트를 생성 함). 따라서 응용 프로그램에 대한 컨텍스트 스택이 필요합니다. 그것은 이전 답변에서 다룬 내용에 대한 요약입니다.

내 목표는 이제 설명하여 우리의 현재 이해를 보완하는 방법 플라스크와 WERKZEUG들이 이러한 상황에 맞는 지역 주민과 함께해야합니까 것. 논리를 더 잘 이해하기 위해 코드를 단순화했지만이 정보를 얻으면 실제 소스 ( werkzeug.localflask.globals) 에있는 대부분의 내용을 쉽게 파악할 수 있습니다 .

Werkzeug가 스레드 로컬을 구현하는 방법을 먼저 이해합시다.

현지

http 요청이 들어 오면 단일 스레드 컨텍스트 내에서 처리됩니다. http 요청 중에 새로운 컨텍스트를 생성하기위한 대안으로 Werkzeug는 일반 스레드 대신 그린 릿 (일부 더 가벼운 "마이크로 스레드")을 사용할 수 있습니다. Greenlet이 설치되어 있지 않으면 대신 스레드 사용으로 되돌아갑니다. 이 스레드 (또는 그린 릿) 각각은 고유 한 ID로 식별 할 수 있으며 모듈의 get_ident()기능으로 검색 할 수 있습니다 . 그 기능을 가진 뒤에 마법의 시작 지점입니다 request, current_app, url_for, g, 및 기타 상황에 바인딩 전역 개체.

try:
    from greenlet import get_ident
except ImportError:
    from thread import get_ident

이제 우리는 identity 함수를 사용하여 주어진 시간에 어떤 스레드를 사용하고 있는지를 알 Local수 있으며 전역 적으로 액세스 할 수있는 컨텍스트 객체 인 thread를 만들 수 있습니다. 그러나 속성에 액세스 할 때 속성은 해당 값으로 확인됩니다 그 특정 스레드. 예 :

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

두 값 모두 전역 적으로 액세스 가능한 Local객체에 동시에 존재 하지만 local.first_name스레드 1의 컨텍스트 내에서 액세스 하면을 제공 'John'하지만 'Debbie'스레드 2 에서는 반환 됩니다.

어떻게 가능합니까? 단순화 된 코드를 살펴 보자.

class Local(object)
    def __init__(self):
        self.storage = {}

    def __getattr__(self, name):
        context_id = get_ident() # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value

    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)

local = Local()

위의 코드에서 우리는 get_ident()현재 그린 릿 또는 스레드를 식별하는 마법이 끓는 것을 볼 수 있습니다 . Local저장소는 단지 현재 스레드에 모든 데이터 콘텐츠를 저장하기위한 키로서 그것을 이용한다.

여러 가질 수 Local프로세스 당 객체를하고 request, g, current_app및 다른 사람은 간단하게 그렇게 만들 수 있었다. 그러나 이것이 기술적으로 Local 객체가 아니라보다 정확하게 LocalProxy객체 Flask에서 수행 된 방식은 아닙니다 . 무엇입니까 LocalProxy?

로컬 프록시

LocalProxy는 a Local를 쿼리하여 관심있는 다른 객체 (즉 프록시하는 객체)를 찾는 객체입니다. 이해해 봅시다 :

class LocalProxy(object):
    def __init__(self, local, name):
        # `local` here is either an actual `Local` object, that can be used
        # to find the object of interest, here identified by `name`, or it's
        # a callable that can resolve to that proxied object
        self.local = local
        # `name` is an identifier that will be passed to the local to find the
        # object of interest.
        self.name = name

    def _get_current_object(self):
        # if `self.local` is truly a `Local` it means that it implements
        # the `__release_local__()` method which, as its name implies, is
        # normally used to release the local. We simply look for it here
        # to identify which is actually a Local and which is rather just
        # a callable:
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # if self.local is not actually a Local it must be a callable that 
        # would resolve to the object of interest.
        return self.local(self.name)

    # Now for the LocalProxy to perform its intended duties i.e. proxying 
    # to an underlying object located somewhere in a Local, we turn all magic
    # methods into proxies for the same methods in the object of interest.
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

    def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    # ... and so on ...

    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __str__ = lambda x: str(x._get_current_object())
    __lt__ = lambda x, o: x._get_current_object() < o
    __le__ = lambda x, o: x._get_current_object() <= o
    __eq__ = lambda x, o: x._get_current_object() == o

    # ... and so forth ...

이제 전 세계적으로 접근 가능한 프록시를 만들려면

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

이제 요청이 진행되는 동안 어느 시점에 어떤 스레드에 관계없이 이전에 만든 프록시가 액세스 할 수있는 일부 객체를 로컬에 저장합니다

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

스스로 LocalProxy액세스하는 대신 전역 액세스 가능한 개체로 사용 하면 개체 Locals관리가 간소화된다는 이점이 있습니다. Local전역 적으로 액세스 가능한 많은 프록시를 만들려면 단일 개체 만 있으면 됩니다. 요청이 끝나면 정리하는 동안 단순히 하나를 해제하고 Local(즉, 저장소에서 context_id를 팝) 프록시에 신경 쓰지 않고 여전히 전역 적으로 액세스 할 수 있으며 여전히 Local객체를 찾기 위해 연기 합니다. 후속 http 요청에 관심이 있습니다.

# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()

Werkzeug는 이미을 LocalProxy가지고있을 때 의 생성을 단순화하기 위해 다음과 같이 마술 방법을 Local구현합니다 Local.__call__().

class Local(object):
    # ... 
    # ... all same stuff as before go here ...
    # ... 

    def __call__(self, name):
        return LocalProxy(self, name)

# now you can do
local = Local()
request = local('request')
g = local('g')

그러나, 당신은 어떻게 아직도의 플라스크 소스 (flask.globals)에 보면 request, g, current_appsession생성됩니다. 우리가 확립 한대로 Flask는 하나의 실제 http 요청에서 여러 개의 "가짜"요청을 생성 할 수 있으며 그 과정에서 여러 응용 프로그램 컨텍스트를 푸시 할 수도 있습니다. 이것은 일반적인 사용 사례는 아니지만 프레임 워크의 기능입니다. 이러한 "동시"요청 및 앱은 여전히 ​​"초점"을 가진 하나만 실행되도록 제한되기 때문에 각각의 컨텍스트에 스택을 사용하는 것이 좋습니다. 새 요청이 생성되거나 응용 프로그램 중 하나가 호출 될 때마다 해당 스택의 맨 위에 컨텍스트를 푸시합니다. 플라스크는 LocalStack이를 위해 객체를 사용 합니다. 그들이 사업을 마치면 스택에서 맥락을 드러냅니다.

LocalStack

이것은 LocalStack코드 의 모양을 이해하기 위해 코드가 단순화 된 모습입니다.

class LocalStack(object):

    def __init__(self):
        self.local = Local()

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self.local, 'stack', None)
        if rv is None:
            self.local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None

위에서 a LocalStack는 스택에 저장된 로컬 그룹이 아니라 로컬에 저장된 스택입니다. 이것은 스택에 전역 적으로 액세스 할 수 있지만 각 스레드에서 다른 스택이라는 것을 의미합니다.

플라스크가없는 request, current_app, g, 그리고 sessionA를 직접 해결하는 객체 LocalStack, 오히려 사용하여 LocalProxy객체를 그 랩 조회 기능 (대신의 Local로부터 기본 개체를 찾을 수 객체) LocalStack:

_request_ctx_stack = LocalStack()
def _find_request():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.request
request = LocalProxy(_find_request)

def _find_session():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.session
session = LocalProxy(_find_session)

_app_ctx_stack = LocalStack()
def _find_g():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.g
g = LocalProxy(_find_g)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
current_app = LocalProxy(_find_app)

이들 모두는 응용 프로그램 시작시 선언되지만 요청 컨텍스트 또는 응용 프로그램 컨텍스트가 해당 스택으로 푸시 될 때까지 실제로는 아무것도 해결하지 않습니다.

컨텍스트에 실제로 스택이 삽입되는 방법과 궁금한 점이 궁금하다면 flask.app.Flask.wsgi_app()wsgi 앱의 시작 지점 (즉, 웹 서버가 호출하고 http 환경을 전달할 대상)을 확인하십시오. 요청)에 제공하고,의 생성에 따라 RequestContext모든 후속 통해 객체를 push()_request_ctx_stack. 스택 상단으로 밀면을 통해 액세스 할 수 있습니다 _request_ctx_stack.top. 흐름을 보여주기위한 약식 코드는 다음과 같습니다.

따라서 앱을 시작하고 WSGI 서버에서 사용할 수있게합니다.

app = Flask(*config, **kwconfig)

# ...

나중에 http 요청이 들어오고 WSGI 서버는 일반적인 매개 변수로 앱을 호출합니다 ...

app(environ, start_response) # aka app.__call__(environ, start_response)

이것은 대략 앱에서 발생하는 것입니다 ...

def Flask(object):

    # ...

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        ctx = RequestContext(self, environ)
        ctx.push()
        try:
            # process the request here
            # raise error if any
            # return Response
        finally:
            ctx.pop()

    # ...

그리고 이것은 대략 RequestContext에서 일어나는 일입니다 ...

class RequestContext(object):

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

요청 초기화가 완료되었다고 가정하면 request.path뷰 함수 중 하나 에서 조회가 다음과 같이 진행됩니다.

  • 전역 적으로 접근 가능한 LocalProxy객체 에서 시작 합니다 request.
  • 기본 관심 객체 (프록시 대상 객체)를 찾으려면 조회 함수 _find_request()(로 등록 된 함수)를 호출합니다 self.local.
  • 해당 함수 는 스택의 최상위 컨텍스트에 대해 LocalStack객체 _request_ctx_stack를 쿼리합니다 .
  • 최상위 컨텍스트를 찾기 위해 LocalStack객체는 먼저 내부 Local속성 ( self.local)에 stack이전에 저장된 속성을 쿼리합니다 .
  • stack그것 에서 그것은 최고 컨텍스트를 얻는다
  • top.request따라서 관심의 기본 개체로 확인됩니다.
  • 그 객체에서 우리는 path속성 을 얻습니다

우리가 방법을 살펴 보았다 그래서 Local, LocalProxy그리고 LocalStack일을, 이제 검색의 의미와 뉘앙스의 순간에 대한 생각 path에서 :

  • request세계적으로 간단한 접근 객체가 될 것이다 객체입니다.
  • request로컬 될 객체입니다.
  • request목적은 지역의 속성으로서 저장된다.
  • request로컬에 저장된 객체에 대한 프록시 객체.
  • request차례로 로컬에 저장되어있는 스택에 저장된 객체.
  • request로컬에 저장된 스택 객체에 대한 프록시 객체. <-Flask가하는 일입니다.

4
훌륭한 런 다운, flask / globals.py 및 werkzeug / local.py에서 코드를 연구 해 왔으며 이것이 내 이해를 분명히하는 데 도움이됩니다. 내 욕심은 이것이 복잡한 디자인이라는 것을 말해 주지만, 의도 된 모든 사용 사례를 이해하지 못한다는 것을 인정합니다. "내부 리디렉션"은 위의 설명에서 본 유일한 정당화이며, "플래시 내부 리디렉션"인터넷 검색은 많이 나타나지 않으므로 여전히 약간의 손실이 있습니다. 내가 플라스크에 대해 좋아하는 것 중 하나는 일반적으로 AbstractProviderContextBaseFactories 등으로 가득 찬 Java 객체 수프 유형이 아닙니다.
QuadrupleA 5

1
@QuadrupleA 당신이 어떻게 이해하면 Local, LocalStack그리고 LocalProxy일, 나는이 다큐먼트의 기사를 다시 방문하는 것이 좋습니다 : flask.pocoo.org/docs/0.11/appcontext를 , flask.pocoo.org/docs/0.11/extensiondevflask.pocoo .org / docs / 0.11 / reqcontext . 당신의 새로운 파악은 당신이 그들을 새로운 빛으로 볼 수있게하고 더 많은 통찰력을 제공 할 수 있습니다.
Michael Ekoka

그 링크를 읽으십시오-그것들은 대부분 의미가 있지만 디자인은 여전히 ​​복잡하고 자신의 이익을 위해 너무 영리하다고 생각합니다. 그러나 나는 일반적으로 OOP를 좋아하지 않으며 암시 적 흐름 제어 항목 (__call __ (), __getattr __ () 재정의, 동적 이벤트 디스패치 대 간단한 함수 호출, 일반 속성을 사용하는 대신 속성 접근 자에 래핑 등) ) 아마도 철학의 차이 일뿐입니다. TDD 실무자도 아닙니다.이 추가 기계의 많은 부분이 지원하려고합니다.
QuadrupleA

1
이것을 공유해 주셔서 감사합니다. 스레딩은 파이썬과 같은 언어의 약점입니다. 위와 같은 패턴으로 응용 프로그램 프레임 워크에 유출되고 실제로 확장되지 않는 패턴이 있습니다. Java는 비슷한 상황에서 또 다른 예입니다. threadlocals, semaphors 등. 정답을 얻거나 유지하기가 어렵다. Erlang / Elixir (BEAM 사용) 또는 이벤트 루프 접근 방식 (예 : nginx vs apache 등)과 같은 언어는 일반적으로 더 강력하고 확장 가능하며 덜 복잡한 접근 방식을 제공합니다.
arcseldon

13

@ Mark Hildreth 의 답변은 거의 없습니다 .

같은 컨텍스트 스택 모양 {thread.get_ident(): []}, []사용했기 때문에 "스택"에만 호출 append( push) pop[-1]( __getitem__(-1)) 작업. 따라서 컨텍스트 스택은 스레드 또는 그린 릿 스레드에 대한 실제 데이터를 유지합니다.

current_app, g, request, session등이다 LocalProxy단지 특별한 방법을 오버라이드 객체 __getattr__, __getitem__, __call__, __eq__등 및 컨텍스트 스택 최상부 (의 리턴 값 [-1](인수 이름) current_app, request예를 들면). LocalProxy이 오브젝트를 한 번 가져와야하므로 실제로 놓칠 수 없습니다. 따라서 request코드의 어느 곳에서나 가져 오는 것이 더 좋으며 대신 함수 및 메소드에 요청 인수를 전송하여 재생하십시오. 당신은 그것으로 자신의 확장을 쉽게 작성할 수 있지만, 방대한 사용법으로 코드를 이해하기가 더 어려워 질 수 있다는 것을 잊지 마십시오.

https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py 를 이해하는 데 시간을 보내십시오 .

그렇다면 두 스택을 모두 채우는 방법은 무엇입니까? 요청시 Flask:

  1. request_context환경별로 생성 (init map_adapter, match path)
  2. 이 요청을 입력하거나 푸시하십시오.
    1. 이전 지우기 request_context
    2. app_context누락되어 응용 프로그램 컨텍스트 스택으로 푸시되면 작성
    3. 이 요청은 컨텍스트 스택을 요청하도록 푸시되었습니다.
    4. 누락 된 경우 초기화 세션
  3. 파견 요청
  4. 요청을 지우고 스택에서 팝

2

예를 들어, 사용자 컨텍스트를 설정한다고 가정하십시오 (Local 및 LocalProxy의 플라스크 구성 사용).

하나의 사용자 클래스를 정의하십시오.

class User(object):
    def __init__(self):
        self.userid = None

현재 스레드 또는 Greenlet 내에서 사용자 오브젝트를 검색하는 함수를 정의하십시오.

def get_user(_local):
    try:
        # get user object in current thread or greenlet
        return _local.user
    except AttributeError:
        # if user object is not set in current thread ,set empty user object 
       _local.user = User()
    return _local.user

이제 LocalProxy를 정의하십시오

usercontext = LocalProxy(partial(get_user, Local()))

현재 스레드에서 사용자의 사용자 ID를 얻으려면 usercontext.userid

설명 :

1. Local은 identity 및 objet을 가지며, identity는 threadid 또는 greenlet id입니다.이 예에서 _local.user = User ()는 _local .___ storage __ [현재 스레드의 id] [ "user"] = User ()와 동일합니다.

  1. LocalProxy는 로컬 오브젝트를 랩핑하도록 조작을 위임 하거나 대상 오브젝트를 리턴하는 함수를 제공 할 수 있습니다. 위 예제에서 get_user 함수는 현재 사용자 객체를 LocalProxy에 제공하고 usercontext.userid로 현재 사용자의 사용자 ID를 요청할 때 LocalProxy의 __getattr__ 함수는 먼저 get_user를 호출하여 User 객체 (user)를 가져온 다음 getattr (user, "userid")를 호출합니다. User (현재 스레드 또는 Greenlet)에서 userid를 설정하려면 다음을 수행하십시오. usercontext.userid = "user_123"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.