dependabot-uv 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26af709186ad20b222961b28c19395f53edfcab51c9fdc0e92734cd06aa2d296
4
- data.tar.gz: c26d70a21a979a655c952c940c0f32a10f89e58cb1d1588238b64c252bf96c48
3
+ metadata.gz: 42bfabcf245ebc41ef81303c9de0c8271a0a0073af315d23b182b64901646b9d
4
+ data.tar.gz: 7e6a04dabc0d50c374a0e118d08b705aadca94e5952eba891b14623c99a76776
5
5
  SHA512:
6
- metadata.gz: d0c2ac20cafd314d293a7a903947cd7fef9df95c5a85fae9f46e41d78920ae856949357cb4e6419c1804e95c19ba625c2a8fbd8bef43d9154d912c6aa31adb43
7
- data.tar.gz: e1cb4fe17761e4f1433f9a55ae619a9271ec49359a3de0198c807f9393748633d9bfddb85002c01d782eadfc40f0a6598488fde5d9a7044e1c4e844198a26dd5
6
+ metadata.gz: 246aebc2e0a8d0402e8edb1754df23a4f5428156b8ebb0c0748ba55f45ae30f1056aa1ff89d17af9b6d7b16d158c97aa1c39c86d0f49e598683baaffa53f3513
7
+ data.tar.gz: e5eaa3c52329795b579f7041d555ebcbec0377b37e6e898e122bacffad4c3432c5bc6319acc0305dfd1dcc6a900b7d1baa576bf25e690c2178e044b342232d98
@@ -7,7 +7,7 @@ plette==2.1.0
7
7
  poetry==1.8.5
8
8
  # TODO: Replace 3p package `tomli` with 3.11's new stdlib `tomllib` once we drop support for Python 3.10.
9
9
  tomli==2.0.1
10
- uv==0.6.2
10
+ uv==0.6.8
11
11
 
12
12
  # Some dependencies will only install if Cython is present
13
13
  Cython==3.0.10
@@ -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)
@@ -104,48 +108,36 @@ module Dependabot
104
108
  original_requires_python = original_content
105
109
  .match(/requires-python\s*=\s*["']([^"']+)["']/)&.captures&.first
106
110
 
107
- # Use the original Python version requirement for the update if one exists
108
- with_original_python_version(original_requires_python) do
109
- new_lockfile = updated_lockfile_content_for(prepared_pyproject)
110
-
111
- # Use direct string replacement to preserve the exact format
112
- # Match the dependency section and update only the version
113
- dependency_section_pattern = /
114
- (\[\[package\]\]\s*\n
115
- .*?name\s*=\s*["']#{Regexp.escape(dependency.name)}["']\s*\n
116
- .*?)
117
- (version\s*=\s*["'][^"']+["'])
118
- (.*?)
119
- (\[\[package\]\]|\z)
120
- /xm
121
-
122
- result = original_content.sub(dependency_section_pattern) do
123
- section_start = Regexp.last_match(1)
124
- version_line = "version = \"#{dependency.version}\""
125
- section_end = Regexp.last_match(3)
126
- next_section_or_end = Regexp.last_match(4)
127
-
128
- "#{section_start}#{version_line}#{section_end}#{next_section_or_end}"
129
- end
130
-
131
- # If the content didn't change and we expect it to, something went wrong
132
- if result == original_content
133
- Dependabot.logger.warn("Package section not found for #{dependency.name}, falling back to raw update")
134
- result = new_lockfile
135
- end
136
-
137
- # Restore the original requires-python if it exists
138
- if original_requires_python
139
- result = result.gsub(/requires-python\s*=\s*["'][^"']+["']/,
140
- "requires-python = \"#{original_requires_python}\"")
141
- end
142
-
143
- result
111
+ # Store the original Python version requirement for later use
112
+ @original_python_version = original_requires_python
113
+
114
+ new_lockfile = updated_lockfile_content_for(prepared_pyproject)
115
+
116
+ # Normalize line endings to ensure proper comparison
117
+ new_lockfile = normalize_line_endings(new_lockfile, original_content)
118
+
119
+ result = new_lockfile
120
+
121
+ # Restore the original requires-python if it exists
122
+ if original_requires_python
123
+ result = result.gsub(/requires-python\s*=\s*["'][^"']+["']/,
124
+ "requires-python = \"#{original_requires_python}\"")
144
125
  end
126
+
127
+ result
145
128
  end
146
129
  end
147
130
 
148
- # Helper method to temporarily override Python version during operations
131
+ # Helper method to normalize line endings between two strings
132
+ def normalize_line_endings(content, reference)
133
+ # Check if reference has escaped newlines like "\n" +
134
+ if reference.include?("\\n")
135
+ content.gsub("\n", "\\n")
136
+ else
137
+ content
138
+ end
139
+ end
140
+
149
141
  def with_original_python_version(original_requires_python)
150
142
  if original_requires_python
151
143
  original_python_version = @original_python_version
@@ -164,7 +156,6 @@ module Dependabot
164
156
  content = updated_pyproject_content
165
157
  content = sanitize(content)
166
158
  content = freeze_other_dependencies(content)
167
- content = update_python_requirement(content)
168
159
  content
169
160
  end
170
161
  end
@@ -175,12 +166,6 @@ module Dependabot
175
166
  .freeze_top_level_dependencies_except(dependencies)
176
167
  end
177
168
 
178
- def update_python_requirement(pyproject_content)
179
- PyprojectPreparer
180
- .new(pyproject_content: pyproject_content)
181
- .update_python_requirement(language_version_manager.python_version)
182
- end
183
-
184
169
  def sanitize(pyproject_content)
185
170
  PyprojectPreparer
186
171
  .new(pyproject_content: pyproject_content)
@@ -192,14 +177,8 @@ module Dependabot
192
177
  SharedHelpers.with_git_configured(credentials: credentials) do
193
178
  write_temporary_dependency_files(pyproject_content)
194
179
 
195
- # Install Python before writing .python-version to make sure we use a version that's available
196
- language_version_manager.install_required_python
197
-
198
- # Determine the Python version to use after installation
199
- python_version = determine_python_version
200
-
201
- # Now write the .python-version file with a version we know is installed
202
- File.write(".python-version", python_version)
180
+ # Set up Python environment using LanguageVersionManager
181
+ setup_python_environment
203
182
 
204
183
  run_update_command
205
184
 
@@ -209,6 +188,7 @@ module Dependabot
209
188
  end
210
189
 
211
190
  def run_update_command
191
+ # Use pyenv exec to ensure we're using the correct Python environment
212
192
  command = "pyenv exec uv lock --upgrade-package #{dependency.name}"
213
193
  fingerprint = "pyenv exec uv lock --upgrade-package <dependency_name>"
214
194
 
@@ -216,6 +196,7 @@ module Dependabot
216
196
  end
217
197
 
218
198
  def run_command(command, fingerprint: nil)
199
+ Dependabot.logger.info("Running command: #{command}")
219
200
  SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
220
201
  end
221
202
 
@@ -226,82 +207,28 @@ module Dependabot
226
207
  File.write(path, file.content)
227
208
  end
228
209
 
229
- # Only write the .python-version file after the language version manager has
230
- # installed the required Python version to ensure it's available
231
210
  # Overwrite the pyproject with updated content
232
211
  File.write("pyproject.toml", pyproject_content)
233
212
  end
234
213
 
235
- def determine_python_version
236
- # Check available Python versions through pyenv
237
- available_versions = nil
238
- begin
239
- available_versions = SharedHelpers.run_shell_command("pyenv versions --bare")
240
- .split("\n")
241
- .map(&:strip)
242
- .reject(&:empty?)
243
- rescue StandardError => e
244
- Dependabot.logger.warn("Error checking available Python versions: #{e}")
245
- end
246
-
247
- # Try to find the closest match for our priority order
248
- preferred_version = find_preferred_version(available_versions)
214
+ def setup_python_environment
215
+ # Use LanguageVersionManager to determine and install the appropriate Python version
216
+ Dependabot.logger.info("Setting up Python environment using LanguageVersionManager")
249
217
 
250
- if preferred_version
251
- # Just return the major.minor version string
252
- preferred_version.match(/^(\d+\.\d+)/)[1]
253
- else
254
- # If all else fails, use "system" which should work with whatever Python is available
255
- "system"
256
- end
257
- end
258
-
259
- def find_preferred_version(available_versions)
260
- return nil unless available_versions&.any?
261
-
262
- # Try each strategy in order of preference
263
- try_version_from_file(available_versions) ||
264
- try_version_from_requires_python(available_versions) ||
265
- try_highest_python3_version(available_versions)
266
- end
267
-
268
- def try_version_from_file(available_versions)
269
- python_version_file = dependency_files.find { |f| f.name == ".python-version" }
270
- return nil unless python_version_file && !python_version_file.content.strip.empty?
271
-
272
- requested_version = python_version_file.content.strip
273
- return requested_version if version_available?(available_versions, requested_version)
274
-
275
- Dependabot.logger.info("Python version #{requested_version} from .python-version not available")
276
- nil
277
- end
278
-
279
- def try_version_from_requires_python(available_versions)
280
- return nil unless @original_python_version
281
-
282
- version_match = @original_python_version.match(/(\d+\.\d+)/)
283
- return nil unless version_match
284
-
285
- requested_version = version_match[1]
286
- return requested_version if version_available?(available_versions, requested_version)
287
-
288
- Dependabot.logger.info("Python version #{requested_version} from requires-python not available")
289
- nil
290
- end
291
-
292
- def try_highest_python3_version(available_versions)
293
- python3_versions = available_versions
294
- .select { |v| v.match(/^3\.\d+/) }
295
- .sort_by { |v| Gem::Version.new(v.match(/^(\d+\.\d+)/)[1]) }
296
- .reverse
218
+ begin
219
+ # Install the required Python version
220
+ language_version_manager.install_required_python
297
221
 
298
- python3_versions.first # returns nil if array is empty
299
- end
222
+ # Set the local Python version
223
+ python_version = language_version_manager.python_version
224
+ Dependabot.logger.info("Setting Python version to #{python_version}")
225
+ SharedHelpers.run_shell_command("pyenv local #{python_version}")
300
226
 
301
- def version_available?(available_versions, requested_version)
302
- # Check if the exact version or a version with the same major.minor is available
303
- available_versions.any? do |v|
304
- v == requested_version || v.start_with?("#{requested_version}.")
227
+ # We don't need to install uv as it should be available in the Docker environment
228
+ Dependabot.logger.info("Using pre-installed uv package")
229
+ rescue StandardError => e
230
+ Dependabot.logger.warn("Error setting up Python environment: #{e.message}")
231
+ Dependabot.logger.info("Falling back to system Python")
305
232
  end
306
233
  end
307
234
 
@@ -386,6 +313,10 @@ module Dependabot
386
313
  def uv_lock
387
314
  dependency_files.find { |f| f.name == "uv.lock" }
388
315
  end
316
+
317
+ def create_or_update_lock_file?
318
+ dependency.requirements.select { _1[:file].end_with?(*REQUIRED_FILES) }.any?
319
+ end
389
320
  end
390
321
  end
391
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.301.1
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-14 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.301.1
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.301.1
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.301.1
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: