dependabot-python 0.357.0 → 0.358.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: 4d9c1af8569ef4090792c2ebee4bd6dd19e58e47c0d6bc1918804c26adeb4dfc
4
- data.tar.gz: 5bb14421ad8c437294aba6d0d5a0bc0bca2c6dd61e8f7d998773c13f73750f5b
3
+ metadata.gz: 48c37437d99c146971cb9ecc12889faf559a657346ca44b95a71bc06d607445a
4
+ data.tar.gz: 163fbc7bfcb2aecde6cbc0f95bdc33bb1cb454201b6014158a65dcc71ab22fc4
5
5
  SHA512:
6
- metadata.gz: 9d91cabe7351ee1d2dcc0fcbe842d9cae3141b13cfbc313d683e4f2723102b9ecadb1fbf5545dc4a3de615a40eb6fcdd9669b84a2c1350df5420cd92486c38ec
7
- data.tar.gz: b4be22afa1102a99dd8e3dc4a8935d28fd839d6917e029a1db19400dfd380b0f21c3c92e97b5aedf06c9b7eb5e6c46a1a90d0550cf37c1c6b80b158b863d8d53
6
+ metadata.gz: de34134537d989cc22bdea97b581b80e0adf85cde617690689916fbc1169e462d1fdea9c9c40369401ac2edcf4cf21015d3c399c9b4ebaafaf0fa9541109f84f
7
+ data.tar.gz: bf298b6e70c2fd083f89d3186a31d610ac937fe749345974edcce57f5871f97579bd215e3d5897f1b6d82e691a7a400f287c3d97241b50249d73dab2f2b1e765
@@ -19,7 +19,8 @@ module Dependabot
19
19
  POETRY_DEPENDENCY_TYPES = %w(dependencies dev-dependencies).freeze
20
20
 
21
21
  # https://python-poetry.org/docs/dependency-specification/
22
- UNSUPPORTED_DEPENDENCY_TYPES = %w(git path url).freeze
22
+ # Git dependencies with tags are now supported for version tracking
23
+ UNSUPPORTED_DEPENDENCY_TYPES = %w(path url).freeze
23
24
 
24
25
  sig { params(dependency_files: T::Array[Dependabot::DependencyFile]).void }
25
26
  def initialize(dependency_files:)
@@ -148,13 +149,29 @@ module Dependabot
148
149
 
149
150
  # @param req can be an Array, Hash or String that represents the constraints for a dependency
150
151
  sig { params(req: T.untyped, type: String).returns(T::Array[T::Hash[Symbol, T.nilable(String)]]) }
152
+ # rubocop:disable Metrics/PerceivedComplexity
151
153
  def parse_requirements_from(req, type)
152
154
  [req].flatten.compact.filter_map do |requirement|
155
+ # Skip unsupported dependency types (path, url), but allow git
153
156
  next if requirement.is_a?(Hash) && UNSUPPORTED_DEPENDENCY_TYPES.intersect?(requirement.keys)
157
+ # Skip git dependencies without tags (e.g., with branch, rev)
158
+ next if requirement.is_a?(Hash) && requirement["git"] && !requirement["tag"]
154
159
 
155
- check_requirements(requirement)
156
-
157
- if requirement.is_a?(String)
160
+ # Handle git dependencies with tags
161
+ if requirement.is_a?(Hash) && requirement["git"] && requirement["tag"]
162
+ {
163
+ requirement: nil,
164
+ file: T.must(pyproject).name,
165
+ source: {
166
+ type: "git",
167
+ url: requirement["git"],
168
+ ref: requirement["tag"],
169
+ branch: nil
170
+ },
171
+ groups: [type]
172
+ }
173
+ elsif requirement.is_a?(String)
174
+ check_requirements(requirement)
158
175
  {
159
176
  requirement: requirement,
160
177
  file: T.must(pyproject).name,
@@ -162,6 +179,7 @@ module Dependabot
162
179
  groups: [type]
163
180
  }
164
181
  else
182
+ check_requirements(requirement)
165
183
  {
166
184
  requirement: requirement["version"],
167
185
  file: T.must(pyproject).name,
@@ -171,6 +189,7 @@ module Dependabot
171
189
  end
172
190
  end
173
191
  end
192
+ # rubocop:enable Metrics/PerceivedComplexity
174
193
 
175
194
  sig { returns(T.nilable(T::Boolean)) }
176
195
  def using_poetry?
@@ -101,16 +101,12 @@ module Dependabot
101
101
 
102
102
  sig { returns(Ecosystem::VersionManager) }
103
103
  def package_manager
104
- if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)
105
- Dependabot.logger.info("Detected package manager : #{detected_package_manager.name}")
106
- end
107
-
108
104
  @package_manager ||= T.let(detected_package_manager, T.nilable(Dependabot::Ecosystem::VersionManager))
109
105
  end
110
106
 
111
107
  sig { returns(Ecosystem::VersionManager) }
112
108
  def detected_package_manager
113
- setup_python_environment if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)
109
+ setup_python_environment
114
110
 
115
111
  return PipenvPackageManager.new(T.must(detect_pipenv_version)) if detect_pipenv_version
116
112
 
@@ -225,11 +221,6 @@ module Dependabot
225
221
 
226
222
  sig { returns(String) }
227
223
  def python_raw_version
228
- if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)
229
- Dependabot.logger.info("Detected python version: #{language_version_manager.python_version}")
230
- Dependabot.logger.info("Detected python major minor version: #{language_version_manager.python_major_minor}")
231
- end
232
-
233
224
  language_version_manager.python_version
234
225
  end
235
226
 
@@ -115,6 +115,9 @@ module Dependabot
115
115
  ).returns(String)
116
116
  end
117
117
  def replace_dep(dep, content, new_r, old_r)
118
+ # Handle Git dependencies with tags
119
+ return update_git_tag(dep, content, new_r, old_r) if git_dependency?(new_r) && git_dependency?(old_r)
120
+
118
121
  new_req = new_r[:requirement]
119
122
  old_req = old_r[:requirement]
120
123
 
@@ -149,6 +152,38 @@ module Dependabot
149
152
  content
150
153
  end
151
154
 
155
+ sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
156
+ def git_dependency?(req)
157
+ req.dig(:source, :type) == "git"
158
+ end
159
+
160
+ sig do
161
+ params(
162
+ dep: Dependabot::Dependency,
163
+ content: String,
164
+ new_r: T::Hash[Symbol, T.untyped],
165
+ old_r: T::Hash[Symbol, T.untyped]
166
+ ).returns(String)
167
+ end
168
+ def update_git_tag(dep, content, new_r, old_r)
169
+ old_tag = old_r.dig(:source, :ref)
170
+ new_tag = new_r.dig(:source, :ref)
171
+
172
+ return content if old_tag == new_tag
173
+
174
+ # Match git dependency declaration with tag
175
+ # Example: fastapi = { git = "...", extras = ["all"], tag = "0.110.0" }
176
+ git_dep_regex = /
177
+ ^(\s*)#{Regexp.escape(dep.name)}(\s*=\s*\{[^}]*tag\s*=\s*)
178
+ ["']#{Regexp.escape(old_tag)}["']([^}]*\})
179
+ /mx
180
+
181
+ content.gsub(git_dep_regex) do
182
+ match_data = T.must(Regexp.last_match)
183
+ "#{match_data[1]}#{dep.name}#{match_data[2]}\"#{new_tag}\"#{match_data[3]}"
184
+ end
185
+ end
186
+
152
187
  sig { returns(String) }
153
188
  def updated_lockfile_content
154
189
  @updated_lockfile_content ||=
@@ -199,6 +234,9 @@ module Dependabot
199
234
 
200
235
  if poetry_object
201
236
  dependencies.each do |dep|
237
+ # Skip Git dependencies - they use tags/refs, not versions
238
+ next if git_dependency_being_updated?(dep)
239
+
202
240
  if dep.requirements.find { |r| r[:file] == pyproject&.name }
203
241
  lock_declaration_to_new_version!(poetry_object, dep)
204
242
  else
@@ -240,6 +278,11 @@ module Dependabot
240
278
  poetry_object[subdep_type][dep.name] = dep.version
241
279
  end
242
280
 
281
+ sig { params(dep: Dependabot::Dependency).returns(T::Boolean) }
282
+ def git_dependency_being_updated?(dep)
283
+ dep.requirements.any? { |r| r.dig(:source, :type) == "git" }
284
+ end
285
+
243
286
  sig { params(pyproject_content: String).returns(String) }
244
287
  def sanitize(pyproject_content)
245
288
  PyprojectPreparer
@@ -389,7 +389,7 @@ module Dependabot
389
389
  version = version.release if version.prerelease?
390
390
 
391
391
  lb_segments = version.segments
392
- lb_segments.pop while lb_segments.last.zero?
392
+ lb_segments.pop while lb_segments.last&.zero?
393
393
 
394
394
  lb_segments
395
395
  end
@@ -400,7 +400,7 @@ module Dependabot
400
400
  version = req.requirements.first.last.release
401
401
 
402
402
  if req_string.strip.start_with?("^")
403
- version.segments.index { |i| i != 0 }
403
+ version.segments.index { |i| i != 0 } || (version.segments.count - 1)
404
404
  elsif req_string.include?("*")
405
405
  version.segments.count - 1
406
406
  elsif req_string.strip.start_with?("~=", "==")
@@ -17,6 +17,7 @@ require "dependabot/update_checkers/base"
17
17
 
18
18
  module Dependabot
19
19
  module Python
20
+ # rubocop:disable Metrics/ClassLength
20
21
  class UpdateChecker < Dependabot::UpdateCheckers::Base
21
22
  extend T::Sig
22
23
 
@@ -35,6 +36,8 @@ module Dependabot
35
36
 
36
37
  sig { override.returns(T.nilable(Gem::Version)) }
37
38
  def latest_version
39
+ return latest_version_for_git_dependency if git_dependency?
40
+
38
41
  @latest_version ||= T.let(
39
42
  fetch_latest_version,
40
43
  T.nilable(Gem::Version)
@@ -43,6 +46,8 @@ module Dependabot
43
46
 
44
47
  sig { override.returns(T.nilable(Gem::Version)) }
45
48
  def latest_resolvable_version
49
+ return latest_resolvable_version_for_git_dependency if git_dependency?
50
+
46
51
  @latest_resolvable_version ||= T.let(
47
52
  if resolver_type == :requirements
48
53
  resolver.latest_resolvable_version
@@ -59,6 +64,8 @@ module Dependabot
59
64
 
60
65
  sig { override.returns(T.nilable(Gem::Version)) }
61
66
  def latest_resolvable_version_with_no_unlock
67
+ return T.cast(dependency.version, T.nilable(Gem::Version)) if git_dependency? && git_commit_checker.pinned?
68
+
62
69
  @latest_resolvable_version_with_no_unlock ||= T.let(
63
70
  if resolver_type == :requirements
64
71
  resolver.latest_resolvable_version_with_no_unlock
@@ -88,6 +95,8 @@ module Dependabot
88
95
 
89
96
  sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
90
97
  def updated_requirements
98
+ return updated_git_requirements if git_dependency?
99
+
91
100
  RequirementsUpdater.new(
92
101
  requirements: requirements,
93
102
  latest_resolvable_version: preferred_resolvable_version&.to_s,
@@ -112,6 +121,64 @@ module Dependabot
112
121
 
113
122
  private
114
123
 
124
+ sig { returns(T::Boolean) }
125
+ def git_dependency?
126
+ git_commit_checker.git_dependency?
127
+ end
128
+
129
+ sig { returns(T.nilable(Gem::Version)) }
130
+ def latest_version_for_git_dependency
131
+ latest_git_version_details&.fetch(:version)
132
+ end
133
+
134
+ sig { returns(T.nilable(Gem::Version)) }
135
+ def latest_resolvable_version_for_git_dependency
136
+ # For git dependencies, we assume the latest version is resolvable
137
+ latest_version_for_git_dependency
138
+ end
139
+
140
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
141
+ def updated_git_requirements
142
+ updated_source = updated_git_source
143
+ return requirements unless updated_source
144
+
145
+ requirements.map do |req|
146
+ req.merge(source: updated_source)
147
+ end
148
+ end
149
+
150
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
151
+ def updated_git_source
152
+ # Update the git tag if a new version is available
153
+ if git_commit_checker.pinned_ref_looks_like_version? && latest_git_version_details
154
+ new_tag = T.must(latest_git_version_details).fetch(:tag)
155
+ source_details = dependency.source_details
156
+ return source_details.transform_keys(&:to_sym).merge(ref: new_tag) if source_details
157
+ end
158
+
159
+ # Otherwise return the original source
160
+ dependency.source_details&.transform_keys(&:to_sym)
161
+ end
162
+
163
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
164
+ def latest_git_version_details
165
+ @latest_git_version_details ||= T.let(
166
+ git_commit_checker.local_tag_for_latest_version,
167
+ T.nilable(T::Hash[Symbol, T.untyped])
168
+ )
169
+ end
170
+
171
+ sig { returns(Dependabot::GitCommitChecker) }
172
+ def git_commit_checker
173
+ @git_commit_checker ||= T.let(
174
+ Dependabot::GitCommitChecker.new(
175
+ dependency: dependency,
176
+ credentials: credentials
177
+ ),
178
+ T.nilable(Dependabot::GitCommitChecker)
179
+ )
180
+ end
181
+
115
182
  sig { override.returns(T::Boolean) }
116
183
  def latest_version_resolvable_with_full_unlock?
117
184
  # Full unlock checks aren't implemented for Python (yet)
@@ -135,10 +202,6 @@ module Dependabot
135
202
 
136
203
  sig { returns(T.untyped) }
137
204
  def resolver
138
- if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)
139
- Dependabot.logger.info("Python package resolver : #{resolver_type}")
140
- end
141
-
142
205
  case resolver_type
143
206
  when :pip_compile then pip_compile_version_resolver
144
207
  when :pipenv then pipenv_version_resolver
@@ -456,6 +519,7 @@ module Dependabot
456
519
  dependency_files.select { |f| f.name.end_with?(".in") }
457
520
  end
458
521
  end
522
+ # rubocop:enable Metrics/ClassLength
459
523
  end
460
524
  end
461
525
 
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.357.0
4
+ version: 0.358.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.357.0
18
+ version: 0.358.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.357.0
25
+ version: 0.358.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -291,7 +291,7 @@ licenses:
291
291
  - MIT
292
292
  metadata:
293
293
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
294
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.357.0
294
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.358.0
295
295
  rdoc_options: []
296
296
  require_paths:
297
297
  - lib