diff --git a/sphinx_reports/Adapter/DocStrCoverage.py b/sphinx_reports/Adapter/DocStrCoverage.py index ef36f714..3d74c688 100644 --- a/sphinx_reports/Adapter/DocStrCoverage.py +++ b/sphinx_reports/Adapter/DocStrCoverage.py @@ -39,7 +39,7 @@ from pyTooling.Decorators import export, readonly from sphinx_reports.Common import ReportExtensionError -from sphinx_reports.DataModel.DocumentationCoverage import ModuleCoverage, PackageCoverage +from sphinx_reports.DataModel.DocumentationCoverage import ModuleCoverage, PackageCoverage, AggregatedCoverage @export @@ -54,7 +54,7 @@ class Analyzer: _moduleFiles: List[Path] _coverageReport: str - def __init__(self, directory: Path, packageName: str): + def __init__(self, directory: Path, packageName: str) -> None: self._searchDirectory = directory self._packageName = packageName self._moduleFiles = [] @@ -95,7 +95,7 @@ def Convert(self) -> PackageCoverage: moduleName = path.stem modulePath = [p.name for p in path.parents] - currentCoverageObject = rootPackageCoverage + currentCoverageObject: AggregatedCoverage = rootPackageCoverage for packageName in modulePath[1:]: try: currentCoverageObject = currentCoverageObject[packageName] diff --git a/sphinx_reports/CodeCoverage.py b/sphinx_reports/CodeCoverage.py index f83e5696..01e62b07 100644 --- a/sphinx_reports/CodeCoverage.py +++ b/sphinx_reports/CodeCoverage.py @@ -32,17 +32,24 @@ **Report code coverage as Sphinx documentation page(s).** """ from pathlib import Path -from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator +from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator, TypedDict from docutils import nodes from pyTooling.Decorators import export from sphinx_reports.Common import ReportExtensionError from sphinx_reports.Sphinx import strip, LegendPosition, BaseDirective -from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage +from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage, AggregatedCoverage from sphinx_reports.Adapter.DocStrCoverage import Analyzer +class package_DictType(TypedDict): + name: str + directory: str + fail_below: int + levels: Dict[int, Dict[str, str]] + + @export class CodeCoverage(BaseDirective): """ @@ -71,15 +78,15 @@ class CodeCoverage(BaseDirective): _levels: Dict[int, Dict[str, str]] _coverage: PackageCoverage - def _CheckOptions(self): + def _CheckOptions(self) -> None: # Parse all directive options or use default values self._packageID = self._ParseStringOption("packageid") self._legend = self._ParseLegendOption("legend", LegendPosition.Bottom) - def _CheckConfiguration(self): + def _CheckConfiguration(self) -> None: # Check configuration fields and load necessary values try: - allPackages = self.config[f"{self.configPrefix}_packages"] + allPackages: Dict[str, package_DictType] = self.config[f"{self.configPrefix}_packages"] except (KeyError, AttributeError) as ex: raise ReportExtensionError(f"Configuration option '{self.configPrefix}_packages' is not configured.") from ex @@ -118,7 +125,7 @@ def _CheckConfiguration(self): 100: {"class": "doccov-below100", "background": "rgba( 0, 200, 82, .2)", "desc": "excellent documented"}, } - def _ConvertToColor(self, currentLevel, configKey): + def _ConvertToColor(self, currentLevel: float, configKey: str) -> str: for levelLimit, levelConfig in self._levels.items(): if (currentLevel * 100) < levelLimit: return levelConfig[configKey] @@ -141,11 +148,11 @@ def _GenerateCoverageTable(self) -> nodes.table: tableBody = nodes.tbody() tableGroup += tableBody - def sortedValues(d: Mapping) -> Generator[Any, None, None]: + def sortedValues(d: Mapping[str, AggregatedCoverage]) -> Generator[AggregatedCoverage, None, None]: for key in sorted(d.keys()): yield d[key] - def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0): + def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0) -> None: tableBody += nodes.row( "", nodes.entry("", nodes.paragraph(text=f"{' '*level}{packageCoverage.Name} ({packageCoverage.File})")), @@ -217,7 +224,7 @@ def _CreateLegend(self, identifier: str, classes: Iterable[str]) -> List[nodes.E return [rubric, table] - def run(self): + def run(self) -> List[nodes.Node]: self._CheckOptions() self._CheckConfiguration() diff --git a/sphinx_reports/Common.py b/sphinx_reports/Common.py index b5395b3e..a96d5910 100644 --- a/sphinx_reports/Common.py +++ b/sphinx_reports/Common.py @@ -45,7 +45,7 @@ class ReportExtensionError(ExtensionError): # Implementing a dummy method for Python versions before __notes__: List[str] if version_info < (3, 11): # pragma: no cover - def add_note(self, message: str): + def add_note(self, message: str) -> None: try: self.__notes__.append(message) except AttributeError: diff --git a/sphinx_reports/DataModel/DocumentationCoverage.py b/sphinx_reports/DataModel/DocumentationCoverage.py index 0ab1ea8b..a970df01 100644 --- a/sphinx_reports/DataModel/DocumentationCoverage.py +++ b/sphinx_reports/DataModel/DocumentationCoverage.py @@ -34,7 +34,7 @@ from enum import Flag from pathlib import Path -from typing import Optional as Nullable, Iterable, Dict, Union +from typing import Optional as Nullable, Iterable, Dict, Union, Tuple from pyTooling.Decorators import export, readonly @@ -60,7 +60,7 @@ class CoverageState(Flag): @export class Coverage: _name: str - _parent: "Coverage" + _parent: Nullable["Coverage"] _total: int _excluded: int @@ -71,7 +71,7 @@ class Coverage: _coverage: float - def __init__(self, name: str, parent: Nullable["Coverage"] = None): + def __init__(self, name: str, parent: Nullable["Coverage"] = None) -> None: self._name = name self._parent = parent @@ -89,7 +89,7 @@ def Name(self) -> str: return self._name @readonly - def Parent(self) -> "Coverage": + def Parent(self) -> Nullable["Coverage"]: return self._parent @readonly @@ -120,14 +120,14 @@ def Uncovered(self) -> int: def Coverage(self) -> float: return self._coverage - def CalculateCoverage(self): + def CalculateCoverage(self) -> None: self._uncovered = self._expected - self._covered if self._expected != 0: self._coverage = self._covered / self._expected else: self._coverage = 1.0 - def _CountCoverage(self, iterator: Iterable[CoverageState]): + def _CountCoverage(self, iterator: Iterable[CoverageState]) -> Tuple[int, int, int, int, int]: total = 0 excluded = 0 ignored = 0 @@ -153,6 +153,7 @@ def _CountCoverage(self, iterator: Iterable[CoverageState]): @export class AggregatedCoverage(Coverage): + _file: Path _aggregatedTotal: int _aggregatedExcluded: int _aggregatedIgnored: int @@ -162,6 +163,10 @@ class AggregatedCoverage(Coverage): _aggregatedCoverage: float + @readonly + def File(self) -> Path: + return self._file + @readonly def AggregatedTotal(self) -> int: return self._aggregatedTotal @@ -205,7 +210,7 @@ class ClassCoverage(Coverage): _methods: Dict[str, CoverageState] _classes: Dict[str, "ClassCoverage"] - def __init__(self, name: str, parent: Union["PackageCoverage", "ClassCoverage", None] = None): + def __init__(self, name: str, parent: Union["PackageCoverage", "ClassCoverage", None] = None) -> None: super().__init__(name, parent) if parent is not None: @@ -215,7 +220,7 @@ def __init__(self, name: str, parent: Union["PackageCoverage", "ClassCoverage", self._methods = {} self._classes = {} - def CalculateCoverage(self): + def CalculateCoverage(self) -> None: for cls in self._classes.values(): cls.CalculateCoverage() @@ -230,13 +235,11 @@ def CalculateCoverage(self): @export class ModuleCoverage(AggregatedCoverage): - _file: Path - _name: str _variables: Dict[str, CoverageState] _functions: Dict[str, CoverageState] _classes: Dict[str, ClassCoverage] - def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = None): + def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = None) -> None: super().__init__(name, parent) if parent is not None: @@ -248,11 +251,7 @@ def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = self._functions = {} self._classes = {} - @readonly - def File(self) -> Path: - return self._file - - def CalculateCoverage(self): + def CalculateCoverage(self) -> None: for cls in self._classes.values(): cls.CalculateCoverage() @@ -285,7 +284,6 @@ def Aggregate(self) -> None: @export class PackageCoverage(AggregatedCoverage): - _file: Path _fileCount: int _variables: Dict[str, CoverageState] _functions: Dict[str, CoverageState] @@ -293,7 +291,7 @@ class PackageCoverage(AggregatedCoverage): _modules: Dict[str, ModuleCoverage] _packages: Dict[str, "PackageCoverage"] - def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = None): + def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = None) -> None: super().__init__(name, parent) if parent is not None: @@ -307,10 +305,6 @@ def __init__(self, file: Path, name: str, parent: Nullable["PackageCoverage"] = self._modules = {} self._packages = {} - @readonly - def File(self) -> Path: - return self._file - @readonly def FileCount(self) -> int: return self._fileCount @@ -321,7 +315,7 @@ def __getitem__(self, key: str) -> Union["PackageCoverage", ModuleCoverage]: except KeyError: return self._packages[key] - def CalculateCoverage(self): + def CalculateCoverage(self) -> None: for cls in self._classes.values(): cls.CalculateCoverage() diff --git a/sphinx_reports/DocCoverage.py b/sphinx_reports/DocCoverage.py index 23a78584..d0a92401 100644 --- a/sphinx_reports/DocCoverage.py +++ b/sphinx_reports/DocCoverage.py @@ -32,17 +32,24 @@ **Report documentation coverage as Sphinx documentation page(s).** """ from pathlib import Path -from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator +from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator, TypedDict from docutils import nodes from pyTooling.Decorators import export -from sphinx_reports.Common import ReportExtensionError -from sphinx_reports.Sphinx import strip, LegendPosition, BaseDirective -from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage +from sphinx_reports.Common import ReportExtensionError, LegendPosition +from sphinx_reports.Sphinx import strip, BaseDirective +from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage, AggregatedCoverage from sphinx_reports.Adapter.DocStrCoverage import Analyzer +class package_DictType(TypedDict): + name: str + directory: str + fail_below: int + levels: Dict[int, Dict[str, str]] + + @export class DocCoverage(BaseDirective): """ @@ -71,17 +78,17 @@ class DocCoverage(BaseDirective): _levels: Dict[int, Dict[str, str]] _coverage: PackageCoverage - def _CheckOptions(self): + def _CheckOptions(self) -> None: # Parse all directive options or use default values self._packageID = self._ParseStringOption("packageid") self._legend = self._ParseLegendOption("legend", LegendPosition.Bottom) - def _CheckConfiguration(self): + def _CheckConfiguration(self) -> None: from sphinx_reports import ReportDomain # Check configuration fields and load necessary values try: - allPackages = self.config[f"{ReportDomain.name}_{self.configPrefix}_packages"] + allPackages: Dict[str, package_DictType] = self.config[f"{ReportDomain.name}_{self.configPrefix}_packages"] except (KeyError, AttributeError) as ex: raise ReportExtensionError(f"Configuration option '{ReportDomain.name}_{self.configPrefix}_packages' is not configured.") from ex @@ -120,7 +127,7 @@ def _CheckConfiguration(self): 100: {"class": "doccov-below100", "background": "rgba( 0, 200, 82, .2)", "desc": "excellent documented"}, } - def _ConvertToColor(self, currentLevel, configKey): + def _ConvertToColor(self, currentLevel: float, configKey: str) -> str: for levelLimit, levelConfig in self._levels.items(): if (currentLevel * 100) < levelLimit: return levelConfig[configKey] @@ -143,11 +150,11 @@ def _GenerateCoverageTable(self) -> nodes.table: tableBody = nodes.tbody() tableGroup += tableBody - def sortedValues(d: Mapping) -> Generator[Any, None, None]: + def sortedValues(d: Mapping[str, AggregatedCoverage]) -> Generator[AggregatedCoverage, None, None]: for key in sorted(d.keys()): yield d[key] - def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0): + def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0) -> None: tableBody += nodes.row( "", nodes.entry("", nodes.paragraph(text=f"{' '*level}{packageCoverage.Name} ({packageCoverage.File})")), @@ -222,7 +229,7 @@ def _CreateLegend(self, identifier: str, classes: Iterable[str]) -> List[nodes.E @export class DocStrCoverage(DocCoverage): - def run(self): + def run(self) -> List[nodes.Node]: self._CheckOptions() self._CheckConfiguration() diff --git a/sphinx_reports/Sphinx.py b/sphinx_reports/Sphinx.py index a5a9347f..e569c657 100644 --- a/sphinx_reports/Sphinx.py +++ b/sphinx_reports/Sphinx.py @@ -42,7 +42,7 @@ @export -def strip(option: str): +def strip(option: str) -> str: return option.strip().lower() @@ -96,7 +96,7 @@ def _ParseBooleanOption(self, optionName: str, default: Nullable[bool] = None) - def _ParseStringOption(self, optionName: str, default: Nullable[str] = None, regexp: str = "\\w+") -> str: try: - option = self.options[optionName] + option: str = self.options[optionName] except KeyError as ex: if default is not None: return default diff --git a/sphinx_reports/Unittest.py b/sphinx_reports/Unittest.py index 0464c769..1dc65a39 100644 --- a/sphinx_reports/Unittest.py +++ b/sphinx_reports/Unittest.py @@ -32,17 +32,24 @@ **Report unit test results as Sphinx documentation page(s).** """ from pathlib import Path -from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator +from typing import Dict, Tuple, Any, List, Iterable, Mapping, Generator, TypedDict from docutils import nodes from pyTooling.Decorators import export from sphinx_reports.Common import ReportExtensionError from sphinx_reports.Sphinx import strip, LegendPosition, BaseDirective -from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage +from sphinx_reports.DataModel.DocumentationCoverage import PackageCoverage, AggregatedCoverage from sphinx_reports.Adapter.DocStrCoverage import Analyzer +class package_DictType(TypedDict): + name: str + directory: str + fail_below: int + levels: Dict[int, Dict[str, str]] + + @export class UnittestSummary(BaseDirective): """ @@ -71,15 +78,15 @@ class UnittestSummary(BaseDirective): _levels: Dict[int, Dict[str, str]] _coverage: PackageCoverage - def _CheckOptions(self): + def _CheckOptions(self) -> None: # Parse all directive options or use default values self._packageID = self._ParseStringOption("packageid") self._legend = self._ParseLegendOption("legend", LegendPosition.Bottom) - def _CheckConfiguration(self): + def _CheckConfiguration(self) -> None: # Check configuration fields and load necessary values try: - allPackages = self.config[f"{self.configPrefix}_packages"] + allPackages: Dict[str, package_DictType] = self.config[f"{self.configPrefix}_packages"] except (KeyError, AttributeError) as ex: raise ReportExtensionError(f"Configuration option '{self.configPrefix}_packages' is not configured.") from ex @@ -118,7 +125,7 @@ def _CheckConfiguration(self): 100: {"class": "doccov-below100", "background": "rgba( 0, 200, 82, .2)", "desc": "excellent documented"}, } - def _ConvertToColor(self, currentLevel, configKey): + def _ConvertToColor(self, currentLevel: float, configKey: str) -> str: for levelLimit, levelConfig in self._levels.items(): if (currentLevel * 100) < levelLimit: return levelConfig[configKey] @@ -141,11 +148,11 @@ def _GenerateCoverageTable(self) -> nodes.table: tableBody = nodes.tbody() tableGroup += tableBody - def sortedValues(d: Mapping) -> Generator[Any, None, None]: + def sortedValues(d: Mapping[str, AggregatedCoverage]) -> Generator[AggregatedCoverage, None, None]: for key in sorted(d.keys()): yield d[key] - def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0): + def renderlevel(tableBody: nodes.tbody, packageCoverage: PackageCoverage, level: int = 0) -> None: tableBody += nodes.row( "", nodes.entry("", nodes.paragraph(text=f"{' '*level}{packageCoverage.Name} ({packageCoverage.File})")), @@ -217,7 +224,7 @@ def _CreateLegend(self, identifier: str, classes: Iterable[str]) -> List[nodes.E return [rubric, table] - def run(self): + def run(self) -> List[nodes.Node]: self._CheckOptions() self._CheckConfiguration() diff --git a/sphinx_reports/__init__.py b/sphinx_reports/__init__.py index e916cb82..98644993 100644 --- a/sphinx_reports/__init__.py +++ b/sphinx_reports/__init__.py @@ -46,7 +46,7 @@ __version__ = "0.3.0" __keywords__ = ["Python3", "Sphinx", "Extension", "Report", "doc-string", "interrogate"] -from typing import Any, Tuple, Dict, Optional as Nullable +from typing import Any, Tuple, Dict, Optional as Nullable, TypedDict, List from docutils import nodes from pyTooling.Decorators import export @@ -85,7 +85,7 @@ class ReportDomain(Domain): name = "report" #: The name of this domain label = "rpt" #: The label of this domain - dependencies = [ + dependencies: List[str] = [ ] #: A list of other extensions this domain depends on. from sphinx_reports.CodeCoverage import CodeCoverage @@ -103,9 +103,9 @@ class ReportDomain(Domain): # "design": DesignRole, } #: A dictionary of all roles in this domain. - indices = { + indices = [ # LibraryIndex, - } #: A dictionary of all indices in this domain. + ] #: A list of all indices in this domain. configValues: Dict[str, Tuple[Any, str, Any]] = { "designs": ({}, "env", Dict), @@ -157,8 +157,15 @@ def resolve_xref( raise NotImplementedError() +class setup_ReturnType(TypedDict): + version: str + env_version: int + parallel_read_safe: bool + parallel_write_safe: bool + + @export -def setup(sphinxApplication: Sphinx): +def setup(sphinxApplication: Sphinx) -> setup_ReturnType: """ Extension setup function registering the ``report`` domain in Sphinx.