Andrew Godwin a publié le DEP 0009: Django compatible Async le 9 mai et a été approuvé par le conseil technique de Django le 21 juillet.Il est donc à espérer qu'au moment de la sortie de Django 3.0, ils pourront faire quelque chose d'intéressant. Cela a déjà été mentionné quelque part dans les commentaires de Habr , mais j'ai décidé de transmettre cette nouvelle à un public plus large en la traduisant - principalement pour ceux qui, comme moi, ne suivent pas particulièrement la nouvelle de Django.
Le Python asynchrone a été développé pendant de nombreuses années, et dans l'écosystème Django, nous l'avons expérimenté dans les canaux en se concentrant principalement sur le support de socket Web.
Au fur et à mesure que l'écosystème se développait, il est devenu évident que s'il n'était pas urgent d'étendre Django pour prendre en charge les protocoles non HTTP tels que les sockets Web, la prise en charge asynchrone offrirait de nombreux avantages pour le cadre traditionnel de modèle de vue de modèle Django.
Les avantages sont décrits dans la section Motivation ci-dessous, mais la conclusion générale à laquelle je suis arrivé est que nous obtenons tellement de Django asynchrone que cela en vaut la peine. Je crois également qu'il est très important d'apporter des changements de manière itérative et soutenue par la communauté qui ne dépendra pas d'un ou deux anciens contributeurs qui pourraient s'épuiser.
Bien que le document soit désigné DEP «Feature», tout cela signifie qu'il s'agit également en partie d'un processus DEP. La portée des changements proposés ci-dessous est incroyablement grande et leur lancement en tant que processus traditionnel à fonctionnalité unique est susceptible d'échouer.
Bien sûr, tout au long de ce document, il est important de se rappeler la philosophie de Django, qui est de garder tout en sécurité et rétrocompatible. Le plan n'est pas de supprimer Django synchrone - le plan est de le garder dans sa forme actuelle, mais d'ajouter l'asynchronie comme option pour ceux qui pensent avoir besoin de performances ou de flexibilité supplémentaires.
Est-ce un travail gigantesque? Bien sûr. Mais je pense que cela peut changer considérablement l'avenir de Django - nous avons la possibilité de prendre un cadre éprouvé et une communauté incroyable, et d'introduire un tout nouvel ensemble d'options qui étaient auparavant impossibles.
Le Web a changé, et Django devrait changer avec lui, mais conformément à nos idéaux, être abordable, sécurisé par défaut et flexible à mesure que les projets évoluent et que leurs besoins changent. Dans le monde de l'entreposage de données en nuage, de l'architecture orientée services et du backend en tant que base d'une logique métier complexe, la capacité de faire des choses de manière compétitive est essentielle.
Ce DEP décrit un plan qui, je pense, nous y mènera. C'est une vision en laquelle je crois vraiment et avec laquelle je travaillerai pour faire tout mon possible. En même temps, une analyse minutieuse et un scepticisme sont justifiés; Je vous demande votre critique constructive, ainsi que votre confiance. Django s'appuie sur une communauté de personnes et les applications qu'ils créent, et si nous devons déterminer la voie vers l'avenir, nous devons le faire ensemble.
Brève description
Nous allons ajouter la prise en charge des représentations asynchrones, du middleware, de l'ORM et d'autres éléments importants à Django.
Cela se fera en exécutant du code synchrone dans les threads, en le remplaçant progressivement par du code asynchrone. Les API synchrones continueront d'exister et seront entièrement prises en charge, et avec le temps, elles se transformeront en wrappers synchrones pour le code initialement asynchrone.
Le mode ASGI lancera Django en tant qu'application asynchrone native. Le mode WSGI déclenchera une boucle d'événements distincte à chaque accès à Django, afin que la couche asynchrone soit compatible avec le serveur synchrone.
Le multithreading autour d'ORM est complexe et nécessite un nouveau concept de contextes de connexion et de threads collants pour exécuter du code ORM synchrone.
De nombreuses parties de Django continueront de fonctionner en synchronisation, et notre priorité sera d'aider les utilisateurs à écrire des vues dans les deux styles, leur permettant de choisir le meilleur style pour la présentation sur laquelle ils travaillent.
Certaines fonctions, telles que les modèles et la mise en cache, auront besoin de leurs propres DEP distincts et d'études sur la façon de les rendre complètement asynchrones. Ce DEP se concentre principalement sur le flux de vue du middleware HTTP et ORM.
Il y aura une compatibilité descendante complète. Un projet Django 2.2 standard devrait s'exécuter dans Django asynchrone (que ce soit 3.0 ou 3.1) sans modification.
Cette proposition est axée sur la mise en œuvre de petites pièces itératives avec leur placement progressif dans la branche principale pour éviter les problèmes avec une fourche longue durée et nous permettre de changer de cap à mesure que des problèmes sont découverts.
C'est une bonne occasion d'attirer de nouveaux membres. Nous devons financer le projet pour que cela se fasse plus rapidement. Le financement devrait être à une échelle à laquelle nous ne sommes pas habitués.
Spécification
L'objectif global est de rendre chaque partie de Django, qui peut être bloquante - c'est-à-dire pas seulement les calculs liés au processeur - devenir asynchrone (exécutée dans une boucle d'événement asynchrone sans verrous).
Cela inclut les fonctionnalités suivantes:
- Couches intermédiaires (middleware)
- Vues
- ORM
- Patterns
- Test
- Mise en cache
- Validation de formulaire
- Courriel
Cependant, cela n'inclut pas des éléments tels que l'internationalisation, qui n'apportera aucun gain de performances, car il s'agit d'une tâche liée au processeur qui s'exécute également rapidement, ou de migrations qui sont monothread lors du lancement via la commande de gestion.
Chaque fonction individuelle qui devient asynchrone à l'intérieur fournira également une interface synchrone rétrocompatible avec l'API actuelle (en 2.2) dans un avenir prévisible - nous pourrions la changer au fil du temps pour les améliorer, mais les API synchrones n'iront nulle part.
Un aperçu de la façon dont cela est réalisé techniquement est donné ci-dessous, puis des détails de mise en œuvre spécifiques pour des domaines spécifiques sont donnés. Il n'est pas exhaustif pour toutes les fonctions de Django, mais si nous atteignons cet objectif initial, alors nous inclurons presque tous les cas d'utilisation.
La dernière partie de cette section, «Procédure», explique également comment ces changements peuvent être mis en œuvre progressivement et par plusieurs équipes de développement en parallèle, ce qui est important pour effectuer ces changements avec l'aide de bénévoles dans un délai raisonnable.
Revue technique
Le principe qui nous permet de maintenir des implémentations synchrones et asynchrones en parallèle est la possibilité d'exécuter un style à l'intérieur d'un autre.
Chaque fonction passera par trois étapes de mise en œuvre:
- Uniquement synchrone (nous sommes ici)
- Implémentation synchrone avec wrapper asynchrone
- Implémentation asynchrone avec wrapper synchrone
Wrapper asynchrone
Tout d'abord, le code synchrone existant sera encapsulé dans une interface asynchrone, qui exécute le code synchrone dans le pool de threads. Cela nous permettra de concevoir et de fournir une interface asynchrone relativement rapidement, sans avoir à réécrire tout le code disponible pour l'asynchronie.
La boîte à outils pour cela est déjà disponible dans asgiref en tant que fonction sync_to_async
, qui prend en charge des choses comme la gestion des exceptions ou les threadlocals (plus de détails ci-dessous).
L'exécution de code dans des threads n'entraînera probablement pas une augmentation des performances - la surcharge qui apparaît le ralentira probablement un peu lorsque vous exécutez simplement du code linéaire normal - mais cela permettra aux développeurs de commencer à exécuter quelque chose de manière compétitive et de s'habituer aux nouvelles fonctionnalités.
De plus, plusieurs parties de Django sont sensibles au démarrage dans le même thread lors d'un accès répété; par exemple, traiter des transactions dans la base de données. Si nous encapsulions du code dans atomic()
qui accéderait alors à l'ORM via des threads aléatoires pris dans le pool, la transaction n'aurait aucun effet, car elle était liée à la connexion à l'intérieur du thread dans lequel la transaction a été démarrée.
Dans de telles situations, un «thread collant» est requis dans lequel le contexte asynchrone appelle séquentiellement tout le code synchrone dans le même thread au lieu de le pousser dans le pool de threads tout en conservant le comportement correct d'ORM et d'autres parties sensibles au thread. Toutes les parties de Django que nous soupçonnons en ont besoin, y compris l'ORM entier, utiliseront la version sync_to_async
, qui en tient compte, donc tout est sûr par défaut. Les utilisateurs pourront désactiver cette option de manière sélective pour l'exécution de requêtes compétitives - pour plus de détails, voir "ORM" ci-dessous.
Implémentation asynchrone
L'étape suivante consiste à réécrire l'implémentation de la fonction en code asynchrone, puis à présenter l'interface synchrone via un wrapper qui exécute du code asynchrone dans une boucle d'événement unique. Ceci est déjà disponible dans asgiref en tant que fonction async_to_sync
.
Il n'est pas nécessaire de réécrire toutes les fonctions à la fois pour passer rapidement à la troisième étape. Nous pouvons concentrer nos efforts sur les parties que nous pouvons bien faire et qui ont le support de bibliothèques tierces, tout en aidant le reste de l'écosystème Python dans des choses qui nécessitent plus de travail pour implémenter l'asynchronie native; Ceci est discuté ci-dessous.
Cette présentation générale fonctionne avec presque toutes les fonctions Django qui devraient devenir asynchrones, à l'exception des emplacements pour lesquels Python ne fournit pas d'équivalents de fonction asynchrones que nous utilisons déjà. Le résultat sera soit un changement dans la façon dont Django présente son API en mode asynchrone, soit un travail avec les développeurs principaux de Python pour aider à développer des fonctionnalités asynchrones Python.
Threadlocals
L'un des détails de base de l'implémentation Django qui doit être mentionné séparément de la plupart des fonctions décrites ci-dessous est threadlocals. Comme son nom l'indique, les threadlocals fonctionnent au sein d'un thread, et bien que Django conserve l'objet HttpRequest
dehors de threadlocal, nous y mettons quelques autres choses - par exemple, les connexions à la base de données ou la langue actuelle.
L'utilisation de threadlocals peut être divisée en deux options:
- «Context localals», où une valeur est nécessaire dans un contexte basé sur la pile, comme une demande. Cela est nécessaire pour définir la langue actuelle.
- «True threadlocals», où le code protégé n'est en fait pas sûr pour appeler à partir d'un autre thread. C'est pour se connecter à la base de données.
À première vue, il pourrait sembler que les «contextes locaux» peuvent être résolus en utilisant le nouveau module contextvars en Python, mais Django 3.0 devra toujours prendre en charge Python 3.6, tandis que ce module est apparu en 3.7. De plus, contextvars
spécifiquement conçu pour se débarrasser du contexte lors du passage, par exemple, à un nouveau flux, tandis que nous devons enregistrer ces valeurs pour permettre aux fonctions sync_to_async
et async_to_sync
de fonctionner normalement comme wrappers. Lorsque Django ne prendra en charge que 3.7 et contextvars
ultérieures, nous pourrions envisager d'utiliser des contextvars
, mais cela nécessiterait un travail considérable dans Django.
Cela a déjà été résolu avec asgiref Local
, qui est compatible avec les coroutines et les threads. Maintenant, il n'utilise pas de contextvars
, mais nous pouvons le faire fonctionner avec backport for 3.6 après quelques tests.
De vrais threadlocals, d'autre part, peuvent simplement continuer à fonctionner dans le thread actuel. Cependant, nous devons être plus prudents pour empêcher ces objets de fuir dans un autre flux; lorsque la présentation n'est plus exécutée dans le même thread, mais génère un thread pour chaque appel ORM (pendant la phase «implémentation synchrone, wrapper asynchrone»), certaines choses qui étaient possibles en mode synchrone seront impossibles en asynchrone.
Cela nécessitera une attention particulière et l'interdiction de certaines opérations auparavant possibles en mode asynchrone; Les cas que nous connaissons sont décrits ci-dessous dans des sections spécifiques.
Prise en charge simultanée des interfaces synchrones et asynchrones
L'un des gros problèmes que nous rencontrerons en essayant de porter Django est que Python ne vous permet pas de créer des versions synchrones et asynchrones d'une fonction du même nom.
Cela signifie que vous ne pouvez pas simplement prendre et créer une API qui fonctionne comme ceci:
Il s'agit d'une limitation malheureuse de la façon dont Python est implémenté de manière asynchrone, et il n'y a pas de solution de contournement évidente. Lorsque quelque chose est appelé, vous ne savez pas si vous serez en attente ou non, il n'y a donc aucun moyen de déterminer ce qui doit être retourné.
(Remarque: cela est dû au fait que Python implémente des fonctions asynchrones en tant qu '«appelable synchrone qui renvoie une coroutine», plutôt que quelque chose comme «appeler la méthode __acall__
sur un objet». Les gestionnaires de contexte et itérateurs asynchrones n'ont pas ce problème, car ils ont des méthodes distinctes __aiter__
et __aenter__
.)
Dans cet esprit, nous devons placer les espaces de noms des implémentations synchrones et asynchrones séparément les uns des autres afin qu'ils n'entrent pas en conflit. Nous pourrions le faire avec l'argument nommé sync=True
, mais cela conduit à confondre les corps de fonctions / méthodes et empêche l'utilisation de async def
, et vous permet également d'oublier accidentellement d'écrire cet argument. Un appel aléatoire à une méthode synchrone lorsque vous souhaitez l'appeler de manière asynchrone est dangereux.
La solution proposée pour la plupart des emplacements dans la base de code Django est de fournir un suffixe pour les noms des implémentations asynchrones de fonctions - par exemple, cache.get_async
en plus de cache.get
synchrone. Bien que ce soit une solution laide, elle permet de détecter très facilement les erreurs lors de l'affichage du code (vous devez utiliser l' await
avec la méthode _async
).
Vues et gestion HTTP
Les vues sont probablement la pierre angulaire de l'utilité de l'asynchronie, et nous nous attendons à ce que la plupart des utilisateurs choisissent entre du code asynchrone et synchrone.
Django prendra en charge deux types de vues:
- Représentations synchrones, définies, comme maintenant, par une fonction ou classe synchrone avec
__call__
synchrone - Représentations asynchrones définies par une fonction asynchrone (renvoyant une coroutine) ou une classe avec
__call__
asynchrone.
Ils seront gérés par BaseHandler
, qui vérifiera la vue reçue du résolveur d'URL et l'appellera en conséquence. Le gestionnaire de base devrait être la première partie de Django à devenir asynchrone, et nous devrons modifier le gestionnaire WSGI pour l'appeler dans sa propre boucle d'événements à l'aide de async_to_sync
.
Les couches intermédiaires (middleware) ou les paramètres comme ATOMIC_REQUESTS
, qui ATOMIC_REQUESTS
les vues dans un code non asynchrone (par exemple, le bloc atomic()
), continueront de fonctionner, mais leur vitesse sera affectée (par exemple, l'interdiction d'appels ORM parallèles à l'intérieur de la vue avec atomic()
)
La classe StreamingHttpResponse
existante sera modifiée pour pouvoir accepter un itérateur synchrone ou asynchrone, puis son implémentation interne sera toujours asynchrone. De même pour FileResponse
. Comme il s'agit d'un point d'incompatibilité en arrière potentiel pour le code tiers qui accède directement aux objets Response, nous devons toujours fournir un __iter__
synchrone pour la période de transition.
WSGI continuera d'être pris en charge par Django indéfiniment, mais le gestionnaire WSGI passera à l'exécution de middleware et de vues asynchrones dans sa propre boucle d'événement unique. Cela est susceptible d'entraîner une légère baisse des performances, mais dans les premières expériences, cela n'a pas eu trop d'impact.
Toutes les fonctions HTTP asynchrones fonctionneront à l'intérieur de WSGI, y compris les longues interrogations et les réponses lentes, mais elles seront aussi inefficaces qu'elles le sont actuellement, prenant un thread / processus pour chaque connexion. Les serveurs ASGI seront les seuls à pouvoir prendre en charge efficacement de nombreuses demandes simultanées, ainsi qu'à gérer des protocoles non HTTP, tels que WebSocket, pour une utilisation par des extensions comme Channels .
Couches intermédiaires
Alors que la section précédente était principalement axée sur le chemin de demande / réponse, le middleware a besoin d'une section distincte en raison de la complexité inhérente à leur conception actuelle.
Les middlewares Django sont désormais organisés sous la forme d'une pile dans laquelle chaque middleware obtient get_response
pour exécuter le middleware suivant dans l'ordre (ou la vue du middleware le plus bas de la pile). Cependant, nous devons maintenir un mélange de middleware synchrone et asynchrone pour la compatibilité descendante, et ces deux types ne pourront pas accéder l'un à l'autre nativement.
Ainsi, afin de garantir le fonctionnement du middleware, nous devrons plutôt initialiser chaque middleware avec l'espace réservé get_response, qui renvoie à la place le contrôle au gestionnaire et gère à la fois le transfert de données entre le middleware et la vue, ainsi que le lever d'exception. D'une certaine manière, cela finira par ressembler à un middleware de l'ère Django 1.0 d'un point de vue interne, bien que l'API utilisateur reste bien sûr la même.
Nous pouvons déclarer un middleware synchrone obsolète, mais je recommande de ne pas le faire de sitôt. Si et quand nous arrivons à la fin du cycle de leur obsolescence, nous pourrions alors retourner l'implémentation du middleware à un modèle de pile purement récursif, comme c'est le cas actuellement.
ORM
ORM est la plus grande partie de Django en termes de taille de code et la plus difficile à convertir en asynchrone.
Cela est largement dû au fait que les pilotes de base de données sous-jacents sont synchrones par conception, et les progrès seront lents vers un ensemble de pilotes de base de données matures, standardisés et asynchrones. Au lieu de cela, nous devons concevoir un avenir dans lequel les pilotes de base de données seront initialement synchrones et jeter les bases pour les contributeurs qui développeront davantage les pilotes asynchrones de manière itérative.
Les problèmes avec ORM se répartissent en deux catégories principales: les threads et le blocage implicite.
Streams
Le principal problème avec ORM est que Django est conçu autour d'un seul objet de connections
globales, ce qui vous donne comme par magie la bonne connexion pour votre thread actuel.
Dans un monde asynchrone - où toutes les coroutines fonctionnent dans le même thread - ce n'est pas seulement ennuyeux, mais simplement dangereux. Sans aucune sécurité supplémentaire, un utilisateur accédant à un ORM comme d'habitude risque de casser des objets de connexion en y accédant depuis plusieurs endroits différents.
Heureusement, les objets de connexion sont au moins portables entre les threads, bien qu'ils ne puissent pas être appelés à partir de deux threads en même temps. Django se soucie déjà de la sécurité des threads pour les pilotes de base de données dans le code ORM, et nous avons donc un endroit pour changer son comportement pour qu'il fonctionne correctement.
Nous allons modifier l'objet connections
pour qu'il comprenne à la fois les coroutines et les threads - en réutilisant du code de asgiref.local
, mais avec l'ajout d'une logique supplémentaire. Les connexions seront partagées en code asynchrone et synchrone qui s'appelle - avec le contexte passant par sync_to_async
et async_to_sync
- et le code synchrone sera forcé de s'exécuter séquentiellement dans un thread collant, donc cela ne fonctionnera pas en même temps briser la sécurité des fils.
Cela implique que nous avons besoin d'une solution comme un gestionnaire de contexte pour ouvrir et fermer une connexion à une base de données, comme atomic()
. Cela nous permettra de fournir un appel cohérent et des threads persistants dans ce contexte et permettra aux utilisateurs de créer plusieurs contextes s'ils souhaitent ouvrir plusieurs connexions. Cela nous donne également un moyen potentiel de nous débarrasser des connections
mondiales magiques si nous voulons développer cela davantage.
Pour le moment, Django n'a pas de gestion du cycle de vie des connexions qui soit indépendante des signaux de la classe de gestionnaire, et nous les utiliserons donc pour créer et effacer ces «contextes de connexion». La documentation sera également mise à jour pour clarifier la façon de gérer correctement les connexions en dehors du cycle de demande / réponse; même dans le code actuel, de nombreux utilisateurs ne savent pas qu'une équipe de gestion de longue date doit périodiquement appeler close_old_connections
pour fonctionner correctement.
La compatibilité descendante signifie que nous devons permettre aux utilisateurs d'accéder aux connections
partir de n'importe quel code aléatoire à tout moment, mais nous n'autoriserons cela que pour le code synchrone; nous veillerons à ce que le code soit enveloppé dans un «contexte de connexion», s'il est asynchrone, dès le premier jour.
Il peut sembler intéressant d'ajouter transaction.atomic()
en plus de transaction.atomic()
et d'obliger l'utilisateur à exécuter tout le code à l'intérieur de l'un d'eux, mais cela peut prêter à confusion sur ce qui se passe si vous attachez l'un d'eux est à l'intérieur de l'autre.
Au lieu de cela, je suggère de créer un nouveau gestionnaire de contexte db.new_connections()
qui active ce comportement, et de le faire créer une nouvelle connexion chaque fois qu'il est appelé, et autoriser atomic()
imbrication atomic()
arbitraire à atomic()
intérieur.
Chaque fois que vous new_connections()
bloc new_connections()
, Django configure un nouveau contexte avec de nouvelles connexions à la base de données. Toutes les transactions effectuées en dehors du bloc se poursuivent; tous les appels ORM à l'intérieur du bloc fonctionnent avec une nouvelle connexion à la base de données et verront la base de données de ce point de vue. Si l'isolement des transactions est activé dans la base de données, comme c'est généralement le cas par défaut, cela signifie que les nouvelles connexions à l'intérieur du bloc peuvent ne pas voir les modifications apportées par les transactions non validées à l'extérieur.
De plus, les connexions à l'intérieur de ce bloc new_connections
peuvent elles-mêmes utiliser atomic()
pour déclencher des transactions supplémentaires sur ces nouvelles connexions. Toute imbrication de ces deux gestionnaires de contexte est autorisée, mais chaque fois que new_connections
utilisé, les transactions précédemment ouvertes sont «suspendues» et n'affectent pas les appels ORM jusqu'à ce qu'un nouveau bloc new_connections
soit new_connections
.
Un exemple de l'apparence de cette API:
async def get_authors(pattern):
Ceci est quelque peu détaillé, mais l'objectif est également d'ajouter des raccourcis de haut niveau pour activer ce comportement (et également couvrir la transition de asyncio.ensure_future
en Python 3.6 à asyncio.create_task
en 3.7).
En utilisant ce gestionnaire de contexte et les flux collants dans le même contexte de connexion, nous garantissons que tout le code sera aussi sécurisé que possible par défaut. il est possible que l'utilisateur puisse utiliser la connexion dans un thread pour deux parties différentes de la demande en utilisant yield
, mais cela est yield
possible maintenant.
Verrous implicites
Un autre problème de la conception ORM actuelle est que les opérations de blocage (liées au réseau), en particulier la lecture des champs liés, sont rencontrées dans les instances de modèle.
Si vous prenez une instance de modèle et accédez ensuite à model_instance.related_field
, Django charge de manière transparente le contenu du modèle associé et vous le renvoie. Cependant, cela n'est pas possible dans le code asynchrone - le code de blocage ne doit pas être exécuté dans le thread principal et il n'y a pas d'accès asynchrone aux attributs.
Heureusement, Django a déjà un moyen de s'en sortir - select_related
, qui charge les champs associés à l'avance, et prefetch_related
pour les relations plusieurs-à-plusieurs. Si vous utilisez ORM de manière asynchrone, nous interdirons toute opération de blocage implicite, comme l'accès en arrière-plan aux attributs, et renverrons à la place une erreur indiquant que vous devez d'abord récupérer le champ.
Cela a l'avantage supplémentaire d'empêcher le code lent qui exécute N requêtes dans une boucle for
, ce qui est une erreur courante de nombreux nouveaux programmeurs Django. Cela soulève la barrière d'entrée, mais rappelez-vous que Django asynchrone sera facultatif - les utilisateurs pourront toujours écrire du code synchrone s'ils le souhaitent (et cela sera encouragé dans le tutoriel, car il est beaucoup plus difficile de faire des erreurs dans le code synchrone).
QuerySet
, QuerySet
peut facilement implémenter des générateurs asynchrones et prendre en charge de manière transparente la synchronisation et l'asynchronie:
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)
Autre
Les parties d'ORM associées aux modifications de schéma ne seront pas asynchrones; ils ne doivent être appelés que par les équipes de gestion. Certains projets les appellent déjà dans les soumissions, mais ce n'est pas une bonne idée de toute façon.
Patterns
Les modèles sont maintenant complètement synchrones et le plan est de les laisser de cette façon dans la première étape. , , 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.
Courriel
Django, . send_mail_async
send_mail
, async
- (, mail_admins
).
Django , - SMTP, . , , , , .
Test
, 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.
Procédure
, , . , .
, . , — .
, , . , 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, «» . , , , , .
Alternatives
, , , .
_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.
Copyright
( ) CC0 1.0 Universal .