dependabot-python 0.211.0 → 0.213.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 (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