dependabot-python 0.212.0 → 0.213.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 +4 -4
- data/helpers/build +1 -6
- data/helpers/lib/parser.py +52 -0
- data/helpers/requirements.txt +3 -3
- data/helpers/run.py +2 -0
- data/lib/dependabot/python/file_fetcher.rb +21 -12
- data/lib/dependabot/python/file_parser/{poetry_files_parser.rb → pyproject_files_parser.rb} +83 -2
- data/lib/dependabot/python/file_parser/setup_file_parser.rb +4 -4
- data/lib/dependabot/python/file_parser.rb +5 -29
- data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +5 -20
- data/lib/dependabot/python/file_updater/pipfile_file_updater.rb +1 -5
- data/lib/dependabot/python/file_updater/poetry_file_updater.rb +7 -6
- data/lib/dependabot/python/file_updater/pyproject_preparer.rb +1 -1
- data/lib/dependabot/python/file_updater.rb +14 -1
- data/lib/dependabot/python/helpers.rb +20 -0
- data/lib/dependabot/python/metadata_finder.rb +2 -0
- data/lib/dependabot/python/python_versions.rb +5 -5
- data/lib/dependabot/python/requirement.rb +7 -4
- data/lib/dependabot/python/requirement_parser.rb +20 -23
- data/lib/dependabot/python/update_checker/index_finder.rb +1 -1
- data/lib/dependabot/python/update_checker/pip_compile_version_resolver.rb +17 -19
- data/lib/dependabot/python/update_checker/pipenv_version_resolver.rb +7 -16
- data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +13 -11
- data/lib/dependabot/python/update_checker/requirements_updater.rb +17 -4
- data/lib/dependabot/python/update_checker.rb +82 -25
- data/lib/dependabot/python/version.rb +2 -2
- metadata +15 -56
@@ -6,7 +6,7 @@ require "dependabot/python/version"
|
|
6
6
|
module Dependabot
|
7
7
|
module Python
|
8
8
|
class Requirement < Gem::Requirement
|
9
|
-
OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s
|
9
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s*\|+/
|
10
10
|
|
11
11
|
# Add equality and arbitrary-equality matchers
|
12
12
|
OPS = OPS.merge(
|
@@ -19,8 +19,8 @@ module Dependabot
|
|
19
19
|
version_pattern = Python::Version::VERSION_PATTERN
|
20
20
|
|
21
21
|
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*"
|
22
|
-
PATTERN = /\A#{PATTERN_RAW}\z
|
23
|
-
PARENS_PATTERN = /\A\(([^)]+)\)\z
|
22
|
+
PATTERN = /\A#{PATTERN_RAW}\z/
|
23
|
+
PARENS_PATTERN = /\A\(([^)]+)\)\z/
|
24
24
|
|
25
25
|
def self.parse(obj)
|
26
26
|
return ["=", Python::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
@@ -60,6 +60,9 @@ module Dependabot
|
|
60
60
|
requirements = requirements.flatten.flat_map do |req_string|
|
61
61
|
next if req_string.nil?
|
62
62
|
|
63
|
+
# Standard python doesn't support whitespace in requirements, but Poetry does.
|
64
|
+
req_string = req_string.gsub(/(\d +)([<=>])/, '\1,\2')
|
65
|
+
|
63
66
|
req_string.split(",").map(&:strip).map do |r|
|
64
67
|
convert_python_constraint_to_ruby_constraint(r)
|
65
68
|
end
|
@@ -87,7 +90,7 @@ module Dependabot
|
|
87
90
|
return nil if req_string == "*"
|
88
91
|
|
89
92
|
req_string = req_string.gsub("~=", "~>")
|
90
|
-
req_string = req_string.gsub(/(?<=\d)[<=>]
|
93
|
+
req_string = req_string.gsub(/(?<=\d)[<=>].*\Z/, "")
|
91
94
|
|
92
95
|
if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
|
93
96
|
elsif req_string.start_with?("^") then convert_caret_req(req_string)
|
@@ -3,29 +3,26 @@
|
|
3
3
|
module Dependabot
|
4
4
|
module Python
|
5
5
|
class RequirementParser
|
6
|
-
NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])
|
7
|
-
EXTRA = /[a-zA-Z0-9\-_\.]
|
8
|
-
COMPARISON =
|
9
|
-
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)
|
10
|
-
|
11
|
-
REQUIREMENT =
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
%r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}.freeze
|
19
|
-
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/.freeze
|
6
|
+
NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
|
7
|
+
EXTRA = /[a-zA-Z0-9\-_\.]+/
|
8
|
+
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
|
9
|
+
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
|
10
|
+
|
11
|
+
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
|
12
|
+
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|$)/
|
13
|
+
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
|
14
|
+
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
|
15
|
+
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
|
16
|
+
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
|
17
|
+
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
|
20
18
|
ENV_VAR =
|
21
19
|
/python_version|python_full_version|os_name|sys_platform|
|
22
20
|
platform_release|platform_system|platform_version|platform_machine|
|
23
21
|
platform_python_implementation|implementation_name|
|
24
|
-
implementation_version
|
25
|
-
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})
|
26
|
-
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}
|
27
|
-
MARKER_EXPR =
|
28
|
-
/(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/.freeze
|
22
|
+
implementation_version/
|
23
|
+
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
|
24
|
+
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
|
25
|
+
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
|
29
26
|
|
30
27
|
INSTALL_REQ_WITH_REQUIREMENT =
|
31
28
|
/\s*\\?\s*(?<name>#{NAME})
|
@@ -34,7 +31,7 @@ module Dependabot
|
|
34
31
|
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
35
32
|
\s*\\?\s*(?<hashes>#{HASHES})?
|
36
33
|
\s*#*\s*(?<comment>.+)?
|
37
|
-
/x
|
34
|
+
/x
|
38
35
|
|
39
36
|
INSTALL_REQ_WITHOUT_REQUIREMENT =
|
40
37
|
/^\s*\\?\s*(?<name>#{NAME})
|
@@ -42,7 +39,7 @@ module Dependabot
|
|
42
39
|
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
43
40
|
\s*\\?\s*(?<hashes>#{HASHES})?
|
44
41
|
\s*#*\s*(?<comment>.+)?$
|
45
|
-
/x
|
42
|
+
/x
|
46
43
|
|
47
44
|
VALID_REQ_TXT_REQUIREMENT =
|
48
45
|
/^\s*\\?\s*(?<name>#{NAME})
|
@@ -51,12 +48,12 @@ module Dependabot
|
|
51
48
|
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
52
49
|
\s*\\?\s*(?<hashes>#{HASHES})?
|
53
50
|
\s*(\#+\s*(?<comment>.*))?$
|
54
|
-
/x
|
51
|
+
/x
|
55
52
|
|
56
53
|
NAME_WITH_EXTRAS =
|
57
54
|
/\s*\\?\s*(?<name>#{NAME})
|
58
55
|
(\s*\\?\s*\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
59
|
-
/x
|
56
|
+
/x
|
60
57
|
end
|
61
58
|
end
|
62
59
|
end
|
@@ -9,7 +9,7 @@ module Dependabot
|
|
9
9
|
class UpdateChecker
|
10
10
|
class IndexFinder
|
11
11
|
PYPI_BASE_URL = "https://pypi.org/simple/"
|
12
|
-
ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}
|
12
|
+
ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
|
13
13
|
|
14
14
|
def initialize(dependency_files:, credentials:)
|
15
15
|
@dependency_files = dependency_files
|
@@ -11,6 +11,7 @@ require "dependabot/python/file_updater/requirement_replacer"
|
|
11
11
|
require "dependabot/python/file_updater/setup_file_sanitizer"
|
12
12
|
require "dependabot/python/version"
|
13
13
|
require "dependabot/shared_helpers"
|
14
|
+
require "dependabot/python/helpers"
|
14
15
|
require "dependabot/python/native_helpers"
|
15
16
|
require "dependabot/python/python_versions"
|
16
17
|
require "dependabot/python/name_normaliser"
|
@@ -24,16 +25,14 @@ module Dependabot
|
|
24
25
|
# - Run `pip-compile` and see what the result is
|
25
26
|
# rubocop:disable Metrics/ClassLength
|
26
27
|
class PipCompileVersionResolver
|
27
|
-
GIT_DEPENDENCY_UNREACHABLE_REGEX =
|
28
|
-
|
29
|
-
GIT_REFERENCE_NOT_FOUND_REGEX =
|
30
|
-
/Did not find branch or tag '(?<tag>[^\n"]+)'/m.freeze
|
28
|
+
GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone --filter=blob:none --quiet (?<url>[^\s]+).* /
|
29
|
+
GIT_REFERENCE_NOT_FOUND_REGEX = /Did not find branch or tag '(?<tag>[^\n"]+)'/m
|
31
30
|
NATIVE_COMPILATION_ERROR =
|
32
31
|
"pip._internal.exceptions.InstallationSubprocessError: Command errored out with exit status 1:"
|
33
32
|
# See https://packaging.python.org/en/latest/tutorials/packaging-projects/#configuring-metadata
|
34
|
-
PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]
|
33
|
+
PYTHON_PACKAGE_NAME_REGEX = /[A-Za-z0-9_\-]+/
|
35
34
|
RESOLUTION_IMPOSSIBLE_ERROR = "ResolutionImpossible"
|
36
|
-
ERROR_REGEX = /(?<=ERROR\:\W)
|
35
|
+
ERROR_REGEX = /(?<=ERROR\:\W).*$/
|
37
36
|
|
38
37
|
attr_reader :dependency, :dependency_files, :credentials
|
39
38
|
|
@@ -72,7 +71,7 @@ module Dependabot
|
|
72
71
|
SharedHelpers.in_a_temporary_directory do
|
73
72
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
74
73
|
write_temporary_dependency_files(updated_req: requirement)
|
75
|
-
install_required_python
|
74
|
+
Helpers.install_required_python(python_version)
|
76
75
|
|
77
76
|
filenames_to_compile.each do |filename|
|
78
77
|
# Shell out to pip-compile.
|
@@ -80,9 +79,17 @@ module Dependabot
|
|
80
79
|
run_pip_compile_command(
|
81
80
|
"pyenv exec pip-compile -v #{pip_compile_options(filename)} -P #{dependency.name} #{filename}"
|
82
81
|
)
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
|
83
|
+
next if dependency.top_level?
|
84
|
+
|
85
|
+
# Run pip-compile a second time for transient dependencies
|
86
|
+
# to make sure we do not update dependencies that are
|
87
|
+
# superfluous. pip-compile does not detect these when
|
88
|
+
# updating a specific dependency with the -P option.
|
89
|
+
# Running pip-compile a second time will automatically remove
|
90
|
+
# superfluous dependencies. Dependabot then marks those with
|
91
|
+
# update_not_possible.
|
92
|
+
write_original_manifest_files
|
86
93
|
run_pip_compile_command(
|
87
94
|
"pyenv exec pip-compile #{pip_compile_options(filename)} #{filename}"
|
88
95
|
)
|
@@ -313,15 +320,6 @@ module Dependabot
|
|
313
320
|
end
|
314
321
|
end
|
315
322
|
|
316
|
-
def install_required_python
|
317
|
-
return if run_command("pyenv versions").include?("#{python_version}\n")
|
318
|
-
|
319
|
-
run_command("pyenv install -s #{python_version}")
|
320
|
-
run_command("pyenv exec pip install --upgrade pip")
|
321
|
-
run_command("pyenv exec pip install -r" \
|
322
|
-
"#{NativeHelpers.python_requirements_path}")
|
323
|
-
end
|
324
|
-
|
325
323
|
def sanitized_setup_file_content(file)
|
326
324
|
@sanitized_setup_file_content ||= {}
|
327
325
|
return @sanitized_setup_file_content[file.name] if @sanitized_setup_file_content[file.name]
|
@@ -30,21 +30,18 @@ module Dependabot
|
|
30
30
|
# still better than nothing, though.
|
31
31
|
class PipenvVersionResolver
|
32
32
|
# rubocop:disable Layout/LineLength
|
33
|
-
GIT_DEPENDENCY_UNREACHABLE_REGEX =
|
34
|
-
|
35
|
-
GIT_REFERENCE_NOT_FOUND_REGEX =
|
36
|
-
%r{git checkout -q (?<tag>[^\n"]+)\n?[^\n]*/(?<name>.*?)(\\n'\]|$)}m.
|
37
|
-
freeze
|
33
|
+
GIT_DEPENDENCY_UNREACHABLE_REGEX = /git clone -q (?<url>[^\s]+).* /
|
34
|
+
GIT_REFERENCE_NOT_FOUND_REGEX = %r{git checkout -q (?<tag>[^\n"]+)\n?[^\n]*/(?<name>.*?)(\\n'\]|$)}m
|
38
35
|
PIPENV_INSTALLATION_ERROR = "pipenv.patched.notpip._internal.exceptions.InstallationError: Command errored out" \
|
39
36
|
" with exit status 1: python setup.py egg_info"
|
40
37
|
TRACEBACK = "Traceback (most recent call last):"
|
41
38
|
PIPENV_INSTALLATION_ERROR_REGEX =
|
42
|
-
/#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}
|
43
|
-
|
39
|
+
/#{Regexp.quote(TRACEBACK)}[\s\S]*^\s+import\s(?<name>.+)[\s\S]*^#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}/
|
40
|
+
|
44
41
|
UNSUPPORTED_DEPS = %w(pyobjc).freeze
|
45
42
|
UNSUPPORTED_DEP_REGEX =
|
46
|
-
/Could not find a version that satisfies the requirement.*(?:#{UNSUPPORTED_DEPS.join("|")})
|
47
|
-
PIPENV_RANGE_WARNING = /Warning:\sPython\s[<>].* was not found
|
43
|
+
/Could not find a version that satisfies the requirement.*(?:#{UNSUPPORTED_DEPS.join("|")})/
|
44
|
+
PIPENV_RANGE_WARNING = /Warning:\sPython\s[<>].* was not found/
|
48
45
|
# rubocop:enable Layout/LineLength
|
49
46
|
|
50
47
|
DEPENDENCY_TYPES = %w(packages dev-packages).freeze
|
@@ -323,13 +320,7 @@ module Dependabot
|
|
323
320
|
nil
|
324
321
|
end
|
325
322
|
|
326
|
-
|
327
|
-
|
328
|
-
requirements_path = NativeHelpers.python_requirements_path
|
329
|
-
run_command("pyenv install -s #{python_version}")
|
330
|
-
run_command("pyenv exec pip install --upgrade pip")
|
331
|
-
run_command("pyenv exec pip install -r " \
|
332
|
-
"#{requirements_path}")
|
323
|
+
Helpers.install_required_python(python_version)
|
333
324
|
end
|
334
325
|
|
335
326
|
def sanitized_setup_file_content(file)
|
@@ -28,10 +28,14 @@ module Dependabot
|
|
28
28
|
'checkout',
|
29
29
|
'(?<tag>.+?)'
|
30
30
|
|
|
31
|
+
Failed to checkout
|
32
|
+
(?<tag>.+?)
|
33
|
+
(?<url>.+?).git at '(?<tag>.+?)'
|
34
|
+
|
|
31
35
|
...Failedtoclone
|
32
36
|
(?<url>.+?).gitat'(?<tag>.+?)',
|
33
37
|
verifyrefexistsonremote)
|
34
|
-
/x
|
38
|
+
/x # TODO: remove the first clause and | when py3.6 support is EoL
|
35
39
|
GIT_DEPENDENCY_UNREACHABLE_REGEX = /
|
36
40
|
(?:'\['git',
|
37
41
|
\s+'clone',
|
@@ -43,7 +47,7 @@ module Dependabot
|
|
43
47
|
\s+Failed\sto\sclone
|
44
48
|
\s+(?<url>.+?),
|
45
49
|
\s+check\syour\sgit\sconfiguration)
|
46
|
-
/mx
|
50
|
+
/mx # TODO: remove the first clause and | when py3.6 support is EoL
|
47
51
|
|
48
52
|
attr_reader :dependency, :dependency_files, :credentials
|
49
53
|
|
@@ -88,13 +92,11 @@ module Dependabot
|
|
88
92
|
write_temporary_dependency_files(updated_req: requirement)
|
89
93
|
add_auth_env_vars
|
90
94
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
"#{NativeHelpers.python_requirements_path}"
|
97
|
-
)
|
95
|
+
Helpers.install_required_python(python_version)
|
96
|
+
|
97
|
+
# use system git instead of the pure Python dulwich
|
98
|
+
unless python_version&.start_with?("3.6")
|
99
|
+
run_poetry_command("pyenv exec poetry config experimental.system-git-client true")
|
98
100
|
end
|
99
101
|
|
100
102
|
# Shell out to Poetry, which handles everything for us.
|
@@ -282,7 +284,7 @@ module Dependabot
|
|
282
284
|
pyproject_object = TomlRB.parse(pyproject_content)
|
283
285
|
poetry_object = pyproject_object.dig("tool", "poetry")
|
284
286
|
|
285
|
-
Dependabot::Python::FileParser::
|
287
|
+
Dependabot::Python::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |type|
|
286
288
|
names = poetry_object[type]&.keys || []
|
287
289
|
pkg_name = names.find { |nm| normalise(nm) == dependency.name }
|
288
290
|
next unless pkg_name
|
@@ -335,7 +337,7 @@ module Dependabot
|
|
335
337
|
stdout, process = Open3.capture2e(command)
|
336
338
|
time_taken = Time.now - start
|
337
339
|
|
338
|
-
# Raise an error with the output from the shell session if
|
340
|
+
# Raise an error with the output from the shell session if poetry
|
339
341
|
# returns a non-zero status
|
340
342
|
return if process.success?
|
341
343
|
|
@@ -9,8 +9,8 @@ module Dependabot
|
|
9
9
|
module Python
|
10
10
|
class UpdateChecker
|
11
11
|
class RequirementsUpdater
|
12
|
-
PYPROJECT_OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s
|
13
|
-
PYPROJECT_SEPARATOR = /#{PYPROJECT_OR_SEPARATOR}
|
12
|
+
PYPROJECT_OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/
|
13
|
+
PYPROJECT_SEPARATOR = /#{PYPROJECT_OR_SEPARATOR}|,/
|
14
14
|
|
15
15
|
class UnfixableRequirement < StandardError; end
|
16
16
|
|
@@ -175,11 +175,25 @@ module Dependabot
|
|
175
175
|
end
|
176
176
|
# rubocop:enable Metrics/PerceivedComplexity
|
177
177
|
|
178
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
179
178
|
def updated_requirement(req)
|
180
179
|
return req unless latest_resolvable_version
|
181
180
|
return req unless req.fetch(:requirement)
|
182
181
|
|
182
|
+
case update_strategy
|
183
|
+
when :bump_versions
|
184
|
+
update_requirement(req)
|
185
|
+
when :bump_versions_if_necessary
|
186
|
+
update_requirement_if_needed(req)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def update_requirement_if_needed(req)
|
191
|
+
return req if new_version_satisfies?(req)
|
192
|
+
|
193
|
+
update_requirement(req)
|
194
|
+
end
|
195
|
+
|
196
|
+
def update_requirement(req)
|
183
197
|
requirement_strings = req[:requirement].split(",").map(&:strip)
|
184
198
|
|
185
199
|
new_requirement =
|
@@ -197,7 +211,6 @@ module Dependabot
|
|
197
211
|
rescue UnfixableRequirement
|
198
212
|
req.merge(requirement: :unfixable)
|
199
213
|
end
|
200
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
201
214
|
|
202
215
|
def new_version_satisfies?(req)
|
203
216
|
requirement_class.
|
@@ -26,7 +26,7 @@ module Dependabot
|
|
26
26
|
https://pypi.python.org/simple/
|
27
27
|
https://pypi.org/simple/
|
28
28
|
).freeze
|
29
|
-
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)
|
29
|
+
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
|
30
30
|
|
31
31
|
def latest_version
|
32
32
|
@latest_version ||= fetch_latest_version
|
@@ -89,7 +89,7 @@ module Dependabot
|
|
89
89
|
|
90
90
|
def updated_requirements
|
91
91
|
RequirementsUpdater.new(
|
92
|
-
requirements:
|
92
|
+
requirements: requirements,
|
93
93
|
latest_resolvable_version: preferred_resolvable_version&.to_s,
|
94
94
|
update_strategy: requirements_update_strategy,
|
95
95
|
has_lockfile: !(pipfile_lock || poetry_lock || pyproject_lock).nil?
|
@@ -100,8 +100,8 @@ module Dependabot
|
|
100
100
|
# If passed in as an option (in the base class) honour that option
|
101
101
|
return @requirements_update_strategy.to_sym if @requirements_update_strategy
|
102
102
|
|
103
|
-
# Otherwise, check if this is a
|
104
|
-
|
103
|
+
# Otherwise, check if this is a library or not
|
104
|
+
library? ? :widen_ranges : :bump_versions
|
105
105
|
end
|
106
106
|
|
107
107
|
private
|
@@ -115,6 +115,17 @@ module Dependabot
|
|
115
115
|
raise NotImplementedError
|
116
116
|
end
|
117
117
|
|
118
|
+
def preferred_version_resolvable_with_unlock?
|
119
|
+
# Our requirements file updater doesn't currently support widening
|
120
|
+
# ranges, so avoid updating this dependency if widening ranges has been
|
121
|
+
# required and the dependency is present on a requirements file.
|
122
|
+
# Otherwise, we will crash later on. TODO: Consider what the correct
|
123
|
+
# behavior is in these cases.
|
124
|
+
return false if requirements_update_strategy == :widen_ranges && updating_requirements_file?
|
125
|
+
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
118
129
|
def fetch_lowest_resolvable_security_fix_version
|
119
130
|
fix_version = lowest_security_fix_version
|
120
131
|
return latest_resolvable_version if fix_version.nil?
|
@@ -133,8 +144,7 @@ module Dependabot
|
|
133
144
|
end
|
134
145
|
|
135
146
|
def resolver_type
|
136
|
-
reqs =
|
137
|
-
req_files = reqs.map { |r| r.fetch(:file) }
|
147
|
+
reqs = requirements
|
138
148
|
|
139
149
|
# If there are no requirements then this is a sub-dependency. It
|
140
150
|
# must come from one of Pipenv, Poetry or pip-tools, and can't come
|
@@ -143,9 +153,9 @@ module Dependabot
|
|
143
153
|
|
144
154
|
# Otherwise, this is a top-level dependency, and we can figure out
|
145
155
|
# which resolver to use based on the filename of its requirements
|
146
|
-
return :pipenv if
|
147
|
-
return
|
148
|
-
return :pip_compile if
|
156
|
+
return :pipenv if updating_pipfile?
|
157
|
+
return pyproject_resolver if updating_pyproject?
|
158
|
+
return :pip_compile if updating_in_file?
|
149
159
|
|
150
160
|
if dependency.version && !exact_requirement?(reqs)
|
151
161
|
subdependency_resolver
|
@@ -162,6 +172,12 @@ module Dependabot
|
|
162
172
|
raise "Claimed to be a sub-dependency, but no lockfile exists!"
|
163
173
|
end
|
164
174
|
|
175
|
+
def pyproject_resolver
|
176
|
+
return :poetry if poetry_based?
|
177
|
+
|
178
|
+
:requirements
|
179
|
+
end
|
180
|
+
|
165
181
|
def exact_requirement?(reqs)
|
166
182
|
reqs = reqs.map { |r| r.fetch(:requirement) }
|
167
183
|
reqs = reqs.compact
|
@@ -202,16 +218,14 @@ module Dependabot
|
|
202
218
|
end
|
203
219
|
|
204
220
|
def current_requirement_string
|
205
|
-
reqs =
|
221
|
+
reqs = requirements
|
206
222
|
return if reqs.none?
|
207
223
|
|
208
|
-
requirement =
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
when :requirements then reqs.find { |r| r[:file].end_with?(".txt") }
|
214
|
-
end
|
224
|
+
requirement = reqs.find do |r|
|
225
|
+
file = r[:file]
|
226
|
+
|
227
|
+
file == "Pipfile" || file == "pyproject.toml" || file.end_with?(".in") || file.end_with?(".txt")
|
228
|
+
end
|
215
229
|
|
216
230
|
requirement&.fetch(:requirement)
|
217
231
|
end
|
@@ -236,7 +250,7 @@ module Dependabot
|
|
236
250
|
return ">= #{dependency.version}" if dependency.version
|
237
251
|
|
238
252
|
version_for_requirement =
|
239
|
-
|
253
|
+
requirements.filter_map { |r| r[:requirement] }.
|
240
254
|
reject { |req_string| req_string.start_with?("<") }.
|
241
255
|
select { |req_string| req_string.match?(VERSION_REGEX) }.
|
242
256
|
map { |req_string| req_string.match(VERSION_REGEX) }.
|
@@ -261,26 +275,53 @@ module Dependabot
|
|
261
275
|
)
|
262
276
|
end
|
263
277
|
|
264
|
-
def
|
265
|
-
|
278
|
+
def poetry_based?
|
279
|
+
updating_pyproject? && !poetry_details.nil?
|
280
|
+
end
|
281
|
+
|
282
|
+
def library?
|
283
|
+
return unless updating_pyproject?
|
266
284
|
|
267
285
|
# Hit PyPi and check whether there are details for a library with a
|
268
286
|
# matching name and description
|
269
|
-
details = TomlRB.parse(pyproject.content).dig("tool", "poetry")
|
270
|
-
return false unless details
|
271
|
-
|
272
287
|
index_response = Dependabot::RegistryClient.get(
|
273
|
-
url: "https://pypi.org/pypi/#{normalised_name(
|
288
|
+
url: "https://pypi.org/pypi/#{normalised_name(library_details['name'])}/json/"
|
274
289
|
)
|
275
290
|
|
276
291
|
return false unless index_response.status == 200
|
277
292
|
|
278
293
|
pypi_info = JSON.parse(index_response.body)["info"] || {}
|
279
|
-
pypi_info["summary"] ==
|
294
|
+
pypi_info["summary"] == library_details["description"]
|
295
|
+
rescue Excon::Error::Timeout
|
296
|
+
false
|
280
297
|
rescue URI::InvalidURIError
|
281
298
|
false
|
282
299
|
end
|
283
300
|
|
301
|
+
def updating_pipfile?
|
302
|
+
requirement_files.any?("Pipfile")
|
303
|
+
end
|
304
|
+
|
305
|
+
def updating_pyproject?
|
306
|
+
requirement_files.any?("pyproject.toml")
|
307
|
+
end
|
308
|
+
|
309
|
+
def updating_in_file?
|
310
|
+
requirement_files.any? { |f| f.end_with?(".in") }
|
311
|
+
end
|
312
|
+
|
313
|
+
def updating_requirements_file?
|
314
|
+
requirement_files.any? { |f| f =~ /\.txt$|\.in$/ }
|
315
|
+
end
|
316
|
+
|
317
|
+
def requirement_files
|
318
|
+
requirements.map { |r| r.fetch(:file) }
|
319
|
+
end
|
320
|
+
|
321
|
+
def requirements
|
322
|
+
dependency.requirements
|
323
|
+
end
|
324
|
+
|
284
325
|
def normalised_name(name)
|
285
326
|
NameNormaliser.normalise(name)
|
286
327
|
end
|
@@ -305,6 +346,22 @@ module Dependabot
|
|
305
346
|
dependency_files.find { |f| f.name == "poetry.lock" }
|
306
347
|
end
|
307
348
|
|
349
|
+
def library_details
|
350
|
+
@library_details ||= poetry_details || standard_details
|
351
|
+
end
|
352
|
+
|
353
|
+
def poetry_details
|
354
|
+
@poetry_details ||= toml_content.dig("tool", "poetry")
|
355
|
+
end
|
356
|
+
|
357
|
+
def standard_details
|
358
|
+
@standard_details ||= toml_content["project"]
|
359
|
+
end
|
360
|
+
|
361
|
+
def toml_content
|
362
|
+
@toml_content ||= TomlRB.parse(pyproject.content)
|
363
|
+
end
|
364
|
+
|
308
365
|
def pip_compile_files
|
309
366
|
dependency_files.select { |f| f.name.end_with?(".in") }
|
310
367
|
end
|
@@ -16,9 +16,9 @@ module Dependabot
|
|
16
16
|
|
17
17
|
# See https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
18
18
|
VERSION_PATTERN = 'v?([1-9][0-9]*!)?[0-9]+[0-9a-zA-Z]*(?>\.[0-9a-zA-Z]+)*' \
|
19
|
-
'(-[0-9A-Za-z
|
19
|
+
'(-[0-9A-Za-z]+(\.[0-9a-zA-Z]+)*)?' \
|
20
20
|
'(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?'
|
21
|
-
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z
|
21
|
+
ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/
|
22
22
|
|
23
23
|
def self.correct?(version)
|
24
24
|
return false if version.nil?
|