class Dependabot::Python::UpdateChecker::LatestVersionFinder

Attributes

credentials[R]
dependency[R]
dependency_files[R]
ignored_versions[R]
security_advisories[R]

Public Class Methods

new(dependency:, dependency_files:, credentials:, ignored_versions:, raise_on_ignored: false, security_advisories:) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 20
def initialize(dependency:, dependency_files:, credentials:,
               ignored_versions:, raise_on_ignored: false,
               security_advisories:)
  @dependency          = dependency
  @dependency_files    = dependency_files
  @credentials         = credentials
  @ignored_versions    = ignored_versions
  @raise_on_ignored    = raise_on_ignored
  @security_advisories = security_advisories
end

Public Instance Methods

latest_version(python_version: nil) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 31
def latest_version(python_version: nil)
  @latest_version ||=
    fetch_latest_version(python_version: python_version)
end
latest_version_with_no_unlock(python_version: nil) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 36
def latest_version_with_no_unlock(python_version: nil)
  @latest_version_with_no_unlock ||=
    fetch_latest_version_with_no_unlock(python_version: python_version)
end
lowest_security_fix_version(python_version: nil) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 41
def lowest_security_fix_version(python_version: nil)
  @lowest_security_fix_version ||=
    fetch_lowest_security_fix_version(python_version: python_version)
end

Private Instance Methods

available_versions() click to toggle source

See www.python.org/dev/peps/pep-0503/ for details of the Simple Repository API we use here.

# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 143
def available_versions
  @available_versions ||=
    index_urls.flat_map do |index_url|
      sanitized_url = index_url.gsub(%r{(?<=//).*(?=@)}, "redacted")
      index_response = registry_response_for_dependency(index_url)

      if [401, 403].include?(index_response.status) &&
         [401, 403].include?(registry_index_response(index_url).status)
        raise PrivateSourceAuthenticationFailure, sanitized_url
      end

      version_links = []
      index_response.body.scan(%r{<a\s.*?>.*?</a>}m) do
        details = version_details_from_link(Regexp.last_match.to_s)
        version_links << details if details
      end

      version_links.compact
    rescue Excon::Error::Timeout, Excon::Error::Socket
      raise if MAIN_PYPI_INDEXES.include?(index_url)

      raise PrivateSourceTimedOut, sanitized_url
    end
end
fetch_latest_version(python_version:) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 51
def fetch_latest_version(python_version:)
  versions = available_versions
  versions = filter_yanked_versions(versions)
  versions = filter_unsupported_versions(versions, python_version)
  versions = filter_prerelease_versions(versions)
  versions = filter_ignored_versions(versions)
  versions.max
end
fetch_latest_version_with_no_unlock(python_version:) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 60
def fetch_latest_version_with_no_unlock(python_version:)
  versions = available_versions
  versions = filter_yanked_versions(versions)
  versions = filter_unsupported_versions(versions, python_version)
  versions = filter_prerelease_versions(versions)
  versions = filter_ignored_versions(versions)
  versions = filter_out_of_range_versions(versions)
  versions.max
end
fetch_lowest_security_fix_version(python_version:) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 70
def fetch_lowest_security_fix_version(python_version:)
  versions = available_versions
  versions = filter_yanked_versions(versions)
  versions = filter_unsupported_versions(versions, python_version)
  versions = filter_prerelease_versions(versions)
  versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(versions,
                                                                                   security_advisories)
  versions = filter_ignored_versions(versions)
  versions = filter_lower_versions(versions)

  versions.min
end
filter_ignored_versions(versions_array) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 104
def filter_ignored_versions(versions_array)
  filtered = versions_array.
             reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
  if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any?
    raise Dependabot::AllVersionsIgnored
  end

  filtered
end
filter_lower_versions(versions_array) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 114
def filter_lower_versions(versions_array)
  return versions_array unless dependency.version && version_class.correct?(dependency.version)

  versions_array.select { |version| version > version_class.new(dependency.version) }
end
filter_out_of_range_versions(versions_array) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 120
def filter_out_of_range_versions(versions_array)
  reqs = dependency.requirements.map do |r|
    requirement_class.requirements_array(r.fetch(:requirement))
  end.compact

  versions_array.
    select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } }
end
filter_prerelease_versions(versions_array) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 98
def filter_prerelease_versions(versions_array)
  return versions_array if wants_prerelease?

  versions_array.reject(&:prerelease?)
end
filter_unsupported_versions(versions_array, python_version) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 87
def filter_unsupported_versions(versions_array, python_version)
  versions_array.map do |details|
    python_requirement = details.fetch(:python_requirement)
    next details.fetch(:version) unless python_version
    next details.fetch(:version) unless python_requirement
    next unless python_requirement.satisfied_by?(python_version)

    details.fetch(:version)
  end.compact
end
filter_yanked_versions(versions_array) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 83
def filter_yanked_versions(versions_array)
  versions_array.reject { |details| details.fetch(:yanked) }
end
get_version_from_filename(filename) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 186
def get_version_from_filename(filename)
  filename.
    gsub(/#{name_regex}-/i, "").
    split(/-|\.tar\.|\.zip|\.whl/).
    first
end
ignore_requirements() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 230
def ignore_requirements
  ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
end
index_urls() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 206
def index_urls
  @index_urls ||=
    IndexFinder.new(
      dependency_files: dependency_files,
      credentials: credentials
    ).index_urls
end
name_regex() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 238
def name_regex
  parts = normalised_name.split(/[\s_.-]/).map { |n| Regexp.quote(n) }
  /#{parts.join("[\s_.-]")}/i
end
normalised_name() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 234
def normalised_name
  NameNormaliser.normalise(dependency.name)
end
registry_index_response(index_url) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 222
def registry_index_response(index_url)
  Excon.get(
    index_url,
    idempotent: true,
    **SharedHelpers.excon_defaults(headers: { "Accept" => "text/html" })
  )
end
registry_response_for_dependency(index_url) click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 214
def registry_response_for_dependency(index_url)
  Excon.get(
    index_url + normalised_name + "/",
    idempotent: true,
    **SharedHelpers.excon_defaults(headers: { "Accept" => "text/html" })
  )
end
requirement_class() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 247
def requirement_class
  Utils.requirement_class_for_package_manager(
    dependency.package_manager
  )
end
version_class() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 243
def version_class
  Utils.version_class_for_package_manager(dependency.package_manager)
end
wants_prerelease?() click to toggle source
# File lib/dependabot/python/update_checker/latest_version_finder.rb, line 129
def wants_prerelease?
  if dependency.version
    version = version_class.new(dependency.version.tr("+", "."))
    return version.prerelease?
  end

  dependency.requirements.any? do |req|
    reqs = (req.fetch(:requirement) || "").split(",").map(&:strip)
    reqs.any? { |r| r.match?(/[A-Za-z]/) }
  end
end