Speeding Up My Python Workflow: Why I'm Switching From Poetry (and asdf) to uv
Where I am coming from
For a long time, my Python development setup has been a well-oiled machine. I've relied heavily on Poetry for dependency management, and I truly appreciate how it elegantly handles virtual environments, package installation, and project structure. It's been a lifesaver, providing reproducible builds and keeping my dependencies organized. Paired with asdf to manage different versions of Python and other tools, I felt like I had a robust and reliable workflow.
Asdf, in particular, allowed me not to have to worry about system Python versions and ensure things like Python interpreters and other tools like node, go, etc, were version managed and available on a per-project basis. This keeps me out of version conflicts and provides great flexibility.
I loved the declarative nature of poetry, ensuring that my project environments were always reproducible.
However, There's a New Kid on the Block: uv
While Poetry and asdf have served me well, I'm always on the lookout for ways to improve my workflow, and that's where uv enters the picture. The concept of a blazing-fast package manager is very appealing, and I am always on the lookout for ways to shorten the feedback loop when developing software. The promise of significantly faster dependency resolution and environment creation is hard to ignore. It's not about throwing away everything I love about Poetry or asdf, but about augmenting my toolbox with the right tool for the job and uv looks very promising.
The Need for Speed
The biggest selling point for uv is its performance. I spend a decent amount of time in the terminal, waiting for Pip or Poetry to resolve and download packages for my projects, and that time can really start to add up and interrupt my development flow. With uv, those waits are significantly shorter. It leverages Rust for speed, and that makes a noticeable difference.
Getting Started with uv: A Practical Example
Let's dive into how to set up a new project with uv. We'll create a basic FastAPI application with SQLModel, and then integrate tools like Ruff for linting.
Step 1: Installing uv
First, you need to install uv. You can follow the instructions on their Github page for various operating systems. Generally, the installation is pretty straightforward
curl -sSfL https://astral.sh/uv/install.sh | sh
or on a Mac you can use Brew
brew install uv
Step 2: Creating a New Project
To create a new project you will find it as simple as typing
uv init {project_name}
This will create a new directory with all the files you need to get started with a new uv project. You will see a list of files like below
.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── hello.py
└── pyproject.toml
You will see it sets everything up for you, including an initialized git environment.
If you open to pyproject.toml file you will see it looks very similar to what you would expect to see with Poetry. This includes the project name, version, description, Python version and an empty list of dependencies for the project. I know this all looks very familiar to other tools you have used but I promise we are getting to the magic next.
Installing dependencies
This is where the magic starts to happen. As stated before we are going to create a FastAPI application so lets start by adding FastAPI and SQLModel. Notice I did fastapi[all], this will add the fastapi cli for running this project. I had to add the \ to all for my terminal for it to work.
uv add fastapi\[all\] sqlmodel
Did you blink? Did you miss it? How amazingly fast was that. Imagine how many CI/CD build minutes that is going to save you!
Now looking back at your pyproject.toml file you will see those dependencies got added for you.
Now create a main.py file and add this super original fastapi code to it to make a heroes api.
from typing import List
from fastapi import FastAPI, HTTPException
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
@app.post("/heroes/", response_model=Hero)
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
@app.get("/heroes/", response_model=List[Hero])
def read_heroes():
with Session(engine) as session:
heroes = session.exec(select(Hero)).all()
return heroes
@app.get("/heroes/{hero_id}", response_model=Hero)
def read_hero(hero_id: int):
with Session(engine) as session:
hero = session.get(Hero, hero_id)
if not hero:
raise HTTPException(status_code=404, detail="Hero not found")
return hero
Now run the application with uv. I know we haven't made any virtual environments yet but watch the magic happen. After running this command you will see a .venv file get created right in your directory for you.
uv run fastapi dev
Now run the application with uv. I know we haven't created any virtual environments yet, but watch the magic happen. After running this command, you will see a .venv file created in your directory.
Managing Python versions
I know, I know, I buried the lead with this one. I love asdf and I use it for everything. However, I no longer need to use it for Python as uv can manage my Python versions for me.
Need to install the latest version of Python?
uv python install
How about installing a specific version persay 3.13 to play with the no gil build
uv python install 3.13
Python version per project
Well, asdf, let me create a .tool-versions file to manage which version of Python I used per project. Does uv do that? As a matter of fact it does. When you ran uv init {project}
it created a .python-version
file. It lists the current system version that you would get if you ran python --version
. However, if you switch that version to whatever you require, persay 3.8 you will see next time you run uv run fastapi dev
it will download 3.8 if you do not already have it. Don't forget you will need to update your pyproject.toml as well as this could will violate the Python version constraint.
Tool usage
As a developer, of course, you use linters, right....? Right! So how do we run a linter like ruff or black with uv? Uv allows us to run tools with a command uv tool run {tool}
or uv tool run ruff
to run ruff. That is a lot to type, so uv provides an alias uvx. You can run ruff by typing uvx ruff
. You will see uv does not install this in your project, but if you want it managed by your project you can install it as you please. If you want it as a dev dependency see the next section.
Dev and other group dependencies
Poetry allowed you to install dependencies in different groups, there is no surprise uv allows this as well. In tool usage we mentioned you could install things like ruff as a dev dependency, using groups is how you will do that. There is a dedicated flag for dev --dev
.
uv add ruff --dev
This will create a groups section in the pyproject.toml and add a dev group with the ruff dependency.
To run uv with the group dependencies add the – group
flag. This will work for any group you add, just put the group name like so uv run --group {group_name} fastapi dev
and it will install that group. Likewise, you can exclude specific groups using the `--no-group {group_name} flag.
Conclusion
Switching to uv has been surprisingly smooth, despite my strong attachment to Poetry and asdf. It's a testament to the strength of the speed offered by uv and it is something I think I will continue using going forward. The speed improvements are undeniable and the ease of use makes it a great contender for managing Python projects. I encourage you to give it a try and see how it can streamline your development experience.
While I will continue to love asdf and everything it does for me, I see a spot for uv in my workflow. I am hopeful that it will continue to be developed and become a tool that I can continue to use for many projects to come.