dependabot-python 0.393.0 → 0.395.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/requirements.txt +2 -2
- data/lib/dependabot/python/file_parser/pep_dependency.rb +53 -0
- data/lib/dependabot/python/file_parser/poetry_lock.rb +95 -0
- data/lib/dependabot/python/file_parser/pyproject_document.rb +284 -0
- data/lib/dependabot/python/file_parser/pyproject_files_parser.rb +134 -128
- data/lib/dependabot/python/file_parser.rb +3 -0
- data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +14 -8
- data/lib/dependabot/python/package/package_details_fetcher.rb +81 -35
- data/lib/dependabot/python/package/package_registry_finder.rb +11 -4
- data/lib/dependabot/python/package/simple_api_parser.rb +91 -0
- data/lib/dependabot/python/shared_file_fetcher.rb +1 -2
- data/lib/dependabot/python/update_checker/latest_version_finder.rb +16 -0
- data/lib/dependabot/python/update_checker/pip_version_resolver.rb +1 -2
- data/lib/dependabot/python/update_checker/requirements_updater.rb +2 -3
- data/lib/dependabot/python/update_checker.rb +48 -49
- metadata +13 -9
|
@@ -12,6 +12,7 @@ require "dependabot/python/name_normaliser"
|
|
|
12
12
|
require "dependabot/package/package_release"
|
|
13
13
|
require "dependabot/package/package_details"
|
|
14
14
|
require "dependabot/python/package/package_registry_finder"
|
|
15
|
+
require "dependabot/python/package/simple_api_parser"
|
|
15
16
|
|
|
16
17
|
# Stores metadata for a package, including all its available versions
|
|
17
18
|
module Dependabot
|
|
@@ -21,6 +22,7 @@ module Dependabot
|
|
|
21
22
|
CREDENTIALS_PASSWORD = "password"
|
|
22
23
|
|
|
23
24
|
APPLICATION_JSON = "application/json"
|
|
25
|
+
APPLICATION_PYPI_SIMPLE_JSON = "application/vnd.pypi.simple.v1+json"
|
|
24
26
|
APPLICATION_TEXT = "text/html"
|
|
25
27
|
CPYTHON = "cpython"
|
|
26
28
|
PYTHON = "python"
|
|
@@ -42,11 +44,7 @@ module Dependabot
|
|
|
42
44
|
credentials: T::Array[Dependabot::Credential]
|
|
43
45
|
).void
|
|
44
46
|
end
|
|
45
|
-
def initialize(
|
|
46
|
-
dependency:,
|
|
47
|
-
dependency_files:,
|
|
48
|
-
credentials:
|
|
49
|
-
)
|
|
47
|
+
def initialize(dependency:, dependency_files:, credentials:)
|
|
50
48
|
@dependency = dependency
|
|
51
49
|
@dependency_files = dependency_files
|
|
52
50
|
@credentials = credentials
|
|
@@ -70,7 +68,7 @@ module Dependabot
|
|
|
70
68
|
.flat_map do |index_url|
|
|
71
69
|
fetch_from_registry(index_url) || [] # Ensure it always returns an array
|
|
72
70
|
rescue Excon::Error::Timeout, Excon::Error::Socket
|
|
73
|
-
raise if
|
|
71
|
+
raise if main_pypi_index?(index_url)
|
|
74
72
|
|
|
75
73
|
raise PrivateSourceTimedOut, sanitized_url(index_url)
|
|
76
74
|
rescue URI::InvalidURIError
|
|
@@ -90,15 +88,46 @@ module Dependabot
|
|
|
90
88
|
.returns(T.nilable(T::Array[Dependabot::Package::PackageRelease]))
|
|
91
89
|
end
|
|
92
90
|
def fetch_from_registry(index_url)
|
|
93
|
-
|
|
91
|
+
return fetch_from_simple_registry(index_url) unless main_pypi_index?(index_url)
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
begin
|
|
94
|
+
metadata = fetch_from_json_registry(index_url)
|
|
95
|
+
|
|
96
|
+
return metadata if metadata&.any?
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
Dependabot.logger.warn("No valid versions found via JSON API. Falling back to HTML.")
|
|
99
|
+
fetch_from_html_registry(index_url)
|
|
100
|
+
rescue Dependabot::DependencyFileNotResolvable
|
|
101
|
+
raise
|
|
102
|
+
rescue StandardError => e
|
|
103
|
+
Dependabot.logger.warn("Unexpected error in JSON fetch: #{e.message}. Falling back to HTML.")
|
|
104
|
+
fetch_from_html_registry(index_url)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
sig { params(index_url: String).returns(T::Array[Dependabot::Package::PackageRelease]) }
|
|
109
|
+
def fetch_from_simple_registry(index_url)
|
|
110
|
+
Dependabot.logger.info(
|
|
111
|
+
"Fetching release information from simple registry at #{sanitized_url(index_url)} for #{dependency.name}"
|
|
112
|
+
)
|
|
113
|
+
response = registry_response_for_dependency(index_url, accept: simple_api_accept)
|
|
114
|
+
project_url = response.path ? Excon::Utils.request_uri(response.data) : index_url + normalised_name + "/"
|
|
115
|
+
check_authentication_response(response, index_url)
|
|
116
|
+
|
|
117
|
+
version_releases = if simple_json_response?(response)
|
|
118
|
+
SimpleApiParser.new(
|
|
119
|
+
dependency: dependency,
|
|
120
|
+
project_url: project_url
|
|
121
|
+
).parse(response.body)
|
|
122
|
+
else
|
|
123
|
+
extract_release_details_json_from_html(response.body)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
format_version_releases(version_releases, preserve_distributions: simple_json_response?(response))
|
|
127
|
+
.sort_by(&:version).reverse
|
|
128
|
+
rescue JSON::ParserError
|
|
129
|
+
Dependabot.logger.warn("JSON parsing error for #{sanitized_url(index_url)}.")
|
|
130
|
+
[]
|
|
102
131
|
end
|
|
103
132
|
|
|
104
133
|
# Example JSON Response Format:
|
|
@@ -203,14 +232,8 @@ module Dependabot
|
|
|
203
232
|
Dependabot.logger.info(
|
|
204
233
|
"Fetching release information from html registry at #{sanitized_url(index_url)} for #{dependency.name}"
|
|
205
234
|
)
|
|
206
|
-
index_response = registry_response_for_dependency(index_url)
|
|
207
|
-
|
|
208
|
-
registry_index_response = registry_index_response(index_url)
|
|
209
|
-
|
|
210
|
-
if registry_index_response.status == 401 || registry_index_response.status == 403
|
|
211
|
-
raise PrivateSourceAuthenticationFailure, sanitized_url(index_url)
|
|
212
|
-
end
|
|
213
|
-
end
|
|
235
|
+
index_response = registry_response_for_dependency(index_url, accept: APPLICATION_TEXT)
|
|
236
|
+
check_authentication_response(index_response, index_url)
|
|
214
237
|
|
|
215
238
|
version_releases = extract_release_details_json_from_html(index_response.body)
|
|
216
239
|
releases = format_version_releases(version_releases)
|
|
@@ -218,6 +241,27 @@ module Dependabot
|
|
|
218
241
|
releases.sort_by(&:version).reverse
|
|
219
242
|
end
|
|
220
243
|
|
|
244
|
+
sig { params(response: Excon::Response).returns(T::Boolean) }
|
|
245
|
+
def simple_json_response?(response)
|
|
246
|
+
response.headers["Content-Type"].to_s.split(";", 2).first.to_s.strip.downcase == APPLICATION_PYPI_SIMPLE_JSON
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
sig { returns(String) }
|
|
250
|
+
def simple_api_accept
|
|
251
|
+
"application/vnd.pypi.simple.v1+json, " \
|
|
252
|
+
"application/vnd.pypi.simple.v1+html;q=0.2, text/html;q=0.01"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
sig { params(response: Excon::Response, index_url: String).void }
|
|
256
|
+
def check_authentication_response(response, index_url)
|
|
257
|
+
return unless [401, 403].include?(response.status)
|
|
258
|
+
|
|
259
|
+
index_response = registry_index_response(index_url)
|
|
260
|
+
return unless [401, 403].include?(index_response.status)
|
|
261
|
+
|
|
262
|
+
raise PrivateSourceAuthenticationFailure, sanitized_url(index_url)
|
|
263
|
+
end
|
|
264
|
+
|
|
221
265
|
sig do
|
|
222
266
|
params(html_body: String)
|
|
223
267
|
.returns(T::Hash[String, T::Array[T::Hash[String, T.untyped]]]) # Returns JSON-like format
|
|
@@ -266,23 +310,17 @@ module Dependabot
|
|
|
266
310
|
|
|
267
311
|
sig do
|
|
268
312
|
params(
|
|
269
|
-
releases_json: T.nilable(T::Hash[String, T::Array[T::Hash[String, T.untyped]]])
|
|
313
|
+
releases_json: T.nilable(T::Hash[String, T::Array[T::Hash[String, T.untyped]]]),
|
|
314
|
+
preserve_distributions: T::Boolean
|
|
270
315
|
)
|
|
271
316
|
.returns(T::Array[Dependabot::Package::PackageRelease])
|
|
272
317
|
end
|
|
273
|
-
def format_version_releases(releases_json)
|
|
318
|
+
def format_version_releases(releases_json, preserve_distributions: false)
|
|
274
319
|
return [] unless releases_json
|
|
275
320
|
|
|
276
321
|
releases_json.each_with_object([]) do |(version, release_data_array), versions|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
next unless release_data
|
|
280
|
-
|
|
281
|
-
release = format_version_release(version, release_data)
|
|
282
|
-
|
|
283
|
-
next unless release
|
|
284
|
-
|
|
285
|
-
versions << release
|
|
322
|
+
release_data_array = [release_data_array.last].compact unless preserve_distributions
|
|
323
|
+
versions.concat(release_data_array.filter_map { |data| format_version_release(version, data) })
|
|
286
324
|
end
|
|
287
325
|
end
|
|
288
326
|
|
|
@@ -397,11 +435,11 @@ module Dependabot
|
|
|
397
435
|
)
|
|
398
436
|
end
|
|
399
437
|
|
|
400
|
-
sig { params(index_url: String).returns(Excon::Response) }
|
|
401
|
-
def registry_response_for_dependency(index_url)
|
|
438
|
+
sig { params(index_url: String, accept: String).returns(Excon::Response) }
|
|
439
|
+
def registry_response_for_dependency(index_url, accept:)
|
|
402
440
|
Dependabot::RegistryClient.get(
|
|
403
441
|
url: index_url + normalised_name + "/",
|
|
404
|
-
headers: { "Accept" =>
|
|
442
|
+
headers: { "Accept" => accept }
|
|
405
443
|
)
|
|
406
444
|
end
|
|
407
445
|
|
|
@@ -487,6 +525,14 @@ module Dependabot
|
|
|
487
525
|
index_url.sub(%r{//([^/@]+)@}, "//redacted@")
|
|
488
526
|
end
|
|
489
527
|
|
|
528
|
+
sig { params(index_url: String).returns(T::Boolean) }
|
|
529
|
+
def main_pypi_index?(index_url)
|
|
530
|
+
uri = URI.parse(index_url)
|
|
531
|
+
T.must(uri.scheme.to_s.casecmp("https")).zero? && uri.port == 443 &&
|
|
532
|
+
uri.path.to_s.split("/").reject(&:empty?) == ["simple"] &&
|
|
533
|
+
%w(pypi.org pypi.python.org).include?(uri.host.to_s.downcase)
|
|
534
|
+
end
|
|
535
|
+
|
|
490
536
|
sig { params(dep_name: String).returns(String) }
|
|
491
537
|
def remove_optional(dep_name)
|
|
492
538
|
# Remove any optional dependencies postfix, e.g., "pyvista[io] "
|
|
@@ -193,7 +193,7 @@ module Dependabot
|
|
|
193
193
|
|
|
194
194
|
sig { params(url: String).returns(String) }
|
|
195
195
|
def clean_check_and_remove_environment_variables(url)
|
|
196
|
-
url = url.strip
|
|
196
|
+
url = with_single_trailing_slash(url.strip)
|
|
197
197
|
|
|
198
198
|
return authed_base_url(url) unless url.match?(ENVIRONMENT_VARIABLE_REGEX)
|
|
199
199
|
|
|
@@ -203,7 +203,7 @@ module Dependabot
|
|
|
203
203
|
*config_variable_index_urls[:extra]
|
|
204
204
|
]
|
|
205
205
|
.compact
|
|
206
|
-
.map { |u| u.strip
|
|
206
|
+
.map { |u| with_single_trailing_slash(u.strip) }
|
|
207
207
|
|
|
208
208
|
regexp = url
|
|
209
209
|
.sub(%r{(?<=://).+@}, "")
|
|
@@ -226,7 +226,7 @@ module Dependabot
|
|
|
226
226
|
cred = credential_for(base_url)
|
|
227
227
|
return base_url unless cred
|
|
228
228
|
|
|
229
|
-
AuthedUrlBuilder.authed_url(credential: cred)
|
|
229
|
+
with_single_trailing_slash(AuthedUrlBuilder.authed_url(credential: cred))
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
sig { params(url: String).returns(T.nilable(Dependabot::Credential)) }
|
|
@@ -234,11 +234,18 @@ module Dependabot
|
|
|
234
234
|
credentials
|
|
235
235
|
.select { |c| c["type"] == "python_index" }
|
|
236
236
|
.find do |c|
|
|
237
|
-
cred_url = c.fetch("index-url")
|
|
237
|
+
cred_url = with_single_trailing_slash(c.fetch("index-url"))
|
|
238
238
|
cred_url.include?(url)
|
|
239
239
|
end
|
|
240
240
|
end
|
|
241
241
|
|
|
242
|
+
sig { params(url: String).returns(String) }
|
|
243
|
+
def with_single_trailing_slash(url)
|
|
244
|
+
normalized_url = url.dup
|
|
245
|
+
normalized_url.chop! while normalized_url.end_with?("/")
|
|
246
|
+
normalized_url + "/"
|
|
247
|
+
end
|
|
248
|
+
|
|
242
249
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
243
250
|
def pip_conf
|
|
244
251
|
dependency_files.find { |f| f.name == "pip.conf" }
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "sorbet-runtime"
|
|
7
|
+
require "dependabot/errors"
|
|
8
|
+
require "dependabot/python/name_normaliser"
|
|
9
|
+
|
|
10
|
+
module Dependabot
|
|
11
|
+
module Python
|
|
12
|
+
module Package
|
|
13
|
+
class SimpleApiParser
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
ReleaseDetail = T.type_alias { T::Hash[String, T.nilable(T.any(String, T::Boolean))] }
|
|
17
|
+
|
|
18
|
+
sig { params(dependency: Dependabot::Dependency, project_url: String).void }
|
|
19
|
+
def initialize(dependency:, project_url:)
|
|
20
|
+
@dependency = dependency
|
|
21
|
+
@project_url = project_url
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig do
|
|
25
|
+
params(json_body: String)
|
|
26
|
+
.returns(T::Hash[String, T::Array[ReleaseDetail]])
|
|
27
|
+
end
|
|
28
|
+
def parse(json_body)
|
|
29
|
+
data = JSON.parse(json_body)
|
|
30
|
+
validate_api_version!(data.dig("meta", "api-version").to_s)
|
|
31
|
+
|
|
32
|
+
data.fetch("files", []).each_with_object({}) do |file, releases|
|
|
33
|
+
filename = file["filename"]
|
|
34
|
+
next unless filename&.match?(name_regex)
|
|
35
|
+
|
|
36
|
+
version = version_from_filename(filename)
|
|
37
|
+
next unless dependency.version_class.correct?(version)
|
|
38
|
+
|
|
39
|
+
yanked = file["yanked"] || false
|
|
40
|
+
details = {
|
|
41
|
+
"version" => version,
|
|
42
|
+
"requires_python" => file["requires-python"],
|
|
43
|
+
"yanked" => yanked != false,
|
|
44
|
+
"yanked_reason" => yanked.is_a?(String) ? yanked : nil,
|
|
45
|
+
"upload_time" => file["upload-time"],
|
|
46
|
+
"url" => resolve_url(file["url"])
|
|
47
|
+
}
|
|
48
|
+
releases[version] ||= []
|
|
49
|
+
releases[version] << details
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
sig { returns(Dependabot::Dependency) }
|
|
56
|
+
attr_reader :dependency
|
|
57
|
+
|
|
58
|
+
sig { returns(String) }
|
|
59
|
+
attr_reader :project_url
|
|
60
|
+
|
|
61
|
+
sig { params(url: T.nilable(String)).returns(T.nilable(String)) }
|
|
62
|
+
def resolve_url(url)
|
|
63
|
+
return unless url
|
|
64
|
+
|
|
65
|
+
resolved_url = URI.join(project_url, url)
|
|
66
|
+
resolved_url.user = nil
|
|
67
|
+
resolved_url.password = nil
|
|
68
|
+
resolved_url.to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig { params(api_version: String).void }
|
|
72
|
+
def validate_api_version!(api_version)
|
|
73
|
+
return unless api_version.split(".").first.to_i > 1
|
|
74
|
+
|
|
75
|
+
raise Dependabot::DependencyFileNotResolvable, "Unsupported PEP 691 API version: #{api_version}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sig { returns(Regexp) }
|
|
79
|
+
def name_regex
|
|
80
|
+
parts = NameNormaliser.normalise(dependency.name).split(/[\s_.-]/).map { |name| Regexp.quote(name) }
|
|
81
|
+
/#{parts.join("[\s_.-]")}/i
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sig { params(filename: String).returns(T.nilable(String)) }
|
|
85
|
+
def version_from_filename(filename)
|
|
86
|
+
filename.strip.gsub(/#{name_regex}-/i, "").split(/-|\.tar\.|\.zip|\.whl/).first
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -249,8 +249,7 @@ module Dependabot
|
|
|
249
249
|
next if previously_fetched_files.map(&:name).include?(path)
|
|
250
250
|
next if file.name == path
|
|
251
251
|
|
|
252
|
-
if Dependabot::
|
|
253
|
-
!@exclude_paths.empty? && Dependabot::FileFiltering.exclude_path?(path, @exclude_paths)
|
|
252
|
+
if !@exclude_paths.empty? && Dependabot::FileFiltering.exclude_path?(path, @exclude_paths)
|
|
254
253
|
raise Dependabot::DependencyFileNotEvaluatable,
|
|
255
254
|
"Cannot process requirements: '#{file.name}' references excluded file '#{path}'. " \
|
|
256
255
|
"Please either remove the reference from '#{file.name}' " \
|
|
@@ -42,6 +42,22 @@ module Dependabot
|
|
|
42
42
|
cooldown.semver_minor_days.to_i.positive? ||
|
|
43
43
|
cooldown.semver_patch_days.to_i.positive?
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
sig do
|
|
47
|
+
params(language_version: T.nilable(T.any(String, Dependabot::Version)))
|
|
48
|
+
.returns(T.nilable(T::Array[Dependabot::Package::PackageRelease]))
|
|
49
|
+
end
|
|
50
|
+
def eligible_releases(language_version: nil)
|
|
51
|
+
releases = available_versions
|
|
52
|
+
return unless releases
|
|
53
|
+
|
|
54
|
+
releases = filter_yanked_versions(releases)
|
|
55
|
+
releases = filter_by_cooldown(releases)
|
|
56
|
+
releases = filter_unsupported_versions(releases, language_version)
|
|
57
|
+
releases = filter_prerelease_versions(releases)
|
|
58
|
+
releases = filter_ignored_versions(releases)
|
|
59
|
+
apply_post_fetch_latest_versions_filter(releases)
|
|
60
|
+
end
|
|
45
61
|
end
|
|
46
62
|
end
|
|
47
63
|
end
|
|
@@ -19,7 +19,7 @@ module Dependabot
|
|
|
19
19
|
class UpdateChecker
|
|
20
20
|
# This resolver intentionally co-locates resolution, marker handling, and
|
|
21
21
|
# constraints matching to keep compatibility decisions in one place.
|
|
22
|
-
# rubocop:disable Metrics/ClassLength
|
|
22
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
23
23
|
class PipVersionResolver
|
|
24
24
|
extend T::Sig
|
|
25
25
|
|
|
@@ -575,7 +575,6 @@ module Dependabot
|
|
|
575
575
|
T.must(@pyproject_content_cache[pyproject.name])
|
|
576
576
|
end
|
|
577
577
|
end
|
|
578
|
-
# rubocop:enable Metrics/ClassLength
|
|
579
578
|
end
|
|
580
579
|
end
|
|
581
580
|
end
|
|
@@ -12,7 +12,7 @@ require "dependabot/requirements_update_strategy"
|
|
|
12
12
|
module Dependabot
|
|
13
13
|
module Python
|
|
14
14
|
class UpdateChecker
|
|
15
|
-
# rubocop:disable Metrics/ClassLength
|
|
15
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
16
16
|
class RequirementsUpdater
|
|
17
17
|
extend T::Sig
|
|
18
18
|
|
|
@@ -358,7 +358,7 @@ module Dependabot
|
|
|
358
358
|
new_segments
|
|
359
359
|
.first(count)
|
|
360
360
|
.map.with_index { |s, i| i < precision ? s : "*" }
|
|
361
|
-
|
|
361
|
+
.join(".")
|
|
362
362
|
end
|
|
363
363
|
|
|
364
364
|
sig { params(requirement_strings: T::Array[String]).returns(String) }
|
|
@@ -542,7 +542,6 @@ module Dependabot
|
|
|
542
542
|
Python::Requirement
|
|
543
543
|
end
|
|
544
544
|
end
|
|
545
|
-
# rubocop:enable Metrics/ClassLength
|
|
546
545
|
end
|
|
547
546
|
end
|
|
548
547
|
end
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "excon"
|
|
5
|
-
require "
|
|
5
|
+
require "json"
|
|
6
6
|
require "sorbet-runtime"
|
|
7
7
|
|
|
8
8
|
require "dependabot/dependency"
|
|
9
9
|
require "dependabot/dependency_requirement"
|
|
10
10
|
require "dependabot/errors"
|
|
11
|
+
require "dependabot/python/file_parser"
|
|
11
12
|
require "dependabot/python/name_normaliser"
|
|
12
13
|
require "dependabot/python/requirement_parser"
|
|
13
14
|
require "dependabot/python/requirement"
|
|
@@ -18,7 +19,7 @@ require "dependabot/update_checkers/base"
|
|
|
18
19
|
|
|
19
20
|
module Dependabot
|
|
20
21
|
module Python
|
|
21
|
-
# rubocop:disable Metrics/ClassLength
|
|
22
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
22
23
|
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
|
23
24
|
extend T::Sig
|
|
24
25
|
|
|
@@ -29,6 +30,8 @@ module Dependabot
|
|
|
29
30
|
require_relative "update_checker/requirements_updater"
|
|
30
31
|
require_relative "update_checker/latest_version_finder"
|
|
31
32
|
|
|
33
|
+
PyprojectDocument = FileParser::PyprojectDocument
|
|
34
|
+
|
|
32
35
|
MAIN_PYPI_INDEXES = %w(
|
|
33
36
|
https://pypi.python.org/simple/
|
|
34
37
|
https://pypi.org/simple/
|
|
@@ -248,7 +251,7 @@ module Dependabot
|
|
|
248
251
|
# For hybrid projects with both [tool.poetry] and [project] sections but no lockfile,
|
|
249
252
|
# use the requirements resolver to handle PEP 621 dependencies
|
|
250
253
|
# For pure Poetry projects, use Poetry resolver even without lockfile
|
|
251
|
-
return :poetry if poetry_based? && (poetry_lock || !
|
|
254
|
+
return :poetry if poetry_based? && (poetry_lock || !pyproject_document.project?)
|
|
252
255
|
|
|
253
256
|
:requirements
|
|
254
257
|
end
|
|
@@ -395,7 +398,7 @@ module Dependabot
|
|
|
395
398
|
|
|
396
399
|
sig { returns(T::Boolean) }
|
|
397
400
|
def poetry_based?
|
|
398
|
-
updating_pyproject? &&
|
|
401
|
+
updating_pyproject? && pyproject_document.poetry?
|
|
399
402
|
end
|
|
400
403
|
|
|
401
404
|
sig { returns(T::Boolean) }
|
|
@@ -410,22 +413,41 @@ module Dependabot
|
|
|
410
413
|
def check_pypi_for_library_match
|
|
411
414
|
return false unless updating_pyproject?
|
|
412
415
|
|
|
413
|
-
|
|
414
|
-
|
|
416
|
+
metadata = library_details
|
|
417
|
+
name = metadata&.name
|
|
418
|
+
return false unless name
|
|
415
419
|
|
|
416
|
-
|
|
420
|
+
local_description = metadata.description
|
|
421
|
+
has_library_metadata = !local_description.nil?
|
|
417
422
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
423
|
+
begin
|
|
424
|
+
response = Dependabot::RegistryClient.get(
|
|
425
|
+
url: "https://pypi.org/pypi/#{normalised_name(name)}/json/"
|
|
426
|
+
)
|
|
427
|
+
return has_library_metadata unless response.status == 200
|
|
422
428
|
|
|
423
|
-
|
|
424
|
-
return true if local_description.nil?
|
|
429
|
+
return true if local_description.nil?
|
|
425
430
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
pypi_summary(response.body) == local_description
|
|
432
|
+
rescue Excon::Error::Timeout, Excon::Error::Socket, URI::InvalidURIError
|
|
433
|
+
has_library_metadata
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
sig { params(body: String).returns(T.nilable(String)) }
|
|
438
|
+
def pypi_summary(body)
|
|
439
|
+
metadata = T.cast(JSON.parse(body), Object)
|
|
440
|
+
raise TypeError, "PyPI metadata must be an object" unless metadata.is_a?(Hash)
|
|
441
|
+
|
|
442
|
+
info = T.cast(metadata["info"], Object)
|
|
443
|
+
return if info.nil?
|
|
444
|
+
raise TypeError, "PyPI info must be an object" unless info.is_a?(Hash)
|
|
445
|
+
|
|
446
|
+
summary = T.cast(info["summary"], Object)
|
|
447
|
+
return if summary.nil?
|
|
448
|
+
return summary if summary.is_a?(String)
|
|
449
|
+
|
|
450
|
+
raise TypeError, "PyPI info.summary must be a string"
|
|
429
451
|
end
|
|
430
452
|
|
|
431
453
|
sig { returns(T::Boolean) }
|
|
@@ -483,43 +505,21 @@ module Dependabot
|
|
|
483
505
|
dependency_files.find { |f| f.name == "poetry.lock" }
|
|
484
506
|
end
|
|
485
507
|
|
|
486
|
-
sig { returns(T.nilable(
|
|
508
|
+
sig { returns(T.nilable(PyprojectDocument::ProjectMetadata)) }
|
|
487
509
|
def library_details
|
|
488
510
|
@library_details ||= T.let(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
495
|
-
def poetry_details
|
|
496
|
-
@poetry_details ||= T.let(
|
|
497
|
-
toml_content.dig("tool", "poetry"),
|
|
498
|
-
T.nilable(T::Hash[String, T.untyped])
|
|
499
|
-
)
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
503
|
-
def standard_details
|
|
504
|
-
@standard_details ||= T.let(
|
|
505
|
-
toml_content["project"],
|
|
506
|
-
T.nilable(T::Hash[String, T.untyped])
|
|
507
|
-
)
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
511
|
-
def build_system_details
|
|
512
|
-
@build_system_details ||= T.let(
|
|
513
|
-
toml_content["build-system"],
|
|
514
|
-
T.nilable(T::Hash[String, T.untyped])
|
|
511
|
+
pyproject_document.poetry_metadata ||
|
|
512
|
+
pyproject_document.project_metadata ||
|
|
513
|
+
pyproject_document.build_system_metadata,
|
|
514
|
+
T.nilable(PyprojectDocument::ProjectMetadata)
|
|
515
515
|
)
|
|
516
516
|
end
|
|
517
517
|
|
|
518
|
-
sig { returns(
|
|
519
|
-
def
|
|
520
|
-
@
|
|
521
|
-
|
|
522
|
-
T.nilable(
|
|
518
|
+
sig { returns(PyprojectDocument) }
|
|
519
|
+
def pyproject_document
|
|
520
|
+
@pyproject_document ||= T.let(
|
|
521
|
+
PyprojectDocument.from_content(T.must(T.must(pyproject).content)),
|
|
522
|
+
T.nilable(PyprojectDocument)
|
|
523
523
|
)
|
|
524
524
|
end
|
|
525
525
|
|
|
@@ -528,7 +528,6 @@ module Dependabot
|
|
|
528
528
|
dependency_files.select { |f| f.name.end_with?(".in") }
|
|
529
529
|
end
|
|
530
530
|
end
|
|
531
|
-
# rubocop:enable Metrics/ClassLength
|
|
532
531
|
end
|
|
533
532
|
end
|
|
534
533
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-python
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.395.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.395.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.395.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -169,28 +169,28 @@ dependencies:
|
|
|
169
169
|
requirements:
|
|
170
170
|
- - "~>"
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: '
|
|
172
|
+
version: '1.1'
|
|
173
173
|
type: :development
|
|
174
174
|
prerelease: false
|
|
175
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
176
176
|
requirements:
|
|
177
177
|
- - "~>"
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
|
-
version: '
|
|
179
|
+
version: '1.1'
|
|
180
180
|
- !ruby/object:Gem::Dependency
|
|
181
|
-
name:
|
|
181
|
+
name: turbo_tests2
|
|
182
182
|
requirement: !ruby/object:Gem::Requirement
|
|
183
183
|
requirements:
|
|
184
184
|
- - "~>"
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
|
-
version:
|
|
186
|
+
version: 3.2.7
|
|
187
187
|
type: :development
|
|
188
188
|
prerelease: false
|
|
189
189
|
version_requirements: !ruby/object:Gem::Requirement
|
|
190
190
|
requirements:
|
|
191
191
|
- - "~>"
|
|
192
192
|
- !ruby/object:Gem::Version
|
|
193
|
-
version:
|
|
193
|
+
version: 3.2.7
|
|
194
194
|
- !ruby/object:Gem::Dependency
|
|
195
195
|
name: vcr
|
|
196
196
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -280,7 +280,10 @@ files:
|
|
|
280
280
|
- lib/dependabot/python/dependency_grapher/requirements_layers.rb
|
|
281
281
|
- lib/dependabot/python/file_fetcher.rb
|
|
282
282
|
- lib/dependabot/python/file_parser.rb
|
|
283
|
+
- lib/dependabot/python/file_parser/pep_dependency.rb
|
|
283
284
|
- lib/dependabot/python/file_parser/pipfile_files_parser.rb
|
|
285
|
+
- lib/dependabot/python/file_parser/poetry_lock.rb
|
|
286
|
+
- lib/dependabot/python/file_parser/pyproject_document.rb
|
|
284
287
|
- lib/dependabot/python/file_parser/pyproject_files_parser.rb
|
|
285
288
|
- lib/dependabot/python/file_parser/python_requirement_parser.rb
|
|
286
289
|
- lib/dependabot/python/file_parser/setup_file_parser.rb
|
|
@@ -302,6 +305,7 @@ files:
|
|
|
302
305
|
- lib/dependabot/python/native_helpers.rb
|
|
303
306
|
- lib/dependabot/python/package/package_details_fetcher.rb
|
|
304
307
|
- lib/dependabot/python/package/package_registry_finder.rb
|
|
308
|
+
- lib/dependabot/python/package/simple_api_parser.rb
|
|
305
309
|
- lib/dependabot/python/package_manager.rb
|
|
306
310
|
- lib/dependabot/python/pip_compile_file_matcher.rb
|
|
307
311
|
- lib/dependabot/python/pipenv_runner.rb
|
|
@@ -323,7 +327,7 @@ licenses:
|
|
|
323
327
|
- MIT
|
|
324
328
|
metadata:
|
|
325
329
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
326
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
330
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.395.0
|
|
327
331
|
rdoc_options: []
|
|
328
332
|
require_paths:
|
|
329
333
|
- lib
|