Каково назначение контексте стопки фляги?


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

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

4   109   2013-11-18 01:39:14

4 ответа:

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

контекст приложения (и его цель) действительно сбивает с толку, пока вы не поймете, что колба может иметь несколько приложений. Представьте себе ситуацию, когда вы хотите, чтобы один интерпретатор WSGI Python запускал несколько приложений 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
})

обратите внимание, что есть два совершенно разных приложения колбы создаются "frontend" и "backend". Другими словами,Flask(...) конструктор приложения вызывался дважды, создавая два экземпляра приложения Flask.

условиях

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

from flask import request

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

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

вместе

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

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

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

from flask import url_for

как наши request, например,

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

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

давайте сначала разберемся, как Werkzeug реализует thread Locals.

Local

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

  • начните с глобально доступного LocalProxy объект request.
  • найти объект интереса (объектом это проксирование) он вызывает свою функцию поиска _find_request() (функция зарегистрирована как self.local).
  • эта функция запрашивает LocalStack объект _request_ctx_stack для верхнего контекста в стеке.
  • чтобы найти верхний контекст,LocalStack "объект" опрашивает свой внутренний (self.local) для stack свойство, которое ранее хранилось там.
  • с stack он получает верхний контекст
  • и top.request таким образом, решается как основной объект интереса.
  • из этого объекта мы получаем path атрибут

Итак, мы видели, как Local,LocalProxy и LocalStack работа, теперь подумайте о последствиях и нюансах в получении path от:

  • a request объект, который будет простым глобально доступным объектом.
  • a request объект, который будет локальным.
  • a request объект хранится как атрибут локального.
  • a request объект, который является прокси-сервером для объекта, хранящегося в локальном.
  • a request объект хранится в стеке, который в свою очередь хранится в локальном.
  • a request объект, который является прокси-сервером для объекта в стеке, хранящемся в локальном.

мало того @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. ясный
    2. создать app_context если он пропустил и толкнул в стек контекста приложения
    3. этот запрос подтолкнул к контексту запроса стек
    4. init сессии, если он пропустил
  3. запрос на отправку
  4. очистить запрос и вытолкнуть его из стека

давайте возьмем один пример, предположим, что вы хотите установить usercontext (используя конструкцию flask 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()))

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

объяснение :

1.Местный имеет диктат идентичности и objet, identity-это threadid или greenlet id, в данном примере _local.user = User () эквивалентно _local.___ storage__[идентификатор текущего потока] ["пользователь"] = User ()

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