dependabot-python 0.320.0 → 0.320.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
4
5
  require "dependabot/python/update_checker"
5
6
  require "dependabot/python/authed_url_builder"
6
7
  require "dependabot/errors"
@@ -9,15 +10,27 @@ module Dependabot
9
10
  module Python
10
11
  module Package
11
12
  class PackageRegistryFinder
13
+ extend T::Sig
14
+
12
15
  PYPI_BASE_URL = "https://pypi.org/simple/"
13
16
  ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
14
17
 
18
+ UrlsHash = T.type_alias { { main: T.nilable(String), extra: T::Array[String] } }
19
+
20
+ sig do
21
+ params(
22
+ dependency_files: T::Array[Dependabot::DependencyFile],
23
+ credentials: T::Array[Dependabot::Credential],
24
+ dependency: Dependabot::Dependency
25
+ ).void
26
+ end
15
27
  def initialize(dependency_files:, credentials:, dependency:)
16
- @dependency_files = dependency_files
17
- @credentials = credentials
18
- @dependency = dependency
28
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
29
+ @credentials = T.let(credentials, T::Array[Dependabot::Credential])
30
+ @dependency = T.let(dependency, Dependabot::Dependency)
19
31
  end
20
32
 
33
+ sig { returns(T::Array[String]) }
21
34
  def registry_urls
22
35
  extra_index_urls =
23
36
  config_variable_index_urls[:extra] +
@@ -40,9 +53,13 @@ module Dependabot
40
53
 
41
54
  private
42
55
 
56
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
43
57
  attr_reader :dependency_files
58
+
59
+ sig { returns(T::Array[Dependabot::Credential]) }
44
60
  attr_reader :credentials
45
61
 
62
+ sig { returns(String) }
46
63
  def main_index_url
47
64
  url =
48
65
  config_variable_index_urls[:main] ||
@@ -55,92 +72,109 @@ module Dependabot
55
72
  clean_check_and_remove_environment_variables(url)
56
73
  end
57
74
 
75
+ sig { returns(UrlsHash) }
58
76
  def requirement_file_index_urls
59
- urls = { main: nil, extra: [] }
77
+ urls = T.let({ main: nil, extra: [] }, UrlsHash)
60
78
 
61
79
  requirements_files.each do |file|
62
- if file.content.match?(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
80
+ content = T.must(file.content)
81
+ if content.match?(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
63
82
  urls[:main] =
64
- file.content.match(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
65
- .captures.first&.strip
83
+ T.must(content.match(/^--index-url\s+['"]?([^\s'"]+)['"]?/))
84
+ .captures.first&.strip
66
85
  end
67
- urls[:extra] +=
68
- file.content
69
- .scan(/^--extra-index-url\s+['"]?([^\s'"]+)['"]?/)
70
- .flatten
71
- .map(&:strip)
86
+ extra_urls = urls[:extra]
87
+ extra_urls +=
88
+ content
89
+ .scan(/^--extra-index-url\s+['"]?([^\s'"]+)['"]?/)
90
+ .flatten
91
+ .map(&:strip)
92
+ urls[:extra] = extra_urls
72
93
  end
73
94
 
74
95
  urls
75
96
  end
76
97
 
98
+ sig { returns(UrlsHash) }
77
99
  def pip_conf_index_urls
78
- urls = { main: nil, extra: [] }
100
+ urls = T.let({ main: nil, extra: [] }, UrlsHash)
79
101
 
80
102
  return urls unless pip_conf
81
103
 
82
- content = pip_conf.content
104
+ content = T.must(pip_conf).content
105
+ return urls unless content
83
106
 
84
107
  if content.match?(/^index-url\s*=/x)
85
- urls[:main] = content.match(/^index-url\s*=\s*(.+)/)
86
- .captures.first
108
+ urls[:main] = T.must(content.match(/^index-url\s*=\s*(.+)/))
109
+ .captures.first
87
110
  end
88
- urls[:extra] += content.scan(/^extra-index-url\s*=(.+)/).flatten
111
+ extra_urls = urls[:extra]
112
+ extra_urls += content.scan(/^extra-index-url\s*=(.+)/).flatten
113
+ urls[:extra] = extra_urls
89
114
 
90
115
  urls
91
116
  end
92
117
 
118
+ sig { returns(UrlsHash) }
93
119
  def pipfile_index_urls
94
- urls = { main: nil, extra: [] }
120
+ urls = T.let({ main: nil, extra: [] }, UrlsHash)
121
+ begin
122
+ return urls unless pipfile
95
123
 
96
- return urls unless pipfile
124
+ content = T.must(pipfile).content
125
+ return urls unless content
97
126
 
98
- pipfile_object = TomlRB.parse(pipfile.content)
127
+ pipfile_object = TomlRB.parse(content)
99
128
 
100
- urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
129
+ urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
101
130
 
102
- pipfile_object["source"]&.each do |source|
103
- urls[:extra] << source.fetch("url") if source["url"]
104
- end
105
- urls[:extra] = urls[:extra].uniq
131
+ pipfile_object["source"]&.each do |source|
132
+ urls[:extra] << source.fetch("url") if source["url"]
133
+ end
134
+ urls[:extra] = urls[:extra].uniq
106
135
 
107
- urls
108
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
109
- urls
136
+ urls
137
+ rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
138
+ urls
139
+ end
110
140
  end
111
141
 
142
+ sig { returns(UrlsHash) }
112
143
  def pyproject_index_urls
113
- urls = { main: nil, extra: [] }
114
-
115
- return urls unless pyproject
116
-
117
- sources =
118
- TomlRB.parse(pyproject.content).dig("tool", "poetry", "source") ||
119
- []
120
-
121
- sources.each do |source|
122
- # If source is PyPI, skip it, and let it pick the default URI
123
- next if source["name"].casecmp?("PyPI")
124
-
125
- if @dependency.all_sources.include?(source["name"])
126
- # If dependency has specified this source, use it
127
- return { main: source["url"], extra: [] }
128
- elsif source["default"]
129
- urls[:main] = source["url"]
130
- elsif source["priority"] != "explicit"
131
- # if source is not explicit, add it to extra
132
- urls[:extra] << source["url"]
144
+ urls = T.let({ main: nil, extra: [] }, UrlsHash)
145
+
146
+ begin
147
+ return urls unless pyproject
148
+
149
+ sources =
150
+ TomlRB.parse(T.must(T.must(pyproject).content)).dig("tool", "poetry", "source") ||
151
+ []
152
+
153
+ sources.each do |source|
154
+ # If source is PyPI, skip it, and let it pick the default URI
155
+ next if source["name"].casecmp?("PyPI")
156
+
157
+ if @dependency.all_sources.include?(source["name"])
158
+ # If dependency has specified this source, use it
159
+ return { main: source["url"], extra: [] }
160
+ elsif source["default"]
161
+ urls[:main] = source["url"]
162
+ elsif source["priority"] != "explicit"
163
+ # if source is not explicit, add it to extra
164
+ urls[:extra] << source["url"]
165
+ end
133
166
  end
134
- end
135
- urls[:extra] = urls[:extra].uniq
167
+ urls[:extra] = urls[:extra].uniq
136
168
 
137
- urls
138
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
139
- urls
169
+ urls
170
+ rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
171
+ urls
172
+ end
140
173
  end
141
174
 
175
+ sig { returns(UrlsHash) }
142
176
  def config_variable_index_urls
143
- urls = { main: nil, extra: [] }
177
+ urls = T.let({ main: nil, extra: [] }, UrlsHash)
144
178
 
145
179
  index_url_creds = credentials
146
180
  .select { |cred| cred["type"] == "python_index" }
@@ -157,6 +191,7 @@ module Dependabot
157
191
  urls
158
192
  end
159
193
 
194
+ sig { params(url: String).returns(String) }
160
195
  def clean_check_and_remove_environment_variables(url)
161
196
  url = url.strip.sub(%r{/+$}, "") + "/"
162
197
 
@@ -186,6 +221,7 @@ module Dependabot
186
221
  raise PrivateSourceAuthenticationFailure, url
187
222
  end
188
223
 
224
+ sig { params(base_url: String).returns(String) }
189
225
  def authed_base_url(base_url)
190
226
  cred = credential_for(base_url)
191
227
  return base_url unless cred
@@ -193,6 +229,7 @@ module Dependabot
193
229
  AuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$}, "") + "/"
194
230
  end
195
231
 
232
+ sig { params(url: String).returns(T.nilable(Dependabot::Credential)) }
196
233
  def credential_for(url)
197
234
  credentials
198
235
  .select { |c| c["type"] == "python_index" }
@@ -202,22 +239,27 @@ module Dependabot
202
239
  end
203
240
  end
204
241
 
242
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
205
243
  def pip_conf
206
244
  dependency_files.find { |f| f.name == "pip.conf" }
207
245
  end
208
246
 
247
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
209
248
  def pipfile
210
249
  dependency_files.find { |f| f.name == "Pipfile" }
211
250
  end
212
251
 
252
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
213
253
  def pyproject
214
254
  dependency_files.find { |f| f.name == "pyproject.toml" }
215
255
  end
216
256
 
257
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
217
258
  def requirements_files
218
259
  dependency_files.select { |f| f.name.match?(/requirements/x) }
219
260
  end
220
261
 
262
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
221
263
  def pip_compile_files
222
264
  dependency_files.select { |f| f.name.end_with?(".in") }
223
265
  end
@@ -25,7 +25,7 @@ module Dependabot
25
25
  @language_version_manager = language_version_manager
26
26
  end
27
27
 
28
- sig { params(constraint: String).returns(String) }
28
+ sig { params(constraint: T.nilable(String)).returns(String) }
29
29
  def run_upgrade(constraint)
30
30
  constraint = "" if constraint == "*"
31
31
  command = "pyenv exec pipenv upgrade --verbose #{dependency_name}#{constraint}"
@@ -34,7 +34,7 @@ module Dependabot
34
34
  run(command, fingerprint: "pyenv exec pipenv upgrade --verbose <dependency_name><constraint>")
35
35
  end
36
36
 
37
- sig { params(constraint: String).returns(T.nilable(String)) }
37
+ sig { params(constraint: T.nilable(String)).returns(T.nilable(String)) }
38
38
  def run_upgrade_and_fetch_version(constraint)
39
39
  run_upgrade(constraint)
40
40
 
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -12,26 +12,27 @@ module Dependabot
12
12
  class Requirement < Dependabot::Requirement
13
13
  extend T::Sig
14
14
 
15
- OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/
15
+ OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
16
16
 
17
17
  # Add equality and arbitrary-equality matchers
18
- OPS = OPS.merge(
19
- "==" => ->(v, r) { v == r },
20
- "===" => ->(v, r) { v.to_s == r.to_s }
21
- )
18
+ OPS = T.let(OPS.merge(
19
+ "==" => ->(v, r) { v == r },
20
+ "===" => ->(v, r) { v.to_s == r.to_s }
21
+ ), T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)])
22
22
 
23
23
  quoted = OPS.keys.sort_by(&:length).reverse
24
24
  .map { |k| Regexp.quote(k) }.join("|")
25
25
  version_pattern = Python::Version::VERSION_PATTERN
26
26
 
27
- PATTERN_RAW = "\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze
28
- PATTERN = /\A#{PATTERN_RAW}\z/
29
- PARENS_PATTERN = /\A\(([^)]+)\)\z/
27
+ PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
28
+ PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
29
+ PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
30
30
 
31
+ sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
31
32
  def self.parse(obj)
32
- return ["=", Python::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
33
+ return ["=", obj] if obj.is_a?(Gem::Version)
33
34
 
34
- line = obj.to_s
35
+ line = obj
35
36
  if (matches = PARENS_PATTERN.match(line))
36
37
  line = matches[1]
37
38
  end
@@ -63,6 +64,7 @@ module Dependabot
63
64
  end
64
65
  end
65
66
 
67
+ sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
66
68
  def initialize(*requirements)
67
69
  requirements = requirements.flatten.flat_map do |req_string|
68
70
  next if req_string.nil?
@@ -78,20 +80,23 @@ module Dependabot
78
80
  super(requirements)
79
81
  end
80
82
 
83
+ sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
81
84
  def satisfied_by?(version)
82
85
  version = Python::Version.new(version.to_s)
83
86
 
84
- requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
87
+ requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
85
88
  end
86
89
 
90
+ sig { returns(T::Boolean) }
87
91
  def exact?
88
- return false unless @requirements.size == 1
92
+ return false unless requirements.size == 1
89
93
 
90
- %w(= == ===).include?(@requirements[0][0])
94
+ %w(= == ===).include?(requirements[0][0])
91
95
  end
92
96
 
93
97
  private
94
98
 
99
+ sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
95
100
  def convert_python_constraint_to_ruby_constraint(req_string)
96
101
  return nil if req_string.nil? || req_string.strip.empty?
97
102
  return nil if req_string == "*"
@@ -111,6 +116,7 @@ module Dependabot
111
116
 
112
117
  # Poetry uses ~ requirements.
113
118
  # https://github.com/sdispater/poetry#tilde-requirements
119
+ sig { params(req_string: String).returns(String) }
114
120
  def convert_tilde_req(req_string)
115
121
  version = req_string.gsub(/^~\>?/, "")
116
122
  parts = version.split(".")
@@ -120,18 +126,19 @@ module Dependabot
120
126
 
121
127
  # Poetry uses ^ requirements
122
128
  # https://github.com/sdispater/poetry#caret-requirement
129
+ sig { params(req_string: String).returns(T::Array[String]) }
123
130
  def convert_caret_req(req_string)
124
131
  version = req_string.gsub(/^\^/, "")
125
132
  parts = version.split(".")
126
- parts.fill(0, parts.length...3)
133
+ parts.fill("0", parts.length...3)
127
134
  first_non_zero = parts.find { |d| d != "0" }
128
135
  first_non_zero_index =
129
136
  first_non_zero ? parts.index(first_non_zero) : parts.count - 1
130
137
  upper_bound = parts.map.with_index do |part, i|
131
- if i < first_non_zero_index then part
138
+ if i < T.must(first_non_zero_index) then part
132
139
  elsif i == first_non_zero_index then (part.to_i + 1).to_s
133
140
  # .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
134
- elsif i > first_non_zero_index && i == 2 then "0.dev"
141
+ elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
135
142
  else
136
143
  0
137
144
  end
@@ -140,24 +147,26 @@ module Dependabot
140
147
  [">= #{version}", "< #{upper_bound}"]
141
148
  end
142
149
 
150
+ sig { params(req_string: String).returns(String) }
143
151
  def convert_wildcard(req_string)
144
152
  # NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
145
153
  # "!= 1.0.0". There's no way to model this correctly in Ruby :'(
146
154
  quoted_ops = OPS.keys.sort_by(&:length).reverse
147
155
  .map { |k| Regexp.quote(k) }.join("|")
148
- op = req_string.match(/\A\s*(#{quoted_ops})?/)
149
- .captures.first.to_s&.strip
156
+ op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
157
+ op = op_match&.captures&.first.to_s.strip
150
158
  exact_op = ["", "=", "==", "==="].include?(op)
151
159
 
152
160
  req_string.strip
153
161
  .split(".")
154
- .first(req_string.split(".").index { |s| s.include?("*") } + 1)
162
+ .first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
155
163
  .join(".")
156
164
  .gsub(/\*(?!$)/, "0")
157
165
  .gsub(/\*$/, "0.dev")
158
166
  .tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
159
167
  end
160
168
 
169
+ sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
161
170
  def convert_exact(req_string)
162
171
  arbitrary_equality = req_string.start_with?("===")
163
172
  cleaned_version = req_string.gsub(/^=+/, "").strip