dependabot-uv 0.302.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13de7d80edd84c1e0f706821592cd4f756a2cbc418f16b33f3af2eb58be41942
4
- data.tar.gz: 5e0e922260aaa77e3041abb3103501c86be7a24b57d6f1fa25a5e782ec7b5a92
3
+ metadata.gz: 42bfabcf245ebc41ef81303c9de0c8271a0a0073af315d23b182b64901646b9d
4
+ data.tar.gz: 7e6a04dabc0d50c374a0e118d08b705aadca94e5952eba891b14623c99a76776
5
5
  SHA512:
6
- metadata.gz: 03fac6d585a7f26b470dda56264b85f3e078b29c561e37a5df3c118298ff2c8a2fe0eb7751efc2a9c127bd6029eed0697a9f2b7dedf0a0f45fb23e597209f346
7
- data.tar.gz: 2c1419e9e906e4b7b198f95ae55f032a05ee2fab815672d0c8d80e99edc1923aecbfe02506c73892b058277664bdfaa23c92c77580c2278b6cdd1bc76cda001d
6
+ metadata.gz: 246aebc2e0a8d0402e8edb1754df23a4f5428156b8ebb0c0748ba55f45ae30f1056aa1ff89d17af9b6d7b16d158c97aa1c39c86d0f49e598683baaffa53f3513
7
+ data.tar.gz: e5eaa3c52329795b579f7041d555ebcbec0377b37e6e898e122bacffad4c3432c5bc6319acc0305dfd1dcc6a900b7d1baa576bf25e690c2178e044b342232d98
@@ -50,7 +50,7 @@ module Dependabot
50
50
  # the user-specified range of versions, not the version Dependabot chose to run.
51
51
  python_requirement_parser = FileParser::PythonRequirementParser.new(dependency_files: files)
52
52
  language_version_manager = LanguageVersionManager.new(python_requirement_parser: python_requirement_parser)
53
- Dependabot.logger.info("Dependabot is using Python version '#{language_version_manager.python_major_minor}'.")
53
+ Dependabot.logger.info("Dependabot is using Python version '#{language_version_manager.python_version}'.")
54
54
  {
55
55
  languages: {
56
56
  python: {
@@ -19,6 +19,8 @@ module Dependabot
19
19
  class LockFileUpdater
20
20
  require_relative "pyproject_preparer"
21
21
 
22
+ REQUIRED_FILES = %w(pyproject.toml uv.lock).freeze # At least one of these files should be present
23
+
22
24
  attr_reader :dependencies
23
25
  attr_reader :dependency_files
24
26
  attr_reader :credentials
@@ -43,6 +45,8 @@ module Dependabot
43
45
  end
44
46
 
45
47
  def fetch_updated_dependency_files
48
+ return [] unless create_or_update_lock_file?
49
+
46
50
  updated_files = []
47
51
 
48
52
  if file_changed?(pyproject)
@@ -185,13 +189,14 @@ module Dependabot
185
189
 
186
190
  def run_update_command
187
191
  # Use pyenv exec to ensure we're using the correct Python environment
188
- command = "pyenv exec python -m uv lock --upgrade-package #{dependency.name}"
189
- fingerprint = "pyenv exec python -m uv lock --upgrade-package <dependency_name>"
192
+ command = "pyenv exec uv lock --upgrade-package #{dependency.name}"
193
+ fingerprint = "pyenv exec uv lock --upgrade-package <dependency_name>"
190
194
 
191
195
  run_command(command, fingerprint:)
192
196
  end
193
197
 
194
198
  def run_command(command, fingerprint: nil)
199
+ Dependabot.logger.info("Running command: #{command}")
195
200
  SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
196
201
  end
197
202
 
@@ -217,7 +222,7 @@ module Dependabot
217
222
  # Set the local Python version
218
223
  python_version = language_version_manager.python_version
219
224
  Dependabot.logger.info("Setting Python version to #{python_version}")
220
- SharedHelpers.run_shell_command("pyenv local #{language_version_manager.python_major_minor}")
225
+ SharedHelpers.run_shell_command("pyenv local #{python_version}")
221
226
 
222
227
  # We don't need to install uv as it should be available in the Docker environment
223
228
  Dependabot.logger.info("Using pre-installed uv package")
@@ -308,6 +313,10 @@ module Dependabot
308
313
  def uv_lock
309
314
  dependency_files.find { |f| f.name == "uv.lock" }
310
315
  end
316
+
317
+ def create_or_update_lock_file?
318
+ dependency.requirements.select { _1[:file].end_with?(*REQUIRED_FILES) }.any?
319
+ end
311
320
  end
312
321
  end
313
322
  end
@@ -1,69 +1,92 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/uv/requirement_parser"
5
5
  require "dependabot/uv/file_updater"
6
6
  require "dependabot/shared_helpers"
7
7
  require "dependabot/uv/native_helpers"
8
+ require "sorbet-runtime"
8
9
 
9
10
  module Dependabot
10
11
  module Uv
11
12
  class FileUpdater
12
13
  class RequirementFileUpdater
14
+ extend T::Sig
15
+
13
16
  require_relative "requirement_replacer"
14
17
 
18
+ sig { returns(T::Array[Dependency]) }
15
19
  attr_reader :dependencies
20
+
21
+ sig { returns(T::Array[DependencyFile]) }
16
22
  attr_reader :dependency_files
23
+
24
+ sig { returns(T::Array[Dependabot::Credential]) }
17
25
  attr_reader :credentials
18
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[String])
33
+ ).void
34
+ end
19
35
  def initialize(dependencies:, dependency_files:, credentials:, index_urls: nil)
20
36
  @dependencies = dependencies
21
37
  @dependency_files = dependency_files
22
38
  @credentials = credentials
23
39
  @index_urls = index_urls
40
+ @updated_dependency_files = T.let(nil, T.nilable(T::Array[DependencyFile]))
24
41
  end
25
42
 
43
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
26
44
  def updated_dependency_files
27
45
  @updated_dependency_files ||= fetch_updated_dependency_files
28
46
  end
29
47
 
30
48
  private
31
49
 
50
+ sig { returns(T.nilable(Dependency)) }
32
51
  def dependency
33
52
  # For now, we'll only ever be updating a single dependency
34
53
  dependencies.first
35
54
  end
36
55
 
56
+ sig { returns(T::Array[DependencyFile]) }
37
57
  def fetch_updated_dependency_files
38
- reqs = dependency.requirements.zip(dependency.previous_requirements)
58
+ previous_requirements = dependency&.previous_requirements || []
59
+ reqs = T.must(dependency).requirements.zip(previous_requirements)
39
60
 
40
61
  reqs.filter_map do |(new_req, old_req)|
41
62
  next if new_req == old_req
42
63
 
43
64
  file = get_original_file(new_req.fetch(:file)).dup
44
65
  updated_content =
45
- updated_requirement_or_setup_file_content(new_req, old_req)
46
- next if updated_content == file.content
66
+ updated_requirement_or_setup_file_content(new_req, T.must(old_req))
67
+ next if updated_content == file&.content
47
68
 
48
- file.content = updated_content
69
+ file&.content = updated_content
49
70
  file
50
71
  end
51
72
  end
52
73
 
74
+ sig { params(new_req: T::Hash[Symbol, T.untyped], old_req: T::Hash[Symbol, T.untyped]).returns(String) }
53
75
  def updated_requirement_or_setup_file_content(new_req, old_req)
54
76
  original_file = get_original_file(new_req.fetch(:file))
55
77
  raise "Could not find a dependency file for #{new_req}" unless original_file
56
78
 
57
79
  RequirementReplacer.new(
58
80
  content: original_file.content,
59
- dependency_name: dependency.name,
81
+ dependency_name: dependency&.name,
60
82
  old_requirement: old_req.fetch(:requirement),
61
83
  new_requirement: new_req.fetch(:requirement),
62
- new_hash_version: dependency.version,
84
+ new_hash_version: dependency&.version,
63
85
  index_urls: @index_urls
64
86
  ).updated_content
65
87
  end
66
88
 
89
+ sig { params(filename: String).returns(T.nilable(DependencyFile)) }
67
90
  def get_original_file(filename)
68
91
  dependency_files.find { |f| f.name == filename }
69
92
  end
@@ -11,28 +11,43 @@ module Dependabot
11
11
 
12
12
  class Language < Dependabot::Ecosystem::VersionManager
13
13
  extend T::Sig
14
- # These versions should match the versions specified at the top of `python/Dockerfile`
15
- PYTHON_3_13 = "3.13"
16
- PYTHON_3_12 = "3.12"
17
- PYTHON_3_11 = "3.11"
18
- PYTHON_3_10 = "3.10"
19
- PYTHON_3_9 = "3.9"
20
- PYTHON_3_8 = "3.8"
14
+ # This list must match the versions specified at the top of `python/Dockerfile`
15
+ # ARG PY_3_13=3.13.2
16
+ PRE_INSTALLED_PYTHON_VERSIONS_RAW = %w(
17
+ 3.13.2
18
+ 3.12.9
19
+ 3.11.11
20
+ 3.10.16
21
+ 3.9.21
22
+ ).freeze
21
23
 
22
- DEPRECATED_VERSIONS = T.let([Version.new(PYTHON_3_8)].freeze, T::Array[Dependabot::Version])
24
+ PRE_INSTALLED_PYTHON_VERSIONS = T.let(PRE_INSTALLED_PYTHON_VERSIONS_RAW.map do |v|
25
+ Version.new(v)
26
+ end.sort, T::Array[Version])
23
27
 
24
- # Keep versions in ascending order
25
- SUPPORTED_VERSIONS = T.let([
26
- Version.new(PYTHON_3_9),
27
- Version.new(PYTHON_3_10),
28
- Version.new(PYTHON_3_11),
29
- Version.new(PYTHON_3_12),
30
- Version.new(PYTHON_3_13)
31
- ].freeze, T::Array[Dependabot::Version])
28
+ PRE_INSTALLED_VERSIONS_MAP = T.let(
29
+ PRE_INSTALLED_PYTHON_VERSIONS.to_h do |v|
30
+ [Version.new(T.must(v.segments[0..1]).join(".")), v]
31
+ end,
32
+ T::Hash[Version, Version]
33
+ )
34
+
35
+ PRE_INSTALLED_HIGHEST_VERSION = T.let(T.must(PRE_INSTALLED_PYTHON_VERSIONS.max), Version)
36
+
37
+ SUPPORTED_VERSIONS = T.let(
38
+ PRE_INSTALLED_PYTHON_VERSIONS.map do |v|
39
+ Version.new(T.must(v.segments[0..1]&.join(".")))
40
+ end,
41
+ T::Array[Version]
42
+ )
43
+
44
+ NON_SUPPORTED_HIGHEST_VERSION = "3.8"
45
+
46
+ DEPRECATED_VERSIONS = T.let([Version.new(NON_SUPPORTED_HIGHEST_VERSION)].freeze, T::Array[Dependabot::Version])
32
47
 
33
48
  sig do
34
49
  params(
35
- detected_version: String,
50
+ detected_version: T.nilable(String),
36
51
  raw_version: T.nilable(String),
37
52
  requirement: T.nilable(Requirement)
38
53
  ).void
@@ -40,7 +55,7 @@ module Dependabot
40
55
  def initialize(detected_version:, raw_version: nil, requirement: nil)
41
56
  super(
42
57
  name: LANGUAGE,
43
- detected_version: major_minor_version(detected_version),
58
+ detected_version: detected_version ? major_minor_version(detected_version) : nil,
44
59
  version: raw_version ? Version.new(raw_version) : nil,
45
60
  deprecated_versions: DEPRECATED_VERSIONS,
46
61
  supported_versions: SUPPORTED_VERSIONS,
@@ -48,25 +63,12 @@ module Dependabot
48
63
  )
49
64
  end
50
65
 
51
- sig { override.returns(T::Boolean) }
52
- def deprecated?
53
- return false unless detected_version
54
- return false if unsupported?
55
-
56
- deprecated_versions.include?(detected_version)
57
- end
58
-
59
- sig { override.returns(T::Boolean) }
60
- def unsupported?
61
- return false unless detected_version
62
-
63
- supported_versions.all? { |supported| supported > detected_version }
64
- end
65
-
66
66
  private
67
67
 
68
- sig { params(version: String).returns(Dependabot::Uv::Version) }
68
+ sig { params(version: String).returns(T.nilable(Version)) }
69
69
  def major_minor_version(version)
70
+ return nil if version.empty?
71
+
70
72
  major_minor = T.let(T.must(Version.new(version).segments[0..1]&.join(".")), String)
71
73
 
72
74
  Version.new(major_minor)
@@ -9,14 +9,6 @@ module Dependabot
9
9
  module Uv
10
10
  class LanguageVersionManager
11
11
  extend T::Sig
12
- # This list must match the versions specified at the top of `python/Dockerfile`
13
- PRE_INSTALLED_PYTHON_VERSIONS = %w(
14
- 3.13.2
15
- 3.12.9
16
- 3.11.11
17
- 3.10.16
18
- 3.9.21
19
- ).freeze
20
12
 
21
13
  sig { params(python_requirement_parser: T.untyped).void }
22
14
  def initialize(python_requirement_parser:)
@@ -44,7 +36,7 @@ module Dependabot
44
36
 
45
37
  sig { returns(T.untyped) }
46
38
  def python_major_minor
47
- @python_major_minor ||= T.let(T.must(Uv::Version.new(python_version).segments[0..1]).join("."), T.untyped)
39
+ @python_major_minor ||= T.let(T.must(Version.new(python_version).segments[0..1]).join("."), T.untyped)
48
40
  end
49
41
 
50
42
  sig { returns(String) }
@@ -62,7 +54,34 @@ module Dependabot
62
54
  user_specified_python_version
63
55
  end
64
56
  else
65
- python_version_matching_imputed_requirements || PRE_INSTALLED_PYTHON_VERSIONS.first
57
+ python_version_matching_imputed_requirements || Language::PRE_INSTALLED_HIGHEST_VERSION.to_s
58
+ end
59
+ end
60
+
61
+ sig { params(requirement_string: T.nilable(String)).returns(T.nilable(String)) }
62
+ def normalize_python_exact_version(requirement_string)
63
+ return requirement_string if requirement_string.nil? || requirement_string.strip.empty?
64
+
65
+ requirement_string = requirement_string.strip
66
+
67
+ # If the requirement already has a wildcard, return nil
68
+ return nil if requirement_string == "*"
69
+
70
+ # If the requirement is not an exact version such as not X.Y.Z, =X.Y.Z, ==X.Y.Z, ===X.Y.Z
71
+ # then return the requirement as is
72
+ return requirement_string unless requirement_string.match?(/^=?={0,2}\s*\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?$/i)
73
+
74
+ parts = requirement_string.gsub(/^=+/, "").split(".")
75
+
76
+ case parts.length
77
+ when 1 # Only major version (X)
78
+ ">= #{parts[0]}.0.0 < #{parts[0].to_i + 1}.0.0" # Ensure only major version range
79
+ when 2 # Major.Minor (X.Y)
80
+ ">= #{parts[0]}.#{parts[1]}.0 < #{parts[0].to_i}.#{parts[1].to_i + 1}.0" # Ensure only minor version range
81
+ when 3 # Major.Minor.Patch (X.Y.Z)
82
+ ">= #{parts[0]}.#{parts[1]}.0 < #{parts[0].to_i}.#{parts[1].to_i + 1}.0" # Convert to >= X.Y.0
83
+ else
84
+ requirement_string
66
85
  end
67
86
  end
68
87
 
@@ -72,15 +91,21 @@ module Dependabot
72
91
 
73
92
  # If the requirement string isn't already a range (eg ">3.10"), coerce it to "major.minor.*".
74
93
  # The patch version is ignored because a non-matching patch version is unlikely to affect resolution.
75
- requirement_string = requirement_string.gsub(/\.\d+$/, ".*") if requirement_string.start_with?(/\d/)
94
+ requirement_string = requirement_string.gsub(/\.\d+$/, ".*") if /^\d/.match?(requirement_string)
95
+
96
+ requirement_string = normalize_python_exact_version(requirement_string)
97
+
98
+ if requirement_string.nil? || requirement_string.strip.empty?
99
+ return Language::PRE_INSTALLED_HIGHEST_VERSION.to_s
100
+ end
76
101
 
77
102
  # Try to match one of our pre-installed Python versions
78
- requirement = T.must(Uv::Requirement.requirements_array(requirement_string).first)
79
- version = PRE_INSTALLED_PYTHON_VERSIONS.find { |v| requirement.satisfied_by?(Uv::Version.new(v)) }
80
- return version if version
103
+ requirement = T.must(Requirement.requirements_array(requirement_string).first)
104
+ version = Language::PRE_INSTALLED_PYTHON_VERSIONS.find { |v| requirement.satisfied_by?(v) }
105
+ return version.to_s if version
81
106
 
82
- # Otherwise we have to raise
83
- supported_versions = PRE_INSTALLED_PYTHON_VERSIONS.map { |x| x.gsub(/\.\d+$/, ".*") }.join(", ")
107
+ # Otherwise we have to raise an error
108
+ supported_versions = Language::SUPPORTED_VERSIONS.map { |v| "#{v}.*" }.join(", ")
84
109
  raise ToolVersionNotSupported.new("Python", python_requirement_string, supported_versions)
85
110
  end
86
111
 
@@ -93,21 +118,20 @@ module Dependabot
93
118
  def python_version_matching_imputed_requirements
94
119
  compiled_file_python_requirement_markers =
95
120
  @python_requirement_parser.imputed_requirements.map do |r|
96
- Dependabot::Uv::Requirement.new(r)
121
+ Requirement.new(r)
97
122
  end
98
123
  python_version_matching(compiled_file_python_requirement_markers)
99
124
  end
100
125
 
101
126
  sig { params(requirements: T.untyped).returns(T.nilable(String)) }
102
127
  def python_version_matching(requirements)
103
- PRE_INSTALLED_PYTHON_VERSIONS.find do |version_string|
104
- version = Uv::Version.new(version_string)
128
+ Language::PRE_INSTALLED_PYTHON_VERSIONS.find do |version|
105
129
  requirements.all? do |req|
106
130
  next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
107
131
 
108
132
  req.satisfied_by?(version)
109
133
  end
110
- end
134
+ end.to_s
111
135
  end
112
136
  end
113
137
  end
@@ -93,7 +93,7 @@ module Dependabot
93
93
  private
94
94
 
95
95
  def convert_python_constraint_to_ruby_constraint(req_string)
96
- return nil if req_string.nil?
96
+ return nil if req_string.nil? || req_string.strip.empty?
97
97
  return nil if req_string == "*"
98
98
 
99
99
  req_string = req_string.gsub("~=", "~>")
@@ -101,6 +101,8 @@ module Dependabot
101
101
 
102
102
  if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
103
103
  elsif req_string.start_with?("^") then convert_caret_req(req_string)
104
+ elsif req_string.match?(/^=?={0,2}\s*\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?(\.\*)?$/i)
105
+ convert_exact(req_string)
104
106
  elsif req_string.include?(".*") then convert_wildcard(req_string)
105
107
  else
106
108
  req_string
@@ -155,6 +157,37 @@ module Dependabot
155
157
  .gsub(/\*$/, "0.dev")
156
158
  .tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
157
159
  end
160
+
161
+ def convert_exact(req_string)
162
+ arbitrary_equality = req_string.start_with?("===")
163
+ cleaned_version = req_string.gsub(/^=+/, "").strip
164
+
165
+ return ["=== #{cleaned_version}"] if arbitrary_equality
166
+
167
+ # Handle versions wildcarded with .*, e.g. 1.0.*
168
+ if cleaned_version.include?(".*")
169
+ # Remove all characters after the first .*, and the .*
170
+ cleaned_version = cleaned_version.split(".*").first
171
+ version = Version.new(cleaned_version)
172
+ # Get the release segment parts [major, minor, patch]
173
+ version_parts = version.release_segment
174
+
175
+ if version_parts.length == 1
176
+ major = T.must(version_parts[0])
177
+ [">= #{major}.0.0.dev", "< #{major + 1}.0.0"]
178
+ elsif version_parts.length == 2
179
+ major, minor = version_parts
180
+ "~> #{major}.#{minor}.0.dev"
181
+ elsif version_parts.length == 3
182
+ major, minor, patch = version_parts
183
+ "~> #{major}.#{minor}.#{patch}.dev"
184
+ else
185
+ "= #{cleaned_version}"
186
+ end
187
+ else
188
+ "= #{cleaned_version}"
189
+ end
190
+ end
158
191
  end
159
192
  end
160
193
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-uv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.302.0
4
+ version: 0.303.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-20 00:00:00.000000000 Z
11
+ date: 2025-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.302.0
19
+ version: 0.303.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.302.0
26
+ version: 0.303.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.8.5
159
+ version: 0.8.7
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.8.5
166
+ version: 0.8.7
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: simplecov
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -285,7 +285,7 @@ licenses:
285
285
  - MIT
286
286
  metadata:
287
287
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
288
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.302.0
288
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.303.0
289
289
  post_install_message:
290
290
  rdoc_options: []
291
291
  require_paths: