dependabot-python 0.398.0 → 0.400.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 +4 -4
- data/helpers/requirements.txt +2 -2
- data/lib/dependabot/python/dependency_grapher/requirements_layers.rb +3 -1
- data/lib/dependabot/python/file_parser/pipfile_document.rb +105 -0
- data/lib/dependabot/python/file_parser/pipfile_files_parser.rb +27 -87
- data/lib/dependabot/python/file_parser/pipfile_lock_document.rb +67 -0
- data/lib/dependabot/python/file_parser/pyproject_document.rb +28 -0
- data/lib/dependabot/python/file_parser/setup_file_parser.rb +71 -72
- data/lib/dependabot/python/file_parser.rb +2 -0
- data/lib/dependabot/python/file_updater/pip_compile_file_updater.rb +10 -0
- data/lib/dependabot/python/package/distribution.rb +180 -0
- data/lib/dependabot/python/package/package_details_fetcher.rb +48 -102
- data/lib/dependabot/python/package/pypi_json_parser.rb +48 -0
- data/lib/dependabot/python/package/simple_api_parser.rb +25 -34
- data/lib/dependabot/python/update_checker/pip_version_resolver.rb +16 -42
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddc78c3d36f56fa3124806151a71bb16e46ccbd4fb002a814974c6d4b8fd2fad
|
|
4
|
+
data.tar.gz: b08192e8bc0a5b8c0125266571bd034029ed17e2c0f77a9d01350b7a9d30fca6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5062ffec97c3b221246787027d1afb91fc0da8aeba188cbf7be0f834448ec3ddf0d8ed25a5c7e18803bf986c8c0f6fea96cd4164ae6ecd81215fb4ffb0f75628
|
|
7
|
+
data.tar.gz: 932c74e3ca269747bcbd005fa0ac624d38a26bc60269953b7a8af99c03abf157b999b1d6ec5e30a64b7791a3c970aca267f964bb62dab880a083fb4021a8cbc9
|
data/helpers/requirements.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
pip==26.2.1
|
|
2
2
|
pip-tools==7.6.1
|
|
3
|
-
flake8==7.
|
|
3
|
+
flake8==7.4.1
|
|
4
4
|
hashin==1.0.5
|
|
5
5
|
pipenv==2024.4.1
|
|
6
6
|
plette==2.2.1
|
|
7
|
-
poetry==2.
|
|
7
|
+
poetry==2.5.1
|
|
8
8
|
pytest==9.1.1
|
|
9
9
|
# TODO: Replace 3p package `tomli` with 3.11's new stdlib `tomllib` once we drop support for Python 3.10.
|
|
10
10
|
tomli==2.4.1
|
|
@@ -129,7 +129,9 @@ module Dependabot
|
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
# When the primary is a compiled `.txt`, the `-r`/`-c` directives live in the paired `.in` files.
|
|
132
|
-
|
|
132
|
+
# Constraints files join every layer below, so they are seeded here too: a constraints file
|
|
133
|
+
# that references siblings needs those siblings on disk for the layer to parse.
|
|
134
|
+
referenced = referenced_requirement_files([primary] + paired + constraints_files)
|
|
133
135
|
|
|
134
136
|
support = (paired + referenced + constraints_files).uniq.map do |f|
|
|
135
137
|
as_support_file(f)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "toml-rb"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
|
|
7
|
+
require "dependabot/dependency_file"
|
|
8
|
+
require "dependabot/errors"
|
|
9
|
+
require "dependabot/python/file_parser"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Python
|
|
13
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
14
|
+
class PipfileDocument
|
|
15
|
+
extend T::Sig
|
|
16
|
+
|
|
17
|
+
class Entry
|
|
18
|
+
extend T::Sig
|
|
19
|
+
|
|
20
|
+
sig { params(name: String, value: Object, context: String).void }
|
|
21
|
+
def initialize(name:, value:, context:)
|
|
22
|
+
@name = name
|
|
23
|
+
@context = context
|
|
24
|
+
@value = T.let(
|
|
25
|
+
value.is_a?(String) ? value : PyprojectValueParser.object_hash(value, context),
|
|
26
|
+
T.any(String, PyprojectValueParser::ObjectHash)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { returns(String) }
|
|
31
|
+
attr_reader :name
|
|
32
|
+
|
|
33
|
+
sig { returns(T::Boolean) }
|
|
34
|
+
def specifies_version?
|
|
35
|
+
value = @value
|
|
36
|
+
return true if value.is_a?(String)
|
|
37
|
+
|
|
38
|
+
version = value["version"]
|
|
39
|
+
return false if version.nil? || version == false
|
|
40
|
+
return true if version.is_a?(String) || version == true
|
|
41
|
+
|
|
42
|
+
raise TypeError, "#{@context} version must be a string or boolean"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sig { returns(T::Boolean) }
|
|
46
|
+
def git_or_path?
|
|
47
|
+
value = @value
|
|
48
|
+
value.is_a?(Hash) && (value.key?("git") || value.key?("path"))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sig { returns(String) }
|
|
52
|
+
def requirement
|
|
53
|
+
value = @value
|
|
54
|
+
return value.empty? ? "*" : value if value.is_a?(String)
|
|
55
|
+
|
|
56
|
+
PyprojectValueParser.string(value["version"], "#{@context} version")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { returns(T.nilable(String)) }
|
|
60
|
+
def lookup_version
|
|
61
|
+
value = @value
|
|
62
|
+
return value.strip if value.is_a?(String)
|
|
63
|
+
|
|
64
|
+
PyprojectValueParser.optional_string(value["version"], "#{@context} version")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sig { returns(T.nilable(String)) }
|
|
68
|
+
def lockfile_version
|
|
69
|
+
value = @value
|
|
70
|
+
version = value.is_a?(String) ? value : value["version"]
|
|
71
|
+
return unless version
|
|
72
|
+
return if git_or_path?
|
|
73
|
+
|
|
74
|
+
PyprojectValueParser.string(version, "#{@context} version")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sig { params(file: Dependabot::DependencyFile).returns(PipfileDocument) }
|
|
79
|
+
def self.from_file(file)
|
|
80
|
+
new(data: T.cast(TomlRB.parse(T.must(file.content)), Object), context: file.path)
|
|
81
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
|
82
|
+
raise Dependabot::DependencyFileNotParseable, file.path
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sig { params(data: Object, context: String).void }
|
|
86
|
+
def initialize(data:, context:)
|
|
87
|
+
@data = T.let(PyprojectValueParser.object_hash(data, context), PyprojectValueParser::ObjectHash)
|
|
88
|
+
@context = context
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
sig { params(group: String).returns(T::Array[Entry]) }
|
|
92
|
+
def entries(group)
|
|
93
|
+
value = @data[group]
|
|
94
|
+
return [] unless value
|
|
95
|
+
return [] if value.is_a?(Array) && value.empty?
|
|
96
|
+
|
|
97
|
+
context = "#{@context} #{group}"
|
|
98
|
+
PyprojectValueParser.object_hash(value, context).map do |name, requirement|
|
|
99
|
+
Entry.new(name: name, value: requirement, context: "#{context} entry")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "toml-rb"
|
|
5
|
-
|
|
6
4
|
require "dependabot/dependency"
|
|
7
5
|
require "dependabot/file_parsers/base/dependency_set"
|
|
8
6
|
require "dependabot/python/file_parser"
|
|
9
|
-
require "dependabot/errors"
|
|
10
7
|
require "dependabot/python/name_normaliser"
|
|
11
8
|
|
|
12
9
|
module Dependabot
|
|
13
10
|
module Python
|
|
14
|
-
class FileParser
|
|
11
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
15
12
|
class PipfileFilesParser
|
|
16
13
|
extend T::Sig
|
|
17
14
|
|
|
@@ -54,30 +51,24 @@ module Dependabot
|
|
|
54
51
|
dependencies = Dependabot::FileParsers::Base::DependencySet.new
|
|
55
52
|
|
|
56
53
|
DEPENDENCY_GROUP_KEYS.each do |keys|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
next
|
|
62
|
-
next if git_or_path_requirement?(req)
|
|
63
|
-
next if pipfile_lock && !dependency_version(dep_name, req, T.must(group))
|
|
64
|
-
|
|
65
|
-
# Empty requirements are not allowed in Dependabot::Dependency and
|
|
66
|
-
# equivalent to "*" (latest available version)
|
|
67
|
-
req = "*" if req == ""
|
|
54
|
+
pipfile_document.entries(keys.fetch(:pipfile)).each do |entry|
|
|
55
|
+
group = keys.fetch(:lockfile)
|
|
56
|
+
next unless entry.specifies_version?
|
|
57
|
+
next if entry.git_or_path?
|
|
58
|
+
next if pipfile_lock && !dependency_version(entry, group)
|
|
68
59
|
|
|
69
60
|
dependencies <<
|
|
70
61
|
Dependency.new(
|
|
71
|
-
name: normalised_name(
|
|
72
|
-
version: dependency_version(
|
|
62
|
+
name: normalised_name(entry.name),
|
|
63
|
+
version: dependency_version(entry, group),
|
|
73
64
|
requirements: [{
|
|
74
|
-
requirement:
|
|
65
|
+
requirement: entry.requirement,
|
|
75
66
|
file: T.must(pipfile).name,
|
|
76
67
|
source: nil,
|
|
77
68
|
groups: [group]
|
|
78
69
|
}],
|
|
79
70
|
package_manager: "pip",
|
|
80
|
-
metadata: { original_name:
|
|
71
|
+
metadata: { original_name: entry.name }
|
|
81
72
|
)
|
|
82
73
|
end
|
|
83
74
|
end
|
|
@@ -94,21 +85,14 @@ module Dependabot
|
|
|
94
85
|
return dependencies unless pipfile_lock
|
|
95
86
|
|
|
96
87
|
DEPENDENCY_GROUP_KEYS.map { |h| h.fetch(:lockfile) }.each do |key|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
section_data.each do |dep_name, details|
|
|
101
|
-
version = case details
|
|
102
|
-
when String then details
|
|
103
|
-
when Hash then details["version"]
|
|
104
|
-
end
|
|
88
|
+
pipfile_lock_document.entries(key).each do |entry|
|
|
89
|
+
version = entry.lockfile_version
|
|
105
90
|
next unless version
|
|
106
|
-
next if git_or_path_requirement?(details)
|
|
107
91
|
|
|
108
92
|
dependencies <<
|
|
109
93
|
Dependency.new(
|
|
110
|
-
name:
|
|
111
|
-
version: version
|
|
94
|
+
name: entry.name,
|
|
95
|
+
version: version.gsub(/^===?/, ""),
|
|
112
96
|
requirements: [],
|
|
113
97
|
package_manager: "pip",
|
|
114
98
|
subdependency_metadata: [{ production: key != "develop" }]
|
|
@@ -119,78 +103,34 @@ module Dependabot
|
|
|
119
103
|
dependencies
|
|
120
104
|
end
|
|
121
105
|
|
|
122
|
-
sig
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
requirement: T.any(String, T::Hash[String, T.untyped]),
|
|
126
|
-
group: String
|
|
127
|
-
).returns(T.nilable(String))
|
|
128
|
-
end
|
|
129
|
-
def dependency_version(dep_name, requirement, group)
|
|
130
|
-
req = version_from_hash_or_string(requirement)
|
|
106
|
+
sig { params(entry: PipfileDocument::Entry, group: String).returns(T.nilable(String)) }
|
|
107
|
+
def dependency_version(entry, group)
|
|
108
|
+
req = entry.lookup_version
|
|
131
109
|
|
|
132
110
|
if pipfile_lock
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
version = version_from_hash_or_string(details)
|
|
111
|
+
version = pipfile_lock_document.version_for(group, normalised_name(entry.name))
|
|
136
112
|
version&.gsub(/^===?/, "")
|
|
137
113
|
elsif T.must(req).start_with?("==") && !T.must(req).include?("*")
|
|
138
114
|
T.must(req).strip.gsub(/^===?/, "")
|
|
139
115
|
end
|
|
140
116
|
end
|
|
141
117
|
|
|
142
|
-
sig do
|
|
143
|
-
params(obj: T.nilable(T.any(String, T::Array[String], T::Hash[String, T.untyped]))).returns(T.nilable(String))
|
|
144
|
-
end
|
|
145
|
-
def version_from_hash_or_string(obj)
|
|
146
|
-
case obj
|
|
147
|
-
when String then obj.strip
|
|
148
|
-
when Hash then obj["version"]
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
sig { params(req: T.any(String, T::Hash[String, T.untyped])).returns(T.nilable(T.any(T::Boolean, String))) }
|
|
153
|
-
def specifies_version?(req)
|
|
154
|
-
return true if req.is_a?(String)
|
|
155
|
-
|
|
156
|
-
req["version"]
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
sig { params(req: T.any(String, T::Hash[String, T.untyped])).returns(T::Boolean) }
|
|
160
|
-
def git_or_path_requirement?(req)
|
|
161
|
-
return false unless req.is_a?(Hash)
|
|
162
|
-
|
|
163
|
-
%w(git path).any? { |k| req.key?(k) }
|
|
164
|
-
end
|
|
165
|
-
|
|
166
118
|
sig { params(name: String, extras: T::Array[String]).returns(String) }
|
|
167
119
|
def normalised_name(name, extras = [])
|
|
168
120
|
NameNormaliser.normalise_including_extras(name, extras)
|
|
169
121
|
end
|
|
170
122
|
|
|
171
|
-
sig { returns(
|
|
172
|
-
def
|
|
173
|
-
@
|
|
174
|
-
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
|
175
|
-
raise Dependabot::DependencyFileNotParseable, T.must(pipfile).path
|
|
123
|
+
sig { returns(PipfileDocument) }
|
|
124
|
+
def pipfile_document
|
|
125
|
+
@pipfile_document ||= T.let(PipfileDocument.from_file(T.must(pipfile)), T.nilable(PipfileDocument))
|
|
176
126
|
end
|
|
177
127
|
|
|
178
|
-
sig { returns(
|
|
179
|
-
def
|
|
180
|
-
@
|
|
181
|
-
|
|
182
|
-
T.nilable(
|
|
128
|
+
sig { returns(PipfileLockDocument) }
|
|
129
|
+
def pipfile_lock_document
|
|
130
|
+
@pipfile_lock_document ||= T.let(
|
|
131
|
+
PipfileLockDocument.from_file(T.must(pipfile_lock)),
|
|
132
|
+
T.nilable(PipfileLockDocument)
|
|
183
133
|
)
|
|
184
|
-
rescue JSON::ParserError
|
|
185
|
-
raise Dependabot::DependencyFileNotParseable, T.must(pipfile_lock).path
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
sig { params(section: String).returns(T.nilable(T::Hash[String, T.untyped])) }
|
|
189
|
-
def parsed_pipfile_lock_section(section)
|
|
190
|
-
section_data = parsed_pipfile_lock[section]
|
|
191
|
-
return section_data if section_data.is_a?(Hash)
|
|
192
|
-
|
|
193
|
-
nil
|
|
194
134
|
end
|
|
195
135
|
|
|
196
136
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
|
|
7
|
+
require "dependabot/dependency_file"
|
|
8
|
+
require "dependabot/errors"
|
|
9
|
+
require "dependabot/python/file_parser"
|
|
10
|
+
require "dependabot/python/file_parser/pipfile_document"
|
|
11
|
+
|
|
12
|
+
module Dependabot
|
|
13
|
+
module Python
|
|
14
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
15
|
+
class PipfileLockDocument
|
|
16
|
+
extend T::Sig
|
|
17
|
+
|
|
18
|
+
sig { params(file: Dependabot::DependencyFile).returns(PipfileLockDocument) }
|
|
19
|
+
def self.from_file(file)
|
|
20
|
+
new(data: T.cast(JSON.parse(T.must(file.content)), Object), context: file.path)
|
|
21
|
+
rescue JSON::ParserError
|
|
22
|
+
raise Dependabot::DependencyFileNotParseable, file.path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig { params(data: Object, context: String).void }
|
|
26
|
+
def initialize(data:, context:)
|
|
27
|
+
@data = T.let(PyprojectValueParser.object_hash(data, context), PyprojectValueParser::ObjectHash)
|
|
28
|
+
@context = context
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(group: String).returns(T::Array[PipfileDocument::Entry]) }
|
|
32
|
+
def entries(group)
|
|
33
|
+
entries = section(group)
|
|
34
|
+
return [] unless entries
|
|
35
|
+
|
|
36
|
+
entries.filter_map do |name, value|
|
|
37
|
+
next unless value.is_a?(String) || value.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
PipfileDocument::Entry.new(name: name, value: value, context: "#{@context} #{group} entry")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig { params(group: String, name: String).returns(T.nilable(String)) }
|
|
44
|
+
def version_for(group, name)
|
|
45
|
+
value = section(group)&.[](name)
|
|
46
|
+
return if value.nil? || value.is_a?(Array)
|
|
47
|
+
|
|
48
|
+
unless value.is_a?(String) || value.is_a?(Hash)
|
|
49
|
+
raise TypeError, "#{@context} #{group} entry must be a string, object, or array"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
PipfileDocument::Entry.new(name: name, value: value, context: "#{@context} #{group} entry").lookup_version
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
sig { params(group: String).returns(T.nilable(PyprojectValueParser::ObjectHash)) }
|
|
58
|
+
def section(group)
|
|
59
|
+
value = @data[group]
|
|
60
|
+
return unless value.is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
PyprojectValueParser.object_hash(value, "#{@context} #{group}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -202,6 +202,34 @@ module Dependabot
|
|
|
202
202
|
PyprojectValueParser.string_array(value, "project.dynamic")
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
+
# Pip resolution ignores malformed containers and non-string entries in these fields.
|
|
206
|
+
sig { returns(T::Array[String]) }
|
|
207
|
+
def pip_dependencies
|
|
208
|
+
project = @data["project"]
|
|
209
|
+
return [] unless project.is_a?(Hash)
|
|
210
|
+
|
|
211
|
+
dependencies = T.cast(project["dependencies"], Object)
|
|
212
|
+
return [] unless dependencies.is_a?(Array)
|
|
213
|
+
|
|
214
|
+
dependencies.grep(String)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
sig { returns(T::Array[String]) }
|
|
218
|
+
def pip_constraint_paths
|
|
219
|
+
tool = @data["tool"]
|
|
220
|
+
return [] unless tool.is_a?(Hash)
|
|
221
|
+
|
|
222
|
+
pip = T.cast(tool["pip"], Object)
|
|
223
|
+
return [] unless pip.is_a?(Hash)
|
|
224
|
+
|
|
225
|
+
constraints = T.cast(pip["constraints"], Object)
|
|
226
|
+
case constraints
|
|
227
|
+
when String then [constraints]
|
|
228
|
+
when Array then constraints.grep(String)
|
|
229
|
+
else []
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
205
233
|
sig { params(name: String).returns(T::Boolean) }
|
|
206
234
|
def optional_dependency_group?(name)
|
|
207
235
|
project = section(@data, "project", "project")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "dependabot/dependency"
|
|
@@ -12,10 +12,21 @@ require "sorbet-runtime"
|
|
|
12
12
|
|
|
13
13
|
module Dependabot
|
|
14
14
|
module Python
|
|
15
|
-
class FileParser
|
|
15
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
16
16
|
class SetupFileParser
|
|
17
17
|
extend T::Sig
|
|
18
18
|
|
|
19
|
+
class SetupDependency < T::ImmutableStruct
|
|
20
|
+
const :name, String
|
|
21
|
+
const :version, T.nilable(String)
|
|
22
|
+
const :markers, T.nilable(String)
|
|
23
|
+
const :file, String
|
|
24
|
+
const :requirement, T.nilable(String)
|
|
25
|
+
const :requirement_type, String
|
|
26
|
+
const :extras, T::Array[String]
|
|
27
|
+
end
|
|
28
|
+
private_constant :SetupDependency
|
|
29
|
+
|
|
19
30
|
INSTALL_REQUIRES_REGEX = /install_requires\s*=\s*\[/m
|
|
20
31
|
SETUP_REQUIRES_REGEX = /setup_requires\s*=\s*\[/m
|
|
21
32
|
TESTS_REQUIRE_REGEX = /tests_require\s*=\s*\[/m
|
|
@@ -35,24 +46,24 @@ module Dependabot
|
|
|
35
46
|
parsed_setup_file.each do |dep|
|
|
36
47
|
# If a requirement has a `<` or `<=` marker then updating it is
|
|
37
48
|
# probably blocked. Ignore it.
|
|
38
|
-
next if dep
|
|
49
|
+
next if dep.markers&.include?("<")
|
|
39
50
|
|
|
40
51
|
# If the requirement is our inserted version, ignore it
|
|
41
52
|
# (we wouldn't be able to update it)
|
|
42
|
-
next if dep
|
|
53
|
+
next if dep.version == "0.0.1+dependabot"
|
|
43
54
|
|
|
44
55
|
dependencies <<
|
|
45
56
|
Dependency.new(
|
|
46
|
-
name: normalise(dep
|
|
47
|
-
version: dep
|
|
57
|
+
name: normalise(dep.name),
|
|
58
|
+
version: dep.version&.include?("*") ? nil : dep.version,
|
|
48
59
|
requirements: [{
|
|
49
|
-
requirement: dep
|
|
50
|
-
file: Pathname.new(dep
|
|
60
|
+
requirement: dep.requirement,
|
|
61
|
+
file: Pathname.new(dep.file).cleanpath.to_path,
|
|
51
62
|
source: nil,
|
|
52
|
-
groups: [dep
|
|
63
|
+
groups: [dep.requirement_type]
|
|
53
64
|
}],
|
|
54
65
|
package_manager: "pip",
|
|
55
|
-
metadata: extras_metadata(dep
|
|
66
|
+
metadata: extras_metadata(dep.extras)
|
|
56
67
|
)
|
|
57
68
|
end
|
|
58
69
|
dependencies
|
|
@@ -63,59 +74,26 @@ module Dependabot
|
|
|
63
74
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
64
75
|
attr_reader :dependency_files
|
|
65
76
|
|
|
66
|
-
sig { returns(T
|
|
77
|
+
sig { returns(T::Array[SetupDependency]) }
|
|
67
78
|
def parsed_setup_file
|
|
68
79
|
SharedHelpers.in_a_temporary_directory do
|
|
69
80
|
write_temporary_dependency_files
|
|
70
|
-
|
|
71
|
-
requirements = T.cast(
|
|
72
|
-
SharedHelpers.run_helper_subprocess(
|
|
73
|
-
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
|
74
|
-
function: "parse_setup",
|
|
75
|
-
args: [Dir.pwd]
|
|
76
|
-
),
|
|
77
|
-
T.nilable(
|
|
78
|
-
T.any(
|
|
79
|
-
T::Hash[String, T.untyped],
|
|
80
|
-
String,
|
|
81
|
-
T::Array[T::Hash[String, T.untyped]]
|
|
82
|
-
)
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
check_requirements(requirements)
|
|
87
|
-
requirements
|
|
81
|
+
run_setup_helper
|
|
88
82
|
end
|
|
89
83
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
90
84
|
raise Dependabot::DependencyFileNotEvaluatable, e.message if e.message.start_with?("InstallationError")
|
|
91
85
|
|
|
92
|
-
|
|
86
|
+
file = setup_file
|
|
87
|
+
return [] unless file
|
|
93
88
|
|
|
94
|
-
parsed_sanitized_setup_file
|
|
89
|
+
parsed_sanitized_setup_file(file)
|
|
95
90
|
end
|
|
96
91
|
|
|
97
|
-
sig {
|
|
98
|
-
def parsed_sanitized_setup_file
|
|
92
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Array[SetupDependency]) }
|
|
93
|
+
def parsed_sanitized_setup_file(file)
|
|
99
94
|
SharedHelpers.in_a_temporary_directory do
|
|
100
|
-
write_sanitized_setup_file
|
|
101
|
-
|
|
102
|
-
requirements = T.cast(
|
|
103
|
-
SharedHelpers.run_helper_subprocess(
|
|
104
|
-
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
|
105
|
-
function: "parse_setup",
|
|
106
|
-
args: [Dir.pwd]
|
|
107
|
-
),
|
|
108
|
-
T.nilable(
|
|
109
|
-
T.any(
|
|
110
|
-
T::Hash[String, T.untyped],
|
|
111
|
-
String,
|
|
112
|
-
T::Array[T::Hash[String, T.untyped]]
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
check_requirements(requirements)
|
|
118
|
-
requirements
|
|
95
|
+
write_sanitized_setup_file(T.must(file.content))
|
|
96
|
+
run_setup_helper
|
|
119
97
|
end
|
|
120
98
|
rescue SharedHelpers::HelperSubprocessFailed
|
|
121
99
|
# Assume there are no dependencies in setup.py files that fail to
|
|
@@ -125,15 +103,36 @@ module Dependabot
|
|
|
125
103
|
[]
|
|
126
104
|
end
|
|
127
105
|
|
|
128
|
-
sig {
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
106
|
+
sig { returns(T::Array[SetupDependency]) }
|
|
107
|
+
def run_setup_helper
|
|
108
|
+
result = SharedHelpers.run_helper_subprocess(
|
|
109
|
+
command: "pyenv exec python3 #{NativeHelpers.python_helper_path}",
|
|
110
|
+
function: "parse_setup",
|
|
111
|
+
args: [Dir.pwd]
|
|
112
|
+
)
|
|
113
|
+
parse_setup_result(result)
|
|
114
|
+
end
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
sig { params(result: Object).returns(T::Array[SetupDependency]) }
|
|
117
|
+
def parse_setup_result(result)
|
|
118
|
+
PyprojectValueParser.array(result, "parse_setup result").each_with_index.map do |value, index|
|
|
119
|
+
context = "parse_setup result[#{index}]"
|
|
120
|
+
fields = PyprojectValueParser.object_hash(value, context)
|
|
121
|
+
requirement = PyprojectValueParser.optional_string(fields["requirement"], "#{context}.requirement")
|
|
122
|
+
Python::Requirement.new(requirement.split(",")) unless requirement.nil?
|
|
123
|
+
|
|
124
|
+
SetupDependency.new(
|
|
125
|
+
name: PyprojectValueParser.string(fields["name"], "#{context}.name"),
|
|
126
|
+
version: PyprojectValueParser.optional_string(fields["version"], "#{context}.version"),
|
|
127
|
+
markers: PyprojectValueParser.optional_string(fields["markers"], "#{context}.markers"),
|
|
128
|
+
file: PyprojectValueParser.string(fields["file"], "#{context}.file"),
|
|
129
|
+
requirement: requirement,
|
|
130
|
+
requirement_type: PyprojectValueParser.string(fields["requirement_type"], "#{context}.requirement_type"),
|
|
131
|
+
extras: PyprojectValueParser.string_array(fields["extras"], "#{context}.extras")
|
|
132
|
+
)
|
|
136
133
|
end
|
|
134
|
+
rescue TypeError, Gem::Requirement::BadRequirementError => e
|
|
135
|
+
raise Dependabot::DependencyFileNotEvaluatable, e.message
|
|
137
136
|
end
|
|
138
137
|
|
|
139
138
|
sig { void }
|
|
@@ -152,12 +151,12 @@ module Dependabot
|
|
|
152
151
|
# This sanitization is far from perfect (it will fail if any of the
|
|
153
152
|
# entries are dynamic), but it is an alternative approach to the one
|
|
154
153
|
# used in parser.py which sometimes succeeds when that has failed.
|
|
155
|
-
sig { void }
|
|
156
|
-
def write_sanitized_setup_file
|
|
157
|
-
install_requires = get_regexed_req_array(INSTALL_REQUIRES_REGEX)
|
|
158
|
-
setup_requires = get_regexed_req_array(SETUP_REQUIRES_REGEX)
|
|
159
|
-
tests_require = get_regexed_req_array(TESTS_REQUIRE_REGEX)
|
|
160
|
-
extras_require = get_regexed_req_dict(EXTRAS_REQUIRE_REGEX)
|
|
154
|
+
sig { params(content: String).void }
|
|
155
|
+
def write_sanitized_setup_file(content)
|
|
156
|
+
install_requires = get_regexed_req_array(content, INSTALL_REQUIRES_REGEX)
|
|
157
|
+
setup_requires = get_regexed_req_array(content, SETUP_REQUIRES_REGEX)
|
|
158
|
+
tests_require = get_regexed_req_array(content, TESTS_REQUIRE_REGEX)
|
|
159
|
+
extras_require = get_regexed_req_dict(content, EXTRAS_REQUIRE_REGEX)
|
|
161
160
|
|
|
162
161
|
tmp = "from setuptools import setup\n\n" \
|
|
163
162
|
"setup(name=\"sanitized-package\",version=\"0.0.1\","
|
|
@@ -171,16 +170,16 @@ module Dependabot
|
|
|
171
170
|
File.write("setup.py", tmp)
|
|
172
171
|
end
|
|
173
172
|
|
|
174
|
-
sig { params(regex: Regexp).returns(T.nilable(String)) }
|
|
175
|
-
def get_regexed_req_array(regex)
|
|
176
|
-
return unless (mch =
|
|
173
|
+
sig { params(content: String, regex: Regexp).returns(T.nilable(String)) }
|
|
174
|
+
def get_regexed_req_array(content, regex)
|
|
175
|
+
return unless (mch = content.match(regex))
|
|
177
176
|
|
|
178
177
|
"[#{mch.post_match[0..closing_bracket_index(mch.post_match, '[')]}"
|
|
179
178
|
end
|
|
180
179
|
|
|
181
|
-
sig { params(regex: Regexp).returns(T.nilable(String)) }
|
|
182
|
-
def get_regexed_req_dict(regex)
|
|
183
|
-
return unless (mch =
|
|
180
|
+
sig { params(content: String, regex: Regexp).returns(T.nilable(String)) }
|
|
181
|
+
def get_regexed_req_dict(content, regex)
|
|
182
|
+
return unless (mch = content.match(regex))
|
|
184
183
|
|
|
185
184
|
"{#{mch.post_match[0..closing_bracket_index(mch.post_match, '{')]}"
|
|
186
185
|
end
|
|
@@ -215,7 +214,7 @@ module Dependabot
|
|
|
215
214
|
{ extras: extras.join(",") }
|
|
216
215
|
end
|
|
217
216
|
|
|
218
|
-
sig { returns(T.
|
|
217
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
219
218
|
def setup_file
|
|
220
219
|
dependency_files.find { |f| f.name == "setup.py" }
|
|
221
220
|
end
|
|
@@ -24,6 +24,8 @@ module Dependabot
|
|
|
24
24
|
require_relative "file_parser/pyproject_document"
|
|
25
25
|
require_relative "file_parser/poetry_lock"
|
|
26
26
|
require_relative "file_parser/pep_dependency"
|
|
27
|
+
require_relative "file_parser/pipfile_document"
|
|
28
|
+
require_relative "file_parser/pipfile_lock_document"
|
|
27
29
|
require_relative "file_parser/pipfile_files_parser"
|
|
28
30
|
require_relative "file_parser/pyproject_files_parser"
|
|
29
31
|
require_relative "file_parser/setup_file_parser"
|