Pypi: Rework to use Json::find_versions
This reworks the new `Pypi` JSON API implementation to use `Json::find_versions` in `Pypi::find_versions`, borrowing some of the approach from the `Crate` strategy. Besides that, this pares down the fields in the `::generate_input_values` return hash to only `:url`, as we're not using a generated regex to match version information in this setup. This adds a `provided_content` parameter to `::find_versions` as part of this process and I will expand the `Pypi` tests to increase coverage (like the `Crates` tests) in a later PR. 75% of `Pypi` checks are failing at the moment (with some returning inaccurate version information), so the current priority is getting this fix merged in the short-term.
This commit is contained in:
parent
d49e01b82b
commit
935eb89eca
@ -1,26 +1,31 @@
|
|||||||
# typed: strict
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "json"
|
|
||||||
require "utils/curl"
|
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
module Livecheck
|
module Livecheck
|
||||||
module Strategy
|
module Strategy
|
||||||
# The {Pypi} strategy identifies versions of software at pypi.org by
|
# The {Pypi} strategy identifies the newest version of a PyPI package by
|
||||||
# using the JSON API endpoint.
|
# checking the JSON API endpoint for the project and using the
|
||||||
|
# `info.version` field from the response.
|
||||||
#
|
#
|
||||||
# PyPI URLs have a standard format:
|
# PyPI URLs have a standard format:
|
||||||
|
# `https://files.pythonhosted.org/packages/<hex>/<hex>/<long_hex>/example-1.2.3.tar.gz`
|
||||||
#
|
#
|
||||||
# * `https://files.pythonhosted.org/packages/<hex>/<hex>/<long_hex>/example-1.2.3.tar.gz`
|
# Upstream documentation for the PyPI JSON API can be found at:
|
||||||
#
|
# https://docs.pypi.org/api/json/#get-a-project
|
||||||
# This method uses the `info.version` field in the JSON response to
|
|
||||||
# determine the latest stable version.
|
|
||||||
#
|
#
|
||||||
# @api public
|
# @api public
|
||||||
class Pypi
|
class Pypi
|
||||||
NICE_NAME = "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").presence
|
||||||
|
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
|
# The `Regexp` used to extract the package name and suffix (e.g. file
|
||||||
# extension) from the URL basename.
|
# extension) from the URL basename.
|
||||||
FILENAME_REGEX = /
|
FILENAME_REGEX = /
|
||||||
@ -46,8 +51,8 @@ module Homebrew
|
|||||||
URL_MATCH_REGEX.match?(url)
|
URL_MATCH_REGEX.match?(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Extracts the package name from the provided URL and generates the
|
# Extracts the package name from the provided URL and uses it to
|
||||||
# PyPI JSON API endpoint.
|
# generate the PyPI JSON API URL for the project.
|
||||||
#
|
#
|
||||||
# @param url [String] the URL used to generate values
|
# @param url [String] the URL used to generate values
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
@ -58,48 +63,41 @@ module Homebrew
|
|||||||
match = File.basename(url).match(FILENAME_REGEX)
|
match = File.basename(url).match(FILENAME_REGEX)
|
||||||
return values if match.blank?
|
return values if match.blank?
|
||||||
|
|
||||||
package_name = T.must(match[:package_name]).gsub(/[_-]/, "-")
|
values[:url] = "https://pypi.org/pypi/#{T.must(match[:package_name]).gsub(/%20|_/, "-")}/json"
|
||||||
values[:url] = "https://pypi.org/project/#{package_name}/#files"
|
|
||||||
values[:regex] = %r{href=.*?/packages.*?/#{package_name}[._-]v?(\d+(?:\.\d+)*(?:[._-]post\d+)?)\.t}i
|
|
||||||
|
|
||||||
values
|
values
|
||||||
end
|
end
|
||||||
|
|
||||||
# Fetches the latest version of the package from the PyPI JSON API.
|
# Generates a PyPI JSON API URL for the project and identifies new
|
||||||
|
# versions using {Json#find_versions} with a block.
|
||||||
#
|
#
|
||||||
# @param url [String] the URL of the content to check
|
# @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
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
sig {
|
sig {
|
||||||
params(
|
params(
|
||||||
url: String,
|
url: String,
|
||||||
regex: T.nilable(Regexp),
|
regex: T.nilable(Regexp),
|
||||||
_unused: T.untyped,
|
provided_content: T.nilable(String),
|
||||||
_block: T.nilable(Proc),
|
unused: T.untyped,
|
||||||
|
block: T.nilable(Proc),
|
||||||
).returns(T::Hash[Symbol, T.untyped])
|
).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, **unused, &block)
|
||||||
match_data = { matches: {}, regex:, url: }
|
match_data = { matches: {}, regex:, url: }
|
||||||
|
|
||||||
generated = generate_input_values(url)
|
generated = generate_input_values(url)
|
||||||
return match_data if generated.blank?
|
return match_data if generated.blank?
|
||||||
|
|
||||||
match_data[:url] = generated[:url]
|
Json.find_versions(
|
||||||
|
url: generated[:url],
|
||||||
# Parse JSON and get the latest version
|
regex:,
|
||||||
begin
|
provided_content:,
|
||||||
response = Utils::Curl.curl_output(generated[:url])
|
**unused,
|
||||||
data = JSON.parse(response.stdout, symbolize_names: true)
|
&block || DEFAULT_BLOCK
|
||||||
latest_version = data.dig(:info, :version)
|
)
|
||||||
rescue => e
|
|
||||||
puts "Error fetching version from PyPI: #{e.message}"
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the version if found
|
|
||||||
return {} if latest_version.blank?
|
|
||||||
|
|
||||||
{ matches: { latest_version => Version.new(latest_version) } }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,8 +10,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
|
|||||||
|
|
||||||
let(:generated) do
|
let(:generated) do
|
||||||
{
|
{
|
||||||
url: "https://pypi.org/project/example-package/#files",
|
url: "https://pypi.org/pypi/example-package/json",
|
||||||
regex: %r{href=.*?/packages.*?/example-package[._-]v?(\d+(?:\.\d+)*(?:[._-]post\d+)?)\.t}i,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user