dependabot-python 0.214.0 → 0.216.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/logger"
4
+ require "dependabot/python/version"
5
+
6
+ module Dependabot
7
+ module Python
8
+ class LanguageVersionManager
9
+ def initialize(python_requirement_parser:)
10
+ @python_requirement_parser = python_requirement_parser
11
+ end
12
+
13
+ def install_required_python
14
+ # The leading space is important in the version check
15
+ return if SharedHelpers.run_shell_command("pyenv versions").include?(" #{python_major_minor}.")
16
+
17
+ if File.exist?("/usr/local/.pyenv/#{python_major_minor}.tar.gz")
18
+ SharedHelpers.run_shell_command(
19
+ "tar xzf /usr/local/.pyenv/#{python_major_minor}.tar.gz -C /usr/local/.pyenv/"
20
+ )
21
+ return if SharedHelpers.run_shell_command("pyenv versions").
22
+ include?(" #{python_major_minor}.")
23
+ end
24
+
25
+ Dependabot.logger.info("Installing required Python #{python_version}.")
26
+ start = Time.now
27
+ SharedHelpers.run_shell_command("pyenv install -s #{python_version}")
28
+ SharedHelpers.run_shell_command("pyenv exec pip install --upgrade pip")
29
+ SharedHelpers.run_shell_command("pyenv exec pip install -r" \
30
+ "#{NativeHelpers.python_requirements_path}")
31
+ time_taken = Time.now - start
32
+ Dependabot.logger.info("Installing Python #{python_version} took #{time_taken}s.")
33
+ end
34
+
35
+ def python_major_minor
36
+ @python ||= Python::Version.new(python_version)
37
+ "#{@python.segments[0]}.#{@python.segments[1]}"
38
+ end
39
+
40
+ def python_version
41
+ @python_version ||= python_version_from_supported_versions
42
+ end
43
+
44
+ def python_requirement_string
45
+ if user_specified_python_version
46
+ if user_specified_python_version.start_with?(/\d/)
47
+ parts = user_specified_python_version.split(".")
48
+ parts.fill("*", (parts.length)..2).join(".")
49
+ else
50
+ user_specified_python_version
51
+ end
52
+ elsif python_version_matching_imputed_requirements
53
+ python_version_matching_imputed_requirements
54
+ else
55
+ PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
56
+ end
57
+ end
58
+
59
+ def python_version_from_supported_versions
60
+ requirement_string = python_requirement_string
61
+
62
+ # Ideally, the requirement is satisfied by a Python version we support
63
+ requirement =
64
+ Python::Requirement.requirements_array(requirement_string).first
65
+ version =
66
+ PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
67
+ find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
68
+ return version if version
69
+
70
+ # If not, and we're dealing with a simple version string
71
+ # and changing the patch version would fix things, we do that
72
+ # as the patch version is unlikely to affect resolution
73
+ if requirement_string.start_with?(/\d/)
74
+ requirement =
75
+ Python::Requirement.new(requirement_string.gsub(/\.\d+$/, ".*"))
76
+ version =
77
+ PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
78
+ find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
79
+ return version if version
80
+ end
81
+
82
+ # Otherwise we have to raise, giving details of the Python versions
83
+ # that Dependabot supports
84
+ msg = "Dependabot detected the following Python requirement " \
85
+ "for your project: '#{requirement_string}'.\n\nCurrently, the " \
86
+ "following Python versions are supported in Dependabot: " \
87
+ "#{PythonVersions::SUPPORTED_VERSIONS.join(', ')}."
88
+ raise DependencyFileNotResolvable, msg
89
+ end
90
+
91
+ def user_specified_python_version
92
+ @python_requirement_parser.user_specified_requirements.first
93
+ end
94
+
95
+ def python_version_matching_imputed_requirements
96
+ compiled_file_python_requirement_markers =
97
+ @python_requirement_parser.imputed_requirements.map do |r|
98
+ Dependabot::Python::Requirement.new(r)
99
+ end
100
+ python_version_matching(compiled_file_python_requirement_markers)
101
+ end
102
+
103
+ def python_version_matching(requirements)
104
+ PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.find do |version_string|
105
+ version = Python::Version.new(version_string)
106
+ requirements.all? do |req|
107
+ next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
108
+
109
+ req.satisfied_by?(version)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -4,7 +4,7 @@ module Dependabot
4
4
  module Python
5
5
  module PythonVersions
6
6
  PRE_INSTALLED_PYTHON_VERSIONS = %w(
7
- 3.11.0
7
+ 3.11.3
8
8
  ).freeze
9
9
 
10
10
  # Due to an OpenSSL issue we can only install the following versions in
@@ -13,11 +13,11 @@ module Dependabot
13
13
  #
14
14
  # WARNING: 3.9.3 is purposefully omitted as it was recalled: https://www.python.org/downloads/release/python-393/
15
15
  SUPPORTED_VERSIONS = %w(
16
- 3.11.0
17
- 3.10.8 3.10.7 3.10.6 3.10.5 3.10.4 3.10.3 3.10.2 3.10.1 3.10.0
18
- 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 3.9.10 3.9.9 3.9.8 3.9.7 3.9.6 3.9.5 3.9.4 3.9.2 3.9.1 3.9.0
19
- 3.8.15 3.8.14 3.8.13 3.8.12 3.8.11 3.8.10 3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 3.8.2 3.8.1 3.8.0
20
- 3.7.15 3.7.14 3.7.13 3.7.12 3.7.11 3.7.10 3.7.9 3.7.8 3.7.7 3.7.6 3.7.5 3.7.4 3.7.3 3.7.2 3.7.1 3.7.0
16
+ 3.11.3 3.11.2 3.11.1 3.11.0
17
+ 3.10.11 3.10.10 3.10.9 3.10.8 3.10.7 3.10.6 3.10.5 3.10.4 3.10.3 3.10.2 3.10.1 3.10.0
18
+ 3.9.16 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 3.9.10 3.9.9 3.9.8 3.9.7 3.9.6 3.9.5 3.9.4 3.9.2 3.9.1 3.9.0
19
+ 3.8.16 3.8.15 3.8.14 3.8.13 3.8.12 3.8.11 3.8.10 3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 3.8.2 3.8.1 3.8.0
20
+ 3.7.16 3.7.15 3.7.14 3.7.13 3.7.12 3.7.11 3.7.10 3.7.9 3.7.8 3.7.7 3.7.6 3.7.5 3.7.4 3.7.3 3.7.2 3.7.1 3.7.0
21
21
  3.6.15 3.6.14 3.6.13 3.6.12 3.6.11 3.6.10 3.6.9 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.6.0
22
22
  3.5.10 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3
23
23
  ).freeze
@@ -9,7 +9,7 @@ module Dependabot
9
9
  VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
10
10
 
11
11
  REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
12
- HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|$)/
12
+ HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
13
13
  REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
14
14
  HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
15
15
  MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
@@ -11,7 +11,7 @@ require "dependabot/python/file_updater/requirement_replacer"
11
11
  require "dependabot/python/file_updater/setup_file_sanitizer"
12
12
  require "dependabot/python/version"
13
13
  require "dependabot/shared_helpers"
14
- require "dependabot/python/helpers"
14
+ require "dependabot/python/language_version_manager"
15
15
  require "dependabot/python/native_helpers"
16
16
  require "dependabot/python/python_versions"
17
17
  require "dependabot/python/name_normaliser"
@@ -71,13 +71,17 @@ module Dependabot
71
71
  SharedHelpers.in_a_temporary_directory do
72
72
  SharedHelpers.with_git_configured(credentials: credentials) do
73
73
  write_temporary_dependency_files(updated_req: requirement)
74
- Helpers.install_required_python(python_version)
74
+ language_version_manager.install_required_python
75
75
 
76
76
  filenames_to_compile.each do |filename|
77
77
  # Shell out to pip-compile.
78
78
  # This is slow, as pip-compile needs to do installs.
79
+ options = pip_compile_options(filename)
80
+ options_fingerprint = pip_compile_options_fingerprint(options)
81
+
79
82
  run_pip_compile_command(
80
- "pyenv exec pip-compile -v #{pip_compile_options(filename)} -P #{dependency.name} #{filename}"
83
+ "pyenv exec pip-compile -v #{options} -P #{dependency.name} #{filename}",
84
+ fingerprint: "pyenv exec pip-compile -v #{options_fingerprint} -P <dependency_name> <filename>"
81
85
  )
82
86
 
83
87
  next if dependency.top_level?
@@ -91,7 +95,8 @@ module Dependabot
91
95
  # update_not_possible.
92
96
  write_original_manifest_files
93
97
  run_pip_compile_command(
94
- "pyenv exec pip-compile #{pip_compile_options(filename)} #{filename}"
98
+ "pyenv exec pip-compile #{options} #{filename}",
99
+ fingerprint: "pyenv exec pip-compile #{options_fingerprint} <filename>"
95
100
  )
96
101
  end
97
102
 
@@ -168,6 +173,10 @@ module Dependabot
168
173
  raise GitDependenciesNotReachable, url
169
174
  end
170
175
 
176
+ raise Dependabot::OutOfDisk if error.message.end_with?("[Errno 28] No space left on device")
177
+
178
+ raise Dependabot::OutOfMemory if error.message.end_with?("MemoryError")
179
+
171
180
  raise
172
181
  end
173
182
  # rubocop:enable Metrics/AbcSize
@@ -183,8 +192,12 @@ module Dependabot
183
192
  write_temporary_dependency_files(update_requirement: false)
184
193
 
185
194
  filenames_to_compile.each do |filename|
195
+ options = pip_compile_options(filename)
196
+ options_fingerprint = pip_compile_options_fingerprint(options)
197
+
186
198
  run_pip_compile_command(
187
- "pyenv exec pip-compile #{pip_compile_options(filename)} #{filename}"
199
+ "pyenv exec pip-compile #{options} #{filename}",
200
+ fingerprint: "pyenv exec pip-compile #{options_fingerprint} <filename>"
188
201
  )
189
202
  end
190
203
 
@@ -204,7 +217,7 @@ module Dependabot
204
217
  end
205
218
  end
206
219
 
207
- def run_command(command, env: python_env)
220
+ def run_command(command, env: python_env, fingerprint:)
208
221
  start = Time.now
209
222
  command = SharedHelpers.escape_command(command)
210
223
  stdout, process = Open3.capture2e(env, command)
@@ -216,6 +229,7 @@ module Dependabot
216
229
  message: stdout,
217
230
  error_context: {
218
231
  command: command,
232
+ fingerprint: fingerprint,
219
233
  time_taken: time_taken,
220
234
  process_exit_value: process.to_s
221
235
  }
@@ -223,7 +237,17 @@ module Dependabot
223
237
  end
224
238
 
225
239
  def new_resolver_supported?
226
- python_version >= Python::Version.new("3.7")
240
+ language_version_manager.python_version >= Python::Version.new("3.7")
241
+ end
242
+
243
+ def pip_compile_options_fingerprint(options)
244
+ options.sub(
245
+ /--output-file=\S+/, "--output-file=<output_file>"
246
+ ).sub(
247
+ /--index-url=\S+/, "--index-url=<index_url>"
248
+ ).sub(
249
+ /--extra-index-url=\S+/, "--extra-index-url=<extra_index_url>"
250
+ )
227
251
  end
228
252
 
229
253
  def pip_compile_options(filename)
@@ -253,9 +277,13 @@ module Dependabot
253
277
  end
254
278
  end
255
279
 
256
- def run_pip_compile_command(command)
257
- run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
258
- run_command(command)
280
+ def run_pip_compile_command(command, fingerprint:)
281
+ run_command(
282
+ "pyenv local #{language_version_manager.python_major_minor}",
283
+ fingerprint: "pyenv local <python_major_minor>"
284
+ )
285
+
286
+ run_command(command, fingerprint: fingerprint)
259
287
  end
260
288
 
261
289
  def python_env
@@ -298,7 +326,7 @@ module Dependabot
298
326
  end
299
327
 
300
328
  # Overwrite the .python-version with updated content
301
- File.write(".python-version", Helpers.python_major_minor(python_version))
329
+ File.write(".python-version", language_version_manager.python_major_minor)
302
330
 
303
331
  setup_files.each do |file|
304
332
  path = file.name
@@ -410,9 +438,9 @@ module Dependabot
410
438
  while (remaining_filenames = filenames - ordered_filenames).any?
411
439
  ordered_filenames +=
412
440
  remaining_filenames.
413
- select do |fn|
441
+ reject do |fn|
414
442
  unupdated_reqs = requirement_map[fn] - ordered_filenames
415
- (unupdated_reqs & filenames).empty?
443
+ unupdated_reqs.intersect?(filenames)
416
444
  end
417
445
  end
418
446
 
@@ -455,41 +483,6 @@ module Dependabot
455
483
  ).parse.find { |d| d.name == dependency.name }&.version
456
484
  end
457
485
 
458
- def python_version
459
- @python_version ||=
460
- user_specified_python_version ||
461
- python_version_matching_imputed_requirements ||
462
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
463
- end
464
-
465
- def user_specified_python_version
466
- return unless python_requirement_parser.user_specified_requirements.any?
467
-
468
- user_specified_requirements =
469
- python_requirement_parser.user_specified_requirements.
470
- map { |r| Python::Requirement.requirements_array(r) }
471
- python_version_matching(user_specified_requirements)
472
- end
473
-
474
- def python_version_matching_imputed_requirements
475
- compiled_file_python_requirement_markers =
476
- python_requirement_parser.imputed_requirements.map do |r|
477
- Dependabot::Python::Requirement.new(r)
478
- end
479
- python_version_matching(compiled_file_python_requirement_markers)
480
- end
481
-
482
- def python_version_matching(requirements)
483
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.find do |version_string|
484
- version = Python::Version.new(version_string)
485
- requirements.all? do |req|
486
- next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
487
-
488
- req.satisfied_by?(version)
489
- end
490
- end
491
- end
492
-
493
486
  def python_requirement_parser
494
487
  @python_requirement_parser ||=
495
488
  FileParser::PythonRequirementParser.new(
@@ -497,8 +490,11 @@ module Dependabot
497
490
  )
498
491
  end
499
492
 
500
- def pre_installed_python?(version)
501
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
493
+ def language_version_manager
494
+ @language_version_manager ||=
495
+ LanguageVersionManager.new(
496
+ python_requirement_parser: python_requirement_parser
497
+ )
502
498
  end
503
499
 
504
500
  def setup_files
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/python/language_version_manager"
3
4
  require "dependabot/python/update_checker"
4
5
  require "dependabot/python/update_checker/latest_version_finder"
5
6
  require "dependabot/python/file_parser/python_requirement_parser"
@@ -20,17 +21,17 @@ module Dependabot
20
21
  end
21
22
 
22
23
  def latest_resolvable_version
23
- latest_version_finder.latest_version(python_version: python_version)
24
+ latest_version_finder.latest_version(python_version: language_version_manager.python_version)
24
25
  end
25
26
 
26
27
  def latest_resolvable_version_with_no_unlock
27
28
  latest_version_finder.
28
- latest_version_with_no_unlock(python_version: python_version)
29
+ latest_version_with_no_unlock(python_version: language_version_manager.python_version)
29
30
  end
30
31
 
31
32
  def lowest_resolvable_security_fix_version
32
33
  latest_version_finder.
33
- lowest_security_fix_version(python_version: python_version)
34
+ lowest_security_fix_version(python_version: language_version_manager.python_version)
34
35
  end
35
36
 
36
37
  private
@@ -49,45 +50,18 @@ module Dependabot
49
50
  )
50
51
  end
51
52
 
52
- def python_version
53
- @python_version ||=
54
- user_specified_python_version ||
55
- python_version_matching_imputed_requirements ||
56
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
57
- end
58
-
59
- def user_specified_python_version
60
- return unless python_requirement_parser.user_specified_requirements.any?
61
-
62
- user_specified_requirements =
63
- python_requirement_parser.user_specified_requirements.
64
- map { |r| Python::Requirement.requirements_array(r) }
65
- python_version_matching(user_specified_requirements)
66
- end
67
-
68
- def python_version_matching_imputed_requirements
69
- compiled_file_python_requirement_markers =
70
- python_requirement_parser.imputed_requirements.map do |r|
71
- Dependabot::Python::Requirement.new(r)
72
- end
73
- python_version_matching(compiled_file_python_requirement_markers)
74
- end
75
-
76
- def python_version_matching(requirements)
77
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.find do |version_string|
78
- version = Python::Version.new(version_string)
79
- requirements.all? do |req|
80
- next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
81
-
82
- req.satisfied_by?(version)
83
- end
84
- end
85
- end
86
-
87
53
  def python_requirement_parser
88
54
  @python_requirement_parser ||=
89
- FileParser::PythonRequirementParser.
90
- new(dependency_files: dependency_files)
55
+ FileParser::PythonRequirementParser.new(
56
+ dependency_files: dependency_files
57
+ )
58
+ end
59
+
60
+ def language_version_manager
61
+ @language_version_manager ||=
62
+ LanguageVersionManager.new(
63
+ python_requirement_parser: python_requirement_parser
64
+ )
91
65
  end
92
66
  end
93
67
  end
@@ -175,7 +175,7 @@ module Dependabot
175
175
  end
176
176
 
177
177
  if error.message.include?("UnsupportedPythonVersion") &&
178
- user_specified_python_requirement
178
+ language_version_manager.user_specified_python_version
179
179
  check_original_requirements_resolvable
180
180
 
181
181
  # The latest version of the dependency we're updating to needs a
@@ -231,7 +231,7 @@ module Dependabot
231
231
  end
232
232
 
233
233
  if error.message.include?("UnsupportedPythonVersion") &&
234
- user_specified_python_requirement
234
+ language_version_manager.user_specified_python_version
235
235
  msg = clean_error_message(error.message).
236
236
  lines.take_while { |l| !l.start_with?("File") }.join.strip
237
237
  raise if msg.empty?
@@ -239,8 +239,10 @@ module Dependabot
239
239
  raise DependencyFileNotResolvable, msg
240
240
  end
241
241
 
242
- # NOTE: Pipenv masks the actualy error, see this issue for updates:
242
+ # NOTE: Pipenv masks the actual error, see this issue for updates:
243
243
  # https://github.com/pypa/pipenv/issues/2791
244
+ # TODO: This may no longer be reproducible on latest pipenv, see linked issue,
245
+ # so investigate when we next bump to newer pipenv...
244
246
  handle_pipenv_installation_error(error.message) if error.message.match?(PIPENV_INSTALLATION_ERROR_REGEX)
245
247
 
246
248
  # Raise an unhandled error, as this could be a problem with
@@ -290,7 +292,7 @@ module Dependabot
290
292
  end
291
293
 
292
294
  # Overwrite the .python-version with updated content
293
- File.write(".python-version", Helpers.python_major_minor(python_version))
295
+ File.write(".python-version", language_version_manager.python_major_minor)
294
296
 
295
297
  setup_files.each do |file|
296
298
  path = file.name
@@ -320,7 +322,7 @@ module Dependabot
320
322
  nil
321
323
  end
322
324
 
323
- Helpers.install_required_python(python_version)
325
+ language_version_manager.install_required_python
324
326
  end
325
327
 
326
328
  def sanitized_setup_file_content(file)
@@ -354,7 +356,7 @@ module Dependabot
354
356
  def update_python_requirement(pipfile_content)
355
357
  Python::FileUpdater::PipfilePreparer.
356
358
  new(pipfile_content: pipfile_content).
357
- update_python_requirement(Helpers.python_major_minor(python_version))
359
+ update_python_requirement(language_version_manager.python_major_minor)
358
360
  end
359
361
 
360
362
  # rubocop:disable Metrics/PerceivedComplexity
@@ -398,57 +400,6 @@ module Dependabot
398
400
  replace_sources(credentials)
399
401
  end
400
402
 
401
- def python_version
402
- @python_version ||= python_version_from_supported_versions
403
- end
404
-
405
- def python_version_from_supported_versions
406
- requirement_string =
407
- if @using_python_two then "2.7.*"
408
- elsif user_specified_python_requirement
409
- parts = user_specified_python_requirement.split(".")
410
- parts.fill("*", (parts.length)..2).join(".")
411
- else
412
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
413
- end
414
-
415
- # Ideally, the requirement is satisfied by a Python version we support
416
- requirement =
417
- Python::Requirement.requirements_array(requirement_string).first
418
- version =
419
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
420
- find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
421
- return version if version
422
-
423
- # If not, and changing the patch version would fix things, we do that
424
- # as the patch version is unlikely to affect resolution
425
- requirement =
426
- Python::Requirement.new(requirement_string.gsub(/\.\d+$/, ".*"))
427
- version =
428
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
429
- find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
430
- return version if version
431
-
432
- # Otherwise we have to raise, giving details of the Python versions
433
- # that Dependabot supports
434
- msg = "Dependabot detected the following Python requirement " \
435
- "for your project: '#{requirement_string}'.\n\nCurrently, the " \
436
- "following Python versions are supported in Dependabot: " \
437
- "#{PythonVersions::SUPPORTED_VERSIONS.join(', ')}."
438
- raise DependencyFileNotResolvable, msg
439
- end
440
-
441
- def user_specified_python_requirement
442
- python_requirement_parser.user_specified_requirements.first
443
- end
444
-
445
- def python_requirement_parser
446
- @python_requirement_parser ||=
447
- FileParser::PythonRequirementParser.new(
448
- dependency_files: dependency_files
449
- )
450
- end
451
-
452
403
  def run_command(command, env: {})
453
404
  start = Time.now
454
405
  command = SharedHelpers.escape_command(command)
@@ -468,7 +419,7 @@ module Dependabot
468
419
  end
469
420
 
470
421
  def run_pipenv_command(command, env: pipenv_env_variables)
471
- run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
422
+ run_command("pyenv local #{language_version_manager.python_major_minor}")
472
423
  run_command(command, env: env)
473
424
  end
474
425
 
@@ -486,6 +437,20 @@ module Dependabot
486
437
  NameNormaliser.normalise(name)
487
438
  end
488
439
 
440
+ def python_requirement_parser
441
+ @python_requirement_parser ||=
442
+ FileParser::PythonRequirementParser.new(
443
+ dependency_files: dependency_files
444
+ )
445
+ end
446
+
447
+ def language_version_manager
448
+ @language_version_manager ||=
449
+ LanguageVersionManager.new(
450
+ python_requirement_parser: python_requirement_parser
451
+ )
452
+ end
453
+
489
454
  def pipfile
490
455
  dependency_files.find { |f| f.name == "Pipfile" }
491
456
  end