Coverage for sphinx_reports/__init__.py: 54%

97 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-09 22:12 +0000

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

2# _ _ _ # 

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

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

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

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

7# |_| |_| # 

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2023-2025 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-2025, Patrick Lehmann" 

45__license__ = "Apache License, Version 2.0" 

46__version__ = "0.9.4" 

47__keywords__ = [ 

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

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

50] 

51 

52from hashlib import md5 

53from pathlib import Path 

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

55 

56from docutils import nodes 

57from sphinx.addnodes import pending_xref 

58from sphinx.application import Sphinx 

59from sphinx.builders import Builder 

60from sphinx.config import Config 

61from sphinx.domains import Domain 

62from sphinx.environment import BuildEnvironment 

63from sphinx.util.logging import getLogger 

64from pyTooling.Decorators import export 

65from pyTooling.Common import readResourceFile 

66 

67from sphinx_reports import static as ResourcePackage 

68from sphinx_reports.Common import ReportExtensionError 

69 

70 

71@export 

72class ReportDomain(Domain): 

73 """ 

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

75 

76 .. rubric:: New directives: 

77 

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

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

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

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

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

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

84 

85 .. rubric:: New roles: 

86 

87 * *None* 

88 

89 .. rubric:: New indices: 

90 

91 * *None* 

92 

93 .. rubric:: Configuration variables 

94 

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

96 

97 * ``report_codecov_packages`` 

98 * ``report_doccov_packages`` 

99 * ``report_unittest_testsuites`` 

100 

101 """ 

102 

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

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

105 

106 dependencies: List[str] = [ 

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

108 

109 from sphinx_reports.CodeCoverage import CodeCoverage, CodeCoverageLegend, ModuleCoverage 

110 from sphinx_reports.DocCoverage import DocStrCoverage, DocCoverageLegend 

111 from sphinx_reports.Dependency import DependencyTable 

112 from sphinx_reports.Unittest import UnittestSummary 

113 

114 directives = { 

115 "code-coverage": CodeCoverage, 

116 "code-coverage-legend": CodeCoverageLegend, 

117 "module-coverage": ModuleCoverage, 

118 "doc-coverage": DocStrCoverage, 

119 "doc-coverage-legend": DocCoverageLegend, 

120 "dependency-table": DependencyTable, 

121 "unittest-summary": UnittestSummary, 

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

123 

124 roles = { 

125 # "design": DesignRole, 

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

127 

128 indices = [ 

129 # LibraryIndex, 

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

131 

132 from sphinx_reports.CodeCoverage import CodeCoverageBase 

133 from sphinx_reports.DocCoverage import DocCoverageBase 

134 from sphinx_reports.Dependency import DependencyTable 

135 from sphinx_reports.Unittest import UnittestSummary 

136 

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

138 **CodeCoverageBase.configValues, 

139 **DocCoverageBase.configValues, 

140 **UnittestSummary.configValues, 

141 **DependencyTable.configValues, 

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

143 

144 del CodeCoverageBase 

145 del CodeCoverage 

146 del CodeCoverageLegend 

147 del ModuleCoverage 

148 del DocCoverageBase 

149 del DocStrCoverage 

150 del DocCoverageLegend 

151 del DependencyTable 

152 del UnittestSummary 

153 

154 initial_data = { 

155 # "reports": {} 

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

157 

158 # @property 

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

160 # return self.data["reports"] 

161 

162 @staticmethod 

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

164 """ 

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

166 

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

168 

169 .. seealso:: 

170 

171 Sphinx *builder-inited* event 

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

173 

174 :param sphinxApplication: The Sphinx application. 

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

176 """ 

177 from sphinx_reports.CodeCoverage import CodeCoverageBase 

178 from sphinx_reports.DocCoverage import DocCoverageBase 

179 from sphinx_reports.Unittest import UnittestSummary 

180 

181 checkConfigurations = ( 

182 CodeCoverageBase.CheckConfiguration, 

183 DocCoverageBase.CheckConfiguration, 

184 UnittestSummary.CheckConfiguration, 

185 ) 

186 

187 for checkConfiguration in checkConfigurations: 

188 try: 

189 checkConfiguration(sphinxApplication, config) 

190 except ReportExtensionError as ex: 

191 logger = getLogger(__name__) 

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

193 

194 @staticmethod 

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

196 """ 

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

198 

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

200 

201 .. seealso:: 

202 

203 Sphinx *builder-inited* event 

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

205 

206 :param sphinxApplication: The Sphinx application. 

207 """ 

208 # Create a new static path for this extension 

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

210 staticDirectory.mkdir(exist_ok=True) 

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

212 

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

214 cssFilename = "sphinx-reports.css" 

215 cssContent = readResourceFile(ResourcePackage, cssFilename) 

216 

217 # Compute md5 hash of CSS file 

218 hash = md5(cssContent.encode("utf8")).hexdigest() 

219 

220 # Write the CSS file into output directory 

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

222 sphinxApplication.add_css_file(cssFile.name) 

223 

224 if not cssFile.exists(): 

225 # Purge old CSS files 

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

227 file.unlink() 

228 

229 # Write CSS content 

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

231 

232 @staticmethod 

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

234 """ 

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

236 

237 This callback will read the linked report files 

238 

239 .. seealso:: 

240 

241 Sphinx *builder-inited* event 

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

243 

244 :param sphinxApplication: The Sphinx application. 

245 """ 

246 from sphinx_reports.CodeCoverage import CodeCoverageBase 

247 from sphinx_reports.Unittest import UnittestSummary 

248 

249 CodeCoverageBase.ReadReports(sphinxApplication) 

250 UnittestSummary.ReadReports(sphinxApplication) 

251 

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

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

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

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

256 

257 def resolve_xref( 

258 self, 

259 env: BuildEnvironment, 

260 fromdocname: str, 

261 builder: Builder, 

262 typ: str, 

263 target: str, 

264 node: pending_xref, 

265 contnode: nodes.Element 

266 ) -> Nullable[nodes.Element]: 

267 raise NotImplementedError() 

268 

269 

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

271 class setup_ReturnType(TypedDict): 

272 version: str 

273 env_version: int 

274 parallel_read_safe: bool 

275 parallel_write_safe: bool 

276 

277 

278@export 

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

280 """ 

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

282 

283 It will execute these steps: 

284 

285 * register domains, directives and roles. 

286 * connect events (register callbacks) 

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

288 

289 :param sphinxApplication: The Sphinx application. 

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

291 """ 

292 sphinxApplication.add_domain(ReportDomain) 

293 

294 # Register callbacks 

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

296 for callback in callbacks: 

297 sphinxApplication.connect(eventName, callback) 

298 

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

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

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

302 

303 return { 

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

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

306 'parallel_read_safe': False, # Not yet evaluated, thus false 

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

308 } 

309 

310