class Dependabot::Python::FileUpdater
def self.updated_files_regex
def self.updated_files_regex [ /^.*Pipfile$/, # Match Pipfile at any level /^.*Pipfile\.lock$/, # Match Pipfile.lock at any level /^.*\.txt$/, # Match any .txt files (e.g., requirements.txt) at any level /^.*\.in$/, # Match any .in files at any level /^.*setup\.py$/, # Match setup.py at any level /^.*setup\.cfg$/, # Match setup.cfg at any level /^.*pyproject\.toml$/, # Match pyproject.toml at any level /^.*pyproject\.lock$/, # Match pyproject.lock at any level /^.*poetry\.lock$/ # Match poetry.lock at any level ] end
def check_required_files
def check_required_files filenames = dependency_files.map(&:name) return if filenames.any? { |name| name.end_with?(".txt", ".in") } return if pipfile return if pyproject return if get_original_file("setup.py") return if get_original_file("setup.cfg") raise "Missing required files!" end
def pip_compile_files
def pip_compile_files @pip_compile_files ||= T.let( dependency_files.select { |f| f.name.end_with?(".in") }, T.nilable(T::Array[DependencyFile]) ) end
def pip_compile_index_urls
def pip_compile_index_urls if credentials.any?(&:replaces_base?) credentials.select(&:replaces_base?).map { |cred| AuthedUrlBuilder.authed_url(credential: cred) } else urls = credentials.map { |cred| AuthedUrlBuilder.authed_url(credential: cred) } # If there are no credentials that replace the base, we need to # ensure that the base URL is included in the list of extra-index-urls. [nil, *urls] end end
def pipfile
def pipfile @pipfile ||= T.let(get_original_file("Pipfile"), T.nilable(Dependabot::DependencyFile)) end
def pipfile_lock
def pipfile_lock @pipfile_lock ||= T.let(get_original_file("Pipfile.lock"), T.nilable(Dependabot::DependencyFile)) end
def poetry_based?
def poetry_based? return false unless pyproject !TomlRB.parse(pyproject&.content).dig("tool", "poetry").nil? end
def poetry_lock
def poetry_lock @poetry_lock ||= T.let(get_original_file("poetry.lock"), T.nilable(Dependabot::DependencyFile)) end
def pyproject
def pyproject @pyproject ||= T.let(get_original_file("pyproject.toml"), T.nilable(Dependabot::DependencyFile)) end
def resolver_type
def resolver_type reqs = dependencies.flat_map(&:requirements) changed_reqs = reqs.zip(dependencies.flat_map(&:previous_requirements)) .reject { |(new_req, old_req)| new_req == old_req } .map(&:first) changed_req_files = changed_reqs.map { |r| r.fetch(:file) } # If there are no requirements then this is a sub-dependency. It # must come from one of Pipenv, Poetry or pip-tools, and can't come # from the first two unless they have a lockfile. return subdependency_resolver if changed_reqs.none? # Otherwise, this is a top-level dependency, and we can figure out # which resolver to use based on the filename of its requirements return :pipfile if changed_req_files.any?("Pipfile") if changed_req_files.any?("pyproject.toml") return :poetry if poetry_based? return :requirements end return :pip_compile if changed_req_files.any? { |f| f.end_with?(".in") } :requirements end
def subdependency_resolver
def subdependency_resolver return :pipfile if pipfile_lock return :poetry if poetry_lock return :pip_compile if pip_compile_files.any? raise "Claimed to be a sub-dependency, but no lockfile exists!" end
def updated_dependency_files
def updated_dependency_files updated_files = case resolver_type when :pipfile then updated_pipfile_based_files when :poetry then updated_poetry_based_files when :pip_compile then updated_pip_compile_based_files when :requirements then updated_requirement_based_files else raise "Unexpected resolver type: #{resolver_type}" end if updated_files.none? || updated_files.sort_by(&:name) == dependency_files.sort_by(&:name) raise "No files have changed!" end updated_files end
def updated_pip_compile_based_files
def updated_pip_compile_based_files T.must(PipCompileFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, credentials: credentials, index_urls: pip_compile_index_urls ).updated_dependency_files) end
def updated_pipfile_based_files
def updated_pipfile_based_files PipfileFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, credentials: credentials, repo_contents_path: repo_contents_path ).updated_dependency_files end
def updated_poetry_based_files
def updated_poetry_based_files PoetryFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, credentials: credentials ).updated_dependency_files end
def updated_requirement_based_files
def updated_requirement_based_files RequirementFileUpdater.new( dependencies: dependencies, dependency_files: dependency_files, credentials: credentials, index_urls: pip_compile_index_urls ).updated_dependency_files end