dependabot-python 0.368.0 → 0.370.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +4 -0
  3. data/helpers/lib/parser.py +26 -0
  4. data/helpers/requirements.txt +1 -0
  5. data/helpers/test/fixtures/no_dependencies.toml +3 -0
  6. data/helpers/test/fixtures/pep621_arbitrary_equality.toml +7 -0
  7. data/helpers/test/fixtures/pep621_dependencies.toml +21 -0
  8. data/helpers/test/fixtures/pep621_empty_deps.toml +8 -0
  9. data/helpers/test/fixtures/pep621_extras.toml +8 -0
  10. data/helpers/test/fixtures/pep621_markers.toml +7 -0
  11. data/helpers/test/fixtures/pep621_multiple_extras.toml +7 -0
  12. data/helpers/test/fixtures/pep621_no_version.toml +8 -0
  13. data/helpers/test/fixtures/pep621_only_build_system.toml +3 -0
  14. data/helpers/test/fixtures/pep621_spaced_specifiers.toml +10 -0
  15. data/helpers/test/fixtures/pep735_cycle.toml +13 -0
  16. data/helpers/test/fixtures/pep735_dependency_groups.toml +18 -0
  17. data/helpers/test/fixtures/requirements/constraints.txt +1 -0
  18. data/helpers/test/fixtures/requirements/markers.txt +1 -0
  19. data/helpers/test/fixtures/requirements/requirements-dev.txt +2 -0
  20. data/helpers/test/fixtures/requirements/requirements.txt +5 -0
  21. data/helpers/test/fixtures/requirements/with_constraints.txt +2 -0
  22. data/helpers/test/fixtures/requirements_empty/.gitkeep +0 -0
  23. data/helpers/test/fixtures/setup_cfg/setup.cfg +16 -0
  24. data/helpers/test/fixtures/setup_py/setup.py +20 -0
  25. data/helpers/test/fixtures/setup_py_comments/setup.py +9 -0
  26. data/helpers/test/test_hasher.py +114 -0
  27. data/helpers/test/test_parse_requirements.py +103 -0
  28. data/helpers/test/test_parse_setup.py +127 -0
  29. data/helpers/test/test_parser.py +265 -0
  30. data/helpers/test/test_run.py +49 -0
  31. data/lib/dependabot/python/dependency_grapher/lockfile_generator.rb +13 -0
  32. data/lib/dependabot/python/file_parser/pyproject_files_parser.rb +42 -11
  33. data/lib/dependabot/python/file_parser.rb +21 -1
  34. data/lib/dependabot/python/file_updater/poetry_file_updater/pep621_updater.rb +162 -0
  35. data/lib/dependabot/python/file_updater/poetry_file_updater.rb +60 -77
  36. data/lib/dependabot/python/file_updater/pyproject_preparer.rb +139 -27
  37. data/lib/dependabot/python/package_manager.rb +16 -0
  38. data/lib/dependabot/python/poetry_plugin_installer.rb +95 -0
  39. data/lib/dependabot/python/update_checker/latest_version_finder.rb +4 -2
  40. data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +13 -0
  41. data/lib/dependabot/python/update_checker/requirements_updater.rb +86 -15
  42. data/lib/dependabot/python/update_checker.rb +6 -2
  43. metadata +32 -4
@@ -11,11 +11,13 @@ require "dependabot/requirements_update_strategy"
11
11
  module Dependabot
12
12
  module Python
13
13
  class UpdateChecker
14
+ # rubocop:disable Metrics/ClassLength
14
15
  class RequirementsUpdater
15
16
  extend T::Sig
16
17
 
17
18
  PYPROJECT_OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9*])\s*\|+/, Regexp)
18
19
  PYPROJECT_SEPARATOR = T.let(/#{PYPROJECT_OR_SEPARATOR}|,/, Regexp)
20
+ LOWER_BOUND_OPS = T.let(%w(> >=).freeze, T::Array[String])
19
21
 
20
22
  class UnfixableRequirement < StandardError; end
21
23
 
@@ -111,13 +113,25 @@ module Dependabot
111
113
  def updated_pyproject_requirement(req)
112
114
  return req unless latest_resolvable_version
113
115
  return req unless req.fetch(:requirement)
114
- return req if new_version_satisfies?(req) && !has_lockfile
116
+ return req if skip_pyproject_update?(req)
115
117
 
118
+ pyproject_update_for_strategy(req)
119
+ rescue UnfixableRequirement
120
+ req.merge(requirement: :unfixable)
121
+ end
122
+
123
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
124
+ def skip_pyproject_update?(req)
125
+ new_version_satisfies?(req) && !has_lockfile &&
126
+ update_strategy != RequirementsUpdateStrategy::BumpVersions
127
+ end
128
+
129
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
130
+ def pyproject_update_for_strategy(req)
116
131
  # If the requirement uses || syntax then we always want to widen it
117
132
  return widen_pyproject_requirement(req) if req.fetch(:requirement).match?(PYPROJECT_OR_SEPARATOR)
118
133
 
119
- # If the requirement is a development dependency we always want to
120
- # bump it
134
+ # If the requirement is a development dependency we always want to bump it
121
135
  return update_pyproject_version(req) if req.fetch(:groups).include?("dev-dependencies")
122
136
 
123
137
  case update_strategy
@@ -126,37 +140,40 @@ module Dependabot
126
140
  when RequirementsUpdateStrategy::BumpVersionsIfNecessary then update_pyproject_version_if_needed(req)
127
141
  else raise "Unexpected update strategy: #{update_strategy}"
128
142
  end
129
- rescue UnfixableRequirement
130
- req.merge(requirement: :unfixable)
131
143
  end
132
144
 
133
145
  sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
134
146
  def update_pyproject_version_if_needed(req)
135
147
  return req if new_version_satisfies?(req)
136
148
 
137
- update_pyproject_version(req)
149
+ update_pyproject_version_core(req, bump_lower_bound: false)
138
150
  end
139
151
 
140
152
  sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
141
153
  def update_pyproject_version(req)
154
+ return req if req[:requirement] == "*"
155
+
156
+ update_pyproject_version_core(req, bump_lower_bound: true)
157
+ end
158
+
159
+ sig do
160
+ params(
161
+ req: T::Hash[Symbol, T.untyped],
162
+ bump_lower_bound: T::Boolean
163
+ ).returns(T::Hash[Symbol, T.untyped])
164
+ end
165
+ def update_pyproject_version_core(req, bump_lower_bound:)
142
166
  requirement_strings = req[:requirement].split(",").map(&:strip)
143
167
 
144
168
  new_requirement =
145
169
  if requirement_strings.any? { |r| r.match?(/^=|^\d/) }
146
- # If there is an equality operator, just update that. It must
147
- # be binding and any other requirements will be being ignored
148
170
  find_and_update_equality_match(requirement_strings)
149
171
  elsif requirement_strings.any? { |r| r.start_with?("~", "^") }
150
- # If a compatibility operator is being used, just bump its
151
- # version (and remove any other requirements)
152
172
  v_req = requirement_strings.find { |r| r.start_with?("~", "^") }
153
173
  bump_version(v_req, latest_resolvable_version.to_s)
154
- elsif new_version_satisfies?(req)
155
- # Otherwise we're looking at a range operator. No change
156
- # required if it's already satisfied
157
- req.fetch(:requirement)
174
+ elsif bump_lower_bound
175
+ bump_requirements_range(requirement_strings)
158
176
  else
159
- # But if it's not, update it
160
177
  update_requirements_range(requirement_strings)
161
178
  end
162
179
 
@@ -344,6 +361,59 @@ module Dependabot
344
361
  .sort_by { |r| requirement_class.new(r).requirements.first.last }.join(",").delete(" ")
345
362
  end
346
363
 
364
+ # Bumps the lower bound of a range requirement to the latest version
365
+ # Used by BumpVersions strategy to increase the minimum version
366
+ sig { params(requirement_strings: T::Array[String]).returns(String) }
367
+ def bump_requirements_range(requirement_strings)
368
+ ruby_requirements = requirement_strings.map { |r| requirement_class.new(r) }
369
+
370
+ validate_lower_bounds_not_too_high(ruby_requirements)
371
+
372
+ updated_requirement_strings = ruby_requirements.map { |r| bump_single_requirement(r) }
373
+
374
+ updated_requirement_strings
375
+ .sort_by { |r| requirement_class.new(r).requirements.first.last }.join(",").delete(" ")
376
+ end
377
+
378
+ sig { params(ruby_requirements: T::Array[Dependabot::Python::Requirement]).void }
379
+ def validate_lower_bounds_not_too_high(ruby_requirements)
380
+ ruby_requirements.each do |r|
381
+ op, version = r.requirements.first
382
+ raise UnfixableRequirement if LOWER_BOUND_OPS.include?(op) && version > T.must(latest_resolvable_version)
383
+ end
384
+ end
385
+
386
+ sig { params(req: Dependabot::Python::Requirement).returns(String) }
387
+ def bump_single_requirement(req)
388
+ op, version = req.requirements.first
389
+
390
+ case op
391
+ when ">=" then ">=" + T.must(latest_resolvable_version).to_s
392
+ # Strict lower bound becomes inclusive because the resolved version
393
+ # is the exact target — using ">" would exclude it.
394
+ when ">" then ">=" + T.must(latest_resolvable_version).to_s
395
+ when "<" then bump_upper_bound_less_than(req, version)
396
+ when "<=" then bump_upper_bound_less_or_equal(req)
397
+ when "~>", "~=" then bump_version(req.to_s, T.must(latest_resolvable_version).to_s)
398
+ when "!=" then req.to_s
399
+ else req.to_s
400
+ end
401
+ end
402
+
403
+ sig { params(req: Dependabot::Python::Requirement, version: Gem::Version).returns(String) }
404
+ def bump_upper_bound_less_than(req, version)
405
+ return req.to_s if req.satisfied_by?(T.must(latest_resolvable_version))
406
+
407
+ "<" + update_greatest_version(version, T.must(latest_resolvable_version))
408
+ end
409
+
410
+ sig { params(req: Dependabot::Python::Requirement).returns(String) }
411
+ def bump_upper_bound_less_or_equal(req)
412
+ return req.to_s if req.satisfied_by?(T.must(latest_resolvable_version))
413
+
414
+ "<=" + T.must(latest_resolvable_version).to_s
415
+ end
416
+
347
417
  # Updates the version in a constraint to be the given version
348
418
  sig { params(req_string: String, version_to_be_permitted: String).returns(String) }
349
419
  def bump_version(req_string, version_to_be_permitted)
@@ -448,6 +518,7 @@ module Dependabot
448
518
  Python::Requirement
449
519
  end
450
520
  end
521
+ # rubocop:enable Metrics/ClassLength
451
522
  end
452
523
  end
453
524
  end
@@ -409,14 +409,18 @@ module Dependabot
409
409
  def check_pypi_for_library_match
410
410
  return false unless updating_pyproject? && library_details && !T.must(library_details)["name"].nil?
411
411
 
412
+ # If the project has a description in its pyproject.toml metadata, treat it as a
413
+ # library when PyPI is unavailable or the package isn't published there yet.
414
+ has_library_metadata = !T.must(library_details)["description"].nil?
415
+
412
416
  response = Dependabot::RegistryClient.get(
413
417
  url: "https://pypi.org/pypi/#{normalised_name(T.must(library_details)['name'])}/json/"
414
418
  )
415
- return false unless response.status == 200
419
+ return has_library_metadata unless response.status == 200
416
420
 
417
421
  (JSON.parse(response.body)["info"] || {})["summary"] == T.must(library_details)["description"]
418
422
  rescue Excon::Error::Timeout, Excon::Error::Socket, URI::InvalidURIError
419
- false
423
+ has_library_metadata
420
424
  end
421
425
 
422
426
  sig { returns(T::Boolean) }
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.368.0
4
+ version: 0.370.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.368.0
18
+ version: 0.370.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.368.0
25
+ version: 0.370.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -247,6 +247,32 @@ files:
247
247
  - helpers/lib/parser.py
248
248
  - helpers/requirements.txt
249
249
  - helpers/run.py
250
+ - helpers/test/fixtures/no_dependencies.toml
251
+ - helpers/test/fixtures/pep621_arbitrary_equality.toml
252
+ - helpers/test/fixtures/pep621_dependencies.toml
253
+ - helpers/test/fixtures/pep621_empty_deps.toml
254
+ - helpers/test/fixtures/pep621_extras.toml
255
+ - helpers/test/fixtures/pep621_markers.toml
256
+ - helpers/test/fixtures/pep621_multiple_extras.toml
257
+ - helpers/test/fixtures/pep621_no_version.toml
258
+ - helpers/test/fixtures/pep621_only_build_system.toml
259
+ - helpers/test/fixtures/pep621_spaced_specifiers.toml
260
+ - helpers/test/fixtures/pep735_cycle.toml
261
+ - helpers/test/fixtures/pep735_dependency_groups.toml
262
+ - helpers/test/fixtures/requirements/constraints.txt
263
+ - helpers/test/fixtures/requirements/markers.txt
264
+ - helpers/test/fixtures/requirements/requirements-dev.txt
265
+ - helpers/test/fixtures/requirements/requirements.txt
266
+ - helpers/test/fixtures/requirements/with_constraints.txt
267
+ - helpers/test/fixtures/requirements_empty/.gitkeep
268
+ - helpers/test/fixtures/setup_cfg/setup.cfg
269
+ - helpers/test/fixtures/setup_py/setup.py
270
+ - helpers/test/fixtures/setup_py_comments/setup.py
271
+ - helpers/test/test_hasher.py
272
+ - helpers/test/test_parse_requirements.py
273
+ - helpers/test/test_parse_setup.py
274
+ - helpers/test/test_parser.py
275
+ - helpers/test/test_run.py
250
276
  - lib/dependabot/python.rb
251
277
  - lib/dependabot/python/authed_url_builder.rb
252
278
  - lib/dependabot/python/dependency_grapher.rb
@@ -263,6 +289,7 @@ files:
263
289
  - lib/dependabot/python/file_updater/pipfile_manifest_updater.rb
264
290
  - lib/dependabot/python/file_updater/pipfile_preparer.rb
265
291
  - lib/dependabot/python/file_updater/poetry_file_updater.rb
292
+ - lib/dependabot/python/file_updater/poetry_file_updater/pep621_updater.rb
266
293
  - lib/dependabot/python/file_updater/pyproject_preparer.rb
267
294
  - lib/dependabot/python/file_updater/requirement_file_updater.rb
268
295
  - lib/dependabot/python/file_updater/requirement_replacer.rb
@@ -277,6 +304,7 @@ files:
277
304
  - lib/dependabot/python/package_manager.rb
278
305
  - lib/dependabot/python/pip_compile_file_matcher.rb
279
306
  - lib/dependabot/python/pipenv_runner.rb
307
+ - lib/dependabot/python/poetry_plugin_installer.rb
280
308
  - lib/dependabot/python/requirement.rb
281
309
  - lib/dependabot/python/requirement_parser.rb
282
310
  - lib/dependabot/python/shared_file_fetcher.rb
@@ -294,7 +322,7 @@ licenses:
294
322
  - MIT
295
323
  metadata:
296
324
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
297
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.368.0
325
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.370.0
298
326
  rdoc_options: []
299
327
  require_paths:
300
328
  - lib