Joshua's Docs - Visual Studio Code (VS Code) for Python Development
Light

πŸ“„ For general VS Code tips and tricks, visit the parent page of this one. If you are new to VS Code in general, I recommend checking it out.

πŸ”— Resource: the official docs on using Python with VSCode
Β Β Β Β Β Β - πŸ’‘ Python Settings Reference

Python and VSCode - Quick Start Guide

Pre-requisite: Having Python installed

  1. Install Visual Studio Code (aka VSCode)
  2. Install the Python extension pack for VSCode
  3. Open a project or file in VSCode that contains Python code, or create a new one
  4. Optional: Use a virtual environment and/or specific interpreter
    • The Python extension will use a multi-step resolution process to look for a Python interpreter to use with your code.
    • Most virtual environments it will detect automatically (such as ./venv, relative to your root workspace)
    • In the rare cases you might want to tell VSCode to use a different specific interpreter, you can do so via the python.defaultInterpreterPath setting, GUI, or Command Palette -> Python: Select Interpreter

That should cover everything you need to get a basic Python editing experience up and running in VSCode, but some more advanced features are broken out further in sections below, such as:

Debugging Python in VS Code

If you wish to debug Python inside VSCode, debugpy is the recommended way to go; AFAIK, it is the only mainstream Python debugger that supports the Debug Adapter Protocol (DAP), which is how the debugger communicates with VSCode (as well as other IDEs).

debugpy with VSCode - General Concepts

πŸ“˜ Official Resource: Python Debugging in VS Code and Debugging in Visual Studio Code

Unlike nodejs debugging with VSCode which support auto-attach and doesn't require extra dependencies, setting debugpy up with VSCode can involve some manual steps. It largely depends on what you are trying to debug.

For each of the debugging options below, here are some common things that will be referenced:

  • Command Palette: A quick command selector / executor, brought up with CTRL + SHIFT + P (CMD + SHIFT + P on macOS), or via View -> Command Palette
  • launch.json: A special file (stored at .vscode/launch.json) which can be used to store debugging configurations
  • Run View: AKA the run and debug sidepanel, this is what you will have open while debugging, and it does things like show you a summary of active breakpoints, watch certain values, inspect the call stack, etc. It will open automatically when running a debug task, but you can also manually open it at any time.
  • Debug Console: Once you have an active debugging session active, you can use the debug console as a live interactive REPL.

Anytime I mention using debugpy from the command line or from Python code, that means you need to already have installed debugpy into your environment (e.g. with pip install debugpy).

For learning how to do things like set breakpoints in VSCode (including conditional breakpoints!) and using the debug panels, refer to the official docs.

Single File Debugging

Singe-file python debugging is by far the most straightforward. Your options are:

  • Command Palette -> Python: Debug Python File
  • Run -> Start Debugging or simply F5 on your keyboard
  • debugpy CLI: Refer to below section on configuration-based debugging

Note: Single file debugging will likely not let you use breakpoints in other files, even if those files are imported and executed in the file you are debugging.

Configuration Based Debugging with Python in VS Code

For pretty much anything beyond single-file debugging in VS Code, you have to set up some sort of configuration in launch.json, mainly because VSCode has to know which port to listen for messages from the debugger on.

πŸ€”For some reason, VS Code has not released an auto-attach feature for Python like it has for NodeJS. If you are interested in this, subscribe to issue #1182.

Luckily, VS Code has presets for most debugging scenarios, and can walk you through setting up a configuration (⁕). To scaffold a new configuration, any of the following can be used:

  • Run -> Add Configuration
  • Command Palette -> Debug: Select and Start Debugging -> Add Configuration
  • Create launch.json (e.g. mkdir -p .vscode && echo "{}" > .vscode/launch.json), then open the file, and click Add Configuration... button that appears

When you do any of the above, VS Code should walk you through picking a preset.

⁕ = There are some edge-cases with this, primarily for things like Django or python sub-processes. For details, see sections below.

If you want to debug libraries (e.g., code within {YOUR_VENV}/lib/python3.x/site-packages, etc.), make sure you have justMyCode set to false

Invoking debugpy via Python Code

πŸ’‘ The neat thing about invoking debugpy via code is that you don't have to change how you launch your application at all and you can start or stop debugging at any point, without having to reload your app! 🀯

The steps for invoking debugpy via Python code actually looks rather similar to the CLI step below; you still have to create a corresponding launch.json file and the CLI arguments are translated to Python methods.

For example, instead of this CLI command:

python -m debugpy --listen 5678 --wait-for-client ./my_python_entrypoint.py

You could use this python code, in my_python_entrypoint.py:

import debugpy

debugpy.listen(5678)
debugpy.wait_for_client()
Show / Hide launch.json config
{
	"version": "0.2.0",
	"configurations": [
		// Based on the `Python: Remote Attach` preset
		{
			"name": "Python: Remote Attach",
			"type": "python",
			"request": "attach",
			"connect": {
				"host": "localhost",
				"port": 5678
			},
			"justMyCode": true
		}
	]
}

The debugpy library also offers debugpy.breakpoint() for setting a breakpoint via code.

Invoking debugpy via CLI

If you want to use the debugpy CLI with VS Code debugging, you will want to mainly follow the debugpy CLI docs - the only special thing is that you need to create a corresponding launch.json configuration first:

  • Use the Python: Remote Attach preset
  • The port number you pick (e.g. 5678) doesn't really matter, except it must be the same port you pass to the debugpy --listen argument
  • If the part of your code that you need to debug will be reached faster than you can open the debug view in VS Code (or hit F5), use the --wait-for-client option with debugpy
    • This will pause your Python code until debugpy detects that the VS Code debugger hsa attached itself (via port communication) to the debugpy adapter
Show / Hide debugpy CLI VS Code Setup

Step 1: Create a launch.json entry:

{
	"version": "0.2.0",
	"configurations": [
		// Based on the `Python: Remote Attach` preset
		{
			"name": "Python: Remote Attach",
			"type": "python",
			"request": "attach",
			"connect": {
				"host": "localhost",
				"port": 5678
			},
			"justMyCode": true
		}
	]
}

In general, you can (and should) remove the pathMapping setting that the preset includes, unless you are debugging across a remote connection (not your local PC)

Step 2: Run my program via debugpy

Notice that I'm using the same port as specified in launch.json, and --wait-for-client to give VS Code time to attach before executing my Python

python -m debugpy --listen 5678 --wait-for-client ./my_python_entrypoint.py

Step 3: Tell VS Code to use my config to attach to debugpy

  • If my config is already selected as the active launch config, or I only have one config, I can just hit F5 or Debug: Start Debugging
  • Otherwise, Command Palette -> Select and Start Debugging, or select the configuration via the Run and Debug panel selector and hit the start button.

Using debugpy with Django

There is a Django launch.json configuration preset that you can use to start manage.py; this should cover most use-cases.

However, in the case that you start Django via something else (e.g. a bash script), you might be looking for alternative approaches. Where this gets tricky is that the auto-reloading feature of Django will break the debugging connection (and hijack ports) if you stick debugpy.listen() in the wrong spot.

If you get error messages about Address already in use or timeouts connecting, that can be a sign that your debugpy setup code is getting invoked more than once, likely caused by Django reloading

For a code-based debugpy setup with Django that will work with auto-reloading, the easiest place to stick the debugpy setup code is in {YOUR_PROJECT}/wsgi.py. Or, any other entry-point hook listed in the answers to this StackOveflow.

Using debugpy with pytest

If you are looking for a code-based approach, a startup hook you can wrap your debugpy setup code is pytest_sessionstart in conftest.py:

import debugpy

def pytest_sessionstart(session):
	debugpy.listen(5678)
	# Optional, if you want to not start tests until debugger is connected
	debugpy.wait_for_client()
	# ...

Enabling Debugger Log Files

You can enable saving the debugpy log to a file by tweaking your launch.json file, or using debugpy.configure.

launch.json

{
	"configurations": [
		{
			"name": "Python Debug",
			"logToFile": true,
			// [...]
		}
	]
}

If you enable logging only through launch.json, the logs will appear in the VSCode extensions directory, instead of within your project directory. To specify a different log directory, set the DEBUGPY_LOG_DIR environment variable to the desired full path.

Python

import debugpy

debugpy.log_to('/Users/Joshua/debugpy-logs')

⚠ For both log_to and DEBUGPY_LOG_DIR I recommend using a full path - so /Users/Joshua/logs instead of ~/Joshua/logs, or ./logs.

πŸ’‘ For more details, check out debugpy's docs: "Enabling Debugger Logs"


Python Linting in VSCode

πŸ“˜ Official Resource: Linting Python in Visual Studio Code

Visual Studio Code offers a large number of Python linting options, which include (at the moment):

  • bandit
  • flake8
  • mypy
  • prospector
  • pycodestyle
  • pydocstyle
  • pylama
  • pylint

To pick a linter to use with your project, use Command Prompt -> Python: Select Linter. Picking a linter will automatically change some corresponding settings in .vscode/settings.json.

Note: The linter either needs to be installed in the same environment as the Python interpreter used with VS Code, or passed via python.linting.{LINTER_NAME}Path: "". The Python extension will detect if the linter is not installed after selecting it, and will offer a one-click install button for convenience!

To configure your linter of choice beyond its defaults, most linters expose additional settings via python.linting.{LINTER_NAME}Args: [], and to customize these, you should refer to the CLI reference for your given linter.

For example, to tell PyCodeStyle to enforce a line-length limit:

{
	"python.linting.enabled": true,
	"python.linting.pycodestyleEnabled": true,
	"python.linting.pycodestyleArgs": [
		"--max-line-length",
		"79"
	]
}

Troubleshooting Python Linting in VSCode

Check the Output -> Python logs.

If linting does not seem to be doing anything / exposing errors, even if it is enabled, check to make sure the path is set up correctly. Some linters (such as flake8) don't seem to work well if the path is set to somewhere outside of the current project.

Django Linting in VSCode

The extra consideration for linting Django code with VS Code is if you are using pylint with the pylint_django plugin. In that case, you will likely find the following settings helpful:

{
	"python.linting.enabled": true,
	"python.linting.pylintEnabled": true,
	"python.linting.pylintArgs": [
		"--load-plugins",
		"pylint_django",
		"--django-settings-module=myproject.settings"
	]
}

Python Types in VSCode

The official Python extension comes bundled with a Pylance extension, which will be auto-installed and activated along with the main extension. This Pylance extension unlocks rich IntelliSense and type-checking features, powered by Microsoft's Pyright (a static type-checker for Python).

🚨 ⚠ The default mode for pylance, as far as in-IDE checking and warning, is off! To get warnings in VS Code for mis-matched types, or even just basic inference, change python.analysis.typeCheckingMode to "basic" or "strict".

Although this works out-of-the-box for the majority of projects (after turning on checking), here are a few special things to note:

  • The Pylance extension comes bundled with some type stubs, for common libraries (see notes in README), such as matplotlib, pandas, and django
    • Warning: VSCode will not warn if you Pylance is using type stubs that do not match the version of the library your project has installed. For example, your project might be using Django v2.x, but the bundle type stubs are for Django v3.x.
      • It might also make it misleading as to which libraries are built-in; for example, it will provide stubs for yaml.* methods if you do import yaml, even if you don't have PyYAML installed
      • It is usually better to explicitly install and use type stubs for your specific project, rather than the defaults
  • You can use Go to Type Definition to see where / how VSCode is sourcing the type for a specific variable, method, etc.
  • The Pylance extension is configurable
  • You can always install your own stubs (and this is usually recommended)

Formatting Python in VS Code

πŸ“˜ Official Resources: Python Formatting Settings Reference and Formatting in VS Code Basics

The main way to enable the use of a Python formatter in VS Code is through .vscode/settings.json.:

{
	// Currently `autopep8`, `black` or `yapf`
	"python.formatting.provider": "autopep8"
}

Additional CLI args can be passed to the formatter of choice, through python.formatting.{FORMATTER_NAME}Args: [], and the path can be specified via python.formatting.{FORMATTER_NAME}Path: "" if you have it installed somewhere other than the current environment.

Here is an example of a more advanced configuration:

{
	"python.formatting.provider": "autopep8",
	"python.formatting.autopep8Args": [
		"--max-line-length",
		"79",
		"--experimental"
	],
	"[python]": {
		"editor.defaultFormatter": "ms-python.python",
		"editor.formatOnSave": true,
		"editor.codeActionsOnSave": {
			"source.organizeImports": true
		}
	}
}

Improving Python Auto-Imports in VSCode

Auto-import in this context refers to the feature in VS Code when you start typing out the name of something that needs to be imported, and VS Code automatically adds the import statement when you hit TAB or ENTER, or use the Quick Fix option to add the import

There are a few settings that can be used to improve the behavior of automatic Python imports in VS Code.

πŸ’‘ For settings where you are passing in paths, you can use ${workspaceFolder} as a macro for the project root / workspace subdirectory root

python.analysis.extraPaths: []

  • This is setting exposed by the Pylance extension, which lets you tell it about extra paths for use with import resolution
  • If you are running into issues with VS Code saying that imports could not be resolved, or adding extra directories (e.g. it auto-fills with dirA.dirB.my_import instead of dirB.my_import), this can fix it
  • You can also use this to get auto-complete for deeply nested imports that don't work out of the box
  • This replaced python.autoComplete.extraPaths (mentioning in case you see this in any configs)

pythonDiscoveryModule

In a few very rare cases (if I remember correctly, they were complex multi-root workspaces), I've had to use the following settings to get some import resolution stuff and interpreter paths working correctly:

{
	"python.experiments.enabled": true,
	"python.experiments.optInto": [
		"pythonDiscoveryModule"
	]
}

Django Editing in VSCode

For the most part, editing a Django project in VSCode works just like editing any other Python project. But, there are a few areas worth calling out - these are links to Django specific sections within this same guide:


Multi-Root Workspaces

A lot of issues and edge-cases with working on Python projects in VSCode can be traced back to how Python projects are organized versus how VSCode typically parses a directory. And issues with paths in general.

It's not a-typical for a Python project to involve multiple subdirectories with nested packages, and sometimes even multiple virtual environments and python interpreters. Out-of-the-box, VSCode tends to falter in these situations, that is, until you break out multi-root workspaces.

πŸ“˜ Official Docs: Multi-root Workspaces

Here is an example of a multi-workspace config (keep in mind, they can get a lot more complicated than this):

{
	"folders": [
		{
			"path": "../flask-api"
		},
		{
			// Name is optional
			"name": "Django-powered Admin Portal",
			"path": "../django-admin"
		}
	],
	"settings": {
		"python.defaultInterpreterPath": "${workspaceFolder}/env/bin/python3"
	}
}

Note that some settings can be shared across all root directories, while others should be configured in a .vscode/settings.json file nested within the individual roots. For details, refer to the official docs.

Markdown Source Last Updated:
Thu Jun 23 2022 10:16:23 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Fri May 13 2022 18:38:35 GMT+0000 (Coordinated Universal Time)
Β© 2022 Joshua Tzucker, Built with Gatsby
Feedback