dependabot-python 0.332.0 → 0.334.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14beb61431b04fa613bbfa5d424c63c7c1d88557cf3983d48d310cd16aca4d32
4
- data.tar.gz: e9fadba2ea95a977b2f40dac856e04ba8ae7eaeb31942b78ffb6b66bba42c8a9
3
+ metadata.gz: b5a4c4861112bf50b42f285587b4786b583347cf52ea4160c605ea3d20f3e224
4
+ data.tar.gz: ba2b3dbcbedb42506cbb78884340ee0f7a38a39d67f9093f27c8b45d84ffae9b
5
5
  SHA512:
6
- metadata.gz: '0687eb3d178a073d2578660c521eece4372f649d94a2e386b8ead4c3ce46cf3aa31f0b980bc7df6ff2de6396e3bec6981632f2a05e0745686615951419662353'
7
- data.tar.gz: cd72b9690629928818f0355e0e0492861f94788d1d7005167cf484b3401ecf667e08f62325bd79c114cf595d19ea07c4b17ee11b9034eaeaf9a7036b1eb6ccc8
6
+ metadata.gz: 7d2d2be1da7577c7bd5e08a167988ce04f8a6864f25a1c6104baedc6592cde12d48e838ea8ee3693f05c24bf7663c7484f5a5537608902b923c130a853522243
7
+ data.tar.gz: b039ecae109ea713a2ef2669b3841ea8cb97a9e4854d1bf69ed93c993eb2240b89af5d9993d8dac92540da348dcb6e461d3e6f492df4e6fe081f3458f6fd8998
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "toml-rb"
@@ -12,9 +12,11 @@ require "dependabot/python/requirement_parser"
12
12
  require "dependabot/python/file_parser/pyproject_files_parser"
13
13
  require "dependabot/python/file_parser/python_requirement_parser"
14
14
  require "dependabot/errors"
15
+ require "dependabot/file_filtering"
15
16
 
16
17
  module Dependabot
17
18
  module Python
19
+ # rubocop:disable Metrics/ClassLength
18
20
  class FileFetcher < Dependabot::FileFetchers::Base
19
21
  extend T::Sig
20
22
  extend T::Helpers
@@ -23,6 +25,7 @@ module Dependabot
23
25
  CONSTRAINT_REGEX = /^-c\s?(?<path>.*\.(?:txt|in))/
24
26
  DEPENDENCY_TYPES = %w(packages dev-packages).freeze
25
27
 
28
+ sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
26
29
  def self.required_files_in?(filenames)
27
30
  return true if filenames.any? { |name| name.end_with?(".txt", ".in") }
28
31
 
@@ -40,11 +43,13 @@ module Dependabot
40
43
  filenames.include?("setup.cfg")
41
44
  end
42
45
 
46
+ sig { override.returns(String) }
43
47
  def self.required_files_message
44
48
  "Repo must contain a requirements.txt, setup.py, setup.cfg, pyproject.toml, " \
45
49
  "or a Pipfile."
46
50
  end
47
51
 
52
+ sig { override.returns(T::Hash[Symbol, T::Hash[Symbol, T::Hash[String, String]]]) }
48
53
  def ecosystem_versions
49
54
  # Hmm... it's weird that this calls file parser methods, but here we are in the file fetcher... for all
50
55
  # ecosystems our goal is to extract the user specified versions, so we'll need to do file parsing... so should
@@ -84,25 +89,34 @@ module Dependabot
84
89
  fetched_files << pip_conf if pip_conf
85
90
  fetched_files << python_version_file if python_version_file
86
91
 
87
- uniq_files(fetched_files)
92
+ uniques = uniq_files(fetched_files)
93
+ filtered_files = uniques.reject do |file|
94
+ Dependabot::FileFiltering.should_exclude_path?(file.name, "file from final collection", @exclude_paths)
95
+ end
96
+
97
+ filtered_files
88
98
  end
89
99
 
90
100
  private
91
101
 
102
+ sig { params(fetched_files: T::Array[Dependabot::DependencyFile]).returns(T::Array[Dependabot::DependencyFile]) }
92
103
  def uniq_files(fetched_files)
93
104
  uniq_files = fetched_files.reject(&:support_file?).uniq
94
105
  uniq_files += fetched_files
95
106
  .reject { |f| uniq_files.map(&:name).include?(f.name) }
96
107
  end
97
108
 
109
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
98
110
  def pipenv_files
99
111
  [pipfile, pipfile_lock].compact
100
112
  end
101
113
 
114
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
102
115
  def pyproject_files
103
116
  [pyproject, poetry_lock, pdm_lock].compact
104
117
  end
105
118
 
119
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
106
120
  def requirement_files
107
121
  [
108
122
  *requirements_txt_files,
@@ -111,114 +125,141 @@ module Dependabot
111
125
  ]
112
126
  end
113
127
 
128
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
114
129
  def setup_file
115
- return @setup_file if defined?(@setup_file)
116
-
117
- @setup_file = fetch_file_if_present("setup.py")
130
+ @setup_file ||= T.let(
131
+ fetch_file_if_present("setup.py"),
132
+ T.nilable(Dependabot::DependencyFile)
133
+ )
118
134
  end
119
135
 
136
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
120
137
  def setup_cfg_file
121
- return @setup_cfg_file if defined?(@setup_cfg_file)
122
-
123
- @setup_cfg_file = fetch_file_if_present("setup.cfg")
138
+ @setup_cfg_file ||= T.let(
139
+ fetch_file_if_present("setup.cfg"),
140
+ T.nilable(Dependabot::DependencyFile)
141
+ )
124
142
  end
125
143
 
144
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
126
145
  def pip_conf
127
- return @pip_conf if defined?(@pip_conf)
128
-
129
- @pip_conf = fetch_support_file("pip.conf")
146
+ @pip_conf ||= T.let(
147
+ fetch_support_file("pip.conf"),
148
+ T.nilable(Dependabot::DependencyFile)
149
+ )
130
150
  end
131
151
 
152
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
132
153
  def python_version_file
133
- return @python_version_file if defined?(@python_version_file)
134
-
135
- @python_version_file = fetch_support_file(".python-version")
136
-
137
- return @python_version_file if @python_version_file
138
- return if [".", "/"].include?(directory)
154
+ @python_version_file ||= T.let(begin
155
+ file = fetch_support_file(".python-version")
156
+ return file if file
157
+ return if [".", "/"].include?(directory)
139
158
 
140
- # Check the top-level for a .python-version file, too
141
- reverse_path = Pathname.new(directory[0]).relative_path_from(directory)
142
- @python_version_file =
159
+ # Check the top-level for a .python-version file, too
160
+ reverse_path = Pathname.new(directory[0]).relative_path_from(directory)
143
161
  fetch_support_file(File.join(reverse_path, ".python-version"))
144
- &.tap { |f| f.name = ".python-version" }
162
+ &.tap { |f| f.name = ".python-version" }
163
+ end, T.nilable(Dependabot::DependencyFile))
145
164
  end
146
165
 
166
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
147
167
  def pipfile
148
- return @pipfile if defined?(@pipfile)
149
-
150
- @pipfile = fetch_file_if_present("Pipfile")
168
+ @pipfile ||= T.let(
169
+ fetch_file_if_present("Pipfile"),
170
+ T.nilable(Dependabot::DependencyFile)
171
+ )
151
172
  end
152
173
 
174
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
153
175
  def pipfile_lock
154
- return @pipfile_lock if defined?(@pipfile_lock)
155
-
156
- @pipfile_lock = fetch_file_if_present("Pipfile.lock")
176
+ @pipfile_lock ||= T.let(
177
+ fetch_file_if_present("Pipfile.lock"),
178
+ T.nilable(Dependabot::DependencyFile)
179
+ )
157
180
  end
158
181
 
182
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
159
183
  def pyproject
160
- return @pyproject if defined?(@pyproject)
161
-
162
- @pyproject = fetch_file_if_present("pyproject.toml")
184
+ @pyproject ||= T.let(
185
+ fetch_file_if_present("pyproject.toml"),
186
+ T.nilable(Dependabot::DependencyFile)
187
+ )
163
188
  end
164
189
 
190
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
165
191
  def poetry_lock
166
- return @poetry_lock if defined?(@poetry_lock)
167
-
168
- @poetry_lock = fetch_file_if_present("poetry.lock")
192
+ @poetry_lock ||= T.let(
193
+ fetch_file_if_present("poetry.lock"),
194
+ T.nilable(Dependabot::DependencyFile)
195
+ )
169
196
  end
170
197
 
198
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
171
199
  def pdm_lock
172
- return @pdm_lock if defined?(@pdm_lock)
173
-
174
- @pdm_lock = fetch_file_if_present("pdm.lock")
200
+ @pdm_lock ||= T.let(
201
+ fetch_file_if_present("pdm.lock"),
202
+ T.nilable(Dependabot::DependencyFile)
203
+ )
175
204
  end
176
205
 
206
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
177
207
  def requirements_txt_files
178
208
  req_txt_and_in_files.select { |f| f.name.end_with?(".txt") }
179
209
  end
180
210
 
211
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
181
212
  def requirements_in_files
182
213
  req_txt_and_in_files.select { |f| f.name.end_with?(".in") } +
183
214
  child_requirement_in_files
184
215
  end
185
216
 
217
+ sig { returns(T::Hash[String, T.untyped]) }
186
218
  def parsed_pipfile
187
219
  raise "No Pipfile" unless pipfile
188
220
 
189
- @parsed_pipfile ||= TomlRB.parse(pipfile.content)
221
+ @parsed_pipfile ||= T.let(
222
+ TomlRB.parse(T.must(pipfile).content),
223
+ T.nilable(T::Hash[String, T.untyped])
224
+ )
190
225
  rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
191
- raise Dependabot::DependencyFileNotParseable, pipfile.path
226
+ raise Dependabot::DependencyFileNotParseable, T.must(pipfile).path
192
227
  end
193
228
 
229
+ sig { returns(T::Hash[String, T.untyped]) }
194
230
  def parsed_pyproject
195
231
  raise "No pyproject.toml" unless pyproject
196
232
 
197
- @parsed_pyproject ||= TomlRB.parse(pyproject.content)
233
+ @parsed_pyproject ||= T.let(
234
+ TomlRB.parse(T.must(pyproject).content),
235
+ T.nilable(T::Hash[String, T.untyped])
236
+ )
198
237
  rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
199
- raise Dependabot::DependencyFileNotParseable, pyproject.path
238
+ raise Dependabot::DependencyFileNotParseable, T.must(pyproject).path
200
239
  end
201
240
 
241
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
202
242
  def req_txt_and_in_files
203
- return @req_txt_and_in_files if @req_txt_and_in_files
243
+ @req_txt_and_in_files ||= T.let(begin
244
+ files = T.let([], T::Array[Dependabot::DependencyFile])
204
245
 
205
- @req_txt_and_in_files = []
246
+ repo_contents
247
+ .select { |f| f.type == "file" }
248
+ .select { |f| f.name.end_with?(".txt", ".in") }
249
+ .reject { |f| f.size > 500_000 }
250
+ .map { |f| fetch_file_from_host(f.name) }
251
+ .select { |f| requirements_file?(f) }
252
+ .each { |f| files << f }
206
253
 
207
- repo_contents
208
- .select { |f| f.type == "file" }
209
- .select { |f| f.name.end_with?(".txt", ".in") }
210
- .reject { |f| f.size > 500_000 }
211
- .map { |f| fetch_file_from_host(f.name) }
212
- .select { |f| requirements_file?(f) }
213
- .each { |f| @req_txt_and_in_files << f }
254
+ repo_contents
255
+ .select { |f| f.type == "dir" }
256
+ .each { |f| files.concat(req_files_for_dir(f)) }
214
257
 
215
- repo_contents
216
- .select { |f| f.type == "dir" }
217
- .each { |f| @req_txt_and_in_files += req_files_for_dir(f) }
218
-
219
- @req_txt_and_in_files
258
+ files
259
+ end, T.nilable(T::Array[Dependabot::DependencyFile]))
220
260
  end
221
261
 
262
+ sig { params(requirements_dir: T.untyped).returns(T::Array[Dependabot::DependencyFile]) }
222
263
  def req_files_for_dir(requirements_dir)
223
264
  dir = directory.gsub(%r{(^/|/$)}, "")
224
265
  relative_reqs_dir =
@@ -232,32 +273,41 @@ module Dependabot
232
273
  .select { |f| requirements_file?(f) }
233
274
  end
234
275
 
276
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
235
277
  def child_requirement_txt_files
236
278
  child_requirement_files.select { |f| f.name.end_with?(".txt") }
237
279
  end
238
280
 
281
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
239
282
  def child_requirement_in_files
240
283
  child_requirement_files.select { |f| f.name.end_with?(".in") }
241
284
  end
242
285
 
286
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
243
287
  def child_requirement_files
244
- @child_requirement_files ||=
245
- begin
246
- fetched_files = req_txt_and_in_files.dup
247
- req_txt_and_in_files.flat_map do |requirement_file|
248
- child_files = fetch_child_requirement_files(
249
- file: requirement_file,
250
- previously_fetched_files: fetched_files
251
- )
252
-
253
- fetched_files += child_files
254
- child_files
255
- end
288
+ @child_requirement_files ||= T.let(begin
289
+ fetched_files = req_txt_and_in_files.dup
290
+ req_txt_and_in_files.flat_map do |requirement_file|
291
+ child_files = fetch_child_requirement_files(
292
+ file: requirement_file,
293
+ previously_fetched_files: fetched_files
294
+ )
295
+
296
+ fetched_files += child_files
297
+ child_files
256
298
  end
299
+ end, T.nilable(T::Array[Dependabot::DependencyFile]))
257
300
  end
258
301
 
302
+ sig do
303
+ params(
304
+ file: Dependabot::DependencyFile,
305
+ previously_fetched_files: T::Array[Dependabot::DependencyFile]
306
+ )
307
+ .returns(T::Array[Dependabot::DependencyFile])
308
+ end
259
309
  def fetch_child_requirement_files(file:, previously_fetched_files:)
260
- paths = file.content.scan(CHILD_REQUIREMENT_REGEX).flatten
310
+ paths = T.must(file.content).scan(CHILD_REQUIREMENT_REGEX).flatten
261
311
  current_dir = File.dirname(file.name)
262
312
 
263
313
  paths.flat_map do |path|
@@ -267,6 +317,14 @@ module Dependabot
267
317
  next if previously_fetched_files.map(&:name).include?(path)
268
318
  next if file.name == path
269
319
 
320
+ if Dependabot::Experiments.enabled?(:enable_exclude_paths_subdirectory_manifest_files) &&
321
+ !@exclude_paths.empty? && Dependabot::FileFiltering.exclude_path?(path, @exclude_paths)
322
+ raise Dependabot::DependencyFileNotEvaluatable,
323
+ "Cannot process requirements: '#{file.name}' references excluded file '#{path}'. " \
324
+ "Please either remove the reference from '#{file.name}' " \
325
+ "or update your exclude_paths configuration."
326
+ end
327
+
270
328
  fetched_file = fetch_file_from_host(path)
271
329
  grandchild_requirement_files = fetch_child_requirement_files(
272
330
  file: fetched_file,
@@ -276,13 +334,14 @@ module Dependabot
276
334
  end.compact
277
335
  end
278
336
 
337
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
279
338
  def constraints_files
280
339
  all_requirement_files = requirements_txt_files +
281
340
  child_requirement_txt_files
282
341
 
283
342
  constraints_paths = all_requirement_files.map do |req_file|
284
343
  current_dir = File.dirname(req_file.name)
285
- paths = req_file.content.scan(CONSTRAINT_REGEX).flatten
344
+ paths = T.must(req_file.content).scan(CONSTRAINT_REGEX).flatten
286
345
 
287
346
  paths.map do |path|
288
347
  path = File.join(current_dir, path) unless current_dir == "."
@@ -293,15 +352,16 @@ module Dependabot
293
352
  constraints_paths.map { |path| fetch_file_from_host(path) }
294
353
  end
295
354
 
355
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
296
356
  def project_files
297
357
  project_files = T.let([], T::Array[Dependabot::DependencyFile])
298
358
  unfetchable_deps = []
299
359
 
300
360
  path_dependencies.each do |dep|
301
- path = dep[:path]
361
+ path = T.must(dep[:path])
302
362
  project_files += fetch_project_file(path)
303
363
  rescue Dependabot::DependencyFileNotFound => e
304
- unfetchable_deps << if sdist_or_wheel?(path)
364
+ unfetchable_deps << if sdist_or_wheel?(T.must(path))
305
365
  e.file_path&.gsub(%r{^/}, "")
306
366
  else
307
367
  "\"#{dep[:name]}\" at #{cleanpath(File.join(directory, dep[:file]))}"
@@ -319,6 +379,7 @@ module Dependabot
319
379
  project_files
320
380
  end
321
381
 
382
+ sig { params(path: String).returns(T::Array[Dependabot::DependencyFile]) }
322
383
  def fetch_project_file(path)
323
384
  project_files = []
324
385
 
@@ -346,10 +407,12 @@ module Dependabot
346
407
  project_files + cfg_files_for_setup_py(path)
347
408
  end
348
409
 
410
+ sig { params(path: String).returns(T::Boolean) }
349
411
  def sdist_or_wheel?(path)
350
412
  path.end_with?(".tar.gz", ".whl", ".zip")
351
413
  end
352
414
 
415
+ sig { params(path: String).returns(T::Array[Dependabot::DependencyFile]) }
353
416
  def cfg_files_for_setup_py(path)
354
417
  cfg_path = path.gsub(/\.py$/, ".cfg")
355
418
 
@@ -364,11 +427,12 @@ module Dependabot
364
427
  end
365
428
  end
366
429
 
430
+ sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
367
431
  def requirements_file?(file)
368
- return false unless file.content.valid_encoding?
432
+ return false unless T.must(file.content).valid_encoding?
369
433
  return true if file.name.match?(/requirements/x)
370
434
 
371
- file.content.lines.all? do |line|
435
+ T.must(file.content).lines.all? do |line|
372
436
  next true if line.strip.empty?
373
437
  next true if line.strip.start_with?("#", "-r ", "-c ", "-e ", "--")
374
438
 
@@ -376,45 +440,54 @@ module Dependabot
376
440
  end
377
441
  end
378
442
 
443
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
379
444
  def path_dependencies
380
445
  requirement_txt_path_dependencies +
381
446
  requirement_in_path_dependencies +
382
447
  pipfile_path_dependencies
383
448
  end
384
449
 
450
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
385
451
  def requirement_txt_path_dependencies
386
452
  (requirements_txt_files + child_requirement_txt_files)
387
453
  .map { |req_file| parse_requirement_path_dependencies(req_file) }
388
454
  .flatten.uniq { |dep| dep[:path] }
389
455
  end
390
456
 
457
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
391
458
  def requirement_in_path_dependencies
392
459
  requirements_in_files
393
460
  .map { |req_file| parse_requirement_path_dependencies(req_file) }
394
461
  .flatten.uniq { |dep| dep[:path] }
395
462
  end
396
463
 
464
+ sig { params(req_file: Dependabot::DependencyFile).returns(T::Array[T::Hash[Symbol, String]]) }
397
465
  def parse_requirement_path_dependencies(req_file)
398
466
  # If this is a pip-compile lockfile, rely on whatever path dependencies we found in the main manifest
399
467
  return [] if pip_compile_file_matcher.lockfile_for_pip_compile_file?(req_file)
400
468
 
401
469
  uneditable_reqs =
402
- req_file.content
403
- .scan(/(?<name>^['"]?(?:file:)?(?<path>\..*?)(?=\[|#|'|"|$))/)
404
- .filter_map do |n, p|
405
- { name: n.strip, path: p.strip, file: req_file.name } unless p.include?("://")
406
- end
470
+ T.must(req_file.content)
471
+ .scan(/(?<name>^['"]?(?:file:)?(?<path>\..*?)(?=\[|#|'|"|$))/)
472
+ .filter_map do |match_array|
473
+ n, p = match_array
474
+ { name: n.to_s.strip, path: p.to_s.strip, file: req_file.name } unless p.to_s.include?("://")
475
+ end
407
476
 
408
477
  editable_reqs =
409
- req_file.content
410
- .scan(/(?<name>^(?:-e)\s+['"]?(?:file:)?(?<path>.*?)(?=\[|#|'|"|$))/)
411
- .filter_map do |n, p|
412
- { name: n.strip, path: p.strip, file: req_file.name } unless p.include?("://") || p.include?("git@")
413
- end
478
+ T.must(req_file.content)
479
+ .scan(/(?<name>^(?:-e)\s+['"]?(?:file:)?(?<path>.*?)(?=\[|#|'|"|$))/)
480
+ .filter_map do |match_array|
481
+ n, p = match_array
482
+ unless p.to_s.include?("://") || p.to_s.include?("git@")
483
+ { name: n.to_s.strip, path: p.to_s.strip, file: req_file.name }
484
+ end
485
+ end
414
486
 
415
487
  uneditable_reqs + editable_reqs
416
488
  end
417
489
 
490
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
418
491
  def pipfile_path_dependencies
419
492
  return [] unless pipfile
420
493
 
@@ -425,13 +498,14 @@ module Dependabot
425
498
  parsed_pipfile[dep_type].each do |_, req|
426
499
  next unless req.is_a?(Hash) && req["path"]
427
500
 
428
- deps << { name: req["path"], path: req["path"], file: pipfile.name }
501
+ deps << { name: req["path"], path: req["path"], file: T.must(pipfile).name }
429
502
  end
430
503
  end
431
504
 
432
505
  deps
433
506
  end
434
507
 
508
+ sig { returns(T::Array[String]) }
435
509
  def poetry_path_dependencies
436
510
  return [] unless pyproject
437
511
 
@@ -449,14 +523,20 @@ module Dependabot
449
523
  paths
450
524
  end
451
525
 
526
+ sig { params(path: String).returns(String) }
452
527
  def cleanpath(path)
453
528
  Pathname.new(path).cleanpath.to_path
454
529
  end
455
530
 
531
+ sig { returns(Dependabot::Python::PipCompileFileMatcher) }
456
532
  def pip_compile_file_matcher
457
- @pip_compile_file_matcher ||= PipCompileFileMatcher.new(requirements_in_files)
533
+ @pip_compile_file_matcher ||= T.let(
534
+ PipCompileFileMatcher.new(requirements_in_files),
535
+ T.nilable(Dependabot::Python::PipCompileFileMatcher)
536
+ )
458
537
  end
459
538
  end
539
+ # rubocop:enable Metrics/ClassLength
460
540
  end
461
541
  end
462
542
 
@@ -14,6 +14,7 @@ module Dependabot
14
14
  class FileParser
15
15
  class PipfileFilesParser
16
16
  extend T::Sig
17
+
17
18
  DEPENDENCY_GROUP_KEYS = T.let([
18
19
  {
19
20
  pipfile: "packages",
@@ -73,8 +74,7 @@ module Dependabot
73
74
  groups: [group]
74
75
  }],
75
76
  package_manager: "pip",
76
- metadata: { original_name: dep_name },
77
- origin_files: [T.must(pipfile).name]
77
+ metadata: { original_name: dep_name }
78
78
  )
79
79
  end
80
80
  end
@@ -107,8 +107,7 @@ module Dependabot
107
107
  version: version&.gsub(/^===?/, ""),
108
108
  requirements: [],
109
109
  package_manager: "pip",
110
- subdependency_metadata: [{ production: key != "develop" }],
111
- origin_files: [T.must(pipfile_lock).name]
110
+ subdependency_metadata: [{ production: key != "develop" }]
112
111
  )
113
112
  end
114
113
  end
@@ -15,6 +15,7 @@ module Dependabot
15
15
  class FileParser
16
16
  class PyprojectFilesParser
17
17
  extend T::Sig
18
+
18
19
  POETRY_DEPENDENCY_TYPES = %w(dependencies dev-dependencies).freeze
19
20
 
20
21
  # https://python-poetry.org/docs/dependency-specification/
@@ -98,8 +99,7 @@ module Dependabot
98
99
  source: nil,
99
100
  groups: [dep["requirement_type"]].compact
100
101
  }],
101
- package_manager: "pip",
102
- origin_files: [Pathname.new(dep["file"]).cleanpath.to_path]
102
+ package_manager: "pip"
103
103
  )
104
104
  end
105
105
 
@@ -124,8 +124,7 @@ module Dependabot
124
124
  name: normalise(name),
125
125
  version: version_from_lockfile(name),
126
126
  requirements: requirements,
127
- package_manager: "pip",
128
- origin_files: [T.must(pyproject).name]
127
+ package_manager: "pip"
129
128
  )
130
129
  end
131
130
  dependencies
@@ -210,8 +209,7 @@ module Dependabot
210
209
  package_manager: "pip",
211
210
  subdependency_metadata: [{
212
211
  production: production_dependency_names.include?(name)
213
- }],
214
- origin_files: [lockfile.name]
212
+ }]
215
213
  )
216
214
  end
217
215
 
@@ -15,6 +15,7 @@ module Dependabot
15
15
  class FileParser
16
16
  class SetupFileParser
17
17
  extend T::Sig
18
+
18
19
  INSTALL_REQUIRES_REGEX = /install_requires\s*=\s*\[/m
19
20
  SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*\[/m
20
21
  TESTS_REQUIRE_REGEX = /tests_require\s*=\s*\[/m
@@ -50,8 +51,7 @@ module Dependabot
50
51
  source: nil,
51
52
  groups: [dep["requirement_type"]]
52
53
  }],
53
- package_manager: "pip",
54
- origin_files: [Pathname.new(dep["file"]).cleanpath.to_path]
54
+ package_manager: "pip"
55
55
  )
56
56
  end
57
57
  dependencies
@@ -19,6 +19,7 @@ module Dependabot
19
19
  module Python
20
20
  class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
21
21
  extend T::Sig
22
+
22
23
  require_relative "file_parser/pipfile_files_parser"
23
24
  require_relative "file_parser/pyproject_files_parser"
24
25
  require_relative "file_parser/setup_file_parser"
@@ -281,8 +282,7 @@ module Dependabot
281
282
  name: normalised_name(name, dep["extras"]),
282
283
  version: version&.include?("*") ? nil : version,
283
284
  requirements: requirements,
284
- package_manager: "pip",
285
- origin_files: [Pathname.new(file).cleanpath.to_path]
285
+ package_manager: "pip"
286
286
  )
287
287
  end
288
288
  dependencies
@@ -19,6 +19,7 @@ module Dependabot
19
19
  # rubocop:disable Metrics/ClassLength
20
20
  class PipCompileFileUpdater
21
21
  extend T::Sig
22
+
22
23
  require_relative "requirement_replacer"
23
24
  require_relative "requirement_file_updater"
24
25
  require_relative "setup_file_sanitizer"
@@ -17,6 +17,7 @@ module Dependabot
17
17
  class FileUpdater
18
18
  class PipfileFileUpdater
19
19
  extend T::Sig
20
+
20
21
  require_relative "pipfile_preparer"
21
22
  require_relative "pipfile_manifest_updater"
22
23
  require_relative "setup_file_sanitizer"
@@ -57,7 +57,7 @@ module Dependabot
57
57
 
58
58
  # provide a default "true" value to file generator in case no value is provided in manifest file
59
59
  pipfile_object["source"].each do |key|
60
- key["verify_ssl"] = verify_ssl.nil? ? true : verify_ssl
60
+ key["verify_ssl"] = verify_ssl.nil? || verify_ssl
61
61
  end
62
62
 
63
63
  TomlRB.dump(pipfile_object)
@@ -11,6 +11,7 @@ module Dependabot
11
11
 
12
12
  class Language < Dependabot::Ecosystem::VersionManager
13
13
  extend T::Sig
14
+
14
15
  # This list must match the versions specified at the top of `python/Dockerfile`
15
16
  # ARG PY_3_13=3.13.2
16
17
  # When updating this list, also update uv/lib/dependabot/uv/language.rb
@@ -14,6 +14,7 @@ module Dependabot
14
14
  module Python
15
15
  class MetadataFinder < Dependabot::MetadataFinders::Base
16
16
  extend T::Sig
17
+
17
18
  MAIN_PYPI_URL = "https://pypi.org/pypi"
18
19
 
19
20
  sig do
@@ -196,7 +197,9 @@ module Dependabot
196
197
  .map { |c| AuthedUrlBuilder.authed_url(credential: c) }
197
198
 
198
199
  (credential_urls + [MAIN_PYPI_URL]).map do |base_url|
199
- base_url.gsub(%r{/$}, "") + "/#{normalised_dependency_name}/json"
200
+ # Convert /simple/ endpoints to /pypi/ for JSON API access
201
+ json_base_url = base_url.sub(%r{/simple/?$}i, "/pypi")
202
+ json_base_url.gsub(%r{/$}, "") + "/#{normalised_dependency_name}/json"
200
203
  end
201
204
  end
202
205
 
@@ -389,7 +389,7 @@ module Dependabot
389
389
  elsif req_string.strip.start_with?("~=", "==")
390
390
  version.segments.count - 2
391
391
  elsif req_string.strip.start_with?("~")
392
- req_string.split(".").count == 1 ? 0 : 1
392
+ req_string.split(".").one? ? 0 : 1
393
393
  else
394
394
  raise "Don't know how to convert #{req_string} to range"
395
395
  end
@@ -1,8 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "toml-rb"
6
+ require "sorbet-runtime"
6
7
 
7
8
  require "dependabot/dependency"
8
9
  require "dependabot/errors"
@@ -17,6 +18,8 @@ require "dependabot/update_checkers/base"
17
18
  module Dependabot
18
19
  module Python
19
20
  class UpdateChecker < Dependabot::UpdateCheckers::Base
21
+ extend T::Sig
22
+
20
23
  require_relative "update_checker/poetry_version_resolver"
21
24
  require_relative "update_checker/pipenv_version_resolver"
22
25
  require_relative "update_checker/pip_compile_version_resolver"
@@ -30,12 +33,17 @@ module Dependabot
30
33
  ).freeze
31
34
  VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
32
35
 
36
+ sig { override.returns(T.nilable(Gem::Version)) }
33
37
  def latest_version
34
- @latest_version ||= fetch_latest_version
38
+ @latest_version ||= T.let(
39
+ fetch_latest_version,
40
+ T.nilable(Gem::Version)
41
+ )
35
42
  end
36
43
 
44
+ sig { override.returns(T.nilable(Gem::Version)) }
37
45
  def latest_resolvable_version
38
- @latest_resolvable_version ||=
46
+ @latest_resolvable_version ||= T.let(
39
47
  if resolver_type == :requirements
40
48
  resolver.latest_resolvable_version
41
49
  elsif resolver_type == :pip_compile && resolver.resolvable?(version: latest_version)
@@ -44,33 +52,41 @@ module Dependabot
44
52
  resolver.latest_resolvable_version(
45
53
  requirement: unlocked_requirement_string
46
54
  )
47
- end
55
+ end,
56
+ T.nilable(Gem::Version)
57
+ )
48
58
  end
49
59
 
60
+ sig { override.returns(T.nilable(Gem::Version)) }
50
61
  def latest_resolvable_version_with_no_unlock
51
- @latest_resolvable_version_with_no_unlock ||=
62
+ @latest_resolvable_version_with_no_unlock ||= T.let(
52
63
  if resolver_type == :requirements
53
64
  resolver.latest_resolvable_version_with_no_unlock
54
65
  else
55
66
  resolver.latest_resolvable_version(
56
67
  requirement: current_requirement_string
57
68
  )
58
- end
69
+ end,
70
+ T.nilable(Gem::Version)
71
+ )
59
72
  end
60
73
 
74
+ sig { override.returns(T.nilable(Gem::Version)) }
61
75
  def lowest_security_fix_version
62
76
  latest_version_finder.lowest_security_fix_version
63
77
  end
64
78
 
79
+ sig { override.returns(T.nilable(Gem::Version)) }
65
80
  def lowest_resolvable_security_fix_version
66
81
  raise "Dependency not vulnerable!" unless vulnerable?
67
82
 
68
- return @lowest_resolvable_security_fix_version if defined?(@lowest_resolvable_security_fix_version)
69
-
70
- @lowest_resolvable_security_fix_version =
71
- fetch_lowest_resolvable_security_fix_version
83
+ @lowest_resolvable_security_fix_version ||= T.let(
84
+ fetch_lowest_resolvable_security_fix_version,
85
+ T.nilable(Gem::Version)
86
+ )
72
87
  end
73
88
 
89
+ sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
74
90
  def updated_requirements
75
91
  RequirementsUpdater.new(
76
92
  requirements: requirements,
@@ -80,10 +96,12 @@ module Dependabot
80
96
  ).updated_requirements
81
97
  end
82
98
 
99
+ sig { override.returns(T::Boolean) }
83
100
  def requirements_unlocked_or_can_be?
84
101
  !requirements_update_strategy.lockfile_only?
85
102
  end
86
103
 
104
+ sig { override.returns(Dependabot::RequirementsUpdateStrategy) }
87
105
  def requirements_update_strategy
88
106
  # If passed in as an option (in the base class) honour that option
89
107
  return @requirements_update_strategy if @requirements_update_strategy
@@ -94,15 +112,18 @@ module Dependabot
94
112
 
95
113
  private
96
114
 
115
+ sig { override.returns(T::Boolean) }
97
116
  def latest_version_resolvable_with_full_unlock?
98
117
  # Full unlock checks aren't implemented for Python (yet)
99
118
  false
100
119
  end
101
120
 
121
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
102
122
  def updated_dependencies_after_full_unlock
103
123
  raise NotImplementedError
104
124
  end
105
125
 
126
+ sig { returns(T.nilable(Gem::Version)) }
106
127
  def fetch_lowest_resolvable_security_fix_version
107
128
  fix_version = lowest_security_fix_version
108
129
  return latest_resolvable_version if fix_version.nil?
@@ -112,6 +133,7 @@ module Dependabot
112
133
  resolver.resolvable?(version: fix_version) ? fix_version : nil
113
134
  end
114
135
 
136
+ sig { returns(T.untyped) }
115
137
  def resolver
116
138
  if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)
117
139
  Dependabot.logger.info("Python package resolver : #{resolver_type}")
@@ -126,6 +148,7 @@ module Dependabot
126
148
  end
127
149
  end
128
150
 
151
+ sig { returns(Symbol) }
129
152
  def resolver_type
130
153
  reqs = requirements
131
154
 
@@ -147,6 +170,7 @@ module Dependabot
147
170
  end
148
171
  end
149
172
 
173
+ sig { returns(Symbol) }
150
174
  def subdependency_resolver
151
175
  return :pipenv if pipfile_lock
152
176
  return :poetry if poetry_lock
@@ -155,12 +179,14 @@ module Dependabot
155
179
  raise "Claimed to be a sub-dependency, but no lockfile exists!"
156
180
  end
157
181
 
182
+ sig { returns(Symbol) }
158
183
  def pyproject_resolver
159
184
  return :poetry if poetry_based?
160
185
 
161
186
  :requirements
162
187
  end
163
188
 
189
+ sig { params(reqs: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Boolean) }
164
190
  def exact_requirement?(reqs)
165
191
  reqs = reqs.map { |r| r.fetch(:requirement) }
166
192
  reqs = reqs.compact
@@ -168,31 +194,62 @@ module Dependabot
168
194
  reqs.any? { |r| Python::Requirement.new(r).exact? }
169
195
  end
170
196
 
197
+ sig { returns(PipenvVersionResolver) }
171
198
  def pipenv_version_resolver
172
- @pipenv_version_resolver ||= PipenvVersionResolver.new(**resolver_args)
199
+ @pipenv_version_resolver ||= T.let(
200
+ PipenvVersionResolver.new(
201
+ dependency: dependency,
202
+ dependency_files: dependency_files,
203
+ credentials: credentials,
204
+ repo_contents_path: repo_contents_path
205
+ ),
206
+ T.nilable(PipenvVersionResolver)
207
+ )
173
208
  end
174
209
 
210
+ sig { returns(PipCompileVersionResolver) }
175
211
  def pip_compile_version_resolver
176
- @pip_compile_version_resolver ||=
177
- PipCompileVersionResolver.new(**resolver_args)
212
+ @pip_compile_version_resolver ||= T.let(
213
+ PipCompileVersionResolver.new(
214
+ dependency: dependency,
215
+ dependency_files: dependency_files,
216
+ credentials: credentials,
217
+ repo_contents_path: repo_contents_path
218
+ ),
219
+ T.nilable(PipCompileVersionResolver)
220
+ )
178
221
  end
179
222
 
223
+ sig { returns(PoetryVersionResolver) }
180
224
  def poetry_version_resolver
181
- @poetry_version_resolver ||= PoetryVersionResolver.new(**resolver_args)
225
+ @poetry_version_resolver ||= T.let(
226
+ PoetryVersionResolver.new(
227
+ dependency: dependency,
228
+ dependency_files: dependency_files,
229
+ credentials: credentials,
230
+ repo_contents_path: repo_contents_path
231
+ ),
232
+ T.nilable(PoetryVersionResolver)
233
+ )
182
234
  end
183
235
 
236
+ sig { returns(PipVersionResolver) }
184
237
  def pip_version_resolver
185
- @pip_version_resolver ||= PipVersionResolver.new(
186
- dependency: dependency,
187
- dependency_files: dependency_files,
188
- credentials: credentials,
189
- ignored_versions: ignored_versions,
190
- update_cooldown: @update_cooldown,
191
- raise_on_ignored: @raise_on_ignored,
192
- security_advisories: security_advisories
238
+ @pip_version_resolver ||= T.let(
239
+ PipVersionResolver.new(
240
+ dependency: dependency,
241
+ dependency_files: dependency_files,
242
+ credentials: credentials,
243
+ ignored_versions: ignored_versions,
244
+ update_cooldown: @update_cooldown,
245
+ raise_on_ignored: @raise_on_ignored,
246
+ security_advisories: security_advisories
247
+ ),
248
+ T.nilable(PipVersionResolver)
193
249
  )
194
250
  end
195
251
 
252
+ sig { returns(T::Hash[Symbol, T.untyped]) }
196
253
  def resolver_args
197
254
  {
198
255
  dependency: dependency,
@@ -202,6 +259,7 @@ module Dependabot
202
259
  }
203
260
  end
204
261
 
262
+ sig { returns(T.nilable(String)) }
205
263
  def current_requirement_string
206
264
  reqs = requirements
207
265
  return if reqs.none?
@@ -215,6 +273,7 @@ module Dependabot
215
273
  requirement&.fetch(:requirement)
216
274
  end
217
275
 
276
+ sig { returns(String) }
218
277
  def unlocked_requirement_string
219
278
  lower_bound_req = updated_version_req_lower_bound
220
279
 
@@ -231,6 +290,7 @@ module Dependabot
231
290
  lower_bound_req + ",<=#{latest_version}"
232
291
  end
233
292
 
293
+ sig { returns(String) }
234
294
  def updated_version_req_lower_bound
235
295
  return ">=#{dependency.version}" if dependency.version
236
296
 
@@ -245,111 +305,151 @@ module Dependabot
245
305
  ">=#{version_for_requirement || 0}"
246
306
  end
247
307
 
308
+ sig { returns(T.nilable(Gem::Version)) }
248
309
  def fetch_latest_version
249
310
  latest_version_finder.latest_version
250
311
  end
251
312
 
313
+ sig { returns(LatestVersionFinder) }
252
314
  def latest_version_finder
253
- @latest_version_finder ||= LatestVersionFinder.new(
254
- dependency: dependency,
255
- dependency_files: dependency_files,
256
- credentials: credentials,
257
- ignored_versions: ignored_versions,
258
- raise_on_ignored: @raise_on_ignored,
259
- cooldown_options: @update_cooldown,
260
- security_advisories: security_advisories
315
+ @latest_version_finder ||= T.let(
316
+ LatestVersionFinder.new(
317
+ dependency: dependency,
318
+ dependency_files: dependency_files,
319
+ credentials: credentials,
320
+ ignored_versions: ignored_versions,
321
+ raise_on_ignored: @raise_on_ignored,
322
+ cooldown_options: @update_cooldown,
323
+ security_advisories: security_advisories
324
+ ),
325
+ T.nilable(LatestVersionFinder)
261
326
  )
262
327
  end
263
328
 
329
+ sig { returns(T::Boolean) }
264
330
  def poetry_based?
265
331
  updating_pyproject? && !poetry_details.nil?
266
332
  end
267
333
 
334
+ sig { returns(T::Boolean) }
268
335
  def library?
269
336
  return false unless updating_pyproject?
337
+ return false unless library_details
270
338
 
271
- return false if library_details["name"].nil?
339
+ return false if T.must(library_details)["name"].nil?
272
340
 
273
341
  # Hit PyPi and check whether there are details for a library with a
274
342
  # matching name and description
275
343
  index_response = Dependabot::RegistryClient.get(
276
- url: "https://pypi.org/pypi/#{normalised_name(library_details['name'])}/json/"
344
+ url: "https://pypi.org/pypi/#{normalised_name(T.must(library_details)['name'])}/json/"
277
345
  )
278
346
 
279
347
  return false unless index_response.status == 200
280
348
 
281
349
  pypi_info = JSON.parse(index_response.body)["info"] || {}
282
- pypi_info["summary"] == library_details["description"]
350
+ pypi_info["summary"] == T.must(library_details)["description"]
283
351
  rescue Excon::Error::Timeout, Excon::Error::Socket
284
352
  false
285
353
  rescue URI::InvalidURIError
286
354
  false
287
355
  end
288
356
 
357
+ sig { returns(T::Boolean) }
289
358
  def updating_pipfile?
290
359
  requirement_files.any?("Pipfile")
291
360
  end
292
361
 
362
+ sig { returns(T::Boolean) }
293
363
  def updating_pyproject?
294
364
  requirement_files.any?("pyproject.toml")
295
365
  end
296
366
 
367
+ sig { returns(T::Boolean) }
297
368
  def updating_in_file?
298
369
  requirement_files.any? { |f| f.end_with?(".in") }
299
370
  end
300
371
 
372
+ sig { returns(T::Boolean) }
301
373
  def updating_requirements_file?
302
374
  requirement_files.any? { |f| f =~ /\.txt$|\.in$/ }
303
375
  end
304
376
 
377
+ sig { returns(T::Array[String]) }
305
378
  def requirement_files
306
379
  requirements.map { |r| r.fetch(:file) }
307
380
  end
308
381
 
382
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
309
383
  def requirements
310
384
  dependency.requirements
311
385
  end
312
386
 
387
+ sig { params(name: String).returns(String) }
313
388
  def normalised_name(name)
314
389
  NameNormaliser.normalise(name)
315
390
  end
316
391
 
392
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
317
393
  def pipfile
318
394
  dependency_files.find { |f| f.name == "Pipfile" }
319
395
  end
320
396
 
397
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
321
398
  def pipfile_lock
322
399
  dependency_files.find { |f| f.name == "Pipfile.lock" }
323
400
  end
324
401
 
402
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
325
403
  def pyproject
326
404
  dependency_files.find { |f| f.name == "pyproject.toml" }
327
405
  end
328
406
 
407
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
329
408
  def poetry_lock
330
409
  dependency_files.find { |f| f.name == "poetry.lock" }
331
410
  end
332
411
 
412
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
333
413
  def library_details
334
- @library_details ||= poetry_details || standard_details || build_system_details
414
+ @library_details ||= T.let(
415
+ poetry_details || standard_details || build_system_details,
416
+ T.nilable(T::Hash[String, T.untyped])
417
+ )
335
418
  end
336
419
 
420
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
337
421
  def poetry_details
338
- @poetry_details ||= toml_content.dig("tool", "poetry")
422
+ @poetry_details ||= T.let(
423
+ toml_content.dig("tool", "poetry"),
424
+ T.nilable(T::Hash[String, T.untyped])
425
+ )
339
426
  end
340
427
 
428
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
341
429
  def standard_details
342
- @standard_details ||= toml_content["project"]
430
+ @standard_details ||= T.let(
431
+ toml_content["project"],
432
+ T.nilable(T::Hash[String, T.untyped])
433
+ )
343
434
  end
344
435
 
436
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
345
437
  def build_system_details
346
- @build_system_details ||= toml_content["build-system"]
438
+ @build_system_details ||= T.let(
439
+ toml_content["build-system"],
440
+ T.nilable(T::Hash[String, T.untyped])
441
+ )
347
442
  end
348
443
 
444
+ sig { returns(T::Hash[String, T.untyped]) }
349
445
  def toml_content
350
- @toml_content ||= TomlRB.parse(pyproject.content)
446
+ @toml_content ||= T.let(
447
+ TomlRB.parse(T.must(pyproject).content),
448
+ T.nilable(T::Hash[String, T.untyped])
449
+ )
351
450
  end
352
451
 
452
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
353
453
  def pip_compile_files
354
454
  dependency_files.select { |f| f.name.end_with?(".in") }
355
455
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-python
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.332.0
4
+ version: 0.334.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.332.0
18
+ version: 0.334.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.332.0
25
+ version: 0.334.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -211,14 +211,14 @@ dependencies:
211
211
  requirements:
212
212
  - - "~>"
213
213
  - !ruby/object:Gem::Version
214
- version: '3.18'
214
+ version: '3.25'
215
215
  type: :development
216
216
  prerelease: false
217
217
  version_requirements: !ruby/object:Gem::Requirement
218
218
  requirements:
219
219
  - - "~>"
220
220
  - !ruby/object:Gem::Version
221
- version: '3.18'
221
+ version: '3.25'
222
222
  - !ruby/object:Gem::Dependency
223
223
  name: webrick
224
224
  requirement: !ruby/object:Gem::Requirement
@@ -290,7 +290,7 @@ licenses:
290
290
  - MIT
291
291
  metadata:
292
292
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
293
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.332.0
293
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.334.0
294
294
  rdoc_options: []
295
295
  require_paths:
296
296
  - lib