O Django 3.0 será assíncrono

Andrew Godwin publicou o DEP 0009: Django compatível com Async em 9 de maio e foi aprovado pelo conselho técnico do Django em 21 de julho. Portanto, espera-se que, com o lançamento do Django 3.0, eles tenham tempo para fazer algo interessante. Já foi mencionado em algum lugar nos comentários de Habr , mas decidi transmitir essas notícias para um público mais amplo traduzindo-as - principalmente para aqueles que, como eu, não seguem particularmente as notícias do Django.



O Python assíncrono foi desenvolvido por muitos anos e, no ecossistema do Django, nós o experimentamos em canais com o foco principal no suporte a soquetes da web.


À medida que o ecossistema se desenvolveu, tornou-se evidente que, embora não houvesse necessidade urgente de estender o Django para suportar protocolos não HTTP, como soquetes da Web, o suporte assíncrono traria muitos benefícios para a estrutura tradicional de modelo de exibição de modelo do Django.


Os benefícios são descritos na seção Motivação abaixo, mas a conclusão geral a que cheguei é que obtemos tanto do Django assíncrono que vale a pena o trabalho duro que é necessário. Também acredito que é muito importante fazer alterações de maneira iterativa e apoiada pela comunidade, que não dependerá de um ou dois colaboradores antigos que possam se queimar.


Embora o documento seja designado como DEP "Recurso", tudo isso significa que ele também é parcialmente um DEP de processo. O escopo das alterações abaixo é incrivelmente grande, e é provável que o lançamento delas como um processo tradicional de recurso único falhe.


Obviamente, ao longo deste documento, é importante lembrar a filosofia do Django, que é manter tudo seguro e compatível com versões anteriores. O plano não é remover o Django síncrono - o plano é mantê-lo em sua forma atual, mas adicionar assincronia como uma opção para aqueles que pensam que precisam de desempenho ou flexibilidade extra.


Este é um trabalho gigantesco? Claro. Mas acho que isso nos permite mudar significativamente o futuro do Django - temos a oportunidade de adotar uma estrutura comprovada e uma comunidade incrível, e introduzir um conjunto completamente novo de opções que antes eram impossíveis.


A web mudou, e o Django deve mudar com ela, mas de acordo com nossos ideais, seja acessível, seguro por padrão e flexível à medida que os projetos crescem e suas necessidades mudam. No mundo do data warehousing em nuvem, arquitetura orientada a serviços e back-end como base da lógica de negócios complexa, a capacidade de fazer as coisas de forma competitiva é essencial.


Esta DEP esboça um plano que acho que nos levará até lá. Esta é uma visão em que realmente acredito e com a qual trabalharei para ajudar a fazer todo o possível. Ao mesmo tempo, justifica-se uma análise cuidadosa e ceticismo; Peço suas críticas construtivas e sua confiança. O Django conta com uma comunidade de pessoas e com os aplicativos que eles criam, e se precisarmos determinar o caminho para o futuro, devemos fazê-lo juntos.


Breve descrição


Nós vamos adicionar suporte para representações assíncronas, middleware, ORM e outros elementos importantes no Django.


Isso será feito executando o código síncrono nos encadeamentos, substituindo-o gradualmente pelo código assíncrono. APIs síncronas continuarão a existir e serão totalmente suportadas e, com o tempo, se transformarão em invólucros síncronos para código inicialmente assíncrono.


O modo ASGI iniciará o Django como um aplicativo assíncrono nativo. O modo WSGI acionará um loop de eventos separado toda vez que o Django for acessado, para que a camada assíncrona seja compatível com o servidor síncrono.


A multithreading em torno dos ORMs é complexa e requer um novo conceito de contextos de conexão e threads persistentes para executar o código ORM síncrono.


Muitas partes do Django continuarão trabalhando em sincronia, e nossa prioridade será oferecer suporte aos usuários que escrevem visualizações nos dois estilos, permitindo que eles escolham o melhor estilo para a apresentação em que estão trabalhando.


Algumas funções, como modelos e armazenamento em cache, precisarão de DEPs e estudos separados sobre como torná-los completamente assíncronos. Essa DEP se concentra principalmente no fluxo de exibição de HTTP-middleware e no ORM.


Haverá compatibilidade com versões anteriores. Um projeto Django 2.2 padrão deve ser executado no Django assíncrono (seja 3.0 ou 3.1) sem alterações.


Esta proposta está focada na implementação de pequenas partes iterativas com sua colocação gradual no ramo mestre para evitar problemas com uma bifurcação de longa duração e nos permite mudar de rumo à medida que os problemas são descobertos.


Esta é uma boa oportunidade para atrair novos membros. Precisamos financiar o projeto para que isso aconteça mais rapidamente. O financiamento deve estar em uma escala à qual não estamos acostumados.


Especificação


O objetivo geral é fazer com que todas as partes do Django, que podem estar bloqueando - isto é, não apenas os cálculos ligados à CPU - se tornem assíncronas (executadas em um loop de eventos assíncrono sem bloqueios).


Isso inclui os seguintes recursos:


  • Camadas intermediárias (Middleware)
  • Visualizações
  • ORM
  • Padrões
  • Teste
  • Armazenamento em cache
  • Validação de formulário
  • E-mail

No entanto, isso não inclui coisas como internacionalização, que não trará nenhum ganho de desempenho, pois é uma tarefa vinculada à CPU que também é executada rapidamente ou migrações que são segmentadas por um único elemento quando iniciadas pelo comando de gerenciamento.


Cada função individual que se torna assíncrona por dentro também fornecerá uma interface síncrona compatível com a API atual (em 2.2) no futuro próximo - poderíamos alterá-la com o tempo para melhorá-las, mas as APIs síncronas não vão a lugar algum.


Uma visão geral de como isso é tecnicamente alcançado é apresentada abaixo e, em seguida, são fornecidos detalhes de implementação específicos para áreas específicas. Não é exaustivo para todas as funções do Django, mas se atingirmos esse objetivo inicial, incluiremos quase todos os casos de uso.


A parte final desta seção, “Procedimento”, também discute como essas mudanças podem ser implementadas gradualmente e por várias equipes de desenvolvimento em paralelo, o que é importante para concluir essas alterações com a ajuda de voluntários em um período de tempo razoável.


Revisão técnica


O princípio que nos permite manter implementações síncronas e assíncronas em paralelo é a capacidade de executar um estilo dentro de outro.


Cada função passará por três estágios de implementação:


  • Somente síncrona (estamos aqui)
  • Implementação síncrona com wrapper assíncrono
  • Implementação assíncrona com wrapper síncrono

Wrapper assíncrono


Primeiro, o código síncrono existente será agrupado em uma interface assíncrona, que executa o código síncrono no pool de threads. Isso nos permitirá projetar e fornecer uma interface assíncrona de forma relativamente rápida, sem precisar reescrever todo o código disponível para assincronia.


O kit de ferramentas para isso já está disponível no asgiref como uma função sync_to_async , que suporta coisas como tratamento de exceções ou sync_to_async (mais sobre isso abaixo).


A execução de código em encadeamentos provavelmente não levará a um aumento de produtividade - a sobrecarga exibida provavelmente diminuirá um pouco quando você executar o código linear normal - mas isso permitirá que os desenvolvedores comecem a executar algo competitivamente e se acostumem a novos recursos.


Além disso, existem várias partes do Django que são sensíveis a iniciar no mesmo thread com acesso repetido; por exemplo, processando transações no banco de dados. Se empacotássemos algum código em atomic() que acessaria o ORM por meio de encadeamentos aleatórios retirados do pool, a transação não teria efeito, pois estava vinculada à conexão dentro do encadeamento no qual a transação foi iniciada.


Nessas situações, é necessário um "encadeamento", no qual o contexto assíncrono chama seqüencialmente todo o código síncrono no mesmo encadeamento, em vez de empurrá-lo para o conjunto de encadeamentos, mantendo o comportamento correto do ORM e de outras partes sensíveis ao encadeamento. Todas as partes do Django que suspeitamos que sejam necessárias, incluindo todo o ORM, usarão a versão sync_to_async , que leva isso em conta, para que tudo esteja seguro por padrão. Os usuários poderão desativá-lo seletivamente para execução competitiva de consultas - para obter mais detalhes, consulte "ORM" abaixo.


Implementação assíncrona


A próxima etapa é reescrever a implementação da função em código assíncrono e, em seguida, apresentar a interface síncrona por meio de um wrapper que executa o código assíncrono em um loop de evento único. Isso já está disponível no asgiref como uma função async_to_sync .


Não é necessário reescrever todas as funções de uma só vez para pular rapidamente para o terceiro estágio. Podemos concentrar nossos esforços nas partes que podemos fazer bem e que têm o apoio de bibliotecas de terceiros, enquanto ajudamos o restante do ecossistema Python em coisas que exigem mais trabalho para implementar a assincronia nativa; Isso é discutido abaixo.


Esta visão geral funciona com quase todas as funções do Django que devem se tornar assíncronas, exceto naqueles lugares para os quais o Python não fornece equivalentes de funções assíncronas que já usamos. O resultado será uma mudança na forma como o Django apresenta sua API no modo assíncrono ou trabalhando com os desenvolvedores principais do Python para ajudar a desenvolver recursos assíncronos do Python.


Threadlocals


Um dos detalhes básicos da implementação do Django que precisa ser mencionado separadamente da maioria das funções descritas abaixo é o threadlocals. Como o nome sugere, threadlocals funcionam em um thread e, embora o Django mantenha o objeto HttpRequest fora do threadlocal, colocamos algumas outras coisas nele - por exemplo, conexões com o banco de dados ou o idioma atual.


O uso de threadlocals pode ser dividido em duas opções:


  • “Locais de contexto”, onde um valor é necessário em algum contexto baseado em pilha, como uma solicitação. Isso é necessário para definir o idioma atual.
  • "Threadlocals verdadeiros", onde o código protegido é realmente inseguro para chamadas de outro thread. Isso é para conectar-se ao banco de dados.

À primeira vista, pode parecer que “locais de contexto” podem ser resolvidos usando o novo módulo contextvars no Python, mas o Django 3.0 ainda precisará suportar o Python 3.6, enquanto este módulo apareceu na versão 3.7. Além disso, o contextvars projetado especificamente para se livrar do contexto ao alternar, por exemplo, para um novo encadeamento, enquanto precisamos salvar esses valores para permitir que as funções async_to_sync e async_to_sync funcionem normalmente como wrappers. Quando o Django suporta apenas 3.7 e posterior, podemos considerar o uso de contextvars , mas isso exigiria um trabalho considerável no Django.


Isso já foi resolvido com o asgiref Local , que é compatível com corotinas e threads. Agora ele não usa contextvars , mas podemos contextvars lo para trabalhar com o backport for 3.6 após alguns testes.


Os threadlocals verdadeiros, por outro lado, podem simplesmente continuar trabalhando no thread atual. No entanto, devemos ter mais cuidado para impedir que esses objetos vazem para outro fluxo; quando a apresentação não é mais executada no mesmo encadeamento, mas gera um encadeamento para cada chamada ORM (durante a fase "implementação síncrona, wrapper assíncrona"), algumas coisas possíveis no modo síncrono não serão possíveis no modo assíncrono.


Isso exigirá atenção especial e a proibição de algumas operações anteriormente possíveis no modo assíncrono; Os casos que conhecemos são descritos abaixo em seções específicas.


Suporte simultâneo para interfaces síncronas e assíncronas


Um dos grandes problemas que encontraremos ao tentar portar o Django é que o Python não permite criar versões síncronas e assíncronas de uma função com o mesmo nome.


Isso significa que você não pode simplesmente pegar e criar uma API que funcione dessa maneira:


 #   value = cache.get("foo") #   value = await cache.get("bar") 

Essa é uma limitação infeliz da maneira como o Python é implementado de forma assíncrona, e não há solução óbvia. Quando algo é chamado, você não sabe se estará aguardando ou não; portanto, não há como determinar o que precisa ser devolvido.


(Nota: isso ocorre porque o Python implementa funções assíncronas como "uma chamada síncrona que retorna uma corotina", em vez de algo como "chamar o método __acall__ em um objeto". Os gerenciadores e iteradores de contexto assíncronos não têm esse problema, porque eles têm métodos separados __aiter__ e __aenter__ .)


Com isso em mente, devemos colocar os namespaces das implementações síncronas e assíncronas separadamente um do outro para que não entrem em conflito. Poderíamos fazer isso com o argumento nomeado sync=True , mas isso leva a corpos confusos de funções / métodos e evita o uso de async def , além de permitir que você acidentalmente esqueça de escrever esse argumento. Uma chamada aleatória para um método síncrono quando você deseja chamá-lo de forma assíncrona é perigosa.


A solução proposta para a maioria dos locais na base de código do Django é fornecer um sufixo para nomes de implementações assíncronas de funções - por exemplo, cache.get_async além de cache.get síncrono. Embora essa seja uma solução feia, facilita muito a detecção de erros ao exibir o código (você deve usar _async com o método _async ).


Exibições e manipulação de HTTP


As visualizações são provavelmente a pedra angular da utilidade da assincronia, e esperamos que a maioria dos usuários escolha entre código assíncrono e síncrono.


O Django suportará dois tipos de visualizações:


  • Representações síncronas, definidas como agora, por uma função ou classe síncrona com __call__ síncrona
  • Representações assíncronas definidas por uma função assíncrona (retornando uma corotina) ou uma classe com __call__ assíncrona.

Eles serão tratados pelo BaseHandler , que verificará a exibição recebida do resolvedor de URL e a chamará de acordo. O manipulador base deve ser a primeira parte do Django a se tornar assíncrono, e precisaremos modificar o manipulador WSGI para chamá-lo em seu próprio loop de eventos usando async_to_sync .


Camadas intermediárias (middleware) ou configurações como ATOMIC_REQUESTS , que ATOMIC_REQUESTS as visualizações em código não assíncrono (por exemplo, o bloco atomic() ), continuarão funcionando, mas sua velocidade será afetada (por exemplo, a proibição de chamadas ORM paralelas dentro da visualização com atomic() )


A classe StreamingHttpResponse existente será modificada para poder aceitar um iterador síncrono ou assíncrono e, em seguida, sua implementação interna será sempre assíncrona. Da mesma forma para FileResponse . Como esse é um possível ponto de incompatibilidade com versões anteriores do código de terceiros que acessa diretamente os objetos Response, ainda precisamos fornecer um __iter__ síncrono para o período de transição.


O WSGI continuará a ser suportado pelo Django indefinidamente, mas o manipulador do WSGI passará a executar middleware assíncrono e visualizações em seu próprio loop de eventos únicos. É provável que isso leve a uma ligeira diminuição no desempenho, mas nas experiências iniciais não teve muito impacto.


Todas as funções HTTP assíncronas funcionarão dentro do WSGI, incluindo pesquisas longas e respostas lentas, mas serão tão ineficientes quanto são agora, ocupando um encadeamento / processo para cada conexão. Os servidores ASGI serão os únicos que podem suportar com eficiência muitas solicitações simultâneas, além de lidar com protocolos não HTTP, como o WebSocket, para uso por extensões como canais .


Camadas intermediárias


Enquanto a seção anterior se concentrou principalmente no caminho de solicitação / resposta, o middleware precisa de uma seção separada devido à complexidade inerente ao seu design atual.


Os middlewares do Django agora são organizados na forma de uma pilha na qual cada middleware recebe get_response para executar o próximo na ordem do middleware (ou a exibição do middleware mais baixo na pilha). No entanto, precisamos manter uma mistura de middleware síncrono e assíncrono para compatibilidade com versões anteriores, e esses dois tipos não poderão acessar um ao outro nativamente.


Portanto, para garantir que o middleware funcione, teremos que inicializar cada middleware com o espaço reservado get_response, que retorna o controle de volta ao manipulador e lida com a transferência de dados entre o middleware e a visualização e o lançamento da exceção. De certa forma, eventualmente parecerá o middleware da era do Django 1.0 de um ponto de vista interno, embora, obviamente, a API do usuário permaneça a mesma.


Podemos declarar obsoleto o middleware síncrono, mas recomendo não fazer isso tão cedo. Se e quando chegarmos ao final do ciclo de obsolescência, poderemos retornar a implementação do middleware para um modelo de pilha puramente recursivo, como é agora.


ORM


ORM é a maior parte do Django em termos de tamanho de código e a mais difícil de converter para assíncrona.


Isso se deve principalmente ao fato de os drivers de banco de dados subjacentes serem síncronos por design e o progresso ser lento em relação a um conjunto de drivers de banco de dados assíncronos, padronizados e maduros. Em vez disso, devemos projetar um futuro no qual os drivers de banco de dados sejam inicialmente síncronos e estabelecer as bases para os contribuidores que continuarão a desenvolver drivers assíncronos de maneira iterativa.


Os problemas com o ORM se enquadram em duas categorias principais - threads e bloqueio implícito.


Streams


O principal problema com o ORM é que o Django é projetado em torno de um único objeto de connections globais, o que, magicamente, fornece a conexão correta para o encadeamento atual.


Em um mundo assíncrono - onde todas as corotinas trabalham no mesmo segmento - isso não é apenas irritante, mas simplesmente perigoso. Sem nenhuma segurança adicional, um usuário que acessa um ORM como de costume arrisca quebrar objetos de conexão acessando-o de vários locais diferentes.


Felizmente, os objetos de conexão são pelo menos portáteis entre os threads, embora não possam ser chamados de dois threads ao mesmo tempo. O Django já se preocupa com a segurança de threads para drivers de banco de dados no código ORM, e por isso temos um lugar para alterar seu comportamento para funcionar corretamente.


Modificaremos o objeto de connections para que ele entenda as corotinas e os threads - reutilizando algum código do asgiref.local , mas com a adição de lógica adicional. As conexões serão compartilhadas em código assíncrono e síncrono que se chama - com o contexto passando por sync_to_async e async_to_sync - e o código síncrono será forçado a executar sequencialmente em um thread async_to_sync , portanto, isso não funcionará quebrando ao mesmo tempo a segurança da linha.


Isso implica que precisamos de uma solução como um gerenciador de contexto para abrir e fechar uma conexão com o banco de dados, como atomic() . Isso nos permitirá fornecer uma chamada consistente e threads persistentes nesse contexto e permitir que os usuários criem múltiplos contextos se desejarem abrir várias conexões. Também nos oferece uma maneira potencial de nos livrarmos de connections mágicas globais, se quisermos desenvolver isso ainda mais.


No momento, o Django não possui gerenciamento do ciclo de vida da conexão que seja independente dos sinais da classe manipuladora e, portanto, os usaremos para criar e limpar esses "contextos de conexão". A documentação também será atualizada para tornar mais claro como lidar adequadamente com conexões fora do ciclo de solicitação / resposta; mesmo no código atual, muitos usuários não sabem que qualquer equipe de gerenciamento de longa duração deve chamar periodicamente close_old_connections para funcionar corretamente.


Compatibilidade com versões anteriores significa que devemos permitir que os usuários acessem connections de qualquer código aleatório a qualquer momento, mas só permitiremos isso para código síncrono; garantiremos que o código seja agrupado em um "contexto de conexão", se for assíncrono, desde o primeiro dia.


Pode parecer bom adicionar transaction.atomic() além de transaction.atomic() e exigir que o usuário execute todo o código em um deles, mas isso pode causar confusão sobre o que acontece se você anexar um deles está dentro do outro.


Em vez disso, sugiro a criação de um novo gerenciador de contexto db.new_connections() que habilite esse comportamento e faça com que ele crie uma nova conexão sempre que for chamada, e permita aninhamento atomic() arbitrário atomic() dentro dele.


Cada vez que você new_connections() bloco new_connections() , o Django configura um novo contexto com novas conexões com o banco de dados. Todas as transações realizadas fora do bloco continuam; qualquer chamada ORM dentro do bloco funciona com uma nova conexão com o banco de dados e o verá desse ponto de vista. Se o isolamento da transação estiver ativado no banco de dados, como normalmente é feito por padrão, isso significa que novas conexões dentro do bloco podem não ver as alterações feitas por quaisquer transações não confirmadas fora dele.


Além disso, as conexões dentro desse bloco new_connections podem usar atomic() para acionar transações adicionais nessas novas conexões. Qualquer aninhamento desses dois gerenciadores de contexto é permitido, mas cada vez que new_connections usadas, transações abertas anteriormente são "suspensas" e não afetam as chamadas ORM até que um novo bloco de novas new_connections seja new_connections .


Um exemplo de como essa API pode parecer:


 async def get_authors(pattern): # Create a new context to call concurrently async with db.new_connections(): return [ author.name async for author in Authors.objects.filter(name__icontains=pattern) ] async def get_books(pattern): # Create a new context to call concurrently async with db.new_connections(): return [ book.title async for book in Book.objects.filter(name__icontains=pattern) ] async def my_view(request): # Query authors and books concurrently task_authors = asyncio.create_task(get_authors("an")) task_books = asyncio.create_task(get_books("di")) return render( request, "template.html", { "books": await task_books, "authors": await task_authors, }, ) 

Isso é um pouco detalhado, mas o objetivo também é adicionar atalhos de alto nível para ativar esse comportamento (e também cobrir a transição de asyncio.ensure_future no Python 3.6 para asyncio.create_task na 3.7).


Usando esse gerenciador de contexto e fluxos persistentes no mesmo contexto de conexão, garantimos que todo o código será o mais seguro possível por padrão. existe a possibilidade de o usuário poder usar a conexão em um encadeamento para duas partes diferentes da solicitação usando yield , mas isso yield é possível agora.


Bloqueios implícitos


Outro problema do design atual do ORM é que operações de bloqueio (relacionadas à rede), em particular campos relacionados à leitura, são encontradas nas instâncias do modelo.


Se você pegar uma instância de modelo e acessar model_instance.related_field , o Django carregará o conteúdo do modelo associado de forma transparente e o retornará para você. No entanto, isso não é possível no código assíncrono - o código de bloqueio não deve ser executado no encadeamento principal e não há acesso assíncrono aos atributos.


Felizmente, o Django já tem uma maneira de resolver isso - select_related , que carrega os campos relacionados antecipadamente, e prefetch_related para relacionamentos muitos-para-muitos. Se você usar o ORM de forma assíncrona, proibiremos qualquer operação de bloqueio implícito, como acesso em segundo plano a atributos, e retornaremos um erro indicando que você deve primeiro recuperar o campo.


Isso tem o benefício adicional de impedir o código lento que executa solicitações N em um loop for , o que é um erro comum de muitos novos programadores do Django. Isso aumenta a barreira de entrada, mas lembre-se de que o Django assíncrono será opcional - os usuários ainda poderão escrever código síncrono se desejarem (e isso será incentivado no tutorial, pois é muito mais difícil cometer erros no código síncrono).


Felizmente, o QuerySet pode implementar facilmente geradores assíncronos e dar suporte transparente à sincronização e assincronia:


 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) 

Outros


Partes do ORM associadas a alterações de esquema não serão assíncronas; eles devem ser chamados apenas de equipes de gerenciamento. Alguns projetos já os chamam de envios, mas essa não é uma boa ideia.


Padrões


Os modelos agora são completamente síncronos, e o plano é deixá-los assim na primeira etapa. , , 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, . , , , , .


Teste


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


    • HTTP, ( )
    • async ORM

  • ( 3.1)


    • ORM ( )
    • ( )
    • ( )


    • ORM ( )
    • ( )
    • Email


; , . , , , , .


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

Source: https://habr.com/ru/post/pt461493/


All Articles