This page looks best with JavaScript enabled

Debugging Python/Django Code

 ·  🎃 kr0m

In this article, we will learn how to debug Python code in our Django project, which will allow us to inspect variable values, execute code step by step, and many other functionalities.

First of all, let’s activate our venv:

cd /home/kr0m/rxWod/
source bin/activate

We install ipdb :

cd rxWodProject
pip install ipdb

In our views, we must import ipdb and assign breakpoints with ipdb.set_trace():

vi rxWod/views.py

from django.shortcuts import get_object_or_404, get_list_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect
from .models import Routine, Exercise
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.http import JsonResponse
import math
import json
import random
import ipdb

max_routines_per_user = 25
max_routine_exercises = 10
max_rounds = 4

max_exercise_reps = 12

@login_required()
@require_http_methods(["GET"])
def index(request):
    #print('username: %s' % request.user.username)

    clear_empty_routines(request.user)

    routines = Routine.objects.filter(user=request.user)
    #print('routines: %s' % routines)
    ipdb.set_trace()

    categories = Exercise._meta.get_field('category_id').choices
    categories = [category[1] for category in categories]
    #print('categories: %s' % categories)

    string_categories = ','.join(map(str, categories))
    #print('string_categories: %s' % string_categories)

    context = {
        'routines': routines,
        'categories': string_categories,
    }
    return render(request, 'rxWod/index.html', context)

We start our server:

python manage.py runserver 0.0.0.0:8000

Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

And when we load the index view from the browser, the following output will appear in the console where we have the web server active:

/usr/home/kr0m/rxWod/lib/python3.7/site-packages/IPython/core/history.py:226: UserWarning: IPython History requires SQLite, your history will not be saved
  warn("IPython History requires SQLite, your history will not be saved")
> /usr/home/kr0m/rxWod/rxWodProject/rxWod/views.py(31)index()
     30 
---> 31     categories = Exercise._meta.get_field('category_id').choices
     32     categories = [category[1] for category in categories]

We can see that the code execution has stopped at line 31 as we wanted. To see a little more in context the code area, we can execute the ll command:

ipdb> ll

     20 @login_required()
     21 @require_http_methods(["GET"])
     22 def index(request):
     23     #print('username: %s' % request.user.username)
     24 
     25     clear_empty_routines(request.user)
     26 
     27     routines = Routine.objects.filter(user=request.user)
     28     #print('routines: %s' % routines)
     29     ipdb.set_trace()
     30 
---> 31     categories = Exercise._meta.get_field('category_id').choices
     32     categories = [category[1] for category in categories]
     33     #print('categories: %s' % categories)
     34 
     35     string_categories = ','.join(map(str, categories))
     36     #print('string_categories: %s' % string_categories)
     37 
     38     context = {
     39         'routines': routines,
     40         'categories': string_categories,
     41     }
     42     return render(request, 'rxWod/index.html', context)
     43 

ipdb> 

This is a shell in the debugger itself, where we can inspect variable values, execute code step by step, among many other functionalities.

We advance one step with n, but without leaving the part of the code where we are, that is, we do not follow the calls to other functions:

ipdb> n

> /usr/home/kr0m/rxWod/rxWodProject/rxWod/views.py(32)index()
     31     categories = Exercise._meta.get_field('category_id').choices
---> 32     categories = [category[1] for category in categories]
     33     #print('categories: %s' % categories)

If we want to follow the calls, we should advance with s for step.
If we want to repeat the last command without having to type it again, we just have to press ENTER, just like in GDB :)

We can see the value of local variables:

ipdb> locals()

{'request': <WSGIRequest: GET '/'>, 'routines': <QuerySet [<Routine: 2021-08-18 07:18:26.887464+00:00>, <Routine: 2021-08-21 09:49:17.355769+00:00>]>, 'categories': [(0, 'Hombros'), (1, 'Espalda'), (2, 'Biceps'), (3, 'Triceps'), (4, 'Pecho'), (5, 'Core'), (6, 'Glúteos'), (7, 'Cuadriceps'), (8, 'Isquiotibial'), (9, 'Cardio'), (10, 'Lumbar'), (11, 'Agarre')]}

We can print the value of a specific variable:

ipdb> categories

[(0, 'Hombros'), (1, 'Espalda'), (2, 'Biceps'), (3, 'Triceps'), (4, 'Pecho'), (5, 'Core'), (6, 'Glúteos'), (7, 'Cuadriceps'), (8, 'Isquiotibial'), (9, 'Cardio'), (10, 'Lumbar'), (11, 'Agarre')]

A more comfortable way to view variables is through prettyPrint:

ipdb> pp categories

[(0, 'Hombros'),
 (1, 'Espalda'),
 (2, 'Biceps'),
 (3, 'Triceps'),
 (4, 'Pecho'),
 (5, 'Core'),
 (6, 'Glúteos'),
 (7, 'Cuadriceps'),
 (8, 'Isquiotibial'),
 (9, 'Cardio'),
 (10, 'Lumbar'),
 (11, 'Agarre')]

The debugger makes our life easier to the point that it allows us to tabulate so that the options of the variable values or the methods of the objects appear:


As an example, we consult the value of the received headers:

ipdb> request.headers

{'Content-Length': '', 'Content-Type': 'text/plain', 'Host': 'localhost:8000', 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Sec-Ch-Ua': '"Chromium";v="91", " Not;A Brand";v="99"', 'Sec-Ch-Ua-Mobile': '?0', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (X11; FreeBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Dest': 'document', 'Referer': 'http://localhost:8000/', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'es', 'Cookie': 'csrftoken=Yl6erIV4TbC3mRTq1J8wO2UH7d3x474LCwmritEdrTl4DITUdbK18EYsTtgqsjih; sessionid=w3e7z8jxg4yurjl69txju69tobb08vdm'}

We can even execute Python code on objects such as counting the number of routines obtained from the database:

ipdb> routines.count()

2

Finally, we press c to continue and the browser will display the website.

In my tests, I have only detected a small inconvenience, and that is that from the debugger shell, breakpoints can be assigned on the fly, but this fails in Django projects for some reason. It allows assigning the breakpoint, but when continuing, it does not stop at that breakpoint.

On the other hand, testing it in a traditional Python script works without problems. ipdb also serves to debug traditional Python scripts.

As a solution, several breakpoints can be assigned by code by executing ipdb.set_trace() at each point.

This debugging method is very useful since we can interactively see which parts of the code are executing, if it enters any if it shouldn’t, if it gets stuck in any for loop forever, if it is making an external request to an API that is not working at that time, or any other malfunction.

As a final note, I leave a link to a website with a summary sheet of ipdb commands:
https://wangchuan.github.io/coding/2017/07/12/ipdb-cheat-sheet.html

If you liked the article, you can treat me to a RedBull here