Я использовал контекст запроса / приложения в течение некоторого времени, не полностью понимая, как он работает или почему он был разработан таким, каким он был. Какова цель «стека», когда дело доходит до запроса или контекста приложения? Это два отдельных стека, или они оба являются частью одного стека? Контекст запроса помещается в стек или это сам стек? Могу ли я вставить / вставить несколько контекстов поверх друг друга? Если так, зачем мне это делать?
Извините за все вопросы, но я все еще растерялся после прочтения документации по контексту запроса и контексту приложения.
Ответы:
Несколько приложений
Контекст приложения (и его назначение) действительно сбивает с толку, пока вы не поймете, что Flask может иметь несколько приложений. Представьте себе ситуацию, когда вы хотите, чтобы один интерпретатор Python WSGI запускал несколько приложений Flask. Мы не говорим здесь о Blueprints, мы говорим о совершенно разных приложениях Flask.
Вы можете настроить это подобно разделу документации Flask в примере «Диспетчеризация приложений» :
Обратите внимание, что создаются два совершенно разных приложения Flask: «внешний интерфейс» и «внутренний интерфейс». Другими словами,
Flask(...)
конструктор приложения вызывался дважды, создавая два экземпляра приложения Flask.Контексты
Когда вы работаете с Flask, вы часто используете глобальные переменные для доступа к различным функциям. Например, у вас, вероятно, есть код, который читает ...
Затем, во время просмотра, вы можете использовать
request
для доступа к информации текущего запроса. Очевидно,request
это не нормальная глобальная переменная; в действительности это контекстная локальная ценность. Другими словами, за кулисами скрывается какое-то волшебство: «когда я звонюrequest.path
, получиpath
атрибут изrequest
объекта запроса CURRENT». Два разных запроса будут иметь разные результаты дляrequest.path
.Фактически, даже если вы запускаете Flask с несколькими потоками, Flask достаточно умен, чтобы сохранять объекты запроса изолированными. При этом становится возможным, чтобы два потока, каждый из которых обрабатывал разные запросы, одновременно вызывали
request.path
и получали правильную информацию для своих соответствующих запросов.Положить его вместе
Итак, мы уже видели, что Flask может обрабатывать несколько приложений в одном и том же интерпретаторе, а также что из-за того, что Flask позволяет вам использовать «контекстные локальные» глобальные переменные, должен быть какой-то механизм, чтобы определить, что такое «текущий» запрос ( для того, чтобы делать такие вещи, как
request.path
).Объединяя эти идеи, также должно иметь смысл, что у Flask должен быть какой-то способ определить, что такое «текущее» приложение!
У вас, вероятно, также есть код, подобный следующему:
Как и в нашем
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
иdb
значение в сценарии , который должен быть запущен из командной строки. Например, скрипт "setup_tables.py" ...В этом случае расширение Flask-SQLAlchemy знает о
app
приложении, но приcreate_all()
этом выдает ошибку с жалобой на отсутствие контекста приложения. Эта ошибка оправдана; вы никогда не указывали Flask, с каким приложением оно должно работать при запускеcreate_all
метода.Вам может быть интересно, почему вам не нужен этот
with app.app_context()
вызов, когда вы запускаете подобные функции в своих представлениях. Причина в том, что Flask уже обрабатывает управление контекстом приложения для вас, когда он обрабатывает реальные веб-запросы. Проблема действительно возникает только за пределами этих функций представления (или других подобных обратных вызовов), например, при использовании ваших моделей в одноразовом скрипте.Решение состоит в том, чтобы подтолкнуть контекст приложения самостоятельно, что можно сделать, выполнив ...
Это подтолкнет новый контекст приложения (используя приложение
app
, помните, что может быть более одного приложения).тестирование
Другой случай, когда вы захотите манипулировать стеком, - для тестирования. Вы можете создать модульный тест, который обрабатывает запрос, и вы проверите результаты:
источник
request = Local()
дизайн для global.py? Вероятно, есть случаи использования, о которых я не думаю.Предыдущие ответы уже дают хороший обзор того, что происходит на фоне 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
, и другие подобные контекстный переплетом глобальных объектов.Теперь, когда у нас есть наша функция идентификации, мы можем знать, в каком потоке мы находимся в любой момент времени, и мы можем создать так называемый поток
Local
, контекстный объект, к которому можно обращаться глобально, но когда вы обращаетесь к его атрибутам, они разрешают их значение для этот конкретный поток. напримерОба значения присутствуют в глобально доступном
Local
объекте одновременно, но доступlocal.first_name
в контексте потока 1 даст вам'John'
, тогда как он вернется'Debbie'
в потоке 2.Как это возможно? Давайте посмотрим на некоторый (упрощенный) код:
Из кода выше мы видим, что магия сводится к тому,
get_ident()
что идентифицирует текущий гринлет или поток. ЗатемLocal
хранилище просто использует это как ключ для хранения любых данных, контекстуальных для текущего потока.Вы можете иметь несколько
Local
объектов для каждого процесса иrequest
,g
,current_app
и другие могли бы просто были созданы таким образом. Но это не так, как это делается во Flask, где это не техническиLocal
объекты, а более точноLocalProxy
объекты. ЧтоLocalProxy
?LocalProxy
LocalProxy - это объект, который запрашивает a,
Local
чтобы найти другой интересующий объект (т. Е. Объект, к которому он относится ). Давайте посмотрим, чтобы понять:Теперь для создания глобально доступных прокси вы должны сделать
и теперь некоторое время на раннем этапе выполнения запроса вы будете хранить некоторые объекты внутри локального хранилища, к которому могут обращаться ранее созданные прокси, независимо от того, в каком потоке мы находимся
Преимущество использования в
LocalProxy
качестве глобально доступных объектов, а не создания ихLocals
самих, состоит в том, что это упрощает их управление. Вам нужен только одинLocal
объект для создания множества глобально доступных прокси. В конце запроса, во время очистки, вы просто освобождаете егоLocal
(то есть извлекаете context_id из его хранилища) и не беспокоитесь о прокси-серверах, они по-прежнему доступны глобально и по-прежнему передаются тому, ктоLocal
ищет свой объект. представляет интерес для последующих запросов http.Чтобы упростить создание a,
LocalProxy
когда у нас уже естьLocal
, Werkzeug реализуетLocal.__call__()
магический метод следующим образом:Тем не менее, если вы смотрите в источнике Колба (flask.globals) , которая все еще не так, как
request
,g
,current_app
иsession
созданы. Как мы выяснили, Flask может создавать несколько «поддельных» запросов (из одного истинного http-запроса) и в процессе также выдвигать несколько контекстов приложения. Это не обычный вариант использования, но это возможность фреймворка. Поскольку эти «параллельные» запросы и приложения по-прежнему ограничены для запуска только с одним, имеющим «фокус» в любое время, имеет смысл использовать стек для их соответствующего контекста. Каждый раз, когда создается новый запрос или вызывается одно из приложений, они помещают свой контекст в верхнюю часть соответствующего стека. Flask используетLocalStack
объекты для этой цели. Когда они заканчивают свое дело, они выталкивают контекст из стека.LocalStack
Вот как это
LocalStack
выглядит (опять же, код упрощен, чтобы облегчить понимание его логики).Обратите внимание на то, что a
LocalStack
является стеком, хранящимся в локальном, а не набором локальных элементов, хранящихся в стеке. Это подразумевает, что хотя стек доступен глобально, в каждом потоке он различен.Колба не имеет его
request
,current_app
,g
иsession
объекты решения непосредственно кLocalStack
, он вместо этого используетLocalProxy
объекты , которые обертывают функцию поиска (вместоLocal
объекта) , который будет найти базовый объект изLocalStack
:Все они объявляются при запуске приложения, но фактически ни к чему не разрешаются, пока контекст запроса или контекст приложения не будет помещен в соответствующий стек.
Если вам интересно посмотреть, как контекст фактически вставляется в стек (а затем вынимается), посмотрите, в
flask.app.Flask.wsgi_app()
какой точке находится точка входа в приложение wsgi (то есть, что вызывает веб-сервер и передают среду http, когда запрос приходит), и следите за созданиемRequestContext
объекта в течение всего его последующегоpush()
в_request_ctx_stack
. После нажатия на вершину стека, он доступен через_request_ctx_stack.top
. Вот некоторый сокращенный код для демонстрации потока:Итак, вы запускаете приложение и делаете его доступным для сервера WSGI ...
Позже приходит запрос http, и сервер WSGI вызывает приложение с обычными параметрами ...
Это примерно то, что происходит в приложении ...
и это примерно то, что происходит с RequestContext ...
Скажем, запрос завершил инициализацию,
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.источник
Local
,LocalStack
иLocalProxy
работу, я предлагаю пересмотреть эти статьи ДоПа: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev и flask.pocoo .org / docs / 0.11 / reqcontext . Ваше новое понимание может позволить вам увидеть их с новым светом и может дать больше понимания.Небольшое дополнение @ Марк Хилдрет в ответ.
Стек контекста выглядит так
{thread.get_ident(): []}
, как[]
называется «стеком», потому что используются операции onlyappend
(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
:request_context
по окружению (initmap_adapter
, match path)request_context
app_context
если он пропустил и толкнул в стек контекста приложенияисточник
Давайте возьмем один пример, предположим, что вы хотите установить пользовательский контекст (используя конструкцию колбы Local и LocalProxy).
Определите один класс пользователя:
определить функцию для извлечения объекта пользователя внутри текущего потока или гринлета
Теперь определите LocalProxy
Теперь, чтобы получить идентификатор пользователя в текущем потоке usercontext.userid
объяснение:
1.Local имеет атрибут идентичности и objet, идентичность - это threadid или greenlet id, в этом примере _local.user = User () эквивалентно _local .___ storage __ [id текущего потока] ["user"] = User ()
источник