Какова цель контекстных стеков Flask?

158

Я использовал контекст запроса / приложения в течение некоторого времени, не полностью понимая, как он работает или почему он был разработан таким, каким он был. Какова цель «стека», когда дело доходит до запроса или контекста приложения? Это два отдельных стека, или они оба являются частью одного стека? Контекст запроса помещается в стек или это сам стек? Могу ли я вставить / вставить несколько контекстов поверх друг друга? Если так, зачем мне это делать?

Извините за все вопросы, но я все еще растерялся после прочтения документации по контексту запроса и контексту приложения.

Бен Дэвис
источник
5
kronosapiens.github.io/blog/2014/08/14/… IMO, этот пост в блоге дает мне наиболее понятное описание контекста колбы.
mission.liao

Ответы:

243

Несколько приложений

Контекст приложения (и его назначение) действительно сбивает с толку, пока вы не поймете, что Flask может иметь несколько приложений. Представьте себе ситуацию, когда вы хотите, чтобы один интерпретатор Python WSGI запускал несколько приложений Flask. Мы не говорим здесь о Blueprints, мы говорим о совершенно разных приложениях 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: «внешний интерфейс» и «внутренний интерфейс». Другими словами, Flask(...)конструктор приложения вызывался дважды, создавая два экземпляра приложения Flask.

Контексты

Когда вы работаете с Flask, вы часто используете глобальные переменные для доступа к различным функциям. Например, у вас, вероятно, есть код, который читает ...

from flask import request

Затем, во время просмотра, вы можете использовать requestдля доступа к информации текущего запроса. Очевидно, requestэто не нормальная глобальная переменная; в действительности это контекстная локальная ценность. Другими словами, за кулисами скрывается какое-то волшебство: «когда я звоню request.path, получи pathатрибут из requestобъекта запроса CURRENT». Два разных запроса будут иметь разные результаты для request.path.

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

Положить его вместе

Итак, мы уже видели, что Flask может обрабатывать несколько приложений в одном и том же интерпретаторе, а также что из-за того, что Flask позволяет вам использовать «контекстные локальные» глобальные переменные, должен быть какой-то механизм, чтобы определить, что такое «текущий» запрос ( для того, чтобы делать такие вещи, как request.path).

Объединяя эти идеи, также должно иметь смысл, что у Flask должен быть какой-то способ определить, что такое «текущее» приложение!

У вас, вероятно, также есть код, подобный следующему:

from flask import url_for

Как и в нашем requestпримере, url_forфункция имеет логику, которая зависит от текущей среды. В этом случае, однако, ясно, что логика сильно зависит от того, какое приложение считается «текущим» приложением. В приведенном выше примере frontend / backend приложения «frontend» и «backend» могут иметь маршрут «/ login» и поэтому url_for('/login')должны возвращать что-то другое в зависимости от того, обрабатывает ли представление запрос для приложения frontend или backend.

Чтобы ответить на ваши вопросы ...

Какова цель «стека», когда дело доходит до запроса или контекста приложения?

Из контекста запроса:

Поскольку контекст запроса внутренне поддерживается как стек, вы можете вставлять и вставлять его несколько раз. Это очень удобно для реализации таких вещей, как внутренние перенаправления.

Другими словами, даже если у вас обычно будет 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 не может быть завершена. Однако даже если вы применили стратегию замены, как вы предлагали, и у вас не было стека (то есть внутренние перенаправления были бы более сложными), это на самом деле не меняет того факта, что контексты приложения и запроса требуются для изоляции обработки запросов.
Марк Хилдрет
Хорошее объяснение! Но я все еще немного сбиваю с толку: «Контекст приложения создается и уничтожается по мере необходимости. Он никогда не перемещается между потоками и не будет распределяться между запросами». В колбе документ. Почему «контекст приложения» не сохраняется вместе с приложением?
Джейвен
1
Пример внутреннего перенаправления в Flask был бы полезен, поиск в Google не очень полезен. Если бы не это, не будет ли проще более простой request = Local()дизайн для global.py? Вероятно, есть случаи использования, о которых я не думаю.
Четырнадцатое
Можно ли вставлять контекст приложения в метод фабрики при импорте представлений? Поскольку представления содержат маршруты, ссылающиеся на current_app, мне нужен контекст.
переменная
48

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

Теперь моя цель - дополнить наше текущее понимание объяснением того, как Flask и Werkzeug делают то, что они делают с этими контекстными локальными пользователями. Я упростил код, чтобы улучшить понимание его логики, но если вы получите это, вы сможете легко понять большую часть того, что находится в фактическом источнике ( werkzeug.localи flask.globals).

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

Местный

Когда приходит http-запрос, он обрабатывается в контексте одного потока. В качестве альтернативного способа создания нового контекста во время http-запроса Werkzeug также позволяет использовать гринлеты (своего рода более легкие «микропотоки») вместо обычных потоков. Если у вас не установлены гринлеты, они вернутся к использованию потоков. Каждый из этих потоков (или гринлетов) идентифицируется по уникальному идентификатору, который вы можете получить с помощью 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и другие могли бы просто были созданы таким образом. Но это не так, как это делается во Flask, где это не технически 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(то есть извлекаете context_id из его хранилища) и не беспокоитесь о прокси-серверах, они по-прежнему доступны глобально и по-прежнему передаются тому, кто Localищет свой объект. представляет интерес для последующих запросов http.

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

Чтобы упростить создание a, 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-запроса) и в процессе также выдвигать несколько контекстов приложения. Это не обычный вариант использования, но это возможность фреймворка. Поскольку эти «параллельные» запросы и приложения по-прежнему ограничены для запуска только с одним, имеющим «фокус» в любое время, имеет смысл использовать стек для их соответствующего контекста. Каждый раз, когда создается новый запрос или вызывается одно из приложений, они помещают свой контекст в верхнюю часть соответствующего стека. Flask использует 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()в _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, и это помогает уточнить мое понимание этого. Мое чувство пауков говорит мне, что это слишком сложный дизайн, но я признаю, что не понимаю всех вариантов использования, для которых он предназначен. «Внутренние перенаправления» - единственное оправдание, которое я видел в приведенных выше описаниях, и поиск в Google «перенаправление внутри фляги» не слишком эффективен, поэтому я все еще немного растерялся. Одна из вещей, которые мне нравятся в фляге, - это, как правило, не объект типа супа 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 . Ваше новое понимание может позволить вам увидеть их с новым светом и может дать больше понимания.
Майкл Экока
Прочитайте эти ссылки - они в основном имеют смысл, но дизайн все еще кажется мне слишком сложным и, возможно, слишком умным для его же блага. Но я не большой поклонник ООП в целом и неявного управления потоками (переопределение __call __ (), __getattr __ (), динамическая диспетчеризация событий по сравнению с простыми вызовами функций, упаковка вещей в средства доступа к свойствам, а не просто использование обычного атрибута и т. Д. .) так что, может быть, это просто разница в философии. Не практикующий TDD, который, кажется, предназначен для поддержки этого дополнительного оборудования.
ЧетырехместноеA
1
Спасибо за то, что поделились этим. Потоки - это слабость таких языков, как python, - вы сталкиваетесь с шаблонами, подобными приведенным выше, которые просачиваются в каркасы приложений и которые также не масштабируются. Java - другой пример в подобной ситуации повторно. локальные потоки, семафоры и т. д. Общеизвестно, что трудно получить права или поддерживать. Именно здесь такие языки, как Erlang / Elixir (с использованием BEAM) или подходы с циклом событий (например, nginx против apache и т. Д.), Как правило, предлагают более мощный, масштабируемый и менее сложный подход.
arcseldon
13

Небольшое дополнение @ Марк Хилдрет в ответ.

Стек контекста выглядит так {thread.get_ident(): []}, как []называется «стеком», потому что используются операции only 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, match path)
  2. введите или нажмите этот запрос:
    1. очистить предыдущий request_context
    2. создать, app_contextесли он пропустил и толкнул в стек контекста приложения
    3. этот запрос отправляется на стек контекста
    4. сеанс инициализации, если он пропустил
  3. запрос на отправку
  4. очистить запрос и вытолкнуть его из стека
tbicr
источник
2

Давайте возьмем один пример, предположим, что вы хотите установить пользовательский контекст (используя конструкцию колбы Local и LocalProxy).

Определите один класс пользователя:

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

определить функцию для извлечения объекта пользователя внутри текущего потока или гринлета

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

Теперь, чтобы получить идентификатор пользователя в текущем потоке usercontext.userid

объяснение:

1.Local имеет атрибут идентичности и objet, идентичность - это threadid или greenlet id, в этом примере _local.user = User () эквивалентно _local .___ storage __ [id текущего потока] ["user"] = User ()

  1. LocalProxy делегирует операцию обернутому локальному объекту или вы можете предоставить функцию, которая возвращает целевой объект. В приведенном выше примере функция get_user предоставляет текущий пользовательский объект для LocalProxy, и когда вы запрашиваете идентификатор пользователя текущего пользователя с помощью usercontext.userid, функция __getattr__ LocalProxy сначала вызывает get_user для получения объекта User (user), а затем вызывает getattr (user, "userid"). чтобы установить идентификатор пользователя на пользователя (в текущем потоке или гринлете), вы просто делаете: usercontext.userid = "user_123"
Ратн Део - Дев
источник