diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aca831163..811b0ec10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,7 +67,7 @@ repos: language: python files: ^src/macaron/|^tests/ types: [text, python] - exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* + pass_filenames: false args: [--show-traceback, --config-file, pyproject.toml] # Enable a whole bunch of useful helper hooks, too. diff --git a/pyproject.toml b/pyproject.toml index 8eabaf6a4..48b742271 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ actions = [ ] dev = [ "flit >=3.2.0,<4.0.0", - "mypy >=1.19.1,<1.20", + "mypy >=2.0.0,<3.0.0", "types-pyyaml >=6.0.4,<7.0.0", "types-requests >=2.25.6,<3.0.0", "types-jsonschema >=4.22.0,<5.0.0", @@ -169,6 +169,18 @@ exclude = [ # https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml [tool.mypy] +files = [ + "src/macaron/", + "tests/", +] +exclude = [ + "tests/malware_analyzer/pypi/resources/sourcecode_samples/", +] +mypy_path = [ + "src/", +] +num_workers = 4 +explicit_package_bases = true show_error_codes = true show_column_numbers = true check_untyped_defs = true @@ -187,13 +199,13 @@ disallow_untyped_decorators = true [[tool.mypy.overrides]] module = [ - "pytest.*", # https://github.com/pytest-dev/pytest/issues/7469 "pydriller.*", "gitdb.*", "yamale.*", "problog.*", ] -ignore_missing_imports = true +# ignore_missing_imports = true +follow_untyped_imports = true # https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html diff --git a/src/macaron/parsers/actionparser.py b/src/macaron/parsers/actionparser.py index 4bed61615..f26f58994 100644 --- a/src/macaron/parsers/actionparser.py +++ b/src/macaron/parsers/actionparser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. """This module contains the parser for GitHub Actions Workflow files.""" @@ -37,7 +37,7 @@ def parse(workflow_path: str) -> Workflow: When parsing fails with errors. """ try: - parse_result = yamale.make_data(workflow_path, parser="ruamel") + parse_result = yamale.make_data(workflow_path, parser="ruamel") # type: ignore[no-untyped-call] except OSError as error: raise ParseError("Cannot parse GitHub Workflow: " + workflow_path) from error diff --git a/src/macaron/parsers/yaml/loader.py b/src/macaron/parsers/yaml/loader.py index e99102bc9..3aff2970a 100644 --- a/src/macaron/parsers/yaml/loader.py +++ b/src/macaron/parsers/yaml/loader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. """This module contains the loader for YAML files.""" @@ -38,7 +38,7 @@ def _load_yaml_content(path: os.PathLike | str) -> list: """ try: logger.debug("Loading yaml from file %s", path) - return list(yamale.make_data(path)) + return list(yamale.make_data(path)) # type: ignore[no-untyped-call] except YAMLError as error: abs_path = os.path.abspath(path) @@ -74,7 +74,7 @@ def validate_yaml_data(cls, schema: Schema, data: list) -> bool: """ try: logger.debug("Validate data %s with schema %s.", str(data), str(schema.dict)) - yamale.validate(schema, data) + yamale.validate(schema, data) # type: ignore[no-untyped-call] return True except yamale.YamaleError as error: logger.debug("Yaml data validation failed.") @@ -84,7 +84,7 @@ def validate_yaml_data(cls, schema: Schema, data: list) -> bool: return False @classmethod - def load(cls, path: os.PathLike | str, schema: Schema = None) -> Any: + def load(cls, path: os.PathLike | str, schema: Schema | None = None) -> Any: """Load and return a Python object from a yaml file. If ``schema`` is provided. This method will validate the loaded content against the diff --git a/src/macaron/repo_finder/repo_utils.py b/src/macaron/repo_finder/repo_utils.py index e36a3df36..2023bd4ca 100644 --- a/src/macaron/repo_finder/repo_utils.py +++ b/src/macaron/repo_finder/repo_utils.py @@ -209,8 +209,8 @@ def get_repo_tags(git_obj: Git) -> dict[str, str]: # Retrieve tags using a Git subprocess. repository_path = git_obj.repo.working_tree_dir - if not os.path.isdir(repository_path): - logger.debug("") + if not repository_path or not os.path.isdir(repository_path): + logger.debug("Invalid repository path: %s", repository_path) return {} try: result = subprocess.run( diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index 4d2bfc103..4d9151847 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -190,10 +190,10 @@ def evaluate_heuristic_results( problog_code = f"{facts}\n\n{self.malware_rules_problog_model}" logger.debug("Problog model used for evaluation:\n %s", problog_code) - problog_model = PrologString(problog_code) - problog_results: dict[Term, float] = get_evaluatable().create_from(problog_model).evaluate() + problog_model = PrologString(problog_code) # type: ignore[no-untyped-call] + problog_results: dict[Term, float] = get_evaluatable().create_from(problog_model).evaluate() # type: ignore[no-untyped-call] - confidence = problog_results.pop(Term(self.problog_result_access), 0.0) + confidence = problog_results.pop(Term(self.problog_result_access), 0.0) # type: ignore[no-untyped-call] if confidence > 0: # a rule was triggered for term, conf in problog_results.items(): if term.args: diff --git a/src/macaron/util.py b/src/macaron/util.py index d4e6420f0..c3f7f485c 100644 --- a/src/macaron/util.py +++ b/src/macaron/util.py @@ -471,7 +471,7 @@ def check_rate_limit(response: Response) -> None: remains = int(response.headers["X-RateLimit-Remaining"]) if "X-RateLimit-Remaining" in response.headers else 2 if remains <= 1: - rate_limit_reset = response.headers.get("X-RateLimit-Reset", default="") + rate_limit_reset = response.headers.get("X-RateLimit-Reset", "") if not rate_limit_reset: return diff --git a/tests/parsers/yaml/test_yaml_loader.py b/tests/parsers/yaml/test_yaml_loader.py index 56b9f5140..f726dcb25 100644 --- a/tests/parsers/yaml/test_yaml_loader.py +++ b/tests/parsers/yaml/test_yaml_loader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. """This module test the yaml loader functions.""" @@ -40,7 +40,7 @@ def test_validate_yaml_data(self) -> None: """Test the validate yaml data method.""" # We are not testing the behavior of yamale methods # so the schema and data can be empty. - mock_schema = Schema({}) + mock_schema = Schema({}) # type: ignore[no-untyped-call] mock_data: list = [] # No errors @@ -48,13 +48,13 @@ def test_validate_yaml_data(self) -> None: assert YamlLoader.validate_yaml_data(mock_schema, mock_data) # Errors exist - with patch("yamale.validate", side_effect=YamaleError(results=[])): + with patch("yamale.validate", side_effect=YamaleError(results=[])): # type: ignore[no-untyped-call] assert not YamlLoader.validate_yaml_data(mock_schema, mock_data) def test_load(self) -> None: """Test the load method of YamlLoader.""" schema_file = os.path.join(self.RESOURCES_DIR, "schema.yaml") - schema: Schema = yamale.make_schema(schema_file) + schema: Schema = yamale.make_schema(schema_file) # type: ignore[no-untyped-call] assert YamlLoader.load(os.path.join(self.RESOURCES_DIR, "invalid.yaml")) == {None: None} assert not YamlLoader.load(os.path.join(self.RESOURCES_DIR, "invalid.yaml"), schema) diff --git a/tests/repo_finder/test_commit_finder.py b/tests/repo_finder/test_commit_finder.py index a505b3df2..bf6fb1851 100644 --- a/tests/repo_finder/test_commit_finder.py +++ b/tests/repo_finder/test_commit_finder.py @@ -7,14 +7,13 @@ import os import re import shutil -from typing import Any import hypothesis import pytest from hypothesis import given, settings from hypothesis.strategies import DataObject, data, text from packageurl import PackageURL -from pydriller.git import Git +from pydriller.git import Git, GitCommit from macaron.repo_finder import commit_finder from macaron.repo_finder.commit_finder import AbstractPurlType, determine_optional_suffix_index @@ -117,24 +116,24 @@ def mocked_repo_() -> Git: @pytest.fixture(name="mocked_repo_commit") -def mocked_repo_commit_(mocked_repo: Git) -> Any: +def mocked_repo_commit_(mocked_repo: Git) -> GitCommit: """Add a commit to the mocked repository.""" return mocked_repo.repo.index.commit(message="Commit_0") @pytest.fixture(name="mocked_repo_empty_commit") -def mocked_repo_empty_commit_(mocked_repo: Git) -> Any: +def mocked_repo_empty_commit_(mocked_repo: Git) -> GitCommit: """Add an empty commit to the mocked repository.""" return mocked_repo.repo.index.commit(message="Empty_Commit") @pytest.fixture(name="mocked_repo_expanded") -def mocked_repo_expanded_(mocked_repo: Git, mocked_repo_commit: Any, mocked_repo_empty_commit: Any) -> Any: +def mocked_repo_expanded_(mocked_repo: Git, mocked_repo_commit: GitCommit, mocked_repo_empty_commit: GitCommit) -> Git: """Add tags to the mocked repository.""" mocked_repo.repo.create_tag("4.5", mocked_repo_commit.hexsha) # Create a tag from a tree. - mocked_repo.repo.create_tag("1.0", ref=mocked_repo.repo.heads.master.commit.tree) + mocked_repo.repo.create_tag("1.0", mocked_repo.repo.heads.master.commit.tree.hexsha) # Add a tag with unicode version. mocked_repo.repo.create_tag(UNICODE_VERSION, mocked_repo_commit.hexsha) @@ -200,7 +199,7 @@ def test_commit_finder_tag_failure( ) def test_commit_finder_success_commit( mocked_repo_expanded: Git, - mocked_repo_commit: Any, + mocked_repo_commit: GitCommit, purl_string: str, ) -> None: """Test Commit Finder on mocked repository that should match valid PURLs.""" @@ -223,7 +222,7 @@ def test_commit_finder_success_commit( ], ) def test_commit_finder_success_empty_commit( - mocked_repo_expanded: Git, mocked_repo_empty_commit: Any, purl_string: str + mocked_repo_expanded: Git, mocked_repo_empty_commit: GitCommit, purl_string: str ) -> None: """Test Commit Finder on mocked repository that should match value PURLs.""" match, outcome = commit_finder.find_commit(mocked_repo_expanded, PackageURL.from_string(purl_string)) @@ -231,7 +230,7 @@ def test_commit_finder_success_empty_commit( assert outcome == CommitFinderInfo.MATCHED -def test_commit_finder_repo_purl_success(mocked_repo_expanded: Git, mocked_repo_commit: Any) -> None: +def test_commit_finder_repo_purl_success(mocked_repo_expanded: Git, mocked_repo_commit: GitCommit) -> None: """Test Commit Finder on mocked repository using a repo type PURL.""" match, outcome = commit_finder.find_commit( mocked_repo_expanded, PackageURL.from_string(f"pkg:github/apache/maven@{mocked_repo_commit.hexsha}") @@ -254,7 +253,7 @@ def test_commit_finder_optional_suffixes(version: str, parts: list, expected: in assert determine_optional_suffix_index(version, parts) == expected -def test_get_repo_tags(mocked_repo_empty_commit: Any) -> None: +def test_get_repo_tags(mocked_repo_empty_commit: GitCommit) -> None: """Test the get repo tags utils function.""" # Create the repository object. repo = Git(os.path.join(REPO_DIR)) diff --git a/tests/slsa_analyzer/git_service/test_gitlab.py b/tests/slsa_analyzer/git_service/test_gitlab.py index e0b44fb97..3cb52855a 100644 --- a/tests/slsa_analyzer/git_service/test_gitlab.py +++ b/tests/slsa_analyzer/git_service/test_gitlab.py @@ -1,8 +1,9 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. """Tests for the GitLab git service.""" +import collections.abc import os from pathlib import Path from unittest import mock @@ -117,7 +118,7 @@ def test_self_hosted_gitlab_without_env_set(tmp_path: Path) -> None: @pytest.fixture(name="self_hosted_gitlab") -def self_hosted_gitlab_repo_fixture(request: pytest.FixtureRequest) -> Git: +def self_hosted_gitlab_repo_fixture(request: pytest.FixtureRequest) -> collections.abc.Iterable[Git]: """Create a mock GitLab self_hosted repo. This fixture expects ONE parameter of type STR which will be the origin remote url initialized for the self_hosted @@ -153,7 +154,7 @@ def self_hosted_gitlab_repo_fixture(request: pytest.FixtureRequest) -> Git: yield gitlab_repo - gitlab_repo.clear() + gitlab_repo.clear() # type: ignore[no-untyped-call] # The indirect parameter is used to note that ``self_hosted_gitlab`` should be passed to the ``self_hosted_gitlab`` diff --git a/tests/slsa_analyzer/mock_git_utils.py b/tests/slsa_analyzer/mock_git_utils.py index 5b6011998..2b46e1053 100644 --- a/tests/slsa_analyzer/mock_git_utils.py +++ b/tests/slsa_analyzer/mock_git_utils.py @@ -39,12 +39,12 @@ def initiate_repo(repo_path: str | os.PathLike, git_init_options: dict | None = os.makedirs(repo_path) try: - git_wrapper = Git(repo_path) + git_wrapper = Git(str(repo_path)) return git_wrapper except GitError: # No git repo at repo_path. git.Repo.init(repo_path, **git_init_options) - return Git(repo_path) + return Git(str(repo_path)) def commit_files(git_wrapper: Git, file_names: list) -> bool: