Skip to content

Commit ed4fb5a

Browse files
committed
Remove freezing dependencies in pyproject.toml
1 parent 5f40373 commit ed4fb5a

File tree

6 files changed

+439
-321
lines changed

6 files changed

+439
-321
lines changed

bin/dry-run.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ def security_fix?(dependency)
853853

854854
# rubocop:enable Style/GlobalVars
855855
rescue StandardError => e
856-
puts "An error occurred: #{e.message}"
856+
puts "An error occurred: #{e.class}, #{e.message}"
857857
exit 1
858858
end
859859

uv/lib/dependabot/uv/file_updater/lock_file_updater.rb

-7
Original file line numberDiff line numberDiff line change
@@ -155,17 +155,10 @@ def prepared_pyproject
155155
begin
156156
content = updated_pyproject_content
157157
content = sanitize(content)
158-
content = freeze_other_dependencies(content)
159158
content
160159
end
161160
end
162161

163-
def freeze_other_dependencies(pyproject_content)
164-
PyprojectPreparer
165-
.new(pyproject_content: pyproject_content, lockfile: lockfile)
166-
.freeze_top_level_dependencies_except(dependencies)
167-
end
168-
169162
def sanitize(pyproject_content)
170163
PyprojectPreparer
171164
.new(pyproject_content: pyproject_content)

uv/lib/dependabot/uv/file_updater/pyproject_preparer.rb

-125
Original file line numberDiff line numberDiff line change
@@ -21,54 +21,6 @@ def initialize(pyproject_content:, lockfile: nil)
2121
@lines = pyproject_content.split("\n")
2222
end
2323

24-
def freeze_top_level_dependencies_except(dependencies_to_update)
25-
return @pyproject_content unless lockfile
26-
27-
deps_to_update_names = dependencies_to_update.map(&:name).map { |n| Uv::FileParser.normalize_dependency_name(n) }
28-
locked_deps = parsed_lockfile_dependencies || {}
29-
in_dependencies = false
30-
in_dependencies_array = false
31-
32-
updated_lines = @lines.map do |line|
33-
if line.match?(/^\[project\]/)
34-
in_dependencies = true
35-
in_dependencies_array = false
36-
line
37-
elsif line.match?(/^dependencies\s*=\s*\[/)
38-
in_dependencies_array = true
39-
line
40-
elsif in_dependencies && in_dependencies_array && line.strip.start_with?('"')
41-
# Extract the full dependency string without quotes and trailing comma
42-
dep_string = line.strip.gsub(/^"|"(?:,\s*)?$/, '')
43-
parsed = parse_dependency(dep_string)
44-
45-
if parsed[:name]
46-
normalized_name = Uv::FileParser.normalize_dependency_name(parsed[:name])
47-
48-
if deps_to_update_names.include?(normalized_name)
49-
line
50-
else
51-
version = locked_version_for_dep(locked_deps, normalized_name)
52-
if version
53-
prefix = " " * line[/^\s*/].length
54-
suffix = line.end_with?(",") ? "," : ""
55-
dep_str = parsed[:extras] ? "#{parsed[:name]}[#{parsed[:extras]}]" : parsed[:name]
56-
%Q(#{prefix}"#{dep_str}==#{version}"#{suffix})
57-
else
58-
line
59-
end
60-
end
61-
else
62-
line
63-
end
64-
else
65-
line
66-
end
67-
end
68-
69-
@pyproject_content = updated_lines.join("\n")
70-
end
71-
7224
def update_python_requirement(python_version)
7325
return @pyproject_content unless python_version
7426

@@ -115,86 +67,9 @@ def sanitize
11567

11668
attr_reader :lockfile
11769

118-
def parsed_lockfile
119-
@parsed_lockfile ||= lockfile ? parse_lockfile(lockfile.content) : {}
120-
end
121-
122-
def parse_lockfile(content)
123-
TomlRB.parse(content)
124-
rescue TomlRB::ParseError
125-
{} # Return empty hash if parsing fails
126-
end
127-
128-
def parsed_lockfile_dependencies
129-
return {} unless lockfile
130-
131-
deps = {}
132-
parsed = parsed_lockfile
133-
134-
# Handle UV lock format (version 1)
135-
if parsed["version"] == 1 && parsed["package"].is_a?(Array)
136-
parsed["package"].each do |pkg|
137-
next unless pkg["name"] && pkg["version"]
138-
139-
deps[pkg["name"]] = { "version" => pkg["version"] }
140-
end
141-
# Handle traditional Poetry-style lock format
142-
elsif parsed["dependencies"]
143-
deps = parsed["dependencies"]
144-
end
145-
146-
deps
147-
end
148-
149-
def locked_version_for_dep(locked_deps, dep_name)
150-
locked_deps.each do |name, details|
151-
next unless Uv::FileParser.normalize_dependency_name(name) == dep_name
152-
return details["version"] if details.is_a?(Hash) && details["version"]
153-
end
154-
nil
155-
end
156-
15770
def sanitize_env_name(url)
15871
url.gsub(%r{^https?://}, "").gsub(/[^a-zA-Z0-9]/, "_").upcase
15972
end
160-
161-
def freeze_dependency(dep_string, deps_to_update_names, locked_deps)
162-
dep_match = dep_string.match(/^([^\[\]=<>!]+)(?:\[([^\]]+)\])?/)
163-
return dep_string unless dep_match
164-
165-
dep_name = dep_match[1].strip
166-
dep_extra = dep_match[2]
167-
168-
normalized_name = Uv::FileParser.normalize_dependency_name(dep_name)
169-
170-
return dep_string if deps_to_update_names.include?(normalized_name)
171-
172-
version = locked_version_for_dep(locked_deps, normalized_name)
173-
return dep_string unless version
174-
175-
dep_extra ? "#{dep_name}[#{dep_extra}]==#{version}" : "#{dep_name}==#{version}"
176-
end
177-
178-
def parse_dependency(dep_string)
179-
# Split by common version operators
180-
parts = dep_string.split(/(?=[<>=~!])/)
181-
name_part = parts.first.strip
182-
version_part = parts[1..]&.join&.strip
183-
184-
# Handle extras in name
185-
if name_part.include?("[")
186-
name, extras = name_part.split("[", 2)
187-
extras = extras.chomp("]")
188-
else
189-
name = name_part
190-
end
191-
192-
{
193-
name: name.strip,
194-
extras: extras,
195-
version_spec: version_part
196-
}
197-
end
19873
end
19974
end
20075
end

uv/spec/dependabot/uv/file_updater/lock_file_updater_spec.rb

-4
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@
120120
allow(Dependabot::Uv::FileUpdater::PyprojectPreparer).to receive(:new)
121121
.and_return(pyproject_preparer)
122122

123-
allow(pyproject_preparer).to receive(:freeze_top_level_dependencies_except)
124-
.with(dependencies)
125-
.and_return("frozen content")
126-
127123
allow(pyproject_preparer).to receive_messages(
128124
update_python_requirement: "python requirement updated content",
129125
sanitize: "sanitized content"

uv/spec/dependabot/uv/file_updater/pyproject_preparer_spec.rb

-184
Original file line numberDiff line numberDiff line change
@@ -77,190 +77,6 @@
7777
end
7878
end
7979

80-
describe "#freeze_top_level_dependencies_except" do
81-
subject(:frozen_content) do
82-
preparer.freeze_top_level_dependencies_except([dependency])
83-
end
84-
85-
it "pins all dependencies except the updated one" do
86-
expect(frozen_content).to include("requests>=2.31.0") # The original constraint
87-
end
88-
89-
context "with multiple dependencies including one to update" do
90-
let(:pyproject_content) do
91-
<<~TOML
92-
[project]
93-
name = "sample-project"
94-
version = "0.1.0"
95-
requires-python = ">=3.7"
96-
dependencies = [
97-
"requests>=2.31.0",
98-
"certifi>=2025.1.0",
99-
]
100-
101-
[build-system]
102-
requires = ["setuptools>=42", "wheel"]
103-
build-backend = "setuptools.build_meta"
104-
TOML
105-
end
106-
107-
it "pins all dependencies except the updated one" do
108-
expect(frozen_content).to include("requests>=2.31.0") # Dependency to update, not pinned
109-
expect(frozen_content).to include("certifi==2025.1.31") # Other dependency, pinned to lock version
110-
end
111-
end
112-
113-
context "without a lockfile" do
114-
let(:lockfile) { nil }
115-
116-
it "returns the original content" do
117-
expect(frozen_content).to eq(pyproject_content)
118-
end
119-
end
120-
121-
context "with dependencies containing extras" do
122-
let(:pyproject_content) do
123-
<<~TOML
124-
[project]
125-
name = "sample-project"
126-
version = "0.1.0"
127-
requires-python = ">=3.7"
128-
dependencies = [
129-
"django>=5.1.7",
130-
"django-storages[google]>=1.14.5",
131-
"whitenoise>=6.8.2",
132-
]
133-
TOML
134-
end
135-
136-
let(:lockfile_content) do
137-
<<~TOML
138-
version = 1
139-
[[package]]
140-
name = "django"
141-
version = "5.1.7"
142-
143-
[[package]]
144-
name = "django-storages"
145-
version = "1.14.5"
146-
147-
[[package]]
148-
name = "whitenoise"
149-
version = "6.8.2"
150-
TOML
151-
end
152-
153-
let(:dependency) do
154-
Dependabot::Dependency.new(
155-
name: "django",
156-
version: "5.1.8",
157-
requirements: [{
158-
file: "pyproject.toml",
159-
requirement: ">=5.1.8",
160-
groups: [],
161-
source: nil
162-
}],
163-
previous_version: "5.1.7",
164-
previous_requirements: [{
165-
file: "pyproject.toml",
166-
requirement: ">=5.1.7",
167-
groups: [],
168-
source: nil
169-
}],
170-
package_manager: "uv"
171-
)
172-
end
173-
174-
it "pins dependencies preserving extras correctly" do
175-
expect(frozen_content).to include("django>=5.1.7") # dependency to update, not pinned
176-
expect(frozen_content).to include("django-storages[google]==1.14.5") # extra preserved and pinned
177-
expect(frozen_content).to include("whitenoise==6.8.2") # pinned without extras
178-
end
179-
end
180-
181-
context "with dependency goups with include-group" do
182-
let(:pyproject_content) do
183-
<<~TOML
184-
[project]
185-
name = "test-project"
186-
version = "1.0.0"
187-
dependencies = [
188-
"requests>=2.0.0",
189-
"click>=8.0.0"
190-
]
191-
192-
[dependency-groups]
193-
test = [
194-
"pytest>=7.0.0",
195-
{ include-group = "coverage" }
196-
]
197-
coverage = [
198-
"coverage>=7.0.0",
199-
"pytest-cov>=4.0.0"
200-
]
201-
dev = [
202-
{ include-group = "test" },
203-
"black>=23.0.0"
204-
]
205-
TOML
206-
end
207-
let(:lockfile_content) do
208-
<<~TOML
209-
version = 1
210-
[[package]]
211-
name = "requests"
212-
version = "2.0.0"
213-
214-
[[package]]
215-
name = "click"
216-
version = "8.0.0"
217-
218-
[[package]]
219-
name = "pytest"
220-
version = "7.0.0"
221-
222-
[[package]]
223-
name = "coverage"
224-
version = "7.0.0"
225-
226-
[[package]]
227-
name = "pytest-cov"
228-
version = "4.0.0"
229-
230-
[[package]]
231-
name = "black"
232-
version = "23.0.0"
233-
TOML
234-
end
235-
let(:dependency) do
236-
Dependabot::Dependency.new(
237-
name: "pytest",
238-
version: "7.0.1",
239-
requirements: [{
240-
file: "pyproject.toml",
241-
requirement: ">=7.0.1",
242-
groups: ["test"],
243-
source: nil
244-
}],
245-
previous_version: "7.0.0",
246-
previous_requirements: [{
247-
file: "pyproject.toml",
248-
requirement: ">=7.0.0",
249-
groups: ["test"],
250-
source: nil
251-
}],
252-
package_manager: "uv"
253-
)
254-
end
255-
it "pins dependencies preserving groups correctly" do
256-
expect(frozen_content).to include("pytest>=7.0.0") # dependency to update, not pinned
257-
expect(frozen_content).to include("coverage==7.0.0") # extra preserved and pinned
258-
expect(frozen_content).to include("pytest-cov==4.0.0") # extra preserved and pinned
259-
expect(frozen_content).to include("black==23.0.0") # pinned without extras
260-
end
261-
end
262-
end
263-
26480
describe "#update_python_requirement" do
26581
subject(:updated_content) { preparer.update_python_requirement("3.10") }
26682

0 commit comments

Comments
 (0)