dependabot-uv 0.299.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/helpers/build +34 -0
  3. data/helpers/lib/__init__.py +0 -0
  4. data/helpers/lib/hasher.py +36 -0
  5. data/helpers/lib/parser.py +270 -0
  6. data/helpers/requirements.txt +13 -0
  7. data/helpers/run.py +22 -0
  8. data/lib/dependabot/uv/authed_url_builder.rb +31 -0
  9. data/lib/dependabot/uv/file_fetcher.rb +328 -0
  10. data/lib/dependabot/uv/file_parser/pipfile_files_parser.rb +192 -0
  11. data/lib/dependabot/uv/file_parser/pyproject_files_parser.rb +345 -0
  12. data/lib/dependabot/uv/file_parser/python_requirement_parser.rb +185 -0
  13. data/lib/dependabot/uv/file_parser/setup_file_parser.rb +193 -0
  14. data/lib/dependabot/uv/file_parser.rb +437 -0
  15. data/lib/dependabot/uv/file_updater/compile_file_updater.rb +576 -0
  16. data/lib/dependabot/uv/file_updater/pyproject_preparer.rb +124 -0
  17. data/lib/dependabot/uv/file_updater/requirement_file_updater.rb +73 -0
  18. data/lib/dependabot/uv/file_updater/requirement_replacer.rb +214 -0
  19. data/lib/dependabot/uv/file_updater.rb +105 -0
  20. data/lib/dependabot/uv/language.rb +76 -0
  21. data/lib/dependabot/uv/language_version_manager.rb +114 -0
  22. data/lib/dependabot/uv/metadata_finder.rb +186 -0
  23. data/lib/dependabot/uv/name_normaliser.rb +26 -0
  24. data/lib/dependabot/uv/native_helpers.rb +38 -0
  25. data/lib/dependabot/uv/package_manager.rb +54 -0
  26. data/lib/dependabot/uv/pip_compile_file_matcher.rb +38 -0
  27. data/lib/dependabot/uv/pipenv_runner.rb +108 -0
  28. data/lib/dependabot/uv/requirement.rb +163 -0
  29. data/lib/dependabot/uv/requirement_parser.rb +60 -0
  30. data/lib/dependabot/uv/update_checker/index_finder.rb +227 -0
  31. data/lib/dependabot/uv/update_checker/latest_version_finder.rb +297 -0
  32. data/lib/dependabot/uv/update_checker/pip_compile_version_resolver.rb +506 -0
  33. data/lib/dependabot/uv/update_checker/pip_version_resolver.rb +73 -0
  34. data/lib/dependabot/uv/update_checker/requirements_updater.rb +391 -0
  35. data/lib/dependabot/uv/update_checker.rb +317 -0
  36. data/lib/dependabot/uv/version.rb +321 -0
  37. data/lib/dependabot/uv.rb +35 -0
  38. metadata +306 -0
@@ -0,0 +1,506 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "open3"
5
+ require "dependabot/dependency"
6
+ require "dependabot/uv/requirement_parser"
7
+ require "dependabot/uv/file_fetcher"
8
+ require "dependabot/uv/file_parser"
9
+ require "dependabot/uv/file_parser/python_requirement_parser"
10
+ require "dependabot/uv/update_checker"
11
+ require "dependabot/uv/file_updater/requirement_replacer"
12
+ require "dependabot/uv/version"
13
+ require "dependabot/shared_helpers"
14
+ require "dependabot/uv/language_version_manager"
15
+ require "dependabot/uv/native_helpers"
16
+ require "dependabot/uv/name_normaliser"
17
+ require "dependabot/uv/authed_url_builder"
18
+
19
+ module Dependabot
20
+ module Uv
21
+ class UpdateChecker
22
+ # This class does version resolution for pip-compile. Its approach is:
23
+ # - Unlock the dependency we're checking in the requirements.in file
24
+ # - Run `pip-compile` and see what the result is
25
+ class PipCompileVersionResolver
26
+ GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone --filter=blob:none --quiet (?<url>[^\s]+).* /
27
+ GIT_REFERENCE_NOT_FOUND_REGEX = /Did not find branch or tag '(?<tag>[^\n"]+)'/m
28
+ NATIVE_COMPILATION_ERROR =
29
+ "pip._internal.exceptions.InstallationSubprocessError: Getting requirements to build wheel exited with 1"
30
+ # See https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata
31
+ PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]+/
32
+ RESOLUTION_IMPOSSIBLE_ERROR = "ResolutionImpossible"
33
+ ERROR_REGEX = /(?<=ERROR\:\W).*$/
34
+ UV_UNRESOLVABLE_REGEX = / × No solution found when resolving dependencies:[\s\S]*$/
35
+
36
+ attr_reader :dependency
37
+ attr_reader :dependency_files
38
+ attr_reader :credentials
39
+ attr_reader :repo_contents_path
40
+ attr_reader :error_handler
41
+
42
+ def initialize(dependency:, dependency_files:, credentials:, repo_contents_path:)
43
+ @dependency = dependency
44
+ @dependency_files = dependency_files
45
+ @credentials = credentials
46
+ @repo_contents_path = repo_contents_path
47
+ @build_isolation = true
48
+ @error_handler = PipCompileErrorHandler.new
49
+ end
50
+
51
+ def latest_resolvable_version(requirement: nil)
52
+ @latest_resolvable_version_string ||= {}
53
+ return @latest_resolvable_version_string[requirement] if @latest_resolvable_version_string.key?(requirement)
54
+
55
+ version_string =
56
+ fetch_latest_resolvable_version_string(requirement: requirement)
57
+
58
+ @latest_resolvable_version_string[requirement] ||=
59
+ version_string.nil? ? nil : Uv::Version.new(version_string)
60
+ end
61
+
62
+ def resolvable?(version:)
63
+ @resolvable ||= {}
64
+ return @resolvable[version] if @resolvable.key?(version)
65
+
66
+ @resolvable[version] = if latest_resolvable_version(requirement: "==#{version}")
67
+ true
68
+ else
69
+ false
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def fetch_latest_resolvable_version_string(requirement:)
76
+ SharedHelpers.in_a_temporary_directory do
77
+ SharedHelpers.with_git_configured(credentials: credentials) do
78
+ write_temporary_dependency_files(updated_req: requirement)
79
+ language_version_manager.install_required_python
80
+
81
+ filenames_to_compile.each do |filename|
82
+ return nil unless compile_file(filename)
83
+ end
84
+
85
+ # Remove any .python-version file before parsing the reqs
86
+ FileUtils.remove_entry(".python-version", true)
87
+
88
+ parse_updated_files
89
+ end
90
+ end
91
+ end
92
+
93
+ def compile_file(filename)
94
+ # Shell out to pip-compile.
95
+ # This is slow, as pip-compile needs to do installs.
96
+ options = pip_compile_options(filename)
97
+ options_fingerprint = pip_compile_options_fingerprint(options)
98
+
99
+ run_pip_compile_command(
100
+ "pyenv exec uv pip compile -v #{options} -P #{dependency.name} #{filename}",
101
+ fingerprint: "pyenv exec uv pip compile -v #{options_fingerprint} -P <dependency_name> <filename>"
102
+ )
103
+
104
+ return true if dependency.top_level?
105
+
106
+ # Run pip-compile a second time for transient dependencies
107
+ # to make sure we do not update dependencies that are
108
+ # superfluous. pip-compile does not detect these when
109
+ # updating a specific dependency with the -P option.
110
+ # Running pip-compile a second time will automatically remove
111
+ # superfluous dependencies. Dependabot then marks those with
112
+ # update_not_possible.
113
+ write_original_manifest_files
114
+ run_pip_compile_command(
115
+ "pyenv exec uv pip compile #{options} #{filename}",
116
+ fingerprint: "pyenv exec uv pip compile #{options_fingerprint} <filename>"
117
+ )
118
+
119
+ true
120
+ rescue SharedHelpers::HelperSubprocessFailed => e
121
+ retry_count ||= 0
122
+ retry_count += 1
123
+ if compilation_error?(e) && retry_count <= 1
124
+ @build_isolation = false
125
+ retry
126
+ end
127
+
128
+ handle_pip_compile_errors(e.message)
129
+ end
130
+
131
+ def compilation_error?(error)
132
+ error.message.include?(NATIVE_COMPILATION_ERROR)
133
+ end
134
+
135
+ # rubocop:disable Metrics/AbcSize
136
+ # rubocop:disable Metrics/PerceivedComplexity
137
+ def handle_pip_compile_errors(message)
138
+ if message.include?("No solution found when resolving dependencies")
139
+ raise DependencyFileNotResolvable, message.scan(UV_UNRESOLVABLE_REGEX).last
140
+ end
141
+
142
+ check_original_requirements_resolvable if message.include?(RESOLUTION_IMPOSSIBLE_ERROR)
143
+
144
+ # If there's an unsupported constraint, check if it existed
145
+ # previously (and raise if it did)
146
+ check_original_requirements_resolvable if message.include?("UnsupportedConstraint")
147
+
148
+ if message.include?(RESOLUTION_IMPOSSIBLE_ERROR) &&
149
+ !message.match?(/#{Regexp.quote(dependency.name)}/i)
150
+ # Sometimes pip-tools gets confused and can't work around
151
+ # sub-dependency incompatibilities. Ignore those cases.
152
+ return nil
153
+ end
154
+
155
+ if message.match?(GIT_REFERENCE_NOT_FOUND_REGEX)
156
+ tag = message.match(GIT_REFERENCE_NOT_FOUND_REGEX).named_captures.fetch("tag")
157
+ constraints_section = message.split("Finding the best candidates:").first
158
+ egg_regex = /#{Regexp.escape(tag)}#egg=(#{PYTHON_PACKAGE_NAME_REGEX})/
159
+ name_match = constraints_section.scan(egg_regex)
160
+
161
+ # We can determine the name of the package from another part of the logger output if it has a unique tag
162
+ raise GitDependencyReferenceNotFound, name_match.first.first if name_match.length == 1
163
+
164
+ raise GitDependencyReferenceNotFound, "(unknown package at #{tag})"
165
+ end
166
+
167
+ if message.match?(GIT_DEPENDENCY_UNREACHABLE_REGEX)
168
+ url = message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX)
169
+ .named_captures.fetch("url")
170
+ raise GitDependenciesNotReachable, url
171
+ end
172
+
173
+ raise Dependabot::OutOfDisk if message.end_with?("[Errno 28] No space left on device")
174
+
175
+ raise Dependabot::OutOfMemory if message.end_with?("MemoryError")
176
+
177
+ error_handler.handle_pipcompile_error(message)
178
+
179
+ raise
180
+ end
181
+ # rubocop:enable Metrics/AbcSize
182
+ # rubocop:enable Metrics/PerceivedComplexity
183
+
184
+ # Needed because pip-compile's resolver isn't perfect.
185
+ # Note: We raise errors from this method, rather than returning a
186
+ # boolean, so that all deps for this repo will raise identical
187
+ # errors when failing to update
188
+ def check_original_requirements_resolvable
189
+ SharedHelpers.in_a_temporary_directory do
190
+ SharedHelpers.with_git_configured(credentials: credentials) do
191
+ write_temporary_dependency_files(update_requirement: false)
192
+
193
+ filenames_to_compile.each do |filename|
194
+ options = pip_compile_options(filename)
195
+ options_fingerprint = pip_compile_options_fingerprint(options)
196
+
197
+ run_pip_compile_command(
198
+ "pyenv exec uv pip compile #{options} #{filename}",
199
+ fingerprint: "pyenv exec uv pip compile #{options_fingerprint} <filename>"
200
+ )
201
+ end
202
+
203
+ true
204
+ rescue SharedHelpers::HelperSubprocessFailed => e
205
+ # Pick the error message that includes resolvability errors, this might be the cause from
206
+ # handle_pip_compile_errors (it's unclear if we should always pick the cause here)
207
+ error_message = [e.message, e.cause&.message].compact.find do |msg|
208
+ msg.include?(RESOLUTION_IMPOSSIBLE_ERROR)
209
+ end
210
+
211
+ cleaned_message = clean_error_message(error_message || "")
212
+ raise if cleaned_message.empty?
213
+
214
+ raise DependencyFileNotResolvable, cleaned_message
215
+ end
216
+ end
217
+ end
218
+
219
+ def run_command(command, env: python_env, fingerprint:)
220
+ SharedHelpers.run_shell_command(command, env: env, fingerprint: fingerprint, stderr_to_stdout: true)
221
+ rescue SharedHelpers::HelperSubprocessFailed => e
222
+ handle_pip_compile_errors(e.message)
223
+ end
224
+
225
+ def pip_compile_options_fingerprint(options)
226
+ options.sub(
227
+ /--output-file=\S+/, "--output-file=<output_file>"
228
+ ).sub(
229
+ /--index-url=\S+/, "--index-url=<index_url>"
230
+ ).sub(
231
+ /--extra-index-url=\S+/, "--extra-index-url=<extra_index_url>"
232
+ )
233
+ end
234
+
235
+ def pip_compile_options(filename)
236
+ options = @build_isolation ? ["--build-isolation"] : ["--no-build-isolation"]
237
+ options += pip_compile_index_options
238
+ # TODO: Stop explicitly specifying `allow-unsafe` once it becomes the default:
239
+ # https://github.com/jazzband/pip-tools/issues/989#issuecomment-1661254701
240
+ options += ["--allow-unsafe"]
241
+
242
+ if (requirements_file = compiled_file_for_filename(filename))
243
+ options << "--output-file=#{requirements_file.name}"
244
+ options += uv_pip_compile_options_from_compiled_file(requirements_file)
245
+ end
246
+
247
+ options.join(" ")
248
+ end
249
+
250
+ def pip_compile_index_options
251
+ credentials
252
+ .select { |cred| cred["type"] == "python_index" }
253
+ .map do |cred|
254
+ authed_url = AuthedUrlBuilder.authed_url(credential: cred)
255
+
256
+ if cred.replaces_base?
257
+ "--index-url=#{authed_url}"
258
+ else
259
+ "--extra-index-url=#{authed_url}"
260
+ end
261
+ end
262
+ end
263
+
264
+ def run_pip_compile_command(command, fingerprint:)
265
+ run_command(
266
+ "pyenv local #{language_version_manager.python_major_minor}",
267
+ fingerprint: "pyenv local <python_major_minor>"
268
+ )
269
+
270
+ run_command(command, fingerprint: fingerprint)
271
+ end
272
+
273
+ def uv_pip_compile_options_from_compiled_file(requirements_file)
274
+ options = []
275
+
276
+ options << "--no-emit-index-url" unless requirements_file.content.include?("index-url http")
277
+
278
+ options << "--generate-hashes" if requirements_file.content.include?("--hash=sha")
279
+
280
+ options << "--no-annotate" unless requirements_file.content.include?("# via ")
281
+
282
+ options << "--pre" if requirements_file.content.include?("--pre")
283
+
284
+ options << "--no-strip-extras" if requirements_file.content.include?("--no-strip-extras")
285
+
286
+ if requirements_file.content.include?("--no-binary") || requirements_file.content.include?("--only-binary")
287
+ options << "--emit-build-options"
288
+ end
289
+
290
+ if (resolver = FileUpdater::CompileFileUpdater::RESOLVER_REGEX.match(requirements_file.content))
291
+ options << "--resolver=#{resolver}"
292
+ end
293
+
294
+ options << "--universal" if requirements_file.content.include?("--universal")
295
+
296
+ options
297
+ end
298
+
299
+ def python_env
300
+ env = {}
301
+
302
+ # Handle Apache Airflow 1.10.x installs
303
+ if dependency_files.any? { |f| f.content.include?("apache-airflow") }
304
+ if dependency_files.any? { |f| f.content.include?("unidecode") }
305
+ env["AIRFLOW_GPL_UNIDECODE"] = "yes"
306
+ else
307
+ env["SLUGIFY_USES_TEXT_UNIDECODE"] = "yes"
308
+ end
309
+ end
310
+
311
+ env
312
+ end
313
+
314
+ def write_temporary_dependency_files(updated_req: nil,
315
+ update_requirement: true)
316
+ dependency_files.each do |file|
317
+ path = file.name
318
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
319
+ updated_content =
320
+ if update_requirement then update_req_file(file, updated_req)
321
+ else
322
+ file.content
323
+ end
324
+ File.write(path, updated_content)
325
+ end
326
+
327
+ # Overwrite the .python-version with updated content
328
+ File.write(".python-version", language_version_manager.python_major_minor)
329
+ end
330
+
331
+ def write_original_manifest_files
332
+ pip_compile_files.each do |file|
333
+ FileUtils.mkdir_p(Pathname.new(file.name).dirname)
334
+ File.write(file.name, file.content)
335
+ end
336
+ end
337
+
338
+ def update_req_file(file, updated_req)
339
+ return file.content unless file.name.end_with?(".in")
340
+
341
+ req = dependency.requirements.find { |r| r[:file] == file.name }
342
+
343
+ return file.content + "\n#{dependency.name} #{updated_req}" unless req&.fetch(:requirement)
344
+
345
+ Uv::FileUpdater::RequirementReplacer.new(
346
+ content: file.content,
347
+ dependency_name: dependency.name,
348
+ old_requirement: req[:requirement],
349
+ new_requirement: updated_req
350
+ ).updated_content
351
+ end
352
+
353
+ def normalise(name)
354
+ NameNormaliser.normalise(name)
355
+ end
356
+
357
+ def clean_error_message(message)
358
+ message.scan(ERROR_REGEX).last
359
+ end
360
+
361
+ def filenames_to_compile
362
+ files_from_reqs =
363
+ dependency.requirements
364
+ .map { |r| r[:file] }
365
+ .select { |fn| fn.end_with?(".in") }
366
+
367
+ files_from_compiled_files =
368
+ pip_compile_files.map(&:name).select do |fn|
369
+ compiled_file = compiled_file_for_filename(fn)
370
+ compiled_file_includes_dependency?(compiled_file)
371
+ end
372
+
373
+ filenames = [*files_from_reqs, *files_from_compiled_files].uniq
374
+
375
+ order_filenames_for_compilation(filenames)
376
+ end
377
+
378
+ def compiled_file_for_filename(filename)
379
+ compiled_file =
380
+ compiled_files
381
+ .find { |f| f.content.match?(output_file_regex(filename)) }
382
+
383
+ compiled_file ||=
384
+ compiled_files
385
+ .find { |f| f.name == filename.gsub(/\.in$/, ".txt") }
386
+
387
+ compiled_file
388
+ end
389
+
390
+ def output_file_regex(filename)
391
+ "--output-file[=\s]+.*\s#{Regexp.escape(filename)}\s*$"
392
+ end
393
+
394
+ def compiled_file_includes_dependency?(compiled_file)
395
+ return false unless compiled_file
396
+
397
+ regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
398
+
399
+ matches = []
400
+ compiled_file.content.scan(regex) { matches << Regexp.last_match }
401
+ matches.any? { |m| normalise(m[:name]) == dependency.name }
402
+ end
403
+
404
+ # If the files we need to update require one another then we need to
405
+ # update them in the right order
406
+ def order_filenames_for_compilation(filenames)
407
+ ordered_filenames = T.let([], T::Array[String])
408
+
409
+ while (remaining_filenames = filenames - ordered_filenames).any?
410
+ ordered_filenames +=
411
+ remaining_filenames
412
+ .reject do |fn|
413
+ unupdated_reqs = requirement_map[fn] - ordered_filenames
414
+ unupdated_reqs.intersect?(filenames)
415
+ end
416
+ end
417
+
418
+ ordered_filenames
419
+ end
420
+
421
+ def requirement_map
422
+ child_req_regex = Uv::FileFetcher::CHILD_REQUIREMENT_REGEX
423
+ @requirement_map ||=
424
+ pip_compile_files.each_with_object({}) do |file, req_map|
425
+ paths = file.content.scan(child_req_regex).flatten
426
+ current_dir = File.dirname(file.name)
427
+
428
+ req_map[file.name] =
429
+ paths.map do |path|
430
+ path = File.join(current_dir, path) if current_dir != "."
431
+ path = Pathname.new(path).cleanpath.to_path
432
+ path = path.gsub(/\.txt$/, ".in")
433
+ next if path == file.name
434
+
435
+ path
436
+ end.uniq.compact
437
+ end
438
+ end
439
+
440
+ def parse_updated_files
441
+ updated_files =
442
+ dependency_files.map do |file|
443
+ next file if file.name == ".python-version"
444
+
445
+ updated_file = file.dup
446
+ updated_file.content = File.read(file.name)
447
+ updated_file
448
+ end
449
+
450
+ Uv::FileParser.new(
451
+ dependency_files: updated_files,
452
+ source: nil,
453
+ credentials: credentials
454
+ ).parse.find { |d| d.name == dependency.name }&.version
455
+ end
456
+
457
+ def python_requirement_parser
458
+ @python_requirement_parser ||=
459
+ FileParser::PythonRequirementParser.new(
460
+ dependency_files: dependency_files
461
+ )
462
+ end
463
+
464
+ def language_version_manager
465
+ @language_version_manager ||=
466
+ LanguageVersionManager.new(
467
+ python_requirement_parser: python_requirement_parser
468
+ )
469
+ end
470
+
471
+ def setup_files
472
+ dependency_files.select { |f| f.name.end_with?("setup.py") }
473
+ end
474
+
475
+ def pip_compile_files
476
+ dependency_files.select { |f| f.name.end_with?(".in") }
477
+ end
478
+
479
+ def compiled_files
480
+ dependency_files.select { |f| f.name.end_with?(".txt") }
481
+ end
482
+
483
+ def setup_cfg_files
484
+ dependency_files.select { |f| f.name.end_with?("setup.cfg") }
485
+ end
486
+ end
487
+ end
488
+
489
+ class PipCompileErrorHandler
490
+ SUBPROCESS_ERROR = /subprocess-exited-with-error/
491
+
492
+ INSTALLATION_ERROR = /InstallationError/
493
+
494
+ INSTALLATION_SUBPROCESS_ERROR = /InstallationSubprocessError/
495
+
496
+ HASH_MISMATCH = /HashMismatch/
497
+
498
+ def handle_pipcompile_error(error)
499
+ return unless error.match?(SUBPROCESS_ERROR) || error.match?(INSTALLATION_ERROR) ||
500
+ error.match?(INSTALLATION_SUBPROCESS_ERROR) || error.match?(HASH_MISMATCH)
501
+
502
+ raise DependencyFileNotResolvable, "Error resolving dependency"
503
+ end
504
+ end
505
+ end
506
+ end
@@ -0,0 +1,73 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/uv/language_version_manager"
5
+ require "dependabot/uv/update_checker"
6
+ require "dependabot/uv/update_checker/latest_version_finder"
7
+ require "dependabot/uv/file_parser/python_requirement_parser"
8
+
9
+ module Dependabot
10
+ module Uv
11
+ class UpdateChecker
12
+ class PipVersionResolver
13
+ def initialize(dependency:, dependency_files:, credentials:,
14
+ ignored_versions:, raise_on_ignored: false,
15
+ security_advisories:)
16
+ @dependency = dependency
17
+ @dependency_files = dependency_files
18
+ @credentials = credentials
19
+ @ignored_versions = ignored_versions
20
+ @raise_on_ignored = raise_on_ignored
21
+ @security_advisories = security_advisories
22
+ end
23
+
24
+ def latest_resolvable_version
25
+ latest_version_finder.latest_version(python_version: language_version_manager.python_version)
26
+ end
27
+
28
+ def latest_resolvable_version_with_no_unlock
29
+ latest_version_finder
30
+ .latest_version_with_no_unlock(python_version: language_version_manager.python_version)
31
+ end
32
+
33
+ def lowest_resolvable_security_fix_version
34
+ latest_version_finder
35
+ .lowest_security_fix_version(python_version: language_version_manager.python_version)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :dependency
41
+ attr_reader :dependency_files
42
+ attr_reader :credentials
43
+ attr_reader :ignored_versions
44
+ attr_reader :security_advisories
45
+
46
+ def latest_version_finder
47
+ @latest_version_finder ||= LatestVersionFinder.new(
48
+ dependency: dependency,
49
+ dependency_files: dependency_files,
50
+ credentials: credentials,
51
+ ignored_versions: ignored_versions,
52
+ raise_on_ignored: @raise_on_ignored,
53
+ security_advisories: security_advisories
54
+ )
55
+ end
56
+
57
+ def python_requirement_parser
58
+ @python_requirement_parser ||=
59
+ FileParser::PythonRequirementParser.new(
60
+ dependency_files: dependency_files
61
+ )
62
+ end
63
+
64
+ def language_version_manager
65
+ @language_version_manager ||=
66
+ LanguageVersionManager.new(
67
+ python_requirement_parser: python_requirement_parser
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end