How to Create a New Python Project with uv init
I tried setting up a new Python project last week. I created a virtual environment, installed dependencies, set up my pyproject.toml, configured linting tools, and more. Two hours later, I still hadn’t written any actual code.
The traditional Python project setup is slow. You need to choose between pip, poetry, or pipenv. You need to configure virtual environments, dependency files, and build systems. You need to install and configure linting and formatting tools.
Then I discovered uv init. It creates a complete Python project in seconds.
Why I Switched to uv for Project Setup
I used to spend the first day of any new project on setup. I would:
- Create a virtual environment with
python -m venv .venv - Activate it (different commands on Windows vs Unix)
- Install dependencies with pip
- Create a
requirements.txtorpyproject.toml - Set up black, flake8, and isort
- Configure pre-commit hooks
- Create a
.gitignorefile
Each step required reading documentation or copying from previous projects.
uv solves this by doing everything in one command. It creates the project structure, manages dependencies, handles virtual environments, and even manages Python versions. The speed difference is noticeable—uv installs packages 10-100x faster than pip.
Creating Your First Project
Let me create a simple application project:
uv init my-appcd my-appThis creates a complete project structure:
my-app/├── .gitignore├── .python-version├── README.md├── main.py└── pyproject.tomlThe .python-version file pins the Python version. The pyproject.toml contains project metadata and dependencies. The main.py is your entry point with a simple “Hello, world!” example.
Let me check what uv init created:
cat pyproject.tomlOutput:
[project]name = "my-app"version = "0.1.0"description = "Add your description here"readme = "README.md"requires-python = ">=3.12"dependencies = []The project is ready to use. No manual configuration needed.
Choosing the Right Project Type
uv init supports three project types:
Application projects (--app) are for web servers, CLIs, and scripts. This is the default:
uv init --app my-web-serverLibrary projects (--lib) are for packages you plan to publish:
uv init --lib my-libraryLibrary projects include a src/ directory structure:
my-library/├── .gitignore├── .python-version├── README.md├── pyproject.toml├── src/│ └── my_library/│ └── __init__.py└── tests/ └── __init__.pyScript projects (--script) create single-file scripts with inline metadata:
uv init --script my-script.pyThis creates a standalone script with embedded dependency declarations.
Adding Dependencies
Now I need to add some packages to my project:
cd my-appuv add requests flaskOutput:
Resolved 12 packages in 0.45sInstalled 12 packages in 0.12s + flask-3.0.0 + requests-2.31.0 ...uv creates a uv.lock file to ensure reproducible installs. It also creates a .venv directory automatically.
Let me add development dependencies for testing and linting:
uv add --dev pytest ruffMy pyproject.toml now includes:
[project]name = "my-app"version = "0.1.0"dependencies = [ "flask>=3.0.0", "requests>=2.31.0",]
[tool.uv]dev-dependencies = [ "pytest>=8.0.0", "ruff>=0.2.0",]The lock file ensures everyone on the team gets the exact same versions.
Running Your Code
I used to activate virtual environments manually:
source .venv/bin/activate # Unix# or.venv\Scripts\activate # Windowspython main.pyWith uv, I just run:
uv run main.pyuv automatically uses the project’s virtual environment. I don’t need to remember activation commands or worry about which Python I’m using.
For scripts with dependencies, uv run is even more useful. It installs dependencies on first run:
uv run my-script.pyThis works for any command, not just Python scripts:
uv run pytestuv run ruff check .uv run flask runWorking with Multiple Python Versions
I needed to test my code on Python 3.11 and 3.12. With uv, I don’t install Python manually:
uv python install 3.11 3.12uv python pin 3.11uv downloads and manages Python installations automatically. The .python-version file tells uv which version to use.
To test on multiple versions:
uv python pin 3.11uv run pytest
uv python pin 3.12uv run pytestNo pyenv or manual installation needed.
Setting Up Workspaces
I had a project with multiple packages. I used separate repositories or a complex poetry setup. uv makes this simpler with workspaces:
mkdir my-workspacecd my-workspaceuv init
# Create workspace membersuv init packages/coreuv init packages/apiuv init packages/webThe root pyproject.toml defines the workspace:
[tool.uv.workspace]members = ["packages/*"]Each package can depend on others:
cd packages/apiuv add core # References packages/coreDependencies within the workspace use local paths, not published packages. This is perfect for monorepos.
Building and Publishing Libraries
For library projects, I wanted to build and publish to PyPI. uv handles this too:
cd my-library
# Build distributionsuv build
# Creates:# dist/my_library-0.1.0-py3-none-any.whl# dist/my_library-0.1.0.tar.gz
# Publish to PyPIuv publishuv prompts for PyPI credentials or uses tokens from the environment. No need for twine or build tools.
Integrating with CI/CD
I set up GitHub Actions for my project. The workflow is simple because uv handles everything:
name: CI
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Install uv uses: astral-sh/setup-uv@v4
- name: Set up Python run: uv python install 3.12
- name: Install dependencies run: uv sync
- name: Run tests run: uv run pytest
- name: Lint run: uv run ruff check .The uv sync command creates the virtual environment and installs all dependencies from the lock file. This ensures CI matches local development.
Comparing uv with Other Tools
I used poetry before switching to uv. Here’s what I noticed:
Speed: uv is significantly faster. Installing Django and its dependencies took 8 seconds with uv vs 45 seconds with poetry.
Simplicity: uv has fewer commands to learn. uv init, uv add, uv run cover most needs. Poetry has separate commands for adding, updating, and installing.
Tool integration: uv includes ruff for linting and formatting. I don’t need to configure black, flake8, and isort separately.
Python management: uv installs and manages Python versions. Poetry requires you to install Python separately.
The traditional pip approach is even slower. Creating a venv, activating it, pip installing, and managing requirements.txt files takes more steps. pip also doesn’t have a lock file by default, making reproducible builds harder.
Common Mistakes I Made
When I started with uv, I made some mistakes:
Not using uv run: I tried activating the virtual environment manually. This defeats uv’s purpose. Use uv run for all commands.
Using pip inside uv projects: I ran pip install in a uv-managed project. This broke the lock file consistency. Always use uv add instead.
Ignoring the lock file: I didn’t commit uv.lock to git initially. This caused different dependency versions across machines. Now I always commit it.
Forgetting .python-version: I didn’t pin the Python version in some projects. Team members had different Python versions installed. uv makes this easy—just commit the .python-version file.
Practical Workflow
Here’s my typical workflow for a new project:
# Create the projectuv init my-projectcd my-project
# Add production dependenciesuv add flask sqlalchemy
# Add dev dependenciesuv add --dev pytest ruff basedpyright
# Create a simple testcat > test_main.py << 'EOF'def test_example(): assert TrueEOF
# Run testsuv run pytest
# Check codeuv run ruff check .
# Type checkuv run basedpyright
# Run the applicationuv run main.pyIn about two minutes, I have a fully configured project with dependencies, tests, and linting.
When to Use Each Project Type
I learned to choose based on the project’s purpose:
- Application (
--app): Use for web servers, CLIs, scripts, and most projects. This is the sensible default. - Library (
--lib): Use only when you plan to publish to PyPI. Thesrc/layout helps with packaging. - Script (
--script): Use for single-file utilities. Inline metadata keeps everything in one file.
Most of my projects are applications, so I rarely need --lib or --script.
Summary
In this post, I showed how to create Python projects with uv. The uv init command creates a complete project structure with pyproject.toml, virtual environments, and Python version management. I covered the three project types (application, library, and script), adding dependencies with uv add, running code with uv run, and setting up workspaces for monorepos. I also shared my workflow and common mistakes to avoid.
The key benefit is speed—uv handles project setup in seconds instead of hours. You get dependency management, virtual environments, Python version management, and build tools in one package. No more juggling pip, poetry, pyenv, and build tools separately.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 uv Documentation
- 👨💻 uv init Guide
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments