Skip to content

Commit 7d9123b

Browse files
Merge pull request #5932 from dependabot/deivid-rodriguez/python-crash
Fix crash when updating Python libraries with multiple manifest types
2 parents 0ff6f2e + 7d91b71 commit 7d9123b

File tree

2 files changed

+79
-48
lines changed

2 files changed

+79
-48
lines changed

python/lib/dependabot/python/update_checker.rb

+33-16
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def lowest_resolvable_security_fix_version
8989

9090
def updated_requirements
9191
RequirementsUpdater.new(
92-
requirements: dependency.requirements,
92+
requirements: requirements,
9393
latest_resolvable_version: preferred_resolvable_version&.to_s,
9494
update_strategy: requirements_update_strategy,
9595
has_lockfile: !(pipfile_lock || poetry_lock || pyproject_lock).nil?
@@ -133,8 +133,7 @@ def fetch_lowest_resolvable_security_fix_version
133133
end
134134

135135
def resolver_type
136-
reqs = dependency.requirements
137-
req_files = reqs.map { |r| r.fetch(:file) }
136+
reqs = requirements
138137

139138
# If there are no requirements then this is a sub-dependency. It
140139
# must come from one of Pipenv, Poetry or pip-tools, and can't come
@@ -143,9 +142,9 @@ def resolver_type
143142

144143
# Otherwise, this is a top-level dependency, and we can figure out
145144
# which resolver to use based on the filename of its requirements
146-
return :pipenv if req_files.any?("Pipfile")
147-
return :poetry if req_files.any?("pyproject.toml")
148-
return :pip_compile if req_files.any? { |f| f.end_with?(".in") }
145+
return :pipenv if updating_pipfile?
146+
return :poetry if updating_pyproject?
147+
return :pip_compile if updating_in_file?
149148

150149
if dependency.version && !exact_requirement?(reqs)
151150
subdependency_resolver
@@ -202,16 +201,14 @@ def resolver_args
202201
end
203202

204203
def current_requirement_string
205-
reqs = dependency.requirements
204+
reqs = requirements
206205
return if reqs.none?
207206

208-
requirement =
209-
case resolver_type
210-
when :pipenv then reqs.find { |r| r[:file] == "Pipfile" }
211-
when :poetry then reqs.find { |r| r[:file] == "pyproject.toml" }
212-
when :pip_compile then reqs.find { |r| r[:file].end_with?(".in") }
213-
when :requirements then reqs.find { |r| r[:file].end_with?(".txt") }
214-
end
207+
requirement = reqs.find do |r|
208+
file = r[:file]
209+
210+
file == "Pipfile" || file == "pyproject.toml" || file.end_with?(".in") || file.end_with?(".txt")
211+
end
215212

216213
requirement&.fetch(:requirement)
217214
end
@@ -236,7 +233,7 @@ def updated_version_req_lower_bound
236233
return ">= #{dependency.version}" if dependency.version
237234

238235
version_for_requirement =
239-
dependency.requirements.filter_map { |r| r[:requirement] }.
236+
requirements.filter_map { |r| r[:requirement] }.
240237
reject { |req_string| req_string.start_with?("<") }.
241238
select { |req_string| req_string.match?(VERSION_REGEX) }.
242239
map { |req_string| req_string.match(VERSION_REGEX) }.
@@ -262,7 +259,7 @@ def latest_version_finder
262259
end
263260

264261
def poetry_library?
265-
return false unless pyproject
262+
return false unless updating_pyproject?
266263

267264
# Hit PyPi and check whether there are details for a library with a
268265
# matching name and description
@@ -283,6 +280,26 @@ def poetry_library?
283280
false
284281
end
285282

283+
def updating_pipfile?
284+
requirement_files.any?("Pipfile")
285+
end
286+
287+
def updating_pyproject?
288+
requirement_files.any?("pyproject.toml")
289+
end
290+
291+
def updating_in_file?
292+
requirement_files.any? { |f| f.end_with?(".in") }
293+
end
294+
295+
def requirement_files
296+
requirements.map { |r| r.fetch(:file) }
297+
end
298+
299+
def requirements
300+
dependency.requirements
301+
end
302+
286303
def normalised_name(name)
287304
NameNormaliser.normalise(name)
288305
end

python/spec/dependabot/python/update_checker_spec.rb

+46-32
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
)
5858
end
5959
let(:requirements_fixture_name) { "version_specified.txt" }
60-
let(:dependency) do
60+
let(:requirements_dependency) do
6161
Dependabot::Dependency.new(
6262
name: dependency_name,
6363
version: dependency_version,
@@ -76,6 +76,8 @@
7676
}]
7777
end
7878

79+
let(:dependency) { requirements_dependency }
80+
7981
describe "#can_update?" do
8082
subject { checker.can_update?(requirements_to_unlock: :own) }
8183

@@ -507,44 +509,56 @@
507509
let(:dependency_files) { [requirements_file, pyproject] }
508510
let(:pyproject_fixture_name) { "caret_version.toml" }
509511

510-
let(:dependency) do
511-
Dependabot::Dependency.new(
512-
name: "requests",
513-
version: "1.2.3",
514-
requirements: [{
515-
file: "pyproject.toml",
516-
requirement: "^1.0.0",
517-
groups: [],
518-
source: nil
519-
}],
520-
package_manager: "pip"
521-
)
522-
end
512+
context "and updating a dependency inside" do
513+
let(:dependency) do
514+
Dependabot::Dependency.new(
515+
name: "requests",
516+
version: "1.2.3",
517+
requirements: [{
518+
file: "pyproject.toml",
519+
requirement: "^1.0.0",
520+
groups: [],
521+
source: nil
522+
}],
523+
package_manager: "pip"
524+
)
525+
end
523526

524-
let(:pypi_url) { "https://pypi.org/simple/requests/" }
525-
let(:pypi_response) do
526-
fixture("pypi", "pypi_simple_response_requests.html")
527-
end
527+
let(:pypi_url) { "https://pypi.org/simple/requests/" }
528+
let(:pypi_response) do
529+
fixture("pypi", "pypi_simple_response_requests.html")
530+
end
528531

529-
context "for a library" do
530-
before do
531-
stub_request(:get, "https://pypi.org/pypi/pendulum/json/").
532-
to_return(
533-
status: 200,
534-
body: fixture("pypi", "pypi_response_pendulum.json")
535-
)
532+
context "for a library" do
533+
before do
534+
stub_request(:get, "https://pypi.org/pypi/pendulum/json/").
535+
to_return(
536+
status: 200,
537+
body: fixture("pypi", "pypi_response_pendulum.json")
538+
)
539+
end
540+
541+
its([:requirement]) { is_expected.to eq(">=1,<3") }
536542
end
537543

538-
its([:requirement]) { is_expected.to eq(">=1,<3") }
539-
end
544+
context "for a non-library" do
545+
before do
546+
stub_request(:get, "https://pypi.org/pypi/pendulum/json/").
547+
to_return(status: 404)
548+
end
540549

541-
context "for a non-library" do
542-
before do
543-
stub_request(:get, "https://pypi.org/pypi/pendulum/json/").
544-
to_return(status: 404)
550+
its([:requirement]) { is_expected.to eq("^2.19.1") }
545551
end
552+
end
553+
554+
context "and updating a dependency in an additional requirements file" do
555+
let(:dependency_files) { super().append(requirements_file) }
546556

547-
its([:requirement]) { is_expected.to eq("^2.19.1") }
557+
let(:dependency) { requirements_dependency }
558+
559+
it "does not get affected by whether it's a library or not and updates using the :increase strategy" do
560+
expect(subject[:requirement]).to eq("==2.6.0")
561+
end
548562
end
549563
end
550564

0 commit comments

Comments
 (0)