Fix Your Broken Python Setup in VSCode Once and For All

Overview

Setting up a Python development environment in

often feels like a constant battle against broken imports, mismatched interpreter versions, and testing suites that refuse to discover your code. This guide moves past temporary fixes to establish a robust, professional workflow. We will focus on creating a project structure that
Pylance
understands and
Pytest
can navigate, using modern tools like
UV
for dependency management.

Fix Your Broken Python Setup in VSCode Once and For All
Fix Your Broken Python Setup in VSCode (Once and For All)

Prerequisites

To follow along, you should have

installed and a basic understanding of the
Python
language. Familiarity with the terminal or command prompt is necessary for running installation commands and project initialization.

Key Libraries & Tools

  • UV
    : A fast Python package installer and resolver written in Rust, used as a modern alternative to
    Poetry
    .
  • Ruff
    : An extremely fast Python linter and code formatter.
  • Pylance
    : The default language server for Python in VSCode, providing IntelliSense and type checking.
  • Pytest
    : A framework that makes it easy to write simple and scalable test suites.
  • Even Better TOML
    : An extension for better syntax highlighting and navigation in configuration files.

Project Structure and Initialization

Instead of installing packages globally, we use

to create an isolated environment. Start by initializing your project in the terminal:

uv init --no-workspace

A professional structure separates the logic from the metadata. Move your code into a src directory and include a __init__.py file to signal that it is a package. Your directory should look like this:

my_project/
└── src/
    └── my_app/
        └── __init__.py
        └── main.py
└── tests/
    └── test_main.py
└── pyproject.toml

To add

as a development dependency, run:

uv add --dev pytest

Solving the Import and Test Discovery Crisis

The most common headache is

failing to find your modules because it doesn't know about the src folder. You fix this by adding a pythonpath setting to your pyproject.toml file:

[tool.pytest.ini_options]
pythonpath = "src"

However,

might still show "reportMissingImports" in the editor even if tests run. You must align the editor's analysis with your runtime path by creating a .vscode/settings.json file:

{
    "python.analysis.extraPaths": ["./src"],
    "python.testing.pytestArgs": ["tests"],
    "python.testing.unittestEnabled": false,
    "python.testing.pytestEnabled": true
}

Professional VSCode Configuration

Managing settings across a team requires moving beyond global user settings. Use Folder Settings within the .vscode directory to ensure every developer on the project uses the same interpreter and formatter.

Recommended Extensions

Share a consistent toolset by creating .vscode/extensions.json. When a team member opens the project, VSCode will prompt them to install the necessary tools:

{
    "recommendations": [
        "ms-python.python",
        "charliermarsh.ruff",
        "tamasfe.even-better-toml"
    ]
}

Syntax Notes and Conventions

  • TOML Folding: Use the
    Even Better TOML
    extension to collapse large configuration blocks in your pyproject.toml.
  • Bundled Formatters: If you use the
    Ruff
    extension, enable the useBundled setting to avoid needing a separate local installation of the binary.
  • Analysis Paths: Always use relative paths (like ./src) in extraPaths to ensure settings work across different machines.

Tips & Gotchas

  • Priority of Settings: Remember that Folder Settings override Workspace Settings, which in turn override User Settings. If your project isn't behaving, check the local .vscode/settings.json first.
  • Syncing: Use VSCode's built-in Settings Sync for personal preferences like themes and fonts, but keep project-specific logic (like the Python path) in the repository.
  • Source Folder: Never name your root code folder source or src without an __init__.py if you intend to import it as a package;
    Pylance
    needs that marker to recognize the package boundary correctly.
4 min read