dependabot-python 0.214.0 → 0.216.0

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