воскресенье, 24 мая 2009 г.

Отладка Django-приложений с помощью pdb

Когда я только начинал знакомство с Django/Python миром, для отладки я использовал что-то вроде

print var1, var2, var3
в нужном месте вида. Однако этот подход категорически неудобен, и я посмотрел в сторону pdb.


Модуль pdb — это интерактивный отладчик для программ написанных на Python. С помощью него можно отлаживать уже готовые программы. Заведем абсолютно бесполезную программу, и назовем ее test.py. Вот ее содержимое:
#!/usr/bin/python
import sys

if len(sys.argv) > 1:
arg = sys.argv[1]
arg = arg + ' for you'
else:
arg = 'Nothing for you'

print arg

Запустим ее:
$ python test.py
Nothing for you
$ python test.py Flowers
Flowers for you

Отлично. Пример очень простой, но все же попробуем запустить ее с использованием отладчика. Привожу лог работы, с моими комментариями:
$ python -m pdb test.py # запускаем test.py с инжектированием модуля pdb
> /home/n0uk/dev/workspace/gtest/test.py(2)()
-> import sys # PDB остановился тут и ждет нашего ввода
(Pdb) l # l или list — вывести исходный код файла, без аргументов выводит 11 строчек до выполняемой строки, и 11 после.
1 #!/usr/bin/python
2 -> import sys # Стрелочка показывает где мы.
3
4 if len(sys.argv) > 1:
5 arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) sys.argv # смотрим переменную sys.argv
*** NameError: name 'sys' is not defined # Она еще не определена.
(Pdb) n # выполняем текущую строку, и переходим к следующей
> /home/n0uk/dev/workspace/gtest/test.py(4)()
-> if len(sys.argv) > 1:
(Pdb) sys.argv
['test.py']
(Pdb) l # оглядываемся
1 #!/usr/bin/python
2 import sys
3
4 -> if len(sys.argv) > 1:
5 arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) sys.argv = ['test.py', 'Only'] # изменим sys.argv "на лету"
(Pdb) sys.argv
['test.py', 'Only']
(Pdb) n # переходим дальше
> /home/n0uk/dev/workspace/gtest/test.py(5)()
-> arg = sys.argv[1]
(Pdb) l
1 #!/usr/bin/python
2 import sys
3
4 if len(sys.argv) > 1:
5 -> arg = sys.argv[1]
6 arg = arg + ' for you'
7 else:
8 arg = 'Nothing for you'
9
10 print arg
[EOF]
(Pdb) n
> /home/n0uk/dev/workspace/gtest/test.py(6)()
-> arg = arg + ' for you'
(Pdb) c # продолжаем выполнение в обычном режиме
Only for you # вот оно наше Only, которое мы подставили в аргументы.

Как видим, мы можем получать состояние переменных в любой момент времени, изменять их во время выполнения, и видеть конечный результат.

Теперь воспользуемся отладчиком в контексте Django. Напишем чего-нибудь несуразное, например поиск по модели пользователей:
from django.shortcuts import render_to_response
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.db.models import Q

def search(query):
""" Search user by email, nickname, last_name, first_name. """
# search in this fields
search_in = 'first_name', 'last_name', 'username', 'email'
filters = None
# make filters for fields
for field in search_in:
kw = {'%s__icontains' % field: query}
filter = Q(**kw)
if filters:
filters = filters | filter
else:
filters = filter
# return qs
return User.objects.filter(filters)

def search_user(request):
""" List users matching query. """
# check query
try:
query = request.GET['query']
except KeyError:
return HttpResponse('Need a `query` parameter.')
# search users
users = search(query.strip())
if users:
response = '%s users matching query.
' % users.count()
response += ','.join([user.username for user in users])
else:
response = '0 users matching query.'
return HttpResponse(response)

Замапим этот урл в urls.py:
    ...
(r'^test/', 'testapp.views.search_user'),
...

Попробуем открыть в браузере и протестировать:



Теперь использование отладчика. Впишем в наш вид такие строки:
def search_user(request):
""" List users matching query. """
import pdb
pdb.set_trace()
...

Запустим dev сервер:
$ ./manage.py runserver
Validating models...
0 errors found

Django version 1.0.2 final, using settings 'gtest.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

И откроем страницу в браузере — http://localhost:8000/test/
Страница не откроется, но в консоли вывалится интерактивный шелл:
> /home/n0uk/dev/workspace/gtest/testapp/views.py(27)search_user()
-> try:
(Pdb)

Где мы можем делать, все, что угодно:
(Pdb) l
22 def search_user(request):
23 """ List users matching query. """
24 import pdb
25 pdb.set_trace()
26 # check query
27 -> try:
28 query = request.GET['query']
29 except KeyError:
30 return HttpResponse('Need a `query` parameter.')
31 # search users
32 users = search(query.strip())
(Pdb) request.GET

(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) n
MultiValueDictKeyError: MultiVal...t: {}>",)
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) l
23 """ List users matching query. """
24 import pdb
25 pdb.set_trace()
26 # check query
27 try:
28 -> query = request.GET['query']
29 except KeyError:
30 return HttpResponse('Need a `query` parameter.')
31 # search users
32 users = search(query.strip())
33 if users:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(29)search_user()
-> except KeyError:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(30)search_user()
-> return HttpResponse('Need a `query` parameter.')
(Pdb) c
[24/May/2009 05:54:12] "GET /test/ HTTP/1.1" 200 25

Ответ ушел в браузер. Посмотрим, что будет если передать параметр:
> /home/n0uk/dev/workspace/gtest/testapp/views.py(27)search_user()
-> try:
(Pdb) request.GET

(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(28)search_user()
-> query = request.GET['query']
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(32)search_user()
-> users = search(query.strip())
(Pdb) s
--Call--
> /home/n0uk/dev/workspace/gtest/testapp/views.py(6)search()
-> def search(query):
(Pdb) l
1 from django.shortcuts import render_to_response
2 from django.http import HttpResponse
3 from django.contrib.auth.models import User
4 from django.db.models import Q
5
6 -> def search(query):
7 """ Search user by email, nickname, last_name, first_name. """
8 # search in this fields
9 search_in = 'first_name', 'last_name', 'username', 'email'
10 filters = None
11 # make filters for fields
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(9)search()
-> search_in = 'first_name', 'last_name', 'username', 'email'
(Pdb) search_in
*** NameError: name 'search_in' is not defined
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(10)search()
-> filters = None
(Pdb) search_in
('first_name', 'last_name', 'username', 'email')
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(12)search()
-> for field in search_in:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(13)search()
-> kw = {'%s__icontains' % field: query}
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(14)search()
-> filter = Q(**kw)
(Pdb) kw
{'first_name__icontains': u'n0uk'}
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(15)search()
-> if filters:
(Pdb) n
> /home/n0uk/dev/workspace/gtest/testapp/views.py(18)search()
-> filters = filter
(Pdb) l
13 kw = {'%s__icontains' % field: query}
14 filter = Q(**kw)
15 if filters:
16 filters = filters | filter
17 else:
18 -> filters = filter
19 # return qs
20 return User.objects.filter(filters)
21
22 def search_user(request):
23 """ List users matching query. """
(Pdb) c
[24/May/2009 05:55:55] "GET /test/?query=n0uk HTTP/1.1" 200 34

Мы вошли в функцию search при выполнении используя команду s (step).
Таким образом мы можем полностью скакать по нашему коду, и коду Django, и смотреть, "где же эта чертова ошибка, которую мы ищем уже вторые сутки".
Документация к модулю pdb
Также для отладки может быть полезен Django-debug-toolbar, про который можно почитать тут.

Комментариев нет:

Отправить комментарий