dependabot-python 0.104.0 → 0.104.1

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: 93762e9f3a2a0f2f376cc958c13b0d4ec07cd07bb6208d4859566bb38dc8c1bb
4
- data.tar.gz: 1ee335372559b02cd5ea0a12541875eb3c389726a759beb68d3dd22cbc9b9cc6
3
+ metadata.gz: 1704546d9328a26bba38bd159150a5238797253f07538aa5ee731e307d1a5a86
4
+ data.tar.gz: 4e1a258ebb51cedb6cd6c801d2ac4a7f2f191658b163b56046a7ab526c24c287
5
5
  SHA512:
6
- metadata.gz: d4a11b74a94191a3e2e102a937d3ea327e0b46d0fda1b74faafd0f7626578a7bc6d496918c65e44b7895aa0103b92026d0dad323a1d8b872ef0c13634212c116
7
- data.tar.gz: 6517220833b3f29e267f010ec2cc5f94aa2342ee4bc6edc8af83a490667ac3d97b625ab0c8c3d51dcd2da0735c55a29ebd0189c87cf67e78031a96ec4868c7ba
6
+ metadata.gz: 1b994991d16cafe0ece709fdc52f5bbe5f25cb3dd635ad08fab6b87885926afe281231642d28cfe24e26724bccd4427750d99dad27e718882ecc04bf333e8ff5
7
+ data.tar.gz: 37ffc6c72e569f50b2c73999089237512b08742e10473fb02f5b7047751be623ffeb9d9d8298376e1748748274dfe22c64c6a15ee60647de831317f8caf71e33
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/python/update_checker"
4
+ require "dependabot/python/authed_url_builder"
5
+ require "dependabot/errors"
6
+
7
+ module Dependabot
8
+ module Python
9
+ class UpdateChecker
10
+ class IndexFinder
11
+ PYPI_BASE_URL = "https://pypi.python.org/simple/"
12
+ ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/.freeze
13
+
14
+ def initialize(dependency_files:, credentials:)
15
+ @dependency_files = dependency_files
16
+ @credentials = credentials
17
+ end
18
+
19
+ def index_urls
20
+ extra_index_urls =
21
+ config_variable_index_urls[:extra] +
22
+ pipfile_index_urls[:extra] +
23
+ requirement_file_index_urls[:extra] +
24
+ pip_conf_index_urls[:extra]
25
+
26
+ extra_index_urls = extra_index_urls.map do |url|
27
+ clean_check_and_remove_environment_variables(url)
28
+ end
29
+
30
+ # URL encode any `@` characters within registry URL creds.
31
+ # TODO: The test that fails if the `map` here is removed is likely a
32
+ # bug in Ruby's URI parser, and should be fixed there.
33
+ [main_index_url, *extra_index_urls].map do |url|
34
+ url.rpartition("@").tap { |a| a.first.gsub!("@", "%40") }.join
35
+ end.uniq
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :dependency_files, :credentials
41
+
42
+ def main_index_url
43
+ url =
44
+ config_variable_index_urls[:main] ||
45
+ pipfile_index_urls[:main] ||
46
+ requirement_file_index_urls[:main] ||
47
+ pip_conf_index_urls[:main] ||
48
+ PYPI_BASE_URL
49
+
50
+ return unless url
51
+
52
+ clean_check_and_remove_environment_variables(url)
53
+ end
54
+
55
+ def requirement_file_index_urls
56
+ urls = { main: nil, extra: [] }
57
+
58
+ requirements_files.each do |file|
59
+ if file.content.match?(/^--index-url\s(.+)/)
60
+ urls[:main] =
61
+ file.content.match(/^--index-url\s(.+)/).captures.first
62
+ end
63
+ urls[:extra] += file.content.scan(/^--extra-index-url\s(.+)/).
64
+ flatten
65
+ end
66
+
67
+ urls
68
+ end
69
+
70
+ def pip_conf_index_urls
71
+ urls = { main: nil, extra: [] }
72
+
73
+ return urls unless pip_conf
74
+
75
+ content = pip_conf.content
76
+
77
+ if content.match?(/^index-url\s*=/x)
78
+ urls[:main] = content.match(/^index-url\s*=\s*(.+)/).
79
+ captures.first
80
+ end
81
+ urls[:extra] += content.scan(/^extra-index-url\s*=(.+)/).flatten
82
+
83
+ urls
84
+ end
85
+
86
+ def pipfile_index_urls
87
+ urls = { main: nil, extra: [] }
88
+
89
+ return urls unless pipfile
90
+
91
+ pipfile_object = TomlRB.parse(pipfile.content)
92
+
93
+ urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
94
+
95
+ pipfile_object["source"]&.each do |source|
96
+ urls[:extra] << source.fetch("url") if source["url"]
97
+ end
98
+ urls[:extra] = urls[:extra].uniq
99
+
100
+ urls
101
+ rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
102
+ urls
103
+ end
104
+
105
+ def config_variable_index_urls
106
+ urls = { main: nil, extra: [] }
107
+
108
+ index_url_creds = credentials.
109
+ select { |cred| cred["type"] == "python_index" }
110
+
111
+ if (main_cred = index_url_creds.find { |cred| cred["replaces-base"] })
112
+ urls[:main] = AuthedUrlBuilder.authed_url(credential: main_cred)
113
+ end
114
+
115
+ urls[:extra] =
116
+ index_url_creds.
117
+ reject { |cred| cred["replaces-base"] }.
118
+ map { |cred| AuthedUrlBuilder.authed_url(credential: cred) }
119
+
120
+ urls
121
+ end
122
+
123
+ def clean_check_and_remove_environment_variables(url)
124
+ url = url.strip.gsub(%r{/*$}, "") + "/"
125
+
126
+ unless url.match?(ENVIRONMENT_VARIABLE_REGEX)
127
+ return authed_base_url(url)
128
+ end
129
+
130
+ config_variable_urls =
131
+ [
132
+ config_variable_index_urls[:main],
133
+ *config_variable_index_urls[:extra]
134
+ ].
135
+ compact.
136
+ map { |u| u.strip.gsub(%r{/*$}, "") + "/" }
137
+
138
+ regexp = url.split(ENVIRONMENT_VARIABLE_REGEX).
139
+ map { |part| Regexp.quote(part) }.
140
+ join(".+")
141
+ authed_url = config_variable_urls.find { |u| u.match?(regexp) }
142
+ return authed_url if authed_url
143
+
144
+ cleaned_url = url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}, "")
145
+ authed_url = authed_base_url(cleaned_url)
146
+ return authed_url if credential_for(cleaned_url)
147
+
148
+ raise PrivateSourceAuthenticationFailure, url
149
+ end
150
+
151
+ def authed_base_url(base_url)
152
+ cred = credential_for(base_url)
153
+ return base_url unless cred
154
+
155
+ AuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$}, "") + "/"
156
+ end
157
+
158
+ def credential_for(url)
159
+ credentials.
160
+ select { |c| c["type"] == "python_index" }.
161
+ find do |c|
162
+ cred_url = c.fetch("index-url").gsub(%r{/*$}, "") + "/"
163
+ cred_url.include?(url)
164
+ end
165
+ end
166
+
167
+ def pip_conf
168
+ dependency_files.find { |f| f.name == "pip.conf" }
169
+ end
170
+
171
+ def pipfile
172
+ dependency_files.find { |f| f.name == "Pipfile" }
173
+ end
174
+
175
+ def pyproject
176
+ dependency_files.find { |f| f.name == "pyproject.toml" }
177
+ end
178
+
179
+ def requirements_files
180
+ dependency_files.select { |f| f.name.match?(/requirements/x) }
181
+ end
182
+
183
+ def pip_compile_files
184
+ dependency_files.select { |f| f.name.end_with?(".in") }
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -10,7 +10,7 @@ module Dependabot
10
10
  module Python
11
11
  class UpdateChecker
12
12
  class LatestVersionFinder
13
- ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/.freeze
13
+ require_relative "index_finder"
14
14
 
15
15
  def initialize(dependency:, dependency_files:, credentials:,
16
16
  ignored_versions:)
@@ -36,21 +36,37 @@ module Dependabot
36
36
 
37
37
  def fetch_latest_version
38
38
  versions = available_versions
39
- versions.reject! { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }
40
- versions.reject!(&:prerelease?) unless wants_prerelease?
39
+ versions = filter_prerelease_versions(versions)
40
+ versions = filter_ignored_versions(versions)
41
41
  versions.max
42
42
  end
43
43
 
44
44
  def fetch_latest_version_with_no_unlock
45
45
  versions = available_versions
46
+ versions = filter_prerelease_versions(versions)
47
+ versions = filter_ignored_versions(versions)
48
+ versions = filter_out_of_range_versions(versions)
49
+ versions.max
50
+ end
51
+
52
+ def filter_prerelease_versions(versions_array)
53
+ return versions_array if wants_prerelease?
54
+
55
+ versions_array.reject(&:prerelease?)
56
+ end
57
+
58
+ def filter_ignored_versions(versions_array)
59
+ versions_array.
60
+ reject { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }
61
+ end
62
+
63
+ def filter_out_of_range_versions(versions_array)
46
64
  reqs = dependency.requirements.map do |r|
47
- reqs = (r.fetch(:requirement) || "").split(",").map(&:strip)
48
- requirement_class.new(reqs)
49
- end
50
- versions.reject!(&:prerelease?) unless wants_prerelease?
51
- versions.sort.reverse.
52
- reject { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }.
53
- find { |v| reqs.all? { |r| r.satisfied_by?(v) } }
65
+ requirement_class.requirements_array(r.fetch(:requirement))
66
+ end.compact
67
+
68
+ versions_array.
69
+ select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } }
54
70
  end
55
71
 
56
72
  def wants_prerelease?
@@ -68,65 +84,42 @@ module Dependabot
68
84
  # See https://www.python.org/dev/peps/pep-0503/ for details of the
69
85
  # Simple Repository API we use here.
70
86
  def available_versions
71
- index_urls.flat_map do |index_url|
72
- sanitized_url = index_url.gsub(%r{(?<=//).*(?=@)}, "redacted")
73
- index_response = registry_response_for_dependency(index_url)
87
+ @available_versions ||=
88
+ index_urls.flat_map do |index_url|
89
+ sanitized_url = index_url.gsub(%r{(?<=//).*(?=@)}, "redacted")
90
+ index_response = registry_response_for_dependency(index_url)
91
+
92
+ if [401, 403].include?(index_response.status) &&
93
+ [401, 403].include?(registry_index_response(index_url).status)
94
+ raise PrivateSourceAuthenticationFailure, sanitized_url
95
+ end
96
+
97
+ index_response.body.
98
+ scan(%r{<a\s.*?>(.*?)</a>}m).flatten.
99
+ select { |n| n.match?(name_regex) }.
100
+ map do |filename|
101
+ version =
102
+ filename.
103
+ gsub(/#{name_regex}-/i, "").
104
+ split(/-|\.tar\.|\.zip|\.whl/).
105
+ first
106
+ next unless version_class.correct?(version)
107
+
108
+ version_class.new(version)
109
+ end.compact
110
+ rescue Excon::Error::Timeout, Excon::Error::Socket
111
+ raise if MAIN_PYPI_INDEXES.include?(index_url)
74
112
 
75
- if [401, 403].include?(index_response.status) &&
76
- [401, 403].include?(registry_index_response(index_url).status)
77
113
  raise PrivateSourceAuthenticationFailure, sanitized_url
78
114
  end
79
-
80
- index_response.body.
81
- scan(%r{<a\s.*?>(.*?)</a>}m).flatten.
82
- select { |n| n.match?(name_regex) }.
83
- map do |filename|
84
- version =
85
- filename.
86
- gsub(/#{name_regex}-/i, "").
87
- split(/-|\.tar\.|\.zip|\.whl/).
88
- first
89
- next unless version_class.correct?(version)
90
-
91
- version_class.new(version)
92
- end.compact
93
- rescue Excon::Error::Timeout, Excon::Error::Socket
94
- next if MAIN_PYPI_INDEXES.include?(index_url)
95
-
96
- raise PrivateSourceAuthenticationFailure, sanitized_url
97
- end
98
115
  end
99
116
 
100
117
  def index_urls
101
- extra_index_urls =
102
- config_variable_index_urls[:extra] +
103
- pipfile_index_urls[:extra] +
104
- requirement_file_index_urls[:extra] +
105
- pip_conf_index_urls[:extra]
106
-
107
- extra_index_urls = extra_index_urls.map do |url|
108
- clean_check_and_remove_environment_variables(url)
109
- end
110
-
111
- # URL encode any `@` characters within registry URL creds.
112
- # TODO: The test that fails if the `map` here is removed is likely a
113
- # bug in Ruby's URI parser, and should be fixed there.
114
- [main_index_url, *extra_index_urls].map do |url|
115
- url.rpartition("@").tap { |a| a.first.gsub!("@", "%40") }.join
116
- end.uniq
117
- end
118
-
119
- def main_index_url
120
- url =
121
- config_variable_index_urls[:main] ||
122
- pipfile_index_urls[:main] ||
123
- requirement_file_index_urls[:main] ||
124
- pip_conf_index_urls[:main] ||
125
- "https://pypi.python.org/simple/"
126
-
127
- return unless url
128
-
129
- clean_check_and_remove_environment_variables(url)
118
+ @index_urls ||=
119
+ IndexFinder.new(
120
+ dependency_files: dependency_files,
121
+ credentials: credentials
122
+ ).index_urls
130
123
  end
131
124
 
132
125
  def registry_response_for_dependency(index_url)
@@ -145,118 +138,6 @@ module Dependabot
145
138
  )
146
139
  end
147
140
 
148
- def requirement_file_index_urls
149
- urls = { main: nil, extra: [] }
150
-
151
- requirements_files.each do |file|
152
- if file.content.match?(/^--index-url\s(.+)/)
153
- urls[:main] =
154
- file.content.match(/^--index-url\s(.+)/).captures.first
155
- end
156
- urls[:extra] += file.content.scan(/^--extra-index-url\s(.+)/).
157
- flatten
158
- end
159
-
160
- urls
161
- end
162
-
163
- def pip_conf_index_urls
164
- urls = { main: nil, extra: [] }
165
-
166
- return urls unless pip_conf
167
-
168
- content = pip_conf.content
169
-
170
- if content.match?(/^index-url\s*=/x)
171
- urls[:main] = content.match(/^index-url\s*=\s*(.+)/).
172
- captures.first
173
- end
174
- urls[:extra] += content.scan(/^extra-index-url\s*=(.+)/).flatten
175
-
176
- urls
177
- end
178
-
179
- def pipfile_index_urls
180
- urls = { main: nil, extra: [] }
181
-
182
- return urls unless pipfile
183
-
184
- pipfile_object = TomlRB.parse(pipfile.content)
185
-
186
- urls[:main] = pipfile_object["source"]&.first&.fetch("url", nil)
187
-
188
- pipfile_object["source"]&.each do |source|
189
- urls[:extra] << source.fetch("url") if source["url"]
190
- end
191
- urls[:extra] = urls[:extra].uniq
192
-
193
- urls
194
- rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
195
- urls
196
- end
197
-
198
- def config_variable_index_urls
199
- urls = { main: nil, extra: [] }
200
-
201
- index_url_creds = credentials.
202
- select { |cred| cred["type"] == "python_index" }
203
-
204
- if (main_cred = index_url_creds.find { |cred| cred["replaces-base"] })
205
- urls[:main] = AuthedUrlBuilder.authed_url(credential: main_cred)
206
- end
207
-
208
- urls[:extra] =
209
- index_url_creds.
210
- reject { |cred| cred["replaces-base"] }.
211
- map { |cred| AuthedUrlBuilder.authed_url(credential: cred) }
212
-
213
- urls
214
- end
215
-
216
- def clean_check_and_remove_environment_variables(url)
217
- url = url.strip.gsub(%r{/*$}, "") + "/"
218
-
219
- unless url.match?(ENVIRONMENT_VARIABLE_REGEX)
220
- return authed_base_url(url)
221
- end
222
-
223
- config_variable_urls =
224
- [
225
- config_variable_index_urls[:main],
226
- *config_variable_index_urls[:extra]
227
- ].
228
- compact.
229
- map { |u| u.strip.gsub(%r{/*$}, "") + "/" }
230
-
231
- regexp = url.split(ENVIRONMENT_VARIABLE_REGEX).
232
- map { |part| Regexp.quote(part) }.
233
- join(".+")
234
- authed_url = config_variable_urls.find { |u| u.match?(regexp) }
235
- return authed_url if authed_url
236
-
237
- cleaned_url = url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}, "")
238
- authed_url = authed_base_url(cleaned_url)
239
- return authed_url if credential_for(cleaned_url)
240
-
241
- raise PrivateSourceAuthenticationFailure, url
242
- end
243
-
244
- def authed_base_url(base_url)
245
- cred = credential_for(base_url)
246
- return base_url unless cred
247
-
248
- AuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$}, "") + "/"
249
- end
250
-
251
- def credential_for(url)
252
- credentials.
253
- select { |c| c["type"] == "python_index" }.
254
- find do |c|
255
- cred_url = c.fetch("index-url").gsub(%r{/*$}, "") + "/"
256
- cred_url.include?(url)
257
- end
258
- end
259
-
260
141
  def ignore_reqs
261
142
  ignored_versions.map { |req| requirement_class.new(req.split(",")) }
262
143
  end
@@ -271,26 +152,6 @@ module Dependabot
271
152
  /#{parts.join("[\s_.-]")}/i
272
153
  end
273
154
 
274
- def pip_conf
275
- dependency_files.find { |f| f.name == "pip.conf" }
276
- end
277
-
278
- def pipfile
279
- dependency_files.find { |f| f.name == "Pipfile" }
280
- end
281
-
282
- def pyproject
283
- dependency_files.find { |f| f.name == "pyproject.toml" }
284
- end
285
-
286
- def requirements_files
287
- dependency_files.select { |f| f.name.match?(/requirements/x) }
288
- end
289
-
290
- def pip_compile_files
291
- dependency_files.select { |f| f.name.end_with?(".in") }
292
- end
293
-
294
155
  def version_class
295
156
  Utils.version_class_for_package_manager(dependency.package_manager)
296
157
  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.104.0
4
+ version: 0.104.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-16 00:00:00.000000000 Z
11
+ date: 2019-04-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.104.0
19
+ version: 0.104.1
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.104.0
26
+ version: 0.104.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -172,6 +172,7 @@ files:
172
172
  - lib/dependabot/python/requirement.rb
173
173
  - lib/dependabot/python/requirement_parser.rb
174
174
  - lib/dependabot/python/update_checker.rb
175
+ - lib/dependabot/python/update_checker/index_finder.rb
175
176
  - lib/dependabot/python/update_checker/latest_version_finder.rb
176
177
  - lib/dependabot/python/update_checker/pip_compile_version_resolver.rb
177
178
  - lib/dependabot/python/update_checker/pipfile_version_resolver.rb