diff --git a/python/helpers/lib/parser.py b/python/helpers/lib/parser.py index f084c06b040..c2cfc8ff409 100644 --- a/python/helpers/lib/parser.py +++ b/python/helpers/lib/parser.py @@ -24,7 +24,7 @@ def parse_pep621_dependencies(pyproject_path): - project_toml = toml.load(pyproject_path)['project'] + project_toml = toml.load(pyproject_path) def parse_toml_section_pep621_dependencies(pyproject_path, dependencies): requirement_packages = [] @@ -54,26 +54,36 @@ def version_from_req(specifier_set): dependencies = [] - if 'dependencies' in project_toml: - dependencies_toml = project_toml['dependencies'] + if 'project' in project_toml: + project_section = project_toml['project'] - runtime_dependencies = parse_toml_section_pep621_dependencies( - pyproject_path, - dependencies_toml - ) - - dependencies.extend(runtime_dependencies) - - if 'optional-dependencies' in project_toml: - optional_dependencies_toml = project_toml['optional-dependencies'] - - for group in optional_dependencies_toml: - group_dependencies = parse_toml_section_pep621_dependencies( + if 'dependencies' in project_section: + dependencies_toml = project_section['dependencies'] + runtime_dependencies = parse_toml_section_pep621_dependencies( pyproject_path, - optional_dependencies_toml[group] + dependencies_toml ) + dependencies.extend(runtime_dependencies) + + if 'optional-dependencies' in project_section: + optional_dependencies_toml = project_section[ + 'optional-dependencies' + ] + for group in optional_dependencies_toml: + group_dependencies = parse_toml_section_pep621_dependencies( + pyproject_path, + optional_dependencies_toml[group] + ) + dependencies.extend(group_dependencies) - dependencies.extend(group_dependencies) + if 'build-system' in project_toml: + build_system_section = project_toml['build-system'] + if 'requires' in build_system_section: + build_system_dependencies = parse_toml_section_pep621_dependencies( + pyproject_path, + build_system_section['requires'] + ) + dependencies.extend(build_system_dependencies) return json.dumps({"result": dependencies}) diff --git a/python/lib/dependabot/python/file_parser/pyproject_files_parser.rb b/python/lib/dependabot/python/file_parser/pyproject_files_parser.rb index 56f689ee072..744d3457dc2 100644 --- a/python/lib/dependabot/python/file_parser/pyproject_files_parser.rb +++ b/python/lib/dependabot/python/file_parser/pyproject_files_parser.rb @@ -167,7 +167,8 @@ def missing_poetry_keys def using_pep621? !parsed_pyproject.dig("project", "dependencies").nil? || - !parsed_pyproject.dig("project", "optional-dependencies").nil? + !parsed_pyproject.dig("project", "optional-dependencies").nil? || + !parsed_pyproject.dig("build-system", "requires").nil? end def poetry_root diff --git a/python/lib/dependabot/python/update_checker.rb b/python/lib/dependabot/python/update_checker.rb index b5dd4e0143f..af874e2d47a 100644 --- a/python/lib/dependabot/python/update_checker.rb +++ b/python/lib/dependabot/python/update_checker.rb @@ -325,7 +325,7 @@ def poetry_lock end def library_details - @library_details ||= poetry_details || standard_details + @library_details ||= poetry_details || standard_details || build_system_details end def poetry_details @@ -336,6 +336,10 @@ def standard_details @standard_details ||= toml_content["project"] end + def build_system_details + @build_system_details ||= toml_content["build-system"] + end + def toml_content @toml_content ||= TomlRB.parse(pyproject.content) end diff --git a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb index 2f2fe8083e6..cd042f22eed 100644 --- a/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb +++ b/python/spec/dependabot/python/file_parser/pyproject_files_parser_spec.rb @@ -281,7 +281,9 @@ let(:pyproject_fixture_name) { "standard_python.toml" } - its(:length) { is_expected.to eq(1) } + # fixture has 1 build system requires and plus 1 dependencies exists + + its(:length) { is_expected.to eq(2) } context "with a string declaration" do subject(:dependency) { dependencies.first } @@ -307,7 +309,10 @@ let(:pyproject_fixture_name) { "no_dependencies.toml" } - its(:length) { is_expected.to eq(0) } + # fixture has 1 build system requires and no dependencies or + # optional dependencies exists + + its(:length) { is_expected.to eq(1) } end context "with dependencies with empty requirements" do @@ -360,8 +365,8 @@ let(:pyproject_fixture_name) { "optional_dependencies.toml" } # fixture has 1 runtime dependency, plus 4 optional dependencies, but one - # is ignored because it has markers - its(:length) { is_expected.to eq(4) } + # is ignored because it has markers, plus 1 is build system requires + its(:length) { is_expected.to eq(5) } end context "with optional dependencies only" do diff --git a/python/spec/dependabot/python/update_checker_spec.rb b/python/spec/dependabot/python/update_checker_spec.rb index bcf2a0bfa22..81e99bd22d1 100644 --- a/python/spec/dependabot/python/update_checker_spec.rb +++ b/python/spec/dependabot/python/update_checker_spec.rb @@ -740,6 +740,63 @@ end end + context "when there is a pyproject.toml file with build system require dependencies" do + let(:dependency_files) { [pyproject] } + let(:pyproject_fixture_name) { "table_build_system_requires.toml" } + + context "when updating a dependency inside" do + let(:dependency) do + Dependabot::Dependency.new( + name: "requests", + version: "1.2.3", + requirements: [{ + file: "pyproject.toml", + requirement: "~=1.0.0", + groups: [], + source: nil + }], + package_manager: "pip" + ) + end + + let(:pypi_url) { "https://pypi.org/simple/requests/" } + let(:pypi_response) do + fixture("pypi", "pypi_simple_response_requests.html") + end + + context "when dealing with a library" do + before do + stub_request(:get, "https://pypi.org/pypi/pendulum/json/") + .to_return( + status: 200, + body: fixture("pypi", "pypi_response_pendulum.json") + ) + end + + its([:requirement]) { is_expected.to eq(">=1.0,<2.20") } + end + + context "when dealing with a non-library" do + before do + stub_request(:get, "https://pypi.org/pypi/pendulum/json/") + .to_return(status: 404) + end + + its([:requirement]) { is_expected.to eq("~=2.19.1") } + end + end + + context "when updating a dependency in an additional requirements file" do + let(:dependency_files) { super().append(requirements_file) } + + let(:dependency) { requirements_dependency } + + it "does not get affected by whether it's a library or not and updates using the :increase strategy" do + expect(first_updated_requirements[:requirement]).to eq("==2.6.0") + end + end + end + context "when there were multiple requirements" do let(:dependency) do Dependabot::Dependency.new( diff --git a/python/spec/fixtures/pyproject_files/table_build_system_requires.toml b/python/spec/fixtures/pyproject_files/table_build_system_requires.toml new file mode 100644 index 00000000000..821f37ab8a7 --- /dev/null +++ b/python/spec/fixtures/pyproject_files/table_build_system_requires.toml @@ -0,0 +1,11 @@ +[build-system] +requires = ["requests~=1.0.0"] + +[project] +name = "pendulum" +version = "2.0.0" +homepage = "https://github.com/roghu/py3_projects" +license = "MIT" +readme = "README.md" +authors = ["Dependabot "] +description = "Python datetimes made easy"