dependabot-python 0.212.0 → 0.214.0

Sign up to get free protection for your applications and to get access to all the features.
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)