Skip to content

Pull-through catching PyPI and using uv leads to metadata issues #1083

Closed

Description

@Benzor94

Version
pulpcore: 3.102.0
pulp-python: 3.24.1

Installation method: Podman, via this docker-compose:

services:
  pulp:
    image: pulp/pulp
    container_name: pulp
    ports:
      - "8080:80"
    cap_add:
      - SYS_ADMIN
    security_opt:
      - label=disable
    devices:
      - /dev/fuse
    volumes:
      - ./settings:/etc/pulp:U
      - pulp_storage:/var/lib/pulp
      - pgsql:/var/lib/pgsql
      - containers:/var/lib/containers
      - container_build:/var/lib/pulp/.local/share/containers
    restart: unless-stopped

volumes:
  pulp_storage:
  pgsql:
  containers:
  container_build:

Describe the bug
Setting up a repository, a corresponding distribution, a remote pointing to pypi, in pull-through caching mode, the setup works with pip, but uv (https://docs.astral.sh/uv/) is unable to resolve dependencies.

To Reproduce

  1. Do the standard setup of pulp and pulp-cli as described in the tutorials.
  2. Set up repository, distribution and remote as follows:
    pulp python repository create --name orgalorg-repo

pulp python distribution create --name orgalorg-dist --base-path orgalorg --repository orgalorg-repo

pulp python remote create --name pypi --url https://pypi.org/ --policy on_demand

pulp python distribution update --name orgalorg-dist --remote pypi

  1. Set up a dummy project via uv (e.g. uv init dummyproject) and in the created pyproject.toml write
[project]
name = "dummyenv"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = ["requests==2.32.5"]

[[tool.uv.index]]
name = "orgalorg"
url = "http://localhost:8080/pypi/orgalorg/simple"
default = true

Vary the dependencies, for example I have tried requests, requests==2.32.5 and numpy.

  1. Run uv sync

  2. Observe that uv fails to resolve dependencies (see later for additional data).

  3. To ensure things work with pip, destroy the venv (rm -rf .venv), then

python -m venv .venv
source .venv/bin/activate
pip install requests==2.32.5

Observe that pip does this successfully and running pulp content list (in a venv where pulp-cli has been installed) will display the downloaded package (Note: aborted resolution attempts by uv can also result in some packages being downloaded and cached).

  1. Destroy the venv again, and try uv sync again, for example with requests==2.32.5 in the dependencies. Again observe failure but now for different reasons (the package is now cached in the pulp server).

  2. Recreate the venv (uv venv) if it doesn't exist and run uv pip install requests=2.32.5 --no-deps. Observe that now uv installs the package successfully (but without its dependencies), confirming that this is not a connection problem but a dependency resolution problem.

Expected behavior
uv should be able to resolve dependencies through the proxy.

Additional context

Output when trying to sync project with uv with requests unconstrained:

benzor@orgalorg:~/Projects/Local/dummyenv$ uv sync
  × Failed to download and build `requests==1.2.3`
  ├─▶ Failed to resolve requirements from `setup.py` build
  ├─▶ No solution found when resolving: `setuptools>=40.8.0`
  ╰─▶ Because only the following versions of setuptools are available:
          setuptools<=40.8.0
          setuptools==40.9.0
          setuptools==41.0.0
          setuptools==41.0.1
          setuptools==41.1.0
          setuptools==41.2.0
          setuptools==41.3.0
          setuptools==41.4.0
          setuptools==41.5.0
          setuptools==41.5.1
          setuptools==41.6.0
          setuptools==42.0.0
          setuptools==42.0.1
          setuptools==42.0.2
          setuptools==43.0.0
          setuptools==44.0.0
          setuptools==44.1.0
          setuptools==44.1.1
          setuptools==45.0.0
          setuptools==45.1.0
          setuptools==45.2.0
          setuptools==45.3.0
          setuptools==46.0.0
          setuptools==46.1.0
          setuptools==46.1.1
          setuptools==46.1.2
          setuptools==46.1.3
          setuptools==46.2.0
          setuptools==46.3.0
          setuptools==46.3.1
          setuptools==46.4.0
          setuptools==47.0.0
          setuptools==47.1.0
          setuptools==47.1.1
          setuptools==47.2.0
          setuptools==47.3.0
          setuptools==47.3.1
          setuptools==47.3.2
          setuptools==48.0.0
          setuptools==49.0.0
          setuptools==49.0.1
          setuptools==49.1.0
          setuptools==49.1.1
          setuptools==49.1.2
          setuptools==49.1.3
          setuptools==49.2.0
          setuptools==49.2.1
          setuptools==49.3.0
          setuptools==49.3.1
          setuptools==49.3.2
          setuptools==49.4.0
          setuptools==49.5.0
          setuptools==49.6.0
          setuptools==50.0.0
          setuptools==50.0.1
          setuptools==50.0.2
          setuptools==50.0.3
          setuptools==50.1.0
          setuptools==50.2.0
          setuptools==50.3.0
          setuptools==50.3.1
          setuptools==50.3.2
          setuptools==51.0.0
          setuptools==51.1.0
          setuptools==51.1.0.post20201221
          setuptools==51.1.1
          setuptools==51.1.2
          setuptools==51.2.0
          setuptools==51.3.0
          setuptools==51.3.1
          setuptools==51.3.2
          setuptools==51.3.3
          setuptools==52.0.0
          setuptools==53.0.0
          setuptools==53.1.0
          setuptools==54.0.0
          setuptools==54.1.0
          setuptools==54.1.1
          setuptools==54.1.2
          setuptools==54.1.3
          setuptools==54.2.0
          setuptools==56.0.0
          setuptools==56.1.0
          setuptools==56.2.0
          setuptools==57.0.0
          setuptools==57.1.0
          setuptools==57.2.0
          setuptools==57.3.0
          setuptools==57.4.0
          setuptools==57.5.0
          setuptools==58.0.0
          setuptools==58.0.1
          setuptools==58.0.2
          setuptools==58.0.3
          setuptools==58.0.4
          setuptools==58.1.0
          setuptools==58.2.0
          setuptools==58.3.0
          setuptools==58.4.0
          setuptools==58.5.0
          setuptools==58.5.1
          setuptools==58.5.2
          setuptools==58.5.3
          setuptools==59.0.1
          setuptools==59.1.0
          setuptools==59.1.1
          setuptools==59.2.0
          setuptools==59.3.0
          setuptools==59.4.0
          setuptools==59.5.0
          setuptools==59.6.0
          setuptools==59.7.0
          setuptools==59.8.0
          setuptools==60.0.0
          setuptools==60.0.1
          setuptools==60.0.2
          setuptools==60.0.3
          setuptools==60.0.4
          setuptools==60.0.5
          setuptools==60.1.0
          setuptools==60.1.1
          setuptools==60.2.0
          setuptools==60.3.0
          setuptools==60.3.1
          setuptools==60.4.0
          setuptools==60.5.0
          setuptools==60.6.0
          setuptools==60.7.0
          setuptools==60.7.1
          setuptools==60.8.0
          setuptools==60.8.1
          setuptools==60.8.2
          setuptools==60.9.0
          setuptools==60.9.1
          setuptools==60.9.2
          setuptools==60.9.3
          setuptools==60.10.0
          setuptools==61.0.0
          setuptools==61.1.0
          setuptools==61.1.1
          setuptools==61.2.0
          setuptools==61.3.0
          setuptools==61.3.1
          setuptools==62.0.0
          setuptools==62.1.0
          setuptools==62.2.0
          setuptools==62.3.0
          setuptools==62.3.1
          setuptools==62.3.2
          setuptools==62.3.3
          setuptools==62.3.4
          setuptools==62.4.0
          setuptools==62.5.0
          setuptools==62.6.0
          setuptools==63.0.0
          setuptools==63.1.0
          setuptools==63.2.0
          setuptools==63.3.0
          setuptools==63.4.0
          setuptools==63.4.1
          setuptools==63.4.2
          setuptools==63.4.3
          setuptools==64.0.0
          setuptools==64.0.1
          setuptools==64.0.2
          setuptools==64.0.3
          setuptools==65.0.0
          setuptools==65.0.1
          setuptools==65.0.2
          setuptools==65.1.0
          setuptools==65.1.1
          setuptools==65.2.0
          setuptools==65.3.0
          setuptools==65.4.0
          setuptools==65.4.1
          setuptools==65.5.0
          setuptools==65.5.1
          setuptools==65.6.0
          setuptools==65.6.1
          setuptools==65.6.2
          setuptools==65.6.3
          setuptools==65.7.0
          setuptools==66.0.0
          setuptools==66.1.0
          setuptools==66.1.1
          setuptools==67.0.0
          setuptools==67.1.0
          setuptools==67.2.0
          setuptools==67.3.1
          setuptools==67.3.2
          setuptools==67.3.3
          setuptools==67.4.0
          setuptools==67.5.0
          setuptools==67.5.1
          setuptools==67.6.0
          setuptools==67.6.1
          setuptools==67.7.0
          setuptools==67.7.1
          setuptools==67.7.2
          setuptools==67.8.0
          setuptools==68.0.0
          setuptools==68.1.0
          setuptools==68.1.2
          setuptools==68.2.0
          setuptools==68.2.1
          setuptools==68.2.2
          setuptools==69.0.0
          setuptools==69.0.1
          setuptools==69.0.2
          setuptools==69.0.3
          setuptools==69.1.0
          setuptools==69.1.1
          setuptools==69.2.0
          setuptools==69.3.0
          setuptools==69.3.1
          setuptools==69.4.0
          setuptools==69.4.1
          setuptools==69.4.2
          setuptools==69.5.0
          setuptools==69.5.1
          setuptools==70.0.0
          setuptools==70.1.0
          setuptools==70.1.1
          setuptools==70.2.0
          setuptools==70.3.0
          setuptools==71.0.0
          setuptools==71.0.1
          setuptools==71.0.2
          setuptools==71.0.3
          setuptools==71.0.4
          setuptools==71.1.0
          setuptools==72.0.0
          setuptools==72.1.0
          setuptools==72.2.0
          setuptools==73.0.0
          setuptools==73.0.1
          setuptools==74.0.0
          setuptools==74.1.0
          setuptools==74.1.1
          setuptools==74.1.2
          setuptools==74.1.3
          setuptools==75.0.0
          setuptools==75.1.0
          setuptools==75.2.0
          setuptools==75.3.0
          setuptools==75.3.1
          setuptools==75.3.2
          setuptools==75.3.3
          setuptools==75.4.0
          setuptools==75.5.0
          setuptools==75.6.0
          setuptools==75.7.0
          setuptools==75.8.0
          setuptools==75.8.1
          setuptools==75.8.2
          setuptools==75.9.0
          setuptools==75.9.1
          setuptools==76.0.0
          setuptools==76.1.0
          setuptools==77.0.1
          setuptools==77.0.3
          setuptools==78.0.1
          setuptools==78.0.2
          setuptools==78.1.0
          setuptools==78.1.1
          setuptools==79.0.0
          setuptools==79.0.1
          setuptools==80.0.0
          setuptools==80.0.1
          setuptools==80.1.0
          setuptools==80.2.0
          setuptools==80.3.0
          setuptools==80.3.1
          setuptools==80.4.0
          setuptools==80.6.0
          setuptools==80.7.0
          setuptools==80.7.1
          setuptools==80.8.0
          setuptools==80.9.0
          setuptools==80.10.1
          setuptools==80.10.2
      and setuptools>=40.8.0 has invalid metadata, we can conclude that setuptools>=40.8.0 cannot be used.
      And because you require setuptools>=40.8.0, we can conclude that your requirements are unsatisfiable.

      hint: Only pre-releases of `setuptools` (e.g., 63.0.0b1) match these build requirements, and
      build environments can't enable pre-releases automatically. Add `setuptools>=63.0.0b1` to
      `build-system.requires`, `[tool.uv.extra-build-dependencies]`, or supply it via `uv build
      --build-constraint`.

      hint: Metadata for `setuptools` (v80.10.2) could not be parsed:
        Metadata field Name not found
  help: `requests` (v1.2.3) was included because `dummyenv` (v0.1.0) depends on `requests`

Output when trying to sync with requests==2.32.5:

benzor@orgalorg:~/Projects/Local/dummyenv$ uv sync
  × No solution found when resolving dependencies:
  ╰─▶ Because requests==2.32.5 has invalid metadata and your project depends on requests==2.32.5, we can
      conclude that your project's requirements are unsatisfiable.

      hint: Metadata for `requests` (v2.32.5) could not be parsed:
        Header cannot start with a space; it is likely an overhanging line from a previous header

Logs in the container when trying to sync with requests==2.32.5:

('pulp [fa62431e7c8547ef92b483ea5742dcb5]: ::1 - - [28/Jan/2026:21:35:42 +0000] "GET /pypi/orgalorg/simple/requests/ HTTP/1.0" 200 99533 "-" "uv/0.9.27 {\"installer\":{\"name\":\"uv\",\"version\":\"0.9.27\",\"subcommand\":[\"sync\"]},\"python\":\"3.14.2\",\"implementation\":{\"name\":\"CPython\",\"version\":\"3.14.2\"},\"distro\":{\"name\":\"Fedora Linux\",\"version\":\"43\",\"id\":\"\",\"libc\":{\"lib\":\"glibc\",\"version\":\"2.42\"}},\"system\":{\"name\":\"Linux\",\"release\":\"6.18.6-200.fc43.x86_64\"},\"cpu\":\"x86_64\",\"openssl_version\":null,\"setuptools_version\":null,\"rustc_version\":null,\"ci\":null}"',)
[2026-01-28 21:35:42 +0000] [485] [ERROR] Error handling request from ::1
Traceback (most recent call last):
  File "/usr/local/lib64/python3.11/site-packages/aiohttp/web_protocol.py", line 510, in _handle_request
    resp = await request_handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib64/python3.11/site-packages/aiohttp/web_app.py", line 569, in _handle
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib64/python3.11/site-packages/aiohttp/web_middlewares.py", line 117, in impl
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/authentication.py", line 27, in guid
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/authentication.py", line 58, in authenticate
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/handler.py", line 291, in stream_content
    return await self._match_and_stream(path, request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/handler.py", line 922, in _match_and_stream
    return await self._stream_remote_artifact(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/handler.py", line 1348, in _stream_remote_artifact
    content_artifacts = await asyncio.shield(
                        ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 504, in __call__
    ret = await asyncio.shield(exec_coro)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 559, in thread_handler
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulpcore/content/handler.py", line 1052, in _save_artifact
    content = c_type.init_from_artifact_and_relative_path(artifact, rel_path)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulp_python/app/models.py", line 217, in init_from_artifact_and_relative_path
    data = artifact_to_python_content_data(path.name, artifact, domain=get_domain())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulp_python/app/utils.py", line 252, in artifact_to_python_content_data
    metadata = get_project_metadata_from_file(temp_file.name)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pulp_python/app/utils.py", line 196, in get_project_metadata_from_file
    pkg_type_index = [filename.endswith(ext) for ext in extensions].index(True)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: True is not in list

Output when trying to sync with requests==2.32.5 after this package has been cached (by installing successfully through pip):

benzor@orgalorg:~/Projects/Local/dummyenv$ uv sync
Using CPython 3.14.2 interpreter at: /usr/bin/python3.14
Creating virtual environment at: .venv
⠧ requests==2.32.5                                                                                            error: Failed to fetch: `http://orgalorg:8080/pulp/content/orgalorg/requests-2.32.5-py3-none-any.whl.metadata`
  Caused by: HTTP status client error (404 Error while fetching from upstream remote(https://pypi.org/requests-2.32.5-py3-none-any.whl.metadata): Not Found) for url (http://orgalorg:8080/pulp/content/orgalorg/requests-2.32.5-py3-none-any.whl.metadata)

And corresponding container logs:

[28/Jan/2026:21:39:19 +0000] "GET /pulp/content/orgalorg/requests-2.32.5-py3-none-any.whl.metadata HTTP/1.0" 404 381 "-" "uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["sync"]},"python":"3.14.2","implementation":{"name":"CPython","version":"3.14.2"},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":{"lib":"glibc","version":"2.42"}},"system":{"name":"Linux","release":"6.18.6-200.fc43.x86_64"},"cpu":"x86_64","openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}"

Notes:

I am quite ignorant when it comes to PyPI specifics and packaging in general besides building wheels, but it seems to me the issue is that uv is trying to get .whl.metadata files. I have no idea what these are (as far as I know, metadata is contained in a wheel itself).

The output of pulp content list is

(pulp-server) benzor@orgalorg:~/Projects/Local/pulp-server$ pulp content list
[
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c068b-217b-769e-9308-779cc829e741/",
    "prn": "prn:python.pythonpackagecontent:019c068b-217b-769e-9308-779cc829e741",
    "pulp_created": "2026-01-28T21:38:27.324595Z",
    "pulp_last_updated": "2026-01-28T21:38:27.324607Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "certifi-2026.1.4-py3-none-any.whl": "/pulp/api/v3/artifacts/019c068b-2175-7cf8-a59e-3609e6cdec82/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c068b-1d85-71ec-9172-55f1a957a019/",
    "prn": "prn:python.pythonpackagecontent:019c068b-1d85-71ec-9172-55f1a957a019",
    "pulp_created": "2026-01-28T21:38:26.309999Z",
    "pulp_last_updated": "2026-01-28T21:38:26.310011Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "urllib3-2.6.3-py3-none-any.whl": "/pulp/api/v3/artifacts/019c068b-1d7f-7434-a594-f1b59069e40e/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c068b-1bb6-7795-9621-541c2b381220/",
    "prn": "prn:python.pythonpackagecontent:019c068b-1bb6-7795-9621-541c2b381220",
    "pulp_created": "2026-01-28T21:38:25.847351Z",
    "pulp_last_updated": "2026-01-28T21:38:25.847360Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "idna-3.11-py3-none-any.whl": "/pulp/api/v3/artifacts/019c068b-1bb1-791e-81e2-59540e0b3196/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c068b-1a8d-7895-928c-92ff922746b6/",
    "prn": "prn:python.pythonpackagecontent:019c068b-1a8d-7895-928c-92ff922746b6",
    "pulp_created": "2026-01-28T21:38:25.550239Z",
    "pulp_last_updated": "2026-01-28T21:38:25.550249Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "/pulp/api/v3/artifacts/019c068b-1a88-7c0d-b8fc-1857d748d2a8/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c068b-1959-7aac-bec0-8bf1e07e6fb1/",
    "prn": "prn:python.pythonpackagecontent:019c068b-1959-7aac-bec0-8bf1e07e6fb1",
    "pulp_created": "2026-01-28T21:38:25.242970Z",
    "pulp_last_updated": "2026-01-28T21:38:25.242982Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "requests-2.32.5-py3-none-any.whl": "/pulp/api/v3/artifacts/019c068b-1953-7093-9f56-8e9f644369f7/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c0678-dbd3-71c0-8f0d-5ef1f9d1b6cd/",
    "prn": "prn:python.pythonpackagecontent:019c0678-dbd3-71c0-8f0d-5ef1f9d1b6cd",
    "pulp_created": "2026-01-28T21:18:29.844404Z",
    "pulp_last_updated": "2026-01-28T21:18:29.844415Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "numpy-1.10.0.post2.tar.gz": "/pulp/api/v3/artifacts/019c0678-db5b-76d2-a581-9a42d978d127/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c0667-1868-77d2-8c37-e5db6ce403c3/",
    "prn": "prn:python.pythonpackagecontent:019c0667-1868-77d2-8c37-e5db6ce403c3",
    "pulp_created": "2026-01-28T20:59:05.706536Z",
    "pulp_last_updated": "2026-01-28T20:59:05.706548Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "requests-1.2.3.tar.gz": "/pulp/api/v3/artifacts/019c0667-1859-70c0-b0e5-f09e55a719ba/"
    }
  },
  {
    "pulp_href": "/pulp/api/v3/content/python/packages/019c065a-5d0c-74b2-a669-099544911d20/",
    "prn": "prn:python.pythonpackagecontent:019c065a-5d0c-74b2-a669-099544911d20",
    "pulp_created": "2026-01-28T20:45:11.310796Z",
    "pulp_last_updated": "2026-01-28T20:45:11.310808Z",
    "pulp_labels": {},
    "vuln_report": null,
    "artifacts": {
      "orgalorg_greeter-0.1.0-py3-none-any.whl": "/pulp/api/v3/artifacts/019c065a-5caa-76ba-8cd3-bdecdb6ee4e3/",
      "orgalorg_greeter-0.1.0-py3-none-any.whl.metadata": "/pulp/api/v3/artifacts/019c065a-5d15-76b8-afd6-385c81a76217/"
    }
  }
]

Here orgalorg_greeter is a dummy package I have created and uploaded to the index. Although this dummy package has no dependencies (so I am not sure if it accurately tests whether uv can resolve dependencies for packages that are uploaded manually to the index as opposed to proxied), uv has no problem resolving this dependency. We can observe the last line

"artifacts": {
      "orgalorg_greeter-0.1.0-py3-none-any.whl": "/pulp/api/v3/artifacts/019c065a-5caa-76ba-8cd3-bdecdb6ee4e3/",
      "orgalorg_greeter-0.1.0-py3-none-any.whl.metadata": "/pulp/api/v3/artifacts/019c065a-5d15-76b8-afd6-385c81a76217/"
    }

which shows that a .whl.metadata file has been uploaded when I published the wheel via twine.

For the rest of the packages -- which have been cached from the PyPI -- there is no such .whl.metadata file. I suspect uv wants this, cannot find it on our index when cached, and for some reason pulp does not properly get this when proxying. But I'm just guessing.

Activity

  1. crupp2 commented on Jan 29, 2026

    @crupp2

    I also ran into this behavior and had to revert to an earlier release. The bug was introduced somewhere since pulpcore 3.94.0 and pulp_python 3.21.0.

  2. gerrod3 commented on Jan 29, 2026

    @gerrod3
    Contributor

    Good write up! Very useful to pinpoint the issue and yes I believe it has to do with pull-through caching not interacting with the PEP 658 (wheel metadata) files correctly. We just added the support for the metadata files in pulp-python 3.23 and it seems that pip does a good job of falling back to the old behavior when the metadata file isn't served correctly while the new tool uv expects it to be there (not a bad assumption since the PEP is pretty old and we just got around to supporting it).

    As for why we now separate out the file, it's for efficiency. The metadata file is much smaller than the whole package so it's faster for tools like uv to download the metadata file (if available) when parsing dependencies to find the correct version to download.

  3. Benzor94 commented on Jan 29, 2026

    @Benzor94
    Author

    Can confirm that falling back to an earlier version works. I used this docker-compose:

    services:
      pulp:
        image: pulp/pulp:3.95.2
        container_name: pulp
        ports:
          - "8080:80"
        cap_add:
          - SYS_ADMIN
        security_opt:
          - label=disable
        devices:
          - /dev/fuse
        volumes:
          - ./settings:/etc/pulp:U
          - pulp_storage:/var/lib/pulp
          - pgsql:/var/lib/pgsql
          - containers:/var/lib/containers
          - container_build:/var/lib/pulp/.local/share/containers
        restart: unless-stopped
    
    volumes:
      pulp_storage:
      pgsql:
      containers:
      container_build:
    
    • Question: Is there any way I can swap plugin versions, like pulp-python? I ended up trying a couple of image tags and running pulp status to check the version of the python plugin, but it would be nicer to be able to choose a plugin version.
    • Do you guys have any onboarding docs more detailed than https://pulpproject.org/pulpcore/docs/dev/tutorials/quickstart/ (edit: never mind that to a degree, there are more docs than i realized, although this plugin itself appears to lack dev docs)? I don't have any experience with web/backend dev with Python (Spring is the only web framework I am a bit familiar with, I use python for numerical optimization) nor a lot of freetime, but since I intend to use this project in our corporate environment, I would be interested in helping out in fixing this issue.
  4. gerrod3 commented on Jan 30, 2026

    @gerrod3
    Contributor

    @Benzor94 Our main images are built around the pulpcore version, so the version of the plugins are typically the latest version that supported that pulpcore version at the time of being built. If you want to set the plugin versions you will have to build custom images, e.g.

    git clone https://github.com/pulp/pulp-oci-images.git
    cd pulp-oci-images
    docker build --build-arg PULP_PYTHON_VERSION="==3.23.0" --file images/pulp/Containerfile --tag pulp/pulp:custom
    

    We have docs about image customization here: https://pulpproject.org/pulp-oci-images/docs/admin/guides/build-your-own-pulp-image/

    As for onboarding docs, there is technically a ton (https://pulpproject.org/pulpcore/docs/dev/learn/plugin-concepts/) you could read, but we don't have any specifically for pulp-python. If you are familiar with Django and REST APIs, specifically how data moves between models<-->serializers<-->viewsets, then you'll be able to get a good grasp of what Pulp is doing by playing around with the endpoints and looking at the code. If you have any questions I would recommend joining our matrix channel or asking on discourse (https://pulpproject.org/help/community/get-involved/).

    Also, I have the fix for the issue, PR coming soon.

  5. self-assigned this
    on Jan 30, 2026
  6. added 5 commits that reference this issue on Feb 3, 2026
    dd05d5b
    4743798
    f0e5539
    0e64d57
    7e1e474
  7. Kimahriman commented on Feb 4, 2026

    @Kimahriman

    We've noticed an issue with the pull-through metadata as well and I'm not sure if it's related or a separate issue. We are pulling from a PyPI mirror that doesn't support the metadata serving. When listing the simple page for a package that hasn't been cached yet, it properly shows all versions with no data-dist-info-metadata. And then if you install something for the first time it works and caches the wheel. But it also registers the metadata sha256, so a subsequent listing of the simple/<package>/ page includes the data-dist-info-metadata attribute for the versions that have been cached, which cause pip and uv to then query for the whl.metadata file, which gets forwarded to the remote, which doesn't exist, and the install fails immediately without trying any other versions.

  8. gerrod3 commented on Feb 5, 2026

    @gerrod3
    Contributor

    @Kimahriman Another similar issue, but slightly different cause in the codebase. I filed an issue to track it here: #1087

  9. added 2 commits that reference this issue on Feb 5, 2026
    3764b48
    641bf36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions