laranevans.com
Guides / Python and FastAPI for Spring Boot Engineers / Set Up a Modern Python Project

uv does for Python what Maven/Gradle and jenv do for Java. It manages the interpreter, the virtual environment, dependencies, and the lockfile.

Install uv and a Python interpreter

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

uv installs and pins Python versions the way jenv manages JDKs:

uv python install 3.14

[!tip] Everything in this guide will work on Python 3.12+.

Create the project

uv init scaffolds a Python project similar to the way Spring Initializr scaffolds a Maven/Gradle project:

uv init --package my-service
cd my-service

The --package flag gives you a src layout analogous to src/main/java. uv creates the following file structure:

my-service/
├── pyproject.toml          # project metadata and dependencies (analogous to pom.xml)
├── README.md
├── .python-version         # the Python interpreter pinned for this project
└── src/
    └── my_service/
        └── __init__.py

The pieces map onto the Java build tools you're familiar with':

Java Python
Maven, Gradle uv
pom.xml, build.gradle pyproject.toml
mvnw, gradlew wrapper uv plus a pinned .python-version
~/.m2, Gradle cache uv's shared cache
target/, build/ .venv/ and build artifacts
SDKMAN, jenv uv python install

Managing your dependencies

Python has a notion of "virtual environments". Virtual environments encapsulate a set of dependencies inside a project.

A number of tools allow you to manage virtual environments. Popular options include venv (built into Python), virtualenv, Conda, Poetry.

uv also manages virtual environments.

Typically, you would create a virtual environment like this:

uv venv my_env

You would add dependencies like this:

uv add "httpx>0.1.0"
uv add --dev pytest ruff pyright

You would update dependencies like this:

uv lock --upgrade # upgrade all dependencies
uv lock --upgrade-package <package_name> # upgrade one package

uv sync # call this after upgrading to install the latest versions into your env.

You would remove dependencies like this:

uv remove httpx

Running code cleanly in your virtual environment

In Java, you would control what dependencies get loaded at runtime by setting the classpath when invoking java.

In Python, there are two ways to control the dependencies loaded at runtime.

Option 1: source .venv/bin/activate

The more traditional method is to "activate" your virtual environment.

You would do that like this:

source .venv/bin/activate

The downside to using this approach is that it doesn't ensure that your code runs with the dependencies you expect. If you switch projects, forget to activate, or fail to keep packages synchronized, your code can either fail or use the wrong dependencies.

Option 2: uv run

The foolproof way to ensure your code runs with the exact dependencies you declared in pyproject.toml is to use uv run like this.

uv run python -m my_service
uv run pytest

uv run ensures that your code runs with the exact locked dependencies every time.

Lint and format with ruff

ruff is your Checkstyle, SpotBugs, and formatter in one fast tool. Two commands cover linting and formatting:

uv run ruff check --fix .   # find and auto-fix lint issues
uv run ruff format .        # apply formatting

Configure both in pyproject.toml, so the project carries its own rules:

[tool.ruff]
target-version = "py314"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I", "B"]   # pycodestyle, pyflakes, isort, bugbear

Your project now resolves, runs, lints, and tests the same way on any machine that has uv. Next: map the Java language to Python.