Skip to content

Commit

Permalink
refactor: configure mypy to strict mode (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
CBeck-96 authored Jan 4, 2025
1 parent 91a8383 commit 4b33dd4
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 57 deletions.
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.8.0"
rev: "v1.13.0"
hooks:
- id: mypy
# List type stub dependencies explicitly, as --install-types should be avoided as per
Expand All @@ -32,7 +32,13 @@ repos:
- types-python-dateutil==2.9.0.20241003
- typing-extensions==4.12.2
- types-jsonschema==4.23.0.20240813
- cyclonedx-python-lib==8.5.0
- univers==30.12.1
- charset-normalizer==3.4.0
- natsort==8.4.0
- docstring-parser==0.16
files: "^cdxev/.*\\.py$"
args: ["--config-file", "pyproject.toml"]
- repo: https://github.com/PyCQA/bandit
rev: "1.7.7"
hooks:
Expand Down
5 changes: 3 additions & 2 deletions cdxev/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import shutil
import sys
import textwrap
import typing as t
from collections.abc import MutableSequence
from dataclasses import dataclass
from pathlib import Path
Expand Down Expand Up @@ -42,7 +43,7 @@ class Status(enum.IntEnum):
VALIDATION_ERROR = 4


def main() -> int:
def main() -> t.Union[int, t.Any]:
"""Main entry point for this tool."""
args = parse_cli()

Expand Down Expand Up @@ -93,7 +94,7 @@ def read_sbom(sbom_file: Path, file_type: Optional[str] = None) -> Tuple[dict, s
return sbom, file_type


def load_json(path: Path) -> dict:
def load_json(path: Path) -> t.Any:
"""Loads a JSON file into a dictionary."""
try:
with path.open(encoding="utf-8-sig") as file:
Expand Down
14 changes: 10 additions & 4 deletions cdxev/amend/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,9 @@ class LicenseNameToId(Operation):

license_map: dict[str, str] = {}

def prepare(self, sbom: dict) -> None:
def prepare(self, sbom: dict) -> None: # type: ignore
license_mapping_file = (
importlib.resources.files(__spec__.parent) / "license_name_spdx_id_map.json" # type: ignore[name-defined, arg-type] # noqa: E501
importlib.resources.files(__spec__.parent) / "license_name_spdx_id_map.json" # type: ignore[arg-type] # noqa: E501
)
license_mapping_json = license_mapping_file.read_text(encoding="utf-8-sig")
license_mapping = json.loads(license_mapping_json)
Expand Down Expand Up @@ -446,10 +446,16 @@ class DeleteAmbiguousLicenses(Operation):
"""

def _has_text(self, license: dict) -> bool:
return license.get("text", {}).get("content", "") != ""
if license.get("text", {}).get("content", "") != "":
return True
else:
return False

def _has_url(self, license: dict) -> bool:
return license.get("url", "") != ""
if license.get("url", "") != "":
return True
else:
return False

def _has_name_only(self, license: dict) -> bool:
# Any fields other than name, text, or url mean the license shouldn't be deleted.
Expand Down
4 changes: 2 additions & 2 deletions cdxev/auxiliary/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ class SWID(dict):
"""

def __str__(self) -> str:
return "tagId: " + self["tagId"]
return "tagId: " + str(self["tagId"])

def __eq__(self, other: object) -> bool:
return isinstance(other, SWID) and self["tagId"] == other["tagId"]

def __hash__(self) -> int: # type: ignore[override]
return self["tagId"].__hash__()
return self["tagId"].__hash__() # type: ignore


@dataclass(init=True, frozen=True, eq=True)
Expand Down
12 changes: 10 additions & 2 deletions cdxev/auxiliary/sbomFunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from cyclonedx.model.component import Component
from dateutil.parser import parse

from cdxev.error import AppError

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -415,8 +417,14 @@ def deserialize(sbom: dict) -> Bom:
sbom.pop(
"compositions"
) # compositions need to be removed till the model supports those
deserialized_bom = Bom.from_json(data=sbom) # type: ignore
return deserialized_bom
deserialized_bom = Bom.from_json(data=sbom) # type:ignore[attr-defined]
if isinstance(deserialized_bom, Bom):
return deserialized_bom
else:
raise AppError(
"Failed deserialization",
("Deserialization of the SBOM into the CycloneDX Python Library failed."),
)


def extract_cyclonedx_components(
Expand Down
21 changes: 7 additions & 14 deletions cdxev/initialize_sbom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,14 @@
from typing import Any, Union
from uuid import uuid4

from cyclonedx.model import ( # type: ignore
ExternalReference,
ExternalReferenceType,
XsUri,
)
from cyclonedx.model.bom import Bom, BomMetaData # type: ignore
from cyclonedx.model.bom_ref import BomRef # type: ignore
from cyclonedx.model.component import Component, ComponentType # type: ignore
from cyclonedx.model.contact import ( # type: ignore
OrganizationalContact,
OrganizationalEntity,
)
from cyclonedx.model.dependency import Dependency # type: ignore
from cyclonedx.model import ExternalReference, ExternalReferenceType, XsUri
from cyclonedx.model.bom import Bom, BomMetaData
from cyclonedx.model.bom_ref import BomRef
from cyclonedx.model.component import Component, ComponentType
from cyclonedx.model.contact import OrganizationalContact, OrganizationalEntity
from cyclonedx.model.dependency import Dependency
from cyclonedx.model.tool import Tool
from cyclonedx.output.json import JsonV1Dot6 # type: ignore
from cyclonedx.output.json import JsonV1Dot6

from cdxev import pkg

Expand Down
29 changes: 10 additions & 19 deletions cdxev/list_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,18 @@
logger = logging.getLogger(__name__)


def print_license(license: dict) -> str:
if license.get("expression", ""):
return license.get("expression", "")
elif license.get("license", {}).get("id", ""):
return license.get("license", {}).get("id", "")
else:
return license.get("license", {}).get("name", "")


def extract_string_from_license(license: License) -> str:
if isinstance(license, DisjunctiveLicense):
if license.id is not None:
return license.id
return str(license.id)
elif license.name is not None:
return license.name
return str(license.name)
else:
return ""

elif isinstance(license, LicenseExpression):
if license.value is not None:
return license.value
return str(license.value)
else:
return ""
else:
Expand All @@ -48,7 +39,7 @@ def extract_license_strings_from_licenses(licenses: list[License]) -> list[str]:
return license_list


def extract_metadata_license_information(metadata: BomMetaData) -> dict:
def extract_metadata_license_information(metadata: BomMetaData) -> dict[str, Any]:
if metadata.component is not None:
metadata_component = metadata.component
software_information: dict[str, Any] = {}
Expand Down Expand Up @@ -115,7 +106,7 @@ def write_list_to_str(str_list: list[str], division_character: str = "\n") -> st
return string


def write_license_dict_to_txt(info_dict: dict) -> str:
def write_license_dict_to_txt(info_dict: dict[str, Any]) -> str:
string = ""

if info_dict.get("name", ""):
Expand All @@ -137,7 +128,7 @@ def write_license_dict_to_txt(info_dict: dict) -> str:
return string


def write_license_dict_to_csv(info_dict: dict) -> str:
def write_license_dict_to_csv(info_dict: dict[str, Any]) -> str:
string = ""

string += '"' + info_dict.get("name", "") + '"'
Expand All @@ -153,7 +144,7 @@ def write_license_dict_to_csv(info_dict: dict) -> str:


def write_license_information_to_txt(
software_information: dict, component_information: list[dict]
software_information: dict[str, Any], component_information: list[dict[str, Any]]
) -> str:

string = write_license_dict_to_txt(software_information)
Expand All @@ -176,8 +167,8 @@ def write_license_information_to_txt(


def write_license_information_to_csv(
software_information: dict,
component_information: list[dict],
software_information: dict[str, Any],
component_information: list[dict[str, Any]],
) -> str:
string = "Name,Copyright,Licenses"

Expand Down Expand Up @@ -293,7 +284,7 @@ def list_components(sbom: Bom, format: str = "txt") -> str:
return string


def list_command(sbom: dict, operation: str, format: str = "txt") -> str:
def list_command(sbom: dict, operation: str, format: str = "txt") -> str: # type: ignore
"""
Lists specific content of the SBOM.
Expand Down
2 changes: 1 addition & 1 deletion cdxev/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def merge_components(governing_sbom: dict, sbom_to_be_merged: dict) -> t.List[di
)
list_of_merged_components.append(component)
list_of_merged_bom_refs.append(new_bom_ref)
return list_of_merged_components
return list_of_merged_components # type:ignore [no-any-return]


def merge_dependency(
Expand Down
6 changes: 3 additions & 3 deletions cdxev/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import typing as t
from dataclasses import dataclass, field, fields

import univers.version_range # type:ignore
import univers.versions # type:ignore
import univers.version_range # type:ignore[import-untyped]
import univers.versions # type:ignore[import-untyped]

from cdxev.auxiliary.identity import ComponentIdentity, Coordinates, Key, KeyType
from cdxev.auxiliary.sbomFunctions import walk_components
Expand Down Expand Up @@ -150,7 +150,7 @@ def create(
group=component.get("group"),
version_range=component.get("version-range", ""),
)
return UpdateIdentity(coordinates) # type:ignore
return UpdateIdentity(coordinates)

else:
return super().create(component, allow_unsafe)
Expand Down
20 changes: 17 additions & 3 deletions cdxev/validator/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def open_schema(
"Path does not exist or is not a file: " + str(schema_path),
)
with schema_path.open() as fp:
return json.load(fp)
return json.load(fp) # type:ignore [no-any-return]
except OSError as e:
raise AppError("Schema not loaded", str(e)) from e
except json.JSONDecodeError as e:
Expand All @@ -55,15 +55,29 @@ def _get_builtin_schema(schema_type: str, spec_version: str) -> dict:
f"schema type '{schema_type}'.",
)
schema_json = schema_file.read_text()
return json.loads(schema_json)
schema = json.loads(schema_json)
if isinstance(schema, dict):
return schema
else:
raise AppError(
"Schema error",
("Loaded builtin schema is not of type dict"),
)


def load_spdx_schema() -> dict:
path_to_embedded_schema = (
resources.files("cdxev.auxiliary.schema") / "spdx.schema.json"
)
with path_to_embedded_schema.open() as f:
return json.load(f)
schema = json.load(f)
if isinstance(schema, dict):
return schema
else:
raise AppError(
"SPDX schema error",
("Loaded SPDX schema is not type dict"),
)


def validate_filename(
Expand Down
10 changes: 6 additions & 4 deletions cdxev/validator/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ def validate_sbom(
)
for error in sorted(v.iter_errors(sbom), key=str):
try:
if error.validator == "required" and error.validator_value == [
"this_is_an_externally_described_component"
]:
if (
error.validator == "required" # type: ignore[comparison-overlap]
and error.validator_value
== ["this_is_an_externally_described_component"]
):
# This requirement in the schema allows us to produce warnings.
comp = t.cast(dict, error.instance)
if "bom-ref" in comp:
Expand Down Expand Up @@ -202,7 +204,7 @@ def validate_sbom(
errors.append(
f"{error_path}'{error.absolute_path[-1]}' should not be empty"
)
elif error.validator == "pattern":
elif error.validator == "pattern": # type: ignore[comparison-overlap]
errors.append(error_path + error.message.replace("\\", ""))
else:
errors.append(error_path + error.message)
Expand Down
30 changes: 28 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,34 @@ build_command = "pip install poetry && poetry build"
packages = "cdxev"
# Excludes tests even when mypy is invoked with a path (as the VS Code extension does, for instance)
exclude = ['tests/']
disallow_untyped_defs = true
no_error_summary = true
strict = true

# Allow generic types for certain files
[[tool.mypy.overrides]]
module = [
"cdxev.merge",
"cdxev.merge_vex",
"cdxev.set",
"cdxev.amend.operations",
"cdxev.amend.license",
"cdxev.amend.command",
"cdxev.__main__",
"cdxev.validator.helper",
"cdxev.validator.validate",
"cdxev.auxiliary.output",
"cdxev.auxiliary.sbomFunctions",
"cdxev.auxiliary.filename_gen",
"cdxev.auxiliary.identity",
"cdxev.validator.customreports",
"cdxev.build_public_bom"
]
disallow_any_generics = false

[[tool.mypy.overrides]]
module = [
"cdxev.__main__",
]
warn_return_any = false

[tool.coverage.run]
source = ["cdxev"]
Expand Down

0 comments on commit 4b33dd4

Please sign in to comment.