dependabot-python 0.398.0 → 0.399.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/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/update_checker/pip_version_resolver.rb +16 -42
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e243e407de697945437e9818b4a451ab39ecb274ea6281b9ddcd2270d1bd16e7
|
|
4
|
+
data.tar.gz: 2ff42e6c9c205775d467942431d3c8aa51e2c4db9557b7bfbea733e2b201cfae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0297caeec2ee5d809878d232089fabb5015966cb703bf59f3fcbdd295ca87908be3bffbe447d9c32b79b2945796f4c7d2a985574643496fe45e69116fb77a7fc'
|
|
7
|
+
data.tar.gz: 9f2c74c8fca0e487014943a033c030c67907c0f9135ae802bf4bd8d38dab8c876f18e0f05f98f7cae033906555ce0981c54b48671dd5d4d345e9b892576dda96
|
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
|
|
@@ -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"
|
|
@@ -363,9 +363,19 @@ module Dependabot
|
|
|
363
363
|
content = replace_header_with_original(updated_content, T.must(file.content))
|
|
364
364
|
content = remove_new_warnings(content, T.must(file.content))
|
|
365
365
|
content = update_hashes_if_required(content, T.must(file.content))
|
|
366
|
+
content = scrub_index_url_credentials(content)
|
|
366
367
|
replace_absolute_file_paths(content, T.must(file.content))
|
|
367
368
|
end
|
|
368
369
|
|
|
370
|
+
# Index URLs are passed to pip-compile with credentials embedded so the
|
|
371
|
+
# private index can be fetched, and pip-tools emits those URLs verbatim
|
|
372
|
+
# into the compiled file when index-url emission is active. Strip any
|
|
373
|
+
# userinfo so tokens are never written into the regenerated file.
|
|
374
|
+
sig { params(content: String).returns(String) }
|
|
375
|
+
def scrub_index_url_credentials(content)
|
|
376
|
+
content.gsub(%r{^(--(?:extra-)?index-url[= ]https?://)[^/\s]+@}, "\\1")
|
|
377
|
+
end
|
|
378
|
+
|
|
369
379
|
sig { params(updated_content: String, original_content: String).returns(String) }
|
|
370
380
|
def replace_header_with_original(updated_content, original_content)
|
|
371
381
|
original_header_lines =
|
|
@@ -13,6 +13,7 @@ require "dependabot/python/package/package_registry_finder"
|
|
|
13
13
|
require "dependabot/python/update_checker"
|
|
14
14
|
require "dependabot/python/update_checker/latest_version_finder"
|
|
15
15
|
require "dependabot/python/file_parser/python_requirement_parser"
|
|
16
|
+
require "dependabot/python/file_parser/pyproject_document"
|
|
16
17
|
|
|
17
18
|
module Dependabot
|
|
18
19
|
module Python
|
|
@@ -62,7 +63,7 @@ module Dependabot
|
|
|
62
63
|
@constraints_files = T.let(nil, T.nilable(T::Array[String]))
|
|
63
64
|
@constraints_file_basenames = T.let(nil, T.nilable(T::Array[String]))
|
|
64
65
|
@requirement_file_directories = T.let(nil, T.nilable(T::Array[String]))
|
|
65
|
-
@
|
|
66
|
+
@pyproject_document_cache = T.let({}, T::Hash[String, FileParser::PyprojectDocument])
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
sig { returns(T.nilable(Dependabot::Version)) }
|
|
@@ -201,19 +202,8 @@ module Dependabot
|
|
|
201
202
|
|
|
202
203
|
sig { params(pyproject: Dependabot::DependencyFile).returns(T::Array[[String, String]]) }
|
|
203
204
|
def pinned_pyproject_dependencies_for(pyproject)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return [] unless project_obj.is_a?(Hash)
|
|
207
|
-
|
|
208
|
-
project_hash = project_obj
|
|
209
|
-
dependencies_obj = T.cast(project_hash["dependencies"], T.nilable(Object))
|
|
210
|
-
return [] unless dependencies_obj.is_a?(Array)
|
|
211
|
-
|
|
212
|
-
dependencies_obj.filter_map do |entry|
|
|
213
|
-
entry_obj = T.cast(entry, T.nilable(Object))
|
|
214
|
-
next unless entry_obj.is_a?(String)
|
|
215
|
-
|
|
216
|
-
requirement_string, marker = split_requirement_and_marker(entry_obj)
|
|
205
|
+
pyproject_document_for(pyproject).pip_dependencies.filter_map do |entry|
|
|
206
|
+
requirement_string, marker = split_requirement_and_marker(entry)
|
|
217
207
|
next unless marker_satisfied_for_python?(marker)
|
|
218
208
|
next if requirement_string.nil? || requirement_string.empty?
|
|
219
209
|
|
|
@@ -432,23 +422,8 @@ module Dependabot
|
|
|
432
422
|
|
|
433
423
|
sig { params(pyproject: Dependabot::DependencyFile).returns(T::Array[String]) }
|
|
434
424
|
def constraints_for_pyproject(pyproject)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return [] unless tool_obj.is_a?(Hash)
|
|
438
|
-
|
|
439
|
-
pip_obj = T.cast(tool_obj["pip"], T.nilable(Object))
|
|
440
|
-
return [] unless pip_obj.is_a?(Hash)
|
|
441
|
-
|
|
442
|
-
constraints_obj = T.cast(pip_obj["constraints"], T.nilable(Object))
|
|
443
|
-
case constraints_obj
|
|
444
|
-
when String
|
|
445
|
-
[resolve_constraint_path(path: constraints_obj, declaring_file: pyproject)]
|
|
446
|
-
when Array
|
|
447
|
-
constraints_obj.grep(String).map do |path|
|
|
448
|
-
resolve_constraint_path(path: path, declaring_file: pyproject)
|
|
449
|
-
end
|
|
450
|
-
else
|
|
451
|
-
[]
|
|
425
|
+
pyproject_document_for(pyproject).pip_constraint_paths.map do |path|
|
|
426
|
+
resolve_constraint_path(path: path, declaring_file: pyproject)
|
|
452
427
|
end
|
|
453
428
|
end
|
|
454
429
|
|
|
@@ -556,23 +531,22 @@ module Dependabot
|
|
|
556
531
|
marker_evaluator.marker_satisfied?(marker: marker, python_version: python_version)
|
|
557
532
|
end
|
|
558
533
|
|
|
559
|
-
sig { params(pyproject: Dependabot::DependencyFile).returns(
|
|
560
|
-
def
|
|
534
|
+
sig { params(pyproject: Dependabot::DependencyFile).returns(FileParser::PyprojectDocument) }
|
|
535
|
+
def pyproject_document_for(pyproject)
|
|
561
536
|
cache_key = pyproject.name
|
|
562
|
-
return T.must(@
|
|
537
|
+
return T.must(@pyproject_document_cache[cache_key]) if @pyproject_document_cache.key?(cache_key)
|
|
563
538
|
|
|
564
|
-
content =
|
|
565
|
-
|
|
566
|
-
|
|
539
|
+
content = pyproject.content
|
|
540
|
+
document =
|
|
541
|
+
if content
|
|
542
|
+
FileParser::PyprojectDocument.from_content(content)
|
|
567
543
|
else
|
|
568
|
-
|
|
544
|
+
FileParser::PyprojectDocument.new({})
|
|
569
545
|
end
|
|
570
546
|
|
|
571
|
-
@
|
|
572
|
-
content
|
|
547
|
+
@pyproject_document_cache[cache_key] = document
|
|
573
548
|
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
|
574
|
-
@
|
|
575
|
-
T.must(@pyproject_content_cache[pyproject.name])
|
|
549
|
+
@pyproject_document_cache[pyproject.name] = FileParser::PyprojectDocument.new({})
|
|
576
550
|
end
|
|
577
551
|
end
|
|
578
552
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-python
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.399.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.399.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.399.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -281,7 +281,9 @@ files:
|
|
|
281
281
|
- lib/dependabot/python/file_fetcher.rb
|
|
282
282
|
- lib/dependabot/python/file_parser.rb
|
|
283
283
|
- lib/dependabot/python/file_parser/pep_dependency.rb
|
|
284
|
+
- lib/dependabot/python/file_parser/pipfile_document.rb
|
|
284
285
|
- lib/dependabot/python/file_parser/pipfile_files_parser.rb
|
|
286
|
+
- lib/dependabot/python/file_parser/pipfile_lock_document.rb
|
|
285
287
|
- lib/dependabot/python/file_parser/poetry_lock.rb
|
|
286
288
|
- lib/dependabot/python/file_parser/pyproject_document.rb
|
|
287
289
|
- lib/dependabot/python/file_parser/pyproject_files_parser.rb
|
|
@@ -327,7 +329,7 @@ licenses:
|
|
|
327
329
|
- MIT
|
|
328
330
|
metadata:
|
|
329
331
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
330
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
332
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.399.0
|
|
331
333
|
rdoc_options: []
|
|
332
334
|
require_paths:
|
|
333
335
|
- lib
|