dependabot-uv 0.349.0 → 0.351.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,288 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "toml-rb"
5
- require "sorbet-runtime"
6
- require "dependabot/dependency"
7
- require "dependabot/dependency_file"
8
- require "dependabot/credential"
9
- require "dependabot/uv/update_checker"
10
- require "dependabot/uv/authed_url_builder"
11
- require "dependabot/errors"
12
-
13
- module Dependabot
14
- module Uv
15
- module Package
16
- class PackageRegistryFinder
17
- extend T::Sig
18
-
19
- PYPI_BASE_URL = "https://pypi.org/simple/"
20
- ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
21
-
22
- UrlsHash = T.type_alias { { main: T.nilable(String), extra: T::Array[String] } }
23
-
24
- sig do
25
- params(
26
- dependency_files: T::Array[Dependabot::DependencyFile],
27
- credentials: T::Array[Dependabot::Credential],
28
- dependency: Dependabot::Dependency
29
- ).void
30
- end
31
- def initialize(dependency_files:, credentials:, dependency:)
32
- @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
33
- @credentials = T.let(credentials, T::Array[Dependabot::Credential])
34
- @dependency = T.let(dependency, Dependabot::Dependency)
35
- end
36
-
37
- sig { returns(T::Array[String]) }
38
- def registry_urls
39
- extra_index_urls =
40
- config_variable_index_urls[:extra] +
41
- pipfile_index_urls[:extra] +
42
- requirement_file_index_urls[:extra] +
43
- pip_conf_index_urls[:extra] +
44
- pyproject_index_urls[:extra]
45
-
46
- extra_index_urls = extra_index_urls.map do |url|
47
- clean_check_and_remove_environment_variables(url)
48
- end
49
-
50
- # URL encode any `@` characters within registry URL creds.
51
- # TODO: The test that fails if the `map` here is removed is likely a
52
- # bug in Ruby's URI parser, and should be fixed there.
53
- [main_index_url, *extra_index_urls].map do |url|
54
- url.rpartition("@").tap { |a| a.first.gsub!("@", "%40") }.join
55
- end.uniq
56
- end
57
-
58
- private
59
-
60
- sig { returns(T::Array[Dependabot::DependencyFile]) }
61
- attr_reader :dependency_files
62
-
63
- sig { returns(T::Array[Dependabot::Credential]) }
64
- attr_reader :credentials
65
-
66
- sig { returns(String) }
67
- def main_index_url
68
- url =
69
- config_variable_index_urls[:main] ||
70
- pipfile_index_urls[:main] ||
71
- requirement_file_index_urls[:main] ||
72
- pip_conf_index_urls[:main] ||
73
- pyproject_index_urls[:main] ||
74
- PYPI_BASE_URL
75
-
76
- clean_check_and_remove_environment_variables(url)
77
- end
78
-
79
- sig { returns(UrlsHash) }
80
- def requirement_file_index_urls
81
- urls = T.let({ main: nil, extra: [] }, UrlsHash)
82
-
83
- requirements_files.each do |file|
84
- content = file.content
85
- next unless content
86
-
87
- if content.match?(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
88
- match_result = content.match(/^--index-url\s+['"]?([^\s'"]+)['"]?/)
89
- urls[:main] = match_result&.captures&.first&.strip
90
- end
91
- extra_urls = urls[:extra]
92
- extra_urls +=
93
- content
94
- .scan(/^--extra-index-url\s+['"]?([^\s'"]+)['"]?/)
95
- .flatten
96
- .map(&:strip)
97
- urls[:extra] = extra_urls
98
- end
99
-
100
- urls
101
- end
102
-
103
- sig { returns(UrlsHash) }
104
- def pip_conf_index_urls
105
- urls = T.let({ main: nil, extra: [] }, UrlsHash)
106
-
107
- return urls unless pip_conf
108
-
109
- pip_conf_file = pip_conf
110
- return urls unless pip_conf_file
111
-
112
- content = pip_conf_file.content
113
- return urls unless content
114
-
115
- if content.match?(/^index-url\s*=/x)
116
- match_result = content.match(/^index-url\s*=\s*(.+)/)
117
- urls[:main] = match_result&.captures&.first
118
- end
119
- extra_urls = urls[:extra]
120
- extra_urls += content.scan(/^extra-index-url\s*=(.+)/).flatten
121
- urls[:extra] = extra_urls
122
-
123
- urls
124
- end
125
-
126
- sig { returns(UrlsHash) }
127
- def pipfile_index_urls
128
- urls = T.let({ main: nil, extra: [] }, UrlsHash)
129
- begin
130
- return urls unless pipfile
131
-
132
- pipfile_file = pipfile
133
- return urls unless pipfile_file
134
-
135
- content = pipfile_file.content
136
- return urls unless content
137
-
138
- pipfile_object = TomlRB.parse(content)
139
-
140
- urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
141
-
142
- pipfile_object["source"]&.each do |source|
143
- urls[:extra] << source.fetch("url") if source["url"]
144
- end
145
- urls[:extra] = urls[:extra].uniq
146
-
147
- urls
148
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
149
- urls
150
- end
151
- end
152
-
153
- # rubocop:disable Metrics/PerceivedComplexity
154
- sig { returns(UrlsHash) }
155
- def pyproject_index_urls
156
- urls = T.let({ main: nil, extra: [] }, UrlsHash)
157
-
158
- begin
159
- return urls unless pyproject
160
-
161
- pyproject_file = pyproject
162
- return urls unless pyproject_file
163
-
164
- pyproject_content = pyproject_file.content
165
- return urls unless pyproject_content
166
-
167
- sources =
168
- TomlRB.parse(pyproject_content).dig("tool", "poetry", "source") ||
169
- []
170
-
171
- sources.each do |source|
172
- # If source is PyPI, skip it, and let it pick the default URI
173
- next if source["name"].casecmp?("PyPI")
174
-
175
- if @dependency.all_sources.include?(source["name"])
176
- # If dependency has specified this source, use it
177
- return { main: source["url"], extra: [] }
178
- elsif source["default"]
179
- urls[:main] = source["url"]
180
- elsif source["priority"] != "explicit"
181
- # if source is not explicit, add it to extra
182
- urls[:extra] << source["url"]
183
- end
184
- end
185
- urls[:extra] = urls[:extra].uniq
186
-
187
- urls
188
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
189
- urls
190
- end
191
- end
192
- # rubocop:enable Metrics/PerceivedComplexity
193
-
194
- sig { returns(UrlsHash) }
195
- def config_variable_index_urls
196
- urls = T.let({ main: nil, extra: [] }, UrlsHash)
197
-
198
- index_url_creds = credentials
199
- .select { |cred| cred["type"] == "python_index" }
200
-
201
- if (main_cred = index_url_creds.find(&:replaces_base?))
202
- urls[:main] = AuthedUrlBuilder.authed_url(credential: main_cred)
203
- end
204
-
205
- urls[:extra] =
206
- index_url_creds
207
- .reject(&:replaces_base?)
208
- .map { |cred| AuthedUrlBuilder.authed_url(credential: cred) }
209
-
210
- urls
211
- end
212
-
213
- sig { params(url: String).returns(String) }
214
- def clean_check_and_remove_environment_variables(url)
215
- url = url.strip.sub(%r{/+$}, "") + "/"
216
-
217
- return authed_base_url(url) unless url.match?(ENVIRONMENT_VARIABLE_REGEX)
218
-
219
- config_variable_urls =
220
- [
221
- config_variable_index_urls[:main],
222
- *config_variable_index_urls[:extra]
223
- ]
224
- .compact
225
- .map { |u| u.strip.gsub(%r{/*$}, "") + "/" }
226
-
227
- regexp = url
228
- .sub(%r{(?<=://).+@}, "")
229
- .sub(%r{https?://}, "")
230
- .split(ENVIRONMENT_VARIABLE_REGEX)
231
- .map { |part| Regexp.quote(part) }
232
- .join(".+")
233
- authed_url = config_variable_urls.find { |u| u.match?(regexp) }
234
- return authed_url if authed_url
235
-
236
- cleaned_url = url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}o, "")
237
- authed_url = authed_base_url(cleaned_url)
238
- return authed_url if credential_for(cleaned_url)
239
-
240
- raise PrivateSourceAuthenticationFailure, url
241
- end
242
-
243
- sig { params(base_url: String).returns(String) }
244
- def authed_base_url(base_url)
245
- cred = credential_for(base_url)
246
- return base_url unless cred
247
-
248
- AuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$}, "") + "/"
249
- end
250
-
251
- sig { params(url: String).returns(T.nilable(Dependabot::Credential)) }
252
- def credential_for(url)
253
- credentials
254
- .select { |c| c["type"] == "python_index" }
255
- .find do |c|
256
- cred_url = c.fetch("index-url").gsub(%r{/*$}, "") + "/"
257
- cred_url.include?(url)
258
- end
259
- end
260
-
261
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
262
- def pip_conf
263
- dependency_files.find { |f| f.name == "pip.conf" }
264
- end
265
-
266
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
267
- def pipfile
268
- dependency_files.find { |f| f.name == "Pipfile" }
269
- end
270
-
271
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
272
- def pyproject
273
- dependency_files.find { |f| f.name == "pyproject.toml" }
274
- end
275
-
276
- sig { returns(T::Array[Dependabot::DependencyFile]) }
277
- def requirements_files
278
- dependency_files.select { |f| f.name.match?(/requirements/x) }
279
- end
280
-
281
- sig { returns(T::Array[Dependabot::DependencyFile]) }
282
- def pip_compile_files
283
- dependency_files.select { |f| f.name.end_with?(".in") }
284
- end
285
- end
286
- end
287
- end
288
- end
@@ -1,110 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "dependabot/shared_helpers"
5
- require "dependabot/uv/file_parser"
6
- require "json"
7
- require "sorbet-runtime"
8
-
9
- module Dependabot
10
- module Uv
11
- class PipenvRunner
12
- extend T::Sig
13
-
14
- sig do
15
- params(
16
- dependency: Dependabot::Dependency,
17
- lockfile: T.nilable(Dependabot::DependencyFile),
18
- language_version_manager: LanguageVersionManager
19
- )
20
- .void
21
- end
22
- def initialize(dependency:, lockfile:, language_version_manager:)
23
- @dependency = dependency
24
- @lockfile = lockfile
25
- @language_version_manager = language_version_manager
26
- end
27
-
28
- sig { params(constraint: String).returns(String) }
29
- def run_upgrade(constraint)
30
- constraint = "" if constraint == "*"
31
- command = "pyenv exec pipenv upgrade --verbose #{dependency_name}#{constraint}"
32
- command << " --dev" if lockfile_section == "develop"
33
-
34
- run(command, fingerprint: "pyenv exec pipenv upgrade --verbose <dependency_name><constraint>")
35
- end
36
-
37
- sig { params(constraint: String).returns(T.nilable(String)) }
38
- def run_upgrade_and_fetch_version(constraint)
39
- run_upgrade(constraint)
40
-
41
- updated_lockfile = JSON.parse(File.read("Pipfile.lock"))
42
-
43
- fetch_version_from_parsed_lockfile(updated_lockfile)
44
- end
45
-
46
- sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
47
- def run(command, fingerprint: nil)
48
- run_command(
49
- "pyenv local #{language_version_manager.python_major_minor}",
50
- fingerprint: "pyenv local <python_major_minor>"
51
- )
52
-
53
- run_command(command, fingerprint: fingerprint)
54
- end
55
-
56
- private
57
-
58
- sig { returns(Dependabot::Dependency) }
59
- attr_reader :dependency
60
-
61
- sig { returns(T.nilable(Dependabot::DependencyFile)) }
62
- attr_reader :lockfile
63
-
64
- sig { returns(LanguageVersionManager) }
65
- attr_reader :language_version_manager
66
-
67
- sig { params(updated_lockfile: T::Hash[String, T.untyped]).returns(T.nilable(String)) }
68
- def fetch_version_from_parsed_lockfile(updated_lockfile)
69
- deps = updated_lockfile[lockfile_section] || {}
70
-
71
- deps.dig(dependency_name, "version")
72
- &.gsub(/^==/, "")
73
- end
74
-
75
- sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
76
- def run_command(command, fingerprint: nil)
77
- SharedHelpers.run_shell_command(command, env: pipenv_env_variables, fingerprint: fingerprint)
78
- end
79
-
80
- sig { returns(String) }
81
- def lockfile_section
82
- if dependency.requirements.any?
83
- T.must(dependency.requirements.first)[:groups].first
84
- else
85
- Uv::FileParser::DEPENDENCY_GROUP_KEYS.each do |keys|
86
- section = keys.fetch(:lockfile)
87
- return section if JSON.parse(T.must(T.must(lockfile).content))[section].keys.any?(dependency_name)
88
- end
89
- end
90
- end
91
-
92
- sig { returns(String) }
93
- def dependency_name
94
- dependency.metadata[:original_name] || dependency.name
95
- end
96
-
97
- sig { returns(T::Hash[String, String]) }
98
- def pipenv_env_variables
99
- {
100
- "PIPENV_YES" => "true", # Install new Python ver if needed
101
- "PIPENV_MAX_RETRIES" => "3", # Retry timeouts
102
- "PIPENV_NOSPIN" => "1", # Don't pollute logs with spinner
103
- "PIPENV_TIMEOUT" => "600", # Set install timeout to 10 minutes
104
- "PIP_DEFAULT_TIMEOUT" => "60", # Set pip timeout to 1 minute
105
- "COLUMNS" => "250" # Avoid line wrapping
106
- }
107
- end
108
- end
109
- end
110
- end