avatarLynn Kwong

Summary

This context provides a comprehensive guide on how to debug Python scripts and API code, both in the console and in Visual Studio Code (VS Code).

Abstract

The context begins by emphasizing the importance of debugging in Python programming, whether for data science or software development. It introduces the built-in Python debugger (pdb) and demonstrates how to use it to debug Python scripts in the console. The article then transitions to debugging in VS Code, explaining how to create a configuration file and use the Python extension for debugging. The latter part of the context focuses on debugging API code, discussing the use of remote Python debugger (rpdb) and debugpy for debugging API code in the console and in VS Code, respectively. The context also covers debugging API code running in a Docker container.

Bullet points

  • Debugging is crucial in Python programming, whether for data science or software development.
  • The built-in Python debugger (pdb) can be used to debug Python scripts in the console.
  • VS Code provides powerful utilities for code debugging, including the Python extension.
  • Remote Python debugger (rpdb) and debugpy can be used to debug API code in the console and in VS Code, respectively.
  • The context also covers debugging API code running in a Docker container.

How to debug Python scripts and API code in the console and in VS Code

Learn different ways to debug your Python code

Debugging is an important step in Python programming, whether you are a Python data scientist or software developer. It can be fairly straightforward to debug simple Python scripts. However, it is more advanced to debug Python API code, especially with VS Code. In this article, the most common ways to debug Python scripts and API code are introduced, both in the console and in VS Code. You shall then be fairly clear about which tools to use and how to use them in your practical work.

Image by OpenClipart-Vectors on Pixabay

How to debug Python scripts with pdb in the console?

In the repo cloned, there is a simple script called python_script.py, where the command line arguments are simply echoed:

To debug a script, the most “naive” way is to use print wherever necessary. This, however, is not convenient. A much better way is to use the pdb module. pdb is the built-in Python debugger that can be used to debug Python programs interactively.

To use pdb for debugging, we need to set some breakpoints in the code. A classical way is to use the following snippet:

import pdb; pdb.set_trace()

You can add breakpoints anywhere you like in the code. Normally you would put the breakpoints at the places where you think there may be bugs. Then you can run the script to trigger the breakpoints. For this script, the command is:

$ python python_script.py 1 2 3 --even --level WARNING

Note that if you want to open a terminal inside VS Code and run the script there, you can do so by clicking “Terminal” => “New Terminal”.

The output is like this:

arg = 1
arg = 2
arg = 3
key = even, value = True
key = level, value = WARNING

With the breakpoints, you can check the code logic and data flow, as well as the values for the variables. Checking the values of variables is the basic way to debug.

How to debug Python scripts in VS Code?

If you prefer to debug with a user interface, VS code is a good choice, which provides powerful utilities for code debugging. Note that you should install the Python extension for VS Code if you haven’t done so yet.

We should create a configuration file, called launch.json, before we can debug our script. Click the “Run and Debug” button on the left sidebar, and click on the “create a launch.json file” link:

With the guide, choose “Python” => “Python File”, and the launch.json file will be created for you automatically. It is located in the .vscode folder in your workspace, with the content like this (the name is adjusted):

The meaning of the options are:

  • name — The name of the configuration. It is shown in the dropdown of the Debug panel, as we will see soon. Therefore, it is better to give each configuration a meaningful name.
  • type — The type of debugger to use. For Python code, the type should be python. If you see some warning saying that the debugger is not available, make sure you have upgraded your VS Code to the latest version and have had the Python extension installed.
  • request —The mode to start the debugger. It can have two values, namely launch and attach. With the launch request, the script is launched/run directly. With the attach request, we attach the debugger to a program that is already running. We will use the attach request when we debug the API code.
  • program —The startup file for the debugger. The default value is ${file} which represents the currently active file in the editor. You can check the VS Code variables in this link.
  • console — It specifies where the debugging output should be displayed. The default value is integratedTerminal which means the VS Code integrated terminal.

However, with the default settings, we cannot run and debug our script because it requires some command-line arguments, which can be specified with the args option:

Now we can start to debug our script in VS Code. Note that you must have the python_script.py file opened and make it the active window in VS Code in order to debug it.

You can then see nice debugging results showing up. You can set breakpoints wherever you want. When you click the “Continue” button, the script will stop at the breakpoints and you can check the values of the variables.

How to debug API code in the console?

We have finished the easy part. Now let’s start the more advanced part.

If you are a Python API developer, you cannot debug the API code with the methods shown above because normally we cannot debug the API code locally, but need to do it remotely. Remotely means the API server is run in a machine or container somewhere, and we need to debug the code from somewhere else.

In the repo for this article, there is a folder called fastapi_web, which contains the setting files and API code for this example.

In order to run the docker-compose commands more conveniently, you need to change the working directory to fastapi_web. In VS Code, click “File” => “Open Folder …” => Choose “fastapi_web” to open it and use it as the working directory.

The API code in this example is written with the FastAPI framework, which is gaining great popularity these days. However, the debugging methods apply to all the API frameworks. You can test the debugging methods with your own API code written with different frameworks.

To run FastAPI code and debug the API code locally, we need to create a virtual environment and install the libraries specified in the requiremnts.txt file.

$ python -m venv .venv
$ source .venv/bin/activate
$ cat app/requirements.txt
fastapi==0.70.1
uvicorn==0.15.0
pydantic==1.8.2
rpdb==0.1.6
debugpy==1.5.1
$ pip install -r app/requirements.txt
  • fastapi — The library for the FastAPI framework.
  • unicorn — The tool to spin up a FastAPI server.
  • pydantic — The library for validating and converting data in FastAPI.
  • rpdb — A remote Python debugger based on the pdb module introduced above. Since it is just a wrapper for pdb, the debugging commands are the same as pdb.
  • debugpy — A tool to debug remote code in VS Code.

If you have problems creating a virtual environment and installing the packages with venv and pip, please have a look at this article. Alternatively, you can jump to the later part of this article to use docker-compose to start a FastAPI server (Don’t skip the following part for the introduction of the API code though).

The API code is very simple. It just has one endpoint, which returns the path and query parameters:

In FastAPI, an API endpoint is defined with a path operation decorator and a path operation function.

  • @app.get()— The path operation decorator. It specifies that the operation is the HTTP GET method.
  • response_model — Specifies that the response will be validated by the given Pydantic model.
  • async def echo() — The path operation function. You can define a function with or without async for regular cases. However, you do need to use the async keyword if there are some asynchronous actions. For example when you want to await something. The function name can be any valid name.
  • For the parameters of the path operation function, you can omit the Path and Query parts and let FastAPI figure out which is a path parameter and which is a query parameter. However, I think it’s better to make it as explicit as possible in most cases.

You can start a FastAPI server locally in two ways:

$ python app/main.py
# Or
$ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Note that if port 8000 is in use, you can use the following command to find which process is using it and kill the corresponding process if possible. Otherwise, just use a different port:

$ sudo lsof -i -P -n | grep LISTEN | grep 8000

Then we can open a new terminal and use curl to call the API. If you prefer to use a UI for testing APIs, you can use Postman.

$ curl localhost:8000/echo/mypath?query=myquery
{"path":"mypath","query":"myquery"}

I have been using the Falcon framework for API development because my group has been using it for the legacy code. However, we have found FastAPI really cool, especially the use of typing systems, Pydantic models, and automatic API document generation. We are switching to FastAPI for all new projects. There will be some dedicated articles for creating APIs with the FastAPI framework soon.

Now let’s finally start the journey to debug API code.

To debug the API code, we need to do it “remotely” because the server is already running somewhere, and we need to debug the code in a different place. Here, in a new terminal.

In file app/main.py, add the following code for debugging:

In the console where you ran the curl command, run that command again. You will see it is blocked there, indicating the program is stopped for debugging.

$ curl localhost:8000/echo/mypath?query=myquery
# The code is blocked for debugging.

Now, open another new terminal, and run the following command to get into the debugging environment:

$ telnet 127.0.0.1 4444

You are now inside the debugging environment of rpdb and can use all the commands for pdb for debugging Python scripts. Check the cheat sheet when you forget some commands.

How to debug API code in VS Code?

Sometimes it can be more convenient to debug the API code with a user interface. To debug the FastAPI code, we can also use the launch request, which means running the FastAPI server directly inside VS Code.

Click “Run” => “Add Configuration” to add a new configuration for launching a FastAPI server.

In the guide of VS Code, choose “Python” => “FastAPI” => Keep “main.py” as the application and press Enter. A new configuration will be created for you. Update it as follows to make it work for our specific example:

Give it a descriptive name so we can choose the correct one in the Debug window. Besides, we need to specify the right path to the application and the right port to use.

Before we can debug in VS Code, we need to stop the original FastAPI server started previously. Press CTRL + C to stop it. If you can’t stop it. Press CTRL + Z to hang it and then use jobs and kill to kill it:

$ jobs
[1]+  Killed                  python app/main.py
$ kill -9 %1

Now we can debug the API code in VS Code. First set some breakpoints in the code, then choose a configuration and start the debugger:

We then need to call the API to trigger the endpoint:

$ curl localhost:8000/echo/mypath?query=myquery
# The code is blocked for debugging.

And in VS Code you can see the breakpoint is triggered and the variables are shown in the Debug window:

You can now use the fancy debugging tools in VS Code to debug your API code.

How to debug API code running in a Docker container?

Technically, in the example above, the API code is not run remotely. They are still running on the same machine where the debugger is run, just in different terminals. In practical cases, it is more common to run the API code in Docker containers (different virtual machines) because there can be many dependencies (services) for it, such as database connection, caching, and logging services.

Let’s first set up a simple Dockerfile and docker-compose.yaml file for our API code:

The Dockerfile is simple, we just need to copy the application folder and install the libraries:

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-alpine3.14
COPY  ./app /app
WORKDIR /app
RUN pip install --upgrade pip && \
    pip install -r requirements.txt

In the docker-compose.yaml file, we need to build the image, mount the volume for the application code, specify the entry point command, and expose the ports for debugging. Port 8000 is for accessing the API, 4444 for debuging with rpdb, and 5678 for debugging with debugpy in VS Code.

Importantly, we need to specify the host as --host 0.0.0.0, otherwise, we cannot access the API from outside the container.

Again you need to stop the existing FastAPI servers to avoid port conflicts. Now let’s build the image and bring up the service for our API:

# Make sure you are in fastapi_web folder.
docker-compose build
docker-compose up

We can now access the API (in a new terminal) as before:

$ curl localhost:8000/echo/mypath?query=myquery
{"path":"mypath","query":"myquery"}

The debugging procedure with rpdb is exactly the same as shown before when the FastAPI server was started locally. Just keep in mind that you must specify addr="0.0.0.0" otherwise you can’t debug with rpdb.

The more complicated and more interesting part is to debug API code running inside a Docker container in VS Code. For this purpose, we need to use the debugpy library as specified in requirements.txt.

Add the following code in the API code to enable debugging in VS Code. Note that we need to remove the code for rpdb otherwise it won’t work properly:

Port 5678 is the default port for debugpy, just as 4444 is for rpdb. Also, importantly, you must specify the host as "0.0.0.0" so we can debug from outside the container.

Now, let’s create a new configuration for “attaching” to the API server for debugging. Click “Run” => “Add Configuration”. In the guide, first, choose “Python”, then choose “Remote Attach”, keep “localhost” as the hostname, and press “Enter”. A new configuration will be created for you, update it as follows to make it work for our specific example:

Same as before, give the configuration a descriptive name so it can be chosen correctly in the Debug window. The request option here is “attach” which means it will attach to a debugger server running somewhere else, not inside VS Code. For the connectpart, we can use localhost because port 5678 has been exposed for the docker container. Finally, we need to map the API code in VS Code to the code in the Docker container. The folder and files should match exactly. Since we mount the app folder to the Docker container, they will always match exactly.

Before we can debug remotely in VS Code, we need to remove the code for rpdb and restart the Docker containers. Just press CTRL + C in the terminal running the Docker containers and run docker-compose up again.

Finally, we can debug the API code running inside the Docker container in VS Code. This time, we need to call the API first so the remote debugger can be started, and then we can attach to it from inside VS Code:

$ curl localhost:8000/echo/mypath?query=myquery
# The code is blocked for debugging.

Then we need to run the debugger in VS Code to attach to the remote debugger started in the Docker container. We don’t need to specify a breakpoint in VS Code to trigger the debugger because it has been set with debugpy.breakpoint() in the container. For now, we only need to choose the right configuration and start the debugger:

Once we press the start button in the Debug window, we are attached to the remote debugger started in the Docker container. We can now start to debug the code as if the FastAPI server is launched by VS Code. We can add breakpoints at any code we want to debug the code conveniently in VS Code.

We have now covered the most common ways to debug Python scripts and API code, both locally and remotely, in the console and in VS Code. You shall now be fairly confident about which tool to use for debugging your own code.

Related articles:

Python
Debugging
Vscode
Pdb
Fastapi
Recommended from ReadMedium