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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48410b5617f95831c3ed5aba2c8e6cd9d48d7d9fc812c65e44eb853a9c46b336
4
- data.tar.gz: 24c798b98475ad41a3600f7b168921f04c63d02029c87b96410585d9ad88af66
3
+ metadata.gz: 6cb23890c79504e40e7e4962485003c76c07179574ac89b210b6529d15d2c216
4
+ data.tar.gz: b96523f9cf991cbffc38fc2831221c43450c74ef560e8e80ff2d2bbf73c889c4
5
5
  SHA512:
6
- metadata.gz: 3adfdad038c43c09ec2ce7cb3c04fa4606b4e538ffc418100bbe3bf3a6a4344e243f141a8c13b7215bd5e669cfc52e31b99b888791cbdf8ce9d75bc2d52cd013
7
- data.tar.gz: 2b4972ad44a0f39a9a3355630baf7f088b1d988224b66d782c65779ed3f9e4f98956190d1ddb3ec4889796edbebb983b281dad9f79e12a0574bc7da3a71bd17a
6
+ metadata.gz: 5beeac4ec63193ce095e6a5d7223c11e4e9c2ace55b3ef5c94f0011d8cb0c70fc7b364108b68cd68f656a5ed79d712b64eecbb75998714325a0f3b101169592c
7
+ data.tar.gz: 17ec5483c750fe4bc35490feebf418a8bd7eaf0eb2d14e2bcfc811e06f94c02f69eca71bafb589cb2d51b8a73155d3d358b97fcc6125a8fb7229b54f06a42fbe
data/helpers/build CHANGED
@@ -18,9 +18,4 @@ 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.10.7 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
@@ -11,11 +11,63 @@ 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 = parse_toml_section_pep621_dependencies(
53
+ pyproject_path,
54
+ project_toml['dependencies']
55
+ )
56
+
57
+ if 'optional-dependencies' in project_toml:
58
+ optional_dependencies_toml = project_toml['optional-dependencies']
59
+
60
+ for group in optional_dependencies_toml:
61
+ group_dependencies = parse_toml_section_pep621_dependencies(
62
+ pyproject_path,
63
+ optional_dependencies_toml[group]
64
+ )
65
+
66
+ dependencies.extend(group_dependencies)
67
+
68
+ return json.dumps({"result": dependencies})
69
+
70
+
19
71
  def parse_requirements(directory):
20
72
  # Parse the requirements.txt
21
73
  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.9.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
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,14 +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
14
+ CHILD_REQUIREMENT_REGEX = /^-r\s?(?<path>.*\.(?:txt|in))/
15
+ CONSTRAINT_REGEX = /^-c\s?(?<path>.*\.(?:txt|in))/
16
+ DEPENDENCY_TYPES = %w(packages dev-packages)
16
17
 
17
18
  def self.required_files_in?(filenames)
18
19
  return true if filenames.any? { |name| name.end_with?(".txt", ".in") }
@@ -23,7 +24,7 @@ module Dependabot
23
24
  # If this repo is using a Pipfile return true
24
25
  return true if filenames.include?("Pipfile")
25
26
 
26
- # If this repo is using Poetry return true
27
+ # If this repo is using pyproject.toml return true
27
28
  return true if filenames.include?("pyproject.toml")
28
29
 
29
30
  return true if filenames.include?("setup.py")
@@ -32,8 +33,8 @@ module Dependabot
32
33
  end
33
34
 
34
35
  def self.required_files_message
35
- "Repo must contain a requirements.txt, setup.py, setup.cfg, pyproject.toml, "\
36
- "or a Pipfile."
36
+ "Repo must contain a requirements.txt, setup.py, setup.cfg, pyproject.toml, " \
37
+ "or a Pipfile."
37
38
  end
38
39
 
39
40
  private
@@ -68,7 +69,7 @@ module Dependabot
68
69
  end
69
70
 
70
71
  def pyproject_files
71
- [pyproject, pyproject_lock, poetry_lock].compact
72
+ [pyproject, pyproject_lock, poetry_lock, pdm_lock].compact
72
73
  end
73
74
 
74
75
  def requirement_files
@@ -80,7 +81,12 @@ module Dependabot
80
81
  end
81
82
 
82
83
  def check_required_files_present
83
- 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
84
90
 
85
91
  path = Pathname.new(File.join(directory, "requirements.txt")).
86
92
  cleanpath.to_path
@@ -135,6 +141,10 @@ module Dependabot
135
141
  @poetry_lock ||= fetch_file_if_present("poetry.lock")
136
142
  end
137
143
 
144
+ def pdm_lock
145
+ @pdm_lock ||= fetch_file_if_present("pdm.lock")
146
+ end
147
+
138
148
  def requirements_txt_files
139
149
  req_txt_and_in_files.select { |f| f.name.end_with?(".txt") }
140
150
  end
@@ -168,7 +178,7 @@ module Dependabot
168
178
  repo_contents.
169
179
  select { |f| f.type == "file" }.
170
180
  select { |f| f.name.end_with?(".txt", ".in") }.
171
- reject { |f| f.size > 200_000 }.
181
+ reject { |f| f.size > 500_000 }.
172
182
  map { |f| fetch_file_from_host(f.name) }.
173
183
  select { |f| requirements_file?(f) }.
174
184
  each { |f| @req_txt_and_in_files << f }
@@ -188,7 +198,7 @@ module Dependabot
188
198
  repo_contents(dir: relative_reqs_dir).
189
199
  select { |f| f.type == "file" }.
190
200
  select { |f| f.name.end_with?(".txt", ".in") }.
191
- reject { |f| f.size > 200_000 }.
201
+ reject { |f| f.size > 500_000 }.
192
202
  map { |f| fetch_file_from_host("#{relative_reqs_dir}/#{f.name}") }.
193
203
  select { |f| requirements_file?(f) }
194
204
  end
@@ -290,8 +300,8 @@ module Dependabot
290
300
  fetch_submodules: true
291
301
  ).tap { |f| f.support_file = true }
292
302
  rescue Dependabot::DependencyFileNotFound
293
- # For Poetry projects attempt to fetch a pyproject.toml at the
294
- # 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
295
305
  # setup.py to be present, so if none can be found, simply return
296
306
  return [] unless allow_pyproject
297
307
 
@@ -372,7 +382,7 @@ module Dependabot
372
382
  return [] unless pipfile
373
383
 
374
384
  paths = []
375
- %w(packages dev-packages).each do |dep_type|
385
+ DEPENDENCY_TYPES.each do |dep_type|
376
386
  next unless parsed_pipfile[dep_type]
377
387
 
378
388
  parsed_pipfile[dep_type].each do |_, req|
@@ -389,7 +399,7 @@ module Dependabot
389
399
  return [] unless pyproject
390
400
 
391
401
  paths = []
392
- Dependabot::Python::FileParser::PoetryFilesParser::POETRY_DEPENDENCY_TYPES.each do |dep_type|
402
+ Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |dep_type|
393
403
  next unless parsed_pyproject.dig("tool", "poetry", dep_type)
394
404
 
395
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,9 +67,47 @@ 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
- [req].flatten.compact.map do |requirement|
110
+ [req].flatten.compact.filter_map do |requirement|
65
111
  next if requirement.is_a?(Hash) && (UNSUPPORTED_DEPENDENCY_TYPES & requirement.keys).any?
66
112
 
67
113
  check_requirements(requirement)
@@ -72,7 +118,19 @@ module Dependabot
72
118
  source: nil,
73
119
  groups: [type]
74
120
  }
75
- end.compact
121
+ end
122
+ end
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
+ end
131
+
132
+ def using_pdm?
133
+ using_pep621? && pdm_lock
76
134
  end
77
135
 
78
136
  # Create a DependencySet where each element has no requirement. Any
@@ -81,8 +139,9 @@ module Dependabot
81
139
  def lockfile_dependencies
82
140
  dependencies = Dependabot::FileParsers::Base::DependencySet.new
83
141
 
142
+ source_types = %w(directory git url)
84
143
  parsed_lockfile.fetch("package", []).each do |details|
85
- next if %w(directory git url).include?(details.dig("source", "type"))
144
+ next if source_types.include?(details.dig("source", "type"))
86
145
 
87
146
  dependencies <<
88
147
  Dependency.new(
@@ -145,6 +204,24 @@ module Dependabot
145
204
  poetry_lock || pyproject_lock
146
205
  end
147
206
 
207
+ def parsed_pep621_dependencies
208
+ SharedHelpers.in_a_temporary_directory do
209
+ write_temporary_pyproject
210
+
211
+ SharedHelpers.run_helper_subprocess(
212
+ command: "pyenv exec python #{NativeHelpers.python_helper_path}",
213
+ function: "parse_pep621_dependencies",
214
+ args: [pyproject.name]
215
+ )
216
+ end
217
+ end
218
+
219
+ def write_temporary_pyproject
220
+ path = pyproject.name
221
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
222
+ File.write(path, pyproject.content)
223
+ end
224
+
148
225
  def parsed_lockfile
149
226
  return parsed_poetry_lock if poetry_lock
150
227
  return parsed_pyproject_lock if pyproject_lock
@@ -159,6 +236,11 @@ module Dependabot
159
236
  @poetry_lock ||=
160
237
  dependency_files.find { |f| f.name == "poetry.lock" }
161
238
  end
239
+
240
+ def pdm_lock
241
+ @pdm_lock ||=
242
+ dependency_files.find { |f| f.name == "pdm.lock" }
243
+ end
162
244
  end
163
245
  end
164
246
  end
@@ -33,8 +33,7 @@ module Dependabot
33
33
  requirement_files.flat_map do |file|
34
34
  file.content.lines.
35
35
  select { |l| l.include?(";") && l.include?("python") }.
36
- map { |l| l.match(/python_version(?<req>.*?["'].*?['"])/) }.
37
- compact.
36
+ filter_map { |l| l.match(/python_version(?<req>.*?["'].*?['"])/) }.
38
37
  map { |re| re.named_captures.fetch("req").gsub(/['"]/, "") }.
39
38
  select { |r| valid_requirement?(r) }
40
39
  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
 
@@ -128,7 +128,7 @@ module Dependabot
128
128
  tests_require = get_regexed_req_array(TESTS_REQUIRE_REGEX)
129
129
  extras_require = get_regexed_req_dict(EXTRAS_REQUIRE_REGEX)
130
130
 
131
- tmp = "from setuptools import setup\n\n"\
131
+ tmp = "from setuptools import setup\n\n" \
132
132
  "setup(name=\"sanitized-package\",version=\"0.0.1\","
133
133
 
134
134
  tmp += "install_requires=#{install_requires}," if install_requires
@@ -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,33 +66,27 @@ 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.
73
73
  # This is slow, as pip-compile needs to do installs.
74
- name_part = "pyenv exec pip-compile "\
75
- "#{pip_compile_options(filename)} -P "\
74
+ name_part = "pyenv exec pip-compile " \
75
+ "#{pip_compile_options(filename)} -P " \
76
76
  "#{dependency.name}"
77
77
  version_part = "#{dependency.version} #{filename}"
78
78
  # Don't escape pyenv `dep-name==version` syntax
79
79
  run_pip_compile_command(
80
- "#{SharedHelpers.escape_command(name_part)}=="\
80
+ "#{SharedHelpers.escape_command(name_part)}==" \
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
93
87
  FileUtils.remove_entry(".python-version", true)
94
88
 
95
- dependency_files.map do |file|
89
+ dependency_files.filter_map do |file|
96
90
  next unless file.name.end_with?(".txt")
97
91
 
98
92
  updated_content = File.read(file.name)
@@ -102,12 +96,12 @@ module Dependabot
102
96
  next if updated_content == file.content
103
97
 
104
98
  file.dup.tap { |f| f.content = updated_content }
105
- end.compact
99
+ end
106
100
  end
107
101
  end
108
102
 
109
103
  def update_manifest_files
110
- dependency_files.map do |file|
104
+ dependency_files.filter_map do |file|
111
105
  next unless file.name.end_with?(".in")
112
106
 
113
107
  file = file.dup
@@ -116,7 +110,7 @@ module Dependabot
116
110
 
117
111
  file.content = updated_content
118
112
  file
119
- end.compact
113
+ end
120
114
  end
121
115
 
122
116
  def update_uncompiled_files(updated_files)
@@ -132,7 +126,7 @@ module Dependabot
132
126
  reject { |file| updated_filenames.include?(file.name) }
133
127
 
134
128
  args = dependency.to_h
135
- args = args.keys.map { |k| [k.to_sym, args[k]] }.to_h
129
+ args = args.keys.to_h { |k| [k.to_sym, args[k]] }
136
130
  args[:requirements] = new_reqs
137
131
  args[:previous_requirements] = old_reqs
138
132
 
@@ -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]
@@ -352,7 +337,7 @@ module Dependabot
352
337
  end
353
338
 
354
339
  def deps_to_augment_hashes_for(updated_content, original_content)
355
- regex = /^#{RequirementParser::INSTALL_REQ_WITH_REQUIREMENT}/
340
+ regex = /^#{RequirementParser::INSTALL_REQ_WITH_REQUIREMENT}/o
356
341
 
357
342
  new_matches = []
358
343
  updated_content.scan(regex) { new_matches << Regexp.last_match }
@@ -18,6 +18,8 @@ module Dependabot
18
18
  require_relative "pipfile_manifest_updater"
19
19
  require_relative "setup_file_sanitizer"
20
20
 
21
+ DEPENDENCY_TYPES = %w(packages dev-packages).freeze
22
+
21
23
  attr_reader :dependencies, :dependency_files, :credentials
22
24
 
23
25
  def initialize(dependencies:, dependency_files:, credentials:)
@@ -145,7 +147,7 @@ module Dependabot
145
147
  pipfile_object = TomlRB.parse(pipfile_content)
146
148
 
147
149
  dependencies.each do |dep|
148
- %w(packages dev-packages).each do |type|
150
+ DEPENDENCY_TYPES.each do |type|
149
151
  names = pipfile_object[type]&.keys || []
150
152
  pkg_name = names.find { |nm| normalise(nm) == dep.name }
151
153
  next unless pkg_name || subdep_type?(type)
@@ -300,11 +302,7 @@ module Dependabot
300
302
  nil
301
303
  end
302
304
 
303
- return if run_command("pyenv versions").include?("#{python_version}\n")
304
-
305
- requirements_path = NativeHelpers.python_requirements_path
306
- run_command("pyenv install -s #{python_version}")
307
- run_command("pyenv exec pip install -r #{requirements_path}")
305
+ Helpers.install_required_python(python_version)
308
306
  end
309
307
 
310
308
  def sanitized_setup_file_content(file)
@@ -350,9 +348,9 @@ module Dependabot
350
348
 
351
349
  # Otherwise we have to raise, giving details of the Python versions
352
350
  # that Dependabot supports
353
- msg = "Dependabot detected the following Python requirement "\
354
- "for your project: '#{requirement_string}'.\n\nCurrently, the "\
355
- "following Python versions are supported in Dependabot: "\
351
+ msg = "Dependabot detected the following Python requirement " \
352
+ "for your project: '#{requirement_string}'.\n\nCurrently, the " \
353
+ "following Python versions are supported in Dependabot: " \
356
354
  "#{PythonVersions::SUPPORTED_VERSIONS.join(', ')}."
357
355
  raise DependencyFileNotResolvable, msg
358
356
  end