dependabot-python 0.212.0 → 0.214.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +5 -6
  3. data/helpers/lib/parser.py +59 -0
  4. data/helpers/requirements.txt +3 -3
  5. data/helpers/run.py +2 -0
  6. data/lib/dependabot/python/file_fetcher.rb +21 -12
  7. data/lib/dependabot/python/file_parser/{poetry_files_parser.rb → pyproject_files_parser.rb} +84 -2
  8. data/lib/dependabot/python/file_parser/setup_file_parser.rb +4 -4
  9. data/lib/dependabot/python/file_parser.rb +5 -29
  10. data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +7 -22
  11. data/lib/dependabot/python/file_updater/pipfile_file_updater.rb +11 -8
  12. data/lib/dependabot/python/file_updater/pipfile_preparer.rb +6 -4
  13. data/lib/dependabot/python/file_updater/poetry_file_updater.rb +15 -7
  14. data/lib/dependabot/python/file_updater/pyproject_preparer.rb +16 -1
  15. data/lib/dependabot/python/file_updater.rb +14 -1
  16. data/lib/dependabot/python/helpers.rb +37 -0
  17. data/lib/dependabot/python/metadata_finder.rb +2 -0
  18. data/lib/dependabot/python/python_versions.rb +11 -7
  19. data/lib/dependabot/python/requirement.rb +7 -4
  20. data/lib/dependabot/python/requirement_parser.rb +20 -23
  21. data/lib/dependabot/python/update_checker/index_finder.rb +1 -1
  22. data/lib/dependabot/python/update_checker/latest_version_finder.rb +2 -2
  23. data/lib/dependabot/python/update_checker/pip_compile_version_resolver.rb +19 -21
  24. data/lib/dependabot/python/update_checker/pipenv_version_resolver.rb +16 -18
  25. data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +14 -12
  26. data/lib/dependabot/python/update_checker/requirements_updater.rb +17 -4
  27. data/lib/dependabot/python/update_checker.rb +82 -25
  28. data/lib/dependabot/python/version.rb +2 -2
  29. metadata +15 -56
@@ -36,6 +36,17 @@ module Dependabot
36
36
  end
37
37
  end
38
38
 
39
+ def update_python_requirement(requirement)
40
+ pyproject_object = TomlRB.parse(@pyproject_content)
41
+ if (python_specification = pyproject_object.dig("tool", "poetry", "dependencies", "python"))
42
+ python_req = Python::Requirement.new(python_specification)
43
+ unless python_req.satisfied_by?(requirement)
44
+ pyproject_object["tool"]["poetry"]["dependencies"]["python"] = "~#{requirement}"
45
+ end
46
+ end
47
+ TomlRB.dump(pyproject_object)
48
+ end
49
+
39
50
  def sanitize
40
51
  # {{ name }} syntax not allowed
41
52
  pyproject_content.
@@ -52,7 +63,7 @@ module Dependabot
52
63
  poetry_object = pyproject_object["tool"]["poetry"]
53
64
  excluded_names = dependencies.map(&:name) + ["python"]
54
65
 
55
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |key|
66
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |key|
56
67
  next unless poetry_object[key]
57
68
 
58
69
  source_types = %w(directory file url)
@@ -72,6 +83,10 @@ module Dependabot
72
83
  }
73
84
  elsif poetry_object[key][dep_name].is_a?(Hash)
74
85
  poetry_object[key][dep_name]["version"] = locked_version
86
+ elsif poetry_object[key][dep_name].is_a?(Array)
87
+ # if it has multiple-constraints, locking to a single version is
88
+ # going to result in a bad lockfile, ignore
89
+ next
75
90
  else
76
91
  poetry_object[key][dep_name] = locked_version
77
92
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "toml-rb"
3
4
  require "dependabot/file_updaters"
4
5
  require "dependabot/file_updaters/base"
5
6
  require "dependabot/shared_helpers"
@@ -61,7 +62,13 @@ module Dependabot
61
62
  # Otherwise, this is a top-level dependency, and we can figure out
62
63
  # which resolver to use based on the filename of its requirements
63
64
  return :pipfile if changed_req_files.any?("Pipfile")
64
- return :poetry if changed_req_files.any?("pyproject.toml")
65
+
66
+ if changed_req_files.any?("pyproject.toml")
67
+ return :poetry if poetry_based?
68
+
69
+ return :requirements
70
+ end
71
+
65
72
  return :pip_compile if changed_req_files.any? { |f| f.end_with?(".in") }
66
73
 
67
74
  :requirements
@@ -119,6 +126,12 @@ module Dependabot
119
126
  raise "Missing required files!"
120
127
  end
121
128
 
129
+ def poetry_based?
130
+ return false unless pyproject
131
+
132
+ !TomlRB.parse(pyproject.content).dig("tool", "poetry").nil?
133
+ end
134
+
122
135
  def pipfile
123
136
  @pipfile ||= get_original_file("Pipfile")
124
137
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/logger"
4
+ require "dependabot/python/version"
5
+
6
+ module Dependabot
7
+ module Python
8
+ module Helpers
9
+ def self.install_required_python(python_version)
10
+ # The leading space is important in the version check
11
+ return if SharedHelpers.run_shell_command("pyenv versions").include?(" #{python_major_minor(python_version)}.")
12
+
13
+ if File.exist?("/usr/local/.pyenv/#{python_major_minor(python_version)}.tar.gz")
14
+ SharedHelpers.run_shell_command(
15
+ "tar xzf /usr/local/.pyenv/#{python_major_minor(python_version)}.tar.gz -C /usr/local/.pyenv/"
16
+ )
17
+ return if SharedHelpers.run_shell_command("pyenv versions").
18
+ include?(" #{python_major_minor(python_version)}.")
19
+ end
20
+
21
+ Dependabot.logger.info("Installing required Python #{python_version}.")
22
+ start = Time.now
23
+ SharedHelpers.run_shell_command("pyenv install -s #{python_version}")
24
+ SharedHelpers.run_shell_command("pyenv exec pip install --upgrade pip")
25
+ SharedHelpers.run_shell_command("pyenv exec pip install -r" \
26
+ "#{NativeHelpers.python_requirements_path}")
27
+ time_taken = Time.now - start
28
+ Dependabot.logger.info("Installing Python #{python_version} took #{time_taken}s.")
29
+ end
30
+
31
+ def self.python_major_minor(python_version)
32
+ python = Python::Version.new(python_version)
33
+ "#{python.segments[0]}.#{python.segments[1]}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -131,6 +131,8 @@ module Dependabot
131
131
  return @pypi_listing
132
132
  rescue JSON::ParserError
133
133
  next
134
+ rescue Excon::Error::Timeout
135
+ next
134
136
  end
135
137
 
136
138
  @pypi_listing = {} # No listing found
@@ -4,18 +4,22 @@ module Dependabot
4
4
  module Python
5
5
  module PythonVersions
6
6
  PRE_INSTALLED_PYTHON_VERSIONS = %w(
7
- 3.10.5
7
+ 3.11.0
8
8
  ).freeze
9
9
 
10
10
  # Due to an OpenSSL issue we can only install the following versions in
11
11
  # the Dependabot container.
12
+ # NOTE: When adding one version, always doublecheck for additional releases: https://www.python.org/downloads/
13
+ #
14
+ # WARNING: 3.9.3 is purposefully omitted as it was recalled: https://www.python.org/downloads/release/python-393/
12
15
  SUPPORTED_VERSIONS = %w(
13
- 3.10.5 3.10.4 3.10.3 3.10.2 3.10.1 3.10.0
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
15
- 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
16
- 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
17
- 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
18
- 3.6.2 3.6.1 3.6.0 3.5.10 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3
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
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
+ 3.5.10 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3
19
23
  ).freeze
20
24
 
21
25
  # This list gets iterated through to find a valid version, so we have
@@ -6,7 +6,7 @@ require "dependabot/python/version"
6
6
  module Dependabot
7
7
  module Python
8
8
  class Requirement < Gem::Requirement
9
- OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/.freeze
9
+ OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/
10
10
 
11
11
  # Add equality and arbitrary-equality matchers
12
12
  OPS = OPS.merge(
@@ -19,8 +19,8 @@ module Dependabot
19
19
  version_pattern = Python::Version::VERSION_PATTERN
20
20
 
21
21
  PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*"
22
- PATTERN = /\A#{PATTERN_RAW}\z/.freeze
23
- PARENS_PATTERN = /\A\(([^)]+)\)\z/.freeze
22
+ PATTERN = /\A#{PATTERN_RAW}\z/
23
+ PARENS_PATTERN = /\A\(([^)]+)\)\z/
24
24
 
25
25
  def self.parse(obj)
26
26
  return ["=", Python::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
@@ -60,6 +60,9 @@ module Dependabot
60
60
  requirements = requirements.flatten.flat_map do |req_string|
61
61
  next if req_string.nil?
62
62
 
63
+ # Standard python doesn't support whitespace in requirements, but Poetry does.
64
+ req_string = req_string.gsub(/(\d +)([<=>])/, '\1,\2')
65
+
63
66
  req_string.split(",").map(&:strip).map do |r|
64
67
  convert_python_constraint_to_ruby_constraint(r)
65
68
  end
@@ -87,7 +90,7 @@ module Dependabot
87
90
  return nil if req_string == "*"
88
91
 
89
92
  req_string = req_string.gsub("~=", "~>")
90
- req_string = req_string.gsub(/(?<=\d)[<=>].*/, "")
93
+ req_string = req_string.gsub(/(?<=\d)[<=>].*\Z/, "")
91
94
 
92
95
  if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
93
96
  elsif req_string.start_with?("^") then convert_caret_req(req_string)
@@ -3,29 +3,26 @@
3
3
  module Dependabot
4
4
  module Python
5
5
  class RequirementParser
6
- NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/.freeze
7
- EXTRA = /[a-zA-Z0-9\-_\.]+/.freeze
8
- COMPARISON = /===|==|>=|<=|<|>|~=|!=/.freeze
9
- VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/.
10
- freeze
11
- REQUIREMENT =
12
- /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/.freeze
13
- HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|$)/.freeze
14
- REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/.freeze
15
- HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/.freeze
16
- MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/.freeze
17
- PYTHON_STR_C =
18
- %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}.freeze
19
- PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/.freeze
6
+ NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
7
+ EXTRA = /[a-zA-Z0-9\-_\.]+/
8
+ COMPARISON = /===|==|>=|<=|<|>|~=|!=/
9
+ VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
10
+
11
+ REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
12
+ HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|$)/
13
+ REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
14
+ HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
15
+ MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
16
+ PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
17
+ PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
20
18
  ENV_VAR =
21
19
  /python_version|python_full_version|os_name|sys_platform|
22
20
  platform_release|platform_system|platform_version|platform_machine|
23
21
  platform_python_implementation|implementation_name|
24
- implementation_version/.freeze
25
- MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/.freeze
26
- MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/.freeze
27
- MARKER_EXPR =
28
- /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/.freeze
22
+ implementation_version/
23
+ MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
24
+ MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
25
+ MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
29
26
 
30
27
  INSTALL_REQ_WITH_REQUIREMENT =
31
28
  /\s*\\?\s*(?<name>#{NAME})
@@ -34,7 +31,7 @@ module Dependabot
34
31
  \s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
35
32
  \s*\\?\s*(?<hashes>#{HASHES})?
36
33
  \s*#*\s*(?<comment>.+)?
37
- /x.freeze
34
+ /x
38
35
 
39
36
  INSTALL_REQ_WITHOUT_REQUIREMENT =
40
37
  /^\s*\\?\s*(?<name>#{NAME})
@@ -42,7 +39,7 @@ module Dependabot
42
39
  \s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
43
40
  \s*\\?\s*(?<hashes>#{HASHES})?
44
41
  \s*#*\s*(?<comment>.+)?$
45
- /x.freeze
42
+ /x
46
43
 
47
44
  VALID_REQ_TXT_REQUIREMENT =
48
45
  /^\s*\\?\s*(?<name>#{NAME})
@@ -51,12 +48,12 @@ module Dependabot
51
48
  \s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
52
49
  \s*\\?\s*(?<hashes>#{HASHES})?
53
50
  \s*(\#+\s*(?<comment>.*))?$
54
- /x.freeze
51
+ /x
55
52
 
56
53
  NAME_WITH_EXTRAS =
57
54
  /\s*\\?\s*(?<name>#{NAME})
58
55
  (\s*\\?\s*\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
59
- /x.freeze
56
+ /x
60
57
  end
61
58
  end
62
59
  end
@@ -9,7 +9,7 @@ module Dependabot
9
9
  class UpdateChecker
10
10
  class IndexFinder
11
11
  PYPI_BASE_URL = "https://pypi.org/simple/"
12
- ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/.freeze
12
+ ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
13
13
 
14
14
  def initialize(dependency_files:, credentials:)
15
15
  @dependency_files = dependency_files
@@ -112,9 +112,9 @@ module Dependabot
112
112
  end
113
113
 
114
114
  def filter_lower_versions(versions_array)
115
- return versions_array unless dependency.version && version_class.correct?(dependency.version)
115
+ return versions_array unless dependency.numeric_version
116
116
 
117
- versions_array.select { |version| version > version_class.new(dependency.version) }
117
+ versions_array.select { |version| version > dependency.numeric_version }
118
118
  end
119
119
 
120
120
  def filter_out_of_range_versions(versions_array)
@@ -11,6 +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
15
  require "dependabot/python/native_helpers"
15
16
  require "dependabot/python/python_versions"
16
17
  require "dependabot/python/name_normaliser"
@@ -24,16 +25,14 @@ module Dependabot
24
25
  # - Run `pip-compile` and see what the result is
25
26
  # rubocop:disable Metrics/ClassLength
26
27
  class PipCompileVersionResolver
27
- GIT_DEPENDENCY_UNREACHABLE_REGEX =
28
- /git clone --filter=blob:none --quiet (?<url>[^\s]+).* /.freeze
29
- GIT_REFERENCE_NOT_FOUND_REGEX =
30
- /Did not find branch or tag '(?<tag>[^\n"]+)'/m.freeze
28
+ GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone --filter=blob:none --quiet (?<url>[^\s]+).* /
29
+ GIT_REFERENCE_NOT_FOUND_REGEX = /Did not find branch or tag '(?<tag>[^\n"]+)'/m
31
30
  NATIVE_COMPILATION_ERROR =
32
31
  "pip._internal.exceptions.InstallationSubprocessError: Command errored out with exit status 1:"
33
32
  # See https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata
34
- PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]+/.freeze
33
+ PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]+/
35
34
  RESOLUTION_IMPOSSIBLE_ERROR = "ResolutionImpossible"
36
- ERROR_REGEX = /(?<=ERROR\:\W).*$/.freeze
35
+ ERROR_REGEX = /(?<=ERROR\:\W).*$/
37
36
 
38
37
  attr_reader :dependency, :dependency_files, :credentials
39
38
 
@@ -72,7 +71,7 @@ module Dependabot
72
71
  SharedHelpers.in_a_temporary_directory do
73
72
  SharedHelpers.with_git_configured(credentials: credentials) do
74
73
  write_temporary_dependency_files(updated_req: requirement)
75
- install_required_python
74
+ Helpers.install_required_python(python_version)
76
75
 
77
76
  filenames_to_compile.each do |filename|
78
77
  # Shell out to pip-compile.
@@ -80,9 +79,17 @@ module Dependabot
80
79
  run_pip_compile_command(
81
80
  "pyenv exec pip-compile -v #{pip_compile_options(filename)} -P #{dependency.name} #{filename}"
82
81
  )
83
- # Run pip-compile a second time, without an update argument,
84
- # to ensure it handles markers correctly
85
- write_original_manifest_files unless dependency.top_level?
82
+
83
+ next if dependency.top_level?
84
+
85
+ # Run pip-compile a second time for transient dependencies
86
+ # to make sure we do not update dependencies that are
87
+ # superfluous. pip-compile does not detect these when
88
+ # updating a specific dependency with the -P option.
89
+ # Running pip-compile a second time will automatically remove
90
+ # superfluous dependencies. Dependabot then marks those with
91
+ # update_not_possible.
92
+ write_original_manifest_files
86
93
  run_pip_compile_command(
87
94
  "pyenv exec pip-compile #{pip_compile_options(filename)} #{filename}"
88
95
  )
@@ -247,7 +254,7 @@ module Dependabot
247
254
  end
248
255
 
249
256
  def run_pip_compile_command(command)
250
- run_command("pyenv local #{python_version}")
257
+ run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
251
258
  run_command(command)
252
259
  end
253
260
 
@@ -291,7 +298,7 @@ module Dependabot
291
298
  end
292
299
 
293
300
  # Overwrite the .python-version with updated content
294
- File.write(".python-version", python_version)
301
+ File.write(".python-version", Helpers.python_major_minor(python_version))
295
302
 
296
303
  setup_files.each do |file|
297
304
  path = file.name
@@ -313,15 +320,6 @@ module Dependabot
313
320
  end
314
321
  end
315
322
 
316
- def install_required_python
317
- return if run_command("pyenv versions").include?("#{python_version}\n")
318
-
319
- run_command("pyenv install -s #{python_version}")
320
- run_command("pyenv exec pip install --upgrade pip")
321
- run_command("pyenv exec pip install -r" \
322
- "#{NativeHelpers.python_requirements_path}")
323
- end
324
-
325
323
  def sanitized_setup_file_content(file)
326
324
  @sanitized_setup_file_content ||= {}
327
325
  return @sanitized_setup_file_content[file.name] if @sanitized_setup_file_content[file.name]
@@ -30,21 +30,18 @@ module Dependabot
30
30
  # still better than nothing, though.
31
31
  class PipenvVersionResolver
32
32
  # rubocop:disable Layout/LineLength
33
- GIT_DEPENDENCY_UNREACHABLE_REGEX =
34
- /git clone -q (?<url>[^\s]+).* /.freeze
35
- GIT_REFERENCE_NOT_FOUND_REGEX =
36
- %r{git checkout -q (?<tag>[^\n"]+)\n?[^\n]*/(?<name>.*?)(\\n'\]|$)}m.
37
- freeze
33
+ GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone -q (?<url>[^\s]+).* /
34
+ GIT_REFERENCE_NOT_FOUND_REGEX = %r{git checkout -q (?<tag>[^\n"]+)\n?[^\n]*/(?<name>.*?)(\\n'\]|$)}m
38
35
  PIPENV_INSTALLATION_ERROR = "pipenv.patched.notpip._internal.exceptions.InstallationError: Command errored out" \
39
36
  " with exit status 1: python setup.py egg_info"
40
37
  TRACEBACK = "Traceback (most recent call last):"
41
38
  PIPENV_INSTALLATION_ERROR_REGEX =
42
- /#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}/.
43
- freeze
39
+ /#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}/
40
+
44
41
  UNSUPPORTED_DEPS = %w(pyobjc).freeze
45
42
  UNSUPPORTED_DEP_REGEX =
46
- /Could not find a version that satisfies the requirement.*(?:#{UNSUPPORTED_DEPS.join("|")})/.freeze
47
- PIPENV_RANGE_WARNING = /Warning:\sPython\s[<>].* was not found/.freeze
43
+ /Could not find a version that satisfies the requirement.*(?:#{UNSUPPORTED_DEPS.join("|")})/
44
+ PIPENV_RANGE_WARNING = /Warning:\sPython\s[<>].* was not found/
48
45
  # rubocop:enable Layout/LineLength
49
46
 
50
47
  DEPENDENCY_TYPES = %w(packages dev-packages).freeze
@@ -293,7 +290,7 @@ module Dependabot
293
290
  end
294
291
 
295
292
  # Overwrite the .python-version with updated content
296
- File.write(".python-version", python_version)
293
+ File.write(".python-version", Helpers.python_major_minor(python_version))
297
294
 
298
295
  setup_files.each do |file|
299
296
  path = file.name
@@ -323,13 +320,7 @@ module Dependabot
323
320
  nil
324
321
  end
325
322
 
326
- return if run_command("pyenv versions").include?("#{python_version}\n")
327
-
328
- requirements_path = NativeHelpers.python_requirements_path
329
- run_command("pyenv install -s #{python_version}")
330
- run_command("pyenv exec pip install --upgrade pip")
331
- run_command("pyenv exec pip install -r " \
332
- "#{requirements_path}")
323
+ Helpers.install_required_python(python_version)
333
324
  end
334
325
 
335
326
  def sanitized_setup_file_content(file)
@@ -350,6 +341,7 @@ module Dependabot
350
341
  content = freeze_other_dependencies(content)
351
342
  content = set_target_dependency_req(content, updated_requirement)
352
343
  content = add_private_sources(content)
344
+ content = update_python_requirement(content)
353
345
  content
354
346
  end
355
347
 
@@ -359,6 +351,12 @@ module Dependabot
359
351
  freeze_top_level_dependencies_except([dependency])
360
352
  end
361
353
 
354
+ def update_python_requirement(pipfile_content)
355
+ Python::FileUpdater::PipfilePreparer.
356
+ new(pipfile_content: pipfile_content).
357
+ update_python_requirement(Helpers.python_major_minor(python_version))
358
+ end
359
+
362
360
  # rubocop:disable Metrics/PerceivedComplexity
363
361
  def set_target_dependency_req(pipfile_content, updated_requirement)
364
362
  return pipfile_content unless updated_requirement
@@ -470,7 +468,7 @@ module Dependabot
470
468
  end
471
469
 
472
470
  def run_pipenv_command(command, env: pipenv_env_variables)
473
- run_command("pyenv local #{python_version}")
471
+ run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
474
472
  run_command(command, env: env)
475
473
  end
476
474
 
@@ -28,10 +28,14 @@ module Dependabot
28
28
  'checkout',
29
29
  '(?<tag>.+?)'
30
30
  |
31
+ Failed to checkout
32
+ (?<tag>.+?)
33
+ (?<url>.+?).git at '(?<tag>.+?)'
34
+ |
31
35
  ...Failedtoclone
32
36
  (?<url>.+?).gitat'(?<tag>.+?)',
33
37
  verifyrefexistsonremote)
34
- /x.freeze # TODO: remove the first clause and | when py3.6 support is EoL
38
+ /x # TODO: remove the first clause and | when py3.6 support is EoL
35
39
  GIT_DEPENDENCY_UNREACHABLE_REGEX = /
36
40
  (?:'\['git',
37
41
  \s+'clone',
@@ -43,7 +47,7 @@ module Dependabot
43
47
  \s+Failed\sto\sclone
44
48
  \s+(?<url>.+?),
45
49
  \s+check\syour\sgit\sconfiguration)
46
- /mx.freeze # TODO: remove the first clause and | when py3.6 support is EoL
50
+ /mx # TODO: remove the first clause and | when py3.6 support is EoL
47
51
 
48
52
  attr_reader :dependency, :dependency_files, :credentials
49
53
 
@@ -88,13 +92,11 @@ module Dependabot
88
92
  write_temporary_dependency_files(updated_req: requirement)
89
93
  add_auth_env_vars
90
94
 
91
- if python_version && !pre_installed_python?(python_version)
92
- run_poetry_command("pyenv install -s #{python_version}")
93
- run_poetry_command("pyenv exec pip install --upgrade pip")
94
- run_poetry_command(
95
- "pyenv exec pip install -r " \
96
- "#{NativeHelpers.python_requirements_path}"
97
- )
95
+ Helpers.install_required_python(python_version)
96
+
97
+ # use system git instead of the pure Python dulwich
98
+ unless python_version&.start_with?("3.6")
99
+ run_poetry_command("pyenv exec poetry config experimental.system-git-client true")
98
100
  end
99
101
 
100
102
  # Shell out to Poetry, which handles everything for us.
@@ -200,7 +202,7 @@ module Dependabot
200
202
  end
201
203
 
202
204
  # Overwrite the .python-version with updated content
203
- File.write(".python-version", python_version) if python_version
205
+ File.write(".python-version", Helpers.python_major_minor(python_version)) if python_version
204
206
 
205
207
  # Overwrite the pyproject with updated content
206
208
  if update_pyproject
@@ -282,7 +284,7 @@ module Dependabot
282
284
  pyproject_object = TomlRB.parse(pyproject_content)
283
285
  poetry_object = pyproject_object.dig("tool", "poetry")
284
286
 
285
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
287
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
286
288
  names = poetry_object[type]&.keys || []
287
289
  pkg_name = names.find { |nm| normalise(nm) == dependency.name }
288
290
  next unless pkg_name
@@ -335,7 +337,7 @@ module Dependabot
335
337
  stdout, process = Open3.capture2e(command)
336
338
  time_taken = Time.now - start
337
339
 
338
- # Raise an error with the output from the shell session if Pipenv
340
+ # Raise an error with the output from the shell session if poetry
339
341
  # returns a non-zero status
340
342
  return if process.success?
341
343
 
@@ -9,8 +9,8 @@ module Dependabot
9
9
  module Python
10
10
  class UpdateChecker
11
11
  class RequirementsUpdater
12
- PYPROJECT_OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/.freeze
13
- PYPROJECT_SEPARATOR = /#{PYPROJECT_OR_SEPARATOR}|,/.freeze
12
+ PYPROJECT_OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/
13
+ PYPROJECT_SEPARATOR = /#{PYPROJECT_OR_SEPARATOR}|,/
14
14
 
15
15
  class UnfixableRequirement < StandardError; end
16
16
 
@@ -175,11 +175,25 @@ module Dependabot
175
175
  end
176
176
  # rubocop:enable Metrics/PerceivedComplexity
177
177
 
178
- # rubocop:disable Metrics/PerceivedComplexity
179
178
  def updated_requirement(req)
180
179
  return req unless latest_resolvable_version
181
180
  return req unless req.fetch(:requirement)
182
181
 
182
+ case update_strategy
183
+ when :bump_versions
184
+ update_requirement(req)
185
+ when :bump_versions_if_necessary
186
+ update_requirement_if_needed(req)
187
+ end
188
+ end
189
+
190
+ def update_requirement_if_needed(req)
191
+ return req if new_version_satisfies?(req)
192
+
193
+ update_requirement(req)
194
+ end
195
+
196
+ def update_requirement(req)
183
197
  requirement_strings = req[:requirement].split(",").map(&:strip)
184
198
 
185
199
  new_requirement =
@@ -197,7 +211,6 @@ module Dependabot
197
211
  rescue UnfixableRequirement
198
212
  req.merge(requirement: :unfixable)
199
213
  end
200
- # rubocop:enable Metrics/PerceivedComplexity
201
214
 
202
215
  def new_version_satisfies?(req)
203
216
  requirement_class.