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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 994970816dfcd8b91b616aa7631a109f49eee0f9b4f0932de511dd98fec2b241
4
- data.tar.gz: bf721b19a76ba1f476dc6ff6a0cc944930ba7c4719d7206b478db5f2a238d9b1
3
+ metadata.gz: ddc78c3d36f56fa3124806151a71bb16e46ccbd4fb002a814974c6d4b8fd2fad
4
+ data.tar.gz: b08192e8bc0a5b8c0125266571bd034029ed17e2c0f77a9d01350b7a9d30fca6
5
5
  SHA512:
6
- metadata.gz: 9a394b52ded2188b362ba17aa4babe2827a9953139986fc2d5d020a63c226477d31684f992f2e1b9b73bdc70a02d7f3eb5b749864bd70c679a369957ef3bd943
7
- data.tar.gz: a0a788f75f42d236527bdf9e78b8371bc434b231029b91c38650ac1b27818974d595c3eb01dfe149bf8a537a8b4edec3e4c8136cd9af18faae90edfd1ab114d0
6
+ metadata.gz: 5062ffec97c3b221246787027d1afb91fc0da8aeba188cbf7be0f834448ec3ddf0d8ed25a5c7e18803bf986c8c0f6fea96cd4164ae6ecd81215fb4ffb0f75628
7
+ data.tar.gz: 932c74e3ca269747bcbd005fa0ac624d38a26bc60269953b7a8af99c03abf157b999b1d6ec5e30a64b7791a3c970aca267f964bb62dab880a083fb4021a8cbc9
@@ -1,10 +1,10 @@
1
1
  pip==26.2.1
2
2
  pip-tools==7.6.1
3
- flake8==7.3.0
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.4.3
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
- referenced = referenced_requirement_files([primary] + paired)
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: strict
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
- next unless parsed_pipfile[T.must(keys[:pipfile])]
58
-
59
- parsed_pipfile[T.must(keys[:pipfile])].map do |dep_name, req|
60
- group = keys[:lockfile]
61
- next unless specifies_version?(req)
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(dep_name),
72
- version: dependency_version(dep_name, req, T.must(group)),
62
+ name: normalised_name(entry.name),
63
+ version: dependency_version(entry, group),
73
64
  requirements: [{
74
- requirement: req.is_a?(String) ? req : req["version"],
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: dep_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
- section_data = parsed_pipfile_lock_section(key)
98
- next unless section_data
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: dep_name,
111
- version: version&.gsub(/^===?/, ""),
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 do
123
- params(
124
- dep_name: String,
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
- details = parsed_pipfile_lock_section(group)&.[](normalised_name(dep_name))
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(T::Hash[String, T.untyped]) }
172
- def parsed_pipfile
173
- @parsed_pipfile ||= T.let(TomlRB.parse(T.must(pipfile).content), T.nilable(T::Hash[String, T.untyped]))
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(T::Hash[String, T.untyped]) }
179
- def parsed_pipfile_lock
180
- @parsed_pipfile_lock ||= T.let(
181
- JSON.parse(T.must(T.must(pipfile_lock).content)),
182
- T.nilable(T::Hash[String, T.untyped])
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: strict
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["markers"].include?("<")
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["version"] == "0.0.1+dependabot"
53
+ next if dep.version == "0.0.1+dependabot"
43
54
 
44
55
  dependencies <<
45
56
  Dependency.new(
46
- name: normalise(dep["name"]),
47
- version: dep["version"]&.include?("*") ? nil : dep["version"],
57
+ name: normalise(dep.name),
58
+ version: dep.version&.include?("*") ? nil : dep.version,
48
59
  requirements: [{
49
- requirement: dep["requirement"],
50
- file: Pathname.new(dep["file"]).cleanpath.to_path,
60
+ requirement: dep.requirement,
61
+ file: Pathname.new(dep.file).cleanpath.to_path,
51
62
  source: nil,
52
- groups: [dep["requirement_type"]]
63
+ groups: [dep.requirement_type]
53
64
  }],
54
65
  package_manager: "pip",
55
- metadata: extras_metadata(dep["extras"])
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.untyped) }
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
- return [] unless setup_file
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 { returns(T.nilable(T.any(T::Hash[String, T.untyped], String, T::Array[T::Hash[String, T.untyped]]))) }
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 { params(requirements: T.untyped).returns(T.untyped) }
129
- def check_requirements(requirements)
130
- requirements&.each do |dep|
131
- next unless dep["requirement"]
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
- Python::Requirement.new(dep["requirement"].split(","))
134
- rescue Gem::Requirement::BadRequirementError => e
135
- raise Dependabot::DependencyFileNotEvaluatable, e.message
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 = setup_file.content.match(regex))
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 = setup_file.content.match(regex))
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.untyped) }
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"