Andrew Godwin publicó DEP 0009: Django con capacidad asíncrona el 9 de mayo, y fue aprobado por el consejo técnico de Django el 21 de julio, por lo que se espera que con el lanzamiento de Django 3.0 tengan tiempo para hacer algo interesante. Ya se mencionó en algún lugar en los comentarios de Habr , pero decidí transmitir esta noticia a una audiencia más amplia traduciéndola, principalmente para aquellos que, como yo, no siguen particularmente las noticias de Django.
Python asíncrono se ha desarrollado durante muchos años, y en el ecosistema de Django, experimentamos con él en canales con el enfoque principal en el soporte de socket web.
A medida que se desarrolló el ecosistema, se hizo evidente que si bien no había una necesidad urgente de extender Django para admitir protocolos que no son HTTP, como los sockets web, el soporte asíncrono proporcionaría muchos beneficios para el marco tradicional de plantilla de vista de modelo de Django.
Los beneficios se describen en la sección Motivación a continuación, pero la conclusión general a la que llegué es que obtenemos tanto de Django asíncrono que vale la pena el arduo trabajo que requiere. También creo que es muy importante hacer cambios de una manera iterativa, apoyada por la comunidad, que no dependa de uno o dos contribuyentes antiguos que podrían agotarse.
Aunque el documento se designa como DEP "Característica", todo esto significa que también es en parte un DEP de proceso. El alcance de los cambios propuestos a continuación es increíblemente grande, y es probable que falle lanzarlos como un proceso tradicional de una sola característica.
Por supuesto, a lo largo de este documento, es importante recordar la filosofía de Django, que es mantener todo seguro y compatible con versiones anteriores. El plan no es eliminar Django síncrono; el plan es mantenerlo en su forma actual, pero agregar asincronía como una opción para aquellos que piensan que necesitan un rendimiento o flexibilidad adicionales.
¿Es este un trabajo gigantesco? Por supuesto Pero creo que esto puede cambiar significativamente el futuro de Django: tenemos la oportunidad de adoptar un marco probado y una comunidad increíble e introducir un conjunto completamente nuevo de opciones que antes eran imposibles.
La web ha cambiado y Django debería cambiar con ella, pero de acuerdo con nuestros ideales, ser asequible, seguro por defecto y flexible a medida que los proyectos crecen y sus necesidades cambian. En el mundo del almacenamiento de datos en la nube, la arquitectura orientada a servicios y el backend como la base de una lógica empresarial compleja, la capacidad de hacer cosas de manera competitiva es clave.
Este DEP describe un plan que creo que nos llevará allí. Esta es una visión en la que realmente creo y con la que trabajaré para ayudar a hacer todo lo posible. Al mismo tiempo, el análisis cuidadoso y el escepticismo están justificados; Le pido su crítica constructiva, así como su confianza. Django depende de una comunidad de personas y de las aplicaciones que crean, y si necesitamos determinar el camino hacia el futuro, debemos hacerlo juntos.
Breve descripción
Vamos a agregar soporte para representaciones asincrónicas, middleware, ORM y otros elementos importantes para Django.
Esto se realizará ejecutando código síncrono en subprocesos, reemplazándolo gradualmente por código asíncrono. Las API síncronas continuarán existiendo y serán totalmente compatibles, y con el tiempo se convertirán en envoltorios síncronos para el código inicialmente asíncrono.
El modo ASGI lanzará Django como una aplicación asincrónica nativa. El modo WSGI activará un bucle de eventos separado cada vez que se acceda a Django, de modo que la capa asincrónica sea compatible con el servidor síncrono.
El subprocesamiento múltiple alrededor de ORM es complejo y requiere un nuevo concepto de contextos de conexión y subprocesos fijos para ejecutar código ORM síncrono.
Muchas partes de Django continuarán funcionando sincronizadas, y nuestra prioridad será apoyar a los usuarios que escriben vistas en ambos estilos, permitiéndoles elegir el mejor estilo para la presentación en la que están trabajando.
Algunas funciones, como las plantillas y el almacenamiento en caché, necesitarán sus propios DEP y estudios independientes sobre cómo hacerlos completamente asíncronos. Este DEP se centra principalmente en el flujo HTTP-middleware-view y ORM.
Habrá compatibilidad total con versiones anteriores. Un proyecto estándar de Django 2.2 debería ejecutarse en Django asíncrono (ya sea 3.0 o 3.1) sin cambios.
Esta propuesta se centra en la implementación de piezas pequeñas e iterativas con su colocación gradual en la rama maestra para evitar problemas con la bifurcación de larga duración y permitirnos cambiar de rumbo a medida que detectamos problemas.
Esta es una buena oportunidad para atraer nuevos miembros. Debemos financiar el proyecto para que esto suceda más rápido. La financiación debe ser en una escala a la que no estamos acostumbrados.
Especificación
El objetivo general es hacer que cada parte de Django, que puede estar bloqueando, es decir, no solo los cálculos vinculados a la CPU, se vuelva asíncrona (se ejecuta en un bucle de evento asincrónico sin bloqueos).
Esto incluye las siguientes características:
- Capas intermedias (Middleware)
- Vistas
- ORM
- Patrones
- Prueba
- Almacenamiento en caché
- Validación de formulario
- Correo electrónico
Sin embargo, esto no incluye cosas tales como la internacionalización, que no traerá ninguna ganancia de rendimiento, ya que es una tarea vinculada a la CPU que también se ejecuta rápidamente, o migraciones que son de un solo subproceso cuando se inicia a través del comando de administración.
Cada función individual que se vuelve asíncrona en el interior también proporcionará una interfaz síncrona que es compatible con la API actual (en 2.2) en el futuro previsible; podríamos cambiarla con el tiempo para mejorarla, pero las API síncronas no irán a ninguna parte.
A continuación se ofrece una descripción general de cómo se logra esto técnicamente, y luego se brindan detalles específicos de implementación para áreas específicas. No es exhaustivo para todas las funciones de Django, pero si logramos este objetivo inicial, incluiremos casi todos los casos de uso.
La parte final de esta sección, "Procedimiento", también analiza cómo estos cambios pueden implementarse gradualmente y por varios grupos de desarrolladores en paralelo, lo cual es importante para completar estos cambios con la ayuda de voluntarios en un período de tiempo razonable.
Revisión técnica
El principio que nos permite mantener implementaciones sincrónicas y asincrónicas en paralelo es la capacidad de ejecutar un estilo dentro de otro.
Cada función pasará por tres etapas de implementación:
- Solo sincrónico (estamos aquí)
- Implementación síncrona con contenedor asíncrono
- Implementación asincrónica con contenedor síncrono
Contenedor asíncrono
Primero, el código síncrono existente se envolverá en una interfaz asíncrona, que ejecuta el código síncrono en el grupo de subprocesos. Esto nos permitirá diseñar y proporcionar una interfaz asíncrona relativamente rápido, sin tener que reescribir todo el código disponible para la asincronía.
El kit de herramientas para esto ya está disponible en asgiref como una función sync_to_async
, que admite cosas como el manejo de excepciones o sync_to_async
(más sobre esto a continuación).
Lo más probable es que ejecutar código en subprocesos no conduzca a un aumento en la productividad; la sobrecarga que aparece probablemente lo ralentizará un poco cuando solo ejecute código lineal normal, pero esto permitirá a los desarrolladores comenzar a ejecutar algo competitivo y acostumbrarse a las nuevas características.
Además, hay varias partes de Django que son sensibles a comenzar en el mismo hilo en el acceso repetido; por ejemplo, procesar transacciones en la base de datos. Si envolviéramos un código en atomic()
que luego accedería al ORM a través de hilos aleatorios tomados del grupo, la transacción no tendría efecto, ya que estaba vinculada a la conexión dentro del hilo en el que se inició la transacción.
En tales situaciones, se requiere un "hilo fijo" en el que el contexto asincrónico invoca secuencialmente todo el código síncrono en el mismo hilo en lugar de empujarlo al conjunto de hilos, mientras se mantiene el comportamiento correcto de ORM y otras partes sensibles al hilo. Todas las partes de Django que sospechamos que lo necesitan, incluido el ORM completo, utilizarán la versión sync_to_async
, que tiene esto en cuenta, por lo que todo está seguro de forma predeterminada. Los usuarios podrán desactivar esto selectivamente para la ejecución competitiva de consultas. Para más detalles, consulte "ORM" a continuación.
Implementación asincrónica
El siguiente paso es reescribir la implementación de la función en código asíncrono y luego presentar la interfaz síncrona a través de un contenedor que ejecuta código asíncrono en un bucle de evento único. Esto ya está disponible en asgiref como una función async_to_sync
.
No es necesario reescribir todas las funciones a la vez para saltar rápidamente a la tercera etapa. Podemos enfocar nuestros esfuerzos en las partes que podemos hacer bien y que tienen el soporte de bibliotecas de terceros, al tiempo que ayudamos al resto del ecosistema de Python en cosas que requieren más trabajo para implementar la asincronía nativa; Esto se discute a continuación.
Esta descripción general funciona con casi todas las funciones de Django que deberían volverse asíncronas, excepto aquellos lugares para los que Python no proporciona equivalentes de funciones asíncronas que ya utilizamos. El resultado será un cambio en la forma en que Django presenta su API en modo asíncrono, o trabajar con los desarrolladores principales de Python para ayudar a desarrollar las funciones asincrónicas de Python.
Threadlocals
Uno de los detalles básicos de la implementación de Django que debe mencionarse por separado de la mayoría de las funciones que se describen a continuación es threadlocals. Como su nombre indica, los threadlocals funcionan dentro de un thread, y aunque Django mantiene el objeto HttpRequest
fuera de threadlocal, ponemos algunas otras cosas en él, por ejemplo, conexiones de base de datos o el idioma actual.
El uso de hilos locales se puede dividir en dos opciones:
- "Contexto locales", donde se necesita un valor dentro de un contexto basado en la pila, como una solicitud. Esto es necesario para configurar el idioma actual.
- "True threadlocals", donde el código protegido no es seguro para llamar desde otro hilo. Esto es para conectarse a la base de datos.
A primera vista, puede parecer que los "contextos locales" pueden resolverse usando el nuevo módulo contextvars en Python, pero Django 3.0 aún tendrá que soportar Python 3.6, mientras que este módulo apareció en 3.7. Además, contextvars
específicamente diseñado para eliminar el contexto al cambiar, por ejemplo, a un nuevo hilo, mientras que necesitamos guardar estos valores para permitir que las funciones sync_to_async
y async_to_sync
funcionen normalmente como envoltorios. Cuando Django solo admitirá 3.7 y contextvars
posteriores, podríamos considerar el uso de contextvars
, pero eso requeriría un trabajo considerable en Django.
Esto ya se ha resuelto con asgiref Local
, que es compatible con corutinas e hilos. Ahora no usa contextvars
, pero podemos cambiarlo para que funcione con backport 3.6 después de algunas pruebas.
Los hilos de rosca verdaderos, por otro lado, simplemente pueden seguir funcionando en el hilo actual. Sin embargo, debemos ser más cuidadosos para evitar que tales objetos se filtren a otra corriente; cuando la presentación ya no se ejecuta en el mismo subproceso, pero genera un subproceso para cada llamada ORM (durante la fase de "implementación sincrónica, envoltura asincrónica"), algunas cosas que fueron posibles en modo sincrónico no serán posibles en asincrónico.
Esto requerirá atención especial y la prohibición de algunas operaciones previamente posibles en modo asíncrono; Los casos que conocemos se describen a continuación en secciones específicas.
Soporte simultáneo para interfaces síncronas y asíncronas.
Uno de los grandes problemas que encontraremos cuando intentemos portar Django es que Python no le permite crear versiones síncronas y asíncronas de una función con el mismo nombre.
Esto significa que no puede simplemente tomar y crear una API que funcione de esta manera:
Esta es una limitación desafortunada de la forma en que Python se implementa de forma asíncrona, y no hay una solución obvia. Cuando se llama a algo, no sabe si lo esperará o no, por lo que no hay forma de determinar qué debe devolverse.
(Nota: esto se debe a que Python implementa funciones asíncronas como "un llamado sincronizado que devuelve una rutina", en lugar de algo como "llamar al método __acall__
en un objeto". Los administradores e iteradores de contexto asíncrono no tienen este problema, porque tienen métodos separados __aiter__
y __aenter__
.)
Con esto en mente, debemos ubicar los espacios de nombres de las implementaciones síncronas y asíncronas por separado para que no entren en conflicto. Podríamos hacer esto con el argumento nombrado sync=True
, pero esto conduce a cuerpos confusos de funciones / métodos y evita el uso de async def
, y también le permite olvidarse accidentalmente de escribir este argumento. Una llamada aleatoria a un método sincrónico cuando desea llamarlo de forma asincrónica es peligroso.
La solución propuesta para la mayoría de los lugares en la base de código de Django es proporcionar un sufijo para los nombres de implementaciones asíncronas de funciones, por ejemplo, cache.get_async
además de cache.get
síncrono. Aunque esta es una solución fea, hace que sea muy fácil detectar errores al ver el código (debe usar _async
con el método _async
).
Vistas y manejo de HTTP
Las vistas son probablemente la piedra angular de la utilidad de la asincronía, y esperamos que la mayoría de los usuarios elijan entre código asincrónico y sincrónico.
Django admitirá dos tipos de vistas:
- Representaciones síncronas, definidas, como ahora, por una función o clase síncrona con
__call__
síncrona - Representaciones asincrónicas definidas por una función asincrónica (que devuelve una rutina) o una clase con
__call__
asincrónica.
Serán manejados por BaseHandler
, que verificará la vista recibida del BaseHandler
de URL y la llamará en consecuencia. El controlador base debería ser la primera parte de Django que se vuelva asíncrono, y tendremos que modificar el controlador WSGI para llamarlo en su propio bucle de eventos usando async_to_sync
.
Las capas intermedias (middleware) o configuraciones como ATOMIC_REQUESTS
, que envuelven las vistas en un código seguro no asíncrono (por ejemplo, el bloque atomic()
), continuarán funcionando, pero su velocidad se verá afectada (por ejemplo, la prohibición de llamadas ORM paralelas dentro de la vista con atomic()
)
La clase StreamingHttpResponse
existente se modificará para poder aceptar un iterador síncrono o asíncrono, y luego su implementación interna siempre será asíncrona. De manera similar para FileResponse
. Dado que este es un posible punto de incompatibilidad hacia atrás para el código de terceros que accede directamente a los objetos de respuesta, aún debemos proporcionar un __iter__
síncrono para el período de transición.
WSGI continuará siendo respaldado por Django indefinidamente, pero el controlador WSGI continuará ejecutando middleware asincrónico y vistas en su propio bucle de eventos de una sola vez. Es probable que esto conduzca a una ligera disminución en el rendimiento, pero en los experimentos iniciales no tuvo demasiado impacto.
Todas las funciones HTTP asíncronas funcionarán dentro de WSGI, incluidas las respuestas de sondeo largo y lento, pero serán tan ineficientes como lo son ahora, ocupando un hilo / proceso para cada conexión. Los servidores ASGI serán los únicos que pueden soportar de manera eficiente muchas solicitudes concurrentes, así como también manejar protocolos no HTTP, como WebSocket, para su uso por extensiones como Canales .
Capas intermedias
Mientras que la sección anterior se centró principalmente en la ruta de solicitud / respuesta, el middleware necesita una sección separada debido a la complejidad inherente a su diseño actual.
Los middlewares de Django ahora se organizan en forma de una pila en la que cada middleware obtiene get_response
para ejecutar el siguiente en orden middleware (o la vista del middleware más bajo en la pila). Sin embargo, necesitamos mantener una mezcla de middleware síncrono y asíncrono para la compatibilidad con versiones anteriores, y estos dos tipos no podrán acceder entre sí de forma nativa.
Por lo tanto, para garantizar que el middleware funcione, tendremos que inicializar cada middleware con el marcador de posición get_response, que en su lugar devuelve el control al controlador y maneja tanto la transferencia de datos entre el middleware y la vista, como un lanzamiento de excepción. En cierto modo, eventualmente se verá como un middleware de la era Django 1.0 desde un punto de vista interno, aunque, por supuesto, la API del usuario seguirá siendo la misma.
Podemos declarar obsoleto el middleware síncrono, pero recomiendo no hacerlo pronto. Si llegamos al final del ciclo de su obsolescencia, podríamos devolver la implementación del middleware a un modelo de pila puramente recursivo, como lo es ahora.
ORM
ORM es la parte más grande de Django en términos de tamaño de código y la más difícil de convertir a asíncrona.
Esto se debe en gran parte al hecho de que los controladores de base de datos subyacentes son síncronos por diseño, y el progreso será lento hacia un conjunto de controladores de bases de datos asíncronos maduros, estandarizados. En cambio, debemos diseñar un futuro en el que los controladores de la base de datos sean inicialmente sincrónicos, y sentar las bases para los contribuyentes que desarrollarán aún más los controladores asincrónicos de forma iterativa.
Los problemas con ORM se dividen en dos categorías principales: subprocesos y bloqueo implícito.
Corrientes
El principal problema con ORM es que Django está diseñado alrededor de un único objeto de connections
globales, que mágicamente le brinda la conexión correcta para su hilo actual.
En un mundo asincrónico, donde todas las rutinas funcionan en el mismo hilo, esto no solo es molesto, sino simplemente peligroso. Sin ninguna seguridad adicional, un usuario que accede a un ORM como de costumbre corre el riesgo de romper los objetos de conexión al acceder desde varios lugares diferentes.
Afortunadamente, los objetos de conexión son al menos portátiles entre subprocesos, aunque no se pueden llamar desde dos subprocesos al mismo tiempo. Django ya se preocupa por la seguridad de subprocesos para los controladores de base de datos en el código ORM, por lo que tenemos un lugar para cambiar su comportamiento para que funcione correctamente.
Modificaremos el objeto de connections
para que comprenda tanto las rutinas como los subprocesos, reutilizando parte del código de asgiref.local
, pero con la adición de lógica adicional. Las conexiones se compartirán en un código asíncrono y sincrónico que se llama entre sí, con el contexto pasando por sync_to_async
y async_to_sync
, y el código sincrónico se verá obligado a ejecutarse secuencialmente en un hilo fijo, por lo que esto no funcionará Al mismo tiempo, romper la seguridad del hilo.
Esto implica que necesitamos una solución como un administrador de contexto para abrir y cerrar una conexión de base de datos, como atomic()
. Esto nos permitirá proporcionar una llamada consistente e hilos fijos en este contexto y permitirá a los usuarios crear múltiples contextos si desean abrir múltiples conexiones. También nos brinda una forma potencial de deshacernos de las connections
globales mágicas si queremos desarrollar esto más.
Por el momento, Django no tiene una gestión del ciclo de vida de la conexión que sea independiente de las señales de la clase de controlador, y por lo tanto las usaremos para crear y borrar estos "contextos de conexión". La documentación también se actualizará para aclarar cómo manejar adecuadamente las conexiones fuera del ciclo de solicitud / respuesta; Incluso en el código actual, muchos usuarios no saben que ningún equipo de gestión de larga data debe llamar periódicamente a close_old_connections
para que funcione correctamente.
La compatibilidad con versiones anteriores significa que debemos permitir a los usuarios acceder a las connections
desde cualquier código aleatorio en cualquier momento, pero solo permitiremos esto para el código síncrono; nos aseguraremos de que el código esté envuelto en un "contexto de conexión", si es asíncrono, desde el primer día.
Puede parecer que sería bueno agregar transaction.atomic()
además de transaction.atomic()
y requerir que el usuario ejecute todo el código dentro de uno de ellos, pero esto puede generar confusión sobre lo que sucede si adjunta uno de ellos está dentro del otro.
En cambio, sugiero crear un nuevo administrador de contexto db.new_connections()
que habilite este comportamiento, y haga que cree una nueva conexión cada vez que se llame, y permita atomic()
anidación arbitraria atomic()
dentro de él.
Cada vez que new_connections()
bloque new_connections()
, Django establece un nuevo contexto con nuevas conexiones de base de datos. Todas las transacciones que se realizaron fuera del bloque continúan; cualquier llamada ORM dentro del bloque funciona con una nueva conexión a la base de datos y verá la base de datos desde este punto de vista. Si el aislamiento de transacciones está habilitado en la base de datos, como generalmente se hace de manera predeterminada, esto significa que las nuevas conexiones dentro del bloque pueden no ver los cambios realizados por cualquier transacción no confirmada fuera de él.
Además, las conexiones dentro de este bloque new_connections
pueden usar atomic()
para desencadenar transacciones adicionales en estas nuevas conexiones. Se permite cualquier anidamiento de estos dos administradores de contexto, pero cada vez que new_connections
utiliza new_connections
, las transacciones abiertas anteriormente se "suspenden" y no afectan las llamadas ORM hasta que se new_connections
un nuevo bloque new_connections
.
Un ejemplo de cómo podría verse esta API:
async def get_authors(pattern):
Esto es algo detallado, pero el objetivo también es agregar accesos directos de alto nivel para habilitar este comportamiento (y también cubrir la transición de asyncio.ensure_future
en Python 3.6 a asyncio.create_task
en 3.7).
Al usar este administrador de contexto y las transmisiones fijas dentro del mismo contexto de conexión, garantizamos que todo el código será lo más seguro posible por defecto. existe la posibilidad de que el usuario pueda usar la conexión en un hilo para dos partes diferentes de la solicitud usando el yield
, pero esto yield
es posible ahora.
Cerraduras implícitas
Otro problema del diseño ORM actual es que las operaciones de bloqueo (relacionadas con la red), en particular los campos relacionados con la lectura, se encuentran en las instancias del modelo.
Si toma una instancia de modelo y luego accede a model_instance.related_field
, Django cargará de forma transparente el contenido del modelo asociado y se lo devolverá. Sin embargo, esto no es posible en el código asincrónico: el código de bloqueo no debe ejecutarse en el hilo principal y no hay acceso asincrónico a los atributos.
Afortunadamente, Django ya tiene una manera de salir de esto: select_related
, que carga los campos relacionados por adelantado, y prefetch_related
para relaciones de muchos a muchos. Si usa ORM de forma asincrónica, prohibiremos cualquier operación de bloqueo implícito, como el acceso en segundo plano a los atributos, y en su lugar, devolverá un error que indica que primero debe recuperar el campo.
Esto tiene el beneficio adicional de evitar el código lento que ejecuta N solicitudes en un bucle for
, que es un error común de muchos programadores nuevos de Django. Esto aumenta la barrera de entrada, pero recuerde que Django asíncrono será opcional: los usuarios aún podrán escribir código sincrónico si lo desean (y esto se fomentará en el tutorial, ya que el código sincrónico es mucho más difícil de cometer errores).
QuerySet
, afortunadamente, puede implementar fácilmente generadores asíncronos y admitir de forma transparente tanto la sincronización como la asincronía:
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)
Otros
Las partes de ORM asociadas con cambios de esquema no serán asincrónicas; deben ser llamados solo de los equipos gerenciales. Algunos proyectos ya los llaman en presentaciones, pero de todos modos esta no es una buena idea.
Patrones
Las plantillas ahora son completamente sincrónicas, y el plan es dejarlas así en el primer paso. , , 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.
Correo electrónico
Django, . send_mail_async
send_mail
, async
- (, mail_admins
).
Django , - SMTP, . , , , , .
Prueba
, 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, , , .
:
( 3.0)
( 3.1)
- ORM ( )
- ( )
- Correo electrónico
; , . , , , , .
, - ; , , . , , 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, «» . , , , , .
Alternativas
, , , .
_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 .