dependabot-python 0.214.0 → 0.216.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 940ed0c4abf7f4d3a496321e4898ba9c123091d6539f86ef54d7ee74dadf3344
4
- data.tar.gz: 802abe558f75bc2e98f1b88e93be85fc48f8b71774a1ff37b8ea16311381f587
3
+ metadata.gz: 62df822653f41408461f72892b9886776c124a195af5569c6af9e3bdf044e93d
4
+ data.tar.gz: 8cba64f792d1be98900fe5136d549230d5156d487d07150e0356a147402c5ca3
5
5
  SHA512:
6
- metadata.gz: 523ff39717afd9636f3d2f3115d6953817ab01585e2e218233eb0439a7cc9e5ac620c4b28d429b35256530a32bff6e71a73ffdfd72587ba53c8b10b6a3070175
7
- data.tar.gz: a3d05a60ad4d1b08dfe8fed7cdac12384aa49fbb3ad130008bf4748ab710df9b20a8297c99f66e33bd672717b52be32c17434b2ed253fe4bb6556cfc87941b05
6
+ metadata.gz: d8c60892487b523ca5879fd08f188dfabcd4d9c4ca17b3317accb3558a22a7731f53129616494230cf62138503e7aece5e9b304fe53bbd525e484197fe74f110
7
+ data.tar.gz: d7cfe8feb5f919ec01521377366c3716954f898361cc666a1df6384afc3389b9e6db9a92dd4c91cd25fa2e043cbd1aa5a71745d7c030a9627f7c434fcfee5731
data/helpers/build CHANGED
@@ -18,8 +18,8 @@ cp -r \
18
18
  "$install_dir"
19
19
 
20
20
  cd "$install_dir"
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"
21
+ PYENV_VERSION=3.11.3 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
22
+ PYENV_VERSION=3.10.11 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
23
+ PYENV_VERSION=3.9.16 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
24
+ PYENV_VERSION=3.8.16 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
25
+ PYENV_VERSION=3.7.16 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ if [ -z "$DEPENDABOT_NATIVE_HELPERS_PATH" ]; then
6
+ echo "Unable to build, DEPENDABOT_NATIVE_HELPERS_PATH is not set"
7
+ exit 1
8
+ fi
9
+
10
+ install_dir="$DEPENDABOT_NATIVE_HELPERS_PATH/python"
11
+ mkdir -p "$install_dir"
12
+
13
+ helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
14
+ cp -r \
15
+ "$helpers_dir/lib" \
16
+ "$helpers_dir/run.py" \
17
+ "$helpers_dir/requirements.txt" \
18
+ "$install_dir"
19
+
20
+ cd "$install_dir"
21
+ PYENV_VERSION=$1 pyenv exec pip --disable-pip-version-check install --use-pep517 -r "requirements.txt"
@@ -1,11 +1,10 @@
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
- flake8==5.0.4
1
+ pip>=21.3.1,<23.1.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.12.3 # Range maintains py36 support TODO: Review python 3.6 support in April 2023 (eol ubuntu 18.04)
4
3
  hashin==0.17.0
5
4
  pipenv==2022.4.8
6
5
  pipfile==0.0.2
7
- poetry>=1.1.15,<1.3.0
6
+ poetry>=1.1.15,<1.4.0
8
7
  wheel==0.37.1
9
8
 
10
9
  # Some dependencies will only install if Cython is present
11
- Cython==0.29.32
10
+ Cython==0.29.34
@@ -48,22 +48,13 @@ module Dependabot
48
48
 
49
49
  POETRY_DEPENDENCY_TYPES.each do |type|
50
50
  deps_hash = parsed_pyproject.dig("tool", "poetry", type) || {}
51
-
52
- deps_hash.each do |name, req|
53
- next if normalise(name) == "python"
54
-
55
- requirements = parse_requirements_from(req, type)
56
- next if requirements.empty?
57
-
58
- dependencies << Dependency.new(
59
- name: normalise(name),
60
- version: version_from_lockfile(name),
61
- requirements: requirements,
62
- package_manager: "pip"
63
- )
64
- end
51
+ dependencies += parse_poetry_dependencies(type, deps_hash)
65
52
  end
66
53
 
54
+ groups = parsed_pyproject.dig("tool", "poetry", "group") || {}
55
+ groups.each do |group, group_spec|
56
+ dependencies += parse_poetry_dependencies(group, group_spec["dependencies"])
57
+ end
67
58
  dependencies
68
59
  end
69
60
 
@@ -101,6 +92,25 @@ module Dependabot
101
92
  dependencies
102
93
  end
103
94
 
95
+ def parse_poetry_dependencies(type, deps_hash)
96
+ dependencies = Dependabot::FileParsers::Base::DependencySet.new
97
+
98
+ deps_hash.each do |name, req|
99
+ next if normalise(name) == "python"
100
+
101
+ requirements = parse_requirements_from(req, type)
102
+ next if requirements.empty?
103
+
104
+ dependencies << Dependency.new(
105
+ name: normalise(name),
106
+ version: version_from_lockfile(name),
107
+ requirements: requirements,
108
+ package_manager: "pip"
109
+ )
110
+ end
111
+ dependencies
112
+ end
113
+
104
114
  def normalised_name(name, extras)
105
115
  NameNormaliser.normalise_including_extras(name, extras)
106
116
  end
@@ -108,7 +118,7 @@ module Dependabot
108
118
  # @param req can be an Array, Hash or String that represents the constraints for a dependency
109
119
  def parse_requirements_from(req, type)
110
120
  [req].flatten.compact.filter_map do |requirement|
111
- next if requirement.is_a?(Hash) && (UNSUPPORTED_DEPENDENCY_TYPES & requirement.keys).any?
121
+ next if requirement.is_a?(Hash) && UNSUPPORTED_DEPENDENCY_TYPES.intersect?(requirement.keys)
112
122
 
113
123
  check_requirements(requirement)
114
124
 
@@ -7,7 +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
+ require "dependabot/python/language_version_manager"
11
11
  require "dependabot/python/native_helpers"
12
12
  require "dependabot/python/python_versions"
13
13
  require "dependabot/python/name_normaliser"
@@ -26,6 +26,7 @@ module Dependabot
26
26
  INCOMPATIBLE_VERSIONS_REGEX = /There are incompatible versions in the resolved dependencies:.*\z/m
27
27
  WARNINGS = /\s*# WARNING:.*\Z/m
28
28
  UNSAFE_NOTE = /\s*# The following packages are considered to be unsafe.*\Z/m
29
+ RESOLVER_REGEX = /(?<=--resolver=)(\w+)/
29
30
 
30
31
  attr_reader :dependencies, :dependency_files, :credentials
31
32
 
@@ -66,20 +67,30 @@ module Dependabot
66
67
  def compile_new_requirement_files
67
68
  SharedHelpers.in_a_temporary_directory do
68
69
  write_updated_dependency_files
69
- Helpers.install_required_python(python_version)
70
+ language_version_manager.install_required_python
70
71
 
71
72
  filenames_to_compile.each do |filename|
72
73
  # Shell out to pip-compile, generate a new set of requirements.
73
74
  # This is slow, as pip-compile needs to do installs.
75
+ options = pip_compile_options(filename)
76
+ options_fingerprint = pip_compile_options_fingerprint(options)
77
+
74
78
  name_part = "pyenv exec pip-compile " \
75
- "#{pip_compile_options(filename)} -P " \
79
+ "#{options} -P " \
76
80
  "#{dependency.name}"
81
+ fingerprint_name_part = "pyenv exec pip-compile " \
82
+ "#{options_fingerprint} -P " \
83
+ "<dependency_name>"
84
+
77
85
  version_part = "#{dependency.version} #{filename}"
86
+ fingerprint_version_part = "<dependency_version> <filename>"
87
+
78
88
  # Don't escape pyenv `dep-name==version` syntax
79
89
  run_pip_compile_command(
80
90
  "#{SharedHelpers.escape_command(name_part)}==" \
81
91
  "#{SharedHelpers.escape_command(version_part)}",
82
- allow_unsafe_shell_command: true
92
+ allow_unsafe_shell_command: true,
93
+ fingerprint: "#{fingerprint_name_part}==#{fingerprint_version_part}"
83
94
  )
84
95
  end
85
96
 
@@ -137,7 +148,7 @@ module Dependabot
137
148
  ).updated_dependency_files
138
149
  end
139
150
 
140
- def run_command(cmd, env: python_env, allow_unsafe_shell_command: false)
151
+ def run_command(cmd, env: python_env, allow_unsafe_shell_command: false, fingerprint:)
141
152
  start = Time.now
142
153
  command = if allow_unsafe_shell_command
143
154
  cmd
@@ -149,10 +160,6 @@ module Dependabot
149
160
 
150
161
  return stdout if process.success?
151
162
 
152
- handle_pip_errors(stdout, command, time_taken, process.to_s)
153
- end
154
-
155
- def handle_pip_errors(stdout, command, time_taken, exit_value)
156
163
  if stdout.match?(INCOMPATIBLE_VERSIONS_REGEX)
157
164
  raise DependencyFileNotResolvable, stdout.match(INCOMPATIBLE_VERSIONS_REGEX)
158
165
  end
@@ -161,17 +168,23 @@ module Dependabot
161
168
  message: stdout,
162
169
  error_context: {
163
170
  command: command,
171
+ fingerprint: fingerprint,
164
172
  time_taken: time_taken,
165
- process_exit_value: exit_value
173
+ process_exit_value: process.to_s
166
174
  }
167
175
  )
168
176
  end
169
177
 
170
- def run_pip_compile_command(command, allow_unsafe_shell_command: false)
171
- run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
178
+ def run_pip_compile_command(command, allow_unsafe_shell_command: false, fingerprint:)
179
+ run_command(
180
+ "pyenv local #{language_version_manager.python_major_minor}",
181
+ fingerprint: "pyenv local <python_major_minor>"
182
+ )
183
+
172
184
  run_command(
173
185
  command,
174
- allow_unsafe_shell_command: allow_unsafe_shell_command
186
+ allow_unsafe_shell_command: allow_unsafe_shell_command,
187
+ fingerprint: fingerprint
175
188
  )
176
189
  end
177
190
 
@@ -198,7 +211,7 @@ module Dependabot
198
211
  end
199
212
 
200
213
  # Overwrite the .python-version with updated content
201
- File.write(".python-version", Helpers.python_major_minor(python_version))
214
+ File.write(".python-version", language_version_manager.python_major_minor)
202
215
 
203
216
  setup_files.each do |file|
204
217
  path = file.name
@@ -391,6 +404,16 @@ module Dependabot
391
404
  current_separator || default_separator
392
405
  end
393
406
 
407
+ def pip_compile_options_fingerprint(options)
408
+ options.sub(
409
+ /--output-file=\S+/, "--output-file=<output_file>"
410
+ ).sub(
411
+ /--index-url=\S+/, "--index-url=<index_url>"
412
+ ).sub(
413
+ /--extra-index-url=\S+/, "--extra-index-url=<extra_index_url>"
414
+ )
415
+ end
416
+
394
417
  def pip_compile_options(filename)
395
418
  options = ["--build-isolation"]
396
419
  options += pip_compile_index_options
@@ -419,6 +442,10 @@ module Dependabot
419
442
 
420
443
  options << "--strip-extras" if requirements_file.content.include?("--strip-extras")
421
444
 
445
+ if (resolver = RESOLVER_REGEX.match(requirements_file.content))
446
+ options << "--resolver=#{resolver}"
447
+ end
448
+
422
449
  options
423
450
  end
424
451
 
@@ -495,9 +522,9 @@ module Dependabot
495
522
  while (remaining_filenames = filenames - ordered_filenames).any?
496
523
  ordered_filenames +=
497
524
  remaining_filenames.
498
- select do |fn|
525
+ reject do |fn|
499
526
  unupdated_reqs = requirement_map[fn] - ordered_filenames
500
- (unupdated_reqs & filenames).empty?
527
+ unupdated_reqs.intersect?(filenames)
501
528
  end
502
529
  end
503
530
 
@@ -523,41 +550,6 @@ module Dependabot
523
550
  end
524
551
  end
525
552
 
526
- def python_version
527
- @python_version ||=
528
- user_specified_python_version ||
529
- python_version_matching_imputed_requirements ||
530
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
531
- end
532
-
533
- def user_specified_python_version
534
- return unless python_requirement_parser.user_specified_requirements.any?
535
-
536
- user_specified_requirements =
537
- python_requirement_parser.user_specified_requirements.
538
- map { |r| Python::Requirement.requirements_array(r) }
539
- python_version_matching(user_specified_requirements)
540
- end
541
-
542
- def python_version_matching_imputed_requirements
543
- compiled_file_python_requirement_markers =
544
- python_requirement_parser.imputed_requirements.map do |r|
545
- Dependabot::Python::Requirement.new(r)
546
- end
547
- python_version_matching(compiled_file_python_requirement_markers)
548
- end
549
-
550
- def python_version_matching(requirements)
551
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.find do |version_string|
552
- version = Python::Version.new(version_string)
553
- requirements.all? do |req|
554
- next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
555
-
556
- req.satisfied_by?(version)
557
- end
558
- end
559
- end
560
-
561
553
  def python_requirement_parser
562
554
  @python_requirement_parser ||=
563
555
  FileParser::PythonRequirementParser.new(
@@ -565,8 +557,11 @@ module Dependabot
565
557
  )
566
558
  end
567
559
 
568
- def pre_installed_python?(version)
569
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
560
+ def language_version_manager
561
+ @language_version_manager ||=
562
+ LanguageVersionManager.new(
563
+ python_requirement_parser: python_requirement_parser
564
+ )
570
565
  end
571
566
 
572
567
  def setup_files
@@ -6,6 +6,7 @@ require "dependabot/dependency"
6
6
  require "dependabot/python/requirement_parser"
7
7
  require "dependabot/python/file_parser/python_requirement_parser"
8
8
  require "dependabot/python/file_updater"
9
+ require "dependabot/python/language_version_manager"
9
10
  require "dependabot/shared_helpers"
10
11
  require "dependabot/python/native_helpers"
11
12
  require "dependabot/python/name_normaliser"
@@ -146,7 +147,7 @@ module Dependabot
146
147
  def update_python_requirement(pipfile_content)
147
148
  PipfilePreparer.
148
149
  new(pipfile_content: pipfile_content).
149
- update_python_requirement(Helpers.python_major_minor(python_version))
150
+ update_python_requirement(language_version_manager.python_major_minor)
150
151
  end
151
152
 
152
153
  # rubocop:disable Metrics/PerceivedComplexity
@@ -198,10 +199,6 @@ module Dependabot
198
199
  write_temporary_dependency_files(prepared_pipfile_content)
199
200
  install_required_python
200
201
 
201
- # Initialize a git repo to appease pip-tools
202
- command = SharedHelpers.escape_command("git init")
203
- IO.popen(command, err: %i(child out)) if setup_files.any?
204
-
205
202
  run_pipenv_command(
206
203
  "pyenv exec pipenv lock"
207
204
  )
@@ -271,7 +268,7 @@ module Dependabot
271
268
  end
272
269
 
273
270
  def run_pipenv_command(command, env: pipenv_env_variables)
274
- run_command("pyenv local #{Helpers.python_major_minor(python_version)}")
271
+ run_command("pyenv local #{language_version_manager.python_major_minor}")
275
272
  run_command(command, env: env)
276
273
  end
277
274
 
@@ -283,7 +280,7 @@ module Dependabot
283
280
  end
284
281
 
285
282
  # Overwrite the .python-version with updated content
286
- File.write(".python-version", Helpers.python_major_minor(python_version))
283
+ File.write(".python-version", language_version_manager.python_major_minor)
287
284
 
288
285
  setup_files.each do |file|
289
286
  path = file.name
@@ -309,7 +306,7 @@ module Dependabot
309
306
  nil
310
307
  end
311
308
 
312
- Helpers.install_required_python(python_version)
309
+ language_version_manager.install_required_python
313
310
  end
314
311
 
315
312
  def sanitized_setup_file_content(file)
@@ -322,57 +319,6 @@ module Dependabot
322
319
  sanitized_content
323
320
  end
324
321
 
325
- def python_version
326
- @python_version ||= python_version_from_supported_versions
327
- end
328
-
329
- def python_version_from_supported_versions
330
- requirement_string =
331
- if @using_python_two then "2.7.*"
332
- elsif user_specified_python_requirement
333
- parts = user_specified_python_requirement.split(".")
334
- parts.fill("*", (parts.length)..2).join(".")
335
- else
336
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.first
337
- end
338
-
339
- # Ideally, the requirement is satisfied by a Python version we support
340
- requirement =
341
- Python::Requirement.requirements_array(requirement_string).first
342
- version =
343
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
344
- find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
345
- return version if version
346
-
347
- # If not, and changing the patch version would fix things, we do that
348
- # as the patch version is unlikely to affect resolution
349
- requirement =
350
- Python::Requirement.new(requirement_string.gsub(/\.\d+$/, ".*"))
351
- version =
352
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.
353
- find { |v| requirement.satisfied_by?(Python::Version.new(v)) }
354
- return version if version
355
-
356
- # Otherwise we have to raise, giving details of the Python versions
357
- # that Dependabot supports
358
- msg = "Dependabot detected the following Python requirement " \
359
- "for your project: '#{requirement_string}'.\n\nCurrently, the " \
360
- "following Python versions are supported in Dependabot: " \
361
- "#{PythonVersions::SUPPORTED_VERSIONS.join(', ')}."
362
- raise DependencyFileNotResolvable, msg
363
- end
364
-
365
- def user_specified_python_requirement
366
- python_requirement_parser.user_specified_requirements.first
367
- end
368
-
369
- def python_requirement_parser
370
- @python_requirement_parser ||=
371
- FileParser::PythonRequirementParser.new(
372
- dependency_files: dependency_files
373
- )
374
- end
375
-
376
322
  def setup_cfg(file)
377
323
  dependency_files.find do |f|
378
324
  f.name == file.name.sub(/\.py$/, ".cfg")
@@ -400,6 +346,20 @@ module Dependabot
400
346
  NameNormaliser.normalise(name)
401
347
  end
402
348
 
349
+ def python_requirement_parser
350
+ @python_requirement_parser ||=
351
+ FileParser::PythonRequirementParser.new(
352
+ dependency_files: dependency_files
353
+ )
354
+ end
355
+
356
+ def language_version_manager
357
+ @language_version_manager ||=
358
+ LanguageVersionManager.new(
359
+ python_requirement_parser: python_requirement_parser
360
+ )
361
+ end
362
+
403
363
  def parsed_lockfile
404
364
  @parsed_lockfile ||= JSON.parse(lockfile.content)
405
365
  end
@@ -21,7 +21,7 @@ module Dependabot
21
21
  pipfile_object = TomlRB.parse(pipfile_content)
22
22
 
23
23
  pipfile_object["source"] =
24
- pipfile_sources.reject { |h| h["url"].include?("${") } +
24
+ pipfile_sources.filter_map { |h| sub_auth_url(h, credentials) } +
25
25
  config_variable_sources(credentials)
26
26
 
27
27
  TomlRB.dump(pipfile_object)
@@ -114,6 +114,22 @@ module Dependabot
114
114
  map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
115
115
  end
116
116
 
117
+ def sub_auth_url(source, credentials)
118
+ if source["url"].include?("${")
119
+ base_url = source["url"].sub(/\${.*}@/, "")
120
+
121
+ source_cred = credentials.
122
+ select { |cred| cred["type"] == "python_index" }.
123
+ find { |c| c["index-url"].sub(/\${.*}@/, "") == base_url }
124
+
125
+ return nil if source_cred.nil?
126
+
127
+ source["url"] = AuthedUrlBuilder.authed_url(credential: source_cred)
128
+ end
129
+
130
+ source
131
+ end
132
+
117
133
  def config_variable_sources(credentials)
118
134
  @config_variable_sources ||=
119
135
  credentials.
@@ -4,7 +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
+ require "dependabot/python/language_version_manager"
8
8
  require "dependabot/python/version"
9
9
  require "dependabot/python/requirement"
10
10
  require "dependabot/python/python_versions"
@@ -75,10 +75,17 @@ module Dependabot
75
75
  find { |r| r[:file] == pyproject.name }.
76
76
  fetch(:requirement)
77
77
 
78
- updated_content =
79
- content.gsub(declaration_regex(dep)) do |line|
80
- line.gsub(old_req, updated_requirement)
81
- end
78
+ declaration_regex = declaration_regex(dep)
79
+ updated_content = if content.match?(declaration_regex)
80
+ content.gsub(declaration_regex(dep)) do |match|
81
+ match.gsub(old_req, updated_requirement)
82
+ end
83
+ else
84
+ content.gsub(table_declaration_regex(dep)) do |match|
85
+ match.gsub(/(\s*version\s*=\s*["'])#{Regexp.escape(old_req)}/,
86
+ '\1' + updated_requirement)
87
+ end
88
+ end
82
89
 
83
90
  raise "Content did not change!" if content == updated_content
84
91
 
@@ -135,7 +142,7 @@ module Dependabot
135
142
  def update_python_requirement(pyproject_content)
136
143
  PyprojectPreparer.
137
144
  new(pyproject_content: pyproject_content).
138
- update_python_requirement(Helpers.python_major_minor(python_version))
145
+ update_python_requirement(language_version_manager.python_major_minor)
139
146
  end
140
147
 
141
148
  def lock_declaration_to_new_version!(poetry_object, dep)
@@ -178,14 +185,14 @@ module Dependabot
178
185
  write_temporary_dependency_files(pyproject_content)
179
186
  add_auth_env_vars
180
187
 
181
- Helpers.install_required_python(python_version)
188
+ language_version_manager.install_required_python
182
189
 
183
190
  # use system git instead of the pure Python dulwich
184
- unless python_version&.start_with?("3.6")
191
+ unless language_version_manager.python_version&.start_with?("3.6")
185
192
  run_poetry_command("pyenv exec poetry config experimental.system-git-client true")
186
193
  end
187
194
 
188
- run_poetry_command(poetry_update_command)
195
+ run_poetry_update_command
189
196
 
190
197
  return File.read("poetry.lock") if File.exist?("poetry.lock")
191
198
 
@@ -196,11 +203,14 @@ module Dependabot
196
203
 
197
204
  # Using `--lock` avoids doing an install.
198
205
  # Using `--no-interaction` avoids asking for passwords.
199
- def poetry_update_command
200
- "pyenv exec poetry update #{dependency.name} --lock --no-interaction"
206
+ def run_poetry_update_command
207
+ run_poetry_command(
208
+ "pyenv exec poetry update #{dependency.name} --lock --no-interaction",
209
+ fingerprint: "pyenv exec poetry update <dependency_name> --lock --no-interaction"
210
+ )
201
211
  end
202
212
 
203
- def run_poetry_command(command)
213
+ def run_poetry_command(command, fingerprint: nil)
204
214
  start = Time.now
205
215
  command = SharedHelpers.escape_command(command)
206
216
  stdout, process = Open3.capture2e(command)
@@ -214,6 +224,7 @@ module Dependabot
214
224
  message: stdout,
215
225
  error_context: {
216
226
  command: command,
227
+ fingerprint: fingerprint,
217
228
  time_taken: time_taken,
218
229
  process_exit_value: process.to_s
219
230
  }
@@ -228,7 +239,7 @@ module Dependabot
228
239
  end
229
240
 
230
241
  # Overwrite the .python-version with updated content
231
- File.write(".python-version", Helpers.python_major_minor(python_version)) if python_version
242
+ File.write(".python-version", language_version_manager.python_major_minor)
232
243
 
233
244
  # Overwrite the pyproject with updated content
234
245
  File.write("pyproject.toml", pyproject_content)
@@ -240,29 +251,6 @@ module Dependabot
240
251
  add_auth_env_vars(credentials)
241
252
  end
242
253
 
243
- def python_version
244
- requirements = python_requirement_parser.user_specified_requirements
245
- requirements = requirements.
246
- map { |r| Python::Requirement.requirements_array(r) }
247
-
248
- PythonVersions::SUPPORTED_VERSIONS_TO_ITERATE.find do |version|
249
- requirements.all? do |reqs|
250
- reqs.any? { |r| r.satisfied_by?(Python::Version.new(version)) }
251
- end
252
- end
253
- end
254
-
255
- def python_requirement_parser
256
- @python_requirement_parser ||=
257
- FileParser::PythonRequirementParser.new(
258
- dependency_files: dependency_files
259
- )
260
- end
261
-
262
- def pre_installed_python?(version)
263
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
264
- end
265
-
266
254
  def pyproject_hash_for(pyproject_content)
267
255
  SharedHelpers.in_a_temporary_directory do |dir|
268
256
  SharedHelpers.with_git_configured(credentials: credentials) do
@@ -278,8 +266,15 @@ module Dependabot
278
266
  end
279
267
 
280
268
  def declaration_regex(dep)
281
- escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
282
- /(?:^\s*|["'])#{escaped_name}["']?\s*=.*$/i
269
+ /(?:^\s*|["'])#{escape(dep)}["']?\s*=.*$/i
270
+ end
271
+
272
+ def table_declaration_regex(dep)
273
+ /tool\.poetry\.[^\n]+\.#{escape(dep)}\]\n.*?\s*version\s* =.*?\n/m
274
+ end
275
+
276
+ def escape(dep)
277
+ Regexp.escape(dep.name).gsub("\\-", "[-_.]")
283
278
  end
284
279
 
285
280
  def file_changed?(file)
@@ -303,6 +298,20 @@ module Dependabot
303
298
  NameNormaliser.normalise(name)
304
299
  end
305
300
 
301
+ def python_requirement_parser
302
+ @python_requirement_parser ||=
303
+ FileParser::PythonRequirementParser.new(
304
+ dependency_files: dependency_files
305
+ )
306
+ end
307
+
308
+ def language_version_manager
309
+ @language_version_manager ||=
310
+ LanguageVersionManager.new(
311
+ python_requirement_parser: python_requirement_parser
312
+ )
313
+ end
314
+
306
315
  def pyproject
307
316
  @pyproject ||=
308
317
  dependency_files.find { |f| f.name == "pyproject.toml" }