Using the line-profiler TOML configuration
------------------------------------------

This tutorial walks the user through setting up a toy Python project and then
interacting with it via the new line-profiler TOML configuration.

First, we need to setup a small project, for which we will use ``uv``. We will
also use the ``tomlkit`` package to edit the config file programatically. If
you don't have these installed, first run:

.. code:: bash

   pip install uv tomlkit


Next, we are going to setup a small package for this demonstration.

.. code:: bash

   TEMP_DIR=$(mktemp -d --suffix=demo_pkg)
   mkdir -p $TEMP_DIR
   cd $TEMP_DIR

   uv init --lib --name demo_pkg

   # helper to prevent indentation errors
   codeblock(){
       echo "$1" | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip('\n'))"
   }

   codeblock "
       import time
       from demo_pkg.utils import leq
       from demo_pkg import utils

       def fib(n):
           if leq(n, 1):
               return n
           part1 = fib(n - 1)
           part2 = fib(n - 2)
           result = utils.add(part1, part2)
           return result

       def sleep_loop(n):
           for _ in range(n):
               time.sleep(0.01)
   " > src/demo_pkg/core.py

   codeblock "
       def leq(a, b):
           return a <= b

       def add(a, b):
           return a + b
   " > src/demo_pkg/utils.py

   codeblock "
       from demo_pkg import core
       import uuid

       def main():
           run_uuid = uuid.uuid4()
           print('The UUID of this run is', run_uuid)
           print('compute fib 10')
           result = core.fib(10)
           print('result', result)
           print('sleeping 5')
           core.sleep_loop(5)
           print('done')

       if __name__ == '__main__':
           main()
   " > src/demo_pkg/__main__.py

   # Run `uv pip install -e .` to install the project locally:
   uv pip install -e .


Test that the main entrypoint works.

.. code:: bash

   python -m demo_pkg


Running kernprof with a main script that uses your package behaves as in 4.x in that no defaults are modified.

.. code:: bash

    kernprof -m demo_pkg


However, you can modify pyproject.toml to specify new defaults. After doing
this, running kernprof will use defaults specified in your pyproject.toml (You
may also pass ``--config`` to tell kernprof to use a different file to load the
default config).

.. code:: bash

   # Edit the `pyproject.toml` file to modify default behavior
   update_pyproject_toml(){
       python -c "if 1:
           import pathlib
           import tomllib
           import tomlkit
           import sys
           config_path = pathlib.Path('pyproject.toml')
           config = tomllib.loads(config_path.read_text())

           # Add in new values
           from textwrap import dedent
           new_text = dedent(sys.argv[1])

           new_parts = tomllib.loads(new_text)
           config.update(new_parts)

           new_text = tomlkit.dumps(config)
           config_path.write_text(new_text)
       " "$1"
   }

   update_pyproject_toml "
       # New Config
       [tool.line_profiler.kernprof]
       line-by-line = true
       rich = true
       verbose = true
       skip-zero = true
       prof-mod = ['demo_pkg']
       "

   # Now, running kernprof uses the new defaults
   kernprof -m demo_pkg


You will now see how long each function took, and what the line-by line breakdown is

.. code::

  # line-by-line breakdown omitted here

  0.05 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/__main__.py:4 - main
  0.00 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/core.py:5 - fib
  0.05 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/core.py:13 - sleep_loop
  0.00 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/utils.py:1 - leq
  0.00 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/utils.py:4 - add


Note that by specifying ``prof-mod``, every function within the package is
automatically profiled without any need for the ``@profile`` decorator.

It is worth noting, there is no requirement that the module you are profiling
is part of your package. You can specify any module name as part of
``prof-mod``. For example, lets profile the stdlib uuid module.


.. code:: bash

   update_pyproject_toml "
       # New Config
       [tool.line_profiler.kernprof]
       line-by-line = true
       rich = true
       verbose = 0
       skip-zero = true
       prof-mod = ['uuid']
       "

   # Now, running kernprof uses the new defaults
   kernprof -m demo_pkg
   python -m line_profiler -rmtz demo_pkg.lprof


This results in only showing calls in the uuid package:

.. code::

  # line-by-line breakdown omitted here

  0.00 seconds - .pyenv/versions/3.13.2/lib/python3.13/uuid.py:142 - UUID.__init__
  0.00 seconds - .pyenv/versions/3.13.2/lib/python3.13/uuid.py:283 - UUID.__str__
  0.00 seconds - .pyenv/versions/3.13.2/lib/python3.13/uuid.py:277 - UUID.__repr__
  0.00 seconds - .pyenv/versions/3.13.2/lib/python3.13/uuid.py:710 - uuid4


You can list exact functions to profile as long as they are addressable by
dotted names. The above only profiles the ``fib`` function in our package:

.. code:: bash

   update_pyproject_toml "
       # New Config
       [tool.line_profiler.kernprof]
       line-by-line = true
       rich = true
       verbose = 0
       skip-zero = true
       prof-mod = ['demo_pkg.core.fib']
       "

   # Now, running kernprof uses the new defaults
   kernprof -m demo_pkg
   python -m line_profiler -rmtz demo_pkg.lprof


The output is:

.. code::

   Line #      Hits         Time  Per Hit   % Time  Line Contents
   ==============================================================
        5                                           def fib(n):
        6       177        145.1      0.8     42.5      if leq(n, 1):
        7        89         29.7      0.3      8.7          return n
        8        88         29.1      0.3      8.5      part1 = fib(n - 1)
        9        88         27.7      0.3      8.1      part2 = fib(n - 2)
       10        88         78.0      0.9     22.8      result = utils.add(part1, part2)
       11        88         32.2      0.4      9.4      return result


     0.00 seconds - /tmp/tmp.vKpODQr6wndemo_pkg/src/demo_pkg/core.py:5 - fib
