Coverage for sphinx_reports/__init__.py: 55%

112 statements  

« prev     ^ index     » next       coverage.py v7.15.1, created at 2026-07-13 18:02 +0000

1# ==================================================================================================================== # 

2# _ _ _ # 

3# ___ _ __ | |__ (_)_ __ __ __ _ __ ___ _ __ ___ _ __| |_ ___ # 

4# / __| '_ \| '_ \| | '_ \\ \/ /____| '__/ _ \ '_ \ / _ \| '__| __/ __| # 

5# \__ \ |_) | | | | | | | |> <_____| | | __/ |_) | (_) | | | |_\__ \ # 

6# |___/ .__/|_| |_|_|_| |_/_/\_\ |_| \___| .__/ \___/|_| \__|___/ # 

7# |_| |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2023-2026 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32**A Sphinx domain providing directives to add reports to the Sphinx-based documentation.** 

33 

34Supported reports: 

35 

36* :ref:`UNITTEST` 

37* :ref:`DOCCOV` 

38* :ref:`CODECOV` 

39* :ref:`DEP` 

40 

41""" 

42__author__ = "Patrick Lehmann" 

43__email__ = "Paebbels@gmail.com" 

44__copyright__ = "2023-2026, Patrick Lehmann" 

45__license__ = "Apache License, Version 2.0" 

46__version__ = "0.11.1" 

47__keywords__ = [ 

48 "Python3", "Sphinx", "Extension", "Report", "doc-string", "interrogate", "Code Coverage", "Coverage", 

49 "Documentation Coverage", "Unittest", "Dependencies", "Summary" 

50] 

51__project_url__ = "https://github.com/pyTooling/sphinx-reports" 

52__documentation_url__ = "https://pyTooling.github.io/sphinx-reports" 

53__issue_tracker_url__ = "https://GitHub.com/pyTooling/sphinx-reports/issues" 

54 

55from hashlib import md5 

56from pathlib import Path 

57from typing import TYPE_CHECKING, Any, Tuple, Dict, Optional as Nullable, TypedDict, List, Callable, Type 

58 

59from docutils.nodes import Element 

60from docutils.transforms import Transform 

61from sphinx.addnodes import pending_xref 

62from sphinx.application import Sphinx 

63from sphinx.builders import Builder 

64from sphinx.config import Config 

65from sphinx.domains import Domain 

66from sphinx.environment import BuildEnvironment 

67from sphinx.util.logging import getLogger 

68from pyTooling.Decorators import export 

69from pyTooling.Common import readResourceFile 

70 

71from sphinx_reports import static as ResourcePackage 

72from sphinx_reports.Common import ReportExtensionError, visitFunc, departFunc 

73from sphinx_reports.Node import Landscape 

74from sphinx_reports.Workaround import FixLatexTableWidths 

75from sphinx_reports.HTML import translateLandscape as translateLandscapeAsHTML 

76from sphinx_reports.LaTeX import translateLandscape as translateLandscapeAsLaTeX 

77 

78 

79@export 

80class RegisteredNode(TypedDict): 

81 """ 

82 Type information for an entry in :attr:`ReportDomain.nodes`. 

83 """ 

84 name: str #: Name of the new docutils node to register. 

85 node: Type[Element] #: The new node class to register. 

86 html: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of HTML output. 

87 latex: Tuple[visitFunc, departFunc] #: A tuple of visit and depart functions rendering the new node in case of LaTeX output. 

88 

89 

90@export 

91class ReportDomain(Domain): 

92 """ 

93 A Sphinx extension providing a ``report`` domain to integrate reports and summaries into a Sphinx-based documentation. 

94 

95 .. rubric:: New directives: 

96 

97 * :rst:dir:`report:code-coverage` 

98 * :rst:dir:`report:code-coverage-legend` 

99 * :rst:dir:`report:doc-coverage` 

100 * :rst:dir:`report:doc-coverage-legend` 

101 * :rst:dir:`report:dependency-table` 

102 * :rst:dir:`report:unittest-summary` 

103 

104 .. rubric:: New roles: 

105 

106 * *None* 

107 

108 .. rubric:: New indices: 

109 

110 * *None* 

111 

112 .. rubric:: Configuration variables 

113 

114 All configuration variables in :file:`conf.py` are prefixed with ``report_*``: 

115 

116 * ``report_codecov_packages`` 

117 * ``report_doccov_packages`` 

118 * ``report_unittest_testsuites`` 

119 

120 """ 

121 

122 name = "report" #: The name of this domain 

123 label = "rpt" #: The label of this domain 

124 

125 dependencies: List[str] = [ 

126 ] #: A list of other extensions this domain depends on. 

127 

128 latexPackages: Tuple[str, ...] = ( 

129 "pdflscape", 

130 ) 

131 nodes: Tuple[RegisteredNode, ...] = ( 

132 { "name": "Landscape", 

133 "node": Landscape, 

134 "html": translateLandscapeAsHTML, 

135 "latex": translateLandscapeAsLaTeX 

136 }, 

137 ) 

138 transformations: Tuple[Type[Transform], ...] = ( 

139 FixLatexTableWidths, 

140 ) 

141 

142 from sphinx_reports.CodeCoverage import CodeCoverage, CodeCoverageLegend, ModuleCoverage 

143 from sphinx_reports.DocCoverage import DocStrCoverage, DocCoverageLegend 

144 from sphinx_reports.Dependency import DependencyTable 

145 from sphinx_reports.Unittest import UnittestSummary 

146 

147 directives = { 

148 "code-coverage": CodeCoverage, 

149 "code-coverage-legend": CodeCoverageLegend, 

150 "module-coverage": ModuleCoverage, 

151 "doc-coverage": DocStrCoverage, 

152 "doc-coverage-legend": DocCoverageLegend, 

153 "dependency-table": DependencyTable, 

154 "unittest-summary": UnittestSummary, 

155 } #: A dictionary of all directives in this domain. 

156 

157 roles = { 

158 # "design": DesignRole, 

159 } #: A dictionary of all roles in this domain. 

160 

161 indices = [ 

162 # LibraryIndex, 

163 ] #: A list of all indices in this domain. 

164 

165 from sphinx_reports.CodeCoverage import CodeCoverageBase 

166 from sphinx_reports.DocCoverage import DocCoverageBase 

167 from sphinx_reports.Dependency import DependencyTable 

168 from sphinx_reports.Unittest import UnittestSummary 

169 

170 configValues: Dict[str, Tuple[Any, str, Any]] = { 

171 **CodeCoverageBase.configValues, 

172 **DocCoverageBase.configValues, 

173 **UnittestSummary.configValues, 

174 **DependencyTable.configValues, 

175 } #: A dictionary of all configuration values used by this domain. (name: (default, rebuilt, type)) 

176 

177 del CodeCoverageBase 

178 del CodeCoverage 

179 del CodeCoverageLegend 

180 del ModuleCoverage 

181 del DocCoverageBase 

182 del DocStrCoverage 

183 del DocCoverageLegend 

184 del DependencyTable 

185 del UnittestSummary 

186 

187 initial_data = { 

188 # "reports": {} 

189 } #: A dictionary of all global data fields used by this domain. 

190 

191 # @property 

192 # def Reports(self) -> Dict[str, Any]: 

193 # return self.data["reports"] 

194 

195 @staticmethod 

196 def CheckConfigurationVariables(sphinxApplication: Sphinx, config: Config) -> None: 

197 """ 

198 Call back for Sphinx ``config-inited`` event. 

199 

200 This callback will verify configuration variables used by that domain. 

201 

202 .. seealso:: 

203 

204 Sphinx *builder-inited* event 

205 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

206 

207 :param sphinxApplication: The Sphinx application. 

208 :param config: Sphinx configuration parsed from ``conf.py``. 

209 """ 

210 from sphinx_reports.CodeCoverage import CodeCoverageBase 

211 from sphinx_reports.DocCoverage import DocCoverageBase 

212 from sphinx_reports.Unittest import UnittestSummary 

213 

214 checkConfigurations = ( 

215 CodeCoverageBase.CheckConfiguration, 

216 DocCoverageBase.CheckConfiguration, 

217 UnittestSummary.CheckConfiguration, 

218 ) 

219 

220 for checkConfiguration in checkConfigurations: 

221 try: 

222 checkConfiguration(sphinxApplication, config) 

223 except ReportExtensionError as ex: 

224 logger = getLogger(__name__) 

225 logger.error(f"Caught {ex.__class__.__name__} when checking configuration variables.\n {ex}") 

226 

227 @staticmethod 

228 def AddCSSFiles(sphinxApplication: Sphinx) -> None: 

229 """ 

230 Call back for Sphinx ``builder-inited`` event. 

231 

232 This callback will copy the CSS file(s) to the build directory. 

233 

234 .. seealso:: 

235 

236 Sphinx *builder-inited* event 

237 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

238 

239 :param sphinxApplication: The Sphinx application. 

240 """ 

241 # Create a new static path for this extension 

242 staticDirectory = (Path(sphinxApplication.outdir) / "_report_static").resolve() 

243 staticDirectory.mkdir(exist_ok=True) 

244 sphinxApplication.config.html_static_path.append(str(staticDirectory)) 

245 

246 # Read the CSS content from package resources and hash it 

247 cssFilename = "sphinx-reports.css" 

248 cssContent = readResourceFile(ResourcePackage, cssFilename) 

249 

250 # Compute md5 hash of CSS file 

251 hash = md5(cssContent.encode("utf8")).hexdigest() # nosec B324 

252 

253 # Write the CSS file into output directory 

254 cssFile = staticDirectory / f"sphinx-reports.{hash}.css" 

255 sphinxApplication.add_css_file(cssFile.name) 

256 

257 if not cssFile.exists(): 

258 # Purge old CSS files 

259 for file in staticDirectory.glob("*.css"): 

260 file.unlink() 

261 

262 # Write CSS content 

263 cssFile.write_text(cssContent, encoding="utf8") 

264 

265 @staticmethod 

266 def ReadReports(sphinxApplication: Sphinx) -> None: 

267 """ 

268 Call back for Sphinx ``builder-inited`` event. 

269 

270 This callback will read the linked report files 

271 

272 .. seealso:: 

273 

274 Sphinx *builder-inited* event 

275 See https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events 

276 

277 :param sphinxApplication: The Sphinx application. 

278 """ 

279 from sphinx_reports.CodeCoverage import CodeCoverageBase 

280 from sphinx_reports.Unittest import UnittestSummary 

281 

282 CodeCoverageBase.ReadReports(sphinxApplication) 

283 UnittestSummary.ReadReports(sphinxApplication) 

284 

285 callbacks: Dict[str, List[Callable]] = { 

286 "config-inited": [CheckConfigurationVariables], # (app, config) 

287 "builder-inited": [AddCSSFiles, ReadReports], # (app) 

288 } #: A dictionary of all events/callbacks <https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx-core-events>`__ used by this domain. 

289 

290 def resolve_xref( 

291 self, 

292 env: BuildEnvironment, 

293 fromdocname: str, 

294 builder: Builder, 

295 typ: str, 

296 target: str, 

297 node: pending_xref, 

298 contnode: Element 

299 ) -> Nullable[Element]: 

300 raise NotImplementedError() 

301 

302 

303if TYPE_CHECKING: 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true

304 class setup_ReturnType(TypedDict): 

305 version: str 

306 env_version: int 

307 parallel_read_safe: bool 

308 parallel_write_safe: bool 

309 

310 

311@export 

312def setup(sphinxApplication: Sphinx) -> "setup_ReturnType": 

313 """ 

314 Extension setup function registering the ``report`` domain in Sphinx. 

315 

316 It will execute these steps: 

317 

318 * register domains, directives and roles. 

319 * connect events (register callbacks) 

320 * register configuration variables for :file:`conf.py` 

321 

322 :param sphinxApplication: The Sphinx application. 

323 :return: Dictionary containing the extension version and some properties. 

324 """ 

325 sphinxApplication.add_domain(ReportDomain) 

326 

327 # Request new LaTeX package dependencies 

328 for latexPackage in ReportDomain.latexPackages: 

329 sphinxApplication.add_latex_package(latexPackage) 

330 

331 # Register new docutil nodes. 

332 for newNode in ReportDomain.nodes: 

333 sphinxApplication.add_node(newNode["node"], html=newNode["html"], latex=newNode["latex"]) 

334 

335 # Register transformations 

336 for transformation in ReportDomain.transformations: 

337 sphinxApplication.add_post_transform(transformation) 

338 

339 # Register callbacks 

340 for eventName, callbacks in ReportDomain.callbacks.items(): 

341 for callback in callbacks: 

342 sphinxApplication.connect(eventName, callback) 

343 

344 # Register configuration options supported/needed in Sphinx's 'conf.py' 

345 for configName, (configDefault, configRebuilt, configTypes) in ReportDomain.configValues.items(): 

346 sphinxApplication.add_config_value(f"{ReportDomain.name}_{configName}", configDefault, configRebuilt, configTypes) 

347 

348 return { 

349 "version": __version__, # version of the extension 

350 "env_version": int(__version__.split(".")[0]), # version of the data structure stored in the environment 

351 'parallel_read_safe': True, # TODO: Not yet evaluated 

352 'parallel_write_safe': True, # Internal data structure is used read-only, thus no problems will occur by parallel writing. 

353 }