dependabot-python 0.301.1 → 0.303.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,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "open3"
@@ -10,78 +10,120 @@ require "dependabot/python/language_version_manager"
10
10
  require "dependabot/shared_helpers"
11
11
  require "dependabot/python/native_helpers"
12
12
  require "dependabot/python/pipenv_runner"
13
+ require "sorbet-runtime"
13
14
 
14
15
  module Dependabot
15
16
  module Python
16
17
  class FileUpdater
17
18
  class PipfileFileUpdater
19
+ extend T::Sig
18
20
  require_relative "pipfile_preparer"
19
21
  require_relative "pipfile_manifest_updater"
20
22
  require_relative "setup_file_sanitizer"
21
23
 
22
- DEPENDENCY_TYPES = %w(packages dev-packages).freeze
24
+ DEPENDENCY_TYPES = T.let(%w(packages dev-packages).freeze, T::Array[String])
23
25
 
26
+ sig { returns(T::Array[Dependabot::Dependency]) }
24
27
  attr_reader :dependencies
28
+
29
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
25
30
  attr_reader :dependency_files
31
+
32
+ sig { returns(T::Array[Dependabot::Credential]) }
26
33
  attr_reader :credentials
34
+
35
+ sig { returns(T.nilable(String)) }
27
36
  attr_reader :repo_contents_path
28
37
 
38
+ # rubocop:disable Metrics/AbcSize
39
+ sig do
40
+ params(
41
+ dependencies: T::Array[Dependabot::Dependency],
42
+ dependency_files: T::Array[Dependabot::DependencyFile],
43
+ credentials: T::Array[Dependabot::Credential],
44
+ repo_contents_path: T.nilable(String)
45
+ ).void
46
+ end
29
47
  def initialize(dependencies:, dependency_files:, credentials:, repo_contents_path:)
30
48
  @dependencies = dependencies
31
49
  @dependency_files = dependency_files
32
50
  @credentials = credentials
33
51
  @repo_contents_path = repo_contents_path
34
- end
35
-
52
+ @updated_pipfile_content = T.let(nil, T.nilable(String))
53
+ @updated_lockfile_content = T.let(nil, T.nilable(String))
54
+ @updated_generated_files = T.let(nil, T.nilable(T::Hash[Symbol, String]))
55
+ @pipfile = T.let(nil, T.nilable(Dependabot::DependencyFile))
56
+ @lockfile = T.let(nil, T.nilable(Dependabot::DependencyFile))
57
+ @setup_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
58
+ @setup_cfg_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
59
+ @requirements_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
60
+ @updated_dependency_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
61
+ @updated_pipfile_content = T.let(nil, T.nilable(String))
62
+ @parsed_lockfile = T.let(nil, T.nilable(T::Hash[String, T::Hash[String, Object]]))
63
+ @pipenv_runner = T.let(nil, T.nilable(PipenvRunner))
64
+ @language_version_manager = T.let(nil, T.nilable(LanguageVersionManager))
65
+ @sanitized_setup_file_content = T.let({}, T.untyped)
66
+ @python_requirement_parser = T.let(nil, T.nilable(FileParser::PythonRequirementParser))
67
+ end
68
+
69
+ # rubocop:enable Metrics/AbcSize
70
+
71
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
36
72
  def updated_dependency_files
37
73
  @updated_dependency_files ||= fetch_updated_dependency_files
38
74
  end
39
75
 
40
76
  private
41
77
 
78
+ sig { returns(T.nilable(Dependabot::Dependency)) }
42
79
  def dependency
43
80
  # For now, we'll only ever be updating a single dependency
44
81
  dependencies.first
45
82
  end
46
83
 
84
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
47
85
  def fetch_updated_dependency_files
48
86
  updated_files = []
49
87
 
50
- if pipfile.content != updated_pipfile_content
88
+ if pipfile&.content != updated_pipfile_content
51
89
  updated_files <<
52
- updated_file(file: pipfile, content: updated_pipfile_content)
90
+ updated_file(file: T.must(pipfile), content: T.must(updated_pipfile_content))
53
91
  end
54
92
 
55
93
  if lockfile
56
- raise "Expected Pipfile.lock to change!" if lockfile.content == updated_lockfile_content
94
+ raise "Expected Pipfile.lock to change!" if lockfile&.content == updated_lockfile_content
57
95
 
58
96
  updated_files <<
59
- updated_file(file: lockfile, content: updated_lockfile_content)
97
+ updated_file(file: T.must(lockfile), content: updated_lockfile_content)
60
98
  end
61
99
 
62
100
  updated_files += updated_generated_requirements_files
63
101
  updated_files
64
102
  end
65
103
 
104
+ sig { returns(T.nilable(String)) }
66
105
  def updated_pipfile_content
67
106
  @updated_pipfile_content ||=
68
107
  PipfileManifestUpdater.new(
69
108
  dependencies: dependencies,
70
- manifest: pipfile
109
+ manifest: T.must(pipfile)
71
110
  ).updated_manifest_content
72
111
  end
73
112
 
113
+ sig { returns(String) }
74
114
  def updated_lockfile_content
75
115
  @updated_lockfile_content ||=
76
116
  updated_generated_files.fetch(:lockfile)
77
117
  end
78
118
 
119
+ sig { returns(T::Boolean) }
79
120
  def generate_updated_requirements_files?
80
121
  return true if generated_requirements_files("default").any?
81
122
 
82
123
  generated_requirements_files("develop").any?
83
124
  end
84
125
 
126
+ sig { params(type: String).returns(T::Array[Dependabot::DependencyFile]) }
85
127
  def generated_requirements_files(type)
86
128
  return [] unless lockfile
87
129
 
@@ -95,12 +137,13 @@ module Dependabot
95
137
  # generated using `pipenv requirements`
96
138
  requirements_files.select do |req_file|
97
139
  deps = []
98
- req_file.content.scan(regex) { deps << Regexp.last_match }
140
+ req_file.content&.scan(regex) { deps << Regexp.last_match }
99
141
  deps = deps.map { |m| m[:name] }
100
142
  deps.sort == pipfile_lock_deps
101
143
  end
102
144
  end
103
145
 
146
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
104
147
  def updated_generated_requirements_files
105
148
  updated_files = []
106
149
 
@@ -121,49 +164,56 @@ module Dependabot
121
164
  updated_files
122
165
  end
123
166
 
167
+ sig { returns(String) }
124
168
  def updated_req_content
125
169
  updated_generated_files.fetch(:requirements_txt)
126
170
  end
127
171
 
172
+ sig { returns(String) }
128
173
  def updated_dev_req_content
129
174
  updated_generated_files.fetch(:dev_requirements_txt)
130
175
  end
131
176
 
177
+ sig { returns(String) }
132
178
  def prepared_pipfile_content
133
179
  content = updated_pipfile_content
134
- content = add_private_sources(content)
180
+ content = add_private_sources(content.to_s)
135
181
  content = update_python_requirement(content)
136
- content = update_ssl_requirement(content, updated_pipfile_content)
182
+ content = update_ssl_requirement(content, updated_pipfile_content.to_s)
137
183
 
138
184
  content
139
185
  end
140
186
 
187
+ sig { params(pipfile_content: String).returns(String) }
141
188
  def update_python_requirement(pipfile_content)
142
189
  PipfilePreparer
143
190
  .new(pipfile_content: pipfile_content)
144
191
  .update_python_requirement(language_version_manager.python_major_minor)
145
192
  end
146
193
 
194
+ sig { params(pipfile_content: String, parsed_file: String).returns(String) }
147
195
  def update_ssl_requirement(pipfile_content, parsed_file)
148
196
  Python::FileUpdater::PipfilePreparer
149
197
  .new(pipfile_content: pipfile_content)
150
198
  .update_ssl_requirement(parsed_file)
151
199
  end
152
200
 
201
+ sig { params(pipfile_content: String).returns(String) }
153
202
  def add_private_sources(pipfile_content)
154
203
  PipfilePreparer
155
204
  .new(pipfile_content: pipfile_content)
156
205
  .replace_sources(credentials)
157
206
  end
158
207
 
208
+ sig { returns(T::Hash[Symbol, String]) }
159
209
  def updated_generated_files
160
210
  @updated_generated_files ||=
161
- SharedHelpers.in_a_temporary_repo_directory(dependency_files.first.directory, repo_contents_path) do
211
+ SharedHelpers.in_a_temporary_repo_directory(T.must(dependency_files.first).directory, repo_contents_path) do
162
212
  SharedHelpers.with_git_configured(credentials: credentials) do
163
213
  write_temporary_dependency_files(prepared_pipfile_content)
164
214
  install_required_python
165
215
 
166
- pipenv_runner.run_upgrade("==#{dependency.version}")
216
+ pipenv_runner&.run_upgrade("==#{dependency&.version}")
167
217
 
168
218
  result = { lockfile: File.read("Pipfile.lock") }
169
219
  result[:lockfile] = post_process_lockfile(result[:lockfile])
@@ -181,10 +231,11 @@ module Dependabot
181
231
  end
182
232
  end
183
233
 
234
+ sig { params(updated_lockfile_content: String).returns(String) }
184
235
  def post_process_lockfile(updated_lockfile_content)
185
- pipfile_hash = pipfile_hash_for(updated_pipfile_content)
186
- original_reqs = parsed_lockfile["_meta"]["requires"]
187
- original_source = parsed_lockfile["_meta"]["sources"]
236
+ pipfile_hash = pipfile_hash_for(updated_pipfile_content.to_s)
237
+ original_reqs = T.must(parsed_lockfile["_meta"])["requires"]
238
+ original_source = T.must(parsed_lockfile["_meta"])["sources"]
188
239
 
189
240
  new_lockfile = updated_lockfile_content.dup
190
241
  new_lockfile_json = JSON.parse(new_lockfile)
@@ -197,6 +248,7 @@ module Dependabot
197
248
  .gsub(/\}\z/, "}\n")
198
249
  end
199
250
 
251
+ sig { returns(Integer) }
200
252
  def generate_updated_requirements_files
201
253
  req_content = run_pipenv_command(
202
254
  "pyenv exec pipenv requirements"
@@ -209,14 +261,17 @@ module Dependabot
209
261
  File.write("dev-req.txt", dev_req_content)
210
262
  end
211
263
 
264
+ sig { params(command: String).returns(String) }
212
265
  def run_command(command)
213
266
  SharedHelpers.run_shell_command(command)
214
267
  end
215
268
 
269
+ sig { params(command: String).returns(String) }
216
270
  def run_pipenv_command(command)
217
- pipenv_runner.run(command)
271
+ T.must(pipenv_runner).run(command)
218
272
  end
219
273
 
274
+ sig { params(pipfile_content: Object).returns(Integer) }
220
275
  def write_temporary_dependency_files(pipfile_content)
221
276
  dependency_files.each do |file|
222
277
  path = file.name
@@ -243,6 +298,7 @@ module Dependabot
243
298
  File.write("Pipfile", pipfile_content)
244
299
  end
245
300
 
301
+ sig { returns(T.nilable(String)) }
246
302
  def install_required_python
247
303
  # Initialize a git repo to appease pip-tools
248
304
  begin
@@ -254,6 +310,7 @@ module Dependabot
254
310
  language_version_manager.install_required_python
255
311
  end
256
312
 
313
+ sig { params(file: Dependabot::DependencyFile).returns(String) }
257
314
  def sanitized_setup_file_content(file)
258
315
  @sanitized_setup_file_content ||= {}
259
316
  return @sanitized_setup_file_content[file.name] if @sanitized_setup_file_content[file.name]
@@ -264,12 +321,24 @@ module Dependabot
264
321
  .sanitized_content
265
322
  end
266
323
 
324
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) }
267
325
  def setup_cfg(file)
268
326
  dependency_files.find do |f|
269
327
  f.name == file.name.sub(/\.py$/, ".cfg")
270
328
  end
271
329
  end
272
330
 
331
+ sig do
332
+ params(
333
+ pipfile_content: String
334
+ ).returns(
335
+ T.nilable(
336
+ T.any(T::Hash[String, T.untyped],
337
+ String,
338
+ T::Array[T::Hash[String, T.untyped]])
339
+ )
340
+ )
341
+ end
273
342
  def pipfile_hash_for(pipfile_content)
274
343
  SharedHelpers.in_a_temporary_directory do |dir|
275
344
  File.write(File.join(dir, "Pipfile"), pipfile_content)
@@ -281,12 +350,14 @@ module Dependabot
281
350
  end
282
351
  end
283
352
 
353
+ sig { params(file: Dependabot::DependencyFile, content: String).returns(Dependabot::DependencyFile) }
284
354
  def updated_file(file:, content:)
285
355
  updated_file = file.dup
286
356
  updated_file.content = content
287
357
  updated_file
288
358
  end
289
359
 
360
+ sig { returns(FileParser::PythonRequirementParser) }
290
361
  def python_requirement_parser
291
362
  @python_requirement_parser ||=
292
363
  FileParser::PythonRequirementParser.new(
@@ -294,6 +365,7 @@ module Dependabot
294
365
  )
295
366
  end
296
367
 
368
+ sig { returns(LanguageVersionManager) }
297
369
  def language_version_manager
298
370
  @language_version_manager ||=
299
371
  LanguageVersionManager.new(
@@ -301,35 +373,42 @@ module Dependabot
301
373
  )
302
374
  end
303
375
 
376
+ sig { returns(T.nilable(PipenvRunner)) }
304
377
  def pipenv_runner
305
378
  @pipenv_runner ||=
306
379
  PipenvRunner.new(
307
- dependency: dependency,
380
+ dependency: T.must(dependency),
308
381
  lockfile: lockfile,
309
382
  language_version_manager: language_version_manager
310
383
  )
311
384
  end
312
385
 
386
+ sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
313
387
  def parsed_lockfile
314
- @parsed_lockfile ||= JSON.parse(lockfile.content)
388
+ @parsed_lockfile ||= JSON.parse(T.must(lockfile&.content))
315
389
  end
316
390
 
391
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
317
392
  def pipfile
318
393
  @pipfile ||= dependency_files.find { |f| f.name == "Pipfile" }
319
394
  end
320
395
 
396
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
321
397
  def lockfile
322
398
  @lockfile ||= dependency_files.find { |f| f.name == "Pipfile.lock" }
323
399
  end
324
400
 
401
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
325
402
  def setup_files
326
403
  dependency_files.select { |f| f.name.end_with?("setup.py") }
327
404
  end
328
405
 
406
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
329
407
  def setup_cfg_files
330
408
  dependency_files.select { |f| f.name.end_with?("setup.cfg") }
331
409
  end
332
410
 
411
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
333
412
  def requirements_files
334
413
  dependency_files.select { |f| f.name.end_with?(".txt") }
335
414
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/python/file_updater"
@@ -7,11 +7,15 @@ module Dependabot
7
7
  module Python
8
8
  class FileUpdater
9
9
  class PipfileManifestUpdater
10
+ extend T::Sig
11
+
12
+ sig { params(dependencies: T::Array[Dependency], manifest: DependencyFile).void }
10
13
  def initialize(dependencies:, manifest:)
11
14
  @dependencies = dependencies
12
15
  @manifest = manifest
13
16
  end
14
17
 
18
+ sig { returns(T.nilable(String)) }
15
19
  def updated_manifest_content
16
20
  dependencies
17
21
  .select { |dep| requirement_changed?(dep) }
@@ -19,7 +23,7 @@ module Dependabot
19
23
  updated_content = content
20
24
 
21
25
  updated_content = update_requirements(
22
- content: updated_content,
26
+ content: T.must(updated_content),
23
27
  dependency: dep
24
28
  )
25
29
 
@@ -31,28 +35,31 @@ module Dependabot
31
35
 
32
36
  private
33
37
 
38
+ sig { returns(T::Array[Dependency]) }
34
39
  attr_reader :dependencies
40
+ sig { returns(DependencyFile) }
35
41
  attr_reader :manifest
36
42
 
43
+ sig { params(content: String, dependency: Dependency).returns(String) }
37
44
  def update_requirements(content:, dependency:)
38
45
  updated_content = content.dup
39
46
 
40
47
  # The UpdateChecker ensures the order of requirements is preserved
41
48
  # when updating, so we can zip them together in new/old pairs.
42
49
  reqs = dependency.requirements
43
- .zip(dependency.previous_requirements)
50
+ .zip(T.must(dependency.previous_requirements))
44
51
  .reject { |new_req, old_req| new_req == old_req }
45
52
 
46
53
  # Loop through each changed requirement
47
54
  reqs.each do |new_req, old_req|
48
- raise "Bad req match" unless new_req[:file] == old_req[:file]
49
- next if new_req[:requirement] == old_req[:requirement]
55
+ raise "Bad req match" unless new_req[:file] == T.must(old_req)[:file]
56
+ next if new_req[:requirement] == T.must(old_req)[:requirement]
50
57
  next unless new_req[:file] == manifest.name
51
58
 
52
59
  updated_content = update_manifest_req(
53
60
  content: updated_content,
54
61
  dep: dependency,
55
- old_req: old_req.fetch(:requirement),
62
+ old_req: T.must(old_req).fetch(:requirement),
56
63
  new_req: new_req.fetch(:requirement)
57
64
  )
58
65
  end
@@ -60,33 +67,43 @@ module Dependabot
60
67
  updated_content
61
68
  end
62
69
 
70
+ sig do
71
+ params(
72
+ content: String,
73
+ dep: Dependency,
74
+ old_req: String,
75
+ new_req: String
76
+ ).returns(String)
77
+ end
63
78
  def update_manifest_req(content:, dep:, old_req:, new_req:)
64
79
  simple_declaration = content.scan(declaration_regex(dep))
65
80
  .find { |m| m.include?(old_req) }
66
81
 
67
82
  if simple_declaration
68
83
  simple_declaration_regex =
69
- /(?:^|["'])#{Regexp.escape(simple_declaration)}/
84
+ /(?:^|["'])#{Regexp.escape(simple_declaration.to_s)}/
70
85
  content.gsub(simple_declaration_regex) do |line|
71
86
  line.gsub(old_req, new_req)
72
87
  end
73
88
  elsif content.match?(table_declaration_version_regex(dep))
74
89
  content.gsub(table_declaration_version_regex(dep)) do |part|
75
- line = content.match(table_declaration_version_regex(dep))
76
- .named_captures.fetch("version_declaration")
77
- new_line = line.gsub(old_req, new_req)
78
- part.gsub(line, new_line)
90
+ line = T.must(content.match(table_declaration_version_regex(dep)))
91
+ .named_captures.fetch("version_declaration")
92
+ new_line = T.must(line).gsub(old_req, new_req)
93
+ part.gsub(T.must(line), new_line)
79
94
  end
80
95
  else
81
96
  content
82
97
  end
83
98
  end
84
99
 
100
+ sig { params(dep: Dependency).returns(Regexp) }
85
101
  def declaration_regex(dep)
86
102
  escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
87
103
  /(?:^|["'])#{escaped_name}["']?\s*=.*$/i
88
104
  end
89
105
 
106
+ sig { params(dep: Dependabot::Dependency).returns(Regexp) }
90
107
  def table_declaration_version_regex(dep)
91
108
  /
92
109
  packages\.#{Regexp.quote(dep.name)}\]
@@ -95,9 +112,10 @@ module Dependabot
95
112
  /mx
96
113
  end
97
114
 
115
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
98
116
  def requirement_changed?(dependency)
99
117
  changed_requirements =
100
- dependency.requirements - dependency.previous_requirements
118
+ dependency.requirements - T.must(dependency.previous_requirements)
101
119
 
102
120
  changed_requirements.any? { |f| f[:file] == manifest.name }
103
121
  end
@@ -7,6 +7,7 @@ require "dependabot/dependency"
7
7
  require "dependabot/python/file_parser"
8
8
  require "dependabot/python/file_updater"
9
9
  require "dependabot/python/authed_url_builder"
10
+ require "sorbet-runtime"
10
11
 
11
12
  module Dependabot
12
13
  module Python
@@ -19,7 +20,7 @@ module Dependabot
19
20
  @pipfile_content = pipfile_content
20
21
  end
21
22
 
22
- sig { params(credentials: T::Array[T::Hash[String, T.untyped]]).returns(String) }
23
+ sig { params(credentials: T::Array[Dependabot::Credential]).returns(String) }
23
24
  def replace_sources(credentials)
24
25
  pipfile_object = TomlRB.parse(pipfile_content)
25
26
 
@@ -67,23 +68,23 @@ module Dependabot
67
68
  sig { returns(String) }
68
69
  attr_reader :pipfile_content
69
70
 
70
- sig { returns(T::Array[T::Hash[String, T.untyped]]) }
71
+ sig { returns(T::Array[T::Hash[String, String]]) }
71
72
  def pipfile_sources
72
73
  @pipfile_sources ||= T.let(TomlRB.parse(pipfile_content).fetch("source", []),
73
- T.nilable(T::Array[T::Hash[String, T.untyped]]))
74
+ T.nilable(T::Array[T::Hash[String, String]]))
74
75
  end
75
76
 
76
77
  sig do
77
- params(source: T::Hash[String, T.untyped],
78
- credentials: T::Array[T::Hash[String, T.untyped]]).returns(T.nilable(T::Hash[String, T.untyped]))
78
+ params(source: T::Hash[String, String],
79
+ credentials: T::Array[Dependabot::Credential]).returns(T.nilable(T::Hash[String, String]))
79
80
  end
80
81
  def sub_auth_url(source, credentials)
81
- if source["url"].include?("${")
82
- base_url = source["url"].sub(/\${.*}@/, "")
82
+ if source["url"]&.include?("${")
83
+ base_url = source["url"]&.sub(/\${.*}@/, "")
83
84
 
84
85
  source_cred = credentials
85
86
  .select { |cred| cred["type"] == "python_index" && cred["index-url"] }
86
- .find { |c| c["index-url"].sub(/\${.*}@/, "") == base_url }
87
+ .find { |c| T.must(c["index-url"]).sub(/\${.*}@/, "") == base_url }
87
88
 
88
89
  return nil if source_cred.nil?
89
90
 
@@ -93,9 +94,9 @@ module Dependabot
93
94
  source
94
95
  end
95
96
 
96
- sig { params(credentials: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.untyped]]) }
97
+ sig { params(credentials: T::Array[Dependabot::Credential]).returns(T::Array[T::Hash[String, String]]) }
97
98
  def config_variable_sources(credentials)
98
- @config_variable_sources = T.let([], T.nilable(T::Array[T::Hash[String, T.untyped]]))
99
+ @config_variable_sources = T.let([], T.nilable(T::Array[T::Hash[String, String]]))
99
100
  @config_variable_sources =
100
101
  credentials.select { |cred| cred["type"] == "python_index" }.map.with_index do |c, i|
101
102
  {