---
title: Python (Experimental)
description: Use experimental native uv workspace support with Turborepo.
product: turborepo
type: integration
summary: Discover uv workspace members as Turborepo packages and run native uv tasks.
prerequisites:
  - /docs/crafting-your-repository/structuring-a-repository
  - /docs/crafting-your-repository/configuring-tasks
related:
  - /docs/guides/multi-language
  - /docs/crafting-your-repository/caching
  - /docs/crafting-your-repository/running-tasks
---

# Python (Experimental)



Turborepo can discover the members of [a uv workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/) as packages, add their dependency relationships to the Package Graph, and map common Turborepo tasks to uv commands.

<Callout type="warn">
  uv support is experimental and can change at any time. We encourage you to try
  it out in side projects, proof-of-concepts, and other environments where
  stability is not essential. Please provide feedback on the [Python
  RFC](https://github.com/vercel/turborepo/discussions/13625).
</Callout>

## Enable uv workspaces

Set the Future Flag in the root `turbo.json`:

```json title="./turbo.json"
{
  "$schema": "https://turborepo.dev/schema.json",
  "futureFlags": {
    "experimentalPythonWorkspaces": true
  },
  "tasks": {}
}
```

## Prerequisites

To work with Python, the repository root must contain:

* `uv` available on `PATH`
* A root `pyproject.toml` containing a [uv workspace](https://docs.astral.sh/uv/concepts/projects/workspaces/)
* A valid `[tool.turbo].name` for identity in Turborepo's graph
* A root `uv.lock`. Turborepo never creates or updates it; run `uv lock` to refresh it.

### Repository structure

[uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) are natively understood by Turborepo.

```toml title="./pyproject.toml"
[tool.turbo]
name = "acme-python"

[tool.uv.workspace]
members = ["packages/*"]
```

In the example above, members are defined as `packages/*`. uv then uses every `packages/*/pyproject.toml` as a member in the workspace. Each member is added to Turborepo's understanding of your repository, identified by its [PEP 503-normalized](https://peps.python.org/pep-0503/#normalized-names) project name.

uv packages come in two types:

* **Member packages** are Package Graph nodes so filtering and affectedness calculations follow Python dependency relationships. They expose verification tasks, and buildable members (those with a `[build-system]` table or `[tool.uv] package = true`) also expose `build`.
* **The root workspace package** uses `[tool.turbo] name`, depends on every member, and runs verification tasks across the whole uv workspace.

Both package types can be used as targets for [`--filter`](/docs/reference/run#--filter-string) and [`affected`](/docs/reference/run#--affected) calculations.

## Built-in tasks

Turborepo natively registers tasks that are common to all uv workspaces.

| Package           | Turbo task     | uv command                           |
| ----------------- | -------------- | ------------------------------------ |
| Buildable member  | `turbo build`  | `uv build --package=<name>`          |
| Any member        | `turbo format` | `uv format -- <member-dir>`          |
| Any member        | `turbo check`  | `uv check --frozen --package=<name>` |
| Workspace package | `turbo format` | `uv format -- <member-dirs...>`      |
| Workspace package | `turbo check`  | `uv check --frozen --all-packages`   |

Turborepo also detects common Python tools declared in `[project].dependencies`, `[dependency-groups]`, or `[tool.uv].dev-dependencies` and maps tasks to them. Detected tools replace the `format` and `check` fallbacks above.

| Declared tool | Turbo task                           | uv command                                                    |
| ------------- | ------------------------------------ | ------------------------------------------------------------- |
| Ruff          | `turbo lint`, `turbo lint:ruff`      | `uv run --active --frozen --package <name> ruff check <dir>`  |
| Ruff          | `turbo format`, `turbo format:ruff`  | `uv run --active --frozen --package <name> ruff format <dir>` |
| Black         | `turbo format`, `turbo format:black` | `uv run --active --frozen --package <name> black <dir>`       |
| mypy          | `turbo check`, `turbo check:mypy`    | `uv run --active --frozen --package <name> mypy <dir>`        |
| ty            | `turbo check`, `turbo check:ty`      | `uv run --active --frozen --package <name> ty check <dir>`    |
| Pyright       | `turbo check`, `turbo check:pyright` | `uv run --active --frozen --package <name> pyright <dir>`     |
| pytest        | `turbo test`                         | `uv run --active --frozen --package <name> pytest <dir>`      |

Tools declared in the root `pyproject.toml` apply to every member unless a member declares its own tool for that role, and root-declared tools run once without `--package`. `lint` and `check` run every detected tool for that role.

* `format` runs one formatter, preferring Ruff over Black.
* A root pytest declaration creates one repository-wide `test` task running `uv run --active --frozen --all-packages pytest`, installing src-layout workspace members before collection. A member declaration creates `test` for that member only. When both declare pytest, both scopes run.
* Without a declared type checker, the built-in `check` task runs uv's bundled ty type checker. Declared mypy, ty, or Pyright tools take precedence when present.

You can use [`--filter`](/docs/reference/run#--filter-string) to target a specific member in the workspace.

### Pass uv arguments

Arguments after Turborepo's `--` are passed to the mapped command. For detected tools, Turborepo inserts them before the member directory targets:

```bash title="Terminal"
turbo run lint:ruff --filter=py-api -- --fix
turbo run test --filter=py-api -- -k smoke
turbo run format -- --check
```

`lint` and `check` reject arguments because they may fan out to several tools. Run the qualified task named in the error instead, such as `turbo run check:mypy --filter=py-api -- --strict`. Arguments to `build` are appended to `uv build --package=<name>` and disable automatic output detection because options such as `--out-dir` can relocate artifacts.

## Create your own tasks

To define a uv-backed task that is not built in, or replace a built-in mapping, enable `experimentalTaskCommand` and set the task's `command`. The command is an argument array that runs directly without a shell. For example, the synthetic workspace package can add a `docs` task and a member can make its `lint` task stricter:

```json title="./turbo.json"
{
  "$schema": "https://turborepo.dev/schema.json",
  "futureFlags": {
    "experimentalPythonWorkspaces": true,
    "experimentalTaskCommand": true
  },
  "tasks": {
    "acme-python#docs": {
      "command": ["uv", "run", "--frozen", "mkdocs", "build"]
    },
    "py-api#lint": {
      "command": [
        "uv",
        "run",
        "--frozen",
        "--package",
        "py-api",
        "ruff",
        "check",
        "--select",
        "ALL"
      ]
    }
  }
}
```

## Caching behavior

With zero configuration, Turborepo creates task hashes using:

* The selected member's source files, plus its internal dependency sources for `check` and `test`
* Every member's source files when verification runs through the workspace package, and the whole repository for a root pytest task because pytest controls collection
* Root uv files (`pyproject.toml`, `uv.toml`, `.python-version`) and supported tool configuration such as `ruff.toml`, `mypy.ini`, `pyrightconfig.json`, `pytest.ini`, and `ty.toml`
* Relevant uv and pip environment variables
* The resolved external dependency closure from `uv.lock`, scoped to each member
* The uv and Python interpreter identities. If Turborepo cannot resolve them, uv tasks remain runnable but implicit caching is disabled with a warning.

Automatic inputs exclude `.venv`, `__pycache__`, and tool cache directories such as `.ruff_cache`, `.mypy_cache`, and `.pytest_cache` at any depth. Because formatting mutates source files, `format` tasks default to uncached. The built-in `uv check` task is cacheable when Turborepo can identify uv and Python.

Project-specific hashing inputs must be accounted for manually. This includes:

* Environment variables read by your tools still need to be declared in the task's [`env`](/docs/reference/configuration#env) configuration
* File inputs that are not included by default. Use [`inputs`](/docs/reference/configuration#inputs) to define your own file inputs and [`$TURBO_DEFAULT$`](/docs/reference/configuration#turbo_default) to preserve zero-configuration file inputs

### Output caching

Automatic output caching stores only the sdist and wheel that a bare `uv build` writes to the workspace `dist/` directory. It does not cache `.venv`, which remains uv's own materialized environment.

Builds whose only build requirement is `uv_build` are cacheable by default. Other PEP 517 build backends default to `cache: false` because uv resolves their isolated build dependencies independently of `uv.lock`. Enabling those builds requires an explicit `cache: true`; configure `outputs` as needed to describe restorable artifacts.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)