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.