Using the Python debugger to trace method execution

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.

Getting ready

We will be reusing the export_stock_level() method shown in the two previous recipes. Be sure to have a copy at hand.

How to do it…

In order to trace the execution of export_stock_level() with pdb, follow the following steps:

  1. Edit the code of the method, and insert the line highlighted here:
    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')
  2. Run the method. We will use the Odoo shell, as explained in the Use the Odoo shell to interactively call methods recipe previously:
    $ 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)
    
  3. At the (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,)
  4. Enter the 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))
  5. Enter the 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:
    
  6. Use the 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'
    
  7. Change the value of fname to point to the /tmp directory:
    (Pdb) !fname = '/tmp/stock_level.txt'
    
  8. Use the 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'):
    
  9. Use the cont (shortcut c) command to resume execution of the program:
    (Pdb) c
    >>>
    

How it works…

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:

  • Reduce the logging level to avoid having too many log lines polluting the output of the debugger. Starting at the 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).
  • Run the server with --workers=0 to avoid any multiprocessing issues that could cause the same break point to be reached twice in two different processes.
  • Run the server with --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 points
    • line_number: This breaks at the specified line in the current file
    • filename: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 function
  • tbreak <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.

There's more…

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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.228