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,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
require "dependabot/python/version"
|
7
|
+
require "dependabot/python/requirement"
|
8
|
+
require "dependabot/python/python_versions"
|
9
|
+
require "dependabot/python/file_updater"
|
10
|
+
require "dependabot/python/native_helpers"
|
11
|
+
|
12
|
+
module Dependabot
|
13
|
+
module Python
|
14
|
+
class FileUpdater
|
15
|
+
class PoetryFileUpdater
|
16
|
+
require_relative "pyproject_preparer"
|
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?(pyproject)
|
44
|
+
updated_files <<
|
45
|
+
updated_file(
|
46
|
+
file: pyproject,
|
47
|
+
content: updated_pyproject_content
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
if lockfile && lockfile.content == updated_lockfile_content
|
52
|
+
raise "Expected lockfile to change!"
|
53
|
+
end
|
54
|
+
|
55
|
+
if lockfile
|
56
|
+
updated_files <<
|
57
|
+
updated_file(file: lockfile, content: updated_lockfile_content)
|
58
|
+
end
|
59
|
+
|
60
|
+
updated_files
|
61
|
+
end
|
62
|
+
|
63
|
+
def updated_pyproject_content
|
64
|
+
dependencies.
|
65
|
+
select { |dep| requirement_changed?(pyproject, dep) }.
|
66
|
+
reduce(pyproject.content.dup) do |content, dep|
|
67
|
+
updated_requirement =
|
68
|
+
dep.requirements.find { |r| r[:file] == pyproject.name }.
|
69
|
+
fetch(:requirement)
|
70
|
+
|
71
|
+
old_req =
|
72
|
+
dep.previous_requirements.
|
73
|
+
find { |r| r[:file] == pyproject.name }.
|
74
|
+
fetch(:requirement)
|
75
|
+
|
76
|
+
updated_content =
|
77
|
+
content.gsub(declaration_regex(dep)) do |line|
|
78
|
+
line.gsub(old_req, updated_requirement)
|
79
|
+
end
|
80
|
+
|
81
|
+
raise "Content did not change!" if content == updated_content
|
82
|
+
|
83
|
+
updated_content
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def updated_lockfile_content
|
88
|
+
@updated_lockfile_content ||=
|
89
|
+
begin
|
90
|
+
new_lockfile = updated_lockfile_content_for(prepared_pyproject)
|
91
|
+
|
92
|
+
tmp_hash =
|
93
|
+
TomlRB.parse(new_lockfile)["metadata"]["content-hash"]
|
94
|
+
correct_hash = pyproject_hash_for(updated_pyproject_content)
|
95
|
+
|
96
|
+
new_lockfile.gsub(tmp_hash, correct_hash)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def prepared_pyproject
|
101
|
+
content = updated_pyproject_content
|
102
|
+
content = freeze_other_dependencies(content)
|
103
|
+
content = freeze_dependencies_being_updated(content)
|
104
|
+
content = add_private_sources(content)
|
105
|
+
content = sanitize(content)
|
106
|
+
content
|
107
|
+
end
|
108
|
+
|
109
|
+
def freeze_other_dependencies(pyproject_content)
|
110
|
+
PyprojectPreparer.
|
111
|
+
new(pyproject_content: pyproject_content).
|
112
|
+
freeze_top_level_dependencies_except(dependencies, lockfile)
|
113
|
+
end
|
114
|
+
|
115
|
+
def freeze_dependencies_being_updated(pyproject_content)
|
116
|
+
pyproject_object = TomlRB.parse(pyproject_content)
|
117
|
+
poetry_object = pyproject_object.fetch("tool").fetch("poetry")
|
118
|
+
|
119
|
+
dependencies.each do |dep|
|
120
|
+
%w(dependencies dev-dependencies).each do |type|
|
121
|
+
names = poetry_object[type]&.keys || []
|
122
|
+
pkg_name = names.find { |nm| normalise(nm) == dep.name }
|
123
|
+
next unless pkg_name
|
124
|
+
|
125
|
+
if poetry_object[type][pkg_name].is_a?(Hash)
|
126
|
+
poetry_object[type][pkg_name]["version"] = dep.version
|
127
|
+
else
|
128
|
+
poetry_object[type][pkg_name] = dep.version
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
TomlRB.dump(pyproject_object)
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_private_sources(pyproject_content)
|
137
|
+
PyprojectPreparer.
|
138
|
+
new(pyproject_content: pyproject_content).
|
139
|
+
replace_sources(credentials)
|
140
|
+
end
|
141
|
+
|
142
|
+
def sanitize(pyproject_content)
|
143
|
+
PyprojectPreparer.
|
144
|
+
new(pyproject_content: pyproject_content).
|
145
|
+
sanitize
|
146
|
+
end
|
147
|
+
|
148
|
+
def updated_lockfile_content_for(pyproject_content)
|
149
|
+
SharedHelpers.in_a_temporary_directory do
|
150
|
+
write_temporary_dependency_files(pyproject_content)
|
151
|
+
|
152
|
+
if python_version && !pre_installed_python?(python_version)
|
153
|
+
run_poetry_command("pyenv install -s")
|
154
|
+
run_poetry_command("pyenv exec pip install --upgrade pip")
|
155
|
+
run_poetry_command(
|
156
|
+
"pyenv exec pip install -r #{python_requirements_path}"
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
run_poetry_command("pyenv exec poetry lock")
|
161
|
+
|
162
|
+
return File.read("poetry.lock") if File.exist?("poetry.lock")
|
163
|
+
|
164
|
+
File.read("pyproject.lock")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def run_poetry_command(cmd)
|
169
|
+
raw_response = nil
|
170
|
+
IO.popen(cmd, err: %i(child out)) { |p| raw_response = p.read }
|
171
|
+
|
172
|
+
# Raise an error with the output from the shell session if Pipenv
|
173
|
+
# returns a non-zero status
|
174
|
+
return if $CHILD_STATUS.success?
|
175
|
+
|
176
|
+
raise SharedHelpers::HelperSubprocessFailed.new(raw_response, cmd)
|
177
|
+
end
|
178
|
+
|
179
|
+
def write_temporary_dependency_files(pyproject_content)
|
180
|
+
dependency_files.each do |file|
|
181
|
+
path = file.name
|
182
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
183
|
+
File.write(path, file.content)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Overwrite the .python-version with updated content
|
187
|
+
File.write(".python-version", python_version) if python_version
|
188
|
+
|
189
|
+
# Overwrite the pyproject with updated content
|
190
|
+
File.write("pyproject.toml", pyproject_content)
|
191
|
+
end
|
192
|
+
|
193
|
+
def python_version
|
194
|
+
pyproject_object = TomlRB.parse(prepared_pyproject)
|
195
|
+
poetry_object = pyproject_object.dig("tool", "poetry")
|
196
|
+
|
197
|
+
requirement =
|
198
|
+
poetry_object&.dig("dependencies", "python") ||
|
199
|
+
poetry_object&.dig("dev-dependencies", "python")
|
200
|
+
|
201
|
+
return python_version_file&.content unless requirement
|
202
|
+
|
203
|
+
requirements = Python::Requirement.requirements_array(requirement)
|
204
|
+
|
205
|
+
PythonVersions::PYTHON_VERSIONS.find do |version|
|
206
|
+
requirements.any? do |r|
|
207
|
+
r.satisfied_by?(Python::Version.new(version))
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def pre_installed_python?(version)
|
213
|
+
PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
|
214
|
+
end
|
215
|
+
|
216
|
+
def pyproject_hash_for(pyproject_content)
|
217
|
+
SharedHelpers.in_a_temporary_directory do |dir|
|
218
|
+
File.write(File.join(dir, "pyproject.toml"), pyproject_content)
|
219
|
+
SharedHelpers.run_helper_subprocess(
|
220
|
+
command: "pyenv exec python #{NativeHelpers.python_helper_path}",
|
221
|
+
function: "get_pyproject_hash",
|
222
|
+
args: [dir]
|
223
|
+
)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def declaration_regex(dep)
|
228
|
+
escaped_name = Regexp.escape(dep.name).gsub("\\-", "[-_.]")
|
229
|
+
/(?:^|["'])#{escaped_name}["']?\s*=.*$/i
|
230
|
+
end
|
231
|
+
|
232
|
+
def file_changed?(file)
|
233
|
+
dependencies.any? { |dep| requirement_changed?(file, dep) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def requirement_changed?(file, dependency)
|
237
|
+
changed_requirements =
|
238
|
+
dependency.requirements - dependency.previous_requirements
|
239
|
+
|
240
|
+
changed_requirements.any? { |f| f[:file] == file.name }
|
241
|
+
end
|
242
|
+
|
243
|
+
def updated_file(file:, content:)
|
244
|
+
updated_file = file.dup
|
245
|
+
updated_file.content = content
|
246
|
+
updated_file
|
247
|
+
end
|
248
|
+
|
249
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
250
|
+
def normalise(name)
|
251
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
252
|
+
end
|
253
|
+
|
254
|
+
def pyproject
|
255
|
+
@pyproject ||=
|
256
|
+
dependency_files.find { |f| f.name == "pyproject.toml" }
|
257
|
+
end
|
258
|
+
|
259
|
+
def lockfile
|
260
|
+
@lockfile ||= pyproject_lock || poetry_lock
|
261
|
+
end
|
262
|
+
|
263
|
+
def pyproject_lock
|
264
|
+
dependency_files.find { |f| f.name == "pyproject.lock" }
|
265
|
+
end
|
266
|
+
|
267
|
+
def poetry_lock
|
268
|
+
dependency_files.find { |f| f.name == "poetry.lock" }
|
269
|
+
end
|
270
|
+
|
271
|
+
def python_version_file
|
272
|
+
dependency_files.find { |f| f.name == ".python-version" }
|
273
|
+
end
|
274
|
+
|
275
|
+
def python_requirements_path
|
276
|
+
project_root = File.join(File.dirname(__FILE__), "../../../../..")
|
277
|
+
File.join(project_root, "helpers/python/requirements.txt")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,103 @@
|
|
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 PyprojectPreparer
|
12
|
+
def initialize(pyproject_content:)
|
13
|
+
@pyproject_content = pyproject_content
|
14
|
+
end
|
15
|
+
|
16
|
+
def replace_sources(credentials)
|
17
|
+
pyproject_object = TomlRB.parse(pyproject_content)
|
18
|
+
poetry_object = pyproject_object.fetch("tool").fetch("poetry")
|
19
|
+
|
20
|
+
poetry_object["source"] = pyproject_sources +
|
21
|
+
config_variable_sources(credentials)
|
22
|
+
|
23
|
+
TomlRB.dump(pyproject_object)
|
24
|
+
end
|
25
|
+
|
26
|
+
def sanitize
|
27
|
+
# {{ name }} syntax not allowed
|
28
|
+
pyproject_content.gsub(/\{\{.*?\}\}/, "something")
|
29
|
+
end
|
30
|
+
|
31
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
32
|
+
def freeze_top_level_dependencies_except(dependencies, lockfile)
|
33
|
+
return pyproject_content unless lockfile
|
34
|
+
|
35
|
+
pyproject_object = TomlRB.parse(pyproject_content)
|
36
|
+
poetry_object = pyproject_object["tool"]["poetry"]
|
37
|
+
excluded_names = dependencies.map(&:name) + ["python"]
|
38
|
+
|
39
|
+
%w(dependencies dev-dependencies).each do |key|
|
40
|
+
next unless poetry_object[key]
|
41
|
+
|
42
|
+
poetry_object.fetch(key).each do |dep_name, _|
|
43
|
+
next if excluded_names.include?(normalise(dep_name))
|
44
|
+
|
45
|
+
locked_details = locked_details(dep_name, lockfile)
|
46
|
+
|
47
|
+
next unless (locked_version = locked_details&.fetch("version"))
|
48
|
+
|
49
|
+
if locked_details&.dig("source", "type") == "git"
|
50
|
+
poetry_object[key][dep_name] = {
|
51
|
+
"git" => locked_details&.dig("source", "url"),
|
52
|
+
"rev" => locked_details&.dig("source", "reference")
|
53
|
+
}
|
54
|
+
elsif poetry_object[dep_name].is_a?(Hash)
|
55
|
+
poetry_object[key][dep_name]["version"] = locked_version
|
56
|
+
else
|
57
|
+
poetry_object[key][dep_name] = locked_version
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
TomlRB.dump(pyproject_object)
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :pyproject_content
|
69
|
+
|
70
|
+
def locked_details(dep_name, lockfile)
|
71
|
+
parsed_lockfile = TomlRB.parse(lockfile.content)
|
72
|
+
|
73
|
+
parsed_lockfile.fetch("package").
|
74
|
+
find { |d| d["name"] == normalise(dep_name) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
78
|
+
def normalise(name)
|
79
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
80
|
+
end
|
81
|
+
|
82
|
+
def pyproject_sources
|
83
|
+
return @pyproject_sources if @pyproject_sources
|
84
|
+
|
85
|
+
pyproject_sources ||=
|
86
|
+
TomlRB.parse(pyproject_content).
|
87
|
+
dig("tool", "poetry", "source")
|
88
|
+
|
89
|
+
@pyproject_sources ||=
|
90
|
+
(pyproject_sources || []).
|
91
|
+
map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
|
92
|
+
end
|
93
|
+
|
94
|
+
def config_variable_sources(credentials)
|
95
|
+
@config_variable_sources ||=
|
96
|
+
credentials.
|
97
|
+
select { |cred| cred["type"] == "python_index" }.
|
98
|
+
map { |cred| { "url" => cred["index-url"] } }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/python/requirement_parser"
|
4
|
+
require "dependabot/python/file_updater"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
require "dependabot/python/native_helpers"
|
7
|
+
|
8
|
+
module Dependabot
|
9
|
+
module Python
|
10
|
+
class FileUpdater
|
11
|
+
class RequirementFileUpdater
|
12
|
+
attr_reader :dependencies, :dependency_files, :credentials
|
13
|
+
|
14
|
+
def initialize(dependencies:, dependency_files:, credentials:)
|
15
|
+
@dependencies = dependencies
|
16
|
+
@dependency_files = dependency_files
|
17
|
+
@credentials = credentials
|
18
|
+
end
|
19
|
+
|
20
|
+
def updated_dependency_files
|
21
|
+
return @updated_dependency_files if @update_already_attempted
|
22
|
+
|
23
|
+
@update_already_attempted = true
|
24
|
+
@updated_dependency_files ||= fetch_updated_dependency_files
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def dependency
|
30
|
+
# For now, we'll only ever be updating a single dependency
|
31
|
+
dependencies.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_updated_dependency_files
|
35
|
+
reqs = dependency.requirements.zip(dependency.previous_requirements)
|
36
|
+
|
37
|
+
reqs.map do |(new_req, old_req)|
|
38
|
+
next if new_req == old_req
|
39
|
+
|
40
|
+
file = get_original_file(new_req.fetch(:file)).dup
|
41
|
+
updated_content =
|
42
|
+
updated_requirement_or_setup_file_content(new_req, old_req)
|
43
|
+
next if updated_content == file.content
|
44
|
+
|
45
|
+
file.content = updated_content
|
46
|
+
file
|
47
|
+
end.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def updated_requirement_or_setup_file_content(new_req, old_req)
|
51
|
+
content = get_original_file(new_req.fetch(:file)).content
|
52
|
+
|
53
|
+
updated_content =
|
54
|
+
content.gsub(
|
55
|
+
original_declaration_replacement_regex(old_req),
|
56
|
+
updated_dependency_declaration_string(new_req, old_req)
|
57
|
+
)
|
58
|
+
|
59
|
+
raise "Expected content to change!" if content == updated_content
|
60
|
+
|
61
|
+
updated_content
|
62
|
+
end
|
63
|
+
|
64
|
+
def original_dependency_declaration_string(requirement)
|
65
|
+
regex = RequirementParser::INSTALL_REQ_WITH_REQUIREMENT
|
66
|
+
matches = []
|
67
|
+
|
68
|
+
get_original_file(requirement.fetch(:file)).
|
69
|
+
content.scan(regex) { matches << Regexp.last_match }
|
70
|
+
dec = matches.
|
71
|
+
select { |m| normalise(m[:name]) == dependency.name }.
|
72
|
+
find do |m|
|
73
|
+
# The FileParser can mess up a requirement's spacing so we
|
74
|
+
# sanitize both requirements before comparing
|
75
|
+
f_req = m[:requirements]&.gsub(/\s/, "")&.split(",")&.sort
|
76
|
+
p_req = requirement.fetch(:requirement)&.
|
77
|
+
gsub(/\s/, "")&.split(",")&.sort
|
78
|
+
f_req == p_req
|
79
|
+
end
|
80
|
+
|
81
|
+
raise "Declaration not found for #{dependency.name}!" unless dec
|
82
|
+
|
83
|
+
dec.to_s.strip
|
84
|
+
end
|
85
|
+
|
86
|
+
def updated_dependency_declaration_string(new_req, old_req)
|
87
|
+
updated_string =
|
88
|
+
original_dependency_declaration_string(old_req).sub(
|
89
|
+
RequirementParser::REQUIREMENTS,
|
90
|
+
new_req.fetch(:requirement)
|
91
|
+
)
|
92
|
+
return updated_string unless requirement_includes_hashes?(old_req)
|
93
|
+
|
94
|
+
updated_string.sub(
|
95
|
+
RequirementParser::HASHES,
|
96
|
+
package_hashes_for(
|
97
|
+
name: dependency.name,
|
98
|
+
version: dependency.version,
|
99
|
+
algorithm: hash_algorithm(old_req)
|
100
|
+
).join(hash_separator(old_req))
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def original_declaration_replacement_regex(requirement)
|
105
|
+
original_string =
|
106
|
+
original_dependency_declaration_string(requirement)
|
107
|
+
/(?<![\-\w])#{Regexp.escape(original_string)}(?![\-\w])/
|
108
|
+
end
|
109
|
+
|
110
|
+
def requirement_includes_hashes?(requirement)
|
111
|
+
original_dependency_declaration_string(requirement).
|
112
|
+
match?(RequirementParser::HASHES)
|
113
|
+
end
|
114
|
+
|
115
|
+
def hash_algorithm(requirement)
|
116
|
+
return unless requirement_includes_hashes?(requirement)
|
117
|
+
|
118
|
+
original_dependency_declaration_string(requirement).
|
119
|
+
match(RequirementParser::HASHES).
|
120
|
+
named_captures.fetch("algorithm")
|
121
|
+
end
|
122
|
+
|
123
|
+
def hash_separator(requirement)
|
124
|
+
return unless requirement_includes_hashes?(requirement)
|
125
|
+
|
126
|
+
hash_regex = RequirementParser::HASH
|
127
|
+
current_separator =
|
128
|
+
original_dependency_declaration_string(requirement).
|
129
|
+
match(/#{hash_regex}((?<separator>\s*\\?\s*?)#{hash_regex})*/).
|
130
|
+
named_captures.fetch("separator")
|
131
|
+
|
132
|
+
default_separator =
|
133
|
+
original_dependency_declaration_string(requirement).
|
134
|
+
match(RequirementParser::HASH).
|
135
|
+
pre_match.match(/(?<separator>\s*\\?\s*?)\z/).
|
136
|
+
named_captures.fetch("separator")
|
137
|
+
|
138
|
+
current_separator || default_separator
|
139
|
+
end
|
140
|
+
|
141
|
+
def package_hashes_for(name:, version:, algorithm:)
|
142
|
+
SharedHelpers.run_helper_subprocess(
|
143
|
+
command: "pyenv exec python #{NativeHelpers.python_helper_path}",
|
144
|
+
function: "get_dependency_hash",
|
145
|
+
args: [name, version, algorithm]
|
146
|
+
).map { |h| "--hash=#{algorithm}:#{h['hash']}" }
|
147
|
+
end
|
148
|
+
|
149
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
150
|
+
def normalise(name)
|
151
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_original_file(filename)
|
155
|
+
dependency_files.find { |f| f.name == filename }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|