Skip to content

Commit 8ef3430

Browse files
authored
Merge pull request #11754 from dependabot/randhircs/dependabot-python-sorbettype-strict
Fix added for sorbet type strict.
2 parents 511f4c7 + 616b3c4 commit 8ef3430

File tree

3 files changed

+53
-23
lines changed

3 files changed

+53
-23
lines changed

python/lib/dependabot/python/file_parser.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ def setup_cfg_file
489489
@setup_cfg_file ||= T.let(get_original_file("setup.cfg"), T.nilable(Dependabot::DependencyFile))
490490
end
491491

492-
sig { returns(T::Array[Dependabot::Python::Requirement]) }
492+
sig { returns(T::Array[Dependabot::DependencyFile]) }
493493
def pip_compile_files
494494
@pip_compile_files ||= T.let(dependency_files.select { |f| f.name.end_with?(".in") }, T.untyped)
495495
end

python/lib/dependabot/python/file_parser/python_requirement_parser.rb

+50-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "toml-rb"
@@ -13,12 +13,17 @@ module Dependabot
1313
module Python
1414
class FileParser
1515
class PythonRequirementParser
16+
extend T::Sig
17+
18+
sig { returns(T::Array[Dependabot::DependencyFile]) }
1619
attr_reader :dependency_files
1720

21+
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
1822
def initialize(dependency_files:)
1923
@dependency_files = dependency_files
2024
end
2125

26+
sig { returns(T::Array[String]) }
2227
def user_specified_requirements
2328
[
2429
pipfile_python_requirement,
@@ -32,22 +37,28 @@ def user_specified_requirements
3237

3338
# TODO: Add better Python version detection using dependency versions
3439
# (e.g., Django 2.x implies Python 3)
40+
sig { returns(T::Array[String]) }
3541
def imputed_requirements
3642
requirement_files.flat_map do |file|
37-
file.content.lines
38-
.select { |l| l.include?(";") && l.include?("python") }
39-
.filter_map { |l| l.match(/python_version(?<req>.*?["'].*?['"])/) }
40-
.map { |re| re.named_captures.fetch("req").gsub(/['"]/, "") }
41-
.select { |r| valid_requirement?(r) }
43+
T.must(file.content).lines
44+
.select { |l| l.include?(";") && l.include?("python") }
45+
.filter_map { |l| l.match(/python_version(?<req>.*?["'].*?['"])/) }
46+
.map { |re| T.must(re.named_captures.fetch("req")).gsub(/['"]/, "") }
47+
.select { |r| valid_requirement?(r) }
4248
end
4349
end
4450

4551
private
4652

53+
# Parses the Pipfile content to extract the Python version requirement.
54+
#
55+
# @return [String, nil] the Python version requirement if specified in the Pipfile,
56+
# or nil if the requirement is not present or does not start with a digit.
57+
sig { returns(T.nilable(String)) }
4758
def pipfile_python_requirement
4859
return unless pipfile
4960

50-
parsed_pipfile = TomlRB.parse(pipfile.content)
61+
parsed_pipfile = TomlRB.parse(T.must(pipfile).content)
5162
requirement =
5263
parsed_pipfile.dig("requires", "python_full_version") ||
5364
parsed_pipfile.dig("requires", "python_version")
@@ -56,10 +67,11 @@ def pipfile_python_requirement
5667
requirement
5768
end
5869

70+
sig { returns(T.nilable(String)) }
5971
def pyproject_python_requirement
6072
return unless pyproject
6173

62-
pyproject_object = TomlRB.parse(pyproject.content)
74+
pyproject_object = TomlRB.parse(T.must(pyproject).content)
6375

6476
# Check for PEP621 requires-python
6577
pep621_python = pyproject_object.dig("project", "requires-python")
@@ -72,9 +84,10 @@ def pyproject_python_requirement
7284
poetry_object&.dig("dev-dependencies", "python")
7385
end
7486

87+
sig { returns(T.nilable(String)) }
7588
def pip_compile_python_requirement
7689
requirement_files.each do |file|
77-
next unless pip_compile_file_matcher.lockfile_for_pip_compile_file?(file)
90+
next unless T.must(pip_compile_file_matcher).lockfile_for_pip_compile_file?(file)
7891

7992
marker = /^# This file is autogenerated by pip-compile with [pP]ython (?<version>\d+.\d+)$/m
8093
match = marker.match(file.content)
@@ -86,98 +99,115 @@ def pip_compile_python_requirement
8699
nil
87100
end
88101

102+
sig { returns(T.nilable(String)) }
89103
def python_version_file_version
90104
return unless python_version_file
91105

92106
# read the content, split into lines and remove any lines with '#'
93-
content_lines = python_version_file.content.each_line.map do |line|
107+
content_lines = T.must(T.must(python_version_file).content).each_line.map do |line|
94108
line.sub(/#.*$/, " ").strip
95109
end.reject(&:empty?)
96110

97111
file_version = content_lines.first
98112
return if file_version&.empty?
99-
return unless pyenv_versions.include?("#{file_version}\n")
113+
return unless T.must(pyenv_versions).include?("#{file_version}\n")
100114

101115
file_version
102116
end
103117

118+
sig { returns(T.nilable(String)) }
104119
def runtime_file_python_version
105120
return unless runtime_file
106121

107-
file_version = runtime_file.content
108-
.match(/(?<=python-).*/)&.to_s&.strip
122+
file_version = T.must(T.must(runtime_file).content)
123+
.match(/(?<=python-).*/)&.to_s&.strip
109124
return if file_version&.empty?
110-
return unless pyenv_versions.include?("#{file_version}\n")
125+
return unless T.must(pyenv_versions).include?("#{file_version}\n")
111126

112127
file_version
113128
end
114129

130+
sig { returns(T.nilable(String)) }
115131
def setup_file_requirement
116132
return unless setup_file
117133

118-
req = setup_file.content
119-
.match(/python_requires\s*=\s*['"](?<req>[^'"]+)['"]/)
120-
&.named_captures&.fetch("req")&.strip
134+
req = T.must(T.must(setup_file).content)
135+
.match(/python_requires\s*=\s*['"](?<req>[^'"]+)['"]/)
136+
&.named_captures&.fetch("req")&.strip
121137

122138
requirement_class.new(req)
123139
req
124140
rescue Gem::Requirement::BadRequirementError
125141
nil
126142
end
127143

144+
sig { returns(T.nilable(String)) }
128145
def pyenv_versions
129-
@pyenv_versions ||= run_command("pyenv install --list")
146+
@pyenv_versions = T.let(run_command("pyenv install --list"), T.nilable(String))
130147
end
131148

149+
sig { params(command: String, env: T::Hash[String, String]).returns(String) }
132150
def run_command(command, env: {})
133151
SharedHelpers.run_shell_command(command, env: env, stderr_to_stdout: true)
134152
end
135153

154+
sig { returns(T.nilable(PipCompileFileMatcher)) }
136155
def pip_compile_file_matcher
137-
@pip_compile_file_matcher ||= PipCompileFileMatcher.new(pip_compile_files)
156+
@pip_compile_file_matcher = T.let(PipCompileFileMatcher.new(pip_compile_files),
157+
T.nilable(PipCompileFileMatcher))
138158
end
139159

160+
sig { returns(T.class_of(Dependabot::Python::Requirement)) }
140161
def requirement_class
141162
Dependabot::Python::Requirement
142163
end
143164

165+
sig { params(req_string: String).returns(T::Boolean) }
144166
def valid_requirement?(req_string)
145167
requirement_class.new(req_string)
146168
true
147169
rescue Gem::Requirement::BadRequirementError
148170
false
149171
end
150172

173+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
151174
def pipfile
152175
dependency_files.find { |f| f.name == "Pipfile" }
153176
end
154177

178+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
155179
def pipfile_lock
156180
dependency_files.find { |f| f.name == "Pipfile.lock" }
157181
end
158182

183+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
159184
def pyproject
160185
dependency_files.find { |f| f.name == "pyproject.toml" }
161186
end
162187

188+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
163189
def setup_file
164190
dependency_files.find { |f| f.name == "setup.py" }
165191
end
166192

193+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
167194
def python_version_file
168195
dependency_files.find { |f| f.name == ".python-version" }
169196
end
170197

198+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
171199
def runtime_file
172200
dependency_files.find { |f| f.name.end_with?("runtime.txt") }
173201
end
174202

203+
sig { returns(T::Array[Dependabot::DependencyFile]) }
175204
def requirement_files
176205
dependency_files.select { |f| f.name.end_with?(".txt") }
177206
end
178207

208+
sig { returns(T::Array[DependencyFile]) }
179209
def pip_compile_files
180-
dependency_files.select { |f| f.name.end_with?(".in") }
210+
@pip_compile_files ||= T.let(dependency_files.select { |f| f.name.end_with?(".in") }, T.untyped)
181211
end
182212
end
183213
end

python/lib/dependabot/python/pip_compile_file_matcher.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module Python
66
class PipCompileFileMatcher
77
extend T::Sig
88

9-
sig { params(requirements_in_files: T::Array[Dependabot::Python::Requirement]).void }
9+
sig { params(requirements_in_files: T::Array[DependencyFile]).void }
1010
def initialize(requirements_in_files)
1111
@requirements_in_files = requirements_in_files
1212
end
@@ -26,7 +26,7 @@ def lockfile_for_pip_compile_file?(file)
2626

2727
private
2828

29-
sig { returns(T::Array[Dependabot::Python::Requirement]) }
29+
sig { returns(T::Array[DependencyFile]) }
3030
attr_reader :requirements_in_files
3131

3232
sig { params(filename: T.any(String, Symbol)).returns(String) }

0 commit comments

Comments
 (0)