dependabot-uv 0.355.0 → 0.356.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.
@@ -1,96 +1,16 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "dependabot/uv/requirement_parser"
4
+ require "dependabot/python/file_updater/requirement_file_updater"
5
5
  require "dependabot/uv/file_updater"
6
- require "dependabot/shared_helpers"
7
- require "dependabot/uv/native_helpers"
8
- require "sorbet-runtime"
9
6
 
10
7
  module Dependabot
11
8
  module Uv
12
9
  class FileUpdater
13
- class RequirementFileUpdater
14
- extend T::Sig
15
-
16
- require_relative "requirement_replacer"
17
-
18
- sig { returns(T::Array[Dependency]) }
19
- attr_reader :dependencies
20
-
21
- sig { returns(T::Array[DependencyFile]) }
22
- attr_reader :dependency_files
23
-
24
- sig { returns(T::Array[Dependabot::Credential]) }
25
- attr_reader :credentials
26
-
27
- sig do
28
- params(
29
- dependencies: T::Array[Dependency],
30
- dependency_files: T::Array[DependencyFile],
31
- credentials: T::Array[Dependabot::Credential],
32
- index_urls: T.nilable(T::Array[T.nilable(String)])
33
- ).void
34
- end
35
- def initialize(dependencies:, dependency_files:, credentials:, index_urls: nil)
36
- @dependencies = dependencies
37
- @dependency_files = dependency_files
38
- @credentials = credentials
39
- @index_urls = index_urls
40
- @updated_dependency_files = T.let(nil, T.nilable(T::Array[DependencyFile]))
41
- end
42
-
43
- sig { returns(T::Array[Dependabot::DependencyFile]) }
44
- def updated_dependency_files
45
- @updated_dependency_files ||= fetch_updated_dependency_files
46
- end
47
-
48
- private
49
-
50
- sig { returns(Dependency) }
51
- def dependency
52
- # For now, we'll only ever be updating a single dependency
53
- T.must(dependencies.first)
54
- end
55
-
56
- sig { returns(T::Array[DependencyFile]) }
57
- def fetch_updated_dependency_files
58
- previous_requirements = dependency.previous_requirements || []
59
- reqs = dependency.requirements.zip(previous_requirements)
60
-
61
- reqs.filter_map do |(new_req, old_req)|
62
- next if new_req == old_req
63
-
64
- file = get_original_file(new_req.fetch(:file)).dup
65
- updated_content =
66
- updated_requirement_or_setup_file_content(new_req, T.must(old_req))
67
- next if updated_content == file&.content
68
-
69
- file&.content = updated_content
70
- file
71
- end
72
- end
73
-
74
- sig { params(new_req: T::Hash[Symbol, T.untyped], old_req: T::Hash[Symbol, T.untyped]).returns(String) }
75
- def updated_requirement_or_setup_file_content(new_req, old_req)
76
- original_file = get_original_file(new_req.fetch(:file))
77
- raise "Could not find a dependency file for #{new_req}" unless original_file
78
-
79
- RequirementReplacer.new(
80
- content: T.must(original_file.content),
81
- dependency_name: dependency.name,
82
- old_requirement: old_req.fetch(:requirement),
83
- new_requirement: new_req.fetch(:requirement),
84
- new_hash_version: dependency.version,
85
- index_urls: @index_urls
86
- ).updated_content
87
- end
88
-
89
- sig { params(filename: String).returns(T.nilable(DependencyFile)) }
90
- def get_original_file(filename)
91
- dependency_files.find { |f| f.name == filename }
92
- end
93
- end
10
+ # UV uses the same requirement file update logic as Python.
11
+ # Both ecosystems share RequirementReplacer and NativeHelpers
12
+ # via aliases, so we reuse Python's implementation.
13
+ RequirementFileUpdater = Dependabot::Python::FileUpdater::RequirementFileUpdater
94
14
  end
95
15
  end
96
16
  end
@@ -1,257 +1,16 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/dependency"
7
- require "dependabot/uv/requirement_parser"
4
+ require "dependabot/python/file_updater/requirement_replacer"
8
5
  require "dependabot/uv/file_updater"
9
- require "dependabot/shared_helpers"
10
- require "dependabot/uv/native_helpers"
11
- require "dependabot/uv/name_normaliser"
12
6
 
13
7
  module Dependabot
14
8
  module Uv
15
9
  class FileUpdater
16
- class RequirementReplacer
17
- extend T::Sig
18
-
19
- PACKAGE_NOT_FOUND_ERROR = "PackageNotFoundError"
20
-
21
- CERTIFICATE_VERIFY_FAILED = /CERTIFICATE_VERIFY_FAILED/
22
-
23
- sig do
24
- params(
25
- content: String,
26
- dependency_name: String,
27
- old_requirement: T.nilable(String),
28
- new_requirement: T.nilable(String),
29
- new_hash_version: T.nilable(String),
30
- index_urls: T.nilable(T::Array[T.nilable(String)])
31
- ).void
32
- end
33
- def initialize(
34
- content:,
35
- dependency_name:,
36
- old_requirement:,
37
- new_requirement:,
38
- new_hash_version: nil,
39
- index_urls: nil
40
- )
41
- @content = T.let(content, String)
42
- @dependency_name = T.let(normalise(dependency_name), String)
43
- @old_requirement = T.let(old_requirement, T.nilable(String))
44
- @new_requirement = T.let(new_requirement, T.nilable(String))
45
- @new_hash_version = T.let(new_hash_version, T.nilable(String))
46
- @index_urls = T.let(index_urls, T.nilable(T::Array[T.nilable(String)]))
47
- end
48
-
49
- sig { returns(String) }
50
- def updated_content
51
- updated_content =
52
- content.gsub(original_declaration_replacement_regex) do |mtch|
53
- # If the "declaration" is setting an option (e.g., no-binary)
54
- # ignore it, since it isn't actually a declaration
55
- next mtch if Regexp.last_match&.pre_match&.match?(/--.*\z/)
56
-
57
- updated_dependency_declaration_string
58
- end
59
-
60
- raise "Expected content to change!" if old_requirement != new_requirement && content == updated_content
61
-
62
- updated_content
63
- end
64
-
65
- private
66
-
67
- sig { returns(String) }
68
- attr_reader :content
69
-
70
- sig { returns(String) }
71
- attr_reader :dependency_name
72
-
73
- sig { returns(T.nilable(String)) }
74
- attr_reader :old_requirement
75
-
76
- sig { returns(T.nilable(String)) }
77
- attr_reader :new_requirement
78
-
79
- sig { returns(T.nilable(String)) }
80
- attr_reader :new_hash_version
81
-
82
- sig { returns(T::Boolean) }
83
- def update_hashes?
84
- !new_hash_version.nil?
85
- end
86
-
87
- sig { returns(T.nilable(String)) }
88
- def updated_requirement_string
89
- new_req_string = new_requirement
90
-
91
- new_req_string = new_req_string&.gsub(/,\s*/, ", ") if add_space_after_commas?
92
-
93
- if add_space_after_operators?
94
- new_req_string =
95
- new_req_string
96
- &.gsub(/(#{RequirementParser::COMPARISON})\s*(?=\d)/o, '\1 ')
97
- end
98
-
99
- new_req_string
100
- end
101
-
102
- sig { returns(String) }
103
- def updated_dependency_declaration_string
104
- old_req = old_requirement
105
- updated_string =
106
- if old_req
107
- original_dependency_declaration_string(old_req)
108
- .sub(RequirementParser::REQUIREMENTS, updated_requirement_string || "")
109
- else
110
- original_dependency_declaration_string(old_req)
111
- .sub(RequirementParser::NAME_WITH_EXTRAS) do |nm|
112
- nm + (updated_requirement_string || "")
113
- end
114
- end
115
-
116
- return updated_string unless update_hashes? && requirement_includes_hashes?(old_req)
117
-
118
- updated_string.sub(
119
- RequirementParser::HASHES,
120
- package_hashes_for(
121
- name: dependency_name,
122
- version: new_hash_version,
123
- algorithm: hash_algorithm(old_req)
124
- ).join(hash_separator(old_req) || "")
125
- )
126
- end
127
-
128
- sig { returns(T::Boolean) }
129
- def add_space_after_commas?
130
- original_dependency_declaration_string(old_requirement)
131
- .match(RequirementParser::REQUIREMENTS)
132
- .to_s.include?(", ")
133
- end
134
-
135
- sig { returns(T::Boolean) }
136
- def add_space_after_operators?
137
- original_dependency_declaration_string(old_requirement)
138
- .match(RequirementParser::REQUIREMENTS)
139
- .to_s.match?(/#{RequirementParser::COMPARISON}\s+\d/o)
140
- end
141
-
142
- sig { returns(Regexp) }
143
- def original_declaration_replacement_regex
144
- original_string =
145
- original_dependency_declaration_string(old_requirement)
146
- /(?<![\-\w\.\[])#{Regexp.escape(original_string)}(?![\-\w\.])/
147
- end
148
-
149
- sig { params(requirement: T.nilable(String)).returns(T::Boolean) }
150
- def requirement_includes_hashes?(requirement)
151
- original_dependency_declaration_string(requirement)
152
- .match?(RequirementParser::HASHES)
153
- end
154
-
155
- sig { params(requirement: T.nilable(String)).returns(T.nilable(String)) }
156
- def hash_algorithm(requirement)
157
- return unless requirement_includes_hashes?(requirement)
158
-
159
- matches = original_dependency_declaration_string(requirement).match(RequirementParser::HASHES)
160
- return unless matches
161
-
162
- matches.named_captures.fetch("algorithm")
163
- end
164
-
165
- sig { params(requirement: T.nilable(String)).returns(T.nilable(String)) }
166
- def hash_separator(requirement)
167
- return unless requirement_includes_hashes?(requirement)
168
-
169
- hash_regex = RequirementParser::HASH
170
- match_result = original_dependency_declaration_string(requirement)
171
- .match(/#{hash_regex}((?<separator>\s*\\?\s*?)#{hash_regex})*/)
172
- current_separator = match_result&.named_captures&.fetch("separator", nil)
173
-
174
- default_match = original_dependency_declaration_string(requirement)
175
- .match(RequirementParser::HASH)
176
- default_separator = default_match&.pre_match&.match(/(?<separator>\s*\\?\s*?)\z/)
177
- &.named_captures&.fetch("separator", nil)
178
-
179
- current_separator || default_separator
180
- end
181
-
182
- sig { params(name: String, version: T.nilable(String), algorithm: T.nilable(String)).returns(T::Array[String]) }
183
- def package_hashes_for(name:, version:, algorithm:)
184
- index_urls = @index_urls || [nil]
185
-
186
- index_urls.map do |index_url|
187
- args = [name, version, algorithm]
188
- args << index_url unless index_url.nil?
189
-
190
- begin
191
- result = SharedHelpers.run_helper_subprocess(
192
- command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
193
- function: "get_dependency_hash",
194
- args: args
195
- )
196
- rescue SharedHelpers::HelperSubprocessFailed => e
197
- requirement_error_handler(e)
198
-
199
- raise unless e.message.include?("PackageNotFoundError")
200
-
201
- next
202
- end
203
-
204
- return result.map { |h| "--hash=#{algorithm}:#{h['hash']}" } if result.is_a?(Array)
205
- end
206
-
207
- raise Dependabot::DependencyFileNotResolvable, "Unable to find hashes for package #{name}"
208
- end
209
-
210
- sig { params(old_req: T.nilable(String)).returns(String) }
211
- def original_dependency_declaration_string(old_req)
212
- matches = []
213
-
214
- dec =
215
- if old_req.nil?
216
- regex = RequirementParser::INSTALL_REQ_WITHOUT_REQUIREMENT
217
- content.scan(regex) { matches << Regexp.last_match }
218
- matches.find { |m| normalise(m[:name]) == dependency_name }
219
- else
220
- regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
221
- content.scan(regex) { matches << Regexp.last_match }
222
- matches
223
- .select { |m| normalise(m[:name]) == dependency_name }
224
- .find { |m| requirements_match(m[:requirements], old_req) }
225
- end
226
-
227
- raise "Declaration not found for #{dependency_name}!" unless dec
228
-
229
- dec.to_s.strip
230
- end
231
-
232
- sig { params(name: String).returns(String) }
233
- def normalise(name)
234
- NameNormaliser.normalise(name)
235
- end
236
-
237
- sig { params(req1: T.nilable(String), req2: T.nilable(String)).returns(T::Boolean) }
238
- def requirements_match(req1, req2)
239
- req1&.split(",")&.map { |r| r.gsub(/\s/, "") }&.sort ==
240
- req2&.split(",")&.map { |r| r.gsub(/\s/, "") }&.sort
241
- end
242
-
243
- public
244
-
245
- sig { params(error: Exception).void }
246
- def requirement_error_handler(error)
247
- Dependabot.logger.warn(error.message)
248
-
249
- return unless error.message.match?(CERTIFICATE_VERIFY_FAILED)
250
-
251
- msg = "Error resolving dependency."
252
- raise DependencyFileNotResolvable, msg
253
- end
254
- end
10
+ # UV uses the same requirement replacement logic as Python.
11
+ # Both ecosystems share RequirementParser, NativeHelpers, and NameNormaliser
12
+ # via aliases, so we reuse Python's implementation.
13
+ RequirementReplacer = Dependabot::Python::FileUpdater::RequirementReplacer
255
14
  end
256
15
  end
257
16
  end
@@ -1,37 +1,16 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require "dependabot/python/update_checker/latest_version_finder"
5
5
  require "dependabot/uv/update_checker"
6
- require "dependabot/uv/package"
7
- require "dependabot/package/package_latest_version_finder"
8
6
 
9
7
  module Dependabot
10
8
  module Uv
11
9
  class UpdateChecker
12
- # UV uses the same PyPI registry for package lookups as Python
13
- class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
14
- extend T::Sig
15
-
16
- sig do
17
- override.returns(T.nilable(Dependabot::Package::PackageDetails))
18
- end
19
- def package_details
20
- @package_details ||= T.let(
21
- Package::PackageDetailsFetcher.new(
22
- dependency: dependency,
23
- dependency_files: dependency_files,
24
- credentials: credentials
25
- ).fetch,
26
- T.nilable(Dependabot::Package::PackageDetails)
27
- )
28
- end
29
-
30
- sig { override.returns(T::Boolean) }
31
- def cooldown_enabled?
32
- true
33
- end
34
- end
10
+ # UV uses the same PyPI registry for package lookups as Python.
11
+ # Both ecosystems use the same PackageDetailsFetcher (via Uv::Package alias)
12
+ # and identical LatestVersionFinder logic, so we reuse Python's implementation.
13
+ LatestVersionFinder = Dependabot::Python::UpdateChecker::LatestVersionFinder
35
14
  end
36
15
  end
37
16
  end
@@ -497,7 +497,7 @@ module Dependabot
497
497
 
498
498
  sig { returns(T::Hash[String, T::Array[String]]) }
499
499
  def requirement_map
500
- child_req_regex = Uv::FileFetcher::CHILD_REQUIREMENT_REGEX
500
+ child_req_regex = Python::SharedFileFetcher::CHILD_REQUIREMENT_REGEX
501
501
  @requirement_map ||= T.let(
502
502
  pip_compile_files.each_with_object({}) do |file, req_map|
503
503
  paths = T.must(file.content).scan(child_req_regex).flatten
@@ -1,118 +1,16 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/uv/language_version_manager"
4
+ require "dependabot/python/update_checker/pip_version_resolver"
7
5
  require "dependabot/uv/update_checker"
8
- require "dependabot/uv/update_checker/latest_version_finder"
9
- require "dependabot/uv/file_parser/python_requirement_parser"
10
6
 
11
7
  module Dependabot
12
8
  module Uv
13
9
  class UpdateChecker
14
- class PipVersionResolver
15
- extend T::Sig
16
-
17
- sig do
18
- params(
19
- dependency: Dependabot::Dependency,
20
- dependency_files: T::Array[Dependabot::DependencyFile],
21
- credentials: T::Array[Dependabot::Credential],
22
- ignored_versions: T::Array[String],
23
- security_advisories: T::Array[Dependabot::SecurityAdvisory],
24
- update_cooldown: T.nilable(Dependabot::Package::ReleaseCooldownOptions),
25
- raise_on_ignored: T::Boolean
26
- ).void
27
- end
28
- def initialize(
29
- dependency:,
30
- dependency_files:,
31
- credentials:,
32
- ignored_versions:,
33
- security_advisories:,
34
- update_cooldown: nil,
35
- raise_on_ignored: false
36
- )
37
- @dependency = T.let(dependency, Dependabot::Dependency)
38
- @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
39
- @credentials = T.let(credentials, T::Array[Dependabot::Credential])
40
- @ignored_versions = T.let(ignored_versions, T::Array[String])
41
- @update_cooldown = T.let(update_cooldown, T.nilable(Dependabot::Package::ReleaseCooldownOptions))
42
- @raise_on_ignored = T.let(raise_on_ignored, T::Boolean)
43
- @security_advisories = T.let(security_advisories, T::Array[Dependabot::SecurityAdvisory])
44
- end
45
-
46
- sig { returns(T.nilable(Dependabot::Version)) }
47
- def latest_resolvable_version
48
- latest_version_finder.latest_version(language_version: language_version_manager.python_version)
49
- end
50
-
51
- sig { returns(T.nilable(Dependabot::Version)) }
52
- def latest_resolvable_version_with_no_unlock
53
- latest_version_finder
54
- .latest_version_with_no_unlock(language_version: language_version_manager.python_version)
55
- end
56
-
57
- sig { returns(T.nilable(Dependabot::Version)) }
58
- def lowest_resolvable_security_fix_version
59
- latest_version_finder
60
- .lowest_security_fix_version(language_version: language_version_manager.python_version)
61
- end
62
-
63
- private
64
-
65
- sig { returns(Dependabot::Dependency) }
66
- attr_reader :dependency
67
-
68
- sig { returns(T::Array[Dependabot::DependencyFile]) }
69
- attr_reader :dependency_files
70
-
71
- sig { returns(T::Array[Dependabot::Credential]) }
72
- attr_reader :credentials
73
-
74
- sig { returns(T::Array[String]) }
75
- attr_reader :ignored_versions
76
-
77
- sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
78
- attr_reader :security_advisories
79
-
80
- sig { returns(LatestVersionFinder) }
81
- def latest_version_finder
82
- @latest_version_finder ||= T.let(
83
- LatestVersionFinder.new(
84
- dependency: dependency,
85
- dependency_files: dependency_files,
86
- credentials: credentials,
87
- ignored_versions: ignored_versions,
88
- raise_on_ignored: @raise_on_ignored,
89
- cooldown_options: @update_cooldown,
90
- security_advisories: security_advisories
91
- ),
92
- T.nilable(LatestVersionFinder)
93
- )
94
- end
95
-
96
- sig { returns(FileParser::PythonRequirementParser) }
97
- def python_requirement_parser
98
- @python_requirement_parser ||= T.let(
99
- FileParser::PythonRequirementParser.new(
100
- dependency_files: dependency_files
101
- ),
102
- T.nilable(FileParser::PythonRequirementParser)
103
- )
104
- end
105
-
106
- sig { returns(LanguageVersionManager) }
107
- def language_version_manager
108
- @language_version_manager ||= T.let(
109
- LanguageVersionManager.new(
110
- python_requirement_parser: python_requirement_parser
111
- ),
112
- T.nilable(LanguageVersionManager)
113
- )
114
- end
115
- end
10
+ # UV uses the same pip version resolution logic as Python.
11
+ # Both ecosystems share LanguageVersionManager, LatestVersionFinder,
12
+ # and PythonRequirementParser via aliases, so we reuse Python's implementation.
13
+ PipVersionResolver = Dependabot::Python::UpdateChecker::PipVersionResolver
116
14
  end
117
15
  end
118
16
  end