dependabot-python 0.320.0 → 0.320.1
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/lib/dependabot/python/authed_url_builder.rb +8 -3
- data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +5 -5
- data/lib/dependabot/python/file_updater/requirement_file_updater.rb +37 -10
- data/lib/dependabot/python/file_updater/requirement_replacer.rb +59 -25
- data/lib/dependabot/python/file_updater/setup_file_sanitizer.rb +39 -13
- data/lib/dependabot/python/file_updater.rb +1 -1
- data/lib/dependabot/python/package/package_registry_finder.rb +97 -55
- data/lib/dependabot/python/pipenv_runner.rb +2 -2
- data/lib/dependabot/python/requirement.rb +29 -20
- data/lib/dependabot/python/update_checker/pip_compile_version_resolver.rb +121 -50
- data/lib/dependabot/python/update_checker/pip_version_resolver.rb +41 -10
- data/lib/dependabot/python/update_checker/pipenv_version_resolver.rb +80 -23
- data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +80 -25
- data/lib/dependabot/python/update_checker/requirements_updater.rb +80 -34
- data/lib/dependabot/python/version.rb +21 -14
- metadata +4 -4
@@ -1,6 +1,7 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "sorbet-runtime"
|
4
5
|
require "dependabot/python/update_checker"
|
5
6
|
require "dependabot/python/authed_url_builder"
|
6
7
|
require "dependabot/errors"
|
@@ -9,15 +10,27 @@ module Dependabot
|
|
9
10
|
module Python
|
10
11
|
module Package
|
11
12
|
class PackageRegistryFinder
|
13
|
+
extend T::Sig
|
14
|
+
|
12
15
|
PYPI_BASE_URL = "https://pypi.org/simple/"
|
13
16
|
ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
|
14
17
|
|
18
|
+
UrlsHash = T.type_alias { { main: T.nilable(String), extra: T::Array[String] } }
|
19
|
+
|
20
|
+
sig do
|
21
|
+
params(
|
22
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
23
|
+
credentials: T::Array[Dependabot::Credential],
|
24
|
+
dependency: Dependabot::Dependency
|
25
|
+
).void
|
26
|
+
end
|
15
27
|
def initialize(dependency_files:, credentials:, dependency:)
|
16
|
-
@dependency_files = dependency_files
|
17
|
-
@credentials = credentials
|
18
|
-
@dependency = dependency
|
28
|
+
@dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
|
29
|
+
@credentials = T.let(credentials, T::Array[Dependabot::Credential])
|
30
|
+
@dependency = T.let(dependency, Dependabot::Dependency)
|
19
31
|
end
|
20
32
|
|
33
|
+
sig { returns(T::Array[String]) }
|
21
34
|
def registry_urls
|
22
35
|
extra_index_urls =
|
23
36
|
config_variable_index_urls[:extra] +
|
@@ -40,9 +53,13 @@ module Dependabot
|
|
40
53
|
|
41
54
|
private
|
42
55
|
|
56
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
43
57
|
attr_reader :dependency_files
|
58
|
+
|
59
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
44
60
|
attr_reader :credentials
|
45
61
|
|
62
|
+
sig { returns(String) }
|
46
63
|
def main_index_url
|
47
64
|
url =
|
48
65
|
config_variable_index_urls[:main] ||
|
@@ -55,92 +72,109 @@ module Dependabot
|
|
55
72
|
clean_check_and_remove_environment_variables(url)
|
56
73
|
end
|
57
74
|
|
75
|
+
sig { returns(UrlsHash) }
|
58
76
|
def requirement_file_index_urls
|
59
|
-
urls = { main: nil, extra: [] }
|
77
|
+
urls = T.let({ main: nil, extra: [] }, UrlsHash)
|
60
78
|
|
61
79
|
requirements_files.each do |file|
|
62
|
-
|
80
|
+
content = T.must(file.content)
|
81
|
+
if content.match?(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
|
63
82
|
urls[:main] =
|
64
|
-
|
65
|
-
|
83
|
+
T.must(content.match(/^--index-url\s+['"]?([^\s'"]+)['"]?/))
|
84
|
+
.captures.first&.strip
|
66
85
|
end
|
67
|
-
urls[:extra]
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
86
|
+
extra_urls = urls[:extra]
|
87
|
+
extra_urls +=
|
88
|
+
content
|
89
|
+
.scan(/^--extra-index-url\s+['"]?([^\s'"]+)['"]?/)
|
90
|
+
.flatten
|
91
|
+
.map(&:strip)
|
92
|
+
urls[:extra] = extra_urls
|
72
93
|
end
|
73
94
|
|
74
95
|
urls
|
75
96
|
end
|
76
97
|
|
98
|
+
sig { returns(UrlsHash) }
|
77
99
|
def pip_conf_index_urls
|
78
|
-
urls = { main: nil, extra: [] }
|
100
|
+
urls = T.let({ main: nil, extra: [] }, UrlsHash)
|
79
101
|
|
80
102
|
return urls unless pip_conf
|
81
103
|
|
82
|
-
content = pip_conf.content
|
104
|
+
content = T.must(pip_conf).content
|
105
|
+
return urls unless content
|
83
106
|
|
84
107
|
if content.match?(/^index-url\s*=/x)
|
85
|
-
urls[:main] = content.match(/^index-url\s*=\s*(.+)/)
|
86
|
-
|
108
|
+
urls[:main] = T.must(content.match(/^index-url\s*=\s*(.+)/))
|
109
|
+
.captures.first
|
87
110
|
end
|
88
|
-
urls[:extra]
|
111
|
+
extra_urls = urls[:extra]
|
112
|
+
extra_urls += content.scan(/^extra-index-url\s*=(.+)/).flatten
|
113
|
+
urls[:extra] = extra_urls
|
89
114
|
|
90
115
|
urls
|
91
116
|
end
|
92
117
|
|
118
|
+
sig { returns(UrlsHash) }
|
93
119
|
def pipfile_index_urls
|
94
|
-
urls = { main: nil, extra: [] }
|
120
|
+
urls = T.let({ main: nil, extra: [] }, UrlsHash)
|
121
|
+
begin
|
122
|
+
return urls unless pipfile
|
95
123
|
|
96
|
-
|
124
|
+
content = T.must(pipfile).content
|
125
|
+
return urls unless content
|
97
126
|
|
98
|
-
|
127
|
+
pipfile_object = TomlRB.parse(content)
|
99
128
|
|
100
|
-
|
129
|
+
urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
|
101
130
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
131
|
+
pipfile_object["source"]&.each do |source|
|
132
|
+
urls[:extra] << source.fetch("url") if source["url"]
|
133
|
+
end
|
134
|
+
urls[:extra] = urls[:extra].uniq
|
106
135
|
|
107
|
-
|
108
|
-
|
109
|
-
|
136
|
+
urls
|
137
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
138
|
+
urls
|
139
|
+
end
|
110
140
|
end
|
111
141
|
|
142
|
+
sig { returns(UrlsHash) }
|
112
143
|
def pyproject_index_urls
|
113
|
-
urls = { main: nil, extra: [] }
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
144
|
+
urls = T.let({ main: nil, extra: [] }, UrlsHash)
|
145
|
+
|
146
|
+
begin
|
147
|
+
return urls unless pyproject
|
148
|
+
|
149
|
+
sources =
|
150
|
+
TomlRB.parse(T.must(T.must(pyproject).content)).dig("tool", "poetry", "source") ||
|
151
|
+
[]
|
152
|
+
|
153
|
+
sources.each do |source|
|
154
|
+
# If source is PyPI, skip it, and let it pick the default URI
|
155
|
+
next if source["name"].casecmp?("PyPI")
|
156
|
+
|
157
|
+
if @dependency.all_sources.include?(source["name"])
|
158
|
+
# If dependency has specified this source, use it
|
159
|
+
return { main: source["url"], extra: [] }
|
160
|
+
elsif source["default"]
|
161
|
+
urls[:main] = source["url"]
|
162
|
+
elsif source["priority"] != "explicit"
|
163
|
+
# if source is not explicit, add it to extra
|
164
|
+
urls[:extra] << source["url"]
|
165
|
+
end
|
133
166
|
end
|
134
|
-
|
135
|
-
urls[:extra] = urls[:extra].uniq
|
167
|
+
urls[:extra] = urls[:extra].uniq
|
136
168
|
|
137
|
-
|
138
|
-
|
139
|
-
|
169
|
+
urls
|
170
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
171
|
+
urls
|
172
|
+
end
|
140
173
|
end
|
141
174
|
|
175
|
+
sig { returns(UrlsHash) }
|
142
176
|
def config_variable_index_urls
|
143
|
-
urls = { main: nil, extra: [] }
|
177
|
+
urls = T.let({ main: nil, extra: [] }, UrlsHash)
|
144
178
|
|
145
179
|
index_url_creds = credentials
|
146
180
|
.select { |cred| cred["type"] == "python_index" }
|
@@ -157,6 +191,7 @@ module Dependabot
|
|
157
191
|
urls
|
158
192
|
end
|
159
193
|
|
194
|
+
sig { params(url: String).returns(String) }
|
160
195
|
def clean_check_and_remove_environment_variables(url)
|
161
196
|
url = url.strip.sub(%r{/+$}, "") + "/"
|
162
197
|
|
@@ -186,6 +221,7 @@ module Dependabot
|
|
186
221
|
raise PrivateSourceAuthenticationFailure, url
|
187
222
|
end
|
188
223
|
|
224
|
+
sig { params(base_url: String).returns(String) }
|
189
225
|
def authed_base_url(base_url)
|
190
226
|
cred = credential_for(base_url)
|
191
227
|
return base_url unless cred
|
@@ -193,6 +229,7 @@ module Dependabot
|
|
193
229
|
AuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$}, "") + "/"
|
194
230
|
end
|
195
231
|
|
232
|
+
sig { params(url: String).returns(T.nilable(Dependabot::Credential)) }
|
196
233
|
def credential_for(url)
|
197
234
|
credentials
|
198
235
|
.select { |c| c["type"] == "python_index" }
|
@@ -202,22 +239,27 @@ module Dependabot
|
|
202
239
|
end
|
203
240
|
end
|
204
241
|
|
242
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
205
243
|
def pip_conf
|
206
244
|
dependency_files.find { |f| f.name == "pip.conf" }
|
207
245
|
end
|
208
246
|
|
247
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
209
248
|
def pipfile
|
210
249
|
dependency_files.find { |f| f.name == "Pipfile" }
|
211
250
|
end
|
212
251
|
|
252
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
213
253
|
def pyproject
|
214
254
|
dependency_files.find { |f| f.name == "pyproject.toml" }
|
215
255
|
end
|
216
256
|
|
257
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
217
258
|
def requirements_files
|
218
259
|
dependency_files.select { |f| f.name.match?(/requirements/x) }
|
219
260
|
end
|
220
261
|
|
262
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
221
263
|
def pip_compile_files
|
222
264
|
dependency_files.select { |f| f.name.end_with?(".in") }
|
223
265
|
end
|
@@ -25,7 +25,7 @@ module Dependabot
|
|
25
25
|
@language_version_manager = language_version_manager
|
26
26
|
end
|
27
27
|
|
28
|
-
sig { params(constraint: String).returns(String) }
|
28
|
+
sig { params(constraint: T.nilable(String)).returns(String) }
|
29
29
|
def run_upgrade(constraint)
|
30
30
|
constraint = "" if constraint == "*"
|
31
31
|
command = "pyenv exec pipenv upgrade --verbose #{dependency_name}#{constraint}"
|
@@ -34,7 +34,7 @@ module Dependabot
|
|
34
34
|
run(command, fingerprint: "pyenv exec pipenv upgrade --verbose <dependency_name><constraint>")
|
35
35
|
end
|
36
36
|
|
37
|
-
sig { params(constraint: String).returns(T.nilable(String)) }
|
37
|
+
sig { params(constraint: T.nilable(String)).returns(T.nilable(String)) }
|
38
38
|
def run_upgrade_and_fetch_version(constraint)
|
39
39
|
run_upgrade(constraint)
|
40
40
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
@@ -12,26 +12,27 @@ module Dependabot
|
|
12
12
|
class Requirement < Dependabot::Requirement
|
13
13
|
extend T::Sig
|
14
14
|
|
15
|
-
OR_SEPARATOR = /(?<=[a-zA-Z0-9)*])\s
|
15
|
+
OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
|
16
16
|
|
17
17
|
# Add equality and arbitrary-equality matchers
|
18
|
-
OPS = OPS.merge(
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
OPS = T.let(OPS.merge(
|
19
|
+
"==" => ->(v, r) { v == r },
|
20
|
+
"===" => ->(v, r) { v.to_s == r.to_s }
|
21
|
+
), T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)])
|
22
22
|
|
23
23
|
quoted = OPS.keys.sort_by(&:length).reverse
|
24
24
|
.map { |k| Regexp.quote(k) }.join("|")
|
25
25
|
version_pattern = Python::Version::VERSION_PATTERN
|
26
26
|
|
27
|
-
PATTERN_RAW = "\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze
|
28
|
-
PATTERN = /\A#{PATTERN_RAW}\z
|
29
|
-
PARENS_PATTERN = /\A\(([^)]+)\)\z
|
27
|
+
PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
|
28
|
+
PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
|
29
|
+
PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
|
30
30
|
|
31
|
+
sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
|
31
32
|
def self.parse(obj)
|
32
|
-
return ["=",
|
33
|
+
return ["=", obj] if obj.is_a?(Gem::Version)
|
33
34
|
|
34
|
-
line = obj
|
35
|
+
line = obj
|
35
36
|
if (matches = PARENS_PATTERN.match(line))
|
36
37
|
line = matches[1]
|
37
38
|
end
|
@@ -63,6 +64,7 @@ module Dependabot
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
|
66
68
|
def initialize(*requirements)
|
67
69
|
requirements = requirements.flatten.flat_map do |req_string|
|
68
70
|
next if req_string.nil?
|
@@ -78,20 +80,23 @@ module Dependabot
|
|
78
80
|
super(requirements)
|
79
81
|
end
|
80
82
|
|
83
|
+
sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
|
81
84
|
def satisfied_by?(version)
|
82
85
|
version = Python::Version.new(version.to_s)
|
83
86
|
|
84
|
-
requirements.all? { |op, rv| (OPS[op] || OPS["="]).call(version, rv) }
|
87
|
+
requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
|
85
88
|
end
|
86
89
|
|
90
|
+
sig { returns(T::Boolean) }
|
87
91
|
def exact?
|
88
|
-
return false unless
|
92
|
+
return false unless requirements.size == 1
|
89
93
|
|
90
|
-
%w(= == ===).include?(
|
94
|
+
%w(= == ===).include?(requirements[0][0])
|
91
95
|
end
|
92
96
|
|
93
97
|
private
|
94
98
|
|
99
|
+
sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
|
95
100
|
def convert_python_constraint_to_ruby_constraint(req_string)
|
96
101
|
return nil if req_string.nil? || req_string.strip.empty?
|
97
102
|
return nil if req_string == "*"
|
@@ -111,6 +116,7 @@ module Dependabot
|
|
111
116
|
|
112
117
|
# Poetry uses ~ requirements.
|
113
118
|
# https://github.com/sdispater/poetry#tilde-requirements
|
119
|
+
sig { params(req_string: String).returns(String) }
|
114
120
|
def convert_tilde_req(req_string)
|
115
121
|
version = req_string.gsub(/^~\>?/, "")
|
116
122
|
parts = version.split(".")
|
@@ -120,18 +126,19 @@ module Dependabot
|
|
120
126
|
|
121
127
|
# Poetry uses ^ requirements
|
122
128
|
# https://github.com/sdispater/poetry#caret-requirement
|
129
|
+
sig { params(req_string: String).returns(T::Array[String]) }
|
123
130
|
def convert_caret_req(req_string)
|
124
131
|
version = req_string.gsub(/^\^/, "")
|
125
132
|
parts = version.split(".")
|
126
|
-
parts.fill(0, parts.length...3)
|
133
|
+
parts.fill("0", parts.length...3)
|
127
134
|
first_non_zero = parts.find { |d| d != "0" }
|
128
135
|
first_non_zero_index =
|
129
136
|
first_non_zero ? parts.index(first_non_zero) : parts.count - 1
|
130
137
|
upper_bound = parts.map.with_index do |part, i|
|
131
|
-
if i < first_non_zero_index then part
|
138
|
+
if i < T.must(first_non_zero_index) then part
|
132
139
|
elsif i == first_non_zero_index then (part.to_i + 1).to_s
|
133
140
|
# .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
|
134
|
-
elsif i > first_non_zero_index && i == 2 then "0.dev"
|
141
|
+
elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
|
135
142
|
else
|
136
143
|
0
|
137
144
|
end
|
@@ -140,24 +147,26 @@ module Dependabot
|
|
140
147
|
[">= #{version}", "< #{upper_bound}"]
|
141
148
|
end
|
142
149
|
|
150
|
+
sig { params(req_string: String).returns(String) }
|
143
151
|
def convert_wildcard(req_string)
|
144
152
|
# NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
|
145
153
|
# "!= 1.0.0". There's no way to model this correctly in Ruby :'(
|
146
154
|
quoted_ops = OPS.keys.sort_by(&:length).reverse
|
147
155
|
.map { |k| Regexp.quote(k) }.join("|")
|
148
|
-
|
149
|
-
|
156
|
+
op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
|
157
|
+
op = op_match&.captures&.first.to_s.strip
|
150
158
|
exact_op = ["", "=", "==", "==="].include?(op)
|
151
159
|
|
152
160
|
req_string.strip
|
153
161
|
.split(".")
|
154
|
-
.first(req_string.split(".").index { |s| s.include?("*") } + 1)
|
162
|
+
.first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
|
155
163
|
.join(".")
|
156
164
|
.gsub(/\*(?!$)/, "0")
|
157
165
|
.gsub(/\*$/, "0.dev")
|
158
166
|
.tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
|
159
167
|
end
|
160
168
|
|
169
|
+
sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
|
161
170
|
def convert_exact(req_string)
|
162
171
|
arbitrary_equality = req_string.start_with?("===")
|
163
172
|
cleaned_version = req_string.gsub(/^=+/, "").strip
|