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.
Jawaban:
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" :
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 ...
Kemudian, selama tampilan, Anda dapat menggunakan
request
untuk mengakses informasi dari permintaan saat ini. Jelas,request
ini 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 meneleponrequest.path
, dapatkanpath
atribut darirequest
objek permintaan CURRENT." Dua permintaan berbeda akan memiliki hasil berbeda untukrequest.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.path
dan 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:
Seperti
request
contoh kita ,url_for
fungsi 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 karenanyaurl_for('/login')
harus mengembalikan sesuatu yang berbeda tergantung pada apakah tampilan menangani permintaan untuk aplikasi frontend atau backend.Untuk menjawab pertanyaan Anda ...
Dari dokumen Konteks Permintaan:
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.
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).
"Konteks permintaan" adalah salah satu item dari "tumpukan konteks permintaan". Demikian pula dengan "konteks aplikasi" dan "tumpukan konteks aplikasi".
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 ...
Kemudian mereka menggunakan
app
dandb
nilai - nilai dalam skrip yang harus dijalankan dari shell. Misalnya, skrip "setup_tables.py" ...Dalam hal ini, ekstensi Flask-SQLAlchemy tahu tentang
app
aplikasi, tetapi selamacreate_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 menjalankancreate_all
metode.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 ...
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:
sumber
request = Local()
desain yang lebih sederhana sudah cukup untuk global.py? Mungkin ada kasus penggunaan yang tidak saya pikirkan.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 utas
Local
fasilitas yang memungkinkan objek sepertirequest
dang
untuk 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.local
danflask.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 memilikirequest
,current_app
,url_for
,g
, dan konteks-terikat benda lain seperti global.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. misalnyaKedua nilai hadir pada
Local
objek yang dapat diakses secara global pada saat yang sama, tetapi mengakseslocal.first_name
dalam konteks utas 1 akan memberi Anda'John'
, sedangkan itu akan kembali'Debbie'
pada utas 2.Bagaimana mungkin? Mari kita lihat beberapa kode (disederhanakan):
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
Local
objek per proses danrequest
,g
,current_app
dan lain-lain bisa hanya telah diciptakan seperti itu. Tapi itu bukan bagaimana hal itu dilakukan dalam termos di mana ini bukan objek secara teknisLocal
, tetapi lebih tepatnyaLocalProxy
objek. Apa ituLocalProxy
?Proxy Lokal
LocalProxy adalah objek yang meminta a
Local
untuk menemukan objek lain yang menarik (yaitu objek yang diproksikan). Mari kita lihat untuk memahami:Sekarang untuk membuat proxy yang dapat diakses secara global yang akan Anda lakukan
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
Keuntungan menggunakan
LocalProxy
objek yang dapat diakses secara global daripada membuatnyaLocals
sendiri adalah menyederhanakan pengelolaannya. Anda hanya perlu satuLocal
objek untuk membuat banyak proxy yang dapat diakses secara global. Pada akhir permintaan, selama pembersihan, Anda cukup melepaskan satuLocal
(yaitu Anda pop konteks_id dari penyimpanannya) dan tidak repot-repot dengan proxy, mereka masih dapat diakses secara global dan masih tunduk kepada yangLocal
menemukan objek mereka menarik untuk permintaan http berikutnya.Untuk menyederhanakan pembuatan
LocalProxy
ketika kita sudah memilikiLocal
, Werkzeug mengimplementasikanLocal.__call__()
metode ajaib sebagai berikut:Namun, jika Anda melihat pada sumber Flask (flask.globals) itu masih belum seberapa
request
,g
,current_app
dansession
diciptakan. 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 menggunakanLocalStack
objek untuk tujuan ini. Ketika mereka menyimpulkan bisnis mereka, mereka mengeluarkan konteks dari tumpukan.Tumpukan lokal
Seperti inilah
LocalStack
bentuknya (sekali lagi kode ini disederhanakan untuk memudahkan pemahaman logikanya).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
, dansession
benda menyelesaikan langsung keLocalStack
, agak menggunakanLocalProxy
benda-benda yang membungkus fungsi lookup (bukanLocal
objek) yang akan menemukan objek yang mendasari dariLocalStack
: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 pembuatanRequestContext
objek semua melalui selanjutnyapush()
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 ...
Kemudian permintaan http muncul dan server WSGI memanggil aplikasi dengan params yang biasa ...
Ini kira-kira yang terjadi di aplikasi ...
dan inilah kira-kira yang terjadi dengan RequestContext ...
Katakanlah permintaan telah selesai diinisialisasi, karena itu pencarian
request.path
dari salah satu fungsi tampilan Anda akan berjalan sebagai berikut:LocalProxy
objek yang dapat diakses secara globalrequest
._find_request()
(fungsi yang didaftarkan sebagaiself.local
).LocalStack
objek_request_ctx_stack
untuk konteks teratas pada stack.LocalStack
objek pertama kali memintaLocal
atribut batinnya (self.local
) untukstack
properti yang sebelumnya disimpan di sana.stack
itu mendapat konteks teratastop.request
dengan demikian diselesaikan sebagai objek yang menarik.path
atributJadi kita telah melihat bagaimana
Local
,LocalProxy
danLocalStack
bekerja, sekarang berpikir sejenak tentang implikasi dan nuansa dalam mengambilpath
dari:request
objek yang akan menjadi sederhana global objek diakses.request
objek yang akan menjadi lokal.request
objek yang tersimpan sebagai atribut lokal.request
objek yang merupakan proxy untuk sebuah objek disimpan dalam lokal.request
objek yang tersimpan pada stack, yang pada gilirannya disimpan dalam lokal.request
objek yang merupakan proxy untuk objek pada tumpukan disimpan dalam lokal. <- inilah yang dilakukan Flask.sumber
Local
,,LocalStack
danLocalProxy
bekerja, 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.Tambahan kecil @ Mark Hildreth jawaban.
Tumpukan konteks terlihat seperti
{thread.get_ident(): []}
, di mana[]
disebut "tumpukan" karena hanya digunakan operasiappend
(push
),pop
dan[-1]
(__getitem__(-1)
). Jadi tumpukan konteks akan menyimpan data aktual untuk utas atau utas greenlet.current_app
,g
,request
,session
Dan lain-lain adalahLocalProxy
objek 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
,request
misalnya).LocalProxy
diperlukan 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
:request_context
dengan lingkungan (initmap_adapter
, match path)request_context
app_context
jika tidak terjawab dan didorong ke tumpukan konteks aplikasisumber
Mari kita ambil satu contoh, misalkan Anda ingin mengatur konteks pengguna (menggunakan konstruk flask dari Local dan LocalProxy).
Tentukan satu kelas Pengguna:
mendefinisikan fungsi untuk mengambil kembali objek pengguna di dalam utas atau greenlet saat ini
Sekarang tentukan LocalProxy
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 ()
sumber