astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
25.78k stars 750 forks source link

Development dependency group support #5632

Closed zanieb closed 2 weeks ago

zanieb commented 3 months ago

This is a tracking issue for the general idea of putting development dependencies into named groups for partial installs.

In part, we're waiting to see where PEP 735 goes, which

... specifies a mechanism for storing package requirements in pyproject.toml files such that they are not included in any built distribution of the project.

This is suitable for creating named groups of dependencies, similar to requirements.txt files, which launchers, IDEs, and other tools can find and identify by name.

The feature defined here is referred to as “Dependency Groups”.

Related:

zanieb commented 3 months ago

@mishamsk regarding

are there any particular plans for PEP 735 functionality? Like for example "we'll wait for PEP 735 to be accepted and will not tackle the case until it is pending", or maybe "we may get to it sooner o despite PEP 735 status but not earlier than X months from now"

We may tackle this behavior before PEP 735 is finalized, but we are not going to include this in our upcoming stable release (in the next couple months). In the meantime, we want to:

1) Learn more about use-cases, e.g. people say they need dependency groups for install speed but that it's largely less of a problem with uv's improved performance 2) Explore how this would work with uv's CLI 3) Plan for transition to PEP 735's spec if it looks like it will be accepted

mishamsk commented 3 months ago

@zanieb thanks.

Learn more about use-cases, e.g. people say they need dependency groups for install speed but that it's largely less of a problem with uv's improved performance

I can elaborate a bit on my use case assuming this is the right place:

So first a rough setup we have for context:

Group requirements

I think this is it... I'll add more if I remember something.

Sorry for a long read

zanieb commented 3 months ago

Thanks for the context!

Some group dependencies may be incompatible with the project dependencies

You highlight this already, but this is a problem in Poetry because it solves for all groups at once and does not allow incompatibilities. This is a problem for us too! If we allow incompatible groups, we have to solve for all possible combinations of the groups to create a lockfile for the project. We're hoping this case is addressed by project-level tools or perhaps in PEP 735.

mishamsk commented 3 months ago

yes, at least in my case, this is essentially about project-level tools. E.g., I have the latest ruff system wide, but lock different versions for specific projects.

From the vantage point of this use cases, it may make sense to have all the groups in their own isolated environments. I know that project-level tools are out of scope for now, but I can imagine a simple solution where "project-level tool" is just telling uv that a specific version of a tool should be used inside this specific project. Then it will work similar to how pyenv/asdf/mise/rye etc handle python itself - shims/path manipulation. No local secondary .venv's for tools, no cross compilation of dependencies etc.

But others may come up with other use cases. Poetry threads related to groups are numerous and very long!

chrisrodrigue commented 3 months ago

I think basic support for development dependency groups and scripts could replace other popular environment management/command running tools like hatch and tox for use cases like @mishamsk’s.

These features would provide a good way to organize dependencies, modularize CI/CD pipelines, and standardize common commands/aliases among teammates and CI/CD by centralizing them in pyproject.toml.

Example

pyproject.toml

[tool.uv.dev-dependencies]
check = [
  "codespell>=2.3.0", 
  "mypy>=1.11.1", 
  "pre-commit>=3.8.0", 
  "ruff>=0.5.6",
]
test = [
  "pytest>=8.3.2",
  "pytest-cov>=5.0.0",
  "pytest-mock>=3.14.0",
]
doc = [
  "mkdocs>=1.6.0",
  "mkdocs-material>=9.5.31",
  "mkdocstrings[python]>=0.25.2",
]
release = [
  "commitizen>=3.28.0",
  "cyclonedx-bom>=4.5.0",
]

[tool.uv.commands]
check = ["codespell .", "ruff check .", "ruff format .", "mypy ."]
test = "pytest"
doc = "mkdocs serve"
cov = "pytest --cov --cov-report term-missing --cov-report xml --cov-report html"
hooks = "pre-commit run --all-files"
sbom = "cyclonedx-py environment .venv --outfile sbom.json --output-format json"
clean = "git clean -fdx"

CLI Usage

Dependency groups

uv add -g test pytest // add pytest to “test” dev dependency group
uv sync -dg test // only install deps and test deps to .venv
uv sync -dog test -g doc // deps, optional deps, test/doc deps
uv sync // install all deps, optional deps, and dev deps

Command running

uv check // or uv run check
uv doc
uv clean

CI/CD Usage

jobs:
  check:
    name: "check"
    runs-on: alpine-latest
    steps:
       - name: "Prepare environment"
         run: uv sync -dg check
       - name: "Spell check"
         run: uv run codespell .
       - name: "Static analysis"
         run: uv run ruff check .
       - name: "Type check"
         run: uv run mypy .
  test:
    name: "test"
    runs-on: alpine-latest
    steps:
      - name: "Prepare environment"
        run: uv sync -dg test
      - name: "Code coverage"
        run: uv cov --run unit
      - name: "Integration tests"
        run: uv test --run integration
lewoudar commented 2 months ago

Hi everyone, I have a workflow almost similar to @chrisrodrigue, so I really hope this feature will be added. It will help poetry (or pdm) users like me to switch to uv.

schlamar commented 2 months ago

I think one important use case when talking about replacing tox or hatch wasn't explicitly mentioned yet. The possibility to target multiple python versions. When you develop libraries instead of applications you want to support a wide range of Python versions and define test environment for various Python versions.

Another use case would be targeting different versions of a library. For example when you are developing a pytest plugin you want to test this against various versions of pytest.

These use cases should be considered when uv is providing a feature for multiple development environments.

See for example matrix documentation from hatch: https://hatch.pypa.io/1.12/environment/#matrix

schlamar commented 2 months ago

Here are two real world tox configurations covering these use cases.

https://github.com/pallets/flask/blob/main/tox.ini https://github.com/pytest-dev/pytest-django/blob/main/tox.ini

Samoed commented 2 months ago

PDM support this and have some use cases examples (I have similar use cases) https://pdm-project.org/latest/usage/dependency/#add-development-only-dependencies

sebastian-correa commented 2 months ago

Our usecase for dependency groups is "distribution".

We develop in a bigish application with many components in a single package. For example, imagine the package is package and inside we have component_a, component_b and models, which itself has 2 components inside (2 .py files).

These components are executed by running some Docker images we generate (one per component). These docker images install package but only install the dependency groups related to each component. For example, say component_a needs to interact with a cloud provider, a database and uses a specific framework but component_b doesn't need any of these. In that scenario, we do poetry install --sync --only main --only cloud_provider --only database --only framework_and_related_deps. This makes Docker builds faster and also results in smaller images.

In that same vein, some GitHub Actions only need some dependency groups to run, which makes the venv cache recovery faster (because resulting venvs are smaller).

I recognize that this compartmentalization is a bit horrible as it needs human knowledge of which sub-packages use which dependency groups, and only works because the packages imported when executing component_a/entrypoint.py are some of them (and not all), but it's been working for us to reduce Docker image sizes.

gotmax23 commented 1 month ago

I like to have separation of concerns so I know which development dependencies are used for what, and for test sessions in nox (typing, linting, pytest unit tests), I don't want to have extra dependencies that a user won't have if they just install the package in a clean environment impact the results or have tests that only pass because of extra transitive dependencies pulled in by other development tools.

mmlynarik commented 1 month ago

Our usecase for dependency groups is "distribution".

We develop in a bigish application with many components in a single package. For example, imagine the package is package and inside we have component_a, component_b and models, which itself has 2 components inside (2 .py files).

These components are executed by running some Docker images we generate (one per component). These docker images install package but only install the dependency groups related to each component. For example, say component_a needs to interact with a cloud provider, a database and uses a specific framework but component_b doesn't need any of these. In that scenario, we do poetry install --sync --only main --only cloud_provider --only database --only framework_and_related_deps. This makes Docker builds faster and also results in smaller images.

I totally agree with this distribution problem description and add one vote in favor of this use case that imho justifies the need for dependency groups in corporate setup.

michelkok commented 1 month ago

Another usecase is for part of the Machine Learning community where GPU dependencies should only be installed at training time, but during inference can be handled by the much smaller CPU dependencies.

jankatins commented 3 weeks ago

Seems this is now tracked in https://github.com/astral-sh/uv/issues/8090 so either this or the new issue could be closed as duplicate.

zanieb commented 2 weeks ago

As of 0.4.27 we support this. See #8272.