pypi: resolve non-PyPI sources missing from PyPI

- `package_name` was always looked up on PyPI as
  `name[extras]==<formula version>`, which fails when a formula built
  from a non-PyPI source (e.g. a GitHub release) uses a version PyPI
  does not have, as with `hermes-agent`.
- In that case, resolve the stable URL as a PEP 508 direct reference
  (`name[extras] @ <url>`) so extras' dependencies are kept.
- Formulae whose version exists on PyPI are unaffected.
This commit is contained in:
Daeho Ro committed 2026-09-19 18:20:41 +09:00
1 parent 2f1c682db0
commit 214a860f58
3 files changed
+61 -3

No files matched your search

+49
View File
@@ -346,6 +346,55 @@ RSpec.describe PyPI do
expect(described_class.update_python_resources!(formula, quiet: true)).to be true
end
context "with a package name and a non-PyPI stable URL" do
let(:url) { "https://github.com/example/foo/archive/refs/tags/v1.0.tar.gz" }
let(:formula) do
path = mktmpdir/"foo.rb"
path.write <<~RUBY
class Foo < Formula
url "#{url}"
sha256 "#{"a" * 64}"
def install
bin.install "foo"
end
end
RUBY
Formulary.from_contents("foo", path, path.read)
end
let(:report) { '{"install":[{"metadata":{"name":"foo","version":"1.0"}}]}' }
before do
allow(Formula).to receive(:[]).with("python").and_return(instance_double(Formula, ensure_installed!: true))
allow(described_class).to receive(:pip_output).and_return(report)
end
it "resolves the stable URL with the package's extras when PyPI lacks the formula version" do
allow(Utils::Curl).to receive(:curl_output)
.and_return(instance_double(SystemCommand::Result,
status: instance_double(Process::Status, success?: false)))
expect(described_class).to receive(:pip_output)
.with(array_including("--report=/dev/stdout", "foo[bar] @ #{url}"), any_args).and_return(report)
described_class.update_python_resources!(formula, package_name: "foo[bar]", quiet: true)
end
it "resolves the package from PyPI when it has the formula version" do
pypi_json = {
info: { name: "foo", version: "1.0" },
urls: [{ packagetype: "sdist", url: "https://files.pythonhosted.org/packages/foo-1.0.tar.gz",
digests: { sha256: "b" * 64 } }],
}.to_json
allow(Utils::Curl).to receive(:curl_output)
.and_return(instance_double(SystemCommand::Result,
status: instance_double(Process::Status, success?: true), stdout: pypi_json))
expect(described_class).to receive(:pip_output)
.with(array_including("--report=/dev/stdout", "foo[bar]==1.0"), any_args).and_return(report)
described_class.update_python_resources!(formula, package_name: "foo[bar]", quiet: true)
end
end
it "keeps resources with livecheck blocks" do
path = mktmpdir/"foo.rb"
livecheck_resource = <<~RUBY
+10 -3
View File
@@ -284,15 +284,22 @@ module PyPI
(python_deps.find(&:any_version_installed?) || python_deps.first).name
end
stable = formula.stable
stable_url = stable&.url
main_package = if package_name.present?
package_string = package_name
package_string += "==#{formula.version}" if version.blank? && formula.version.present?
Package.new(package_string, python_name:)
package = Package.new(package_string, python_name:)
if stable && stable_url && package_string != package_name &&
!stable_url.start_with?(PYTHONHOSTED_URL_PREFIX) && !stable.downloader.is_a?(VCSDownloadStrategy) &&
package.pypi_info(ignore_errors: true).nil?
Package.new("#{package_name} @ #{stable_url}", is_url: true, python_name:)
else
package
end
elsif package_name == ""
nil
else
stable = formula.stable
stable_url = stable&.url
if stable.nil? || stable_url.nil?
odie "#{formula.full_name} has no stable URL to determine the main Python package from."
end
+2
View File
@@ -56,6 +56,8 @@ pypi_packages package_name: "upstream-name",
dependencies: "resolver-dependency"
```
If the stable URL is not on PyPI and PyPI has no release matching the formula's version, `package_name` (including any extras such as `upstream-name[all]`) is resolved against the stable URL instead.
### Installing a Python application
Include `Language::Python::Virtualenv` and use `virtualenv_install_with_resources` for the standard application layout.