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,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/file_parsers"
|
7
|
+
require "dependabot/file_parsers/base"
|
8
|
+
require "dependabot/file_parsers/base/dependency_set"
|
9
|
+
require "dependabot/shared_helpers"
|
10
|
+
require "dependabot/python/requirement"
|
11
|
+
require "dependabot/errors"
|
12
|
+
require "dependabot/python/native_helpers"
|
13
|
+
|
14
|
+
module Dependabot
|
15
|
+
module Python
|
16
|
+
class FileParser < Dependabot::FileParsers::Base
|
17
|
+
require_relative "file_parser/pipfile_files_parser"
|
18
|
+
require_relative "file_parser/poetry_files_parser"
|
19
|
+
require_relative "file_parser/setup_file_parser"
|
20
|
+
|
21
|
+
POETRY_DEPENDENCY_TYPES =
|
22
|
+
%w(tool.poetry.dependencies tool.poetry.dev-dependencies).freeze
|
23
|
+
DEPENDENCY_GROUP_KEYS = [
|
24
|
+
{
|
25
|
+
pipfile: "packages",
|
26
|
+
lockfile: "default"
|
27
|
+
},
|
28
|
+
{
|
29
|
+
pipfile: "dev-packages",
|
30
|
+
lockfile: "develop"
|
31
|
+
}
|
32
|
+
].freeze
|
33
|
+
REQUIREMENT_FILE_EVALUATION_ERRORS = %w(
|
34
|
+
InstallationError RequirementsFileParseError InvalidMarker
|
35
|
+
InvalidRequirement
|
36
|
+
).freeze
|
37
|
+
|
38
|
+
def parse
|
39
|
+
dependency_set = DependencySet.new
|
40
|
+
|
41
|
+
dependency_set += pipenv_dependencies if pipfile
|
42
|
+
dependency_set += poetry_dependencies if using_poetry?
|
43
|
+
dependency_set += requirement_dependencies if requirement_files.any?
|
44
|
+
dependency_set += setup_file_dependencies if setup_file
|
45
|
+
|
46
|
+
dependency_set.dependencies
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def requirement_files
|
52
|
+
dependency_files.select { |f| f.name.end_with?(".txt", ".in") }
|
53
|
+
end
|
54
|
+
|
55
|
+
def pipenv_dependencies
|
56
|
+
@pipenv_dependencies ||=
|
57
|
+
PipfileFilesParser.
|
58
|
+
new(dependency_files: dependency_files).
|
59
|
+
dependency_set
|
60
|
+
end
|
61
|
+
|
62
|
+
def poetry_dependencies
|
63
|
+
@poetry_dependencies ||=
|
64
|
+
PoetryFilesParser.
|
65
|
+
new(dependency_files: dependency_files).
|
66
|
+
dependency_set
|
67
|
+
end
|
68
|
+
|
69
|
+
def requirement_dependencies
|
70
|
+
dependencies = DependencySet.new
|
71
|
+
parsed_requirement_files.each do |dep|
|
72
|
+
# This isn't ideal, but currently the FileUpdater won't update
|
73
|
+
# deps that appear in a requirements.txt and Pipfile / Pipfile.lock
|
74
|
+
# and *aren't* a straight lockfile for the Pipfile
|
75
|
+
next if included_in_pipenv_deps?(normalised_name(dep["name"]))
|
76
|
+
|
77
|
+
# If a requirement has a `<` or `<=` marker then updating it is
|
78
|
+
# probably blocked. Ignore it.
|
79
|
+
next if dep["markers"].include?("<")
|
80
|
+
|
81
|
+
requirements =
|
82
|
+
if lockfile_for_pip_compile_file?(dep["file"]) then []
|
83
|
+
else
|
84
|
+
[{
|
85
|
+
requirement: dep["requirement"],
|
86
|
+
file: Pathname.new(dep["file"]).cleanpath.to_path,
|
87
|
+
source: nil,
|
88
|
+
groups: []
|
89
|
+
}]
|
90
|
+
end
|
91
|
+
|
92
|
+
dependencies <<
|
93
|
+
Dependency.new(
|
94
|
+
name: normalised_name(dep["name"]),
|
95
|
+
version: dep["version"]&.include?("*") ? nil : dep["version"],
|
96
|
+
requirements: requirements,
|
97
|
+
package_manager: "pip"
|
98
|
+
)
|
99
|
+
end
|
100
|
+
dependencies
|
101
|
+
end
|
102
|
+
|
103
|
+
def included_in_pipenv_deps?(dep_name)
|
104
|
+
return false unless pipfile
|
105
|
+
|
106
|
+
pipenv_dependencies.dependencies.map(&:name).include?(dep_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def setup_file_dependencies
|
110
|
+
@setup_file_dependencies ||=
|
111
|
+
SetupFileParser.
|
112
|
+
new(dependency_files: dependency_files).
|
113
|
+
dependency_set
|
114
|
+
end
|
115
|
+
|
116
|
+
def lockfile_for_pip_compile_file?(filename)
|
117
|
+
return false unless pip_compile_files.any?
|
118
|
+
return false unless filename.end_with?(".txt")
|
119
|
+
|
120
|
+
basename = filename.gsub(/\.txt$/, "")
|
121
|
+
pip_compile_files.any? { |f| f.name == basename + ".in" }
|
122
|
+
end
|
123
|
+
|
124
|
+
def parsed_requirement_files
|
125
|
+
SharedHelpers.in_a_temporary_directory do
|
126
|
+
write_temporary_dependency_files
|
127
|
+
|
128
|
+
requirements = SharedHelpers.run_helper_subprocess(
|
129
|
+
command: "pyenv exec python #{NativeHelpers.python_helper_path}",
|
130
|
+
function: "parse_requirements",
|
131
|
+
args: [Dir.pwd]
|
132
|
+
)
|
133
|
+
|
134
|
+
check_requirements(requirements)
|
135
|
+
requirements
|
136
|
+
end
|
137
|
+
rescue SharedHelpers::HelperSubprocessFailed => error
|
138
|
+
evaluation_errors = REQUIREMENT_FILE_EVALUATION_ERRORS
|
139
|
+
raise unless error.message.start_with?(*evaluation_errors)
|
140
|
+
|
141
|
+
raise Dependabot::DependencyFileNotEvaluatable, error.message
|
142
|
+
end
|
143
|
+
|
144
|
+
def check_requirements(requirements)
|
145
|
+
requirements.each do |dep|
|
146
|
+
next unless dep["requirement"]
|
147
|
+
|
148
|
+
Python::Requirement.new(dep["requirement"].split(","))
|
149
|
+
rescue Gem::Requirement::BadRequirementError => error
|
150
|
+
raise Dependabot::DependencyFileNotEvaluatable, error.message
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def write_temporary_dependency_files
|
155
|
+
dependency_files.
|
156
|
+
reject { |f| f.name == ".python-version" }.
|
157
|
+
each do |file|
|
158
|
+
path = file.name
|
159
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
160
|
+
File.write(path, file.content)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
165
|
+
def normalised_name(name)
|
166
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_required_files
|
170
|
+
filenames = dependency_files.map(&:name)
|
171
|
+
return if filenames.any? { |name| name.end_with?(".txt", ".in") }
|
172
|
+
return if pipfile
|
173
|
+
return if pyproject
|
174
|
+
return if setup_file
|
175
|
+
|
176
|
+
raise "No requirements.txt or setup.py!"
|
177
|
+
end
|
178
|
+
|
179
|
+
def pipfile
|
180
|
+
@pipfile ||= get_original_file("Pipfile")
|
181
|
+
end
|
182
|
+
|
183
|
+
def pipfile_lock
|
184
|
+
@pipfile_lock ||= get_original_file("Pipfile.lock")
|
185
|
+
end
|
186
|
+
|
187
|
+
def using_poetry?
|
188
|
+
return false unless pyproject
|
189
|
+
return true if poetry_lock || pyproject_lock
|
190
|
+
|
191
|
+
!TomlRB.parse(pyproject.content).dig("tool", "poetry").nil?
|
192
|
+
rescue TomlRB::ParseError
|
193
|
+
raise Dependabot::DependencyFileNotParseable, pyproject.path
|
194
|
+
end
|
195
|
+
|
196
|
+
def pyproject
|
197
|
+
@pyproject ||= get_original_file("pyproject.toml")
|
198
|
+
end
|
199
|
+
|
200
|
+
def pyproject_lock
|
201
|
+
@pyproject_lock ||= get_original_file("pyproject.lock")
|
202
|
+
end
|
203
|
+
|
204
|
+
def poetry_lock
|
205
|
+
@poetry_lock ||= get_original_file("poetry.lock")
|
206
|
+
end
|
207
|
+
|
208
|
+
def setup_file
|
209
|
+
@setup_file ||= get_original_file("setup.py")
|
210
|
+
end
|
211
|
+
|
212
|
+
def pip_compile_files
|
213
|
+
@pip_compile_files ||=
|
214
|
+
dependency_files.select { |f| f.name.end_with?(".in") }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
Dependabot::FileParsers.
|
221
|
+
register("pip", Dependabot::Python::FileParser)
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/file_parsers/base/dependency_set"
|
7
|
+
require "dependabot/python/file_parser"
|
8
|
+
require "dependabot/errors"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Python
|
12
|
+
class FileParser
|
13
|
+
class PipfileFilesParser
|
14
|
+
DEPENDENCY_GROUP_KEYS = [
|
15
|
+
{
|
16
|
+
pipfile: "packages",
|
17
|
+
lockfile: "default"
|
18
|
+
},
|
19
|
+
{
|
20
|
+
pipfile: "dev-packages",
|
21
|
+
lockfile: "develop"
|
22
|
+
}
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
def initialize(dependency_files:)
|
26
|
+
@dependency_files = dependency_files
|
27
|
+
end
|
28
|
+
|
29
|
+
def dependency_set
|
30
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
31
|
+
|
32
|
+
dependency_set += pipfile_dependencies
|
33
|
+
dependency_set += pipfile_lock_dependencies
|
34
|
+
|
35
|
+
dependency_set
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :dependency_files
|
41
|
+
|
42
|
+
def pipfile_dependencies
|
43
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
44
|
+
|
45
|
+
DEPENDENCY_GROUP_KEYS.each do |keys|
|
46
|
+
next unless parsed_pipfile[keys[:pipfile]]
|
47
|
+
|
48
|
+
parsed_pipfile[keys[:pipfile]].map do |dep_name, req|
|
49
|
+
group = keys[:lockfile]
|
50
|
+
next unless req.is_a?(String) || req["version"]
|
51
|
+
next if pipfile_lock && !dependency_version(dep_name, req, group)
|
52
|
+
|
53
|
+
dependencies <<
|
54
|
+
Dependency.new(
|
55
|
+
name: normalised_name(dep_name),
|
56
|
+
version: dependency_version(dep_name, req, group),
|
57
|
+
requirements: [{
|
58
|
+
requirement: req.is_a?(String) ? req : req["version"],
|
59
|
+
file: pipfile.name,
|
60
|
+
source: nil,
|
61
|
+
groups: [group]
|
62
|
+
}],
|
63
|
+
package_manager: "pip"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
dependencies
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create a DependencySet where each element has no requirement. Any
|
72
|
+
# requirements will be added when combining the DependencySet with
|
73
|
+
# other DependencySets.
|
74
|
+
def pipfile_lock_dependencies
|
75
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
76
|
+
return dependencies unless pipfile_lock
|
77
|
+
|
78
|
+
DEPENDENCY_GROUP_KEYS.map { |h| h.fetch(:lockfile) }.each do |key|
|
79
|
+
next unless parsed_pipfile_lock[key]
|
80
|
+
|
81
|
+
parsed_pipfile_lock[key].each do |dep_name, details|
|
82
|
+
version = case details
|
83
|
+
when String then details
|
84
|
+
when Hash then details["version"]
|
85
|
+
end
|
86
|
+
next unless version
|
87
|
+
|
88
|
+
dependencies <<
|
89
|
+
Dependency.new(
|
90
|
+
name: dep_name,
|
91
|
+
version: version&.gsub(/^===?/, ""),
|
92
|
+
requirements: [],
|
93
|
+
package_manager: "pip"
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
dependencies
|
99
|
+
end
|
100
|
+
|
101
|
+
def dependency_version(dep_name, requirement, group)
|
102
|
+
req = version_from_hash_or_string(requirement)
|
103
|
+
|
104
|
+
if pipfile_lock
|
105
|
+
details = parsed_pipfile_lock.
|
106
|
+
dig(group, normalised_name(dep_name))
|
107
|
+
|
108
|
+
version = version_from_hash_or_string(details)
|
109
|
+
version&.gsub(/^===?/, "")
|
110
|
+
elsif req.start_with?("==") && !req.include?("*")
|
111
|
+
req.strip.gsub(/^===?/, "")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def version_from_hash_or_string(obj)
|
116
|
+
case obj
|
117
|
+
when String then obj.strip
|
118
|
+
when Hash then obj["version"]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
123
|
+
def normalised_name(name)
|
124
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
125
|
+
end
|
126
|
+
|
127
|
+
def parsed_pipfile
|
128
|
+
@parsed_pipfile ||= TomlRB.parse(pipfile.content)
|
129
|
+
rescue TomlRB::ParseError
|
130
|
+
raise Dependabot::DependencyFileNotParseable, pipfile.path
|
131
|
+
end
|
132
|
+
|
133
|
+
def parsed_pipfile_lock
|
134
|
+
@parsed_pipfile_lock ||= JSON.parse(pipfile_lock.content)
|
135
|
+
rescue JSON::ParserError
|
136
|
+
raise Dependabot::DependencyFileNotParseable, pipfile_lock.path
|
137
|
+
end
|
138
|
+
|
139
|
+
def pipfile
|
140
|
+
@pipfile ||= dependency_files.find { |f| f.name == "Pipfile" }
|
141
|
+
end
|
142
|
+
|
143
|
+
def pipfile_lock
|
144
|
+
@pipfile_lock ||=
|
145
|
+
dependency_files.find { |f| f.name == "Pipfile.lock" }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "toml-rb"
|
4
|
+
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/file_parsers/base/dependency_set"
|
7
|
+
require "dependabot/python/file_parser"
|
8
|
+
require "dependabot/errors"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Python
|
12
|
+
class FileParser
|
13
|
+
class PoetryFilesParser
|
14
|
+
POETRY_DEPENDENCY_TYPES = %w(dependencies dev-dependencies).freeze
|
15
|
+
|
16
|
+
def initialize(dependency_files:)
|
17
|
+
@dependency_files = dependency_files
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependency_set
|
21
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
22
|
+
|
23
|
+
dependency_set += pyproject_dependencies
|
24
|
+
dependency_set += lockfile_dependencies if lockfile
|
25
|
+
|
26
|
+
dependency_set
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :dependency_files
|
32
|
+
|
33
|
+
def pyproject_dependencies
|
34
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
35
|
+
|
36
|
+
POETRY_DEPENDENCY_TYPES.each do |type|
|
37
|
+
deps_hash = parsed_pyproject.dig("tool", "poetry", type) || {}
|
38
|
+
|
39
|
+
deps_hash.each do |name, req|
|
40
|
+
next if normalised_name(name) == "python"
|
41
|
+
next if req.is_a?(Hash) && req.key?("git")
|
42
|
+
|
43
|
+
dependencies <<
|
44
|
+
Dependency.new(
|
45
|
+
name: normalised_name(name),
|
46
|
+
version: version_from_lockfile(name),
|
47
|
+
requirements: [{
|
48
|
+
requirement: req.is_a?(String) ? req : req["version"],
|
49
|
+
file: pyproject.name,
|
50
|
+
source: nil,
|
51
|
+
groups: [type]
|
52
|
+
}],
|
53
|
+
package_manager: "pip"
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
dependencies
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a DependencySet where each element has no requirement. Any
|
62
|
+
# requirements will be added when combining the DependencySet with
|
63
|
+
# other DependencySets.
|
64
|
+
def lockfile_dependencies
|
65
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
66
|
+
|
67
|
+
parsed_lockfile.fetch("package", []).each do |details|
|
68
|
+
next if details.dig("source", "type") == "git"
|
69
|
+
|
70
|
+
dependencies <<
|
71
|
+
Dependency.new(
|
72
|
+
name: details.fetch("name"),
|
73
|
+
version: details.fetch("version"),
|
74
|
+
requirements: [],
|
75
|
+
package_manager: "pip"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
dependencies
|
80
|
+
end
|
81
|
+
|
82
|
+
def version_from_lockfile(dep_name)
|
83
|
+
return unless parsed_lockfile
|
84
|
+
|
85
|
+
parsed_lockfile.fetch("package", []).
|
86
|
+
find { |p| p.fetch("name") == normalised_name(dep_name) }&.
|
87
|
+
fetch("verison", nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
# See https://www.python.org/dev/peps/pep-0503/#normalized-names
|
91
|
+
def normalised_name(name)
|
92
|
+
name.downcase.gsub(/[-_.]+/, "-")
|
93
|
+
end
|
94
|
+
|
95
|
+
def parsed_pyproject
|
96
|
+
@parsed_pyproject ||= TomlRB.parse(pyproject.content)
|
97
|
+
rescue TomlRB::ParseError
|
98
|
+
raise Dependabot::DependencyFileNotParseable, pyproject.path
|
99
|
+
end
|
100
|
+
|
101
|
+
def parsed_pyproject_lock
|
102
|
+
@parsed_pyproject_lock ||= TomlRB.parse(pyproject_lock.content)
|
103
|
+
rescue TomlRB::ParseError
|
104
|
+
raise Dependabot::DependencyFileNotParseable, pyproject_lock.path
|
105
|
+
end
|
106
|
+
|
107
|
+
def parsed_poetry_lock
|
108
|
+
@parsed_poetry_lock ||= TomlRB.parse(poetry_lock.content)
|
109
|
+
rescue TomlRB::ParseError
|
110
|
+
raise Dependabot::DependencyFileNotParseable, poetry_lock.path
|
111
|
+
end
|
112
|
+
|
113
|
+
def pyproject
|
114
|
+
@pyproject ||=
|
115
|
+
dependency_files.find { |f| f.name == "pyproject.toml" }
|
116
|
+
end
|
117
|
+
|
118
|
+
def lockfile
|
119
|
+
poetry_lock || pyproject_lock
|
120
|
+
end
|
121
|
+
|
122
|
+
def parsed_lockfile
|
123
|
+
return parsed_poetry_lock if poetry_lock
|
124
|
+
return parsed_pyproject_lock if pyproject_lock
|
125
|
+
end
|
126
|
+
|
127
|
+
def pyproject_lock
|
128
|
+
@pyproject_lock ||=
|
129
|
+
dependency_files.find { |f| f.name == "pyproject.lock" }
|
130
|
+
end
|
131
|
+
|
132
|
+
def poetry_lock
|
133
|
+
@poetry_lock ||=
|
134
|
+
dependency_files.find { |f| f.name == "poetry.lock" }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|