Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Python docs to use uv instead of poetry #1847

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion django/getting-started/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ mkdir hello-django && cd hello-django

### Virtual Environment

For this guide, we use [venv](https://docs.python.org/3/library/venv.html#module-venv) but any of the other popular choices such as [Poetry](https://python-poetry.org/), [Pipenv](https://github.com/pypa/pipenv), or [pyenv](https://github.com/pyenv/pyenv) work too.
For this guide, we use [venv](https://docs.python.org/3/library/venv.html#module-venv) but any of the other popular choices such as
[uv](https://docs.astral.sh/uv/),
[Poetry](https://python-poetry.org/),
[Pipenv](https://github.com/pypa/pipenv), or
[pyenv](https://github.com/pyenv/pyenv) work too.

```shell
# Unix/macOS
Expand Down
2 changes: 1 addition & 1 deletion python/do-more/add-object-storage.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fly secrets list
The de-facto library for interacting with s3 storage is [boto3](https://pypi.org/project/boto3/+external), let's add it to the project:

```cmd
poetry add boto3
uv add boto3
```

Now we can initialize the client:
Expand Down
2 changes: 1 addition & 1 deletion python/do-more/add-ollama.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fly secrets set OLLAMA_HOST=http://<your-app>.flycast
To interact with our new AI friend, we will have to install the `ollama` package:

```cmd
poetry add ollama
uv add ollama
```

Now we can initialize the client:
Expand Down
2 changes: 1 addition & 1 deletion python/do-more/add-postgres.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ At this point you can use a database driver to interact with the database. You h
Let's create a function to get some metadata about the database using `asyncpg`:

```cmd
poetry add asyncpg
uv add asyncpg
```

```python
Expand Down
4 changes: 2 additions & 2 deletions python/frameworks/fastapi.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ Deploying a FastAPI app on Fly.io is... well it's fast! You can be up and runnin

## _Deploy a FastAPI app from scratch_

<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "FastAPI" } %>
<%= partial "/docs/python/partials/uv_init", locals: { runtime: "FastAPI" } %>


Then we have to add the FastAPI dependency:

```cmd
poetry add "fastapi[standard]"
uv add "fastapi[standard]"
```

Now, let's create a simple FastAPI app in `main.py`:
Expand Down
8 changes: 4 additions & 4 deletions python/frameworks/flask.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ Spinning up a flask app is fun!

<%= partial "/docs/languages-and-frameworks/partials/flyctl" %>

<%= partial "/docs/python/partials/speedrun", locals: { runtime: "Flask", repo: "hello-flask-poetry" } %>
<%= partial "/docs/python/partials/speedrun", locals: { runtime: "Flask", repo: "hello-flask-uv" } %>

## _Deploy a Flask app from scratch_

<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "Flask" } %>
<%= partial "/docs/python/partials/uv_init", locals: { runtime: "Flask" } %>

To run this app we will need 2 dependencies:

- flask: the framework
- gunicorn: the server

```cmd
poetry add flask gunicorn
uv add flask gunicorn
```

Now let's create a basic app in `app.py`:
Expand Down Expand Up @@ -67,4 +67,4 @@ flask --app hello run

If you open http://127.0.0.1:5000/ in your web browser, it should display `hello from fly.io`.

<%= partial "/docs/python/partials/deploy", locals: { runtime: "Flask", repo: "hello-flask-poetry" } %>
<%= partial "/docs/python/partials/deploy", locals: { runtime: "Flask", repo: "hello-flask-uv" } %>
6 changes: 3 additions & 3 deletions python/frameworks/streamlit.html.markerb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ Spinning up a `streamlit` app takes no time at all!

## _Deploy a Streamlit from scratch_

<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "Streamlit" } %>
<%= partial "/docs/python/partials/uv_init", locals: { runtime: "Streamlit" } %>


Then we have to add the streamlit dependency:

```cmd
poetry add streamlit
uv add streamlit
```

Now, let's create a simple streamlit app in `main.py`:
Expand Down Expand Up @@ -56,4 +56,4 @@ headless = true

This ensures that our app doesn't give a prompt when we ship it.

<%= partial "/docs/python/partials/deploy", locals: { runtime: "streamlit", repo: "hello-streamlit" } %>
<%= partial "/docs/python/partials/deploy", locals: { runtime: "streamlit", repo: "hello-streamlit" } %>
9 changes: 0 additions & 9 deletions python/partials/_poetry_new.erb

This file was deleted.

9 changes: 9 additions & 0 deletions python/partials/_uv_init.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
For managing our project, we use [uv](https://docs.astral.sh/uv/).
For more information on the initial setup with uv, refer to ["Setting up a Python Environment"](/docs/python/the-basics/initial-setup).
We can initialize a new project like so:

```cmd
uv init <%= runtime.downcase %>-app
cd <%= runtime.downcase %>-app
uv run python
```
27 changes: 7 additions & 20 deletions python/the-basics/initial-setup.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,35 @@ We recommend the latest [supported versions](https://devguide.python.org/version

## Dependency Management

For project and dependency management we use [Poetry](https://python-poetry.org/). Like most package managers, Poetry combines multiple tools in one.
For project and dependency management we use [uv](https://docs.astral.sh/uv/). Like most package managers, uv combines multiple tools in one.

You have other options:
- [venv](https://docs.python.org/3/library/venv.html) and [pip](https://pip.pypa.io/)
- [Poetry](https://python-poetry.org/)
- [Pipenv](https://github.com/pypa/pipenv)
- [pyenv](https://github.com/pyenv/pyenv)
- [pip-tools](https://pypi.org/project/pip-tools/)

If you are just starting out, it is easiest to follow along using `poetry`.
After installing it, you will have to set 2 flags for virtual environment management.

```cmd
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
```
If you are just starting out, it is easiest to follow along using `uv`.

This will make your development environment resemble what ends up happening inside the docker image.

You can create a new project using this command:

```cmd
poetry new <app-name>
uv init <app-name>
```

Once inside the project, you can add packages with the add command:

```cmd
poetry add <dep>
uv add <dep>
```

This will automatically create a virtual environment for you.

To interact with your virtual environment, you can prefix your commands with `poetry run`:

```cmd
poetry run python main.py
```

Alternatively, you can activate the environment:
To interact with your virtual environment, you can prefix your commands with `uv run`:

```cmd
poetry shell
python main.py
uv run python main.py
```

47 changes: 27 additions & 20 deletions python/the-basics/multi-stage-builds.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,54 @@ In short, we split the process of building and compiling dependencies and runnin
- The resulting image is smaller in size
- The 'attack surface' of your application is smaller

Let's make a multi-stage `Dockerfile` from scratch. Here's part 1:

<div class="note icon">
In this example we assume the use of `poetry`, however you can adapt the file to work with other dependency managers too.
</div>
Let's make a multi-stage `Dockerfile` using uv, based on the [uv-docker-example](https://github.com/astral-sh/uv-docker-example/raw/refs/heads/main/multistage.Dockerfile). Here's part 1:

```dockerfile
FROM python:3.11.9-bookworm AS builder

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Builder stage
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

RUN pip install poetry && poetry config virtualenvs.in-project true
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

WORKDIR /app

COPY pyproject.toml poetry.lock ./
# Install dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-dev

RUN poetry install
# Copy the application code
ADD . /app

# Install the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
```

So what's going on here? First, we use a "fat" python 3.11.9 image and installing and building all dependencies. Defining it as `builder` gives us a way to interact with it later. What essentially happens here is exactly what happens when you install a project locally using poetry: a `.venv/` directory is created and in it are all your built dependencies and binaries. You can inspect your own `.venv/` folder to see what that looks like. This directory is the primary artifact that we want.
So what's going on here? First, we use a slim Python image that includes `uv`. We set some environment variables and install all dependencies using `uv sync`. This stage is defined as `builder`, which gives us a way to interact with it later.

What essentially happens here is similar to what happens when you install a project locally using a package manager: dependencies are installed and the project is set up. The primary artifact we want is the installed project with all its dependencies.

Part 2, the runtime, looks something like this:

```dockerfile
FROM python:3.11.9-slim-bookworm
# Runtime stage
FROM python:3.12-slim-bookworm

WORKDIR /app
# Copy the application from the builder
COPY --from=builder --chown=app:app /app /app

COPY --from=builder /app .
COPY [python-app]/ ./[python-app]
# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"

CMD ["/app/.venv/bin/python", "[python-app]/app.py"]
# Run the application
CMD ["python", "/app/src/your_app_name/main.py"]
```

Here we see very little actually going on; instead of the "fat" image, we now pick the slim variant. This one is about 5 times smaller in size, but is unable to compile many of the dependencies we would want compiled. We have already done that part though, so we can copy that `.venv/` folder over to this image without having to compile it again.
Here we see very little actually going on; we use a slim Python image that matches the builder's Python version. We've already done the compilation and installation in the builder stage, so we can copy the entire `/app` directory (which includes the virtual environment) from the builder stage to this runtime image.

With this setup our image will be around 200MB most of the time (depending on what else you include). This setup is used for nearly all Python apps you deploy on Fly.io.

<div class="note icon">
The image size is largely dependent on what files you add in the dockerfile; by default the entire working directory is copied in. If you do not want to add certain files, you can specify them in a `.dockerignore` file.
</div>