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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4862b59513a97a33d51ff8ad57b6d9815842769c87f612426ffbcd8e75a9655c
4
- data.tar.gz: 9543935b3ab8027344757b41aa8ff265d823beac956a06d20d4917e75962698d
3
+ metadata.gz: 940ed0c4abf7f4d3a496321e4898ba9c123091d6539f86ef54d7ee74dadf3344
4
+ data.tar.gz: 802abe558f75bc2e98f1b88e93be85fc48f8b71774a1ff37b8ea16311381f587
5
5
  SHA512:
6
- metadata.gz: fd9e547d19f01addfdc2fcc4eeb7095bf8e886eded086772f81b9bfbd6acfb2fc6076c354c970ae5fad0691a25679d99db8eb0884121e986c1bcf39b17c041ba
7
- data.tar.gz: be587fe031f132dd20d4fc023668e31d81c37608d915a9f586ca159b675302993a208b6e93209cfaedad83d08eaae1ca06f6af7eec8933bcc2c44e8a61293ccf
6
+ metadata.gz: 523ff39717afd9636f3d2f3115d6953817ab01585e2e218233eb0439a7cc9e5ac620c4b28d429b35256530a32bff6e71a73ffdfd72587ba53c8b10b6a3070175
7
+ data.tar.gz: a3d05a60ad4d1b08dfe8fed7cdac12384aa49fbb3ad130008bf4748ab710df9b20a8297c99f66e33bd672717b52be32c17434b2ed253fe4bb6556cfc87941b05
data/helpers/build CHANGED
@@ -18,9 +18,8 @@ cp -r \
18
18
  "$install_dir"
19
19
 
20
20
  cd "$install_dir"
21
- PYENV_VERSION=3.10.5 pyenv exec pip --disable-pip-version-check install -r "requirements.txt"
22
-
23
- # Workaround of https://github.com/python-poetry/poetry/issues/3010
24
- # By default poetry config file is stored under ~/.config/pypoetry
25
- # and is not bound to any specific Python version
26
- PYENV_VERSION=3.10.5 pyenv exec poetry config experimental.new-installer false
21
+ PYENV_VERSION=3.11.0 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
22
+ PYENV_VERSION=3.10.8 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
23
+ PYENV_VERSION=3.9.15 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
24
+ PYENV_VERSION=3.8.15 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
25
+ PYENV_VERSION=3.7.15 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
@@ -11,11 +11,70 @@ from pip._internal.req.constructors import (
11
11
  install_req_from_line,
12
12
  install_req_from_parsed_requirement,
13
13
  )
14
+
15
+ from packaging.requirements import InvalidRequirement, Requirement
16
+ import toml
17
+
14
18
  # Inspired by pips internal check:
15
19
  # https://github.com/pypa/pip/blob/0bb3ac87f5bb149bd75cceac000844128b574385/src/pip/_internal/req/req_file.py#L35
16
20
  COMMENT_RE = re.compile(r'(^|\s+)#.*$')
17
21
 
18
22
 
23
+ def parse_pep621_dependencies(pyproject_path):
24
+ project_toml = toml.load(pyproject_path)['project']
25
+
26
+ def parse_toml_section_pep621_dependencies(pyproject_path, dependencies):
27
+ requirement_packages = []
28
+
29
+ def version_from_req(specifier_set):
30
+ if (len(specifier_set) == 1 and
31
+ next(iter(specifier_set)).operator in {"==", "==="}):
32
+ return next(iter(specifier_set)).version
33
+
34
+ for dependency in dependencies:
35
+ try:
36
+ req = Requirement(dependency)
37
+ except InvalidRequirement as e:
38
+ print(json.dumps({"error": repr(e)}))
39
+ exit(1)
40
+ else:
41
+ requirement_packages.append({
42
+ "name": req.name,
43
+ "version": version_from_req(req.specifier),
44
+ "markers": str(req.marker) or None,
45
+ "file": pyproject_path,
46
+ "requirement": str(req.specifier),
47
+ "extras": sorted(list(req.extras))
48
+ })
49
+
50
+ return requirement_packages
51
+
52
+ dependencies = []
53
+
54
+ if 'dependencies' in project_toml:
55
+ dependencies_toml = project_toml['dependencies']
56
+
57
+ runtime_dependencies = parse_toml_section_pep621_dependencies(
58
+ pyproject_path,
59
+ dependencies_toml
60
+ )
61
+
62
+ dependencies.extend(runtime_dependencies)
63
+
64
+ if 'optional-dependencies' in project_toml:
65
+ optional_dependencies_toml = project_toml['optional-dependencies']
66
+
67
+ for group in optional_dependencies_toml:
68
+ group_dependencies = parse_toml_section_pep621_dependencies(
69
+ pyproject_path,
70
+ optional_dependencies_toml[group]
71
+ )
72
+
73
+ dependencies.extend(group_dependencies)
74
+
75
+ return json.dumps({"result": dependencies})
76
+
77
+
19
78
  def parse_requirements(directory):
20
79
  # Parse the requirements.txt
21
80
  requirement_packages = []
@@ -1,10 +1,10 @@
1
- pip>=21.3.1,<22.2.3 # Range maintains py36 support TODO: Review python 3.6 support in April 2023 (eol ubuntu 18.04)
2
- pip-tools>=6.4.0,<6.8.1 # Range maintains py36 support TODO: Review python 3.6 support in April 2023 (eol ubuntu 18.04)
1
+ pip>=21.3.1,<22.4.0 # Range maintains py36 support TODO: Review python 3.6 support in April 2023 (eol ubuntu 18.04)
2
+ pip-tools>=6.4.0,<6.10.1 # Range maintains py36 support TODO: Review python 3.6 support in April 2023 (eol ubuntu 18.04)
3
3
  flake8==5.0.4
4
4
  hashin==0.17.0
5
5
  pipenv==2022.4.8
6
6
  pipfile==0.0.2
7
- poetry>=1.1.15,<=1.2.0
7
+ poetry>=1.1.15,<1.3.0
8
8
  wheel==0.37.1
9
9
 
10
10
  # Some dependencies will only install if Cython is present
data/helpers/run.py CHANGED
@@ -10,6 +10,8 @@ if __name__ == "__main__":
10
10
  print(parser.parse_requirements(args["args"][0]))
11
11
  elif args["function"] == "parse_setup":
12
12
  print(parser.parse_setup(args["args"][0]))
13
+ elif args["function"] == "parse_pep621_dependencies":
14
+ print(parser.parse_pep621_dependencies(args["args"][0]))
13
15
  elif args["function"] == "get_dependency_hash":
14
16
  print(hasher.get_dependency_hash(*args["args"]))
15
17
  elif args["function"] == "get_pipfile_hash":
@@ -5,15 +5,15 @@ require "toml-rb"
5
5
  require "dependabot/file_fetchers"
6
6
  require "dependabot/file_fetchers/base"
7
7
  require "dependabot/python/requirement_parser"
8
- require "dependabot/python/file_parser/poetry_files_parser"
8
+ require "dependabot/python/file_parser/pyproject_files_parser"
9
9
  require "dependabot/errors"
10
10
 
11
11
  module Dependabot
12
12
  module Python
13
13
  class FileFetcher < Dependabot::FileFetchers::Base
14
- CHILD_REQUIREMENT_REGEX = /^-r\s?(?<path>.*\.(?:txt|in))/.freeze
15
- CONSTRAINT_REGEX = /^-c\s?(?<path>.*\.(?:txt|in))/.freeze
16
- DEPENDENCY_TYPES = %w(packages dev-packages).freeze
14
+ CHILD_REQUIREMENT_REGEX = /^-r\s?(?<path>.*\.(?:txt|in))/
15
+ CONSTRAINT_REGEX = /^-c\s?(?<path>.*\.(?:txt|in))/
16
+ DEPENDENCY_TYPES = %w(packages dev-packages)
17
17
 
18
18
  def self.required_files_in?(filenames)
19
19
  return true if filenames.any? { |name| name.end_with?(".txt", ".in") }
@@ -24,7 +24,7 @@ module Dependabot
24
24
  # If this repo is using a Pipfile return true
25
25
  return true if filenames.include?("Pipfile")
26
26
 
27
- # If this repo is using Poetry return true
27
+ # If this repo is using pyproject.toml return true
28
28
  return true if filenames.include?("pyproject.toml")
29
29
 
30
30
  return true if filenames.include?("setup.py")
@@ -69,7 +69,7 @@ module Dependabot
69
69
  end
70
70
 
71
71
  def pyproject_files
72
- [pyproject, pyproject_lock, poetry_lock].compact
72
+ [pyproject, pyproject_lock, poetry_lock, pdm_lock].compact
73
73
  end
74
74
 
75
75
  def requirement_files
@@ -81,7 +81,12 @@ module Dependabot
81
81
  end
82
82
 
83
83
  def check_required_files_present
84
- return if requirements_txt_files.any? || setup_file || setup_cfg_file || pipfile || pyproject
84
+ return if requirements_txt_files.any? ||
85
+ requirements_in_files.any? ||
86
+ setup_file ||
87
+ setup_cfg_file ||
88
+ pipfile ||
89
+ pyproject
85
90
 
86
91
  path = Pathname.new(File.join(directory, "requirements.txt")).
87
92
  cleanpath.to_path
@@ -136,6 +141,10 @@ module Dependabot
136
141
  @poetry_lock ||= fetch_file_if_present("poetry.lock")
137
142
  end
138
143
 
144
+ def pdm_lock
145
+ @pdm_lock ||= fetch_file_if_present("pdm.lock")
146
+ end
147
+
139
148
  def requirements_txt_files
140
149
  req_txt_and_in_files.select { |f| f.name.end_with?(".txt") }
141
150
  end
@@ -169,7 +178,7 @@ module Dependabot
169
178
  repo_contents.
170
179
  select { |f| f.type == "file" }.
171
180
  select { |f| f.name.end_with?(".txt", ".in") }.
172
- reject { |f| f.size > 200_000 }.
181
+ reject { |f| f.size > 500_000 }.
173
182
  map { |f| fetch_file_from_host(f.name) }.
174
183
  select { |f| requirements_file?(f) }.
175
184
  each { |f| @req_txt_and_in_files << f }
@@ -189,7 +198,7 @@ module Dependabot
189
198
  repo_contents(dir: relative_reqs_dir).
190
199
  select { |f| f.type == "file" }.
191
200
  select { |f| f.name.end_with?(".txt", ".in") }.
192
- reject { |f| f.size > 200_000 }.
201
+ reject { |f| f.size > 500_000 }.
193
202
  map { |f| fetch_file_from_host("#{relative_reqs_dir}/#{f.name}") }.
194
203
  select { |f| requirements_file?(f) }
195
204
  end
@@ -291,8 +300,8 @@ module Dependabot
291
300
  fetch_submodules: true
292
301
  ).tap { |f| f.support_file = true }
293
302
  rescue Dependabot::DependencyFileNotFound
294
- # For Poetry projects attempt to fetch a pyproject.toml at the
295
- # given path instead of a setup.py. We do not require a
303
+ # For projects with pyproject.toml attempt to fetch a pyproject.toml
304
+ # at the given path instead of a setup.py. We do not require a
296
305
  # setup.py to be present, so if none can be found, simply return
297
306
  return [] unless allow_pyproject
298
307
 
@@ -390,7 +399,7 @@ module Dependabot
390
399
  return [] unless pyproject
391
400
 
392
401
  paths = []
393
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |dep_type|
402
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |dep_type|
394
403
  next unless parsed_pyproject.dig("tool", "poetry", dep_type)
395
404
 
396
405
  parsed_pyproject.dig("tool", "poetry", dep_type).each do |_, req|
@@ -12,7 +12,7 @@ require "dependabot/python/name_normaliser"
12
12
  module Dependabot
13
13
  module Python
14
14
  class FileParser
15
- class PoetryFilesParser
15
+ class PyprojectFilesParser
16
16
  POETRY_DEPENDENCY_TYPES = %w(dependencies dev-dependencies).freeze
17
17
 
18
18
  # https://python-poetry.org/docs/dependency-specification/
@@ -25,7 +25,7 @@ module Dependabot
25
25
  def dependency_set
26
26
  dependency_set = Dependabot::FileParsers::Base::DependencySet.new
27
27
 
28
- dependency_set += pyproject_dependencies
28
+ dependency_set += pyproject_dependencies if using_poetry? || using_pep621?
29
29
  dependency_set += lockfile_dependencies if lockfile
30
30
 
31
31
  dependency_set
@@ -36,6 +36,14 @@ module Dependabot
36
36
  attr_reader :dependency_files
37
37
 
38
38
  def pyproject_dependencies
39
+ if using_poetry?
40
+ poetry_dependencies
41
+ else
42
+ pep621_dependencies
43
+ end
44
+ end
45
+
46
+ def poetry_dependencies
39
47
  dependencies = Dependabot::FileParsers::Base::DependencySet.new
40
48
 
41
49
  POETRY_DEPENDENCY_TYPES.each do |type|
@@ -59,6 +67,44 @@ module Dependabot
59
67
  dependencies
60
68
  end
61
69
 
70
+ def pep621_dependencies
71
+ dependencies = Dependabot::FileParsers::Base::DependencySet.new
72
+
73
+ # PDM is not yet supported, so we want to ignore it for now because in
74
+ # the current state of things, going on would result in updating
75
+ # pyproject.toml but leaving pdm.lock out of sync, which is
76
+ # undesirable. Leave PDM alone until properly supported
77
+ return dependencies if using_pdm?
78
+
79
+ parsed_pep621_dependencies.each do |dep|
80
+ # If a requirement has a `<` or `<=` marker then updating it is
81
+ # probably blocked. Ignore it.
82
+ next if dep["markers"].include?("<")
83
+
84
+ # If no requirement, don't add it
85
+ next if dep["requirement"].empty?
86
+
87
+ dependencies <<
88
+ Dependency.new(
89
+ name: normalised_name(dep["name"], dep["extras"]),
90
+ version: dep["version"]&.include?("*") ? nil : dep["version"],
91
+ requirements: [{
92
+ requirement: dep["requirement"],
93
+ file: Pathname.new(dep["file"]).cleanpath.to_path,
94
+ source: nil,
95
+ groups: [dep["requirement_type"]]
96
+ }],
97
+ package_manager: "pip"
98
+ )
99
+ end
100
+
101
+ dependencies
102
+ end
103
+
104
+ def normalised_name(name, extras)
105
+ NameNormaliser.normalise_including_extras(name, extras)
106
+ end
107
+
62
108
  # @param req can be an Array, Hash or String that represents the constraints for a dependency
63
109
  def parse_requirements_from(req, type)
64
110
  [req].flatten.compact.filter_map do |requirement|
@@ -75,6 +121,19 @@ module Dependabot
75
121
  end
76
122
  end
77
123
 
124
+ def using_poetry?
125
+ !parsed_pyproject.dig("tool", "poetry").nil?
126
+ end
127
+
128
+ def using_pep621?
129
+ !parsed_pyproject.dig("project", "dependencies").nil? ||
130
+ !parsed_pyproject.dig("project", "optional-dependencies").nil?
131
+ end
132
+
133
+ def using_pdm?
134
+ using_pep621? && pdm_lock
135
+ end
136
+
78
137
  # Create a DependencySet where each element has no requirement. Any
79
138
  # requirements will be added when combining the DependencySet with
80
139
  # other DependencySets.
@@ -146,6 +205,24 @@ module Dependabot
146
205
  poetry_lock || pyproject_lock
147
206
  end
148
207
 
208
+ def parsed_pep621_dependencies
209
+ SharedHelpers.in_a_temporary_directory do
210
+ write_temporary_pyproject
211
+
212
+ SharedHelpers.run_helper_subprocess(
213
+ command: "pyenv exec python #{NativeHelpers.python_helper_path}",
214
+ function: "parse_pep621_dependencies",
215
+ args: [pyproject.name]
216
+ )
217
+ end
218
+ end
219
+
220
+ def write_temporary_pyproject
221
+ path = pyproject.name
222
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
223
+ File.write(path, pyproject.content)
224
+ end
225
+
149
226
  def parsed_lockfile
150
227
  return parsed_poetry_lock if poetry_lock
151
228
  return parsed_pyproject_lock if pyproject_lock
@@ -160,6 +237,11 @@ module Dependabot
160
237
  @poetry_lock ||=
161
238
  dependency_files.find { |f| f.name == "poetry.lock" }
162
239
  end
240
+
241
+ def pdm_lock
242
+ @pdm_lock ||=
243
+ dependency_files.find { |f| f.name == "pdm.lock" }
244
+ end
163
245
  end
164
246
  end
165
247
  end
@@ -12,10 +12,10 @@ module Dependabot
12
12
  module Python
13
13
  class FileParser
14
14
  class SetupFileParser
15
- INSTALL_REQUIRES_REGEX = /install_requires\s*=\s*\[/m.freeze
16
- SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*\[/m.freeze
17
- TESTS_REQUIRE_REGEX = /tests_require\s*=\s*\[/m.freeze
18
- EXTRAS_REQUIRE_REGEX = /extras_require\s*=\s*\{/m.freeze
15
+ INSTALL_REQUIRES_REGEX = /install_requires\s*=\s*\[/m
16
+ SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*\[/m
17
+ TESTS_REQUIRE_REGEX = /tests_require\s*=\s*\[/m
18
+ EXTRAS_REQUIRE_REGEX = /extras_require\s*=\s*\{/m
19
19
 
20
20
  CLOSING_BRACKET = { "[" => "]", "{" => "}" }.freeze
21
21
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "toml-rb"
4
3
  require "dependabot/dependency"
5
4
  require "dependabot/file_parsers"
6
5
  require "dependabot/file_parsers/base"
@@ -15,11 +14,9 @@ module Dependabot
15
14
  module Python
16
15
  class FileParser < Dependabot::FileParsers::Base
17
16
  require_relative "file_parser/pipfile_files_parser"
18
- require_relative "file_parser/poetry_files_parser"
17
+ require_relative "file_parser/pyproject_files_parser"
19
18
  require_relative "file_parser/setup_file_parser"
20
19
 
21
- POETRY_DEPENDENCY_TYPES =
22
- %w(tool.poetry.dependencies tool.poetry.dev-dependencies).freeze
23
20
  DEPENDENCY_GROUP_KEYS = [
24
21
  {
25
22
  pipfile: "packages",
@@ -42,7 +39,7 @@ module Dependabot
42
39
  dependency_set = DependencySet.new
43
40
 
44
41
  dependency_set += pipenv_dependencies if pipfile
45
- dependency_set += poetry_dependencies if using_poetry?
42
+ dependency_set += pyproject_file_dependencies if pyproject
46
43
  dependency_set += requirement_dependencies if requirement_files.any?
47
44
  dependency_set += setup_file_dependencies if setup_file || setup_cfg_file
48
45
 
@@ -62,9 +59,9 @@ module Dependabot
62
59
  dependency_set
63
60
  end
64
61
 
65
- def poetry_dependencies
66
- @poetry_dependencies ||=
67
- PoetryFilesParser.
62
+ def pyproject_file_dependencies
63
+ @pyproject_file_dependencies ||=
64
+ PyprojectFilesParser.
68
65
  new(dependency_files: dependency_files).
69
66
  dependency_set
70
67
  end
@@ -105,18 +102,6 @@ module Dependabot
105
102
  end
106
103
  end
107
104
 
108
- def included_in_pipenv_deps?(dep_name)
109
- return false unless pipfile
110
-
111
- pipenv_dependencies.dependencies.map(&:name).include?(dep_name)
112
- end
113
-
114
- def included_in_poetry_deps?(dep_name)
115
- return false unless using_poetry?
116
-
117
- poetry_dependencies.dependencies.map(&:name).include?(dep_name)
118
- end
119
-
120
105
  def blocking_marker?(dep)
121
106
  return false if dep["markers"] == "None"
122
107
  return true if dep["markers"].include?("<")
@@ -215,15 +200,6 @@ module Dependabot
215
200
  @pipfile_lock ||= get_original_file("Pipfile.lock")
216
201
  end
217
202
 
218
- def using_poetry?
219
- return false unless pyproject
220
- return true if poetry_lock || pyproject_lock
221
-
222
- !TomlRB.parse(pyproject.content).dig("tool", "poetry").nil?
223
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
224
- raise Dependabot::DependencyFileNotParseable, pyproject.path
225
- end
226
-
227
203
  def output_file_regex(filename)
228
204
  "--output-file[=\s]+#{Regexp.escape(filename)}(?:\s|$)"
229
205
  end
@@ -7,6 +7,7 @@ require "dependabot/python/file_fetcher"
7
7
  require "dependabot/python/file_parser/python_requirement_parser"
8
8
  require "dependabot/python/file_updater"
9
9
  require "dependabot/shared_helpers"
10
+ require "dependabot/python/helpers"
10
11
  require "dependabot/python/native_helpers"
11
12
  require "dependabot/python/python_versions"
12
13
  require "dependabot/python/name_normaliser"
@@ -22,10 +23,9 @@ module Dependabot
22
23
  require_relative "setup_file_sanitizer"
23
24
 
24
25
  UNSAFE_PACKAGES = %w(setuptools distribute pip).freeze
25
- INCOMPATIBLE_VERSIONS_REGEX = /There are incompatible versions in the resolved dependencies:.*\z/m.freeze
26
- WARNINGS = /\s*# WARNING:.*\Z/m.freeze
27
- UNSAFE_NOTE =
28
- /\s*# The following packages are considered to be unsafe.*\Z/m.freeze
26
+ INCOMPATIBLE_VERSIONS_REGEX = /There are incompatible versions in the resolved dependencies:.*\z/m
27
+ WARNINGS = /\s*# WARNING:.*\Z/m
28
+ UNSAFE_NOTE = /\s*# The following packages are considered to be unsafe.*\Z/m
29
29
 
30
30
  attr_reader :dependencies, :dependency_files, :credentials
31
31
 
@@ -66,7 +66,7 @@ module Dependabot
66
66
  def compile_new_requirement_files
67
67
  SharedHelpers.in_a_temporary_directory do
68
68
  write_updated_dependency_files
69
- install_required_python
69
+ Helpers.install_required_python(python_version)
70
70
 
71
71
  filenames_to_compile.each do |filename|
72
72
  # Shell out to pip-compile, generate a new set of requirements.
@@ -81,12 +81,6 @@ module Dependabot
81
81
  "#{SharedHelpers.escape_command(version_part)}",
82
82
  allow_unsafe_shell_command: true
83
83
  )
84
- # Run pip-compile a second time, without an update argument, to
85
- # ensure it resets the right comments.
86
- run_pip_compile_command(
87
- "pyenv exec pip-compile #{pip_compile_options(filename)} " \
88
- "#{filename}"
89
- )
90
84
  end
91
85
 
92
86
  # Remove any .python-version file before parsing the reqs
@@ -174,7 +168,7 @@ module Dependabot
174
168
  end
175
169
 
176
170
  def run_pip_compile_command(command, allow_unsafe_shell_command: false)
177
- run_command("pyenv local #{python_version}")
171
+ run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
178
172
  run_command(
179
173
  command,
180
174
  allow_unsafe_shell_command: allow_unsafe_shell_command
@@ -204,7 +198,7 @@ module Dependabot
204
198
  end
205
199
 
206
200
  # Overwrite the .python-version with updated content
207
- File.write(".python-version", python_version)
201
+ File.write(".python-version", Helpers.python_major_minor(python_version))
208
202
 
209
203
  setup_files.each do |file|
210
204
  path = file.name
@@ -219,15 +213,6 @@ module Dependabot
219
213
  end
220
214
  end
221
215
 
222
- def install_required_python
223
- return if run_command("pyenv versions").include?("#{python_version}\n")
224
-
225
- run_command("pyenv install -s #{python_version}")
226
- run_command("pyenv exec pip install --upgrade pip")
227
- run_command("pyenv exec pip install -r " \
228
- "#{NativeHelpers.python_requirements_path}")
229
- end
230
-
231
216
  def sanitized_setup_file_content(file)
232
217
  @sanitized_setup_file_content ||= {}
233
218
  return @sanitized_setup_file_content[file.name] if @sanitized_setup_file_content[file.name]
@@ -133,6 +133,7 @@ module Dependabot
133
133
  content = freeze_other_dependencies(content)
134
134
  content = freeze_dependencies_being_updated(content)
135
135
  content = add_private_sources(content)
136
+ content = update_python_requirement(content)
136
137
  content
137
138
  end
138
139
 
@@ -142,6 +143,12 @@ module Dependabot
142
143
  freeze_top_level_dependencies_except(dependencies)
143
144
  end
144
145
 
146
+ def update_python_requirement(pipfile_content)
147
+ PipfilePreparer.
148
+ new(pipfile_content: pipfile_content).
149
+ update_python_requirement(Helpers.python_major_minor(python_version))
150
+ end
151
+
145
152
  # rubocop:disable Metrics/PerceivedComplexity
146
153
  def freeze_dependencies_being_updated(pipfile_content)
147
154
  pipfile_object = TomlRB.parse(pipfile_content)
@@ -246,7 +253,7 @@ module Dependabot
246
253
  def run_command(command, env: {})
247
254
  start = Time.now
248
255
  command = SharedHelpers.escape_command(command)
249
- stdout, process = Open3.capture2e(env, command)
256
+ stdout, _, process = Open3.capture3(env, command)
250
257
  time_taken = Time.now - start
251
258
 
252
259
  # Raise an error with the output from the shell session if Pipenv
@@ -264,7 +271,7 @@ module Dependabot
264
271
  end
265
272
 
266
273
  def run_pipenv_command(command, env: pipenv_env_variables)
267
- run_command("pyenv local #{python_version}")
274
+ run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
268
275
  run_command(command, env: env)
269
276
  end
270
277
 
@@ -276,7 +283,7 @@ module Dependabot
276
283
  end
277
284
 
278
285
  # Overwrite the .python-version with updated content
279
- File.write(".python-version", python_version)
286
+ File.write(".python-version", Helpers.python_major_minor(python_version))
280
287
 
281
288
  setup_files.each do |file|
282
289
  path = file.name
@@ -302,11 +309,7 @@ module Dependabot
302
309
  nil
303
310
  end
304
311
 
305
- return if run_command("pyenv versions").include?("#{python_version}\n")
306
-
307
- requirements_path = NativeHelpers.python_requirements_path
308
- run_command("pyenv install -s #{python_version}")
309
- run_command("pyenv exec pip install -r #{requirements_path}")
312
+ Helpers.install_required_python(python_version)
310
313
  end
311
314
 
312
315
  def sanitized_setup_file_content(file)
@@ -70,10 +70,12 @@ module Dependabot
70
70
  pipfile_object = TomlRB.parse(pipfile_content)
71
71
 
72
72
  pipfile_object["requires"] ||= {}
73
- pipfile_object["requires"].delete("python_full_version")
74
- pipfile_object["requires"].delete("python_version")
75
- pipfile_object["requires"]["python_full_version"] = requirement
76
-
73
+ if pipfile_object.dig("requires", "python_full_version") && pipfile_object.dig("requires", "python_version")
74
+ pipfile_object["requires"].delete("python_full_version")
75
+ elsif pipfile_object.dig("requires", "python_full_version")
76
+ pipfile_object["requires"].delete("python_full_version")
77
+ pipfile_object["requires"]["python_version"] = requirement
78
+ end
77
79
  TomlRB.dump(pipfile_object)
78
80
  end
79
81
 
@@ -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"
@@ -105,6 +106,7 @@ module Dependabot
105
106
  content = sanitize(content)
106
107
  content = freeze_other_dependencies(content)
107
108
  content = freeze_dependencies_being_updated(content)
109
+ content = update_python_requirement(content)
108
110
  content
109
111
  end
110
112
  end
@@ -130,8 +132,14 @@ module Dependabot
130
132
  TomlRB.dump(pyproject_object)
131
133
  end
132
134
 
135
+ def update_python_requirement(pyproject_content)
136
+ PyprojectPreparer.
137
+ new(pyproject_content: pyproject_content).
138
+ update_python_requirement(Helpers.python_major_minor(python_version))
139
+ end
140
+
133
141
  def lock_declaration_to_new_version!(poetry_object, dep)
134
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
142
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
135
143
  names = poetry_object[type]&.keys || []
136
144
  pkg_name = names.find { |nm| normalise(nm) == dep.name }
137
145
  next unless pkg_name
@@ -170,11 +178,11 @@ module Dependabot
170
178
  write_temporary_dependency_files(pyproject_content)
171
179
  add_auth_env_vars
172
180
 
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}")
181
+ Helpers.install_required_python(python_version)
182
+
183
+ # use system git instead of the pure Python dulwich
184
+ unless python_version&.start_with?("3.6")
185
+ run_poetry_command("pyenv exec poetry config experimental.system-git-client true")
178
186
  end
179
187
 
180
188
  run_poetry_command(poetry_update_command)
@@ -220,7 +228,7 @@ module Dependabot
220
228
  end
221
229
 
222
230
  # Overwrite the .python-version with updated content
223
- File.write(".python-version", python_version) if python_version
231
+ File.write(".python-version", Helpers.python_major_minor(python_version)) if python_version
224
232
 
225
233
  # Overwrite the pyproject with updated content
226
234
  File.write("pyproject.toml", pyproject_content)