Skip to content

fix(pypi): preserve real version for URL-pinned pip deps on re-lock - #923

Open
thomas-dresselhaus wants to merge 1 commit into
conda:mainfrom
thomas-dresselhaus:fix-url-locked-version-reconcile
Open

fix(pypi): preserve real version for URL-pinned pip deps on re-lock#923
thomas-dresselhaus wants to merge 1 commit into
conda:mainfrom
thomas-dresselhaus:fix-url-locked-version-reconcile

Conversation

@thomas-dresselhaus

Copy link
Copy Markdown

Description

Fixes #922.

get_package reconstructs a Poetry package from an already-locked dependency during a re-lock (the
reconcile solve). For URL-sourced deps it hard-coded version="0.0.0", discarding the real version
stored in locked.version.

That placeholder breaks the reconcile whenever another package constrains the URL-pinned dep by
version (e.g. a transitive foo >=1.0): the solver either silently downgrades the constraining
packages to ancient versions (and emits the URL dep as 0.0.0), or fails outright with a
SolverProblemError when no downgrade is possible. A first-time lock is unaffected (the URL dep is
resolved fresh); only reconciliation (--update, or a changed input under --check-input-hash)
hits it.

This uses locked.version — matching the non-URL else branch in the same function — while
leaving the URL pin (source_type/source_url) intact. A LockedDependency's source is always a
URL source (DependencySource.type is Literal["url"]), so no source-type gating is needed.

(Prepared with AI assistance.) 🤖

Change — conda_lock/pypi_solver.py

 def get_package(locked: LockedDependency) -> PoetryPackage:
     if locked.source is not None:
         return PoetryPackage(
             locked.name,
             source_type="url",
             source_url=locked.source.url,
-            version="0.0.0",
+            version=locked.version,
         )
     else:
         return PoetryPackage(locked.name, version=locked.version)

Test — tests/test_conda_lock.py

A deterministic, network-free regression test pinning the defect (fails pre-fix with 0.0.0,
passes post-fix), asserting both the version and the preserved URL pin:

def test_get_package_preserves_version_for_url_source():
    """A URL-sourced locked dep must reconcile with its real version, not a 0.0.0 placeholder."""
    url = "https://example.com/foo-1.2.3-py3-none-any.whl"
    locked = LockedDependency(
        name="foo",
        version="1.2.3",
        manager="pip",
        platform="linux-64",
        dependencies={},
        url=url,
        hash=HashModel(),
        source=DependencySource(type="url", url=url),
    )

    package = get_package(locked)

    assert str(package.version) == "1.2.3"
    # The dependency stays URL-pinned.
    assert package.source_type == "url"
    assert package.source_url == url

Notes for maintainers

  • The end-to-end consequence (reconcile corruption / SolverProblemError) needs a graph with a
    transitive version constraint on the URL-pinned dep; the minimal case is idna @ <url> +
    requests, locked then re-locked. I kept the committed regression test at the unit level (no
    network, deterministic) since that's the smallest reliable gate; happy to add an integration
    fixture if you'd prefer one.

🤖 Generated with Claude Code

`get_package` reconstructs an already-locked dependency into a Poetry
package during the reconcile solve (`--update`, or a changed input under
`--check-input-hash`). For URL-sourced deps it hard-coded
`version="0.0.0"`, discarding the real version in `locked.version`.

When another package constrains the URL-pinned dep by version, holding it
at `0.0.0` corrupts the reconcile: the solver either silently downgrades
the constraining packages to ancient versions (emitting the URL dep as
`0.0.0`), or fails with a `SolverProblemError` when no downgrade is
possible. A first-time lock is unaffected because the URL dep is resolved
fresh.

Use `locked.version`, matching the non-URL `else` branch, while keeping
the URL pin intact. A LockedDependency's source is always a URL source
(`DependencySource.type` is `Literal["url"]`), so no gating is needed.

Adds a deterministic regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thomas-dresselhaus
thomas-dresselhaus requested a review from a team as a code owner July 8, 2026 15:50
@netlify

netlify Bot commented Jul 8, 2026

Copy link
Copy Markdown

Deploy Preview for conda-lock ready!

Name Link
🔨 Latest commit e648b5c
🔍 Latest deploy log https://app.netlify.com/projects/conda-lock/deploys/6a4e71bb4625860008bc749f
😎 Deploy Preview https://deploy-preview-923--conda-lock.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URL-pinned pip deps are reconciled with a 0.0.0 placeholder version on re-lock, corrupting the solve

1 participant