dependabot-core 0.78.0 → 0.79.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/helpers/npm/lib/updater.js +11 -5
  4. data/helpers/npm/package.json +2 -2
  5. data/helpers/npm/yarn.lock +26 -28
  6. data/helpers/yarn/lib/replace-lockfile-declaration.js +15 -3
  7. data/helpers/yarn/lib/updater.js +17 -5
  8. data/helpers/yarn/package.json +2 -2
  9. data/helpers/yarn/yarn.lock +24 -31
  10. data/lib/dependabot/file_fetchers.rb +0 -2
  11. data/lib/dependabot/file_parsers.rb +0 -2
  12. data/lib/dependabot/file_updaters.rb +0 -2
  13. data/lib/dependabot/metadata_finders.rb +0 -2
  14. data/lib/dependabot/update_checkers.rb +0 -2
  15. data/lib/dependabot/utils.rb +0 -4
  16. data/lib/dependabot/version.rb +1 -1
  17. metadata +3 -34
  18. data/helpers/python/lib/__init__.py +0 -0
  19. data/helpers/python/lib/hasher.py +0 -23
  20. data/helpers/python/lib/parser.py +0 -130
  21. data/helpers/python/requirements.txt +0 -9
  22. data/helpers/python/run.py +0 -18
  23. data/lib/dependabot/file_fetchers/python/pip.rb +0 -305
  24. data/lib/dependabot/file_parsers/python/pip.rb +0 -223
  25. data/lib/dependabot/file_parsers/python/pip/pipfile_files_parser.rb +0 -154
  26. data/lib/dependabot/file_parsers/python/pip/poetry_files_parser.rb +0 -141
  27. data/lib/dependabot/file_parsers/python/pip/setup_file_parser.rb +0 -164
  28. data/lib/dependabot/file_updaters/python/pip.rb +0 -147
  29. data/lib/dependabot/file_updaters/python/pip/pip_compile_file_updater.rb +0 -363
  30. data/lib/dependabot/file_updaters/python/pip/pipfile_file_updater.rb +0 -397
  31. data/lib/dependabot/file_updaters/python/pip/pipfile_preparer.rb +0 -125
  32. data/lib/dependabot/file_updaters/python/pip/poetry_file_updater.rb +0 -289
  33. data/lib/dependabot/file_updaters/python/pip/pyproject_preparer.rb +0 -105
  34. data/lib/dependabot/file_updaters/python/pip/requirement_file_updater.rb +0 -166
  35. data/lib/dependabot/file_updaters/python/pip/requirement_replacer.rb +0 -95
  36. data/lib/dependabot/file_updaters/python/pip/setup_file_sanitizer.rb +0 -91
  37. data/lib/dependabot/file_updaters/ruby/.DS_Store +0 -0
  38. data/lib/dependabot/metadata_finders/python/pip.rb +0 -120
  39. data/lib/dependabot/update_checkers/python/pip.rb +0 -227
  40. data/lib/dependabot/update_checkers/python/pip/latest_version_finder.rb +0 -252
  41. data/lib/dependabot/update_checkers/python/pip/pip_compile_version_resolver.rb +0 -380
  42. data/lib/dependabot/update_checkers/python/pip/pipfile_version_resolver.rb +0 -559
  43. data/lib/dependabot/update_checkers/python/pip/poetry_version_resolver.rb +0 -300
  44. data/lib/dependabot/update_checkers/python/pip/requirements_updater.rb +0 -367
  45. data/lib/dependabot/utils/python/requirement.rb +0 -130
  46. data/lib/dependabot/utils/python/version.rb +0 -88
  47. data/lib/python_requirement_parser.rb +0 -33
  48. data/lib/python_versions.rb +0 -21
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "python_requirement_parser"
4
- require "dependabot/file_updaters/python/pip"
5
- require "dependabot/shared_helpers"
6
-
7
- module Dependabot
8
- module FileUpdaters
9
- module Python
10
- class Pip
11
- class RequirementReplacer
12
- attr_reader :content, :dependency_name, :old_requirement,
13
- :new_requirement
14
-
15
- def initialize(content:, dependency_name:, old_requirement:,
16
- new_requirement:)
17
- @content = content
18
- @dependency_name = dependency_name
19
- @old_requirement = old_requirement
20
- @new_requirement = new_requirement
21
- end
22
-
23
- def updated_content
24
- updated_content =
25
- content.gsub(original_declaration_replacement_regex) do |mtch|
26
- # If the "declaration" is setting an option (e.g., no-binary)
27
- # ignore it, since it isn't actually a declaration
28
- next mtch if Regexp.last_match.pre_match.match?(/--.*\z/)
29
-
30
- updated_dependency_declaration_string(
31
- old_requirement,
32
- new_requirement
33
- )
34
- end
35
-
36
- raise "Expected content to change!" if content == updated_content
37
-
38
- updated_content
39
- end
40
-
41
- private
42
-
43
- def original_dependency_declaration_string(old_req)
44
- matches = []
45
-
46
- dec =
47
- if old_req.nil?
48
- regex = PythonRequirementParser::INSTALL_REQ_WITHOUT_REQUIREMENT
49
- content.scan(regex) { matches << Regexp.last_match }
50
- matches.find { |m| normalise(m[:name]) == dependency_name }
51
- else
52
- regex = PythonRequirementParser::INSTALL_REQ_WITH_REQUIREMENT
53
- content.scan(regex) { matches << Regexp.last_match }
54
- matches.
55
- select { |m| normalise(m[:name]) == dependency_name }.
56
- find { |m| requirements_match(m[:requirements], old_req) }
57
- end
58
-
59
- raise "Declaration not found for #{dependency_name}!" unless dec
60
-
61
- dec.to_s.strip
62
- end
63
-
64
- def updated_dependency_declaration_string(old_req, new_req)
65
- if old_req
66
- original_dependency_declaration_string(old_req).
67
- sub(PythonRequirementParser::REQUIREMENTS, new_req)
68
- else
69
- original_dependency_declaration_string(old_req).
70
- sub(PythonRequirementParser::NAME_WITH_EXTRAS) do |nm|
71
- nm + new_req
72
- end
73
- end
74
- end
75
-
76
- def original_declaration_replacement_regex
77
- original_string =
78
- original_dependency_declaration_string(old_requirement)
79
- /(?<![\-\w\.])#{Regexp.escape(original_string)}(?![\-\w\.])/
80
- end
81
-
82
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
83
- def normalise(name)
84
- name.downcase.gsub(/[-_.]+/, "-")
85
- end
86
-
87
- def requirements_match(req1, req2)
88
- req1&.split(",")&.map { |r| r.gsub(/\s/, "") }&.sort ==
89
- req2&.split(",")&.map { |r| r.gsub(/\s/, "") }&.sort
90
- end
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dependabot/file_updaters/python/pip"
4
- require "dependabot/file_parsers/python/pip/setup_file_parser"
5
-
6
- module Dependabot
7
- module FileUpdaters
8
- module Python
9
- class Pip
10
- # Take a setup.py, parses it (carefully!) and then create a new, clean
11
- # setup.py using only the information which will appear in the lockfile.
12
- class SetupFileSanitizer
13
- def initialize(setup_file:, setup_cfg:)
14
- @setup_file = setup_file
15
- @setup_cfg = setup_cfg
16
- end
17
-
18
- def sanitized_content
19
- # The part of the setup.py that Pipenv cares about appears to be the
20
- # install_requires. A name and version are required by don't end up
21
- # in the lockfile.
22
- content =
23
- "from setuptools import setup\n\n"\
24
- "setup(name=\"sanitized-package\",version=\"0.0.1\","\
25
- "install_requires=#{install_requires_array.to_json},"\
26
- "extras_require=#{extras_require_hash.to_json}"
27
-
28
- content += ',setup_requires=["pbr"],pbr=True' if include_pbr?
29
- content + ")"
30
- end
31
-
32
- private
33
-
34
- attr_reader :setup_file, :setup_cfg
35
-
36
- def include_pbr?
37
- setup_requires_array.any? { |d| d.start_with?("pbr") }
38
- end
39
-
40
- def install_requires_array
41
- @install_requires_array ||=
42
- parsed_setup_file.dependencies.map do |dep|
43
- next unless dep.requirements.first[:groups].
44
- include?("install_requires")
45
-
46
- dep.name + dep.requirements.first[:requirement].to_s
47
- end.compact
48
- end
49
-
50
- def setup_requires_array
51
- @setup_requires_array ||=
52
- parsed_setup_file.dependencies.map do |dep|
53
- next unless dep.requirements.first[:groups].
54
- include?("setup_requires")
55
-
56
- dep.name + dep.requirements.first[:requirement].to_s
57
- end.compact
58
- end
59
-
60
- def extras_require_hash
61
- @extras_require_hash ||=
62
- begin
63
- hash = {}
64
- parsed_setup_file.dependencies.each do |dep|
65
- dep.requirements.first[:groups].each do |group|
66
- next unless group.start_with?("extras_require:")
67
-
68
- hash[group.split(":").last] ||= []
69
- hash[group.split(":").last] <<
70
- dep.name + dep.requirements.first[:requirement].to_s
71
- end
72
- end
73
-
74
- hash
75
- end
76
- end
77
-
78
- def parsed_setup_file
79
- @parsed_setup_file ||=
80
- FileParsers::Python::Pip::SetupFileParser.new(
81
- dependency_files: [
82
- setup_file&.dup&.tap { |f| f.name = "setup.py" },
83
- setup_cfg&.dup&.tap { |f| f.name = "setup.cfg" }
84
- ].compact
85
- ).dependency_set
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,120 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "excon"
4
- require "dependabot/metadata_finders/base"
5
- require "dependabot/shared_helpers"
6
-
7
- module Dependabot
8
- module MetadataFinders
9
- module Python
10
- class Pip < Dependabot::MetadataFinders::Base
11
- MAIN_PYPI_URL = "https://pypi.org/pypi"
12
-
13
- def homepage_url
14
- pypi_listing.dig("info", "home_page") || super
15
- end
16
-
17
- private
18
-
19
- def look_up_source
20
- potential_source_urls = [
21
- pypi_listing.dig("info", "home_page"),
22
- pypi_listing.dig("info", "bugtrack_url"),
23
- pypi_listing.dig("info", "download_url"),
24
- pypi_listing.dig("info", "docs_url")
25
- ].compact
26
-
27
- source_url = potential_source_urls.find { |url| Source.from_url(url) }
28
- source_url ||= source_from_description
29
- source_url ||= source_from_homepage
30
-
31
- Source.from_url(source_url)
32
- end
33
-
34
- def source_from_description
35
- github_urls = []
36
- desc = pypi_listing.dig("info", "description")
37
- return unless desc
38
-
39
- desc.scan(Source::SOURCE_REGEX) do
40
- github_urls << Regexp.last_match.to_s
41
- end
42
-
43
- github_urls.find do |url|
44
- repo = Source.from_url(url).repo
45
- repo.downcase.end_with?(dependency.name)
46
- end
47
- end
48
-
49
- def source_from_homepage
50
- return unless homepage_body
51
-
52
- github_urls = []
53
- homepage_body.scan(Source::SOURCE_REGEX) do
54
- github_urls << Regexp.last_match.to_s
55
- end
56
-
57
- github_urls.find do |url|
58
- repo = Source.from_url(url).repo
59
- repo.downcase.end_with?(dependency.name)
60
- end
61
- end
62
-
63
- def homepage_body
64
- homepage_url = pypi_listing.dig("info", "home_page")
65
-
66
- return unless homepage_url
67
- return if homepage_url.include?("pypi.python.org")
68
- return if homepage_url.include?("pypi.org")
69
-
70
- @homepage_response ||=
71
- begin
72
- Excon.get(
73
- homepage_url,
74
- idempotent: true,
75
- **SharedHelpers.excon_defaults
76
- )
77
- rescue Excon::Error::Timeout, Excon::Error::Socket, ArgumentError
78
- nil
79
- end
80
-
81
- return unless @homepage_response&.status == 200
82
-
83
- @homepage_response.body
84
- end
85
-
86
- def pypi_listing
87
- return @pypi_listing unless @pypi_listing.nil?
88
- return @pypi_listing = {} if dependency.version.include?("+")
89
-
90
- possible_listing_urls.each do |url|
91
- response = Excon.get(
92
- url,
93
- idempotent: true,
94
- **SharedHelpers.excon_defaults
95
- )
96
- next unless response.status == 200
97
-
98
- @pypi_listing = JSON.parse(response.body)
99
- return @pypi_listing
100
- rescue JSON::ParserError
101
- next
102
- end
103
-
104
- @pypi_listing = {} # No listing found
105
- end
106
-
107
- def possible_listing_urls
108
- credential_urls =
109
- credentials.
110
- select { |cred| cred["type"] == "python_index" }.
111
- map { |cred| cred["index-url"].gsub(%r{/$}, "") }
112
-
113
- (credential_urls + [MAIN_PYPI_URL]).map do |base_url|
114
- base_url + "/#{dependency.name}/json"
115
- end
116
- end
117
- end
118
- end
119
- end
120
- end
@@ -1,227 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "excon"
4
- require "toml-rb"
5
-
6
- require "python_requirement_parser"
7
- require "dependabot/update_checkers/base"
8
- require "dependabot/shared_helpers"
9
- require "dependabot/utils/python/requirement"
10
-
11
- module Dependabot
12
- module UpdateCheckers
13
- module Python
14
- class Pip < Dependabot::UpdateCheckers::Base
15
- require_relative "pip/poetry_version_resolver"
16
- require_relative "pip/pipfile_version_resolver"
17
- require_relative "pip/pip_compile_version_resolver"
18
- require_relative "pip/requirements_updater"
19
- require_relative "pip/latest_version_finder"
20
-
21
- MAIN_PYPI_INDEXES = %w(
22
- https://pypi.python.org/simple/
23
- https://pypi.org/simple/
24
- ).freeze
25
-
26
- def latest_version
27
- @latest_version ||= fetch_latest_version
28
- end
29
-
30
- def latest_resolvable_version
31
- @latest_resolvable_version ||=
32
- case resolver_type
33
- when :pipfile
34
- PipfileVersionResolver.new(
35
- resolver_args.merge(unlock_requirement: true)
36
- ).latest_resolvable_version
37
- when :poetry
38
- PoetryVersionResolver.new(
39
- resolver_args.merge(unlock_requirement: true)
40
- ).latest_resolvable_version
41
- when :pip_compile
42
- PipCompileVersionResolver.new(
43
- resolver_args.merge(unlock_requirement: true)
44
- ).latest_resolvable_version
45
- when :requirements
46
- # pip doesn't (yet) do any dependency resolution, so if we don't
47
- # have a Pipfile or a pip-compile file, we just return the latest
48
- # version.
49
- latest_version
50
- else raise "Unexpected resolver type #{resolver_type}"
51
- end
52
- end
53
-
54
- def latest_resolvable_version_with_no_unlock
55
- @latest_resolvable_version_with_no_unlock ||=
56
- case resolver_type
57
- when :pipfile
58
- PipfileVersionResolver.new(
59
- resolver_args.merge(unlock_requirement: false)
60
- ).latest_resolvable_version
61
- when :poetry
62
- PoetryVersionResolver.new(
63
- resolver_args.merge(unlock_requirement: false)
64
- ).latest_resolvable_version
65
- when :pip_compile
66
- PipCompileVersionResolver.new(
67
- resolver_args.merge(unlock_requirement: false)
68
- ).latest_resolvable_version
69
- when :requirements
70
- latest_pip_version_with_no_unlock
71
- else raise "Unexpected resolver type #{resolver_type}"
72
- end
73
- end
74
-
75
- def updated_requirements
76
- RequirementsUpdater.new(
77
- requirements: dependency.requirements,
78
- latest_version: latest_version&.to_s,
79
- latest_resolvable_version: latest_resolvable_version&.to_s,
80
- update_strategy: requirements_update_strategy,
81
- has_lockfile: !(pipfile_lock || poetry_lock || pyproject_lock).nil?
82
- ).updated_requirements
83
- end
84
-
85
- def requirements_update_strategy
86
- # If passed in as an option (in the base class) honour that option
87
- if @requirements_update_strategy
88
- return @requirements_update_strategy.to_sym
89
- end
90
-
91
- # Otherwise, check if this is a poetry library or not
92
- poetry_library? ? :widen_ranges : :bump_versions
93
- end
94
-
95
- private
96
-
97
- def latest_version_resolvable_with_full_unlock?
98
- # Full unlock checks aren't implemented for pip because they're not
99
- # relevant (pip doesn't have a resolver). This method always returns
100
- # false to ensure `updated_dependencies_after_full_unlock` is never
101
- # called.
102
- false
103
- end
104
-
105
- def updated_dependencies_after_full_unlock
106
- raise NotImplementedError
107
- end
108
-
109
- # rubocop:disable Metrics/PerceivedComplexity
110
- def resolver_type
111
- reqs = dependency.requirements
112
- req_files = reqs.map { |r| r.fetch(:file) }
113
-
114
- # If there are no requirements then this is a sub-dependency. It
115
- # must come from one of Pipenv, Poetry or pip-tools, and can't come
116
- # from the first two unless they have a lockfile.
117
- return subdependency_resolver if reqs.none?
118
-
119
- # Otherwise, this is a top-level dependency, and we can figure out
120
- # which resolver to use based on the filename of its requirements
121
- return :pipfile if req_files.any? { |f| f == "Pipfile" }
122
- return :poetry if req_files.any? { |f| f == "pyproject.toml" }
123
- return :pip_compile if req_files.any? { |f| f.end_with?(".in") }
124
-
125
- if dependency.version && !exact_requirement?(reqs)
126
- subdependency_resolver
127
- else
128
- :requirements
129
- end
130
- end
131
- # rubocop:enable Metrics/PerceivedComplexity
132
-
133
- def subdependency_resolver
134
- return :pipfile if pipfile_lock
135
- return :poetry if poetry_lock || pyproject_lock
136
- return :pip_compile if pip_compile_files.any?
137
-
138
- raise "Claimed to be a sub-dependency, but no lockfile exists!"
139
- end
140
-
141
- def exact_requirement?(reqs)
142
- reqs = reqs.map { |r| r.fetch(:requirement) }
143
- reqs = reqs.compact
144
- reqs = reqs.flat_map { |r| r.split(",").map(&:strip) }
145
- reqs.any? { |r| Utils::Python::Requirement.new(r).exact? }
146
- end
147
-
148
- def resolver_args
149
- {
150
- dependency: dependency,
151
- dependency_files: dependency_files,
152
- credentials: credentials,
153
- latest_allowable_version: latest_version
154
- }
155
- end
156
-
157
- def fetch_latest_version
158
- latest_version_finder.latest_version
159
- end
160
-
161
- def latest_pip_version_with_no_unlock
162
- latest_version_finder.latest_version_with_no_unlock
163
- end
164
-
165
- def latest_version_finder
166
- @latest_version_finder ||= LatestVersionFinder.new(
167
- dependency: dependency,
168
- dependency_files: dependency_files,
169
- credentials: credentials,
170
- ignored_versions: ignored_versions
171
- )
172
- end
173
-
174
- def poetry_library?
175
- return false unless pyproject
176
-
177
- # Hit PyPi and check whether there are details for a library with a
178
- # matching name and description
179
- details = TomlRB.parse(pyproject.content).dig("tool", "poetry")
180
- return false unless details
181
-
182
- index_response = Excon.get(
183
- "https://pypi.org/pypi/#{normalised_name(details['name'])}/json",
184
- idempotent: true,
185
- **SharedHelpers.excon_defaults
186
- )
187
-
188
- return false unless index_response.status == 200
189
-
190
- pypi_info = JSON.parse(index_response.body)["info"] || {}
191
- pypi_info["summary"] == details["description"]
192
- rescue URI::InvalidURIError
193
- false
194
- end
195
-
196
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
197
- def normalised_name(name)
198
- name.downcase.gsub(/[-_.]+/, "-")
199
- end
200
-
201
- def pipfile
202
- dependency_files.find { |f| f.name == "Pipfile" }
203
- end
204
-
205
- def pipfile_lock
206
- dependency_files.find { |f| f.name == "Pipfile.lock" }
207
- end
208
-
209
- def pyproject
210
- dependency_files.find { |f| f.name == "pyproject.toml" }
211
- end
212
-
213
- def pyproject_lock
214
- dependency_files.find { |f| f.name == "pyproject.lock" }
215
- end
216
-
217
- def poetry_lock
218
- dependency_files.find { |f| f.name == "poetry.lock" }
219
- end
220
-
221
- def pip_compile_files
222
- dependency_files.select { |f| f.name.end_with?(".in") }
223
- end
224
- end
225
- end
226
- end
227
- end