dependabot-python 0.280.0 → 0.281.0

Sign up to get free protection for your applications and to get access to all the features.
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: []