Como enviar SMS no FastAPI com a Twilio

April 13, 2021
Escrito por
Revisado por
Mia Adjei
Twilion

Como enviar SMS no FastAPI com a Twilio

O propósito da Twilio é impulsionar a comunicação de forma conveniente e rápida em qualquer linguagem. No entanto, se você tiver um aplicativo Python escrito com o pacote asyncio, talvez não fique totalmente claro como traduzir os exemplos da documentação em um código sem bloqueio que funcione bem com um loop assíncrono.

Neste tutorial, você aprenderá como fornecer notificações via SMS corretamente por meio de um aplicativo FastAPI. As técnicas mostradas aqui também são aplicáveis a outras estruturas asyncio.

Demonstração do projeto

Requisitos do tutorial

Comprar um número de telefone Twilio

Caso ainda não tenha feito isto, sua primeira tarefa é comprar um número de telefone Twilio para poder enviar SMS.

Faça login no Console da Twilio, selecione Phone Numbers (Números de telefone) e clique no sinal de adição vermelho para comprar um número Twilio. Observe que, se você tiver uma conta gratuita, você usará seu crédito de avaliação para esta compra.

Na página "Buy a Number" (Comprar um número), selecione seu país e marque SMS no campo "Capabilities" (Recursos). Se você solicitar um número local da sua região, poderá introduzir o seu código de área no campo "Number" (Número).

Comprar um número de telefone

Clique no botão "Search" (Pesquisar) para ver quais números estão disponíveis e, em seguida, clique em "Buy" (Comprar) para adquirir o número de sua preferência exibido nos resultados. Depois de confirmar a compra, clique no botão "Close" (Fechar).

Instalar e configurar o projeto

Nesta seção, você fará configuração de um novo projeto FastAPI. Para manter a organização, abra um terminal ou prompt de comando, encontre um local adequado e crie um novo diretório onde o projeto que você está prestes a criar será armazenado:

mkdir fastapi-sms
cd fastapi-sms

Como criar um ambiente virtual

Com base nas práticas recomendadas do Python, você criará um ambiente virtual, no qual instalará as dependências do Python necessárias para esse projeto.

Se você estiver usando um sistema Unix ou MacOS, abra um terminal e digite os seguintes comandos:

python3 -m venv venv
source venv/bin/activate

Se você estiver seguindo o tutorial no Windows, digite os seguintes comandos em uma janela de prompt de comando:

python -m venv venv
venv\Scripts\activate

Com o ambiente virtual ativado, você está pronto para instalar as dependências do Python necessárias para esse projeto:

pip install fastapi python-dotenv aiofiles python-multipart uvicorn twilio

Os seis pacotes Python que esse projeto usa são:

Configurar as credenciais e o número de telefone Twilio

Para poder enviar um SMS com o Twilio, o aplicativo FastAPI precisará ter acesso às credenciais da conta Twilio para autenticação. Além disso, para enviar um SMS, você precisará fornecer um número de remetente, o número Twilio adquirido anteriormente.

A maneira mais segura de estabelecer esses valores de configuração é definindo variáveis de ambiente para eles. E a maneira mais conveniente de gerenciar suas variáveis de ambiente em um aplicativo FastAPI é usando um arquivo .env.

Abra um novo arquivo chamado .env (veja que há um ponto à esquerda) no editor de texto e digite o seguinte conteúdo:

TWILIO_ACCOUNT_SID=xxxxxxxxx
TWILIO_AUTH_TOKEN=xxxxxxxxx
TWILIO_PHONE_NUMBER=xxxxxxxxx

Será necessário substituir todos os xxxxxxxxx pelos valores corretos que se aplicam a você. As duas primeiras variáveis são seu SID de conta e seu token de autenticação Twilio. Você pode encontrá-los no dashboard do Console da Twilio:

Account SID (SID da conta) e auth token (token de autenticação) da Twilio

A variável TWILIO_NUMBER é o número de telefone que você comprou na etapa acima. Quando você digitar esse número de telefone no arquivo .env, use o formato E.164, que inclui um sinal de adição e o código do país. Por exemplo, um número dos Estados Unidos seria fornecido como +1aaabbbcccc, em que aaa é o código de área e bbb-cccc é o número local.

Para incorporar essas três variáveis ao aplicativo FastAPI, crie um arquivo chamado config.py com o seguinte conteúdo:

from pydantic import BaseSettings


class Settings(BaseSettings):
    twilio_account_sid: str
    twilio_auth_token: str
    twilio_phone_number: str

    class Config:
        env_file = '.env'

O FastAPI depende da classe BaseSettingsdo pydantic para gerenciar sua configuração. As subclasses de BaseSettings importam automaticamente variáveis definidas como atributos das variáveis de ambiente ou diretamente do arquivo .env com sua integração com o dotenv.

Você aprenderá a trabalhar com a classe Settings na próxima seção.

Enviar um SMS com o FastAPI

Agora, estamos prontos para iniciar a codificação do aplicativo FastAPI. Faremos isso em algumas etapas.

Aplicativo FastAPI básico

Abaixo, você pode ver a primeira iteração do nosso aplicativo FastAPI. Esta versão apenas retorna a página principal, que apresenta um formulário da Web no qual o usuário pode digitar o número de telefone em que receberá o SMS.

Abra um novo arquivo chamado app.py no seu editor de texto ou IDE e digite este código:

import asyncio
from fastapi import FastAPI, Form, status
from fastapi.responses import FileResponse, RedirectResponse
from twilio.rest import Client
import config

app = FastAPI()
settings = config.Settings()


@app.get('/')
async def index():
    return FileResponse('index.html')

O decorador @app.get(‘/’) define um endpoint que é mapeado para o URL raiz do aplicativo. A implementação desse endpoint retorna uma resposta que é carregada com base em um arquivo estático denominado index.html.

Para que esse endpoint funcione, agora precisamos criar o arquivo HTML. Abra um novo arquivo chamado index.html no seu editor ou IDE e digite o seguinte código HTML:

<!doctype html>
<html>
  <head>
    <title>FastAPI SMS Example</title>
  </head>
  <body>
    <h1>FastAPI SMS Example</h1>
    <form method="post">
      <label for="phone">Your phone number:</label>
      <input name="phone" id="phone" placeholder="+12345678900">
      <input type="submit" value="Send SMS!">
    </form>
  </body>
</html>

Executar o servidor

O aplicativo está incompleto, mas funcional o suficiente para ser iniciado. Certifique-se de que os arquivos app.pyindex.html e .env tenham sido criados anteriormente no diretório do projeto e inicie o aplicativo usando o seguinte comando:

uvicorn app:app --reload

Uvicorn é o servidor recomendado para executar aplicativos FastAPI. Estamos iniciando o servidor com a opção --reload, que fará com que o uvicorn veja nossos arquivos de origem e reinicie automaticamente o servidor quando as alterações forem feitas. Você pode deixar o servidor em execução durante todo o restante do tutorial.

Para verificar se o aplicativo está correto, abra um navegador da Web e digite http://localhost:8000 na barra de endereços. O navegador deve carregar a página principal do aplicativo, que se parece com esta:

Formulário da Web

Gerenciar dados de formulário

Se você tentar enviar o formulário, o FastAPI retornará a mensagem de erro "Method not allowed" (Método não permitido). Isso ocorre porque ainda não implementamos o endpoint de envio de formulário.

Se você observar o elemento <form> no index.html, definimos o formulário com o atributo method definido como post e nenhum atributo action. Isso significa que o formulário será enviado com uma solicitação POST para o URL de origem, que, neste caso, é o URL raiz do aplicativo.

O endpoint terá a seguinte estrutura. Você pode adicioná-lo na parte inferior do app.py, mas observe as linhas que começam com # TODO, que indicam partes da função que ainda não foram criadas.

@app.post('/')
async def handle_form(phone: str = Form(...)):
    # TODO: send the SMS!
    # TODO: return a response to the client

Neste segundo endpoint, estamos usando o decorador @app.post(‘/’) para definir um manipulador para as solicitações POST. O formulário da Web que estamos usando tem um único campo chamado phone, então esse será um argumento na função. O FastAPI analisará os dados do formulário e extrairá o valor desse campo passado pelo cliente, e o enviará para a função nesse argumento.

Embora a estrutura da função já esteja bem compreendida, temos trabalho a fazer para enviar o SMS e retornar uma resposta. Por esse motivo, continuaremos trabalhando nesse endpoint nas seções a seguir.

Enviar o SMS

Quando a função handle_form() for chamada, teremos um número de telefone para o qual enviar um SMS. Podemos acessar as credenciais do Twilio e o número de telefone do remetente na classe Settings que escrevemos anteriormente. Portanto, agora temos tudo o que precisamos para enviar um SMS com o Twilio.

O problema é que a biblioteca auxiliar do Twilio para Python não oferece suporte a aplicativos assíncronos. Como essa biblioteca fará solicitações de rede para servidores Twilio, ela bloqueará o loop se ele for usado diretamente na função assíncrona. Para evitar esse problema, encapsularemos todo o trabalho relacionado ao Twilio em uma função que chamaremos de send_sms() e a executaremos dentro de um executor para manter o aplicativo assíncrono rodando sem problemas.

Abaixo, você pode ver uma versão atualizada da função handle_form() de app.py com a lógica para executar a função send_sms() em um executor.

@app.post('/')
async def handle_form(phone: str = Form(...)):
    await asyncio.get_event_loop().run_in_executor(
        None, send_sms, phone, 'Hello from FastAPI!')
    # TODO: return a response to the client

Com o método run_in_executor() do loop asyncio, é possível executar uma função de bloqueio em um thread ou processo separado para que o loop não seja bloqueado. O primeiro argumento é o executor de sua preferência, ou None, se você não se importar de usar um executor de thread padrão. Os argumentos restantes são a função a ser executada e seus argumentos.

Agora, veremos a implementação da função send_sms(). Observe que esse é um código síncrono padrão, portanto, essa função não é definida com a palavra-chave async. Adicionar esta função a app.py:

def send_sms(to_number, body):
    client = Client(settings.twilio_account_sid, settings.twilio_auth_token)
    return client.messages.create(from_=settings.twilio_phone_number,
                                  to=to_number, body=body)

A função cria uma instância do objeto Twilio Client e a inicializa com os valores de SID da conta e token de autenticação provenientes do objeto settings.

Em seguida, o client.messages.create() é usado para criar e entregar o SMS. Essa função usa os argumentos from_to e body para definir o remetente, destinatário e corpo do SMS, respectivamente. Observe que from_ é usado porque from é uma palavra-chave reservada no Python.

Como enviar uma resposta

Na seção anterior, deixamos a função handle_form() incompleta. Depois que o SMS é enviado pelo executor, o servidor precisa retornar uma resposta. A prática mais aceita ao lidar com um envio de formulário é responder com um redirecionamento, o que evita uma série de possíveis problemas, incluindo envios duplicados de formulário e avisos confusos exibidos no navegador para o usuário. Essa prática é conhecida como Post/Redirect/Get pattern ou PRG.

Neste aplicativo, podemos redirecionar para uma página que indica que o SMS foi enviado com êxito e dar a opção ao usuário de enviar outro SMS. Aqui está a implementação completa da função handle_form(). Atualize sua versão no app.py.

@app.post('/')
async def handle_form(phone: str = Form(...)):
    await asyncio.get_event_loop().run_in_executor(
        None, send_sms, phone, 'Hello from FastAPI!')
    return RedirectResponse('/success', status_code=status.HTTP_303_SEE_OTHER)

A resposta instruirá o navegador a redirecionar imediatamente para o URL /success. Agora podemos criar um manipulador para esse URL. Esse manipulador é aplicado no final de app.py:

@app.get('/success')
async def success():
    return FileResponse('success.html')

Esse é outro manipulador curto que renderiza uma página HTML, assim como o principal. Crie um arquivo success.html e copie o seguinte conteúdo nele:

<!doctype html>
<html>
  <head>
    <title>FastAPI SMS Example</title>
  </head>
  <body>
    <h1>FastAPI SMS Example</h1>
    <p>Check your phone!</p>
    <p><a href="/">Send another one?</a></p>
  </body>
</html>

Essa página solicita que o usuário verifique a mensagem em seu telefone e inclui um link para a página principal, caso o usuário queira enviar outro SMS.

Testar o aplicativo

Chegamos ao momento que você estava esperando. O aplicativo está concluído e estamos prontos para testá-lo. Verifique se o app.py está atualizado com todas as funções mostradas acima e se os arquivos config.py.envindex.html e success.html também estão no diretório do projeto.

Se você deixou o uvicorn em execução desde o início do tutorial, sempre que fizer uma atualização nos arquivos de origem, o servidor deverá reiniciar sozinho e, então, você estará pronto para começar. Se você não estiver executando o servidor, poderá iniciá-lo agora com o comando:

uvicorn app:app --reload

Abra o navegador da Web e navegue até http://localhost:8000. No formulário, digite seu número de celular pessoal usando o formato E.164. Clique no botão enviar e, logo em seguida, você receberá o SMS em seu telefone.

Página do projeto

Se você estiver usando uma conta gratuita do Twilio, lembre-se de que o número que você usar como destinatário deverá ser verificado para poder receber um SMS. Esse requisito não se aplicará se você estiver usando uma conta paga.

Conclusão

Parabéns! Agora você sabe como enviar SMS com o FastAPI. Como mencionado na introdução, as técnicas que você aprendeu neste artigo podem ser aplicadas a outras estruturas baseadas em asyncio, como QuartSanic e Tornado. Para uma discussão aprofundada sobre como trabalhar com o Twilio em seus aplicativos assíncronos, verifique o artigo Using the Twilio Python Helper Library in your Async Applications (Usar a Biblioteca auxiliar do Twilio para Python em seus aplicativos assíncronos).

Adoraria ver suas criações com o Twilio e o asyncio!

Este artigo foi traduzido do original "How to Send an SMS in FastAPI with Twilio". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.