dependabot-python 0.280.0 → 0.281.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: 769029ca498ad0bf57b8e8a3f7470dded08b67253371747cdc2cd14f19733660
4
- data.tar.gz: '05984613c4a3eb2298b755c4e96287c4d1b15a94553a378ab31b8992d20151b7'
3
+ metadata.gz: 1273f0793f6c0432a5a66468ec19a460c854eda211c935c45af189d1c7b11e9d
4
+ data.tar.gz: c938f342ad448550e12036631ecefe4623d5d16afebf21aa01bfe85a0c048ab2
5
5
  SHA512:
6
- metadata.gz: c3de2cf52a0326efb5e91b045001bfca0f25574394425f70ab59b7c6fe15f9618f690cfd847e8736c81b29d3e8fd28e5524931db4f9bad90171f8daffeeafad3
7
- data.tar.gz: d47f611d5f940b9c420f727d2f44d19308a7317a6ca799e86a29369401c64c6e833020f4279a991515ecfe3dcf6c3522d5b0d9aea8a282cbcc181b3a2d3c303a
6
+ metadata.gz: a2ae4f68e9cdba267b00989165544e24384d8cd10b58c99802066214fb0b354a9c331af03e8b611a0195eda2d6d760c8cb354584b29b3a7b0d7f42e8e14067a7
7
+ data.tar.gz: 39a45d259c975b32debd5f3d21edd53622fb863630da7fe0bdafb6170f6adde5e3f556c36896cac357475b14797ac878855c4b9d171f7d29799bc8bd8ac20854
@@ -36,7 +36,17 @@ module Dependabot
36
36
  line = matches[1]
37
37
  end
38
38
 
39
- unless (matches = PATTERN.match(line))
39
+ pattern = PATTERN
40
+
41
+ if Dependabot::Experiments.enabled?(:python_new_version)
42
+ quoted = OPS.keys.sort_by(&:length).reverse
43
+ .map { |k| Regexp.quote(k) }.join("|")
44
+ version_pattern = Python::Version::NEW_VERSION_PATTERN
45
+ pattern_raw = "\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze
46
+ pattern = /\A#{pattern_raw}\z/
47
+ end
48
+
49
+ unless (matches = pattern.match(line))
40
50
  msg = "Illformed requirement [#{obj.inspect}]"
41
51
  raise BadRequirementError, msg
42
52
  end
@@ -128,7 +138,8 @@ module Dependabot
128
138
  upper_bound = parts.map.with_index do |part, i|
129
139
  if i < first_non_zero_index then part
130
140
  elsif i == first_non_zero_index then (part.to_i + 1).to_s
131
- elsif i > first_non_zero_index && i == 2 then "0.a"
141
+ # .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
142
+ elsif i > first_non_zero_index && i == 2 then "0.dev"
132
143
  else
133
144
  0
134
145
  end
@@ -151,7 +162,7 @@ module Dependabot
151
162
  .first(req_string.split(".").index { |s| s.include?("*") } + 1)
152
163
  .join(".")
153
164
  .gsub(/\*(?!$)/, "0")
154
- .gsub(/\*$/, "0.a")
165
+ .gsub(/\*$/, "0.dev")
155
166
  .tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
156
167
  end
157
168
  end
@@ -4,71 +4,333 @@
4
4
  require "dependabot/version"
5
5
  require "dependabot/utils"
6
6
 
7
- # Python versions can include a local version identifier, which Ruby can't
8
- # parse. This class augments Gem::Version with local version identifier info.
9
- # See https://www.python.org/dev/peps/pep-0440 for details.
7
+ # See https://packaging.python.org/en/latest/specifications/version-specifiers for spec details.
10
8
 
11
9
  module Dependabot
12
10
  module Python
13
11
  class Version < Dependabot::Version
12
+ sig { returns(Integer) }
14
13
  attr_reader :epoch
14
+
15
+ sig { returns(T::Array[Integer]) }
16
+ attr_reader :release_segment
17
+
18
+ sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
19
+ attr_reader :dev
20
+
21
+ sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
22
+ attr_reader :pre
23
+
24
+ sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
25
+ attr_reader :post
26
+
27
+ sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
28
+ attr_reader :local
29
+
15
30
  attr_reader :local_version
16
31
  attr_reader :post_release_version
17
32
 
33
+ INFINITY = 1000
34
+ NEGATIVE_INFINITY = -INFINITY
35
+
18
36
  # See https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
37
+ NEW_VERSION_PATTERN = /
38
+ v?
39
+ (?:
40
+ (?:(?<epoch>[0-9]+)!)? # epoch
41
+ (?<release>[0-9]+(?:\.[0-9]+)*) # release
42
+ (?<pre> # prerelease
43
+ [-_\.]?
44
+ (?<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
45
+ [-_\.]?
46
+ (?<pre_n>[0-9]+)?
47
+ )?
48
+ (?<post> # post release
49
+ (?:-(?<post_n1>[0-9]+))
50
+ |
51
+ (?:
52
+ [-_\.]?
53
+ (?<post_l>post|rev|r)
54
+ [-_\.]?
55
+ (?<post_n2>[0-9]+)?
56
+ )
57
+ )?
58
+ (?<dev> # dev release
59
+ [-_\.]?
60
+ (?<dev_l>dev)
61
+ [-_\.]?
62
+ (?<dev_n>[0-9]+)?
63
+ )?
64
+ )
65
+ (?:\+(?<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
66
+ /ix
67
+
19
68
  VERSION_PATTERN = 'v?([1-9][0-9]*!)?[0-9]+[0-9a-zA-Z]*(?>\.[0-9a-zA-Z]+)*' \
20
69
  '(-[0-9A-Za-z]+(\.[0-9a-zA-Z]+)*)?' \
21
70
  '(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?'
22
- ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/
23
71
 
72
+ ANCHORED_VERSION_PATTERN = /\A\s*#{VERSION_PATTERN}\s*\z/
73
+
74
+ sig { override.params(version: VersionParameter).returns(T::Boolean) }
24
75
  def self.correct?(version)
25
76
  return false if version.nil?
26
77
 
27
- version.to_s.match?(ANCHORED_VERSION_PATTERN)
78
+ if Dependabot::Experiments.enabled?(:python_new_version)
79
+ version.to_s.match?(/\A\s*#{NEW_VERSION_PATTERN}\s*\z/o)
80
+ else
81
+ version.to_s.match?(ANCHORED_VERSION_PATTERN)
82
+ end
28
83
  end
29
84
 
30
- def initialize(version)
85
+ sig { override.params(version: VersionParameter).void }
86
+ def initialize(version) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity
87
+ raise Dependabot::BadRequirementError, "Malformed version string - string is nil" if version.nil?
88
+
31
89
  @version_string = version.to_s
32
- version, @local_version = @version_string.split("+")
33
- version ||= ""
34
- version = version.gsub(/^v/, "")
35
- if version.include?("!")
36
- @epoch, version = version.split("!")
90
+
91
+ raise Dependabot::BadRequirementError, "Malformed version string - string is empty" if @version_string.empty?
92
+
93
+ matches = anchored_version_pattern.match(@version_string.downcase)
94
+
95
+ unless matches
96
+ raise Dependabot::BadRequirementError,
97
+ "Malformed version string - #{@version_string} does not match regex"
98
+ end
99
+
100
+ if Dependabot::Experiments.enabled?(:python_new_version)
101
+ @epoch = matches["epoch"].to_i
102
+ @release_segment = matches["release"]&.split(".")&.map(&:to_i) || []
103
+ @pre = parse_letter_version(matches["pre_l"], matches["pre_n"])
104
+ @post = parse_letter_version(matches["post_l"], matches["post_n1"] || matches["post_n2"])
105
+ @dev = parse_letter_version(matches["dev_l"], matches["dev_n"])
106
+ @local = parse_local_version(matches["local"])
107
+ super(matches["release"] || "")
37
108
  else
38
- @epoch = "0"
109
+ version, @local_version = @version_string.split("+")
110
+ version ||= ""
111
+ version = version.gsub(/^v/, "")
112
+ if version.include?("!")
113
+ epoch, version = version.split("!")
114
+ @epoch = epoch.to_i
115
+ else
116
+ @epoch = 0
117
+ end
118
+ version = normalise_prerelease(version)
119
+ version, @post_release_version = version.split(/\.r(?=\d)/)
120
+ version ||= ""
121
+ @local_version = normalise_prerelease(@local_version) if @local_version
122
+ super
39
123
  end
40
- version = normalise_prerelease(version)
41
- version, @post_release_version = version.split(/\.r(?=\d)/)
42
- version ||= ""
43
- @local_version = normalise_prerelease(@local_version) if @local_version
44
- super
45
124
  end
46
125
 
126
+ sig { override.params(version: VersionParameter).returns(Dependabot::Python::Version) }
127
+ def self.new(version)
128
+ T.cast(super, Dependabot::Python::Version)
129
+ end
130
+
131
+ sig { returns(String) }
47
132
  def to_s
48
133
  @version_string
49
134
  end
50
135
 
136
+ sig { returns(String) }
51
137
  def inspect # :nodoc:
52
138
  "#<#{self.class} #{@version_string}>"
53
139
  end
54
140
 
55
- def <=>(other)
56
- other = Version.new(other.to_s) unless other.is_a?(Python::Version)
141
+ sig { returns(T::Boolean) }
142
+ def prerelease?
143
+ return super unless Dependabot::Experiments.enabled?(:python_new_version)
57
144
 
58
- epoch_comparison = epoch_comparison(other)
59
- return epoch_comparison unless epoch_comparison.zero?
145
+ !!(pre || dev)
146
+ end
147
+
148
+ sig { returns(T.any(Gem::Version, Dependabot::Python::Version)) }
149
+ def release
150
+ return super unless Dependabot::Experiments.enabled?(:python_new_version)
151
+
152
+ Dependabot::Python::Version.new(release_segment.join("."))
153
+ end
154
+
155
+ sig { params(other: VersionParameter).returns(Integer) }
156
+ def <=>(other) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity
157
+ other = Dependabot::Python::Version.new(other.to_s) unless other.is_a?(Dependabot::Python::Version)
158
+ other = T.cast(other, Dependabot::Python::Version)
60
159
 
61
- version_comparison = super
62
- return version_comparison unless version_comparison&.zero?
160
+ if Dependabot::Experiments.enabled?(:python_new_version)
161
+ epoch_comparison = epoch <=> other.epoch
162
+ return epoch_comparison unless epoch_comparison.zero?
63
163
 
64
- post_version_comparison = post_version_comparison(other)
65
- return post_version_comparison unless post_version_comparison.zero?
164
+ release_comparison = release_version_comparison(other)
165
+ return release_comparison unless release_comparison.zero?
166
+
167
+ pre_comparison = compare_keys(pre_cmp_key, other.pre_cmp_key)
168
+ return pre_comparison unless pre_comparison.zero?
169
+
170
+ post_comparison = compare_keys(post_cmp_key, other.post_cmp_key)
171
+ return post_comparison unless post_comparison.zero?
172
+
173
+ dev_comparison = compare_keys(dev_cmp_key, other.dev_cmp_key)
174
+ return dev_comparison unless dev_comparison.zero?
175
+
176
+ compare_keys(local_cmp_key, other.local_cmp_key)
177
+ else
178
+ epoch_comparison = epoch_comparison(other)
179
+ return epoch_comparison unless epoch_comparison.zero?
66
180
 
67
- local_version_comparison(other)
181
+ version_comparison = super
182
+ return T.must(version_comparison) unless version_comparison&.zero?
183
+
184
+ post_version_comparison = post_version_comparison(other)
185
+ return post_version_comparison unless post_version_comparison.zero?
186
+
187
+ local_version_comparison(other)
188
+ end
189
+ end
190
+
191
+ sig do
192
+ params(
193
+ key: T.any(Integer, T::Array[T.any(String, Integer)]),
194
+ other_key: T.any(Integer, T::Array[T.any(String, Integer)])
195
+ ).returns(Integer)
196
+ end
197
+ def compare_keys(key, other_key)
198
+ if key.is_a?(Integer) && other_key.is_a?(Integer)
199
+ key <=> other_key
200
+ elsif key.is_a?(Array) && other_key.is_a?(Array)
201
+ key <=> other_key
202
+ elsif key.is_a?(Integer)
203
+ key == NEGATIVE_INFINITY ? -1 : 1
204
+ elsif other_key.is_a?(Integer)
205
+ other_key == NEGATIVE_INFINITY ? 1 : -1
206
+ end
207
+ end
208
+
209
+ sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
210
+ def pre_cmp_key
211
+ if pre.nil? && post.nil? && dev # sort 1.0.dev0 before 1.0a0
212
+ NEGATIVE_INFINITY
213
+ elsif pre.nil?
214
+ INFINITY # versions without a pre-release should sort after those with one.
215
+ else
216
+ T.must(pre)
217
+ end
218
+ end
219
+
220
+ sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
221
+ def local_cmp_key
222
+ if local.nil?
223
+ # Versions without a local segment should sort before those with one.
224
+ NEGATIVE_INFINITY
225
+ else
226
+ # According to PEP440.
227
+ # - Alphanumeric segments sort before numeric segments
228
+ # - Alphanumeric segments sort lexicographically
229
+ # - Numeric segments sort numerically
230
+ # - Shorter versions sort before longer versions when the prefixes match exactly
231
+ local&.map do |token|
232
+ if token.is_a?(Integer)
233
+ [token, ""]
234
+ else
235
+ [NEGATIVE_INFINITY, token]
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
242
+ def post_cmp_key
243
+ # Versions without a post segment should sort before those with one.
244
+ return NEGATIVE_INFINITY if post.nil?
245
+
246
+ T.must(post)
247
+ end
248
+
249
+ sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
250
+ def dev_cmp_key
251
+ # Versions without a dev segment should sort after those with one.
252
+ return INFINITY if dev.nil?
253
+
254
+ T.must(dev)
68
255
  end
69
256
 
70
257
  private
71
258
 
259
+ sig { params(other: Dependabot::Python::Version).returns(Integer) }
260
+ def release_version_comparison(other)
261
+ tokens, other_tokens = pad_for_comparison(release_segment, other.release_segment)
262
+ tokens <=> other_tokens
263
+ end
264
+
265
+ sig do
266
+ params(
267
+ tokens: T::Array[Integer],
268
+ other_tokens: T::Array[Integer]
269
+ ).returns(T::Array[T::Array[Integer]])
270
+ end
271
+ def pad_for_comparison(tokens, other_tokens)
272
+ tokens = tokens.dup
273
+ other_tokens = other_tokens.dup
274
+
275
+ longer = [tokens, other_tokens].max_by(&:count)
276
+ shorter = [tokens, other_tokens].min_by(&:count)
277
+
278
+ difference = T.must(longer).length - T.must(shorter).length
279
+
280
+ difference.times { T.must(shorter) << 0 }
281
+
282
+ [tokens, other_tokens]
283
+ end
284
+
285
+ sig { params(local: T.nilable(String)).returns(T.nilable(T::Array[T.any(String, Integer)])) }
286
+ def parse_local_version(local)
287
+ return if local.nil?
288
+
289
+ # Takes a string like abc.1.twelve and turns it into ["abc", 1, "twelve"]
290
+ local.split(/[\._-]/).map { |s| /^\d+$/.match?(s) ? s.to_i : s }
291
+ end
292
+
293
+ sig do
294
+ params(
295
+ letter: T.nilable(String), number: T.nilable(String)
296
+ ).returns(T.nilable(T::Array[T.any(String, Integer)]))
297
+ end
298
+ def parse_letter_version(letter = nil, number = nil)
299
+ return if letter.nil? && number.nil?
300
+
301
+ if letter
302
+ # Implicit 0 for cases where prerelease has no numeral
303
+ number ||= 0
304
+
305
+ # Normalize alternate spellings
306
+ if letter == "alpha"
307
+ letter = "a"
308
+ elsif letter == "beta"
309
+ letter = "b"
310
+ elsif %w(c pre preview).include? letter
311
+ letter = "rc"
312
+ elsif %w(rev r).include? letter
313
+ letter = "post"
314
+ end
315
+
316
+ return letter, number.to_i
317
+ end
318
+
319
+ # Number but no letter i.e. implicit post release syntax (e.g. 1.0-1)
320
+ letter = "post"
321
+
322
+ [letter, number.to_i]
323
+ end
324
+
325
+ sig { returns(Regexp) }
326
+ def anchored_version_pattern
327
+ if Dependabot::Experiments.enabled?(:python_new_version)
328
+ /\A\s*#{NEW_VERSION_PATTERN}\s*\z/o
329
+ else
330
+ ANCHORED_VERSION_PATTERN
331
+ end
332
+ end
333
+
72
334
  def epoch_comparison(other)
73
335
  epoch.to_i <=> other.epoch.to_i
74
336
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-python
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.280.0
4
+ version: 0.281.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-10 00:00:00.000000000 Z
11
+ date: 2024-10-17 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.280.0
19
+ version: 0.281.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.280.0
26
+ version: 0.281.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -288,8 +288,8 @@ licenses:
288
288
  - MIT
289
289
  metadata:
290
290
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
291
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.280.0
292
- post_install_message:
291
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.281.0
292
+ post_install_message:
293
293
  rdoc_options: []
294
294
  require_paths:
295
295
  - lib
@@ -305,7 +305,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
305
305
  version: 3.1.0
306
306
  requirements: []
307
307
  rubygems_version: 3.5.9
308
- signing_key:
308
+ signing_key:
309
309
  specification_version: 4
310
310
  summary: Provides Dependabot support for Python
311
311
  test_files: []