Como Tratar Erros em uma Aplicação Flask

O autor selecionou o Free and Open Source Fund para receber uma doação como parte do programa Write for DOnations.

Introdução

Flask é um framework web leve em Python que fornece ferramentas e recursos úteis para a criação de aplicativos web na linguagem Python.

Ao desenvolver um aplicativo web, você inevitavelmente se deparará com situações em que seu aplicativo se comporta de maneira contrária à que você esperava. Você pode escrever errado um nome de variável, usar mal um loop for ou construir uma instrução if de forma a gerar uma exceção em Python, como chamar uma função antes de declará-la ou simplesmente procurar uma página que não existe. Você achará mais fácil e suave desenvolver seus aplicativos Flask se aprender a lidar adequadamente com erros e exceções.

Neste tutorial, você construirá um pequeno aplicativo web que demonstra como lidar com erros comuns encontrados ao desenvolver um aplicativo web. Você criará páginas de erro personalizadas, usará o depurador Flask para solucionar exceções e utilizará o registro para acompanhar eventos em seu aplicativo.

Pré-requisitos

Passo 1 — Usando O Debugger do Flask

Neste passo, você criará uma aplicação que possui alguns erros e a executará sem o modo de depuração para ver como a aplicação responde. Então, você a executará com o modo de depuração ativado e usará o debugger para solucionar erros na aplicação.

Com seu ambiente de programação ativado e Flask instalado, abra um arquivo chamado app.py para edição dentro do diretório flask_app:

  1. nano app.py

Adicione o seguinte código dentro do arquivo app.py:

flask_app/app.py
from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')

No código acima, primeiro importa a classe Flask do pacote flask. Em seguida, cria uma instância de aplicação Flask chamada app. Utiliza o decorador @app.route() para criar uma função de visualização chamada index(), que chama a função render_template() como valor de retorno, que por sua vez renderiza um template chamado index.html. Existem dois erros neste código: o primeiro é que você não importou a função render_template(), e o segundo é que o arquivo de template index.html não existe.

Salve e feche o arquivo.

Em seguida, informe ao Flask sobre a aplicação usando a variável de ambiente FLASK_APP com o seguinte comando (no Windows, use set em vez de export):

  1. export FLASK_APP=app

Então execute o servidor de aplicação usando o comando flask run:

  1. flask run

Você verá as seguintes informações em seu terminal:

Output
* Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Esta saída fornece as seguintes informações:

  • A aplicação Flask que está sendo servida (app.py neste caso)

  • O ambiente, que é production aqui. A mensagem de aviso destaca que este servidor não é para implantação em produção. Você está usando este servidor para desenvolvimento, então pode ignorar este aviso, mas para mais informações, consulte a página Opções de Implantação na documentação do Flask. Você também pode conferir este tutorial de implantação do Flask com Gunicorn, ou este com uWSGI, ou pode usar a plataforma DigitalOcean App Platform para implantar sua aplicação Flask seguindo o Como Implantar uma Aplicação Flask Usando Gunicorn no App Platform tutorial.

  • O modo de depuração está desativado, o que significa que o depurador do Flask não está em execução e você não receberá mensagens de erro úteis em sua aplicação. Em um ambiente de produção, exibir erros detalhados expõe sua aplicação a vulnerabilidades de segurança.

  • O servidor está rodando no URL http://127.0.0.1:5000/. Para parar o servidor, use CTRL+C, mas não faça isso agora mesmo.

Agora, visite a página inicial usando seu navegador:

http://127.0.0.1:5000/

Você verá uma mensagem que se parece com o seguinte:

Output
Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

Este é o 500 Internal Server Error, que é uma resposta de erro do servidor que indica que o servidor encontrou um erro interno no código da aplicação.

No terminal, você verá a seguinte saída:

Output
[2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET] Traceback (most recent call last): File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app response = self.full_dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request rv = self.handle_user_exception(e) File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request rv = self.dispatch_request() File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index return render_template('index.html') NameError: name 'render_template' is not defined 127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -

O rastreamento acima passa pelo código que desencadeou o erro interno do servidor. A linha NameError: name 'render_template' is not defined indica a causa raiz do problema: a função render_template() não foi importada.

Como você pode ver aqui, você precisa ir ao terminal para solucionar erros, o que não é conveniente.

Você pode ter uma experiência de solução de problemas melhor habilitando o modo de depuração no seu servidor de desenvolvimento. Para fazer isso, pare o servidor com CTRL+C e defina a variável de ambiente FLASK_ENV como development, para que você possa executar a aplicação no modo de desenvolvimento (que habilita o depurador), usando o seguinte comando (no Windows, use set em vez de export):

  1. export FLASK_ENV=development

Execute o servidor de desenvolvimento:

  1. flask run

Você verá uma saída semelhante à seguinte no terminal:

Output
* Serving Flask app 'app' (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 120-484-907

Aqui você vê que o ambiente agora é development, o modo de depuração está ativado e o depurador está ativo. O Debugger PIN é um PIN que você precisa para desbloquear o console no seu navegador (um shell interativo de Python que você pode acessar clicando no pequeno ícone de terminal encerrado na imagem abaixo).

Atualize a página inicial no seu navegador e você verá a seguinte página:

Aqui, você vê a mensagem de erro exibida de uma maneira mais fácil de entender. O primeiro título fornece o nome da exceção Python que causou o problema (NameError neste caso). A segunda linha fornece a razão direta (render_template() não está definido, o que significa que não foi importado neste caso). Depois disso, você tem o rastreamento através do código interno do Flask que foi executado. Leia o rastreamento de baixo para cima, porque a última linha no rastreamento geralmente contém as informações mais úteis.

Nota:
O ícone de terminal circulado permite que você execute código Python no navegador em diferentes frames. Isso é útil quando você deseja verificar o valor de uma variável da mesma forma que faria em um shell interativo de Python. Quando você clicar no ícone do terminal, precisará digitar o código PIN do Depurador que obteve ao executar o servidor. Você não precisará deste shell interativo neste tutorial.

Para resolver esse problema de NameError, deixe o servidor em execução, abra uma nova janela de terminal, ative seu ambiente e abra o arquivo app.py:

  1. nano app.py

Modifique o arquivo para ficar assim:

flask_app/app.py

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')

Salve e feche o arquivo.

Aqui, você importou a função render_template() que estava faltando.

Com o servidor de desenvolvimento em execução, atualize a página inicial no seu navegador.

Desta vez, você verá uma página de erro com informações que se parecem com isto:

Output
jinja2.exceptions.TemplateNotFound jinja2.exceptions.TemplateNotFound: index.html

Esta mensagem de erro indica que o template index.html não existe.

Para corrigir isso, você criará um arquivo de template base.html do qual outros templates herdarão para evitar repetição de código, e então um template index.html que estende o template base.

Crie o diretório templates, que é o diretório onde o Flask procura por arquivos de template. Então, abra um arquivo base.html usando seu editor favorito:

  1. mkdir templates
  2. nano templates/base.html

Adicione o seguinte código ao seu arquivo base.html:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

Salve e feche o arquivo.

Este template base contém todo o boilerplate HTML que você precisará reutilizar em seus outros templates. O bloco title será substituído para definir um título para cada página, e o bloco content será substituído pelo conteúdo de cada página. A barra de navegação possui dois links, um para a página inicial onde você usa a função auxiliar url_for() para vincular à função de visualização index(), e outro para uma página Sobre, caso você opte por incluir uma em sua aplicação.

Em seguida, abra um arquivo de template chamado index.html, que herdará do template base.

  1. nano templates/index.html

Adicione o seguinte código a ele:

flask_app/templates/index.html
{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Index {% endblock %}</h1>
    <h2>Welcome to FlaskApp!</h2>
{% endblock %}

Salve e feche o arquivo.

No código acima, você estende o template base e sobrescreve o bloco content. Em seguida, define um título para a página e o exibe em um cabeçalho H1 usando o bloco title, e exibe uma saudação em um cabeçalho H2.

Com o servidor de desenvolvimento em execução, atualize a página index no seu navegador.

Você verá que o aplicativo não exibe mais erros e a página index é exibida conforme esperado.

Agora você usou o modo de depuração e viu como lidar com mensagens de erro. Em seguida, você abortará uma solicitação para responder com uma mensagem de erro de sua escolha e verá como responder com páginas de erro personalizadas.

Passo 2 — Criando Páginas de Erro Personalizadas

Neste passo, você aprenderá como abortar solicitações e responder com uma mensagem de erro HTTP 404 para quando o usuário solicita dados que não existem no servidor. Você também aprenderá como criar páginas de erro personalizadas para erros HTTP comuns, como o erro 404 Not Found e o erro 500 Internal Server Error.

Para demonstrar como abortar requisições e responder com uma página de erro HTTP 404 personalizada, você criará uma página que exibe algumas mensagens. Se a mensagem solicitada não existir, você responderá com um erro 404.

Primeiro, abra seu arquivo app.py para adicionar uma nova rota para a página de mensagens:

  1. nano app.py

Adicione a seguinte rota no final do arquivo:

flask_app/app.py
# ...

@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    return render_template('message.html', message=messages[idx])

Salve e feche o arquivo.

Na rota acima, você tem uma variável de URL idx. Este é o índice que determinará qual mensagem será exibida. Por exemplo, se a URL for /messages/0, a primeira mensagem (Message Zero) será exibida. Você usa o int converter para aceitar apenas inteiros positivos, pois variáveis de URL têm valores de string por padrão.

Dentro da função de visualização message(), você tem uma lista regular do Python chamada messages com três mensagens. (Em um cenário do mundo real, essas mensagens viriam de um banco de dados, uma API ou outra fonte de dados externa.) A função retorna uma chamada para a função render_template() com dois argumentos, message.html como o arquivo de template, e uma variável message que será passada para o template. Esta variável terá um item da lista messages dependendo do valor da variável idx na URL.

Em seguida, abra um novo arquivo de template message.html:

  1. nano templates/message.html

Adicione o seguinte código a ele:

flask_app/templates/message.html
{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Messages {% endblock %}</h1>
    <h2>{{ message }}</h2>
{% endblock %}

Salve e feche o arquivo.

No código acima, você estende o modelo base e sobrescreve o bloco content. Adiciona um título (Messages) em um cabeçalho H1 e exibe o valor da variável message em um cabeçalho H2.

Com o servidor de desenvolvimento em execução, visite as seguintes URLs no seu navegador:

http://127.0.0.1:5000/messages/0
http://127.0.0.1:5000/messages/1
http://127.0.0.1:5000/messages/2
http://127.0.0.1:5000/messages/3

Você verá que o H2 contém o texto Message Zero, Message One ou Message Two respectivamente em cada uma das três primeiras URLs. No entanto, na quarta URL, o servidor responderá com uma mensagem de erro IndexError: list index out of range. Em um ambiente de produção, a resposta teria sido um 500 Internal Server Error, mas a resposta adequada aqui é um 404 Not Found para indicar que o servidor não pode encontrar uma mensagem com um índice de 3.

Você pode responder com um erro 404 usando a função auxiliar abort() do Flask. Para fazer isso, abra o arquivo app.py:

  1. nano app.py

Edite a primeira linha para importar a função abort(). Em seguida, edite a função de visualização message() adicionando uma cláusula try ... except conforme mostrado nas partes destacadas abaixo:

flask_app/app.py
from flask import Flask, render_template, abort

# ...
# ...


@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        return render_template('message.html', message=messages[idx])
    except IndexError:
        abort(404)

Salve e feche o arquivo.

No código acima, você importa a função abort(), que você usa para abortar a solicitação e responder com um erro. Na função de visualização message(), você usa uma cláusula try ... except para envolver a função. Primeiro, tenta retornar o template messages com a mensagem que corresponde ao índice na URL. Se o índice não tiver uma mensagem correspondente, a exceção IndexError será levantada. Então, você usa a cláusula except para capturar esse erro e usa abort(404) para abortar a solicitação e responder com um erro HTTP 404 Not Found.

Com o servidor de desenvolvimento em execução, use seu navegador para revisitar a URL que respondeu com um IndexError anteriormente (ou visite qualquer URL com um índice maior que 2):

http://127.0.0.1:5000/messages/3

Você verá a seguinte resposta:

Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

Agora você tem uma mensagem de erro melhor que indica que o servidor não pôde encontrar a mensagem solicitada.

Em seguida, você criará um template para a página de erro 404 e outro para a página de erro 500.

Primeiro, você registrará uma função com o decorador especial @app.errorhandler() como um manipulador para o erro 404. Abra o arquivo app.py para edição:

nano app.py

Edite o arquivo adicionando a parte destacada da seguinte forma:

flask_app/app.py
from flask import Flask, render_template, abort

app = Flask(__name__)


@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/messages/<int:idx>')
def message(idx):
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        return render_template('message.html', message=messages[idx])
    except IndexError:
        abort(404)

Salve e feche o arquivo.

Aqui, você usa o decorador @app.errorhandler() para registrar a função page_not_found() como um manipulador de erro personalizado. A função recebe o erro como argumento e retorna uma chamada para a função render_template() com um template chamado 404.html. Você criará esse template posteriormente e pode usar outro nome se desejar. Você também retorna o inteiro 404 após a chamada render_template(). Isso informa ao Flask que o código de status na resposta deve ser 404. Se você não adicioná-lo, o código de status de resposta padrão será 200, o que significa que a solicitação foi bem-sucedida.

Em seguida, abra um novo template 404.html:

  1. nano templates/404.html

Adicione o seguinte código a ele:

flask_app/templates/404.html
{% extends 'base.html' %}

{% block content %}
        <h1>{% block title %} 404 Not Found. {% endblock %}</h1>
        <p>OOPS! Sammy couldn't find your page; looks like it doesn't exist.</p>
        <p>If you entered the URL manually, please check your spelling and try again.</p>
{% endblock %}

Salve e feche o arquivo.

Assim como qualquer outro template, você estende o template base, substitui o conteúdo dos blocos content e title, e adiciona seu próprio código HTML. Aqui, você tem um título com a tag <h1>, uma tag <p> com uma mensagem de erro personalizada informando ao usuário que a página não foi encontrada, e uma mensagem útil para usuários que podem ter inserido a URL manualmente.

Você pode usar qualquer HTML, CSS e JavaScript que desejar em suas páginas de erro da mesma maneira que faria em outros templates.

Com o servidor de desenvolvimento em execução, use seu navegador para revisitar a seguinte URL:

http://127.0.0.1:5000/messages/3

Você verá que a página agora possui a barra de navegação que está no template base e a mensagem de erro personalizada.

Da mesma forma, você pode adicionar uma página de erro personalizada para seus erros 500 Internal Server Error. Abra o arquivo app.py:

  1. nano app.py

Adicione o seguinte manipulador de erro abaixo do manipulador de erro 404:

flask_app/app.py
# ...

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_error(error):
    return render_template('500.html'), 500

# ...

Aqui, você usa o mesmo padrão que utilizou para o manipulador de erro 404. Você usa o decorador app.errorhandler() com um argumento 500 para transformar a função chamada internal_error() em um manipulador de erro. Você renderiza um template chamado 500.html e responde com um código de status 500.

Então, para demonstrar como o erro personalizado será apresentado, adicione uma rota que responda com um erro HTTP 500 no final do arquivo. Esta rota sempre fornecerá um 500 Internal Server Error, independentemente de o depurador estar em execução ou não:

flask_app/app.py

# ...
@app.route('/500')
def error500():
    abort(500)

Aqui, você cria uma rota /500 e usa a função abort() para responder com um erro HTTP 500.

Salve e feche o arquivo.

Em seguida, abra o novo template 500.html:

  1. nano templates/500.html

Adicione o seguinte código a ele:

flask_app/templates/500.html
{% extends 'base.html' %}

{% block content %}
        <h1>{% block title %} 500 Internal Server Error {% endblock %}</h1>
        <p>OOOOPS! Something went wrong on the server.</p>
        <p>Sammy is currently working on this issue. Please try again later.</p>
{% endblock %}

Salve e feche o arquivo.

Aqui, você faz a mesma coisa que fez com o template 404.html. Você estende o template base e substitui o bloco de conteúdo com um título e duas mensagens personalizadas informando ao usuário sobre o erro interno do servidor.

Com o servidor de desenvolvimento em execução, visite a rota que responde com um erro 500:

http://127.0.0.1:5000/500

Sua página personalizada aparecerá em vez da página de erro genérica.

Agora você sabe como usar páginas de erro personalizadas para erros HTTP em sua aplicação Flask. Em seguida, você aprenderá como usar o registro (logging) para rastrear eventos em sua aplicação. Rastrear eventos ajuda a entender como seu código se comporta, o que, por sua vez, auxilia no desenvolvimento e na solução de problemas.

Passo 3 — Usando Logging para Rastrear Eventos em Sua Aplicação

Neste passo, você usará o registro (logging) para rastrear eventos que ocorrem enquanto o servidor está em execução e a aplicação está sendo utilizada, o que ajuda a ver o que está acontecendo no código da sua aplicação, facilitando a solução de erros.

Você já viu registros sempre que o servidor de desenvolvimento está em execução, que geralmente se parecem com isto:

127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -

Nesses registros, você pode ver as seguintes informações:

  • 127.0.0.1: O host em que o servidor estava em execução.
  • [21/Set/2021 14:36:45]: A data e hora da solicitação.
  • GET: O método de requisição HTTP. Neste caso, GET é usado para recuperar dados.
  • /messages/2: O caminho solicitado pelo usuário.
  • HTTP/1.1: A versão do HTTP.
  • 200 ou 404: O código de status da resposta.

Esses logs ajudam a diagnosticar problemas que ocorrem em sua aplicação. Você pode registrar mais informações quando deseja saber mais detalhes sobre certas solicitações usando o logger app.logger que o Flask fornece.

Com o registro, você pode usar diferentes funções para reportar informações em diferentes níveis de registro. Cada nível indica um evento que ocorreu com um certo grau de severidade. As seguintes funções podem ser utilizadas:

  • app.logger.debug(): Para informações detalhadas sobre o evento.
  • app.logger.info(): Confirmação de que as coisas estão funcionando como esperado.
  • app.logger.warning(): Indicação de que algo inesperado aconteceu (como “espaço em disco baixo”), mas a aplicação está funcionando como esperado.
  • app.logger.error(): Um erro ocorreu em alguma parte da aplicação.
  • app.logger.critical(): Um erro crítico; a aplicação inteira pode parar de funcionar.

Para demonstrar como usar o logger do Flask, abra seu arquivo app.py para edição e registre alguns eventos:

  1. nano app.py

Edite a função de visualização message() para parecer com o seguinte:

flask_app/app.py

# ...

@app.route('/messages/<int:idx>')
def message(idx):
    app.logger.info('Building the messages list...')
    messages = ['Message Zero', 'Message One', 'Message Two']
    try:
        app.logger.debug('Get message with index: {}'.format(idx))
        return render_template('message.html', message=messages[idx])
    except IndexError:
        app.logger.error('Index {} is causing an IndexError'.format(idx))
        abort(404)

# ...

Salve e feche o arquivo.

Aqui, você registrou alguns eventos em diferentes níveis. Você usa app.logger.info() para registrar um evento que está funcionando como esperado (que é um nível INFO). Você usa app.logger.debug() para informações detalhadas (DEBUG level), mencionando que a aplicação está agora recebendo uma mensagem com um índice específico. Então, você usa app.logger.error() para registrar o fato de que uma exceção IndexError foi levantada com o índice específico que causou o problema (ERROR level, porque ocorreu um erro).

Visite a seguinte URL:

http://127.0.0.1:5000/messages/1

Você verá as seguintes informações no terminal onde seu servidor está rodando:

Output
[2021-09-21 15:17:02,625] INFO in app: Building the messages list... [2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1 127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -

Aqui você vê a mensagem INFO app.logger.info() registra, e a mensagem DEBUG com o número de índice que você registrou usando app.logger.debug().

Agora visite uma URL para uma mensagem que não existe:

http://127.0.0.1:5000/messages/3

Você verá as seguintes informações no terminal:

Output
[2021-09-21 15:33:43,899] INFO in app: Building the messages list... [2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3 [2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError 127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -

Como você pode ver, você tem logs INFO e DEBUG que você já viu antes, e um novo log ERROR porque uma mensagem com índice 3 não existe.

Registrar eventos, informações detalhadas e erros ajuda a identificar onde algo deu errado e facilita a solução de problemas.

Você aprendeu neste passo como usar o logger do Flask. Confira Como Usar Logging em Python 3 para uma melhor compreensão de logging. Para uma análise aprofundada do logging, consulte a documentação de logging do Flask e a documentação do Python para logging.

Conclusão

Agora você sabe como usar o modo de depuração no Flask e como solucionar e corrigir alguns erros comuns que pode encontrar ao desenvolver uma aplicação web em Flask. Você também criou páginas de erro customizadas para erros HTTP comuns e utilizou o logger do Flask para rastrear eventos em sua aplicação, ajudando-o a inspecionar e entender como sua aplicação se comporta.

Se você deseja ler mais sobre Flask, confira a página de tópicos sobre Flask.

Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-errors-in-a-flask-application