Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def write_json(queue: Queue[list[problem.Problem]], fd: TextIO, settings: types.


def _audit_sq(
sq: platform.Platform, settings: types.ConfigSettings, what_to_audit: Optional[list[str]] = None, key_list: types.KeyList = None
sq: platform.Platform, settings: types.ConfigSettings, what_to_audit: Optional[list[str]] = None, key_list: Optional[types.KeyList] = None
) -> list[problem.Problem]:
"""Audits a SonarQube/Cloud platform"""
everything = what_to_audit is None
Expand Down
5 changes: 3 additions & 2 deletions cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import sys
import random
from argparse import ArgumentParser
from typing import Optional

import sonar.logging as log
from sonar import errcodes, version, utilities, exceptions
Expand Down Expand Up @@ -186,7 +187,7 @@ def __check_file_writeable(file: str) -> None:
os.remove(file)


def parse_and_check(parser: ArgumentParser, logger_name: str = None, verify_token: bool = True, is_migration: bool = False) -> object:
def parse_and_check(parser: ArgumentParser, logger_name: Optional[str] = None, verify_token: bool = True, is_migration: bool = False) -> object:
"""Parses arguments, applies default settings and perform common environment checks"""
try:
args = parser.parse_args()
Expand Down Expand Up @@ -408,7 +409,7 @@ def set_target_sonar_args(parser: ArgumentParser) -> ArgumentParser:
return parser


def set_output_file_args(parser: ArgumentParser, help_str: str = None, allowed_formats: tuple[str, ...] = ("csv",)) -> ArgumentParser:
def set_output_file_args(parser: ArgumentParser, help_str: Optional[str] = None, allowed_formats: tuple[str, ...] = ("csv",)) -> ArgumentParser:
"""Sets the output file CLI options"""
if not help_str:
help_str = "Report file, stdout by default"
Expand Down
3 changes: 2 additions & 1 deletion cli/projects_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def __import_projects(endpoint: platform.Platform, **kwargs) -> None:
if proj["key"] in statuses:
proj.update(statuses[proj["key"]])
else:
_ = [proj.pop(k, None) for k in ("importStatus", "importDate", "importProjectUrl")]
for k in ("importStatus", "importDate", "importProjectUrl"):
proj.pop(k, None)
data["importSonarqubeEnvironment"] = {
"url": endpoint.url(),
"version": ".".join([str(n) for n in endpoint.version()[:2]]),
Expand Down
1 change: 1 addition & 0 deletions conf/build_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ do
b=$(basename "${f}" .py)
cp "${f}" "${ROOT_DIR}/${GEN_LOC}/${target}/${b}_${target}.py"
done
rm "${ROOT_DIR}/${GEN_LOC}/${target}"/credentials*.py
cp "credentials-${target}.py" "${ROOT_DIR}/${GEN_LOC}/${target}/credentials.py"
mv "${ROOT_DIR}/${GEN_LOC}/${target}/conftest_${target}.py" "${ROOT_DIR}/${GEN_LOC}/${target}/conftest.py"
mv "${ROOT_DIR}/${GEN_LOC}/${target}/utilities_${target}.py" "${ROOT_DIR}/${GEN_LOC}/${target}/utilities.py"
Expand Down
4 changes: 2 additions & 2 deletions sonar/aggregations.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class Aggregation(comp.Component):
"""Parent class of applications and portfolios"""

def __init__(self, endpoint: pf.Platform, key: str, data: types.ApiPayload = None) -> None:
self._nbr_projects = None
self._permissions = None
self._nbr_projects: Optional[int] = None
self._permissions: Optional[object] = None
super().__init__(endpoint=endpoint, key=key)

def reload(self, data: dict[str, any]) -> None:
Expand Down
3 changes: 2 additions & 1 deletion sonar/app_branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from __future__ import annotations
from typing import Optional
from datetime import datetime

import json
from requests.utils import quote
Expand Down Expand Up @@ -62,7 +63,7 @@ def __init__(
self.sq_json = branch_data
self._is_main = is_main
self._project_branches = project_branches
self._last_analysis = None
self._last_analysis: Optional[datetime] = None
log.debug("Created object %s with uuid %d id %x", str(self), hash(self), id(self))
ApplicationBranch.CACHE.put(self)

Expand Down
17 changes: 9 additions & 8 deletions sonar/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ class Application(aggr.Aggregation):
def __init__(self, endpoint: pf.Platform, key: str, name: str) -> None:
"""Don't use this directly, go through the class methods to create Objects"""
super().__init__(endpoint=endpoint, key=key)
self._branches = None
self._projects = None
self._description = None
self._branches: Optional[dict[str, app_branches.ApplicationBranch]] = None
self._projects: Optional[dict[str, str]] = None
self._description: Optional[str] = None
self.name = name
log.debug("Created object %s with uuid %d id %x", str(self), hash(self), id(self))
Application.CACHE.put(self)
Expand All @@ -90,7 +90,7 @@ def get_object(cls, endpoint: pf.Platform, key: str) -> Application:
:rtype: Application
"""
check_supported(endpoint)
o = Application.CACHE.get(key, endpoint.local_url)
o: Application = Application.CACHE.get(key, endpoint.local_url)
if o:
return o
data = json.loads(endpoint.get(Application.API[c.GET], params={"application": key}).text)["application"]
Expand All @@ -109,7 +109,7 @@ def load(cls, endpoint: pf.Platform, data: types.ApiPayload) -> Application:
:rtype: Application
"""
check_supported(endpoint)
o = Application.CACHE.get(data["key"], endpoint.local_url)
o: Application = Application.CACHE.get(data["key"], endpoint.local_url)
if not o:
o = cls(endpoint, data["key"], data["name"])
o.reload(data)
Expand Down Expand Up @@ -259,7 +259,7 @@ def delete(self) -> bool:
branch.delete()
return super().delete()

def get_hotspots(self, filters: dict[str, str] = None) -> dict[str, object]:
def get_hotspots(self, filters: Optional[dict[str, str]] = None) -> dict[str, object]:
new_filters = filters.copy() if filters else {}
pattern = new_filters.pop("branch", None) if new_filters else None
if not pattern:
Expand All @@ -270,7 +270,7 @@ def get_hotspots(self, filters: dict[str, str] = None) -> dict[str, object]:
findings_list |= comp.get_hotspots(new_filters)
return findings_list

def get_issues(self, filters: dict[str, str] = None) -> dict[str, object]:
def get_issues(self, filters: Optional[dict[str, str]] = None) -> dict[str, object]:
new_filters = filters.copy() if filters else {}
pattern = new_filters.pop("branch", None) if new_filters else None
if not pattern:
Expand Down Expand Up @@ -412,7 +412,8 @@ def update(self, data: types.ObjectJsonRepr) -> None:
main_branch_name = next((k for k, v in data.get("branches", {}).items() if v.get("isMain", False)), None)
main_branch_name is None or self.main_branch().rename(main_branch_name)

_ = [self.set_branches(name, branch_data) for name, branch_data in data.get("branches", {}).items()]
for name, branch_data in data.get("branches", {}).items():
self.set_branches(name, branch_data)

def api_params(self, op: Optional[str] = None) -> types.ApiParams:
ops = {c.READ: {"application": self.key}, c.RECOMPUTE: {"key": self.key}}
Expand Down
9 changes: 6 additions & 3 deletions sonar/audit/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#

import csv
from typing import Optional

import sonar.logging as log
from sonar import utilities
Expand Down Expand Up @@ -60,7 +61,9 @@ def to_json(self, with_url=False):
return d


def dump_report(problems: list[Problem], file: str, server_id: str = None, format: str = "csv", with_url: bool = False, separator: str = ",") -> None:
def dump_report(
problems: list[Problem], file: str, server_id: Optional[str] = None, format: str = "csv", with_url: bool = False, separator: str = ","
) -> None:
"""Dumps to file a report about a list of problems

:param list[Problems] problems: List of problems to dump
Expand All @@ -76,7 +79,7 @@ def dump_report(problems: list[Problem], file: str, server_id: str = None, forma
__dump_csv(problems=problems, file=file, server_id=server_id, with_url=with_url, separator=separator)


def __dump_csv(problems: list[Problem], file: str, server_id: str = None, with_url: bool = False, separator: str = ",") -> None:
def __dump_csv(problems: list[Problem], file: str, server_id: Optional[str] = None, with_url: bool = False, separator: str = ",") -> None:
"""Writes a list of problems in CSV format

:param list[Problems] problems: List of problems to dump
Expand All @@ -98,7 +101,7 @@ def __dump_csv(problems: list[Problem], file: str, server_id: str = None, with_u
csvwriter.writerow(data)


def __dump_json(problems: list[Problem], file: str, server_id: str = None, with_url: bool = False) -> None:
def __dump_json(problems: list[Problem], file: str, server_id: Optional[str] = None, with_url: bool = False) -> None:
"""Writes a list of problems in JSON format

:param list[Problems] problems: List of problems to dump
Expand Down
9 changes: 5 additions & 4 deletions sonar/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from __future__ import annotations
from http import HTTPStatus
from typing import Optional
from datetime import datetime
import json
import re
from urllib.parse import unquote
Expand Down Expand Up @@ -67,10 +68,10 @@ def __init__(self, project: projects.Project, name: str) -> None:
super().__init__(endpoint=project.endpoint, key=name)
self.name = name
self.concerned_object = project
self._is_main = None
self._new_code = None
self._last_analysis = None
self._keep_when_inactive = None
self._is_main: Optional[bool] = None
self._new_code: Optional[str] = None
self._last_analysis: Optional[datetime] = None
self._keep_when_inactive: Optional[bool] = None
Branch.CACHE.put(self)
log.debug("Created object %s", str(self))

Expand Down
2 changes: 1 addition & 1 deletion sonar/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, jsonlog: types.ApiPayload, concerned_object: object) -> None:
"""
self.concerned_object = concerned_object
self.sq_json = jsonlog
self._change_type = None
self._change_type: Optional[str] = None

def __str__(self) -> str:
"""str() implementation"""
Expand Down
24 changes: 11 additions & 13 deletions sonar/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"""

from __future__ import annotations
from typing import Optional
from typing import Any, Optional
import math
import json

Expand Down Expand Up @@ -56,20 +56,18 @@ class Component(sq.SqObject):
def __init__(self, endpoint: pf.Platform, key: str, data: types.ApiPayload = None) -> None:
"""Constructor"""
super().__init__(endpoint=endpoint, key=key)
self.name = None
self.nbr_issues = None
self.ncloc = None
self._description = None
self._last_analysis = None
self._visibility = None
self.name: Optional[str] = None
self.nbr_issues: Optional[int] = None
self.ncloc: Optional[int] = None
self._description: Optional[str] = None
self._last_analysis: Optional[datetime] = None
self._visibility: Optional[str] = None
if data is not None:
self.reload(data)

def reload(self, data: types.ApiPayload) -> Component:
log.debug("Reloading %s with %s", str(self), utilities.json_dump(data))
if not self.sq_json:
self.sq_json = {}
self.sq_json.update(data)
"""Loads a SonarQube API JSON payload in a Component"""
super().reload(data)
if "name" in data:
self.name = data["name"]
if "visibility" in data:
Expand Down Expand Up @@ -200,7 +198,7 @@ def get_measures(self, metrics_list: types.KeyList) -> dict[str, any]:
self.ncloc = 0 if not m["ncloc"].value else int(m["ncloc"].value)
return m

def get_measure(self, metric: str, fallback: int = None) -> any:
def get_measure(self, metric: str, fallback: Any = None) -> Any:
"""Returns a component measure"""
meas = self.get_measures([metric])
return meas[metric].value if metric in meas and meas[metric] and meas[metric].value is not None else fallback
Expand All @@ -215,7 +213,7 @@ def get_navigation_data(self) -> types.ApiPayload:
"""Returns a component navigation data"""
params = utilities.replace_keys(measures.ALT_COMPONENTS, "component", self.api_params(c.GET))
data = json.loads(self.get("navigation/component", params=params).text)
self.sq_json.update(data)
super().reload(data)
return data

def refresh(self) -> Component:
Expand Down
13 changes: 10 additions & 3 deletions sonar/custom_measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"""

import json
from typing import Any, Optional
import sonar.sqobject as sq
import sonar.platform as pf

Expand All @@ -32,15 +33,21 @@ class CustomMeasure(sq.SqObject):
API_ROOT = "api/custom_measures/"

def __init__(
self, key: str, endpoint: pf.Platform, uuid: str = None, project_key: str = None, value: any = None, description: str = None
self,
key: str,
endpoint: pf.Platform,
uuid: Optional[str] = None,
project_key: Optional[str] = None,
value: Any = None,
description: Optional[str] = None,
) -> None:
super().__init__(endpoint=endpoint, key=key)
self.uuid = uuid
self.projectKey = project_key
self.value = value
self.description = description

def create(self, project_key: str, metric_key: str, value: any, description: str = None) -> bool:
def create(self, project_key: str, metric_key: str, value: Any, description: Optional[str] = None) -> bool:
return self.post(
CustomMeasure.API_ROOT + "create",
{
Expand All @@ -51,7 +58,7 @@ def create(self, project_key: str, metric_key: str, value: any, description: str
},
).ok

def update(self, value: any, description: str = None) -> bool:
def update(self, value: Any, description: Optional[str] = None) -> bool:
"""Updates a custom measure"""
return self.post(
CustomMeasure.API_ROOT + "update",
Expand Down
51 changes: 26 additions & 25 deletions sonar/findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import sonar.utilities as util
from sonar import projects, rules
from sonar.changelog import Changelog

_JSON_FIELDS_REMAPPED = (("pull_request", "pullRequest"), ("_comments", "comments"))

Expand Down Expand Up @@ -109,26 +110,26 @@ class Finding(sq.SqObject):
def __init__(self, endpoint: pf.Platform, key: str, data: types.ApiPayload = None, from_export: bool = False) -> None:
"""Constructor"""
super().__init__(endpoint=endpoint, key=key)
self.severity = None #: Severity (str)
self.type = None #: Type (str): VULNERABILITY, BUG, CODE_SMELL or SECURITY_HOTSPOT
self.impacts = None #: 10.x MQR mode
self.author = None #: Author (str)
self.assignee = None #: Assignee (str)
self.status = None #: Status (str)
self.resolution = None #: Resolution (str)
self.rule = None #: Rule Id (str)
self.projectKey = None #: Project key (str)
self._changelog = None
self._comments = None
self.file = None #: File (str)
self.line = 0 #: Line (int)
self.component = None
self.message = None #: Message
self.creation_date = None #: Creation date (datetime)
self.modification_date = None #: Last modification date (datetime)
self.hash = None #: Hash (str)
self.branch = None #: Branch (str)
self.pull_request = None #: Pull request (str)
self.severity = None # BLOCKER, CRITICAL, MAJOR, MINOR, INFO
self.type: Optional[str] = None # VULNERABILITY, BUG, CODE_SMELL or SECURITY_HOTSPOT
self.impacts: Optional[dict[str, str]] = None #: 10.x MQR mode
self.author: Optional[str] = None
self.assignee: Optional[str] = None
self.status: Optional[str] = None # OPEN, CONFIRMED, REOPENED, RESOLVED, CLOSED, ACCEPTED, FALSE_POSITIVE
self.resolution: Optional[str] = None
self.rule: Optional[str] = None
self.projectKey: Optional[str] = None
self._changelog: Optional[dict[str, Changelog]] = None
self._comments: Optional[dict[str, dict[str, str]]] = None
self.file: Optional[str] = None
self.line: int = 0
self.component: Optional[str] = None
self.message: Optional[str] = None #: Message
self.creation_date: Optional[datetime] = None #: Creation date (datetime)
self.modification_date: Optional[datetime] = None #: Last modification date (datetime)
self.hash: Optional[str] = None #: Hash (str)
self.branch: Optional[str] = None #: Branch (str)
self.pull_request: Optional[str] = None #: Pull request (str)
self._load(data, from_export)

def _load(self, data: types.ApiPayload, from_export: bool = False) -> None:
Expand Down Expand Up @@ -271,16 +272,16 @@ def to_sarif(self, full: bool = True) -> dict[str, str]:
return data

def is_vulnerability(self) -> bool:
return self.type == "VULNERABILITY" or "SECURITY" in self.impacts
return "SECURITY" in self.impacts if self.endpoint.is_mqr_mode() else self.type == "VULNERABILITY"

def is_hotspot(self) -> bool:
return self.type == "SECURITY_HOTSPOT" or "SECURITY" in self.impacts
return self.type == "SECURITY_HOTSPOT"

def is_bug(self) -> bool:
return self.type == "BUG" or "RELIABILITY" in self.impacts
return "RELIABILITY" in self.impacts if self.endpoint.is_mqr_mode() else self.type == "BUG"

def is_code_smell(self) -> bool:
return self.type == "CODE_SMELL" or "MAINTAINABILITY" in self.impacts
return "MAINTAINABILITY" in self.impacts if self.endpoint.is_mqr_mode() else self.type == "CODE_SMELL"

def is_security_issue(self) -> bool:
return self.is_vulnerability() or self.is_hotspot()
Expand Down Expand Up @@ -473,7 +474,7 @@ def get_branch_and_pr(self, data: types.ApiPayload) -> tuple[Optional[str], Opti
return branch, pr


def export_findings(endpoint: pf.Platform, project_key: str, branch: str = None, pull_request: str = None) -> dict[str, Finding]:
def export_findings(endpoint: pf.Platform, project_key: str, branch: Optional[str] = None, pull_request: Optional[str] = None) -> dict[str, Finding]:
"""Export all findings of a given project

:param Platform endpoint: Reference to the SonarQube platform
Expand Down
Loading