dependabot-uv 0.299.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 +7 -0
- data/helpers/build +34 -0
- data/helpers/lib/__init__.py +0 -0
- data/helpers/lib/hasher.py +36 -0
- data/helpers/lib/parser.py +270 -0
- data/helpers/requirements.txt +13 -0
- data/helpers/run.py +22 -0
- data/lib/dependabot/uv/authed_url_builder.rb +31 -0
- data/lib/dependabot/uv/file_fetcher.rb +328 -0
- data/lib/dependabot/uv/file_parser/pipfile_files_parser.rb +192 -0
- data/lib/dependabot/uv/file_parser/pyproject_files_parser.rb +345 -0
- data/lib/dependabot/uv/file_parser/python_requirement_parser.rb +185 -0
- data/lib/dependabot/uv/file_parser/setup_file_parser.rb +193 -0
- data/lib/dependabot/uv/file_parser.rb +437 -0
- data/lib/dependabot/uv/file_updater/compile_file_updater.rb +576 -0
- data/lib/dependabot/uv/file_updater/pyproject_preparer.rb +124 -0
- data/lib/dependabot/uv/file_updater/requirement_file_updater.rb +73 -0
- data/lib/dependabot/uv/file_updater/requirement_replacer.rb +214 -0
- data/lib/dependabot/uv/file_updater.rb +105 -0
- data/lib/dependabot/uv/language.rb +76 -0
- data/lib/dependabot/uv/language_version_manager.rb +114 -0
- data/lib/dependabot/uv/metadata_finder.rb +186 -0
- data/lib/dependabot/uv/name_normaliser.rb +26 -0
- data/lib/dependabot/uv/native_helpers.rb +38 -0
- data/lib/dependabot/uv/package_manager.rb +54 -0
- data/lib/dependabot/uv/pip_compile_file_matcher.rb +38 -0
- data/lib/dependabot/uv/pipenv_runner.rb +108 -0
- data/lib/dependabot/uv/requirement.rb +163 -0
- data/lib/dependabot/uv/requirement_parser.rb +60 -0
- data/lib/dependabot/uv/update_checker/index_finder.rb +227 -0
- data/lib/dependabot/uv/update_checker/latest_version_finder.rb +297 -0
- data/lib/dependabot/uv/update_checker/pip_compile_version_resolver.rb +506 -0
- data/lib/dependabot/uv/update_checker/pip_version_resolver.rb +73 -0
- data/lib/dependabot/uv/update_checker/requirements_updater.rb +391 -0
- data/lib/dependabot/uv/update_checker.rb +317 -0
- data/lib/dependabot/uv/version.rb +321 -0
- data/lib/dependabot/uv.rb +35 -0
- metadata +306 -0
@@ -0,0 +1,345 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "toml-rb"
|
5
|
+
|
6
|
+
require "dependabot/dependency"
|
7
|
+
require "dependabot/file_parsers/base/dependency_set"
|
8
|
+
require "dependabot/uv/file_parser"
|
9
|
+
require "dependabot/uv/requirement"
|
10
|
+
require "dependabot/errors"
|
11
|
+
require "dependabot/uv/name_normaliser"
|
12
|
+
|
13
|
+
module Dependabot
|
14
|
+
module Uv
|
15
|
+
class FileParser
|
16
|
+
class PyprojectFilesParser
|
17
|
+
extend T::Sig
|
18
|
+
POETRY_DEPENDENCY_TYPES = %w(dependencies dev-dependencies).freeze
|
19
|
+
|
20
|
+
# https://python-poetry.org/docs/dependency-specification/
|
21
|
+
UNSUPPORTED_DEPENDENCY_TYPES = %w(git path url).freeze
|
22
|
+
|
23
|
+
sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
|
24
|
+
def initialize(dependency_files:)
|
25
|
+
@dependency_files = dependency_files
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
29
|
+
def dependency_set
|
30
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
31
|
+
|
32
|
+
dependency_set += pyproject_dependencies if using_poetry? || using_pep621?
|
33
|
+
dependency_set += lockfile_dependencies if using_poetry? && lockfile
|
34
|
+
|
35
|
+
dependency_set
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
41
|
+
attr_reader :dependency_files
|
42
|
+
|
43
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
44
|
+
def pyproject_dependencies
|
45
|
+
if using_poetry?
|
46
|
+
missing_keys = missing_poetry_keys
|
47
|
+
|
48
|
+
if missing_keys.any?
|
49
|
+
raise DependencyFileNotParseable.new(
|
50
|
+
T.must(pyproject).path,
|
51
|
+
"#{T.must(pyproject).path} is missing the following sections:\n" \
|
52
|
+
" * #{missing_keys.map { |key| "tool.poetry.#{key}" }.join("\n * ")}\n"
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
poetry_dependencies
|
57
|
+
else
|
58
|
+
pep621_dependencies
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
63
|
+
def poetry_dependencies
|
64
|
+
@poetry_dependencies ||= T.let(parse_poetry_dependencies, T.untyped)
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
68
|
+
def parse_poetry_dependencies
|
69
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
70
|
+
|
71
|
+
POETRY_DEPENDENCY_TYPES.each do |type|
|
72
|
+
deps_hash = T.must(poetry_root)[type] || {}
|
73
|
+
dependencies += parse_poetry_dependency_group(type, deps_hash)
|
74
|
+
end
|
75
|
+
|
76
|
+
groups = T.must(poetry_root)["group"] || {}
|
77
|
+
groups.each do |group, group_spec|
|
78
|
+
dependencies += parse_poetry_dependency_group(group, group_spec["dependencies"])
|
79
|
+
end
|
80
|
+
dependencies
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
84
|
+
def pep621_dependencies
|
85
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
86
|
+
|
87
|
+
# PDM is not yet supported, so we want to ignore it for now because in
|
88
|
+
# the current state of things, going on would result in updating
|
89
|
+
# pyproject.toml but leaving pdm.lock out of sync, which is
|
90
|
+
# undesirable. Leave PDM alone until properly supported
|
91
|
+
return dependencies if using_pdm?
|
92
|
+
|
93
|
+
parsed_pep621_dependencies.each do |dep|
|
94
|
+
# If a requirement has a `<` or `<=` marker then updating it is
|
95
|
+
# probably blocked. Ignore it.
|
96
|
+
next if dep["markers"].include?("<")
|
97
|
+
|
98
|
+
# If no requirement, don't add it
|
99
|
+
next if dep["requirement"].empty?
|
100
|
+
|
101
|
+
dependencies <<
|
102
|
+
Dependency.new(
|
103
|
+
name: normalised_name(dep["name"], dep["extras"]),
|
104
|
+
version: dep["version"]&.include?("*") ? nil : dep["version"],
|
105
|
+
requirements: [{
|
106
|
+
requirement: dep["requirement"],
|
107
|
+
file: Pathname.new(dep["file"]).cleanpath.to_path,
|
108
|
+
source: nil,
|
109
|
+
groups: [dep["requirement_type"]].compact
|
110
|
+
}],
|
111
|
+
package_manager: "uv"
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
dependencies
|
116
|
+
end
|
117
|
+
|
118
|
+
sig do
|
119
|
+
params(type: String,
|
120
|
+
deps_hash: T::Hash[String,
|
121
|
+
T.untyped]).returns(Dependabot::FileParsers::Base::DependencySet)
|
122
|
+
end
|
123
|
+
def parse_poetry_dependency_group(type, deps_hash)
|
124
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
125
|
+
|
126
|
+
deps_hash.each do |name, req|
|
127
|
+
next if normalise(name) == "python"
|
128
|
+
|
129
|
+
requirements = parse_requirements_from(req, type)
|
130
|
+
next if requirements.empty?
|
131
|
+
|
132
|
+
dependencies << Dependency.new(
|
133
|
+
name: normalise(name),
|
134
|
+
version: version_from_lockfile(name),
|
135
|
+
requirements: requirements,
|
136
|
+
package_manager: "uv"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
dependencies
|
140
|
+
end
|
141
|
+
|
142
|
+
sig { params(name: String, extras: T::Array[String]).returns(String) }
|
143
|
+
def normalised_name(name, extras)
|
144
|
+
NameNormaliser.normalise_including_extras(name, extras)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param req can be an Array, Hash or String that represents the constraints for a dependency
|
148
|
+
sig { params(req: T.untyped, type: String).returns(T::Array[T::Hash[Symbol, T.nilable(String)]]) }
|
149
|
+
def parse_requirements_from(req, type)
|
150
|
+
[req].flatten.compact.filter_map do |requirement|
|
151
|
+
next if requirement.is_a?(Hash) && UNSUPPORTED_DEPENDENCY_TYPES.intersect?(requirement.keys)
|
152
|
+
|
153
|
+
check_requirements(requirement)
|
154
|
+
|
155
|
+
if requirement.is_a?(String)
|
156
|
+
{
|
157
|
+
requirement: requirement,
|
158
|
+
file: T.must(pyproject).name,
|
159
|
+
source: nil,
|
160
|
+
groups: [type]
|
161
|
+
}
|
162
|
+
else
|
163
|
+
{
|
164
|
+
requirement: requirement["version"],
|
165
|
+
file: T.must(pyproject).name,
|
166
|
+
source: requirement.fetch("source", nil),
|
167
|
+
groups: [type]
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { returns(T.nilable(T::Boolean)) }
|
174
|
+
def using_poetry?
|
175
|
+
!poetry_root.nil?
|
176
|
+
end
|
177
|
+
|
178
|
+
sig { returns(T::Array[String]) }
|
179
|
+
def missing_poetry_keys
|
180
|
+
package_mode = T.must(poetry_root).fetch("package-mode", true)
|
181
|
+
required_keys = package_mode ? %w(name version description authors) : []
|
182
|
+
required_keys.reject { |key| T.must(poetry_root).key?(key) }
|
183
|
+
end
|
184
|
+
|
185
|
+
sig { returns(T::Boolean) }
|
186
|
+
def using_pep621?
|
187
|
+
!parsed_pyproject.dig("project", "dependencies").nil? ||
|
188
|
+
!parsed_pyproject.dig("project", "optional-dependencies").nil? ||
|
189
|
+
!parsed_pyproject.dig("build-system", "requires").nil?
|
190
|
+
end
|
191
|
+
|
192
|
+
sig { returns(T.nilable(T::Hash[String, T.untyped])) }
|
193
|
+
def poetry_root
|
194
|
+
parsed_pyproject.dig("tool", "poetry")
|
195
|
+
end
|
196
|
+
|
197
|
+
sig { returns(T.untyped) }
|
198
|
+
def using_pdm?
|
199
|
+
using_pep621? && pdm_lock
|
200
|
+
end
|
201
|
+
|
202
|
+
# Create a DependencySet where each element has no requirement. Any
|
203
|
+
# requirements will be added when combining the DependencySet with
|
204
|
+
# other DependencySets.
|
205
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
206
|
+
def lockfile_dependencies
|
207
|
+
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
208
|
+
|
209
|
+
source_types = %w(directory git url)
|
210
|
+
parsed_lockfile.fetch("package", []).each do |details|
|
211
|
+
next if source_types.include?(details.dig("source", "type"))
|
212
|
+
|
213
|
+
name = normalise(details.fetch("name"))
|
214
|
+
|
215
|
+
dependencies <<
|
216
|
+
Dependency.new(
|
217
|
+
name: name,
|
218
|
+
version: details.fetch("version"),
|
219
|
+
requirements: [],
|
220
|
+
package_manager: "uv",
|
221
|
+
subdependency_metadata: [{
|
222
|
+
production: production_dependency_names.include?(name)
|
223
|
+
}]
|
224
|
+
)
|
225
|
+
end
|
226
|
+
|
227
|
+
dependencies
|
228
|
+
end
|
229
|
+
|
230
|
+
sig { returns(T::Array[T.nilable(String)]) }
|
231
|
+
def production_dependency_names
|
232
|
+
@production_dependency_names ||= T.let(parse_production_dependency_names,
|
233
|
+
T.nilable(T::Array[T.nilable(String)]))
|
234
|
+
end
|
235
|
+
|
236
|
+
sig { returns(T::Array[T.nilable(String)]) }
|
237
|
+
def parse_production_dependency_names
|
238
|
+
SharedHelpers.in_a_temporary_directory do
|
239
|
+
File.write(T.must(pyproject).name, T.must(pyproject).content)
|
240
|
+
File.write(lockfile.name, lockfile.content)
|
241
|
+
|
242
|
+
begin
|
243
|
+
output = SharedHelpers.run_shell_command("pyenv exec poetry show --only main")
|
244
|
+
|
245
|
+
output.split("\n").map { |line| line.split.first }
|
246
|
+
rescue SharedHelpers::HelperSubprocessFailed
|
247
|
+
# Sometimes, we may be dealing with an old lockfile that our
|
248
|
+
# poetry version can't show dependency information for. Other
|
249
|
+
# commands we use like `poetry update` are more resilient and
|
250
|
+
# automatically heal the lockfile. So we rescue the error and make
|
251
|
+
# a best effort approach to this.
|
252
|
+
poetry_dependencies.dependencies.filter_map do |dep|
|
253
|
+
dep.name if dep.production?
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
sig { params(dep_name: String).returns(T.untyped) }
|
260
|
+
def version_from_lockfile(dep_name)
|
261
|
+
return unless parsed_lockfile
|
262
|
+
|
263
|
+
parsed_lockfile.fetch("package", [])
|
264
|
+
.find { |p| normalise(p.fetch("name")) == normalise(dep_name) }
|
265
|
+
&.fetch("version", nil)
|
266
|
+
end
|
267
|
+
|
268
|
+
sig { params(req: T.untyped).returns(T::Array[Dependabot::Uv::Requirement]) }
|
269
|
+
def check_requirements(req)
|
270
|
+
requirement = req.is_a?(String) ? req : req["version"]
|
271
|
+
Uv::Requirement.requirements_array(requirement)
|
272
|
+
rescue Gem::Requirement::BadRequirementError => e
|
273
|
+
raise Dependabot::DependencyFileNotEvaluatable, e.message
|
274
|
+
end
|
275
|
+
|
276
|
+
sig { params(name: String).returns(String) }
|
277
|
+
def normalise(name)
|
278
|
+
NameNormaliser.normalise(name)
|
279
|
+
end
|
280
|
+
|
281
|
+
sig { returns(T.untyped) }
|
282
|
+
def parsed_pyproject
|
283
|
+
@parsed_pyproject ||= T.let(TomlRB.parse(T.must(pyproject).content), T.untyped)
|
284
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
285
|
+
raise Dependabot::DependencyFileNotParseable, T.must(pyproject).path
|
286
|
+
end
|
287
|
+
|
288
|
+
sig { returns(T.untyped) }
|
289
|
+
def parsed_poetry_lock
|
290
|
+
@parsed_poetry_lock ||= T.let(TomlRB.parse(T.must(poetry_lock).content), T.untyped)
|
291
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
292
|
+
raise Dependabot::DependencyFileNotParseable, T.must(poetry_lock).path
|
293
|
+
end
|
294
|
+
|
295
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
296
|
+
def pyproject
|
297
|
+
@pyproject ||= T.let(dependency_files.find { |f| f.name == "pyproject.toml" },
|
298
|
+
T.nilable(Dependabot::DependencyFile))
|
299
|
+
end
|
300
|
+
|
301
|
+
sig { returns(T.untyped) }
|
302
|
+
def lockfile
|
303
|
+
poetry_lock
|
304
|
+
end
|
305
|
+
|
306
|
+
sig { returns(T.untyped) }
|
307
|
+
def parsed_pep621_dependencies
|
308
|
+
SharedHelpers.in_a_temporary_directory do
|
309
|
+
write_temporary_pyproject
|
310
|
+
|
311
|
+
SharedHelpers.run_helper_subprocess(
|
312
|
+
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
313
|
+
function: "parse_pep621_dependencies",
|
314
|
+
args: [T.must(pyproject).name]
|
315
|
+
)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
sig { returns(Integer) }
|
320
|
+
def write_temporary_pyproject
|
321
|
+
path = T.must(pyproject).name
|
322
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
323
|
+
File.write(path, T.must(pyproject).content)
|
324
|
+
end
|
325
|
+
|
326
|
+
sig { returns(T.untyped) }
|
327
|
+
def parsed_lockfile
|
328
|
+
parsed_poetry_lock if poetry_lock
|
329
|
+
end
|
330
|
+
|
331
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
332
|
+
def poetry_lock
|
333
|
+
@poetry_lock ||= T.let(dependency_files.find { |f| f.name == "poetry.lock" },
|
334
|
+
T.nilable(Dependabot::DependencyFile))
|
335
|
+
end
|
336
|
+
|
337
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
338
|
+
def pdm_lock
|
339
|
+
@pdm_lock ||= T.let(dependency_files.find { |f| f.name == "pdm.lock" },
|
340
|
+
T.nilable(Dependabot::DependencyFile))
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "toml-rb"
|
5
|
+
require "open3"
|
6
|
+
require "dependabot/errors"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
require "dependabot/uv/file_parser"
|
9
|
+
require "dependabot/uv/pip_compile_file_matcher"
|
10
|
+
require "dependabot/uv/requirement"
|
11
|
+
|
12
|
+
module Dependabot
|
13
|
+
module Uv
|
14
|
+
class FileParser
|
15
|
+
class PythonRequirementParser
|
16
|
+
attr_reader :dependency_files
|
17
|
+
|
18
|
+
def initialize(dependency_files:)
|
19
|
+
@dependency_files = dependency_files
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_specified_requirements
|
23
|
+
[
|
24
|
+
pipfile_python_requirement,
|
25
|
+
pyproject_python_requirement,
|
26
|
+
pip_compile_python_requirement,
|
27
|
+
python_version_file_version,
|
28
|
+
runtime_file_python_version,
|
29
|
+
setup_file_requirement
|
30
|
+
].compact
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO: Add better Python version detection using dependency versions
|
34
|
+
# (e.g., Django 2.x implies Python 3)
|
35
|
+
def imputed_requirements
|
36
|
+
requirement_files.flat_map do |file|
|
37
|
+
file.content.lines
|
38
|
+
.select { |l| l.include?(";") && l.include?("python") }
|
39
|
+
.filter_map { |l| l.match(/python_version(?<req>.*?["'].*?['"])/) }
|
40
|
+
.map { |re| re.named_captures.fetch("req").gsub(/['"]/, "") }
|
41
|
+
.select { |r| valid_requirement?(r) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def pipfile_python_requirement
|
48
|
+
return unless pipfile
|
49
|
+
|
50
|
+
parsed_pipfile = TomlRB.parse(pipfile.content)
|
51
|
+
requirement =
|
52
|
+
parsed_pipfile.dig("requires", "python_full_version") ||
|
53
|
+
parsed_pipfile.dig("requires", "python_version")
|
54
|
+
return unless requirement&.match?(/^\d/)
|
55
|
+
|
56
|
+
requirement
|
57
|
+
end
|
58
|
+
|
59
|
+
def pyproject_python_requirement
|
60
|
+
return unless pyproject
|
61
|
+
|
62
|
+
pyproject_object = TomlRB.parse(pyproject.content)
|
63
|
+
|
64
|
+
# Check for PEP621 requires-python
|
65
|
+
pep621_python = pyproject_object.dig("project", "requires-python")
|
66
|
+
return pep621_python if pep621_python
|
67
|
+
|
68
|
+
# Fallback to Poetry configuration
|
69
|
+
poetry_object = pyproject_object.dig("tool", "poetry")
|
70
|
+
|
71
|
+
poetry_object&.dig("dependencies", "python") ||
|
72
|
+
poetry_object&.dig("dev-dependencies", "python")
|
73
|
+
end
|
74
|
+
|
75
|
+
def pip_compile_python_requirement
|
76
|
+
requirement_files.each do |file|
|
77
|
+
next unless pip_compile_file_matcher.lockfile_for_pip_compile_file?(file)
|
78
|
+
|
79
|
+
marker = /^# This file is autogenerated by pip-compile with [pP]ython (?<version>\d+.\d+)$/m
|
80
|
+
match = marker.match(file.content)
|
81
|
+
next unless match
|
82
|
+
|
83
|
+
return match[:version]
|
84
|
+
end
|
85
|
+
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def python_version_file_version
|
90
|
+
return unless python_version_file
|
91
|
+
|
92
|
+
# read the content, split into lines and remove any lines with '#'
|
93
|
+
content_lines = python_version_file.content.each_line.map do |line|
|
94
|
+
line.sub(/#.*$/, " ").strip
|
95
|
+
end.reject(&:empty?)
|
96
|
+
|
97
|
+
file_version = content_lines.first
|
98
|
+
return if file_version&.empty?
|
99
|
+
return unless pyenv_versions.include?("#{file_version}\n")
|
100
|
+
|
101
|
+
file_version
|
102
|
+
end
|
103
|
+
|
104
|
+
def runtime_file_python_version
|
105
|
+
return unless runtime_file
|
106
|
+
|
107
|
+
file_version = runtime_file.content
|
108
|
+
.match(/(?<=python-).*/)&.to_s&.strip
|
109
|
+
return if file_version&.empty?
|
110
|
+
return unless pyenv_versions.include?("#{file_version}\n")
|
111
|
+
|
112
|
+
file_version
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_file_requirement
|
116
|
+
return unless setup_file
|
117
|
+
|
118
|
+
req = setup_file.content
|
119
|
+
.match(/python_requires\s*=\s*['"](?<req>[^'"]+)['"]/)
|
120
|
+
&.named_captures&.fetch("req")&.strip
|
121
|
+
|
122
|
+
requirement_class.new(req)
|
123
|
+
req
|
124
|
+
rescue Gem::Requirement::BadRequirementError
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def pyenv_versions
|
129
|
+
@pyenv_versions ||= run_command("pyenv install --list")
|
130
|
+
end
|
131
|
+
|
132
|
+
def run_command(command, env: {})
|
133
|
+
SharedHelpers.run_shell_command(command, env: env, stderr_to_stdout: true)
|
134
|
+
end
|
135
|
+
|
136
|
+
def pip_compile_file_matcher
|
137
|
+
@pip_compile_file_matcher ||= PipCompileFileMatcher.new(pip_compile_files)
|
138
|
+
end
|
139
|
+
|
140
|
+
def requirement_class
|
141
|
+
Dependabot::Uv::Requirement
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid_requirement?(req_string)
|
145
|
+
requirement_class.new(req_string)
|
146
|
+
true
|
147
|
+
rescue Gem::Requirement::BadRequirementError
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
def pipfile
|
152
|
+
dependency_files.find { |f| f.name == "Pipfile" }
|
153
|
+
end
|
154
|
+
|
155
|
+
def pipfile_lock
|
156
|
+
dependency_files.find { |f| f.name == "Pipfile.lock" }
|
157
|
+
end
|
158
|
+
|
159
|
+
def pyproject
|
160
|
+
dependency_files.find { |f| f.name == "pyproject.toml" }
|
161
|
+
end
|
162
|
+
|
163
|
+
def setup_file
|
164
|
+
dependency_files.find { |f| f.name == "setup.py" }
|
165
|
+
end
|
166
|
+
|
167
|
+
def python_version_file
|
168
|
+
dependency_files.find { |f| f.name == ".python-version" }
|
169
|
+
end
|
170
|
+
|
171
|
+
def runtime_file
|
172
|
+
dependency_files.find { |f| f.name.end_with?("runtime.txt") }
|
173
|
+
end
|
174
|
+
|
175
|
+
def requirement_files
|
176
|
+
dependency_files.select { |f| f.name.end_with?(".txt") }
|
177
|
+
end
|
178
|
+
|
179
|
+
def pip_compile_files
|
180
|
+
dependency_files.select { |f| f.name.end_with?(".in") }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|