dependabot-python 0.211.0 → 0.213.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +1 -6
  3. data/helpers/lib/parser.py +52 -0
  4. data/helpers/requirements.txt +3 -3
  5. data/helpers/run.py +2 -0
  6. data/lib/dependabot/python/file_fetcher.rb +24 -14
  7. data/lib/dependabot/python/file_parser/{poetry_files_parser.rb → pyproject_files_parser.rb} +87 -5
  8. data/lib/dependabot/python/file_parser/python_requirement_parser.rb +1 -2
  9. data/lib/dependabot/python/file_parser/setup_file_parser.rb +5 -5
  10. data/lib/dependabot/python/file_parser.rb +5 -29
  11. data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +14 -29
  12. data/lib/dependabot/python/file_updater/pipfile_file_updater.rb +7 -9
  13. data/lib/dependabot/python/file_updater/poetry_file_updater.rb +7 -6
  14. data/lib/dependabot/python/file_updater/pyproject_preparer.rb +3 -2
  15. data/lib/dependabot/python/file_updater/requirement_file_updater.rb +2 -2
  16. data/lib/dependabot/python/file_updater/requirement_replacer.rb +2 -2
  17. data/lib/dependabot/python/file_updater/setup_file_sanitizer.rb +8 -8
  18. data/lib/dependabot/python/file_updater.rb +15 -2
  19. data/lib/dependabot/python/helpers.rb +20 -0
  20. data/lib/dependabot/python/metadata_finder.rb +2 -0
  21. data/lib/dependabot/python/native_helpers.rb +1 -1
  22. data/lib/dependabot/python/python_versions.rb +5 -5
  23. data/lib/dependabot/python/requirement.rb +7 -4
  24. data/lib/dependabot/python/requirement_parser.rb +20 -23
  25. data/lib/dependabot/python/update_checker/index_finder.rb +2 -2
  26. data/lib/dependabot/python/update_checker/latest_version_finder.rb +10 -7
  27. data/lib/dependabot/python/update_checker/pip_compile_version_resolver.rb +17 -19
  28. data/lib/dependabot/python/update_checker/pipenv_version_resolver.rb +29 -34
  29. data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +45 -26
  30. data/lib/dependabot/python/update_checker/requirements_updater.rb +18 -5
  31. data/lib/dependabot/python/update_checker.rb +82 -27
  32. data/lib/dependabot/python/version.rb +2 -2
  33. metadata +16 -43
@@ -4,6 +4,7 @@ require "toml-rb"
4
4
  require "open3"
5
5
  require "dependabot/dependency"
6
6
  require "dependabot/shared_helpers"
7
+ require "dependabot/python/helpers"
7
8
  require "dependabot/python/version"
8
9
  require "dependabot/python/requirement"
9
10
  require "dependabot/python/python_versions"
@@ -131,7 +132,7 @@ module Dependabot
131
132
  end
132
133
 
133
134
  def lock_declaration_to_new_version!(poetry_object, dep)
134
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
135
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
135
136
  names = poetry_object[type]&.keys || []
136
137
  pkg_name = names.find { |nm| normalise(nm) == dep.name }
137
138
  next unless pkg_name
@@ -170,11 +171,11 @@ module Dependabot
170
171
  write_temporary_dependency_files(pyproject_content)
171
172
  add_auth_env_vars
172
173
 
173
- if python_version && !pre_installed_python?(python_version)
174
- run_poetry_command("pyenv install -s #{python_version}")
175
- run_poetry_command("pyenv exec pip install --upgrade pip")
176
- run_poetry_command("pyenv exec pip install -r"\
177
- "#{NativeHelpers.python_requirements_path}")
174
+ Helpers.install_required_python(python_version)
175
+
176
+ # use system git instead of the pure Python dulwich
177
+ unless python_version&.start_with?("3.6")
178
+ run_poetry_command("pyenv exec poetry config experimental.system-git-client true")
178
179
  end
179
180
 
180
181
  run_poetry_command(poetry_update_command)
@@ -52,9 +52,10 @@ module Dependabot
52
52
  poetry_object = pyproject_object["tool"]["poetry"]
53
53
  excluded_names = dependencies.map(&:name) + ["python"]
54
54
 
55
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |key|
55
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |key|
56
56
  next unless poetry_object[key]
57
57
 
58
+ source_types = %w(directory file url)
58
59
  poetry_object.fetch(key).each do |dep_name, _|
59
60
  next if excluded_names.include?(normalise(dep_name))
60
61
 
@@ -62,7 +63,7 @@ module Dependabot
62
63
 
63
64
  next unless (locked_version = locked_details&.fetch("version"))
64
65
 
65
- next if %w(directory file url).include?(locked_details&.dig("source", "type"))
66
+ next if source_types.include?(locked_details&.dig("source", "type"))
66
67
 
67
68
  if locked_details&.dig("source", "type") == "git"
68
69
  poetry_object[key][dep_name] = {
@@ -36,7 +36,7 @@ module Dependabot
36
36
  def fetch_updated_dependency_files
37
37
  reqs = dependency.requirements.zip(dependency.previous_requirements)
38
38
 
39
- reqs.map do |(new_req, old_req)|
39
+ reqs.filter_map do |(new_req, old_req)|
40
40
  next if new_req == old_req
41
41
 
42
42
  file = get_original_file(new_req.fetch(:file)).dup
@@ -46,7 +46,7 @@ module Dependabot
46
46
 
47
47
  file.content = updated_content
48
48
  file
49
- end.compact
49
+ end
50
50
  end
51
51
 
52
52
  def updated_requirement_or_setup_file_content(new_req, old_req)
@@ -52,7 +52,7 @@ module Dependabot
52
52
  if add_space_after_operators?
53
53
  new_req_string =
54
54
  new_req_string.
55
- gsub(/(#{RequirementParser::COMPARISON})\s*(?=\d)/, '\1 ')
55
+ gsub(/(#{RequirementParser::COMPARISON})\s*(?=\d)/o, '\1 ')
56
56
  end
57
57
 
58
58
  new_req_string
@@ -92,7 +92,7 @@ module Dependabot
92
92
  def add_space_after_operators?
93
93
  original_dependency_declaration_string(old_requirement).
94
94
  match(RequirementParser::REQUIREMENTS).
95
- to_s.match?(/#{RequirementParser::COMPARISON}\s+\d/)
95
+ to_s.match?(/#{RequirementParser::COMPARISON}\s+\d/o)
96
96
  end
97
97
 
98
98
  def original_declaration_replacement_regex
@@ -19,9 +19,9 @@ module Dependabot
19
19
  # install_requires. A name and version are required by don't end up
20
20
  # in the lockfile.
21
21
  content =
22
- "from setuptools import setup\n\n"\
23
- "setup(name=\"sanitized-package\",version=\"0.0.1\","\
24
- "install_requires=#{install_requires_array.to_json},"\
22
+ "from setuptools import setup\n\n" \
23
+ "setup(name=\"sanitized-package\",version=\"0.0.1\"," \
24
+ "install_requires=#{install_requires_array.to_json}," \
25
25
  "extras_require=#{extras_require_hash.to_json}"
26
26
 
27
27
  content += ',setup_requires=["pbr"],pbr=True' if include_pbr?
@@ -38,22 +38,22 @@ module Dependabot
38
38
 
39
39
  def install_requires_array
40
40
  @install_requires_array ||=
41
- parsed_setup_file.dependencies.map do |dep|
41
+ parsed_setup_file.dependencies.filter_map do |dep|
42
42
  next unless dep.requirements.first[:groups].
43
43
  include?("install_requires")
44
44
 
45
45
  dep.name + dep.requirements.first[:requirement].to_s
46
- end.compact
46
+ end
47
47
  end
48
48
 
49
49
  def setup_requires_array
50
50
  @setup_requires_array ||=
51
- parsed_setup_file.dependencies.map do |dep|
51
+ parsed_setup_file.dependencies.filter_map do |dep|
52
52
  next unless dep.requirements.first[:groups].
53
53
  include?("setup_requires")
54
54
 
55
55
  dep.name + dep.requirements.first[:requirement].to_s
56
- end.compact
56
+ end
57
57
  end
58
58
 
59
59
  def extras_require_hash
@@ -66,7 +66,7 @@ module Dependabot
66
66
 
67
67
  hash[group.split(":").last] ||= []
68
68
  hash[group.split(":").last] <<
69
- dep.name + dep.requirements.first[:requirement].to_s
69
+ (dep.name + dep.requirements.first[:requirement].to_s)
70
70
  end
71
71
  end
72
72
 
@@ -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"
@@ -60,8 +61,14 @@ module Dependabot
60
61
 
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
- return :pipfile if changed_req_files.any? { |f| f == "Pipfile" }
64
- return :poetry if changed_req_files.any? { |f| f == "pyproject.toml" }
64
+ return :pipfile if changed_req_files.any?("Pipfile")
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,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/logger"
4
+
5
+ module Dependabot
6
+ module Python
7
+ module Helpers
8
+ def self.install_required_python(python_version)
9
+ # The leading space is important in the version check
10
+ return if SharedHelpers.run_shell_command("pyenv versions").include?(" #{python_version}")
11
+
12
+ Dependabot.logger.info("Installing required Python #{python_version}.")
13
+ SharedHelpers.run_shell_command("pyenv install -s #{python_version}")
14
+ SharedHelpers.run_shell_command("pyenv exec pip install --upgrade pip")
15
+ SharedHelpers.run_shell_command("pyenv exec pip install -r" \
16
+ "#{NativeHelpers.python_requirements_path}")
17
+ end
18
+ end
19
+ end
20
+ 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
@@ -12,7 +12,7 @@ module Dependabot
12
12
  end
13
13
 
14
14
  def self.python_helpers_dir
15
- File.join(native_helpers_root, "python/helpers")
15
+ File.join(native_helpers_root, "python")
16
16
  end
17
17
 
18
18
  def self.native_helpers_root
@@ -4,16 +4,16 @@ module Dependabot
4
4
  module Python
5
5
  module PythonVersions
6
6
  PRE_INSTALLED_PYTHON_VERSIONS = %w(
7
- 3.10.5
7
+ 3.10.7
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
12
  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
13
+ 3.10.7 3.10.6 3.10.5 3.10.4 3.10.3 3.10.2 3.10.1 3.10.0
14
+ 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
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
16
+ 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
17
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
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
19
19
  ).freeze
@@ -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
@@ -171,7 +171,7 @@ module Dependabot
171
171
  authed_url = config_variable_urls.find { |u| u.match?(regexp) }
172
172
  return authed_url if authed_url
173
173
 
174
- cleaned_url = url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}, "")
174
+ cleaned_url = url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}o, "")
175
175
  authed_url = authed_base_url(cleaned_url)
176
176
  return authed_url if credential_for(cleaned_url)
177
177
 
@@ -85,14 +85,14 @@ module Dependabot
85
85
  end
86
86
 
87
87
  def filter_unsupported_versions(versions_array, python_version)
88
- versions_array.map do |details|
88
+ versions_array.filter_map do |details|
89
89
  python_requirement = details.fetch(:python_requirement)
90
90
  next details.fetch(:version) unless python_version
91
91
  next details.fetch(:version) unless python_requirement
92
92
  next unless python_requirement.satisfied_by?(python_version)
93
93
 
94
94
  details.fetch(:version)
95
- end.compact
95
+ end
96
96
  end
97
97
 
98
98
  def filter_prerelease_versions(versions_array)
@@ -118,9 +118,9 @@ module Dependabot
118
118
  end
119
119
 
120
120
  def filter_out_of_range_versions(versions_array)
121
- reqs = dependency.requirements.map do |r|
121
+ reqs = dependency.requirements.filter_map do |r|
122
122
  requirement_class.requirements_array(r.fetch(:requirement))
123
- end.compact
123
+ end
124
124
 
125
125
  versions_array.
126
126
  select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } }
@@ -144,11 +144,14 @@ module Dependabot
144
144
  @available_versions ||=
145
145
  index_urls.flat_map do |index_url|
146
146
  sanitized_url = index_url.gsub(%r{(?<=//).*(?=@)}, "redacted")
147
+
147
148
  index_response = registry_response_for_dependency(index_url)
149
+ if index_response.status == 401 || index_response.status == 403
150
+ registry_index_response = registry_index_response(index_url)
148
151
 
149
- if [401, 403].include?(index_response.status) &&
150
- [401, 403].include?(registry_index_response(index_url).status)
151
- raise PrivateSourceAuthenticationFailure, sanitized_url
152
+ if registry_index_response.status == 401 || registry_index_response.status == 403
153
+ raise PrivateSourceAuthenticationFailure, sanitized_url
154
+ end
152
155
  end
153
156
 
154
157
  version_links = []
@@ -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
  )
@@ -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]
@@ -29,21 +29,22 @@ module Dependabot
29
29
  # just raise if the latest version can't be resolved. Knowing that is
30
30
  # still better than nothing, though.
31
31
  class PipenvVersionResolver
32
- GIT_DEPENDENCY_UNREACHABLE_REGEX =
33
- /git clone -q (?<url>[^\s]+).* /.freeze
34
- GIT_REFERENCE_NOT_FOUND_REGEX =
35
- %r{git checkout -q (?<tag>[^\n"]+)\n?[^\n]*/(?<name>.*?)(\\n'\]|$)}m.
36
- freeze
37
- PIPENV_INSTALLATION_ERROR = "pipenv.patched.notpip._internal.exceptions.InstallationError: Command errored out"\
32
+ # rubocop:disable Layout/LineLength
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
35
+ PIPENV_INSTALLATION_ERROR = "pipenv.patched.notpip._internal.exceptions.InstallationError: Command errored out" \
38
36
  " with exit status 1: python setup.py egg_info"
39
37
  TRACEBACK = "Traceback (most recent call last):"
40
38
  PIPENV_INSTALLATION_ERROR_REGEX =
41
- /#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}/.
42
- freeze
39
+ /#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}/
40
+
43
41
  UNSUPPORTED_DEPS = %w(pyobjc).freeze
44
42
  UNSUPPORTED_DEP_REGEX =
45
- /Could not find a version that satisfies the requirement.*(?:#{UNSUPPORTED_DEPS.join("|")})/.freeze
46
- 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/
45
+ # rubocop:enable Layout/LineLength
46
+
47
+ DEPENDENCY_TYPES = %w(packages dev-packages).freeze
47
48
 
48
49
  attr_reader :dependency, :dependency_files, :credentials
49
50
 
@@ -136,20 +137,20 @@ module Dependabot
136
137
  end
137
138
 
138
139
  if error.message.match?(UNSUPPORTED_DEP_REGEX)
139
- msg = "Dependabot detected a dependency that can't be built on "\
140
- "linux. Currently, all Dependabot builds happen on linux "\
141
- "boxes, so there is no way for Dependabot to resolve your "\
142
- "dependency files.\n\n"\
143
- "Unless you think Dependabot has made a mistake (please "\
144
- "tag us if so) you may wish to disable Dependabot on this "\
140
+ msg = "Dependabot detected a dependency that can't be built on " \
141
+ "linux. Currently, all Dependabot builds happen on linux " \
142
+ "boxes, so there is no way for Dependabot to resolve your " \
143
+ "dependency files.\n\n" \
144
+ "Unless you think Dependabot has made a mistake (please " \
145
+ "tag us if so) you may wish to disable Dependabot on this " \
145
146
  "repo."
146
147
  raise DependencyFileNotResolvable, msg
147
148
  end
148
149
 
149
150
  if error.message.match?(PIPENV_RANGE_WARNING)
150
- msg = "Pipenv does not support specifying Python ranges "\
151
- "(see https://github.com/pypa/pipenv/issues/1050 for more "\
152
- "details)."
151
+ msg = "Pipenv does not support specifying Python ranges " \
152
+ "(see https://github.com/pypa/pipenv/issues/1050 for more " \
153
+ "details)."
153
154
  raise DependencyFileNotResolvable, msg
154
155
  end
155
156
 
@@ -159,7 +160,7 @@ module Dependabot
159
160
 
160
161
  if error.message.include?("SyntaxError: invalid syntax")
161
162
  raise DependencyFileNotResolvable,
162
- "SyntaxError while installing dependencies. Is one of the dependencies not Python 3 compatible? "\
163
+ "SyntaxError while installing dependencies. Is one of the dependencies not Python 3 compatible? " \
163
164
  "Pip v21 no longer supports Python 2."
164
165
  end
165
166
 
@@ -272,9 +273,9 @@ module Dependabot
272
273
  dependency_name = error_message.match(PIPENV_INSTALLATION_ERROR_REGEX).named_captures["name"]
273
274
  raise unless dependency_name
274
275
 
275
- msg = "Pipenv failed to install \"#{dependency_name}\". This could be caused by missing system "\
276
- "dependencies that can't be installed by Dependabot or required installation flags.\n\n"\
277
- "Error output from running \"pipenv lock\":\n"\
276
+ msg = "Pipenv failed to install \"#{dependency_name}\". This could be caused by missing system " \
277
+ "dependencies that can't be installed by Dependabot or required installation flags.\n\n" \
278
+ "Error output from running \"pipenv lock\":\n" \
278
279
  "#{clean_error_message(error_message)}"
279
280
 
280
281
  raise DependencyFileNotResolvable, msg
@@ -319,13 +320,7 @@ module Dependabot
319
320
  nil
320
321
  end
321
322
 
322
- return if run_command("pyenv versions").include?("#{python_version}\n")
323
-
324
- requirements_path = NativeHelpers.python_requirements_path
325
- run_command("pyenv install -s #{python_version}")
326
- run_command("pyenv exec pip install --upgrade pip")
327
- run_command("pyenv exec pip install -r "\
328
- "#{requirements_path}")
323
+ Helpers.install_required_python(python_version)
329
324
  end
330
325
 
331
326
  def sanitized_setup_file_content(file)
@@ -361,7 +356,7 @@ module Dependabot
361
356
 
362
357
  pipfile_object = TomlRB.parse(pipfile_content)
363
358
 
364
- %w(packages dev-packages).each do |type|
359
+ DEPENDENCY_TYPES.each do |type|
365
360
  names = pipfile_object[type]&.keys || []
366
361
  pkg_name = names.find { |nm| normalise(nm) == dependency.name }
367
362
  next unless pkg_name || subdep_type?(type)
@@ -429,9 +424,9 @@ module Dependabot
429
424
 
430
425
  # Otherwise we have to raise, giving details of the Python versions
431
426
  # that Dependabot supports
432
- msg = "Dependabot detected the following Python requirement "\
433
- "for your project: '#{requirement_string}'.\n\nCurrently, the "\
434
- "following Python versions are supported in Dependabot: "\
427
+ msg = "Dependabot detected the following Python requirement " \
428
+ "for your project: '#{requirement_string}'.\n\nCurrently, the " \
429
+ "following Python versions are supported in Dependabot: " \
435
430
  "#{PythonVersions::SUPPORTED_VERSIONS.join(', ')}."
436
431
  raise DependencyFileNotResolvable, msg
437
432
  end