dependabot-python 0.79.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 +7 -0
- data/helpers/build +17 -0
- data/helpers/lib/__init__.py +0 -0
- data/helpers/lib/hasher.py +23 -0
- data/helpers/lib/parser.py +130 -0
- data/helpers/requirements.txt +9 -0
- data/helpers/run.py +18 -0
- data/lib/dependabot/python.rb +11 -0
- data/lib/dependabot/python/file_fetcher.rb +307 -0
- data/lib/dependabot/python/file_parser.rb +221 -0
- data/lib/dependabot/python/file_parser/pipfile_files_parser.rb +150 -0
- data/lib/dependabot/python/file_parser/poetry_files_parser.rb +139 -0
- data/lib/dependabot/python/file_parser/setup_file_parser.rb +158 -0
- data/lib/dependabot/python/file_updater.rb +149 -0
- data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +361 -0
- data/lib/dependabot/python/file_updater/pipfile_file_updater.rb +391 -0
- data/lib/dependabot/python/file_updater/pipfile_preparer.rb +123 -0
- data/lib/dependabot/python/file_updater/poetry_file_updater.rb +282 -0
- data/lib/dependabot/python/file_updater/pyproject_preparer.rb +103 -0
- data/lib/dependabot/python/file_updater/requirement_file_updater.rb +160 -0
- data/lib/dependabot/python/file_updater/requirement_replacer.rb +93 -0
- data/lib/dependabot/python/file_updater/setup_file_sanitizer.rb +89 -0
- data/lib/dependabot/python/metadata_finder.rb +122 -0
- data/lib/dependabot/python/native_helpers.rb +17 -0
- data/lib/dependabot/python/python_versions.rb +25 -0
- data/lib/dependabot/python/requirement.rb +129 -0
- data/lib/dependabot/python/requirement_parser.rb +38 -0
- data/lib/dependabot/python/update_checker.rb +229 -0
- data/lib/dependabot/python/update_checker/latest_version_finder.rb +250 -0
- data/lib/dependabot/python/update_checker/pip_compile_version_resolver.rb +379 -0
- data/lib/dependabot/python/update_checker/pipfile_version_resolver.rb +558 -0
- data/lib/dependabot/python/update_checker/poetry_version_resolver.rb +298 -0
- data/lib/dependabot/python/update_checker/requirements_updater.rb +365 -0
- data/lib/dependabot/python/version.rb +87 -0
- metadata +203 -0
@@ -0,0 +1,391 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/python/requirement_parser"
|
6
|
+
require "dependabot/python/file_updater"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
require "dependabot/python/native_helpers"
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
11
|
+
module Dependabot
|
12
|
+
module Python
|
13
|
+
class FileUpdater
|
14
|
+
class PipfileFileUpdater
|
15
|
+
require_relative "pipfile_preparer"
|
16
|
+
require_relative "setup_file_sanitizer"
|
17
|
+
|
18
|
+
attr_reader :dependencies, :dependency_files, :credentials
|
19
|
+
|
20
|
+
def initialize(dependencies:, dependency_files:, credentials:)
|
21
|
+
@dependencies = dependencies
|
22
|
+
@dependency_files = dependency_files
|
23
|
+
@credentials = credentials
|
24
|
+
end
|
25
|
+
|
26
|
+
def updated_dependency_files
|
27
|
+
return @updated_dependency_files if @update_already_attempted
|
28
|
+
|
29
|
+
@update_already_attempted = true
|
30
|
+
@updated_dependency_files ||= fetch_updated_dependency_files
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def dependency
|
36
|
+
# For now, we'll only ever be updating a single dependency
|
37
|
+
dependencies.first
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_updated_dependency_files
|
41
|
+
updated_files = []
|
42
|
+
|
43
|
+
if file_changed?(pipfile)
|
44
|
+
updated_files <<
|
45
|
+
updated_file(file: pipfile, content: updated_pipfile_content)
|
46
|
+
end
|
47
|
+
|
48
|
+
if lockfile
|
49
|
+
if lockfile.content == updated_lockfile_content
|
50
|
+
raise "Expected Pipfile.lock to change!"
|
51
|
+
end
|
52
|
+
|
53
|
+
updated_files <<
|
54
|
+
updated_file(file: lockfile, content: updated_lockfile_content)
|
55
|
+
end
|
56
|
+
|
57
|
+
updated_files += updated_generated_requirements_files
|
58
|
+
updated_files
|
59
|
+
end
|
60
|
+
|
61
|
+
def updated_pipfile_content
|
62
|
+
dependencies.
|
63
|
+
select { |dep| requirement_changed?(pipfile, dep) }.
|
64
|
+
reduce(pipfile.content.dup) do |content, dep|
|
65
|
+
updated_requirement =
|
66
|
+
dep.requirements.find { |r| r[:file] == pipfile.name }.
|
67
|
+
fetch(:requirement)
|
68
|
+
|
69
|
+
old_req =
|
70
|
+
dep.previous_requirements.
|
71
|
+
find { |r| r[:file] == pipfile.name }.
|
72
|
+
fetch(:requirement)
|
73
|
+
|
74
|
+
updated_content =
|
75
|
+
content.gsub(declaration_regex(dep)) do |line|
|
76
|
+
line.gsub(old_req, updated_requirement)
|
77
|
+
end
|
78
|
+
|
79
|
+
raise "Content did not change!" if content == updated_content
|
80
|
+
|
81
|
+
updated_content
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def updated_lockfile_content
|
86
|
+
@updated_lockfile_content ||=
|
87
|
+
updated_generated_files.fetch(:lockfile)
|
88
|
+
end
|
89
|
+
|
90
|
+
def generate_updated_requirements_files?
|
91
|
+
return true if generated_requirements_files("default").any?
|
92
|
+
|
93
|
+
generated_requirements_files("develop").any?
|
94
|
+
end
|
95
|
+
|
96
|
+
def generated_requirements_files(type)
|
97
|
+
return [] unless lockfile
|
98
|
+
|
99
|
+
pipfile_lock_deps = parsed_lockfile[type]&.keys&.sort || []
|
100
|
+
pipfile_lock_deps = pipfile_lock_deps.map { |n| normalise(n) }
|
101
|
+
|
102
|
+
regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
|
103
|
+
|
104
|
+
# Find any requirement files that list the same dependencies as
|
105
|
+
# the (old) Pipfile.lock. Any such files were almost certainly
|
106
|
+
# generated using `pipenv lock -r`
|
107
|
+
requirements_files.select do |req_file|
|
108
|
+
deps = []
|
109
|
+
req_file.content.scan(regex) { deps << Regexp.last_match }
|
110
|
+
deps = deps.map { |m| normalise(m[:name]) }
|
111
|
+
deps.sort == pipfile_lock_deps
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def updated_generated_requirements_files
|
116
|
+
updated_files = []
|
117
|
+
|
118
|
+
generated_requirements_files("default").each do |file|
|
119
|
+
next if file.content == updated_req_content
|
120
|
+
|
121
|
+
updated_files <<
|
122
|
+
updated_file(file: file, content: updated_req_content)
|
123
|
+
end
|
124
|
+
|
125
|
+
generated_requirements_files("develop").each do |file|
|
126
|
+
next if file.content == updated_dev_req_content
|
127
|
+
|
128
|
+
updated_files <<
|
129
|
+
updated_file(file: file, content: updated_dev_req_content)
|
130
|
+
end
|
131
|
+
|
132
|
+
updated_files
|
133
|
+
end
|
134
|
+
|
135
|
+
def updated_req_content
|
136
|
+
updated_generated_files.fetch(:requirements_txt)
|
137
|
+
end
|
138
|
+
|
139
|
+
def updated_dev_req_content
|
140
|
+
updated_generated_files.fetch(:dev_requirements_txt)
|
141
|
+
end
|
142
|
+
|
143
|
+
def prepared_pipfile_content
|
144
|
+
content = updated_pipfile_content
|
145
|
+
content = freeze_other_dependencies(content)
|
146
|
+
content = freeze_dependencies_being_updated(content)
|
147
|
+
content = add_private_sources(content)
|
148
|
+
content
|
149
|
+
end
|
150
|
+
|
151
|
+
def freeze_other_dependencies(pipfile_content)
|
152
|
+
PipfilePreparer.
|
153
|
+
new(pipfile_content: pipfile_content).
|
154
|
+
freeze_top_level_dependencies_except(dependencies, lockfile)
|
155
|
+
end
|
156
|
+
|
157
|
+
def freeze_dependencies_being_updated(pipfile_content)
|
158
|
+
pipfile_object = TomlRB.parse(pipfile_content)
|
159
|
+
|
160
|
+
dependencies.each do |dep|
|
161
|
+
%w(packages dev-packages).each do |type|
|
162
|
+
names = pipfile_object[type]&.keys || []
|
163
|
+
pkg_name = names.find { |nm| normalise(nm) == dep.name }
|
164
|
+
next unless pkg_name
|
165
|
+
|
166
|
+
if pipfile_object[type][pkg_name].is_a?(Hash)
|
167
|
+
pipfile_object[type][pkg_name]["version"] =
|
168
|
+
"==#{dep.version}"
|
169
|
+
else
|
170
|
+
pipfile_object[type][pkg_name] = "==#{dep.version}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
TomlRB.dump(pipfile_object)
|
176
|
+
end
|
177
|
+
|
178
|
+
def add_private_sources(pipfile_content)
|
179
|
+
PipfilePreparer.
|
180
|
+
new(pipfile_content: pipfile_content).
|
181
|
+
replace_sources(credentials)
|
182
|
+
end
|
183
|
+
|
184
|
+
def updated_generated_files
|
185
|
+
@updated_generated_files ||=
|
186
|
+
SharedHelpers.in_a_temporary_directory do
|
187
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
188
|
+
write_temporary_dependency_files(prepared_pipfile_content)
|
189
|
+
|
190
|
+
# Initialize a git repo to appease pip-tools
|
191
|
+
IO.popen("git init", err: %i(child out)) if setup_files.any?
|
192
|
+
|
193
|
+
run_pipenv_command(
|
194
|
+
pipenv_environment_variables + "pyenv exec pipenv lock"
|
195
|
+
)
|
196
|
+
|
197
|
+
result = { lockfile: File.read("Pipfile.lock") }
|
198
|
+
result[:lockfile] = post_process_lockfile(result[:lockfile])
|
199
|
+
|
200
|
+
# Generate updated requirement.txt entries, if needed.
|
201
|
+
if generate_updated_requirements_files?
|
202
|
+
generate_updated_requirements_files
|
203
|
+
|
204
|
+
result[:requirements_txt] = File.read("req.txt")
|
205
|
+
result[:dev_requirements_txt] = File.read("dev-req.txt")
|
206
|
+
end
|
207
|
+
|
208
|
+
result
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def post_process_lockfile(updated_lockfile_content)
|
214
|
+
pipfile_hash = pipfile_hash_for(updated_pipfile_content)
|
215
|
+
original_reqs = parsed_lockfile["_meta"]["requires"]
|
216
|
+
original_source = parsed_lockfile["_meta"]["sources"]
|
217
|
+
|
218
|
+
new_lockfile = updated_lockfile_content.dup
|
219
|
+
new_lockfile_json = JSON.parse(new_lockfile)
|
220
|
+
new_lockfile_json["_meta"]["hash"]["sha256"] = pipfile_hash
|
221
|
+
new_lockfile_json["_meta"]["requires"] = original_reqs
|
222
|
+
new_lockfile_json["_meta"]["sources"] = original_source
|
223
|
+
|
224
|
+
JSON.pretty_generate(new_lockfile_json, indent: " ").
|
225
|
+
gsub(/\{\n\s*\}/, "{}").
|
226
|
+
gsub(/\}\z/, "}\n")
|
227
|
+
end
|
228
|
+
|
229
|
+
def generate_updated_requirements_files
|
230
|
+
run_pipenv_command(
|
231
|
+
pipenv_environment_variables +
|
232
|
+
"pyenv exec pipenv lock -r > req.txt"
|
233
|
+
)
|
234
|
+
run_pipenv_command(
|
235
|
+
pipenv_environment_variables +
|
236
|
+
"pyenv exec pipenv lock -r -d > dev-req.txt"
|
237
|
+
)
|
238
|
+
end
|
239
|
+
|
240
|
+
def run_pipenv_command(cmd)
|
241
|
+
raw_response = nil
|
242
|
+
IO.popen(cmd, err: %i(child out)) { |p| raw_response = p.read }
|
243
|
+
|
244
|
+
# Raise an error with the output from the shell session if Pipenv
|
245
|
+
# returns a non-zero status
|
246
|
+
return if $CHILD_STATUS.success?
|
247
|
+
|
248
|
+
raise SharedHelpers::HelperSubprocessFailed.new(raw_response, cmd)
|
249
|
+
rescue SharedHelpers::HelperSubprocessFailed => error
|
250
|
+
original_error ||= error
|
251
|
+
msg = error.message
|
252
|
+
|
253
|
+
relevant_error =
|
254
|
+
if error_suggests_bad_python_version?(msg) then original_error
|
255
|
+
else error
|
256
|
+
end
|
257
|
+
|
258
|
+
raise relevant_error unless error_suggests_bad_python_version?(msg)
|
259
|
+
raise relevant_error if cmd.include?("--two")
|
260
|
+
|
261
|
+
cmd = cmd.gsub("pipenv ", "pipenv --two ")
|
262
|
+
retry
|
263
|
+
end
|
264
|
+
|
265
|
+
def error_suggests_bad_python_version?(message)
|
266
|
+
return true if message.include?("UnsupportedPythonVersion")
|
267
|
+
|
268
|
+
message.include?('Command "python setup.py egg_info" failed')
|
269
|
+
end
|
270
|
+
|
271
|
+
def write_temporary_dependency_files(pipfile_content)
|
272
|
+
dependency_files.each do |file|
|
273
|
+
next if file.name == ".python-version"
|
274
|
+
|
275
|
+
path = file.name
|
276
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
277
|
+
File.write(path, file.content)
|
278
|
+
end
|
279
|
+
|
280
|
+
setup_files.each do |file|
|
281
|
+
path = file.name
|
282
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
283
|
+
File.write(path, sanitized_setup_file_content(file))
|
284
|
+
end
|
285
|
+
|
286
|
+
setup_cfg_files.each do |file|
|
287
|
+
path = file.name
|
288
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
289
|
+
File.write(path, "[metadata]\nname = sanitized-package\n")
|
290
|
+
end
|
291
|
+
|
292
|
+
# Overwrite the pipfile with updated content
|
293
|
+
File.write("Pipfile", pipfile_content)
|
294
|
+
end
|
295
|
+
|
296
|
+
def sanitized_setup_file_content(file)
|
297
|
+
@sanitized_setup_file_content ||= {}
|
298
|
+
if @sanitized_setup_file_content[file.name]
|
299
|
+
return @sanitized_setup_file_content[file.name]
|
300
|
+
end
|
301
|
+
|
302
|
+
@sanitized_setup_file_content[file.name] =
|
303
|
+
SetupFileSanitizer.
|
304
|
+
new(setup_file: file, setup_cfg: setup_cfg(file)).
|
305
|
+
sanitized_content
|
306
|
+
end
|
307
|
+
|
308
|
+
def setup_cfg(file)
|
309
|
+
dependency_files.find do |f|
|
310
|
+
f.name == file.name.sub(/\.py$/, ".cfg")
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def pipfile_hash_for(pipfile_content)
|
315
|
+
SharedHelpers.in_a_temporary_directory do |dir|
|
316
|
+
File.write(File.join(dir, "Pipfile"), pipfile_content)
|
317
|
+
SharedHelpers.run_helper_subprocess(
|
318
|
+
command: "pyenv exec python #{NativeHelpers.python_helper_path}",
|
319
|
+
function: "get_pipfile_hash",
|
320
|
+
args: [dir]
|
321
|
+
)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def declaration_regex(dep)
|
326
|
+
escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
|
327
|
+
/(?:^|["'])#{escaped_name}["']?\s*=.*$/i
|
328
|
+
end
|
329
|
+
|
330
|
+
def file_changed?(file)
|
331
|
+
dependencies.any? { |dep| requirement_changed?(file, dep) }
|
332
|
+
end
|
333
|
+
|
334
|
+
def requirement_changed?(file, dependency)
|
335
|
+
changed_requirements =
|
336
|
+
dependency.requirements - dependency.previous_requirements
|
337
|
+
|
338
|
+
changed_requirements.any? { |f| f[:file] == file.name }
|
339
|
+
end
|
340
|
+
|
341
|
+
def updated_file(file:, content:)
|
342
|
+
updated_file = file.dup
|
343
|
+
updated_file.content = content
|
344
|
+
updated_file
|
345
|
+
end
|
346
|
+
|
347
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
348
|
+
def normalise(name)
|
349
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
350
|
+
end
|
351
|
+
|
352
|
+
def parsed_lockfile
|
353
|
+
@parsed_lockfile ||= JSON.parse(lockfile.content)
|
354
|
+
end
|
355
|
+
|
356
|
+
def pipfile
|
357
|
+
@pipfile ||= dependency_files.find { |f| f.name == "Pipfile" }
|
358
|
+
end
|
359
|
+
|
360
|
+
def lockfile
|
361
|
+
@lockfile ||= dependency_files.find { |f| f.name == "Pipfile.lock" }
|
362
|
+
end
|
363
|
+
|
364
|
+
def setup_files
|
365
|
+
dependency_files.select { |f| f.name.end_with?("setup.py") }
|
366
|
+
end
|
367
|
+
|
368
|
+
def setup_cfg_files
|
369
|
+
dependency_files.select { |f| f.name.end_with?("setup.cfg") }
|
370
|
+
end
|
371
|
+
|
372
|
+
def requirements_files
|
373
|
+
dependency_files.select { |f| f.name.end_with?(".txt") }
|
374
|
+
end
|
375
|
+
|
376
|
+
def pipenv_environment_variables
|
377
|
+
environment_variables = [
|
378
|
+
"PIPENV_YES=true", # Install new Python versions if needed
|
379
|
+
"PIPENV_MAX_RETRIES=3", # Retry timeouts
|
380
|
+
"PIPENV_NOSPIN=1", # Don't pollute logs with spinner
|
381
|
+
"PIPENV_TIMEOUT=600", # Set install timeout to 10 minutes
|
382
|
+
"PIP_DEFAULT_TIMEOUT=60" # Set pip timeout to 1 minute
|
383
|
+
]
|
384
|
+
|
385
|
+
environment_variables.join(" ") + " "
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/python/file_parser"
|
6
|
+
require "dependabot/python/file_updater"
|
7
|
+
|
8
|
+
module Dependabot
|
9
|
+
module Python
|
10
|
+
class FileUpdater
|
11
|
+
class PipfilePreparer
|
12
|
+
def initialize(pipfile_content:)
|
13
|
+
@pipfile_content = pipfile_content
|
14
|
+
end
|
15
|
+
|
16
|
+
def replace_sources(credentials)
|
17
|
+
pipfile_object = TomlRB.parse(pipfile_content)
|
18
|
+
|
19
|
+
pipfile_object["source"] =
|
20
|
+
pipfile_sources.reject { |h| h["url"].include?("${") } +
|
21
|
+
config_variable_sources(credentials)
|
22
|
+
|
23
|
+
TomlRB.dump(pipfile_object)
|
24
|
+
end
|
25
|
+
|
26
|
+
def freeze_top_level_dependencies_except(dependencies, lockfile)
|
27
|
+
return pipfile_content unless lockfile
|
28
|
+
|
29
|
+
pipfile_object = TomlRB.parse(pipfile_content)
|
30
|
+
excluded_names = dependencies.map(&:name)
|
31
|
+
|
32
|
+
Python::FileParser::DEPENDENCY_GROUP_KEYS.each do |keys|
|
33
|
+
next unless pipfile_object[keys[:pipfile]]
|
34
|
+
|
35
|
+
pipfile_object.fetch(keys[:pipfile]).each do |dep_name, _|
|
36
|
+
next if excluded_names.include?(normalise(dep_name))
|
37
|
+
|
38
|
+
freeze_dependency(dep_name, pipfile_object, lockfile, keys)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
TomlRB.dump(pipfile_object)
|
43
|
+
end
|
44
|
+
|
45
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
46
|
+
def freeze_dependency(dep_name, pipfile_object, lockfile, keys)
|
47
|
+
locked_version = version_from_lockfile(
|
48
|
+
lockfile,
|
49
|
+
keys[:lockfile],
|
50
|
+
normalise(dep_name)
|
51
|
+
)
|
52
|
+
locked_ref = ref_from_lockfile(
|
53
|
+
lockfile,
|
54
|
+
keys[:lockfile],
|
55
|
+
normalise(dep_name)
|
56
|
+
)
|
57
|
+
|
58
|
+
pipfile_req = pipfile_object[keys[:pipfile]][dep_name]
|
59
|
+
if pipfile_req.is_a?(Hash) && locked_version
|
60
|
+
pipfile_req["version"] = "==#{locked_version}"
|
61
|
+
elsif pipfile_req.is_a?(Hash) && locked_ref && !pipfile_req["ref"]
|
62
|
+
pipfile_req["ref"] = locked_ref
|
63
|
+
elsif locked_version
|
64
|
+
pipfile_object[keys[:pipfile]][dep_name] = "==#{locked_version}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
68
|
+
|
69
|
+
def update_python_requirement(requirement)
|
70
|
+
pipfile_object = TomlRB.parse(pipfile_content)
|
71
|
+
|
72
|
+
pipfile_object["requires"] ||= {}
|
73
|
+
pipfile_object["requires"].delete("python_full_version")
|
74
|
+
pipfile_object["requires"].delete("python_version")
|
75
|
+
pipfile_object["requires"]["python_full_version"] = requirement
|
76
|
+
|
77
|
+
TomlRB.dump(pipfile_object)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
attr_reader :pipfile_content
|
83
|
+
|
84
|
+
def version_from_lockfile(lockfile, dep_type, dep_name)
|
85
|
+
details = JSON.parse(lockfile.content).
|
86
|
+
dig(dep_type, normalise(dep_name))
|
87
|
+
|
88
|
+
case details
|
89
|
+
when String then details.gsub(/^==/, "")
|
90
|
+
when Hash then details["version"]&.gsub(/^==/, "")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def ref_from_lockfile(lockfile, dep_type, dep_name)
|
95
|
+
details = JSON.parse(lockfile.content).
|
96
|
+
dig(dep_type, normalise(dep_name))
|
97
|
+
|
98
|
+
case details
|
99
|
+
when Hash then details["ref"]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
104
|
+
def normalise(name)
|
105
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
106
|
+
end
|
107
|
+
|
108
|
+
def pipfile_sources
|
109
|
+
@pipfile_sources ||=
|
110
|
+
TomlRB.parse(pipfile_content).fetch("source", []).
|
111
|
+
map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
|
112
|
+
end
|
113
|
+
|
114
|
+
def config_variable_sources(credentials)
|
115
|
+
@config_variable_sources ||=
|
116
|
+
credentials.
|
117
|
+
select { |cred| cred["type"] == "python_index" }.
|
118
|
+
map { |cred| { "url" => cred["index-url"] } }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|