Sometimes, application logs are not enough to figure out what is going wrong. Fortunately, we have the Python debugger available to us. This recipe shows how to insert a break point in a method and trace the execution by hand.
We will be reusing the export_stock_level()
method shown in the two previous recipes. Be sure to have a copy at hand.
In order to trace the execution of export_stock_level()
with pdb
, follow the following steps:
def export_stock_level(self, stock_location):
import pdb; pdb.set_trace()
products = self.with_context(
location=stock_location.id
).search([])
fname = join(EXPORTS_DIR, 'stock_level.txt')
try:
with open(fname, 'w') as fobj:
for prod in products.filtered('qty_available'):
fobj.write('%s %f
' % (prod.name, prod.qty_available))
except IOError:
raise exceptions.UserError('unable to save file')
$ odoo.py shell -c project.cfg --log-level=error [...] >>> product = env['product.product'] >>> location_stock = env.ref('product.stock_location_stock') >>> product.export_stock_level(location_stock) > /home/cookbook/stock_level/models.py(16)export_stock_level() -> products = self.with_context( (Pdb)
Pdb
) prompt, issue the args
(shortcut a
) command to get the values of the arguments passed to the method:(Pdb) a
self = product.product() stock_location = stock.location(12,)
list
command to check where in the code you are standing:(Pdb) list
def export_stock_level(self, stock_location): import pdb; pdb.set_trace() products = self.with_context( location=stock_location.id ).search([]) fname = join(EXPORTS_DIR, 'stock_level.txt') try: with open(fname, 'w') as fobj: for prod in products.filtered('qty_available'): fobj.write('%s %f ' % (prod.name, prod.qty_available))
next
command three times to walk through the first lines of the method. You may also use n
, which is a shortcut:(Pdb) next > /home/cookbook/stock_level/models.py(17)export_stock_level() -> location=stock_location.id (Pdb) n > /home/cookbook/stock_level/models.py(18)export_stock_level() -> ).search([]) (Pdb) n > /home/cookbook/stock_level/models.py(19)export_stock_level() -> fname = join(EXPORTS_DIR, 'stock_level.txt') (Pdb) n > /home/cookbook/stock_level/models.py(20)export_stock_level() -> try:
p
command to display the values of the products
and fname
variables:(Pdb) p products product.product(67, 14, 12, 6, 7, 8, 17, 18, 13, 55, 15, 10, 11, 64, 31, 23, 42, 30, 29, 53, 56, 63, 62, 43, 61, 35, 51, 26, 24, 25, 39, 40, 45, 34, 32, 33, 57, 65, 28, 27, 38, 16, 19, 49, 5, 36, 37, 44, 21, 22, 20, 59, 52, 66, 58, 54, 60, 46, 41, 47, 48, 50, 3, 2, 1, 4) (Pdb) p fname '/srv/exports/stock_level.txt'
fname
to point to the /tmp
directory:(Pdb) !fname = '/tmp/stock_level.txt'
return
(shortcut r
) command to execute the current function to its end:(Pdb) return --Return-- > /home/afayolle/work/OdooCookbook/project1/stock_level/models.py(22)export_stock_level()->None -> for product in products.filtered('qty_available'):
cont
(shortcut c
) command to resume execution of the program:(Pdb) c >>>
In step 1, we hardcode a break point in the source code of the method by calling the set_trace()
method of the pdb
module from the Python standard library. When this method is executed, the normal flow of the program stops, and you get a (Pdb
) prompt in which you can enter pdb
commands.
Step 2 calls the stock_level_export()
method using the shell mode. It is also possible to restart the server normally and to use the web interface to generate a call to the method you need to trace by clicking on the appropriate elements of the user interface.
When you need to manually step through some code using the Python debugger, here are a few tips that will make your life easier:
ERROR
level is generally fine. You may want to enable some specific loggers with a higher verbosity, which you can do by using the --log-handler
command line option (see the preceding recipe, Produce server logs to help debug methods).--workers=0
to avoid any multiprocessing issues that could cause the same break point to be reached twice in two different processes.--max-cron-threads=0
to disable the processing of ir.cron
periodic tasks, which otherwise may trigger while you are stepping through the method, producing unwanted logs and side effects.Steps 3 to 8 use several pdb
commands to step through the execution of the method. Here is a summary of the main commands of pdb
. Most of them are also available using the first letter as a shortcut. We indicate this by having the optional letters between parenthesis:
h(elp)
: This displays help on the pdb
commands.a(rgs)
: This shows the value of the arguments of the current function/methods.l(ist)
: This displays the source code being executed by chunks of 11 lines, initially centered on the current line. Successive calls will move further in the source code file. Optionally, you can pass two integers, start
and end
, specifying the region to display.p
: This prints a variable.pp
: This pretty-prints a variable (useful with lists and dictionaries).w(here)
: This shows the call stack, with the current line at the bottom and the Python interpreter at the top.u(p)
: This moves up one level in the call stack.d(own)
: This moves down one level in the call stack.n(ext)
: This executes the current line of code and then stops.s(tep)
: This is to step inside the execution of a method call.r(eturn)
: This resumes the execution of the current method until it returns.c(ont)
: This resumes the execution of the program until the next break point is hit.b(reak) <args>
: This creates a new break point, and displays its identifier. args
can be one of the following:<empty>
: This lists all break pointsline_number
: This breaks at the specified line in the current filefilename:line_number
: This breaks at the specified line of the specified file (which is searched for in the directories of sys.path
)function_name
: This breaks at the first line of the specified functiontbreak <args>
: This is similar to break
but the break point will be canceled after it has been reached, so successive execution of the line won't trigger it twice.disable bp_id
: This disables a breakpoint by ID.enable bl_id
: This enables a disabled breakpoint by ID.j(ump) lineno
: The next line to execute will be the one specified. This can be used to rerun or to skip some lines.(!) statement
: This executes a Python statement. The !
character can be omitted if the command does not look like a pdb
command; for instance, you need it if you want to set the value of a variable named a
, because a
is the shortcut for the args
command.In the recipe, we inserted a pdb.set_trace()
statement to break into pdb
. We can also start pdb
directly from within the Odoo shell, which is very useful when you cannot easily modify the code of the project by using pdb.runcall()
. This function takes a method as the first argument and the arguments to pass to the function as the next arguments. So inside the Odoo shell, you do this:
>>> import pdb >>> product = env['product.product'] >>> location_stock = env.ref('stock.stock_location_stock') >>> pdb.runcall(product.export_stock_level, location_stock) > /home/cookbook/odoo/openerp/api.py(235)wrapper() -> if '_ids' in self.__dict__: (Pdb)
Notice that in this case you do not end up in the export_stock_level()
method but in the wrapper()
function from openerp.api
. This is part of the implementation of the @api.model
decorator we set on expert_stock_level()
and you will encounter lots of these in your pdb
sessions when stepping inside method calls. You can either manually walk through the decorators or add breakpoints inside the source code of the methods.
If you choose to step manually, you will need to use next until you the line calling new_api()
and step inside that call:
(Pdb) n > /home/cookbook/odoo/openerp/api.py(236)wrapper() -> return new_api(self, *args, **kwargs) (Pdb) s --Call-- > /home/cookbook/stock_level/models.py(13)export_stock_level() -> @api.model (Pdb) n > /home/cookbook/stock_level/models.py(15)export_stock_level() -> products = self.with_context(
If you'd rather add a breakpoint, check the file name and line number and use the break command like this:
(Pdb) b /home/cookbook/stock_level/models.py:15 Breakpoint 1 at /home/cookbook/stock_level/models.py:15 (Pdb) cont > /home/cookbook/stock_level/models.py(15)export_stock_level() -> products = self.with_context( (Pdb)
Notice that we had to use the full path to the file because it is not in a directory inside sys.path
.
In this recipe, we focused on the Python debugger from the Python standard library, pdb
. It is very useful to know this tool because it is guaranteed to be available on any Python distribution. There are other Python debuggers available, such as ipdb
(https://pypi.python.org/pypi/ipdb) and pudb
(https://pypi.python.org/pypi/pudb), which can be used as drop-in replacements for pdb
. They share the same API and most commands seen in this recipe are unchanged. And, of course, if you develop for Odoo using a Python IDE, you certainly have access to a debugger integrated with it.
pdb
, refer to https://docs.python.org/2.7/library/pdb.html18.191.176.228