class Dependabot::Uv::FileUpdater::PyprojectPreparer
def add_auth_env_vars(credentials)
def add_auth_env_vars(credentials) return unless credentials credentials.each do |credential| next unless credential["type"] == "python_index" token = credential["token"] index_url = credential["index-url"] next unless token && index_url # Set environment variables for uv auth ENV["UV_INDEX_URL_TOKEN_#{sanitize_env_name(index_url)}"] = token # Also set pip-style credentials for compatibility ENV["PIP_INDEX_URL"] ||= "https://#{token}@#{index_url.gsub(%r{^https?://}, '')}" end end
def freeze_dependency(dep_string, deps_to_update_names, locked_deps)
def freeze_dependency(dep_string, deps_to_update_names, locked_deps) package_name = dep_string.split(/[=>~<\[]/).first.strip normalized_name = Uv::FileParser.normalize_dependency_name(package_name) return dep_string if deps_to_update_names.include?(normalized_name) version = locked_version_for_dep(locked_deps, normalized_name) return dep_string unless version if dep_string.include?("=") || dep_string.include?(">") || dep_string.include?("<") || dep_string.include?("~") # Replace version constraint with exact version dep_string.sub(/[=>~<\[].*$/, "==#{version}") else # Simple dependency, just append version "#{dep_string}==#{version}" end end
def freeze_top_level_dependencies_except(dependencies_to_update)
def freeze_top_level_dependencies_except(dependencies_to_update) return @pyproject_content unless lockfile pyproject_object = TomlRB.parse(@pyproject_content) deps_to_update_names = dependencies_to_update.map(&:name) if pyproject_object["project"]&.key?("dependencies") locked_deps = parsed_lockfile_dependencies || {} pyproject_object["project"]["dependencies"] = pyproject_object["project"]["dependencies"].map do |dep_string| freeze_dependency(dep_string, deps_to_update_names, locked_deps) end end TomlRB.dump(pyproject_object) end
def initialize(pyproject_content:, lockfile: nil)
def initialize(pyproject_content:, lockfile: nil) @pyproject_content = pyproject_content @lockfile = lockfile end
def locked_version_for_dep(locked_deps, dep_name)
def locked_version_for_dep(locked_deps, dep_name) locked_deps.each do |name, details| next unless Uv::FileParser.normalize_dependency_name(name) == dep_name return details["version"] if details.is_a?(Hash) && details["version"] end nil end
def parse_lockfile(content)
def parse_lockfile(content) TomlRB.parse(content) rescue TomlRB::ParseError {} # Return empty hash if parsing fails end
def parsed_lockfile
def parsed_lockfile @parsed_lockfile ||= lockfile ? parse_lockfile(lockfile.content) : {} end
def parsed_lockfile_dependencies
def parsed_lockfile_dependencies return {} unless lockfile deps = {} parsed = parsed_lockfile # Handle UV lock format (version 1) if parsed["version"] == 1 && parsed["package"].is_a?(Array) parsed["package"].each do |pkg| next unless pkg["name"] && pkg["version"] deps[pkg["name"]] = { "version" => pkg["version"] } end # Handle traditional Poetry-style lock format elsif parsed["dependencies"] deps = parsed["dependencies"] end deps end
def sanitize
def sanitize # No special sanitization needed for UV files at this point @pyproject_content end
def sanitize_env_name(url)
def sanitize_env_name(url) url.gsub(%r{^https?://}, "").gsub(/[^a-zA-Z0-9]/, "_").upcase end
def update_python_requirement(python_version)
def update_python_requirement(python_version) return @pyproject_content unless python_version pyproject_object = TomlRB.parse(@pyproject_content) if pyproject_object["project"]&.key?("requires-python") pyproject_object["project"]["requires-python"] = ">=#{python_version}" end TomlRB.dump(pyproject_object) end