dependabot-python 0.214.0 → 0.216.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.
@@ -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