Objetivo
Mostrar o funcionamento de APIs REST com Flask.
Introdução
Flask é um “micro-framework” que contém um core robusto que inclui as funcionalidades básicas que toda aplicação web necessita e ainda permite a utilização de extensões, que oferecem funcionalidades extras como acesso a banco de dados, validação de formulários, autenticação de usuários etc.
Flask tem algumas dependências principais como:
Werkzeug – Permite o uso de rotas (routes), debugging e WSGI (Web Server Gateway Interface).
Jinja2 – Suporte a templates.
Click – Interface de linha de comando.
Iniciando
Toda aplicação ou API em Flask precisa criar um application instance. O servidor web passa todas as requisições que ele recebe dos clientes para um application instance através do protocolo WSGI. Um application instance por sua vez é um objeto da classe Flask, geralmente criado da seguinte forma.
from flask import Flask
app = Flask(__name__)
O único argumento obrigatório do construtor da classe Flask é o nome do módulo principal ou pacote da aplicação. Na maioria dos casos, a variável __name__ é o valor correto para este argumento.
Flask usa este argumento para determinar a localização da aplicação, que por sua vez permite localizar outros arquivos que são parte da aplicação, assim como imagens e templates.
Routes
Quando um application instance recebe uma requisição ele precisa saber qual o código executar para cada URL recebida, assim, ele mantém um mapeamento de URLs para funções em Python. Esta associação entre URLs e funções é chamada de route.
A forma mais fácil de definir uma rota em uma aplicação Flask é através do decorator app.route que é exposto pela instância da aplicação (application instance) conforme exemplo abaixo.
@app.route('/')
def index():
return 'Hello World!
'
O exemplo anterior registra a função index() como sendo a responsável por tratar requisições para a URL root (‘/’) da aplicação.
Funções como index(), que são as responsáveis por tratar as URLs, são chamadas de view functions. O valor retornado por uma view function é a resposta (response) que o cliente recebe. No caso de APIs, esta resposta geralmente é texto que pode estar no formato txt, json, yaml, xml etc.
O código anterior usou um decorator para registrar a função index(), mas é possível utilizar também o método app.add_url_rule(), que usa três argumentos: URL, o nome do endpoint e o nome da view function. O próximo exemplo usa este método para registrar a função index(), de forma equivalente ao que foi feito no exemplo anterior.
def index():
return 'Hello World!
'
app.add_url_rule('/', 'index', index)
Rotas dinâmicas
Algumas URLs podem conter componentes dinâmicos, por exemplo, https://www.facebook.com/<your-name>, que inclui o nome de usuário, sendo diferente para cada usuário que acessa o facebook. Mas a pergunta é, como fazer para que uma função reconheça e trate este componente dinâmico? Em Flask é possível fazer esta tratativa com uma sintaxe especial do decorator app.route. O exemplo abaixo define uma rota com componente dinâmico.
@app.route('/user/')
def user(name):
return 'Hello, {}!
'.format(name)
A parte da URL que está entre os sinais de maior e menor (<>) é a parte dinâmica, o restante da URL é a porção estática. Então, qualquer URL que contenha a porção estática (/user) será mapeada para a view funtion user() e quando esta função for chamada o componente dinâmico (name) será passado como argumento. No exemplo anterior, o argumento name é usado para gerar uma resposta personalizada.
Web Server do Flask
O Flask contém um servidor web que pode ser utilizado para testar uma aplicação. Este web server tem o propósito de ser utilizado para desenvolvimento e teste, não sendo indicado para ambientes produtivos.
Há pelo menos duas formas de iniciar o web server do Flask:
- Usando o método app.run()
- Através de linha de comando
Para iniciar o web server usando o método app.run(), basta que ele seja invocado no final do código.
app.run()
Para iniciar o web server por linha de comando é necessário setar a variável de ambiente FLASK_APP e executar o comando flask run.
$ set FLASK_APP=hello.py
$ flask run
Depois de iniciado, o web server mostrará uma mensagem como esta.
* Serving Flask app "hello"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
API teste
O código abaixo demonstra como uma simples API pode ser definida. O Web Server é iniciado ao final do código chamando o método app.run().
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World!
'
@app.route('/user/')
def user(name):
return 'Hello, {}!
'.format(name)
app.run()
* Serving Flask app 'teste1'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
Para testar esta API podemos utilizar o browser ou o Postman.
A aplicação responde na rota raíz (‘/’) e também /user/<name>.
Linha de comando
O comando flask pode ser usado com diversas opções. Para verificar quais são estas opções rode flask –help.
flask --help
* Ignoring a call to 'app.run()' that would block the current 'flask' CLI command.
Only call 'app.run()' in an 'if __name__ == "__main__"' guard.
Usage: flask [OPTIONS] COMMAND [ARGS]...
A general utility script for Flask applications.
An application to load must be given with the '--app' option, 'FLASK_APP'
environment variable, or with a 'wsgi.py' or 'app.py' file in the current
directory.
Options:
-e, --env-file FILE Load environment variables from this file. python-
dotenv must be installed.
-A, --app IMPORT The Flask application or factory function to load, in
the form 'module:name'. Module can be a dotted import
or file path. Name is not required if it is 'app',
'application', 'create_app', or 'make_app', and can be
'name(args)' to pass arguments.
--debug / --no-debug Set debug mode.
--version Show the Flask version.
--help Show this message and exit.
Commands:
routes Show the routes for the app.
run Run a development server.
shell Run a shell in the app context.
O comando flask run –host x.x.x.x é útil pois diz a Flask em qual interface de rede o web server irá escutar as requisições dos clientes. Por padrão, o servidor web do Flask escuta em localhost na porta 5000.
O comando abaixo diz ao servidor web para escutar em todos as interfaces disponíveis na máquina.
flask run --host 0.0.0.0
* Ignoring a call to 'app.run()' that would block the current 'flask' CLI command.
Only call 'app.run()' in an 'if __name__ == "__main__"' guard.
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.1.3:5000
Press CTRL+C to quit
Ciclo Request-Response - Contexto de aplicação e contexto de requisição
Quando o Flask recebe uma requisição de um cliente, ele precisa disponibilizar alguns objetos para a view function que irá tratar a requisição. Um bom exemplo é o objeto request, que encapsula a requisição HTTP enviada pelo cliente.
A forma mais óbvia do Flask dar o objeto request para uma view function seria passar o objeto como um argumento. O problema aqui é que todas view functions precisariam de um parâmetro extra e podemos pensar que seria ainda mais complicado se considerarmos que o objeto request pode não ser o único objeto que uma view function precisa para ter acesso a uma requisição completa.
Para evitar ter view functions repletas de parâmetros o Flask tem conceito de contexto (context) para tornar certos objetos globalmente acessíveis temporariamente.
Neste sentido as view functions não aceitam um objeto request que contenha os metadados de uma requisição HTTP. Em outros frameworks, como Django, seria diferente, conforme exemplificado a seguir.
def users(request):
if request.method == 'POST':
# Save the form data to the database
# Send response
else:
# Get all users from the database
# Send response
Em Flask, é preciso importar o objeto request desta forma.
from flask import request
@app.route('/users', methods=['GET', 'POST'])
def users():
if request.method == 'POST':
# Save the form data to the database
# Send response
else:
# Get all users from the database
# Send response
Em Flask, o objeto request parece e age como uma variável global, mas não é. Em um ambiente multithread diversas threads podem tratar diferentes requisições de múltiplos clientes simultaneamente, assim, cada thread precisa ver um objeto request diferente.
O Flask usa os contextos para fazer os objetos agirem como variáveis globais em uma determinada thread sem interferir em outras threads.
Quando um requisição é recebida, o Flask fornece dois contextos: Application e Request.
Application (current_app e g) – mantém o controle de dados ao nível de aplicação (variáveis de configuração, logger, conexão de banco).
Request (request e session) – mantém o controle dos dados ao nível de requisição (URL, método HTTP, headers, dados da requisição, informações de sessão).
O Flask cria estes contextos quando uma requisição é recebida e antes de ser enviada para aplicação, e remove os contextos quando uma requisição é tratada. Isto é feito automaticamente pelo Flask, então, não precisamos nos preocupar muito sobre como funciona internamente este mecanismo, mas se desejarmos testar estes contextos fora de uma view function ou no shell do Python precisamos ativar(push) os contextos.
O código tenta imprimir o contexto da aplicação sem manualmente ativá-lo.
from flask import Flask, current_app # importa os objetos Flask e current_app.
app = Flask(__name__) # inicia uma aplicação Flask.
print(f'Nome da aplicação recuperado do contexto: {current_app.name}') # imprime o nome da aplicação (arquivo).
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
O código abaixo cria um contexto de aplicação usando o método app_context() da instância Flask (app).
from flask import Flask, current_app # importa os objetos Flask e current_app.
app = Flask(__name__) # inicia uma aplicação Flask.
appli_context = app.app_context() # chama o método do contexto da aplicação.
appli_context.push() # ativa o contexto da aplicação.
print(f'Nome da aplicação recuperado do contexto: {current_app.name}') # imprime o nome da aplicação (arquivo).
Nome da aplicação recuperado do contexto: teste1
Conforme falei anteriormente, estes contextos são automaticamente criados quando o cliente faz uma requisição, então, o importante é conhecer os atributos e métodos expostos por estes contextos.
O objeto Request
Como vimos o Flask expõe um objeto Request como uma variável de contexto chamada request. Este objeto é extremamente útil pois contém todas as informações que o cliente incluiu na requisição HTTP.
A seguir temos a descrição dos principais atributos e métodos do objeto Request.
form – Um dicionário com todos os campos do formulário enviados com a solicitação.
args – Um dicionário com todos os argumentos passados na string de consulta da URL.
values – Um dicionário que combina os valores em form e args.
cookies – Um dicionário com todos os cookies incluídos no pedido.
headers – Um dicionário com todos os cabeçalhos HTTP incluídos na solicitação.
files – Um dicionário com todos os uploads de arquivos incluídos na solicitação.
get_data() – Retorna os dados armazenados em buffer do corpo da solicitação.
get_json() – Retorna um dicionário Python com o JSON analisado incluído no corpo da solicitação.
blueprint – O nome do blueprint do Flask que está processando a solicitação. Os projetos são apresentados no Capítulo 7.
endpoint – O nome do endpoint Flask que está processando a solicitação. O Flask usa o nome da função de visualização como o nome do ponto de extremidade para uma rota.
method – O método de solicitação HTTP, como GET ou POST.
scheme – O esquema de URL (http ou https).
is_secure() – Retorna True se a solicitação veio por meio de uma conexão segura (HTTPS).
host – O host definido na solicitação, incluindo o número da porta, se fornecido pelo cliente.
path – A parte do caminho do URL.
query_string – A parte da string de consulta da URL, como um valor binário bruto.
full_path – As partes do caminho e da string de consulta da URL.
url – A URL completa solicitada pelo cliente.
base_url – O mesmo que url, mas sem o componente de string de consulta.
remote_addr – O endereço IP do cliente.
environ – O dicionário de ambiente WSGI bruto para a solicitação.
Responses
Quando o Flask invoca uma view function, ele espera que o valor de retorno da função seja a resposta da requisição. Na maioria das vezes a resposta é um string(html, json etc) que é enviado de volta para o cliente. Entretanto, o protocolo HTTP exige mais do que um string como resposta a uma requisição. Uma parte importante da resposta é o status code, que o Flask configura por padrão como sendo 200, que indica que a requisição foi tratada com sucesso.
É possível fazer com que uma view function retorne um status diferente, como no exemplo abaixo, onde a função retorna um status 400(error).
@app.route('/')
def index():
return 'Bad Request
', 400
As responses de uma view function podem retornar, além do texto e do status code, um terceiro argumento, um dicionário de headers que são adicionados da resposta HTTP.
Ao invés de retornar um, dois ou três valores como uma tupla, as view functions tem a opção de retornar um objeto do tipo response.
A função make_response() recebe os mesmo três argumentos que uma view function pode retornar e cria um objeto do tipo response equivalente.
Algumas vezes é útil gerar um objeto response dentro da view function e então usar seus métodos para configurar a resposta. O exemplo a seguir cria um objeto response e configura um cookie dentro dele.
from flask import make_response
@app.route('/')
def index():
response = make_response('Esta resposta tem um cookie!
')
response.set_cookie('answer', '42')
return response
A seguir estão os principais atributos e métodos de um objeto response.
status_code – O código de status HTTP numérico.
headers – Um objeto semelhante a um dicionário com todos os cabeçalhos que serão enviados com a resposta.
set_cookie() – Adiciona um cookie à resposta.
delete_cookie() – Remove um cookie
content_length – O comprimento do corpo de resposta
content_type – O tipo de mídia do corpo da resposta
set_data() – Define o corpo da resposta como um valor de string ou bytes
get_data() – Obtém o corpo da resposta.