Apa tujuan tumpukan konteks Flask?


158

Saya telah menggunakan konteks permintaan / aplikasi untuk beberapa waktu tanpa sepenuhnya memahami cara kerjanya atau mengapa ia dirancang seperti itu. Apa tujuan dari "tumpukan" ketika datang ke konteks permintaan atau aplikasi? Apakah kedua tumpukan ini terpisah, atau keduanya merupakan bagian dari satu tumpukan? Apakah konteks permintaan didorong ke tumpukan, atau apakah itu tumpukan itu sendiri? Apakah saya dapat mendorong / membuat banyak konteks di atas satu sama lain? Jika demikian, mengapa saya ingin melakukan itu?

Maaf untuk semua pertanyaan, tetapi saya masih bingung setelah membaca dokumentasi untuk Konteks Permintaan dan Konteks Aplikasi.


5
kronosapiens.github.io/blog/2014/08/14/... IMO, posting blog ini memberi saya deskripsi yang paling dapat dimengerti dari konteks labu.
mission.liao

Jawaban:


243

Beberapa Aplikasi

Konteks aplikasi (dan tujuannya) memang membingungkan sampai Anda menyadari bahwa Flask dapat memiliki beberapa aplikasi. Bayangkan situasi di mana Anda ingin memiliki satu juru bahasa WSGI Python menjalankan beberapa aplikasi Flask. Kami tidak berbicara Cetak Biru di sini, kami berbicara tentang aplikasi Flask yang sama sekali berbeda.

Anda dapat mengatur ini mirip dengan bagian dokumentasi Flask pada contoh "Pengiriman Aplikasi" :

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

Perhatikan bahwa ada dua aplikasi Flask yang sama sekali berbeda sedang dibuat "frontend" dan "backend". Dengan kata lain, Flask(...)konstruktor aplikasi telah dipanggil dua kali, menciptakan dua contoh aplikasi Flask.

Konteks

Ketika Anda bekerja dengan Flask, Anda sering berakhir menggunakan variabel global untuk mengakses berbagai fungsi. Misalnya, Anda mungkin memiliki kode yang bertuliskan ...

from flask import request

Kemudian, selama tampilan, Anda dapat menggunakan requestuntuk mengakses informasi dari permintaan saat ini. Jelas, requestini bukan variabel global yang normal; dalam kenyataannya, itu adalah konteks nilai lokal . Dengan kata lain, ada beberapa keajaiban di balik layar yang mengatakan "ketika saya menelepon request.path, dapatkan pathatribut dari requestobjek permintaan CURRENT." Dua permintaan berbeda akan memiliki hasil berbeda untuk request.path.

Bahkan, bahkan jika Anda menjalankan Flask dengan banyak utas, Flask cukup pintar untuk menjaga objek permintaan terisolasi. Dengan melakukan hal itu, dimungkinkan untuk dua utas, masing-masing menangani permintaan yang berbeda, untuk secara bersamaan menelepon request.pathdan mendapatkan informasi yang benar untuk permintaan mereka masing-masing.

Menyatukannya

Jadi kita sudah melihat bahwa Flask dapat menangani beberapa aplikasi dalam interpreter yang sama, dan juga bahwa karena cara yang Flask memungkinkan Anda untuk menggunakan "konteks lokal" GLOBALS harus ada mekanisme untuk menentukan apa yang "saat ini" permintaan adalah ( untuk melakukan hal-hal seperti request.path).

Menyatukan ide-ide ini, juga masuk akal bahwa Flask harus memiliki beberapa cara untuk menentukan apa aplikasi "saat ini"!

Anda mungkin juga memiliki kode yang mirip dengan yang berikut:

from flask import url_for

Seperti requestcontoh kita , url_forfungsi memiliki logika yang bergantung pada lingkungan saat ini. Dalam hal ini, bagaimanapun, jelas untuk melihat bahwa logika sangat bergantung pada aplikasi mana yang dianggap sebagai aplikasi "saat ini". Pada contoh frontend / backend yang ditunjukkan di atas, aplikasi "frontend" dan "backend" dapat memiliki rute "/ login", dan karenanya url_for('/login')harus mengembalikan sesuatu yang berbeda tergantung pada apakah tampilan menangani permintaan untuk aplikasi frontend atau backend.

Untuk menjawab pertanyaan Anda ...

Apa tujuan dari "tumpukan" ketika datang ke konteks permintaan atau aplikasi?

Dari dokumen Konteks Permintaan:

Karena konteks permintaan dipertahankan secara internal sebagai tumpukan, Anda dapat mendorong dan pop berkali-kali. Ini sangat berguna untuk menerapkan hal-hal seperti pengalihan internal.

Dengan kata lain, meskipun Anda biasanya akan memiliki 0 atau 1 item pada tumpukan permintaan "saat ini" atau aplikasi "saat ini", ada kemungkinan bahwa Anda dapat memiliki lebih banyak.

Contoh yang diberikan adalah di mana permintaan Anda akan mengembalikan hasil "redirect internal". Katakanlah pengguna meminta A, tetapi Anda ingin kembali ke pengguna B. Dalam kebanyakan kasus, Anda mengeluarkan pengalihan ke pengguna, dan mengarahkan pengguna ke sumber daya B, artinya pengguna akan menjalankan permintaan kedua untuk mengambil B. A cara penanganan yang sedikit berbeda adalah dengan melakukan pengalihan internal, yang berarti bahwa saat memproses A, Flask akan membuat permintaan baru untuk dirinya sendiri untuk sumber daya B, dan menggunakan hasil dari permintaan kedua ini sebagai hasil dari permintaan asli pengguna.

Apakah kedua tumpukan ini terpisah, atau keduanya merupakan bagian dari satu tumpukan?

Mereka adalah dua tumpukan terpisah . Namun, ini adalah detail implementasi. Yang lebih penting adalah tidak begitu banyak ada tumpukan, tetapi kenyataan bahwa setiap saat Anda bisa mendapatkan aplikasi atau permintaan "saat ini" (atas tumpukan).

Apakah konteks permintaan didorong ke tumpukan, atau apakah itu tumpukan itu sendiri?

"Konteks permintaan" adalah salah satu item dari "tumpukan konteks permintaan". Demikian pula dengan "konteks aplikasi" dan "tumpukan konteks aplikasi".

Apakah saya dapat mendorong / membuat banyak konteks di atas satu sama lain? Jika demikian, mengapa saya ingin melakukan itu?

Dalam aplikasi Flask, Anda biasanya tidak akan melakukan ini. Salah satu contoh di mana Anda mungkin ingin adalah redirect internal (dijelaskan di atas). Meskipun demikian, bahkan dalam kasus itu, Anda mungkin akan berakhir dengan meminta Flask menangani permintaan baru, dan karenanya Flask akan melakukan semua upaya mendorong / muncul untuk Anda.

Namun, ada beberapa kasus di mana Anda ingin memanipulasi tumpukan itu sendiri.

Menjalankan kode di luar permintaan

Salah satu masalah khas yang orang miliki adalah bahwa mereka menggunakan ekstensi Flask-SQLAlchemy untuk mengatur database SQL dan definisi model menggunakan kode sesuatu seperti apa yang ditunjukkan di bawah ini ...

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

Kemudian mereka menggunakan appdan dbnilai - nilai dalam skrip yang harus dijalankan dari shell. Misalnya, skrip "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

Dalam hal ini, ekstensi Flask-SQLAlchemy tahu tentang appaplikasi, tetapi selama create_all()itu akan menimbulkan kesalahan karena tidak ada konteks aplikasi. Kesalahan ini dibenarkan; Anda tidak pernah memberi tahu Flask aplikasi apa yang harus ditangani ketika menjalankan create_allmetode.

Anda mungkin bertanya-tanya mengapa pada akhirnya Anda tidak membutuhkan with app.app_context()panggilan ini saat menjalankan fungsi serupa dalam tampilan Anda. Alasannya adalah bahwa Flask sudah menangani pengelolaan konteks aplikasi untuk Anda ketika menangani permintaan web yang sebenarnya. Masalahnya benar-benar hanya muncul di luar fungsi tampilan ini (atau panggilan balik semacam itu), seperti ketika menggunakan model Anda dalam skrip satu kali.

Resolusi tersebut adalah untuk mendorong konteks aplikasi sendiri, yang dapat dilakukan dengan ...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

Ini akan mendorong konteks aplikasi baru (menggunakan aplikasi app, ingat mungkin ada lebih dari satu aplikasi).

Pengujian

Kasus lain di mana Anda ingin memanipulasi tumpukan adalah untuk pengujian. Anda bisa membuat unit test yang menangani permintaan dan Anda memeriksa hasilnya:

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
Ini masih membingungkan saya! Mengapa tidak memiliki satu konteks permintaan tunggal dan menggantinya jika Anda ingin melakukan redirect internal. Sepertinya desain yang jelas bagi saya.
Maarten

@ Maarten Jika saat menangani permintaan A Anda membuat permintaan B, dan permintaan B menggantikan permintaan A di tumpukan, penanganan untuk permintaan A tidak dapat selesai. Namun, bahkan jika Anda melakukan strategi penggantian seperti yang Anda sarankan dan tidak memiliki tumpukan (yang berarti pengalihan internal akan lebih sulit) ini tidak benar-benar mengubah fakta bahwa konteks aplikasi dan permintaan diperlukan untuk mengisolasi penanganan permintaan.
Mark Hildreth

Penjelasan yang bagus! Tetapi saya masih sedikit bingung tentang: "Konteks aplikasi dibuat dan dihancurkan seperlunya. Tidak pernah berpindah di antara utas dan tidak akan dibagikan di antara permintaan." Dalam dokumen labu. Mengapa "konteks aplikasi" tidak bertahan bersama dengan aplikasi?
jayven

1
Contoh redirect internal di Flask akan membantu, googling itu tidak muncul banyak. Jika bukan karena itu, bukankah request = Local()desain yang lebih sederhana sudah cukup untuk global.py? Mungkin ada kasus penggunaan yang tidak saya pikirkan.
QuadrupleA

Apakah boleh mendorong konteks aplikasi di dalam metode pabrik saat mengimpor tampilan? Karena tampilan berisi rute yang merujuk ke current_app, saya perlu konteksnya.
variabel

48

Jawaban sebelumnya sudah memberikan gambaran yang bagus tentang apa yang terjadi di latar belakang Flask selama permintaan. Jika Anda belum membacanya saya merekomendasikan jawaban @ MarkHildreth sebelum membaca ini. Singkatnya, konteks baru (utas) dibuat untuk setiap permintaan http, itulah sebabnya mengapa perlu ada utasLocal fasilitas yang memungkinkan objek seperti requestdanguntuk dapat diakses secara global di seluruh utas, sambil mempertahankan konteks permintaan khusus mereka. Lebih lanjut, saat memproses permintaan http, Flask dapat mengemulasi permintaan tambahan dari dalam, karenanya perlunya menyimpan konteksnya masing-masing pada tumpukan. Juga, Flask memungkinkan beberapa aplikasi wsgi untuk berjalan bersama dalam satu proses tunggal, dan lebih dari satu dapat dipanggil untuk bertindak selama permintaan (setiap permintaan membuat konteks aplikasi baru), maka kebutuhan untuk tumpukan konteks untuk aplikasi. Itu adalah ringkasan dari apa yang dicakup dalam jawaban sebelumnya.

Tujuan saya sekarang adalah untuk melengkapi pemahaman kita saat ini dengan menjelaskan bagaimana Flask dan Werkzeug melakukan apa yang mereka lakukan dengan penduduk setempat. Saya menyederhanakan kode untuk meningkatkan pemahaman logikanya, tetapi jika Anda mendapatkan ini, Anda harus dapat dengan mudah memahami sebagian besar apa yang ada di sumber yang sebenarnya ( werkzeug.localdanflask.globals ).

Pertama-tama mari kita memahami bagaimana Werkzeug mengimplementasikan thread Locals.

Lokal

Ketika permintaan http masuk, diproses dalam konteks utas tunggal. Sebagai alternatif untuk menelurkan konteks baru selama permintaan http, Werkzeug juga memungkinkan penggunaan greenlets (semacam "thread mikro" yang lebih ringan) alih-alih thread normal. Jika Anda tidak menginstal greenlets, maka ia akan kembali menggunakan utas. Masing-masing utas ini (atau greenlets) dapat diidentifikasi oleh id unik, yang dapat Anda ambil dengan fungsi modul get_ident(). Fungsi itu adalah titik mulai keajaiban di balik memiliki request, current_app, url_for, g, dan konteks-terikat benda lain seperti global.

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

Sekarang setelah kita memiliki fungsi identitas kita, kita dapat mengetahui utas mana yang sedang kita pakai pada waktu tertentu dan kita dapat membuat apa yang disebut utas Local, objek kontekstual yang dapat diakses secara global, tetapi ketika Anda mengakses atributnya mereka memutuskan untuk nilainya bagi utas spesifik itu. misalnya

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Kedua nilai hadir pada Localobjek yang dapat diakses secara global pada saat yang sama, tetapi mengakses local.first_namedalam konteks utas 1 akan memberi Anda 'John', sedangkan itu akan kembali'Debbie' pada utas 2.

Bagaimana mungkin? Mari kita lihat beberapa kode (disederhanakan):

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

Dari kode di atas kita dapat melihat bahwa sihir bermuara di get_ident()mana mengidentifikasi greenlet atau utas saat ini. ItuLocal penyimpanan kemudian hanya menggunakan itu sebagai kunci untuk menyimpan data kontekstual untuk thread saat ini.

Anda dapat memiliki beberapa Localobjek per proses dan request, g, current_appdan lain-lain bisa hanya telah diciptakan seperti itu. Tapi itu bukan bagaimana hal itu dilakukan dalam termos di mana ini bukan objek secara teknis Local , tetapi lebih tepatnya LocalProxyobjek. Apa ituLocalProxy ?

Proxy Lokal

LocalProxy adalah objek yang meminta a Localuntuk menemukan objek lain yang menarik (yaitu objek yang diproksikan). Mari kita lihat untuk memahami:

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 ...

Sekarang untuk membuat proxy yang dapat diakses secara global yang akan Anda lakukan

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

dan sekarang beberapa waktu lebih awal selama permintaan Anda akan menyimpan beberapa objek di dalam lokal yang dapat diakses oleh proxy yang dibuat sebelumnya, tidak peduli di mana thread kami berada

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

Keuntungan menggunakan LocalProxyobjek yang dapat diakses secara global daripada membuatnya Localssendiri adalah menyederhanakan pengelolaannya. Anda hanya perlu satu Localobjek untuk membuat banyak proxy yang dapat diakses secara global. Pada akhir permintaan, selama pembersihan, Anda cukup melepaskan satu Local(yaitu Anda pop konteks_id dari penyimpanannya) dan tidak repot-repot dengan proxy, mereka masih dapat diakses secara global dan masih tunduk kepada yang Localmenemukan objek mereka menarik untuk permintaan http berikutnya.

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

Untuk menyederhanakan pembuatan LocalProxyketika kita sudah memiliki Local, Werkzeug mengimplementasikan Local.__call__()metode ajaib sebagai berikut:

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

Namun, jika Anda melihat pada sumber Flask (flask.globals) itu masih belum seberapa request, g, current_appdan sessiondiciptakan. Seperti yang telah kami tentukan, Flask dapat menelurkan beberapa permintaan "palsu" (dari satu permintaan http sejati) dan dalam proses itu juga mendorong beberapa konteks aplikasi. Ini bukan kasus penggunaan umum, tapi itu adalah kemampuan dari kerangka kerja. Karena permintaan dan aplikasi "bersamaan" ini masih terbatas untuk dijalankan dengan hanya satu yang memiliki "fokus" kapan saja, masuk akal untuk menggunakan tumpukan untuk konteks masing-masing. Setiap kali permintaan baru muncul atau salah satu aplikasi dipanggil, mereka mendorong konteksnya di bagian atas tumpukan masing-masing. Labu menggunakan LocalStackobjek untuk tujuan ini. Ketika mereka menyimpulkan bisnis mereka, mereka mengeluarkan konteks dari tumpukan.

Tumpukan lokal

Seperti inilah LocalStackbentuknya (sekali lagi kode ini disederhanakan untuk memudahkan pemahaman logikanya).

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

Perhatikan dari atas bahwa a LocalStack adalah tumpukan yang disimpan di lokal, bukan sekelompok penduduk setempat yang disimpan di tumpukan. Ini menyiratkan bahwa meskipun tumpukan dapat diakses secara global, itu adalah tumpukan yang berbeda di setiap utas.

Flask tidak memiliki nya request, current_app, g, dan sessionbenda menyelesaikan langsung ke LocalStack, agak menggunakan LocalProxybenda-benda yang membungkus fungsi lookup (bukan Localobjek) yang akan menemukan objek yang mendasari dari 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)

Semua ini dinyatakan pada saat permulaan aplikasi, tetapi tidak benar-benar menyelesaikan apa pun sampai konteks permintaan atau konteks aplikasi didorong ke tumpukan masing-masing.

Jika Anda penasaran untuk melihat bagaimana konteks sebenarnya dimasukkan dalam tumpukan (dan kemudian muncul), lihat di flask.app.Flask.wsgi_app()mana merupakan titik masuknya aplikasi wsgi (yaitu apa yang disebut server web dan berikan lingkungan http ketika sebuah permintaan masuk), dan ikuti pembuatan RequestContextobjek semua melalui selanjutnya push()ke _request_ctx_stack. Setelah didorong di bagian atas tumpukan, dapat diakses melalui _request_ctx_stack.top. Berikut beberapa kode singkat untuk menunjukkan alur:

Jadi Anda memulai aplikasi dan membuatnya tersedia untuk server WSGI ...

app = Flask(*config, **kwconfig)

# ...

Kemudian permintaan http muncul dan server WSGI memanggil aplikasi dengan params yang biasa ...

app(environ, start_response) # aka app.__call__(environ, start_response)

Ini kira-kira yang terjadi di aplikasi ...

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

    # ...

dan inilah kira-kira yang terjadi dengan 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()

Katakanlah permintaan telah selesai diinisialisasi, karena itu pencarian request.pathdari salah satu fungsi tampilan Anda akan berjalan sebagai berikut:

  • mulai dari LocalProxyobjek yang dapat diakses secara global request.
  • untuk menemukan objek yang mendasarinya (objek tempat proxy) memanggil fungsi pencariannya _find_request()(fungsi yang didaftarkan sebagaiself.local ).
  • fungsi itu menanyakan LocalStackobjek_request_ctx_stack untuk konteks teratas pada stack.
  • untuk menemukan konteks teratas, LocalStackobjek pertama kali meminta Localatribut batinnya ( self.local) untukstack properti yang sebelumnya disimpan di sana.
  • dari stack itu mendapat konteks teratas
  • dan top.request dengan demikian diselesaikan sebagai objek yang menarik.
  • dari objek itu kita mendapatkan pathatribut

Jadi kita telah melihat bagaimana Local, LocalProxydan LocalStackbekerja, sekarang berpikir sejenak tentang implikasi dan nuansa dalam mengambil pathdari:

  • sebuah requestobjek yang akan menjadi sederhana global objek diakses.
  • sebuah requestobjek yang akan menjadi lokal.
  • sebuah requestobjek yang tersimpan sebagai atribut lokal.
  • Sebuah request objek yang merupakan proxy untuk sebuah objek disimpan dalam lokal.
  • Sebuah request objek yang tersimpan pada stack, yang pada gilirannya disimpan dalam lokal.
  • sebuah requestobjek yang merupakan proxy untuk objek pada tumpukan disimpan dalam lokal. <- inilah yang dilakukan Flask.

4
Ikhtisar yang sangat baik, saya telah mempelajari kode di flask / globals.py dan werkzeug / local.py dan ini membantu memperjelas pemahaman saya tentang hal itu. Perasaan spidey saya memberi tahu saya bahwa ini adalah desain yang terlalu rumit, tapi saya akui saya tidak mengerti semua kasus penggunaan yang dimaksudkan. "Pengalihan internal" adalah satu-satunya pembenaran yang telah saya lihat dalam deskripsi di atas, dan googling "pengalihan internal lab" tidak muncul banyak sehingga saya masih sedikit bingung. Salah satu hal yang saya sukai tentang labu adalah itu umumnya bukan benda java-jenis sup penuh AbstractProviderContextBaseFactories dan semacamnya.
QuadrupleA

1
@ QuadrupleA Setelah Anda memahami bagaimana ini Local,, LocalStackdan LocalProxybekerja, saya sarankan untuk mengunjungi kembali artikel-artikel ini dari dokumen: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev , dan flask.pocoo .org / docs / 0.11 / reqcontext . Genggaman segar Anda bisa membuat Anda melihatnya dengan cahaya baru dan dapat memberikan lebih banyak wawasan.
Michael Ekoka

Bacalah tautan-tautan tersebut - sebagian besar masuk akal tetapi desainnya masih terasa terlalu rumit dan mungkin terlalu pintar untuk kebaikannya sendiri. Tapi saya bukan penggemar OOP secara umum, dan hal-hal kontrol aliran implisit (menimpa __call __ (), __getattr __ (), pengiriman acara dinamis versus panggilan fungsi sederhana, membungkus barang-barang di accessor properti daripada hanya menggunakan atribut biasa, dll. .) jadi mungkin itu hanya perbedaan filosofi. Juga bukan seorang praktisi TDD, yang tampaknya didukung oleh banyak alat ekstra ini.
QuadrupleA

1
Terima kasih telah berbagi ini, dihargai. Threading adalah kelemahan dengan bahasa seperti python - Anda berakhir dengan pola seperti di atas yang bocor ke dalam kerangka kerja aplikasi dan yang tidak benar-benar skala baik. Java adalah contoh lain dalam situasi serupa kembali. threadlocals, semaphor dll. Sangat sulit untuk mendapatkan yang benar, atau mempertahankan. Di sinilah bahasa seperti Erlang / Elixir (menggunakan BEAM), atau pendekatan loop peristiwa (mis. Nginx vs apache dll) biasanya menawarkan pendekatan yang lebih kuat, dapat diukur, dan kurang kompleks.
arcseldon

13

Tambahan kecil @ Mark Hildreth jawaban.

Tumpukan konteks terlihat seperti {thread.get_ident(): []}, di mana []disebut "tumpukan" karena hanya digunakan operasi append( push), popdan [-1]( __getitem__(-1)). Jadi tumpukan konteks akan menyimpan data aktual untuk utas atau utas greenlet.

current_app, g, request, sessionDan lain-lain adalah LocalProxyobjek yang hanya overrided metode khusus __getattr__, __getitem__, __call__, __eq__dan lain-lain dan nilai kembali dari konteks tumpukan atas ( [-1]) dengan nama argumen ( current_app, requestmisalnya). LocalProxydiperlukan untuk mengimpor objek ini sekali dan mereka tidak akan kehilangan aktualitas. Jadi lebih baik impor sajarequest mana pun Anda berada dalam kode daripada bermain dengan mengirimkan argumen permintaan ke fungsi dan metode Anda. Anda dapat dengan mudah menulis ekstensi dengan itu, tetapi jangan lupa bahwa penggunaan sembrono dapat membuat kode lebih sulit untuk dipahami.

Luangkan waktu untuk memahami https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .

Jadi bagaimana dihuni kedua tumpukan? Atas permintaan Flask:

  1. buat request_contextdengan lingkungan (init map_adapter, match path)
  2. masukkan atau dorong permintaan ini:
    1. hapus sebelumnya request_context
    2. buat app_contextjika tidak terjawab dan didorong ke tumpukan konteks aplikasi
    3. permintaan ini didorong untuk meminta tumpukan konteks
    4. sesi init jika tidak terjawab
  3. permintaan pengiriman
  4. hapus permintaan dan keluarkan dari tumpukan

2

Mari kita ambil satu contoh, misalkan Anda ingin mengatur konteks pengguna (menggunakan konstruk flask dari Local dan LocalProxy).

Tentukan satu kelas Pengguna:

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

mendefinisikan fungsi untuk mengambil kembali objek pengguna di dalam utas atau greenlet saat ini

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

Sekarang tentukan LocalProxy

usercontext = LocalProxy(partial(get_user, Local()))

Sekarang untuk mendapatkan userid dari pengguna di utas saat ini usercontext.userid

penjelasan:

1.Local memiliki dict identitas dan objek, identitas adalah id threadid atau greenlet, dalam contoh ini _local.user = Pengguna () sama dengan _local .___ penyimpanan __ [id utas saat ini] ["pengguna"] = Pengguna ()

  1. Operasi delegasi LocalProxy untuk membungkus objek lokal atau Anda dapat menyediakan fungsi yang mengembalikan objek target. Dalam contoh di atas, fungsi get_user menyediakan objek pengguna saat ini ke LocalProxy, dan ketika Anda meminta userid pengguna saat ini dengan usercontext.userid, fungsi __getattr__ LocalProxy pertama-tama memanggil get_user untuk mendapatkan objek Pengguna (pengguna) dan kemudian memanggil getattr (pengguna, "userid"). untuk mengatur userid pada Pengguna (di utas saat ini atau greenlet) Anda cukup lakukan: usercontext.userid = "user_123"
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.