diff --git a/Library/Homebrew/livecheck/strategy/pypi.rb b/Library/Homebrew/livecheck/strategy/pypi.rb index c598b331680fac..0d1a27cac0a84d 100644 --- a/Library/Homebrew/livecheck/strategy/pypi.rb +++ b/Library/Homebrew/livecheck/strategy/pypi.rb @@ -1,9 +1,6 @@ # typed: strict # frozen_string_literal: true -require "json" -require "utils/curl" - module Homebrew module Livecheck module Strategy @@ -21,6 +18,14 @@ module Strategy class Pypi NICE_NAME = "PyPI" + # The default `strategy` block used to extract version information when + # a `strategy` block isn't provided. + DEFAULT_BLOCK = T.let(proc do |json| + json.dig("info", "version") + end.freeze, T.proc.params( + arg0: T::Hash[String, T.untyped], + ).returns(T.nilable(String))) + # The `Regexp` used to extract the package name and suffix (e.g. file # extension) from the URL basename. FILENAME_REGEX = / @@ -47,7 +52,7 @@ def self.match?(url) end # Extracts the package name from the provided URL and generates the - # PyPI JSON API endpoint. + # PyPI JSON API URL. # # @param url [String] the URL used to generate values # @return [Hash] @@ -58,48 +63,52 @@ def self.generate_input_values(url) match = File.basename(url).match(FILENAME_REGEX) return values if match.blank? - package_name = T.must(match[:package_name]).gsub(/[_-]/, "-") - values[:url] = "https://pypi.org/project/#{package_name}/#files" - values[:regex] = %r{href=.*?/packages.*?/#{package_name}[._-]v?(\d+(?:\.\d+)*(?:[._-]post\d+)?)\.t}i + values[:url] = "https://pypi.org/pypi/#{T.must(match[:package_name]).gsub(/%20|_/, "-")}/json" values end - # Fetches the latest version of the package from the PyPI JSON API. + # Generates a URL, fetches the JSON content, and identifies new + # versions using {Json#versions_from_content} with a block. # # @param url [String] the URL of the content to check - # @param regex [Regexp] a regex used for matching versions in content (optional) + # @param regex [Regexp] a regex used for matching versions in content + # @param provided_content [String, nil] content to check instead of + # fetching + # @param homebrew_curl [Boolean] whether to use brewed curl with the URL # @return [Hash] sig { params( - url: String, - regex: T.nilable(Regexp), - _unused: T.untyped, - _block: T.nilable(Proc), + url: String, + regex: T.nilable(Regexp), + provided_content: T.nilable(String), + homebrew_curl: T::Boolean, + _unused: T.untyped, + block: T.nilable(Proc), ).returns(T::Hash[Symbol, T.untyped]) } - def self.find_versions(url:, regex: nil, **_unused, &_block) + def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) match_data = { matches: {}, regex:, url: } + match_data[:cached] = true if provided_content.is_a?(String) generated = generate_input_values(url) return match_data if generated.blank? match_data[:url] = generated[:url] - # Parse JSON and get the latest version - begin - response = Utils::Curl.curl_output(generated[:url]) - data = JSON.parse(response.stdout, symbolize_names: true) - latest_version = data.dig(:info, :version) - rescue => e - puts "Error fetching version from PyPI: #{e.message}" - return {} + content = if provided_content + provided_content + else + match_data.merge!(Strategy.page_content(match_data[:url], homebrew_curl:)) + match_data[:content] end + return match_data unless content - # Return the version if found - return {} if latest_version.blank? + Json.versions_from_content(content, regex, &block || DEFAULT_BLOCK).each do |match_text| + match_data[:matches][match_text] = Version.new(match_text) + end - { matches: { latest_version => Version.new(latest_version) } } + match_data end end end diff --git a/Library/Homebrew/test/livecheck/strategy/pypi_spec.rb b/Library/Homebrew/test/livecheck/strategy/pypi_spec.rb index f42a3d174c5807..2ee5aa35e8786d 100644 --- a/Library/Homebrew/test/livecheck/strategy/pypi_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/pypi_spec.rb @@ -10,8 +10,7 @@ let(:generated) do { - url: "https://pypi.org/project/example-package/#files", - regex: %r{href=.*?/packages.*?/example-package[._-]v?(\d+(?:\.\d+)*(?:[._-]post\d+)?)\.t}i, + url: "https://pypi.org/pypi/example-package/json", } end