Andrew Godwin menerbitkan DEP 0009: Django yang mampu Async pada 9 Mei, dan disetujui oleh dewan teknis Django pada 21 Juli, sehingga diharapkan dengan rilis Django 3.0 mereka akan memiliki waktu untuk melakukan sesuatu yang menarik. Itu sudah disebutkan di suatu tempat di komentar Habr , tetapi saya memutuskan untuk menyampaikan berita ini kepada khalayak yang lebih luas dengan menerjemahkannya - terutama bagi mereka yang, seperti saya, tidak secara khusus mengikuti berita Django.
Asynchronous Python telah dikembangkan selama bertahun-tahun, dan dalam ekosistem Django, kami bereksperimen dengannya di Channels dengan fokus utama pada dukungan soket web.
Ketika ekosistem berkembang, menjadi jelas bahwa sementara tidak ada kebutuhan mendesak untuk memperluas Django untuk mendukung protokol non-HTTP seperti soket web, dukungan asinkron akan memberikan banyak manfaat untuk kerangka kerja model-view-template Django tradisional.
Manfaatnya dijelaskan pada bagian Motivasi di bawah ini, tetapi kesimpulan umum yang saya dapatkan adalah bahwa kita mendapatkan begitu banyak dari Django asinkron yang sepadan dengan kerja keras yang diperlukan. Saya juga percaya bahwa sangat penting untuk membuat perubahan dengan cara yang berulang-ulang, yang didukung masyarakat yang tidak akan bergantung pada satu atau dua kontributor lama yang mungkin hangus.
Meskipun dokumen tersebut ditetapkan sebagai DEP "Fitur", semua ini berarti bahwa ia juga sebagian merupakan DEP Proses. Cakupan perubahan yang diusulkan di bawah ini sangat besar, dan meluncurkannya sebagai proses fitur tunggal tradisional cenderung gagal.
Tentu saja, di seluruh dokumen ini, penting untuk mengingat filosofi Django, yaitu menjaga semuanya aman dan terbelakang kompatibel. Rencananya bukan untuk menghapus Django yang sinkron - rencananya adalah menyimpannya dalam bentuk saat ini, tetapi menambahkan asinkron sebagai pilihan bagi mereka yang berpikir mereka membutuhkan kinerja atau fleksibilitas ekstra.
Apakah ini pekerjaan raksasa? Tentu saja Tetapi saya merasa bahwa ini dapat secara signifikan mengubah masa depan Django - kami memiliki kesempatan untuk mengambil kerangka kerja yang terbukti, dan komunitas yang luar biasa, dan memperkenalkan serangkaian opsi yang benar-benar baru yang sebelumnya tidak mungkin.
Web telah berubah, dan Django harus berubah dengan itu, tetapi sesuai dengan cita-cita kami, menjadi terjangkau, aman secara default, dan fleksibel ketika proyek tumbuh dan kebutuhan mereka berubah. Dalam dunia pergudangan data cloud, arsitektur berorientasi layanan, dan backend sebagai dasar logika bisnis yang kompleks, kemampuan untuk melakukan berbagai hal secara kompetitif adalah kuncinya.
DEP ini menguraikan rencana yang saya pikir akan membawa kita ke sana. Ini adalah visi yang saya benar-benar percaya dan dengan yang saya akan bekerja untuk membantu melakukan segala yang mungkin. Pada saat yang sama, analisis dan skeptisisme yang cermat dibenarkan; Saya meminta kritik membangun Anda, serta kepercayaan Anda. Django bergantung pada komunitas orang dan aplikasi yang mereka buat, dan jika kita perlu menentukan jalan menuju masa depan, kita harus melakukannya bersama.
Deskripsi singkat
Kami akan menambahkan dukungan untuk representasi asinkron, middleware, ORM, dan elemen penting lainnya ke Django.
Ini akan dilakukan dengan menjalankan kode sinkron di utas, secara bertahap menggantinya dengan kode asinkron. API sinkron akan terus ada dan didukung penuh, dan seiring waktu akan berubah menjadi pembungkus sinkron untuk kode asinkron yang awalnya.
Mode ASGI akan meluncurkan Django sebagai aplikasi asinkron asli. Mode WSGI akan memicu loop peristiwa terpisah setiap kali Django diakses, sehingga lapisan asinkron kompatibel dengan server sinkron.
Multithreading di sekitar ORM adalah kompleks dan membutuhkan konsep konteks koneksi dan lengket baru untuk menjalankan kode ORM yang sinkron.
Banyak bagian Django akan terus bekerja dalam sinkronisasi, dan prioritas kami adalah untuk mendukung pengguna menulis pandangan dalam kedua gaya, memungkinkan mereka untuk memilih gaya terbaik untuk presentasi yang sedang mereka kerjakan.
Beberapa fungsi, seperti templat dan caching, akan membutuhkan DEP dan studi terpisah mereka sendiri tentang cara membuatnya sama sekali tidak sinkron. DEP ini terutama berfokus pada aliran HTTP-middleware-view dan ORM.
Akan ada kompatibilitas penuh. Proyek Django 2.2 standar harus dijalankan dalam Django asinkron (baik 3.0 atau 3.1) tanpa perubahan.
Proposal ini difokuskan pada penerapan bagian-bagian kecil yang berulang dengan penempatan bertahap mereka di cabang utama untuk menghindari masalah dengan garpu berumur panjang dan memungkinkan kami untuk mengubah arah saat kami mendeteksi masalah.
Ini adalah kesempatan bagus untuk menarik anggota baru. Kita harus membiayai proyek sehingga ini terjadi lebih cepat. Pendanaan harus dalam skala yang tidak biasa kita lakukan.
Spesifikasi
Tujuan keseluruhan adalah untuk membuat setiap bagian Django, yang dapat memblokir - yaitu, bukan hanya perhitungan yang terikat CPU - menjadi asinkron (berjalan dalam loop peristiwa asinkron tanpa kunci).
Ini termasuk fitur-fitur berikut:
- Lapisan antara (Middleware)
- Tampilan
- ORM
- Pola
- Pengujian
- Caching
- Validasi formulir
- Email
Namun, ini tidak termasuk hal-hal seperti internasionalisasi, yang tidak akan membawa peningkatan kinerja, karena ini adalah tugas yang terikat CPU yang juga berjalan cepat, atau migrasi yang single-threaded ketika diluncurkan melalui perintah manajemen.
Setiap fungsi individu yang menjadi asinkron di dalamnya juga akan menyediakan antarmuka sinkron yang kompatibel dengan API saat ini (dalam 2.2) untuk masa mendatang - kami dapat mengubahnya dari waktu ke waktu untuk membuatnya lebih baik, tetapi API sinkron tidak akan pergi ke mana pun.
Gambaran umum bagaimana hal ini dicapai secara teknis diberikan di bawah ini, dan kemudian rincian implementasi spesifik untuk bidang-bidang tertentu diberikan. Ini tidak lengkap untuk semua fungsi Django, tetapi jika kita mencapai tujuan awal ini, maka kita akan memasukkan hampir semua kasus penggunaan.
Bagian terakhir dari bagian ini, "Prosedur," juga membahas bagaimana perubahan ini dapat diterapkan secara bertahap dan oleh beberapa kelompok pengembang secara paralel, yang penting untuk menyelesaikan perubahan ini dengan bantuan sukarelawan dalam jumlah waktu yang wajar.
Ulasan teknis
Prinsip yang memungkinkan kita untuk mempertahankan implementasi sinkron dan asinkron secara paralel adalah kemampuan untuk menjalankan satu gaya di dalam yang lain.
Setiap fungsi akan melalui tiga tahap implementasi:
- Hanya sinkron (kami ada di sini)
- Implementasi sinkron dengan pembungkus asinkron
- Implementasi asinkron dengan pembungkus sinkron
Pembungkus asinkron
Pertama, kode sinkron yang ada akan dibungkus dalam antarmuka asinkron, yang menjalankan kode sinkron di kumpulan utas. Ini akan memungkinkan kami untuk merancang dan menyediakan antarmuka asinkron secara relatif cepat, tanpa harus menulis ulang semua kode yang tersedia untuk asinkron.
Toolkit untuk ini sudah tersedia di asgiref sebagai fungsi sync_to_async
, yang mendukung hal-hal seperti penanganan pengecualian atau threadlocals (lebih lanjut tentang ini di bawah).
Menjalankan kode di utas kemungkinan besar tidak akan mengarah pada peningkatan produktivitas - overhead yang muncul mungkin akan memperlambatnya sedikit ketika Anda hanya menjalankan kode linier normal - tetapi ini akan memungkinkan pengembang untuk mulai menjalankan sesuatu secara kompetitif dan terbiasa dengan fitur baru.
Selain itu, ada beberapa bagian Django yang sensitif untuk memulai di utas yang sama setelah akses berulang; misalnya, memproses transaksi dalam database. Jika kami membungkus beberapa kode dalam atomic()
yang kemudian akan mengakses ORM melalui utas acak yang diambil dari kumpulan, transaksi tidak akan berpengaruh, karena terikat pada koneksi di dalam utas tempat transaksi dimulai.
Dalam situasi seperti itu, sebuah "sticky thread" diperlukan di mana konteks asinkron memanggil semua kode sinkron secara berurutan di thread yang sama alih-alih mendorongnya ke kumpulan thread, sambil mempertahankan perilaku yang benar dari ORM dan bagian-bagian yang sensitif terhadap thread lainnya. Semua bagian Django yang kami duga membutuhkannya, termasuk seluruh ORM, akan menggunakan versi sync_to_async
, yang mempertimbangkan ini, jadi semuanya aman secara default. Pengguna akan dapat secara selektif menonaktifkan ini untuk eksekusi permintaan kompetitif - untuk lebih jelasnya lihat "ORM" di bawah ini.
Implementasi asinkron
Langkah selanjutnya adalah menulis ulang implementasi fungsi ke kode asinkron dan kemudian menyajikan antarmuka sinkron melalui pembungkus yang mengeksekusi kode asinkron dalam satu kali peristiwa loop. Ini sudah tersedia di asgiref sebagai fungsi async_to_sync
.
Tidak perlu menulis ulang semua fungsi sekaligus untuk dengan cepat melompat ke tahap ketiga. Kami dapat memfokuskan upaya kami pada bagian yang dapat kami lakukan dengan baik dan yang memiliki dukungan perpustakaan pihak ketiga, sambil membantu seluruh ekosistem Python dalam hal-hal yang memerlukan lebih banyak pekerjaan untuk mengimplementasikan asinkroni asli; Ini dibahas di bawah ini.
Gambaran umum ini berfungsi dengan hampir semua fungsi Django yang seharusnya menjadi asinkron, kecuali untuk tempat-tempat yang Python tidak menyediakan setara fungsi asinkron yang sudah kita gunakan. Hasilnya akan berupa perubahan dalam cara Django menyajikan API dalam mode asinkron, atau bekerja dengan pengembang inti Python untuk membantu mengembangkan fitur asinkron Python.
Threadlocals
Salah satu detail dasar implementasi Django yang perlu disebutkan secara terpisah dari sebagian besar fungsi yang dijelaskan di bawah ini adalah threadlocals. Seperti namanya, threadlocals bekerja di dalam sebuah thread, dan meskipun Django membuat objek HttpRequest
luar threadlocal, kami menempatkan beberapa hal lain di dalamnya - misalnya, koneksi database atau bahasa saat ini.
Menggunakan threadlocals dapat dibagi menjadi dua opsi:
- "Konteks lokal", di mana nilai diperlukan dalam beberapa konteks berbasis tumpukan, seperti permintaan. Ini diperlukan untuk mengatur bahasa saat ini.
- "True threadlocals", di mana kode yang dilindungi sebenarnya tidak aman untuk memanggil dari utas lainnya. Ini untuk menghubungkan ke database.
Pada pandangan pertama, mungkin terlihat bahwa βkonteks lokalβ dapat diselesaikan dengan menggunakan modul contextvars baru dengan Python, tetapi Django 3.0 masih harus mendukung Python 3.6, sementara modul ini muncul di 3.7. Selain itu, contextvars
dirancang khusus untuk menghilangkan konteks ketika beralih, misalnya, ke utas baru, sementara kita perlu menyimpan nilai-nilai ini untuk memungkinkan fungsi sync_to_async
dan async_to_sync
berfungsi normal sebagai pembungkus. Ketika Django hanya akan mendukung 3.7 dan yang lebih baru, kami mungkin mempertimbangkan untuk menggunakan contextvars
, tetapi itu akan membutuhkan banyak kerja di Django.
Ini telah diselesaikan dengan asgiref Local
, yang kompatibel dengan coroutine dan utas. Sekarang tidak menggunakan contextvars
, tetapi kita dapat mengubahnya untuk bekerja dengan backport untuk 3,6 setelah beberapa pengujian.
Di lain pihak, threadlocals sejati dapat terus bekerja di utas saat ini. Namun, kita harus lebih berhati-hati untuk mencegah benda-benda tersebut bocor ke aliran lain; ketika presentasi tidak lagi dieksekusi di utas yang sama, tetapi memunculkan utas untuk setiap panggilan ORM (selama fase "implementasi sinkron, pembungkus tidak sinkron"), beberapa hal yang mungkin dalam mode sinkron tidak akan mungkin dalam tidak sinkron.
Ini akan membutuhkan perhatian khusus dan larangan beberapa operasi yang sebelumnya mungkin dalam mode asinkron; Kasus-kasus yang kami ketahui dijelaskan di bawah ini di bagian tertentu.
Dukungan simultan untuk antarmuka sinkron dan asinkron
Salah satu masalah besar yang akan kita temui ketika mencoba port Django adalah bahwa Python tidak memungkinkan Anda untuk membuat versi fungsi yang sinkron dan asinkron dengan nama yang sama.
Ini berarti Anda tidak bisa hanya mengambil dan membuat API yang berfungsi seperti ini:
Ini adalah batasan yang disayangkan dari cara Python diimplementasikan secara asinkron, dan tidak ada solusi yang jelas. Ketika sesuatu dipanggil, Anda tidak tahu apakah Anda akan menunggu atau tidak, jadi tidak ada cara untuk menentukan apa yang perlu dikembalikan.
(Catatan: ini karena Python mengimplementasikan fungsi asinkron sebagai "callable sinkron yang mengembalikan coroutine," daripada sesuatu seperti "memanggil metode __acall__
pada suatu objek." Manajer konteks dan iterator asinkron tidak memiliki masalah ini karena mereka memiliki metode terpisah __aiter__
dan __aenter__
.)
Dengan mengingat hal ini, kita harus menempatkan ruang nama implementasi sinkron dan asinkron secara terpisah satu sama lain sehingga tidak bertentangan. Kita bisa melakukan ini dengan argumen sync=True
, tetapi ini mengarah pada kumpulan fungsi / metode yang membingungkan dan mencegah penggunaan async def
, dan juga memungkinkan Anda untuk secara tidak sengaja lupa menulis argumen ini. Panggilan acak ke metode sinkron ketika Anda ingin menyebutnya asinkron berbahaya.
Solusi yang diusulkan untuk sebagian besar tempat di basis kode Django adalah untuk memberikan akhiran untuk nama-nama implementasi asinkron fungsi - sebagai contoh, cache.get_async
sebagai tambahan dari cache.get
sinkron. Meskipun ini adalah solusi yang jelek, itu membuatnya sangat mudah untuk mendeteksi kesalahan saat melihat kode (Anda harus menggunakan await
dengan metode _async
).
Penanganan Tampilan dan HTTP
Tampilan mungkin merupakan landasan kegunaan asinkron, dan kami berharap sebagian besar pengguna memilih antara kode asinkron dan sinkron.
Django akan mendukung dua jenis tampilan:
- Representasi sinkron, didefinisikan, seperti sekarang, oleh fungsi atau kelas yang sinkron dengan
__call__
sinkron - Representasi asinkron ditentukan oleh fungsi asinkron (mengembalikan coroutine) atau kelas dengan
__call__
asinkron.
Mereka akan ditangani oleh BaseHandler
, yang akan memeriksa tampilan yang diterima dari penyelesai URL dan menyebutnya sesuai. Handler dasar harus menjadi bagian pertama dari Django untuk menjadi asinkron, dan kita perlu memodifikasi handler WSGI untuk memanggilnya dalam event loop sendiri menggunakan async_to_sync
.
Lapisan perantara (middleware) atau pengaturan seperti ATOMIC_REQUESTS
, yang membungkus tampilan dalam kode yang tidak asinkron secara aman (misalnya, blok atomic()
), akan terus berfungsi, tetapi kecepatannya akan terpengaruh (misalnya, larangan panggilan ORM paralel di dalam tampilan dengan atomic()
)
Kelas StreamingHttpResponse
ada akan dimodifikasi untuk dapat menerima iterator sinkron atau asinkron, dan kemudian implementasi internalnya akan selalu asinkron. Demikian pula untuk FileResponse
. Karena ini merupakan titik ketidakcocokan mundur potensial untuk kode pihak ketiga yang secara langsung mengakses objek Respons, kita masih perlu menyediakan __iter__
sinkron untuk periode transisi.
WSGI akan terus didukung oleh Django tanpa batas waktu, tetapi handler WSGI akan melanjutkan untuk menjalankan middleware asinkron dan melihat dalam satu kali event loop. Ini kemungkinan akan menyebabkan sedikit penurunan kinerja, tetapi dalam percobaan awal itu tidak memiliki dampak terlalu banyak.
Semua fungsi HTTP asinkron akan bekerja di dalam WSGI, termasuk polling panjang dan respons lambat, tetapi mereka akan tidak seefisien sekarang, mengambil utas / proses untuk setiap koneksi. Server ASGI akan menjadi satu-satunya yang secara efisien dapat mendukung banyak permintaan bersamaan, serta menangani protokol non-HTTP, seperti WebSocket, untuk digunakan oleh ekstensi seperti Channels .
Lapisan menengah
Sementara bagian sebelumnya terutama berfokus pada jalur permintaan / respons, middleware membutuhkan bagian terpisah karena kompleksitas yang melekat dalam desain mereka saat ini.
Middleware Django sekarang disusun dalam bentuk tumpukan di mana setiap middleware mendapatkan get_response
untuk menjalankan middleware berikutnya dalam urutan (atau tampilan untuk middleware terendah pada tumpukan). Namun, kita perlu mempertahankan campuran middleware sinkron dan asinkron untuk kompatibilitas, dan kedua jenis ini tidak akan dapat saling mengakses secara asli.
Dengan demikian, untuk memastikan bahwa middleware berfungsi, kita harus menginisialisasi setiap middleware dengan get_response placeholder, yang sebaliknya mengembalikan kontrol kembali ke handler dan menangani transfer data antara middleware dan tampilan, dan pengecualian lemparan. Di satu sisi, pada akhirnya akan terlihat seperti middleware dari era Django 1.0 dari sudut pandang internal, meskipun, tentu saja, API pengguna akan tetap sama.
Kami dapat mendeklarasikan middleware sinkron yang sudah usang, tetapi saya sarankan tidak melakukan ini dalam waktu dekat. Jika dan ketika kita sampai pada akhir siklus keusangannya, kita kemudian dapat mengembalikan implementasi middleware ke model tumpukan rekursif murni, seperti sekarang.
ORM
ORM adalah bagian terbesar dari Django dalam hal ukuran kode dan yang paling sulit dikonversi menjadi asinkron.
Hal ini sebagian besar disebabkan oleh fakta bahwa driver basis data yang mendasarinya sinkron dengan desain, dan kemajuan akan lambat menuju seperangkat driver basis data yang matang, standar, asinkron. Sebagai gantinya, kita harus merancang masa depan di mana driver basis data pada awalnya akan sinkron, dan meletakkan dasar bagi kontributor yang selanjutnya akan mengembangkan driver asinkron dengan cara yang berulang-ulang.
Masalah dengan ORM terbagi dalam dua kategori utama - utas dan pemblokiran implisit.
Streaming
Masalah utama dengan ORM adalah bahwa Django dirancang di sekitar objek connections
global tunggal, yang secara ajaib memberi Anda koneksi yang tepat untuk utas Anda saat ini.
Di dunia asinkron - di mana semua coroutine bekerja di utas yang sama - ini tidak hanya menjengkelkan, tetapi juga berbahaya. Tanpa keamanan tambahan, pengguna yang mengakses ORM seperti biasa berisiko mematahkan objek koneksi dengan mengaksesnya dari beberapa tempat berbeda.
Untungnya, objek koneksi setidaknya bersifat portable di antara utas, meskipun tidak dapat dipanggil dari dua utas secara bersamaan. Django sudah peduli tentang keamanan utas untuk driver basis data dalam kode ORM, jadi kami memiliki tempat untuk mengubah perilakunya agar berfungsi dengan benar.
Kami akan memodifikasi objek connections
sehingga memahami coroutine dan utas - menggunakan kembali beberapa kode dari asgiref.local
, tetapi dengan penambahan logika tambahan. Koneksi akan dibagi dalam kode asinkron dan sinkron yang memanggil satu sama lain - dengan konteks melewati sync_to_async
dan async_to_sync
- dan kode sinkron akan dipaksa untuk dieksekusi secara berurutan dalam satu untaian lengket, jadi ini tidak akan berfungsi pada saat yang sama melanggar keamanan benang.
Ini menyiratkan bahwa kita memerlukan solusi seperti manajer konteks untuk membuka dan menutup koneksi database, seperti atomic()
. Ini akan memungkinkan kami untuk memberikan panggilan yang konsisten dan lengket utas dalam konteks ini dan memungkinkan pengguna untuk membuat beberapa konteks jika mereka ingin membuka beberapa koneksi. Ini juga memberi kita cara potensial untuk menyingkirkan connections
global magis jika kita ingin mengembangkan ini lebih lanjut.
Saat ini, Django tidak memiliki manajemen siklus hidup koneksi yang independen dari sinyal dari kelas handler, dan oleh karena itu kami akan menggunakannya untuk membuat dan menghapus "konteks koneksi" ini. Dokumentasi juga akan diperbarui untuk memperjelas cara menangani koneksi dengan benar di luar siklus permintaan / respons; bahkan dalam kode saat ini, banyak pengguna tidak tahu bahwa tim manajemen lama mana pun harus secara berkala memanggil close_old_connections
agar berfungsi dengan benar.
Kompatibilitas mundur berarti bahwa kami harus mengizinkan pengguna mengakses connections
dari sembarang kode acak kapan saja, tetapi kami hanya akan mengizinkan ini untuk kode sinkron; kami akan memastikan bahwa kode tersebut dibungkus dalam "konteks koneksi", jika asinkron, mulai hari pertama.
Mungkin sepertinya akan menyenangkan untuk menambahkan transaction.atomic()
di samping transaction.atomic()
dan mengharuskan pengguna untuk menjalankan semua kode di dalam salah satu dari mereka, tetapi ini dapat menyebabkan kebingungan tentang apa yang terjadi jika Anda melampirkan salah satunya ada di dalam yang lain.
Alih-alih, saya sarankan untuk membuat manajer konteks baru db.new_connections()
yang memungkinkan perilaku ini, dan membuatnya membuat koneksi baru setiap kali dipanggil, dan memungkinkan sembarang atomic()
bersarang di dalamnya.
Setiap kali Anda new_connections()
blok new_connections()
, Django membuat konteks baru dengan koneksi database baru. Semua transaksi yang dilakukan di luar blok berlanjut; setiap panggilan ORM di dalam blok berfungsi dengan koneksi baru ke database dan akan melihat database dari sudut pandang ini. Jika isolasi transaksi diaktifkan dalam database, seperti yang biasanya dilakukan secara default, ini berarti bahwa koneksi baru di dalam blok mungkin tidak melihat perubahan yang dilakukan oleh transaksi yang tidak berkomitmen di luarnya.
Selain itu, koneksi di dalam blok new_connections
ini sendiri dapat menggunakan atomic()
untuk memicu transaksi tambahan pada koneksi baru ini. Setiap sarang dari dua konteks manajer ini diperbolehkan, tetapi setiap kali new_connections
digunakan, transaksi yang dibuka sebelumnya "ditangguhkan" dan tidak mempengaruhi panggilan ORM sampai blok new_connections
baru baru new_connections
.
Contoh tampilan API ini:
async def get_authors(pattern):
Ini agak bertele-tele, tetapi tujuannya juga untuk menambahkan pintasan tingkat tinggi untuk mengaktifkan perilaku ini (dan juga mencakup transisi dari asyncio.ensure_future
dengan Python 3.6 ke asyncio.create_task
di 3.7).
Menggunakan manajer konteks ini dan aliran sticky dalam konteks koneksi yang sama, kami menjamin bahwa semua kode akan seaman yang kami bisa secara default. ada kemungkinan bahwa pengguna dapat menggunakan koneksi dalam satu utas untuk dua bagian berbeda dari permintaan menggunakan yield
, tetapi ini yield
mungkin sekarang.
Kunci implisit
Masalah lain dari desain ORM saat ini adalah bahwa operasi pemblokiran (terkait jaringan), khususnya bidang terkait bacaan, ditemui dalam contoh model.
Jika Anda mengambil contoh model dan kemudian mengakses model_instance.related_field
, Django akan memuat konten model terkait secara transparan dan mengembalikannya kepada Anda. Namun, ini tidak mungkin dalam kode asinkron - kode pemblokiran tidak boleh dieksekusi di utas utama, dan tidak ada akses asinkron ke atribut.
Untungnya, Django sudah memiliki jalan keluar dari hal ini - select_related
, yang memuat bidang terkait sebelumnya, dan prefetch_related
untuk banyak-ke-banyak hubungan. Jika Anda menggunakan ORM secara tidak sinkron, kami akan melarang operasi apa pun yang secara implisit memblokir, seperti akses latar belakang ke atribut, dan sebagai gantinya mengembalikan kesalahan yang menunjukkan bahwa Anda harus terlebih dahulu mengambil bidang.
Ini memiliki manfaat tambahan untuk mencegah kode lambat yang mengeksekusi permintaan N dalam loop for
, yang merupakan kesalahan umum banyak programmer Django baru. Ini memunculkan penghalang entri, tetapi ingat bahwa Django asinkron akan menjadi opsional - pengguna masih dapat menulis kode sinkron jika mereka mau (dan ini akan didorong dalam tutorial, karena kode sinkron jauh lebih sulit untuk membuat kesalahan).
QuerySet
, untungnya, dapat dengan mudah mengimplementasikan generator asinkron dan secara transparan mendukung sinkronisasi dan asinkron:
async def view(request): data = [] async for user in User.objects.all(): data.append(await extract_important_info(user)) return await render("template.html", data)
Lainnya
Bagian ORM yang terkait dengan perubahan skema tidak akan tidak sinkron; mereka harus dipanggil hanya dari tim manajemen. Beberapa proyek sudah memanggil mereka dalam pengiriman, tetapi ini bukan ide yang baik.
Pola
Template sekarang sepenuhnya sinkron, dan rencananya adalah membiarkannya seperti itu di langkah pertama. , , DEP.
, Jinja2 , .
, Django , . Jinja2 , , , .
, render_async
, render
; , , .
Django β _async
- (, get_async
, set_async
).
, API sync_to_async
, BaseCache
.
, thread-safety API , Django, , . , ORM, , .
, , , ModelForm
ORM .
, - clean
save
, , . , , , DEP.
Email
Django, . send_mail_async
send_mail
, async
- (, mail_admins
).
Django , - SMTP, . , , , , .
Pengujian
, Django .
ASGI- asgiref.testing.ApplicationCommunicator
. assert' .
Django , , . , β , , HTTP event loop, WSGI.
. , , .
, , . async def
@async_to_sync
, , , Django test runner.
asyncio ( loop' , ) , , , DEBUG=True
. β , , .
WebSockets
Django; , Channels , ASGI, .
, Channels, , ASGI.
, , . , .
, . , β .
, , . , ORM, , , .
:
; , . , , , , .
, - ; , , . , , Django, Django async-only .
, , DEP , , , email . DBAPI β , core Python , , PEP, .
, , , . Django , - , -; .
, - . , β , , .
Python β . - Python , , .
Python asyncio
, , . , , , , Django-size .
Django, «»; , , β , Django β .
, . , API , Django - .
, , Django . , Django ORM , , , -.
, , β . - long-poll server-sent events. Django , - .
Django; . , , , .
Django , . ; Django- , , , , , .
, -- Django .
, . Β« DjangoΒ», ; , , .
, , , , API Django, , , Python 3, API, Django Python.
Python
Python . Python, , , .
, Django β - Python β , Python, Python . , , , .
, Django, . , Django , .
β , , , , .
, β , , β Django ( , Python ).
Django?
, Django. , Lawrence Journal-World β , , SPA β , , . , , , , .
, Django , - , β β . , , ; , Django , .
, Django . , . , , , β , , .
Django django-developers , , , , DEP.
, , , :
- : master- Django .
- : Django , , , , Django .
- : , , Django, , Django. , , , , .
Channels DEP ; Django , .
, , . DEP , , β Django .
. Django, , , WSGI , event loop , . 10% β , . , .
, , (- , Python ). , Django ; , , , Django master- .
, ( ORM, ..), ; Python.
, , Django, «» . , , , , .
Alternatif
, , , .
_async
, , (, django.core.cache.cache.get_async
), :
from django.core.cache_async import cache cache.get("foo")
, ; , , .
, , ; .
Django
- , ; , , , β .
, , , β , .
Channels
, Channels , «» Django. , - , , Django; ORM, HTTP/middleware flow .
asyncio
event loop' Python, asyncio
, Django. await
async
Python event loop .
, asyncio
, ; Django , , . Django ; , , async runtime, , .
Greenlets/Gevent
Gevent, , Python.
, . yield
await
, API, Django, , . , .
, , , . greenlet-safe Django ORM - new-connection-context, .
, . Django Β« Β» gevent, , , , .
DEP.
, β , β , ( ).
, , - . Django Fellows ; β , ( ), , , - .
β , Kickstarter migrations
contrib.postgres
, MOSS (Mozilla) Channels . , Django, , .
, . β Python, Django β β . , Django/async, .
HTTP/middleware/view flow, , , , Β« Β», .
, , , ( , Fellows, / , Channels, , ), , .
, , , , Django, .
, , , , API.
, , , , HTTP/middleware flow. , API, APM, .
, , Django , , . , ORM , , β , ORM .
DEP , ; Django .
, asgiref , , . Django Django.
Channels , Django, Django.
( ) CC0 1.0 Universal .