UV Usage part 2

This post follows on from UV Usage written a few days ago, where I wrote about about moving to using UV and the commonly-used commands: run, add, remove. Here I’ll cover adding it to an existing project, which is what I did. Creating one from scratch is pretty well covered in the documentation.

In the case of zpdatafetch, I wasn’t starting clean. I had an existing pyproject.toml. I decided to start just adding things to see how it worked.

Since the venv was not set up in the way that uv would build it, I simply removed it entirely to start clean. Then I tried uv init. This didn’t work:

error: Project is already initialized in `/Users/doug/Development/Zwift/zpd/zpdatafetch`

So, I decided to just add the dependencies and see what happened. This also failed, but seemed to be moving in the right direction:

$ uv add beautifulsoup4 httpx lxml keyring keyrings-alt

warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
error: Failed to build: `zpdatafetch @ file:///Users/doug/Development/Zwift/zpd/zpdatafetch`
  Caused by: Build backend failed to determine extra requires with `build_editable()` with exit status: 1
--- stdout:
configuration error: You cannot provide a value for `project.dependencies` and list it under `project.dynamic` at the same time
DESCRIPTION:
    According to PEP 621:  Build back-ends MUST raise an error if the metadata
    specifies a field statically as well as being listed in dynamic.

GIVEN VALUE:
    {
        "dependencies": [
            "beautifulsoup4",
            "httpx",
            "lxml",
            "keyring"
        ],
        "...": " # ...",
        "dynamic": [
            "version",
            "authors",
            "description",
            "readme",
            "license",
            "urls",
            "dependencies"
        ]
    }

OFFENDING RULE: 'PEP 621'

DEFINITION:
    {
        "see": "https://packaging.python.org/en/latest/specifications/pyproject-toml/#dynamic"
    }
--- stderr:
Traceback (most recent call last):
  File "<string>", line 14, in <module>
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/build_meta.py", line 464, in get_requires_for_build_editable
    return self.get_requires_for_build_wheel(config_settings)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/build_meta.py", line 332, in get_requires_for_build_wheel
    return self._get_build_requires(config_settings, requirements=[])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/build_meta.py", line 302, in _get_build_requires
    self.run_setup()
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/build_meta.py", line 318, in run_setup
    exec(code, locals())
  File "<string>", line 1, in <module>
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/__init__.py", line 117, in setup
    return distutils.core.setup(**attrs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/_distutils/core.py", line 158, in setup
    dist.parse_config_files()
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/_virtualenv.py", line 22, in parse_config_files
    result = old_parse_config_files(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/dist.py", line 608, in parse_config_files
    pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 71, in apply_configuration
    config = read_configuration(filepath, True, ignore_option_errors, dist)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 136, in read_configuration
    validate(subset, filepath)
  File "/Users/doug/Library/Caches/uv/builds-v0/.tmpnGXgAH/lib/python3.12/site-packages/setuptools/config/pyprojecttoml.py", line 60, in validate
    raise ValueError(f"{error}\n{summary}") from None
ValueError: invalid pyproject.toml config: `project.dependencies`.
configuration error: You cannot provide a value for `project.dependencies` and list it under `project.dynamic` at the same time
---

In this case, the problem was that I had dependencies listed in the dynamic section of the pyproject.toml. I removed them and tried again:

$ uv add beautifulsoup4 httpx lxml keyring keyrings-alt
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
Resolved 23 packages in 3.54s
   Built zpdatafetch @ file:///Users/doug/Development/Zwift/zpd/zpdatafetch
Prepared 17 packages in 2.39s
Installed 17 packages in 22ms
 + anyio==4.4.0
 + beautifulsoup4==4.12.3
 + certifi==2024.7.4
 + h11==0.14.0
 + httpcore==1.0.5
 + httpx==0.27.2
 + idna==3.8
 + jaraco-classes==3.4.0
 + jaraco-context==6.0.1
 + jaraco-functools==4.0.2
 + keyring==25.3.0
 + keyrings-alt==5.0.2
 + lxml==5.3.0
 + more-itertools==10.4.0
 + sniffio==1.3.1
 + soupsieve==2.6
 + zpdatafetch==1.1.0 (from file:///Users/doug/Development/Zwift/zpd/zpdatafetch)

Bingo!. So next the dev dependencies:

$ uv add --dev pytest ruff setuptools build wheel
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
Resolved 33 packages in 3.22s
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
   Built zpdatafetch @ file:///Users/doug/Development/Zwift/zpd/zpdatafetch
Prepared 10 packages in 3.27s
Uninstalled 1 package in 3ms
Installed 10 packages in 21ms
 + build==1.2.1
 + iniconfig==2.0.0
 + packaging==24.1
 + pluggy==1.5.0
 + pyproject-hooks==1.1.0
 + pytest==8.3.2
 + ruff==0.6.2
 + setuptools==74.0.0
 + wheel==0.44.0
 ~ zpdatafetch==1.1.0 (from file:///Users/doug/Development/Zwift/zpd/zpdatafetch)

In both cases the system is giving a warning about the python version. I have tested 3.10 and up. So I added the requirement to the project section:

[project]
requires-python = ">=3.10"

And then ran uv sync and he that seemed to take care of the issue:

$ uv sync
Resolved 39 packages in 3.08s
   Built zpdatafetch @ file:///Users/doug/Development/Zwift/zpd/zpdatafetch
Prepared 1 package in 3.10s
Uninstalled 1 package in 3ms
Installed 1 package in 13ms
 ~ zpdatafetch==1.1.0 (from file:///Users/doug/Development/Zwift/zpd/zpdatafetch)

At this point, not only were all my dependencies installed, but I had a working install set up in my venv. The zpdata command, which is installed as an executable with the package, was installed in .venv/bin along with the other tools from the dependencies: pytest, httpx, keyring, ruff, etc.

A quick test showed that the command installed and works:

$ uv run zpdata -h
usage: zpdata [-h] [-v] [{config,cyclist,primes,result,signup,team}] [id ...]

Module for fetching zwiftpower data using the Zwifpower API

positional arguments:
  {config,cyclist,primes,result,signup,team}
                        which command to run
  id                    the id to search for, ignored for config

options:
  -h, --help            show this help message and exit
  -v, --verbose         provide feedback while running

All other commands work as expected under uv run as well.

In the next update, I’ll write about how I integrated uv into github actions and published the next version of zpdatafetch with uv.

© Doug Morris
Written on 28 August 2024