फ्लास्क के संदर्भ स्टैक का उद्देश्य क्या है?


158

मैं कुछ समय के लिए अनुरोध / आवेदन के संदर्भ का उपयोग पूरी तरह से समझने के बिना कर रहा हूं कि यह कैसे काम करता है या इसे जिस तरह से डिजाइन किया गया था वह क्यों बनाया गया है। अनुरोध या आवेदन के संदर्भ में "स्टैक" का उद्देश्य क्या है? क्या ये दो अलग-अलग ढेर हैं, या वे दोनों एक ढेर के हिस्से हैं? क्या अनुरोध संदर्भ एक स्टैक पर धकेल दिया जाता है, या क्या यह एक स्टैक है? क्या मैं प्रत्येक शीर्ष के शीर्ष पर कई संदर्भों को धक्का / पॉप करने में सक्षम हूं? यदि हां, तो मैं ऐसा क्यों करना चाहूंगा?

सभी प्रश्नों के लिए क्षमा करें, लेकिन अनुरोध संदर्भ और अनुप्रयोग संदर्भ के लिए दस्तावेज़ पढ़ने के बाद भी मैं उलझन में हूं।


5
kronosapiens.github.io/blog/2014/08/14/… IMO, यह ब्लॉग पोस्ट मुझे फ्लास्क संदर्भ का सबसे समझने योग्य विवरण देता है।
मिशन.लियाओ

जवाबों:


243

कई ऐप्स

एप्लिकेशन संदर्भ (और इसका उद्देश्य) वास्तव में भ्रमित है जब तक आपको एहसास नहीं होता है कि फ्लास्क में कई ऐप हो सकते हैं। उस स्थिति की कल्पना करें जहां आप एक एकल WSGI पायथन दुभाषिया को कई फ्लास्क एप्लिकेशन चलाना चाहते हैं। हम यहां ब्लूप्रिंट पर बात नहीं कर रहे हैं, हम पूरी तरह से अलग फ्लास्क एप्लिकेशन पर बात कर रहे हैं।

आप इसे "एप्लिकेशन डिस्पैचिंग" उदाहरण पर फ्लास्क प्रलेखन अनुभाग के समान सेट कर सकते हैं :

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(...)एप्लीकेशन कंस्ट्रक्टर को दो बार बुलाया गया है, जिससे फ्लास्क एप्लिकेशन के दो उदाहरण हैं।

संदर्भों

जब आप फ्लास्क के साथ काम कर रहे होते हैं, तो आप अक्सर विभिन्न कार्यक्षमता का उपयोग करने के लिए वैश्विक चर का उपयोग करते हुए समाप्त होते हैं। उदाहरण के लिए, आपके पास संभवतः कोड है जो पढ़ता है ...

from flask import request

फिर, एक दृश्य के दौरान, आप requestवर्तमान अनुरोध की जानकारी तक पहुँचने के लिए उपयोग कर सकते हैं। जाहिर है, requestएक सामान्य वैश्विक चर नहीं है; वास्तविकता में, यह एक संदर्भ स्थानीय मूल्य है। दूसरे शब्दों में, पर्दे के पीछे कुछ जादू है जो कहता है "जब मैं फोन करता हूं request.path, तो प्राप्त करेंpathrequest तो CURRES अनुरोध के ऑब्जेक्ट से विशेषता ।" दो अलग-अलग अनुरोधों के लिए एक अलग परिणाम होगा request.path

वास्तव में, भले ही आप कई थ्रेड्स के साथ फ्लास्क चलाते हैं, फ्लास्क अनुरोध वस्तुओं को अलग-थलग रखने के लिए पर्याप्त स्मार्ट है। ऐसा करने में, एक साथ कॉल करने के लिए, दो धागे के लिए यह संभव हो जाता है, प्रत्येक एक अलग अनुरोध को संभालता हैrequest.path अपने संबंधित अनुरोधों के लिए सही जानकारी प्राप्त करने और प्राप्त करने के लिए।

इसे एक साथ रखना

इसलिए हम पहले ही देख चुके हैं कि फ्लास्क एक ही दुभाषिया में कई अनुप्रयोगों को संभाल सकता है, और यह भी कि जिस तरह से फ्लास्क आपको "संदर्भ स्थानीय" ग्लोबल्स का उपयोग करने की अनुमति देता है, यह निर्धारित करने के लिए कुछ तंत्र होना चाहिए कि "वर्तमान" अनुरोध क्या है ( इस तरह के काम करने के लिए request.path)।

इन विचारों को एक साथ रखते हुए, यह भी समझ में आना चाहिए कि फ्लास्क को यह निर्धारित करने का कोई तरीका होना चाहिए कि "वर्तमान" अनुप्रयोग क्या है!

आपके पास शायद निम्नलिखित के समान कोड भी है:

from flask import url_for

हमारे requestउदाहरण की तरह , url_forफ़ंक्शन में तर्क है जो वर्तमान परिवेश पर निर्भर है। इस मामले में, हालांकि, यह स्पष्ट है कि तर्क बहुत हद तक निर्भर करता है कि किस ऐप को "वर्तमान" ऐप माना जाता है। ऊपर दिखाए गए दृश्य / बैकएंड उदाहरण में, "फ्रंटएंड" और "बैकएंड" दोनों ऐप में "/ लॉगिन" मार्ग हो सकता है, और इसी तरहurl_for('/login') इस आधार पर कुछ अलग लौटना चाहिए कि क्या दृश्य फ्रंटेंड या बैकएंड ऐप के लिए अनुरोध को संभाल रहा है।

आपके सवालों के जवाब देने के लिए ...

अनुरोध या आवेदन के संदर्भ में "स्टैक" का उद्देश्य क्या है?

अनुरोध संदर्भ डॉक्स से:

क्योंकि अनुरोध संदर्भ आंतरिक रूप से एक स्टैक के रूप में बनाए रखा जाता है जिसे आप कई बार धक्का और पॉप कर सकते हैं। आंतरिक पुनर्निर्देश जैसी चीजों को लागू करने के लिए यह बहुत आसान है।

दूसरे शब्दों में, भले ही आपके पास "वर्तमान" अनुरोधों या "वर्तमान" अनुप्रयोगों के इन ढेर पर 0 या 1 आइटम होंगे, यह संभव है कि आप अधिक हो सकते हैं।

दिया गया उदाहरण वह है जहां आप अपने अनुरोध को "आंतरिक पुनर्निर्देशन" के परिणाम वापस करेंगे। मान लीजिए कि कोई उपयोगकर्ता A का अनुरोध करता है, लेकिन आप उपयोगकर्ता B पर वापस लौटना चाहते हैं। ज्यादातर मामलों में, आप उपयोगकर्ता को पुनर्निर्देश जारी करते हैं, और उपयोगकर्ता को संसाधन B पर इंगित करते हैं, जिसका अर्थ है कि उपयोगकर्ता B A को लाने के लिए दूसरा अनुरोध चलाएगा। इसे संभालने का थोड़ा अलग तरीका एक आंतरिक रीडायरेक्ट करना होगा, जिसका अर्थ है कि ए को संसाधित करते समय, फ्लास्क संसाधन बी के लिए खुद के लिए एक नया अनुरोध करेगा, और उपयोगकर्ता के मूल अनुरोध के परिणामों के रूप में इस दूसरे अनुरोध के परिणामों का उपयोग करेगा।

क्या ये दो अलग-अलग ढेर हैं, या वे दोनों एक ढेर के हिस्से हैं?

वे दो अलग-अलग ढेर हैं । हालाँकि, यह एक कार्यान्वयन विवरण है। जो अधिक महत्वपूर्ण है वह इतना अधिक नहीं है कि एक स्टैक है, लेकिन यह तथ्य कि किसी भी समय आप "वर्तमान" ऐप या अनुरोध (स्टैक के ऊपर) प्राप्त कर सकते हैं।

क्या अनुरोध संदर्भ एक स्टैक पर धकेल दिया जाता है, या क्या यह एक स्टैक है?

एक "अनुरोध संदर्भ" "अनुरोध संदर्भ स्टैक" का एक आइटम है। इसी प्रकार "ऐप संदर्भ" और "ऐप संदर्भ स्टैक" के साथ।

क्या मैं प्रत्येक शीर्ष के शीर्ष पर कई संदर्भों को धक्का / पॉप करने में सक्षम हूं? यदि हां, तो मैं ऐसा क्यों करना चाहूंगा?

फ्लास्क एप्लिकेशन में, आप आमतौर पर ऐसा नहीं करेंगे। एक उदाहरण जहां आप चाहते हैं कि आंतरिक पुनर्निर्देशन (ऊपर वर्णित) के लिए हो सकता है। उस मामले में भी, हालांकि, आप संभवतः फ्लास्क एक नया अनुरोध संभाल लेंगे, और इसलिए फ्लास्क आपके लिए सभी पुश / पॉपिंग करेगा।

हालाँकि, कुछ ऐसे मामले भी हैं जहाँ आप खुद को ढेर बनाना चाहते हैं।

अनुरोध के बाहर कोड चलाना

लोगों की एक खास समस्या यह है कि वे 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()

इस मामले में, फ्लास्क-SQLAlchemy एक्सटेंशन appएप्लिकेशन के बारे में जानता है , लेकिन इसके दौरान create_all()एक त्रुटि के बारे में शिकायत करेगा जो कि एप्लिकेशन संदर्भ नहीं है। यह त्रुटि उचित है; आपने फ्लास्क को कभी नहीं बताया कि रनिंग करते समय उसे किस एप्लिकेशन से निपटना चाहिएcreate_all विधि को ।

आप सोच रहे होंगे कि आपको इसकी आवश्यकता क्यों नहीं है with app.app_context() जब आप अपने विचारों में समान कार्य करते हैं तो आपको कॉल की पड़ती। कारण यह है कि फ्लास्क पहले से ही आपके लिए एप्लिकेशन संदर्भ के प्रबंधन को संभालता है जब वह वास्तविक वेब अनुरोधों को संभाल रहा होता है। समस्या वास्तव में केवल इन व्यू फ़ंक्शंस (या अन्य ऐसे कॉलबैक) के बाहर आती है, जैसे कि आपके मॉडल को एक-बंद स्क्रिप्ट में उपयोग करते समय।

संकल्प आवेदन के संदर्भ को स्वयं आगे बढ़ाने के लिए है, जिसे करके ...

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 यदि अनुरोध ए को हैंडल करते समय आप अनुरोध बी बनाते हैं, और बी अनुरोध को ए पर स्टैक पर अनुरोध करता है, तो अनुरोध ए के लिए हैंडलिंग समाप्त नहीं हो सकती है। हालाँकि, भले ही आपने प्रतिस्थापित रणनीति बनाई हो, जैसा कि आपने सुझाव दिया था और एक स्टैक नहीं था (मतलब आंतरिक पुनर्निर्देशन अधिक कठिन होगा) यह वास्तव में इस तथ्य को नहीं बदलता है कि एप्लिकेशन और अनुरोध संदर्भों को अनुरोधों से निपटने के लिए अलग करना आवश्यक है।
मार्क हिल्ड्रेथ

अच्छे खर्च! लेकिन मैं अभी भी इस बारे में थोड़ा भ्रमित हूं: "आवेदन का संदर्भ आवश्यक रूप से बनाया और नष्ट किया गया है। यह धागे के बीच कभी नहीं चलता है और इसे अनुरोधों के बीच साझा नहीं किया जाएगा।" फ्लास्क के दस्तावेज़ में। क्यों एक "अनुप्रयोग संदर्भ" एप्लिकेशन के साथ जारी नहीं है?
जायवन

1
फ्लास्क में एक आंतरिक पुनर्निर्देश का एक उदाहरण सहायक होगा, गुगली करना यह बहुत ऊपर नहीं जाता है। यदि ऐसा नहीं है request = Local(), तो Global.py के लिए कोई सरल डिज़ाइन पर्याप्त नहीं है ? शायद ऐसे मामले हैं जो मैं नहीं सोच रहा हूं।
QuadrupleA

क्या विचारों को आयात करते समय कारखाना संदर्भ के अंदर एप्लिकेशन संदर्भ को धक्का देना ठीक है? क्योंकि विचारों में वर्तमान_ संदर्भ के संदर्भ में मार्ग होते हैं, जिन्हें मुझे संदर्भ की आवश्यकता है।
चर

48

पिछले जवाब पहले से ही एक अनुरोध के दौरान फ्लास्क की पृष्ठभूमि में क्या होता है का एक अच्छा अवलोकन देते हैं। यदि आपने इसे अभी तक नहीं पढ़ा है तो मैं इसे पढ़ने से पहले @ MarkHildreth के उत्तर की सिफारिश करता हूं। संक्षेप में, प्रत्येक http अनुरोध के लिए एक नया संदर्भ (थ्रेड) बनाया गया है, यही कारण है कि थ्रेड Localसुविधा होना आवश्यक है जो इस तरह की अनुमति देता हैrequest औरgधागे के पार विश्व स्तर पर सुलभ होना, जबकि उनके अनुरोध को विशिष्ट संदर्भ बनाए रखना। इसके अलावा, एक http अनुरोध संसाधित करते समय फ्लास्क भीतर से अतिरिक्त अनुरोधों का अनुकरण कर सकता है, इसलिए एक स्टैक पर उनके संबंधित संदर्भ को संग्रहीत करने की आवश्यकता है। इसके अलावा, फ्लास्क एक प्रक्रिया के भीतर कई wsgi अनुप्रयोगों को एक दूसरे के साथ चलने की अनुमति देता है, और एक से अधिक अनुरोध के दौरान कार्रवाई के लिए कहा जा सकता है (प्रत्येक अनुरोध एक नया अनुप्रयोग संदर्भ बनाता है), इसलिए अनुप्रयोगों के लिए एक संदर्भ स्टैक की आवश्यकता है। यह एक सारांश है जो पिछले उत्तरों में शामिल था।

मेरा लक्ष्य अब यह बताकर हमारी वर्तमान समझ को पूरक बनाना है कि फ्लास्क और वर्क्ज़ुग इन संदर्भों के स्थानीय लोगों के साथ क्या करते हैं। मैंने इसके तर्क की समझ को बढ़ाने के लिए कोड को सरल बनाया है, लेकिन यदि आपको यह मिलता है, तो आपको वास्तविक स्रोत ( 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और दूसरों को बस ऐसे ही बनाया जा सकता था। लेकिन यह नहीं है कि यह कैसे फ्लास्क में किया गया है जिसमें ये तकनीकी रूप से Local ऑब्जेक्ट नहीं हैं, बल्कि अधिक सटीक LocalProxyऑब्जेक्ट हैं। क्या है LocalProxy?

LocalProxy

लोकलप्रॉक्सी एक ऐसी वस्तु है जो 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(यानी आप इसके भंडारण से संदर्भ_आईडी को पॉप करते हैं) और परदे के पीछे परेशान नहीं करते हैं, वे अभी भी विश्व स्तर पर सुलभ हैं और अभी भी 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बनाया जाता है। जैसा कि हमने स्थापित किया है, फ्लास्क कई "नकली" अनुरोधों (एक सच्चे 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

ऊपर से ध्यान दें कि एक 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 वातावरण को पास करता है जब a अनुरोध में आता है), और के निर्माण का पालन 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वस्तु एक ढेर एक स्थानीय में संग्रहीत पर एक वस्तु के लिए एक प्रॉक्सी है। <- यही फ्लास्क करता है।

4
बहुत बढ़िया ठहरनेवाला, मैं फ्लास्क / ग्लोबाल्सहोम और वेर्केग्यूग / लोकलहोम में कोड का अध्ययन कर रहा हूं और इससे मेरी समझ को स्पष्ट करने में मदद मिलती है। मेरी स्पष्ट समझ मुझे बताती है कि यह एक तरह से अतिरंजित डिजाइन है, लेकिन मैं मानता हूं कि इसके उपयोग के सभी मामलों को मैं नहीं समझता। "आंतरिक पुनर्निर्देशन" केवल औचित्य है जो मैंने ऊपर वर्णित विवरणों में देखा है, और "फ्लास्क आंतरिक पुनर्निर्देशन" को गुगली करना बहुत अधिक नहीं मुड़ता है इसलिए मैं अभी भी एक नुकसान में थोड़ा सा हूं। फ्लास्क के बारे में मुझे जो चीजें पसंद हैं उनमें से एक यह है कि आम तौर पर एक जावा ऑब्जेक्ट-सूप प्रकार नहीं है जो कि AbstractProviderContextBaseFactories और इस तरह से भरा हुआ है।
क्वाड्रूपलए

1
@QuadrupleA एक बार जब आप समझते हैं कि कैसे इन Local, LocalStackऔर LocalProxyकाम करते हैं, मैं दस्तावेज़ के इन लेखों पर दोबारा जाने का सुझाव देते हैं: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev , और flask.pocoo .org / डॉक्स / 0.11 / reconcontext । आपकी ताज़ा समझ आपको एक नई रोशनी के साथ देख सकती है और अधिक जानकारी प्रदान कर सकती है।
माइकल एक्का

उन लिंक के माध्यम से पढ़ें - वे ज्यादातर समझ में आते हैं, लेकिन डिजाइन अभी भी मुझे अधकपारी के रूप में मारता है और शायद अपने स्वयं के अच्छे के लिए बहुत चालाक है। लेकिन मैं सामान्य रूप से OOP का एक बड़ा प्रशंसक नहीं हूं, और अंतर्निहित प्रवाह नियंत्रण सामान (ओवरराइडिंग __call __ (), __getattr __ (), डायनेमिक इवेंट डिस्पैचिंग बनाम साधारण फ़ंक्शन कॉल, केवल एक नियमित विशेषता का उपयोग करने के बजाय संपत्ति के स्रोतों में चीजों को लपेटना, आदि) ।) तो शायद यह सिर्फ दर्शन में अंतर है। टीडीडी व्यवसायी भी नहीं, जो इस अतिरिक्त मशीनरी का बहुत समर्थन करने का इरादा रखता है।
चौगुना

1
इसे साझा करने के लिए धन्यवाद, सराहना की गई। थ्रेडिंग पायथन जैसी भाषाओं के साथ कमजोरी है - आप इस तरह के पैटर्न के साथ ऊपर उठते हैं जैसे कि एप्लीकेशन फ्रेमवर्क में रिसाव होता है और जो वास्तव में पैमाने पर नहीं होता है। जावा इसी तरह की स्थिति में एक और उदाहरण है। थ्रेडोकोल, सेमाफोर आदि को ठीक से प्राप्त करने या बनाए रखने के लिए कुख्यात मुश्किल। यह वह जगह है जहाँ Erlang / Elixir (BEAM का उपयोग करके), या इवेंट लूप अप्रोच (जैसे। Nginx बनाम अपाचे आदि) जैसी भाषाएं आमतौर पर अधिक शक्तिशाली, स्केलेबल और कम जटिल दृष्टिकोण प्रदान करती हैं।
आर्केल्डन

13

थोड़ा अतिरिक्त @ मर्क हिल्ड्रेथ का जवाब।

संदर्भ स्टैक जैसा दिखता है {thread.get_ident(): []}, जहां []"स्टैक" कहा जाता है क्योंकि केवल append( push), popऔर [-1]( __getitem__(-1)) संचालन का उपयोग किया जाता है । तो संदर्भ स्टैक धागे या ग्रीनलेट थ्रेड के लिए वास्तविक डेटा रखेगा।

current_app, g, request, sessionऔर आदि है LocalProxyवस्तु जो सिर्फ विशेष तरीकों overrided __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

वर्तमान थ्रेड या ग्रीनलेट के अंदर उपयोगकर्ता ऑब्जेक्ट को पुनः प्राप्त करने के लिए एक फ़ंक्शन को परिभाषित करें

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 की पहचान और आज्ञाकारिता की एक पहचान है, पहचान थ्रेडिड या ग्रीनलेट आईडी है, इस उदाहरण में _local.user = उपयोगकर्ता () _local के लिए eqivalent है ।___ भंडारण __ [वर्तमान धागा की आईडी] [उपयोगकर्ता "] = उपयोगकर्ता ()।

  1. LocalProxy प्रतिनिधियों की समाप्ति के स्थानीय ऑब्जेक्ट के लिए आपरेशन या आप एक समारोह है कि रिटर्न वस्तु को लक्षित प्रदान कर सकते हैं। उपरोक्त उदाहरण में get_user फ़ंक्शन लोकलप्रॉक्सी को वर्तमान उपयोगकर्ता ऑब्जेक्ट प्रदान करता है, और जब आप usercontext.userid द्वारा वर्तमान उपयोगकर्ता के उपयोगकर्ता के लिए पूछते हैं, तो LocalProxy का __getattr__ फ़ंक्शन पहले call_user को उपयोगकर्ता ऑब्जेक्ट (उपयोगकर्ता) प्राप्त करने के लिए कहता है और फिर गेटअटर (उपयोगकर्ता, "उपयोगकर्ता आईडी") को कॉल करता है। उपयोगकर्ता पर (वर्तमान थ्रेड या ग्रीनलेट में) उपयोगकर्ता को सेट करने के लिए आप बस ऐसा करें: usercontext.userid = "user_2_in
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.