Eu passei um bom tempo usando print() para debugar código Python que estava rodando em containers por não saber como usar o debugger de maneira adequada (dã). Acho que descobri.

Como eu utilizo o PyCharm (e talvez você devesse também), esse tutorial não cobre VS Code ou pdb puro.

Sigamos.

Caso simples: Uma app boba

Digamos que você tem uma web app Flask super complexa:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    my_return = "Hello, World!"
    return my_return

if __name__ == '__main__':
    app.run(host="0.0.0.0")

Dockerfile:

FROM python:3.10
WORKDIR /
COPY . .
RUN pip3 install -r requirements.txt
CMD [ "python3", "./main.py"]

docker-compose.yml:

version: "3.5"
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
  • Vou utilizar um docker-compose.yml por ser útil para mapear portas e para adicionar outros serviços (ex.: bd), como veremos adiante.

Bom, se tentarmos debugar utilizando o interpretador que está aí no seu venv (pf esteja usando um venv), vai funcionar, porém, não estaríamos debugando a aplicação rodando no container e sim localmente. Temos que especificar um interpretador que esteja dentro do container que será debugado.

Add novo interpretador “Docker Compose”

  • Se preferir: menu > settings > project > python interpreter > add new interpreter

Especifique o caminho do seu docker-compose.yml. Depois, escolha o nome do que deseja debugar, especificando o mesmo nome que foi utilizado no docker-compose.yml. No nosso caso é app. Clique next.

  • Poderiam haver outros serviços como bancos de dados ou caches que não seriam alvo de debug

Por último, escolhemos o interpretador. System Interpreter > /usr/local/bin/python3, clique create.

  • Deixamos na opção System Interpreter pois não tem necessidade de usar um venv dentro do container.

Ok, interpretador criado!

O próximo passo é criar uma configuração de run/debug, para rodar nosso programa utilizando o interpretador que acabamos de criar. Láá em cima, do lado da baratinha:


Clique no “+” e crie um novo perfil “Python”

Escolha o interpretador que foi criado e especifique o entrypoint da sua aplicação. Nesse caso é o próprio main.py, com o tipo “script”. Clique Apply

  • Poderia ser um perfil “Flask”, mas para ser mais genérico vamos de “Python”

🤗Pronto🤗

Agora, coloque seus breakpoints e clique na baratinha para debugar.

Caso menos simples: Uma app boba + um banco de dados

Bom, digamos que existam outros serviços adjacentes à sua aplicação, como, por exemplo, um banco de dados.

docker-compose.yml:

version: "3.5"
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
  mongo:
    extends:
      file: mongo.yml
      service: mongo
    volumes:
      - mongo_data:/data/db

volumes:
  mongo_data:

mongo.yml:

version: "3.5"

services:
  mongo:
    image: mongo:5
    command: mongod --port 27017
    restart: always
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: password

Nesse caso, se seguirmos os passos da seção anterior, nosso banco de dados nunca será executado, pois o interpretador configurado só diz respeito à aplicação em si (app), e a configuração de run/debug também, e apenas executa nosso programa a partir de um entrypoint pré-estabelecido (main.py).

Criaremos então outra configuração de run/debug. Assim como antes, abra a aba de run/debug, e dessa vez, clique no “+” e escolha uma config do tipo “Docker Compose”.

Especifique o caminho do arquivo “docker-compose.yml”. Note, no campo “Services” está escrito: “Leave empty to run all services” (Deixe em branco para rodar todos os serviços).

Ou seja, podemos deixar o campo em branco para que essa config de run/debug rode os dois serviços (app e mongo), ou, especificar apenas o serviço mongo, uma vez que não podemos “debugar” a app utilizando essa config do tipo “Docker Compose”. Para debugar nossa app de fato, utilizando breakpoints e tudo mais, é necessário utilizar a config criada anteriormente que especifica o interpretador Dockerizado (criado lá no início) e o entrypoint (main.py nesse caso).

Porém, não é problema deixarmos essa nova config de run/debug iniciar os dois serviços e depois executarmos a config criada anteriormente para então debugar nossa aplicação. Por isso, pode deixar em branco, ou especificar apenas mongo na aba “Services”.

Ok, agora é só rodar (clicando na setinha do lado da barata) essa nova config para subir o mongo + app (ou só mongo), e depois rodar a config que criamos anteriormente para então debugar nossa aplicação. Lembre de usar a baratinha

  • A app acaba nem chamando o mongo… mas poderia.

Debugando testes com Pytest

Se você precisa debugar seus testes, crie uma nova config de run/debug do tipo “Pytest”. Escolha o interpretador criado anteriormente (aquele do container), e, se você estiver utilizando pytest-cov no seu projeto, no campo “Additional Arguments” coloque --no-cov. Pronto, agora pode colocar seus breakpoints nos testes e debugar feliz da vida.

Como estou dirigindo?

Se você tem alguma sugestão, dica, ou encontrou algum erro, pode deixar nos comentários abaixo. (:

bye bye