Яка мета контекстних стеків Flask?


158

Я деякий час використовую контекст запиту / програми, не розуміючи, як він працює або чому він був розроблений таким, яким він був. Яка мета "стека", коли мова йде про запит або контекст програми? Це дві окремі стеки, або вони обидві частини однієї стеки? Чи контекст запиту висувається на стек, чи це сам стек? Чи я в змозі натиснути / попсувати декілька контекстів поверх одного? Якщо так, то чому б я хотів це зробити?

Вибачте за всі питання, але я все ще плутаюсь після прочитання документації для запиту контексту та контексту програми.


5
kronosapiens.github.io/blog/2014/08/14/… IMO, ця публікація в блозі дає мені найбільш зрозумілий опис контексту колби.
місія.liao

Відповіді:


243

Кілька додатків

Контекст програми (і його призначення) справді заплутаний, поки ви не зрозумієте, що Flask може мати кілька додатків. Уявіть ситуацію, коли ви хочете, щоб один інтерпретатор WSGI Python запустив декілька програм Flask. Ми не говоримо тут про креслення, ми говоримо зовсім про інші програми Flask.

Ви можете встановити це подібним до розділу документації на колбу на прикладі "Диспетчеризація програм" :

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 "frontend" та "backkend". Іншими словами, Flask(...)конструктор додатків викликався двічі, створюючи два екземпляри програми Flask.

Контексти

Працюючи з Flask, ви часто використовуєте глобальні змінні для доступу до різних функціональних можливостей. Наприклад, у вас, ймовірно, є код, який читає ...

from flask import request

Потім під час перегляду ви можете використовувати requestдля доступу до інформації поточного запиту. Очевидно, requestце не нормальна глобальна змінна; фактично це контекстне місцеве значення. Іншими словами, за лаштунками є якась магія, яка говорить "коли я дзвоню request.path, отримай pathатрибут від requestоб'єкта СУЧАСНОГО запиту". Два різні запити матимуть різні результати для request.path.

Насправді, навіть якщо ви запускаєте Flask з декількома потоками, Flask досить розумний, щоб тримати ізоляційні об'єкти запиту. При цьому стає можливим для двох потоків, кожен з яких обробляє різний запит, одночасно зателефонувати request.pathта отримати правильну інформацію для відповідних запитів.

Поєднання разом

Тож ми вже бачили, що Flask може обробляти декілька додатків в одному інтерпретаторі, а також тому, що Flask дозволяє вам використовувати "контекстні локальні" глобальні глобали, повинен існувати якийсь механізм для визначення того, що таке "поточний" запит ( для того, щоб робити такі речі, як request.path).

Збираючи ці ідеї разом, також має бути сенс, що Flask повинен мати певний спосіб визначити, що таке "поточна" програма!

Можливо, ви також маєте код, подібний до наступного:

from flask import url_for

Як у нашому requestприкладі, url_forфункція має логіку, яка залежить від поточного середовища. Однак у цьому випадку зрозуміло, що логіка сильно залежить від того, який додаток вважається "поточним" додатком. У прикладі frontend / backkend, показаному вище, і програми "frontend" і "backkend" можуть мати маршрут "/ login", і тому вони url_for('/login')повинні повертати щось інше в залежності від того, чи подання обробляє запит на додаток для фронтену чи резервного інтерфейсу.

Щоб відповісти на ваші запитання ...

Яка мета "стека", коли мова йде про запит або контекст програми?

З Документів контекстного запиту:

Оскільки контекст запиту внутрішньо підтримується як стек, ви можете натискати та виконувати кілька разів. Це дуже зручно для реалізації таких речей, як внутрішні переадресації.

Іншими словами, незважаючи на те, що зазвичай у вас є 0 або 1 елемент у цьому стеку "поточних" запитів або "поточних" додатків, можливо, у вас може бути більше.

Наведений приклад - коли ви хочете, щоб ваш запит повернув результати "внутрішнього переадресації". Скажімо, користувач запитує A, але ви хочете повернутися до користувача B. У більшості випадків ви надсилаєте перенаправлення на користувача та вказуєте користувачеві на ресурс B, тобто користувач виконує другий запит на отримання B. A дещо іншим способом обробки цього було б зробити внутрішнє переспрямування, що означає, що під час обробки A Flask зробить собі новий запит на ресурс B, а результати цього другого запиту використають як результати оригінального запиту користувача.

Це дві окремі стеки, або вони обидві частини однієї стеки?

Вони є двома окремими стеками . Однак, це деталь реалізації. Що важливіше, це не стільки те, що є стек, а те, що в будь-який час ви можете отримати "поточний" додаток або запит (у верхній частині стека).

Чи контекст запиту висувається на стек, чи це сам стек?

"Контекст запиту" - це один елемент "стека контексту запиту". Аналогічно з "контекстом програми" та "стеком контексту додатків".

Чи я в змозі натиснути / попсувати декілька контекстів поверх одного? Якщо так, то чому б я хотів це зробити?

У програмі Flask ти зазвичай цього не робиш. Один із прикладів того, де ви могли б хотіти, - це внутрішнє перенаправлення (описане вище). Навіть у такому випадку ви, мабуть, в кінцевому підсумку матимете Flask обробляти новий запит, і тому Flask зробив би всі натискання / вискакування для вас.

Однак є деякі випадки, коли ви хочете маніпулювати стеком самостійно.

Запуск коду поза запитом

Одна з типових проблем людей полягає в тому, що вони використовують розширення Flask-SQLAlchemy, щоб створити базу даних SQL та визначення моделі, використовуючи код, подібний до того, що показано нижче ...

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

Потім вони використовують значення appта dbзначення у скрипті, який слід запустити з оболонки. Наприклад, сценарій "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

У цьому випадку розширення Flask-SQLAlchemy знає про appдодаток, але під час create_all()нього буде видано помилку, нарікаючи на те, що не існує контексту програми. Ця помилка виправдана; ви ніколи не говорили Flask, з якою програмою має працювати, коли працює create_allметод.

Вам може бути цікаво, чому вам не потрібен цей 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 Якщо під час обробки запиту A ви робите запит B, а запит B замінює запит A на стеці, обробка запиту A не може закінчитися. Однак, навіть якщо ви виконали стратегію заміни, як запропонували, і не мали стека (тобто внутрішні переадресації були б складнішими), це насправді не змінює факту, що контексти додатків і запитів необхідні для ізоляції обробки запитів.
Марк Хілдрет

Гарне пояснення! Але я все ще трохи заплутаний щодо: "Контекст програми створюється та знищується за необхідності. Він ніколи не переміщується між потоками, і він не поділятиметься між запитами". У колбі документа. Чому поряд із додатком не зберігається "контекст програми"?
jayven

1
Приклад внутрішнього переадресації у Flask був би корисним, гугл він не сильно з'являється. Якби не це, чи не було б більш простого request = Local()дизайну для global.py? Ймовірно, є випадки використання, про які я не думаю.
ЧотириміснийA

Чи добре підштовхувати контекст програми всередині фабричного методу при імпорті переглядів? Оскільки представлення містять маршрути, що посилаються на current_app, мені потрібен контекст.
змінна

48

Попередні відповіді вже дають хороший огляд того, що відбувається у фоновому режимі під час запиту. Якщо ви ще цього не прочитали, рекомендую відповідь @ MarkHildreth перед її читанням. Коротше кажучи, для кожного запиту http створюється новий контекст (потік), тому необхідно мати об'єкт потоку Local, який дозволяє об'єкти, такі як requestіgбути доступними в усьому світі через потоки, зберігаючи конкретний контекст їх запиту. Крім того, обробляючи http-запит, Flask може імітувати додаткові запити зсередини, отже, необхідність зберігання відповідного контексту у стеку. Крім того, Flask дозволяє декільком програмам wsgi запускати один одного в межах одного процесу, і більше одного можна викликати до дії під час запиту (кожен запит створює новий контекст програми), отже, необхідність контекстного стеку для додатків. Це підсумок того, що було висвітлено у попередніх відповідях.

Моя мета зараз - доповнити наше сучасне розуміння, пояснивши, як Фляск і Веркгейг роблять те, що роблять з цими місцевими жителями. Я спростив код, щоб покращити розуміння його логіки, але якщо ви це отримаєте, ви зможете легко зрозуміти більшість того, що знаходиться у фактичному джерелі ( werkzeug.localі flask.globals).

Давайте спочатку розберемося, як Werkzeug реалізує локальні потоки.

Місцеві

Коли надходить http-запит, він обробляється в контексті одного потоку. Як альтернативне засіб для породження нового контексту під час http-запиту, Werkzeug також дозволяє використовувати зелені (на зразок легших "мікропотоків") замість звичайних ниток. Якщо у вас немає встановлених зелених грінок, він замість цього повернеться до використання тем. Кожен з цих потоків (або greenlets) ідентифікується за допомогою унікального ідентифікатора, який ви можете отримати за допомогою функції модуля get_ident(). Ця функція є відправною точкою до магії за мають request, current_app, url_for, g, та інші подібні контекстний палітуркою глобальних об'єктів.

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

Тепер, коли ми маємо свою функцію ідентичності, ми можемо знати, на якому потоці ми перебуваємо в будь-який момент часу, і ми можемо створити те, що називається потоком Local, контекстним об'єктом, до якого можна отримати доступ у всьому світі, але коли ви отримаєте доступ до його атрибутів, вони визначають їх значення для що конкретна нитка. напр

# 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об'єкти. Що таке LocalProxy?

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(тобто ви завантажуєте контекст_id із його сховища) і не турбуєтесь про проксі, вони все ще доступні в усьому світі і все одно відхиляються від того, Localщоб знайти свій об’єкт представляє інтерес для подальших http-запитів.

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

Щоб спростити створення, LocalProxyколи у нас вже є Local, Werkzeug реалізує 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_appі sessionстворені. Як ми встановили, 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і sessionоб'єкти рішення безпосередньо до 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()INTO _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об'єкт , який є проксі об'єкта на стеку зберігається в локальній. <- це те, що робить колба.

4
Чудовий час проживання, я вивчав код у flask / globals.py та werkzeug / local.py, і це допомагає уточнити моє розуміння цього. Моє чудове почуття говорить мені, що це дуже складний дизайн, але я визнаю, що я не розумію всіх випадків використання, для яких він призначений. "Внутрішні переадресації" - єдине обґрунтування, яке я бачив у описах вище, і гуглінг "внутрішньої переадресації колби" не виявляється сильно, тому я все ще трохи втрачаю. Одне з речей, які мені подобаються у колбі, це те, що це, як правило, не предмет об'єкта-супу Java, наповнений AbstractProviderContextBaseFactories тощо.
ЧотириміснийA

1
@QuadrupleA Після того, як ви зрозумієте, як це Local, LocalStackі LocalProxyпрацюють, я пропоную переглянути ці статті документа: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev та flask.pocoo .org / docs / 0,11 / reqcontext . Ваше свіже розуміння може дати вам побачити їх з новим світлом і може дати більше розуміння.
Майкл Екока

Прочитайте ці посилання - вони в основному мають сенс, але дизайн все ще вражає мене як складний і, можливо, занадто розумний для власного блага. Але я не є великим прихильником OOP в цілому, і неявні елементи управління потоком (переосмислюючи __call __ (), __getattr __ (), динамічна диспетчерація подій порівняно з простими функціями викликів, загортання речей у аксесуари властивостей, а не просто використання звичайного атрибута тощо .) тож, можливо, це лише різниця у філософії. Не є практиком TDD, який, як видається, призначений для підтримки багатьох цих додаткових механізмів.
ЧотириміснийA

1
Дякуємо, що поділилися цим, вдячні. Тематика - це слабкість з такими мовами, як python - ви виявляєте шаблони, подібні до вище, що просочуються в рамки додатків, які теж не дуже масштабні. Java - ще один приклад подібної ситуації. різьблення, семафор тощо Саме тут такі мови, як Erlang / Elixir (за допомогою BEAM) або підходи до циклу подій (наприклад, nginx vs apache тощо), як правило, пропонують більш потужний, масштабований та менш складний підхід.
arcseldon

13

Невелике доповнення відповідь @Mark Hildreth .

Контекстний стек виглядає так {thread.get_ident(): []}, де його []називають "стек", оскільки використовується лише операцій append( push) popта [-1]( __getitem__(-1)). Таким чином, контекстний стек буде зберігати фактичні дані для потоку або нитки greenlet.

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, шлях відповідності)
  2. введіть або натисніть цей запит:
    1. чіткий попередній request_context
    2. створити, app_contextякщо він пропущений і пересунутий на стек контексту програми
    3. цей запит підштовхнувся до запиту контекстного стека
    4. init сесія, якщо вона пропущена
  3. запит на відправлення
  4. чіткий запит і вискакуйте його зі стека

2

Візьмемо один приклад, припустимо, ви хочете встановити контекст користувача (використовуючи колбу з локальної та локальної програми).

Визначте один клас користувача:

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()))

Тепер, щоб отримати userid користувача в поточному потоці usercontext.userid

пояснення:

1.Local має набір ідентичності та objet, ідентичність - це нитковий або greenlet id, у цьому прикладі _local.user = Користувач () еквівалентний _local .___ зберігання __ [ідентифікатор поточного потоку] ["користувач"] = Користувач ()

  1. LocalProxy делегує операцію для завершення локального об'єкта або ви можете надати функцію, яка повертає цільовий об'єкт. У наведеному вище прикладі функція get_user надає об’єкт поточного користувача LocalProxy, і коли ви запитуєте поточного користувача Userid користувачем usercontext.userid, функція __getattr__ LocalProxy спочатку викликає get_user, щоб отримати User object (user), а потім викликає getattr (user, "userid"). щоб встановити userid на User (у поточній темі чи greenlet), ви просто зробите: usercontext.userid = "user_123"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.