Coverage for pyEDAA/OSVVM/AlertLog.py: 80%

274 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-27 22:24 +0000

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

2# _____ ____ _ _ ___ ______ ____ ____ __ # 

3# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| # 

7# |_| |___/ # 

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2021-2025 Electronic Design Automation Abstraction (EDA²) # 

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"""A data model for OSVVM's AlertLog YAML file format.""" 

32from datetime import timedelta 

33from enum import Enum, auto 

34from pathlib import Path 

35from typing import Optional as Nullable, Dict, Iterator, Iterable, Callable 

36 

37from ruamel.yaml import YAML, CommentedSeq, CommentedMap 

38from pyTooling.Decorators import readonly, export 

39from pyTooling.MetaClasses import ExtendedType 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Stopwatch import Stopwatch 

42from pyTooling.Tree import Node 

43 

44from pyEDAA.OSVVM import OSVVMException 

45 

46 

47@export 

48class AlertLogException(OSVVMException): 

49 """Base-class for all pyEDAA.OSVVM.AlertLog specific exceptions.""" 

50 

51 

52@export 

53class DuplicateItemException(AlertLogException): 

54 """Raised if a duplicate item is detected in the AlertLog hierarchy.""" 

55 

56 

57@export 

58class AlertLogStatus(Enum): 

59 """Status of an :class:`AlertLogItem`.""" 

60 Unknown = auto() 

61 Passed = auto() 

62 Failed = auto() 

63 

64 __MAPPINGS__ = { 

65 "passed": Passed, 

66 "failed": Failed 

67 } 

68 

69 @classmethod 

70 def Parse(self, name: str) -> "AlertLogStatus": 

71 try: 

72 return self.__MAPPINGS__[name.lower()] 

73 except KeyError as ex: 

74 raise AlertLogException(f"Unknown AlertLog status '{name}'.") from ex 

75 

76 def __bool__(self) -> bool: 

77 """ 

78 Convert an *AlertLogStatus* to a boolean. 

79 

80 :returns: Return true, if the status is ``Passed``. 

81 """ 

82 return self is self.Passed 

83 

84 

85@export 

86def _format(node: Node) -> str: 

87 """ 

88 User-defined :external+pyTool:ref:`pyTooling Tree <STRUCT/Tree>` formatting function for nodes referencing :class:`AlertLogItems <AlertLogItem>`. 

89 

90 :param node: Node to format. 

91 :returns: String representation (one-liner) describing an AlertLogItem. 

92 """ 

93 return f"{node["Name"]}: {node["TotalErrors"]}={node["AlertCountFailures"]}/{node["AlertCountErrors"]}/{node["AlertCountWarnings"]} {node["PassedCount"]}/{node["AffirmCount"]}" 

94 

95 

96@export 

97class AlertLogItem(metaclass=ExtendedType, slots=True): 

98 """ 

99 An *AlertLogItem* represents an AlertLog hierarchy item. 

100 

101 An item has a reference to its parent item in the AlertLog hierarchy. If the item is the top-most element (root 

102 element), the parent reference is ``None``. 

103 

104 An item can contain further child items. 

105 """ 

106 _parent: "AlertLogItem" #: Reference to the parent item. 

107 _name: str #: Name of the AlertLog item. 

108 _children: Dict[str, "AlertLogItem"] #: Dictionary of child items. 

109 

110 _status: AlertLogStatus #: AlertLog item's status 

111 _totalErrors: int #: Total number of warnings, errors and failures. 

112 _alertCountWarnings: int #: Warning count. 

113 _alertCountErrors: int #: Error count. 

114 _alertCountFailures: int #: Failure count. 

115 _passedCount: int #: Passed affirmation count. 

116 _affirmCount: int #: Overall affirmation count (incl. failed affirmations). 

117 _requirementsPassed: int #: Count of passed requirements. 

118 _requirementsGoal: int #: Overall expected requirements. 

119 _disabledAlertCountWarnings: int #: Count of disabled warnings. 

120 _disabledAlertCountErrors: int #: Count of disabled errors. 

121 _disabledAlertCountFailures: int #: Count of disabled failures. 

122 

123 def __init__( 

124 self, 

125 name: str, 

126 status: AlertLogStatus = AlertLogStatus.Unknown, 

127 totalErrors: int = 0, 

128 alertCountWarnings: int = 0, 

129 alertCountErrors: int = 0, 

130 alertCountFailures: int = 0, 

131 passedCount: int = 0, 

132 affirmCount: int = 0, 

133 requirementsPassed: int = 0, 

134 requirementsGoal: int = 0, 

135 disabledAlertCountWarnings: int = 0, 

136 disabledAlertCountErrors: int = 0, 

137 disabledAlertCountFailures: int = 0, 

138 children: Iterable["AlertLogItem"] = None, 

139 parent: Nullable["AlertLogItem"] = None 

140 ) -> None: 

141 self._name = name 

142 self._parent = parent 

143 if parent is not None: 

144 if not isinstance(parent, AlertLogItem): 144 ↛ 145line 144 didn't jump to line 145 because the condition on line 144 was never true

145 ex = TypeError(f"Parameter 'parent' is not an AlertLogItem.") 

146 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

147 raise ex 

148 elif name in parent._children: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true

149 raise DuplicateItemException(f"AlertLogItem '{name}' already exists in '{parent._name}'.") 

150 

151 parent._children[name] = self 

152 

153 self._children = {} 

154 if children is not None: 

155 for child in children: 

156 if not isinstance(child, AlertLogItem): 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true

157 ex = TypeError(f"Item in parameter 'children' is not an AlertLogItem.") 

158 ex.add_note(f"Got type '{getFullyQualifiedName(child)}'.") 

159 raise ex 

160 elif child._name in self._children: 160 ↛ 161line 160 didn't jump to line 161 because the condition on line 160 was never true

161 raise DuplicateItemException(f"AlertLogItem '{child._name}' already exists in '{self._name}'.") 

162 elif child._parent is not None: 162 ↛ 163line 162 didn't jump to line 163 because the condition on line 162 was never true

163 raise AlertLogException(f"AlertLogItem '{child._name}' is already part of another AlertLog hierarchy ({child._parent._name}).") 

164 

165 self._children[child._name] = child 

166 child._parent = self 

167 

168 self._status = status 

169 self._totalErrors = totalErrors 

170 self._alertCountWarnings = alertCountWarnings 

171 self._alertCountErrors = alertCountErrors 

172 self._alertCountFailures = alertCountFailures 

173 self._passedCount = passedCount 

174 self._affirmCount = affirmCount 

175 self._requirementsPassed = requirementsPassed 

176 self._requirementsGoal = requirementsGoal 

177 self._disabledAlertCountWarnings = disabledAlertCountWarnings 

178 self._disabledAlertCountErrors = disabledAlertCountErrors 

179 self._disabledAlertCountFailures = disabledAlertCountFailures 

180 

181 @property 

182 def Parent(self) -> Nullable["AlertLogItem"]: 

183 """ 

184 Property to access the parent item of this item (:attr:`_parent`). 

185 

186 :returns: The item's parent item. ``None``, if it's the top-most item (root). 

187 """ 

188 return self._parent 

189 

190 @Parent.setter 

191 def Parent(self, value: Nullable["AlertLogItem"]) -> None: 

192 if value is None: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true

193 del self._parent._children[self._name] 

194 else: 

195 if not isinstance(value, AlertLogItem): 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true

196 ex = TypeError(f"Parameter 'value' is not an AlertLogItem.") 

197 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

198 raise ex 

199 elif self._name in value._children: 199 ↛ 200line 199 didn't jump to line 200 because the condition on line 199 was never true

200 raise DuplicateItemException(f"AlertLogItem '{self._name}' already exists in '{value._name}'.") 

201 

202 value._children[self._name] = self 

203 

204 self._parent = value 

205 

206 @readonly 

207 def Name(self) -> str: 

208 """ 

209 Read-only property to access the AlertLog item's name (:attr:`_name`). 

210 

211 :returns: AlertLog item's name. 

212 """ 

213 return self._name 

214 

215 @readonly 

216 def Status(self) -> AlertLogStatus: 

217 """ 

218 Read-only property to access the AlertLog item's status (:attr:`_status`). 

219 

220 :returns: AlertLog item's status. 

221 """ 

222 return self._status 

223 

224 @readonly 

225 def TotalErrors(self) -> int: 

226 """ 

227 Read-only property to access the AlertLog item's total error count (:attr:`_totalErrors`). 

228 

229 :returns: AlertLog item's total errors. 

230 """ 

231 return self._totalErrors 

232 

233 @readonly 

234 def AlertCountWarnings(self) -> int: 

235 """ 

236 Read-only property to access the AlertLog item's warning count (:attr:`_alertCountWarnings`). 

237 

238 :returns: AlertLog item's warning count. 

239 """ 

240 return self._alertCountWarnings 

241 

242 @readonly 

243 def AlertCountErrors(self) -> int: 

244 """ 

245 Read-only property to access the AlertLog item's error count (:attr:`_alertCountErrors`). 

246 

247 :returns: AlertLog item's error count. 

248 """ 

249 return self._alertCountErrors 

250 

251 @readonly 

252 def AlertCountFailures(self) -> int: 

253 """ 

254 Read-only property to access the AlertLog item's failure count (:attr:`_alertCountFailures`). 

255 

256 :returns: AlertLog item's failure count. 

257 """ 

258 return self._alertCountFailures 

259 

260 @readonly 

261 def PassedCount(self) -> int: 

262 """ 

263 Read-only property to access the AlertLog item's passed affirmation count (:attr:`_alertCountFailures`). 

264 

265 :returns: AlertLog item's passed affirmations. 

266 """ 

267 return self._passedCount 

268 

269 @readonly 

270 def AffirmCount(self) -> int: 

271 """ 

272 Read-only property to access the AlertLog item's overall affirmation count (:attr:`_affirmCount`). 

273 

274 :returns: AlertLog item's overall affirmations. 

275 """ 

276 return self._affirmCount 

277 

278 @readonly 

279 def RequirementsPassed(self) -> int: 

280 return self._requirementsPassed 

281 

282 @readonly 

283 def RequirementsGoal(self) -> int: 

284 return self._requirementsGoal 

285 

286 @readonly 

287 def DisabledAlertCountWarnings(self) -> int: 

288 """ 

289 Read-only property to access the AlertLog item's count of disabled warnings (:attr:`_disabledAlertCountWarnings`). 

290 

291 :returns: AlertLog item's count of disabled warnings. 

292 """ 

293 return self._disabledAlertCountWarnings 

294 

295 @readonly 

296 def DisabledAlertCountErrors(self) -> int: 

297 """ 

298 Read-only property to access the AlertLog item's count of disabled errors (:attr:`_disabledAlertCountErrors`). 

299 

300 :returns: AlertLog item's count of disabled errors. 

301 """ 

302 return self._disabledAlertCountErrors 

303 

304 @readonly 

305 def DisabledAlertCountFailures(self) -> int: 

306 """ 

307 Read-only property to access the AlertLog item's count of disabled failures (:attr:`_disabledAlertCountFailures`). 

308 

309 :returns: AlertLog item's count of disabled failures. 

310 """ 

311 return self._disabledAlertCountFailures 

312 

313 @readonly 

314 def Children(self) -> Dict[str, "AlertLogItem"]: 

315 return self._children 

316 

317 def __iter__(self) -> Iterator["AlertLogItem"]: 

318 """ 

319 Iterate all child AlertLog items. 

320 

321 :return: An iterator of child items. 

322 """ 

323 return iter(self._children.values()) 

324 

325 def __len__(self) -> int: 

326 """ 

327 Returns number of child AlertLog items. 

328 

329 :returns: The number of nested AlertLog items. 

330 """ 

331 return len(self._children) 

332 

333 def __getitem__(self, name: str) -> "AlertLogItem": 

334 """Index access for returning child AlertLog items. 

335 

336 :param name: The child's name. 

337 :returns: The referenced child. 

338 :raises KeyError: When the child referenced by parameter 'name' doesn't exist. 

339 """ 

340 return self._children[name] 

341 

342 def ToTree(self, format: Callable[[Node], str] = _format) -> Node: 

343 """ 

344 Convert the AlertLog hierarchy starting from this AlertLog item to a :external+pyTool:ref:`pyTooling Tree <STRUCT/Tree>`. 

345 

346 :params format: A user-defined :external+pyTool:ref:`pyTooling Tree <STRUCT/Tree>` formatting function. 

347 :returns: A tree of nodes referencing an AlertLog item. 

348 """ 

349 node = Node( 

350 value=self, 

351 keyValuePairs={ 

352 "Name": self._name, 

353 "TotalErrors": self._totalErrors, 

354 "AlertCountFailures": self._alertCountFailures, 

355 "AlertCountErrors": self._alertCountErrors, 

356 "AlertCountWarnings": self._alertCountWarnings, 

357 "PassedCount": self._passedCount, 

358 "AffirmCount": self._affirmCount 

359 }, 

360 children=(child.ToTree() for child in self._children.values()), 

361 format=format 

362 ) 

363 

364 return node 

365 

366 

367@export 

368class Settings(metaclass=ExtendedType, mixin=True): 

369 _externalWarningCount: int 

370 _externalErrorCount: int 

371 _externalFailureCount: int 

372 _failOnDisabledErrors: bool 

373 _failOnRequirementErrors: bool 

374 _failOnWarning: bool 

375 

376 def __init__(self) -> None: 

377 self._externalWarningCount = 0 

378 self._externalErrorCount = 0 

379 self._externalFailureCount = 0 

380 self._failOnDisabledErrors = False 

381 self._failOnRequirementErrors = True 

382 self._failOnWarning = False 

383 

384 

385@export 

386class Document(AlertLogItem, Settings): 

387 """ 

388 An *AlertLog Document* represents an OSVVM AlertLog report document (YAML file). 

389 

390 The document inherits :class:`AlertLogItem` and represents the AlertLog hierarchy's root element. 

391 

392 When analyzing and converting the document, the YAML analysis duration as well as the model conversion duration gets 

393 captured. 

394 """ 

395 

396 _path: Path #: Path to the YAML file. 

397 _yamlDocument: Nullable[YAML] #: Internal YAML document instance. 

398 

399 _analysisDuration: Nullable[timedelta] #: YAML file analysis duration in seconds. 

400 _modelConversionDuration: Nullable[timedelta] #: Data structure conversion duration in seconds. 

401 

402 def __init__(self, filename: Path, analyzeAndConvert: bool = False) -> None: 

403 """ 

404 Initializes an AlertLog YAML document. 

405 

406 :param filename: Path to the YAML file. 

407 :param analyzeAndConvert: If true, analyze the YAML document and convert the content to an AlertLog data model instance. 

408 """ 

409 super().__init__("", parent=None) 

410 Settings.__init__(self) 

411 

412 self._path = filename 

413 self._yamlDocument = None 

414 

415 self._analysisDuration = None 

416 self._modelConversionDuration = None 

417 

418 if analyzeAndConvert: 

419 self.Analyze() 

420 self.Parse() 

421 

422 @property 

423 def Path(self) -> Path: 

424 """ 

425 Read-only property to access the path to the YAML file of this document (:attr:`_path`). 

426 

427 :returns: The document's path to the YAML file. 

428 """ 

429 return self._path 

430 

431 @readonly 

432 def AnalysisDuration(self) -> timedelta: 

433 """ 

434 Read-only property to access the time spent for YAML file analysis (:attr:`_analysisDuration`). 

435 

436 :returns: The YAML file analysis duration. 

437 """ 

438 if self._analysisDuration is None: 

439 raise AlertLogException(f"Document '{self._path}' was not analyzed.") 

440 

441 return self._analysisDuration 

442 

443 @readonly 

444 def ModelConversionDuration(self) -> timedelta: 

445 """ 

446 Read-only property to access the time spent for data structure to AlertLog hierarchy conversion (:attr:`_modelConversionDuration`). 

447 

448 :returns: The data structure conversion duration. 

449 """ 

450 if self._modelConversionDuration is None: 

451 raise AlertLogException(f"Document '{self._path}' was not converted.") 

452 

453 return self._modelConversionDuration 

454 

455 def Analyze(self) -> None: 

456 """ 

457 Analyze the YAML file (specified by :attr:`_path`) and store the YAML document in :attr:`_yamlDocument`. 

458 

459 :raises AlertLogException: If YAML file doesn't exist. 

460 :raises AlertLogException: If YAML file can't be opened. 

461 """ 

462 if not self._path.exists(): 462 ↛ 463line 462 didn't jump to line 463 because the condition on line 462 was never true

463 raise AlertLogException(f"OSVVM AlertLog YAML file '{self._path}' does not exist.") \ 

464 from FileNotFoundError(f"File '{self._path}' not found.") 

465 

466 with Stopwatch() as sw: 

467 try: 

468 yamlReader = YAML() 

469 self._yamlDocument = yamlReader.load(self._path) 

470 except Exception as ex: 

471 raise AlertLogException(f"Couldn't open '{self._path}'.") from ex 

472 

473 self._analysisDuration = timedelta(seconds=sw.Duration) 

474 

475 def Parse(self) -> None: 

476 """ 

477 Convert the YAML data structure to a hierarchy of :class:`AlertLogItem` instances. 

478 

479 :raises AlertLogException: If YAML file was not analyzed. 

480 """ 

481 if self._yamlDocument is None: 481 ↛ 482line 481 didn't jump to line 482 because the condition on line 481 was never true

482 ex = AlertLogException(f"OSVVM AlertLog YAML file '{self._path}' needs to be read and analyzed by a YAML parser.") 

483 ex.add_note(f"Call 'Document.Analyze()' or create the document using 'Document(path, parse=True)'.") 

484 raise ex 

485 

486 with Stopwatch() as sw: 

487 self._name = self._ParseStrFieldFromYAML(self._yamlDocument, "Name") 

488 self._status = AlertLogStatus.Parse(self._ParseStrFieldFromYAML(self._yamlDocument, "Status")) 

489 for child in self._ParseSequenceFromYAML(self._yamlDocument, "Children"): 

490 _ = self._ParseAlertLogItem(child, self) 

491 

492 self._modelConversionDuration = timedelta(seconds=sw.Duration) 

493 

494 @staticmethod 

495 def _ParseSequenceFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedSeq]: 

496 try: 

497 value = node[fieldName] 

498 except KeyError as ex: 

499 newEx = OSVVMException(f"Sequence field '{fieldName}' not found in node starting at line {node.lc.line + 1}.") 

500 newEx.add_note(f"Available fields: {', '.join(key for key in node)}") 

501 raise newEx from ex 

502 

503 if value is None: 503 ↛ 504line 503 didn't jump to line 504 because the condition on line 503 was never true

504 return () 

505 elif not isinstance(value, CommentedSeq): 505 ↛ 506line 505 didn't jump to line 506 because the condition on line 505 was never true

506 ex = AlertLogException(f"Field '{fieldName}' is not a sequence.") # TODO: from TypeError?? 

507 ex.add_note(f"Found type {value.__class__.__name__} at line {node._yaml_line_col.data[fieldName][0] + 1}.") 

508 raise ex 

509 

510 return value 

511 

512 @staticmethod 

513 def _ParseMapFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedMap]: 

514 try: 

515 value = node[fieldName] 

516 except KeyError as ex: 

517 newEx = OSVVMException(f"Dictionary field '{fieldName}' not found in node starting at line {node.lc.line + 1}.") 

518 newEx.add_note(f"Available fields: {', '.join(key for key in node)}") 

519 raise newEx from ex 

520 

521 if value is None: 521 ↛ 522line 521 didn't jump to line 522 because the condition on line 521 was never true

522 return {} 

523 elif not isinstance(value, CommentedMap): 523 ↛ 524line 523 didn't jump to line 524 because the condition on line 523 was never true

524 ex = AlertLogException(f"Field '{fieldName}' is not a list.") # TODO: from TypeError?? 

525 ex.add_note(f"Type mismatch found for line {node._yaml_line_col.data[fieldName][0] + 1}.") 

526 raise ex 

527 return value 

528 

529 @staticmethod 

530 def _ParseStrFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[str]: 

531 try: 

532 value = node[fieldName] 

533 except KeyError as ex: 

534 newEx = OSVVMException(f"String field '{fieldName}' not found in node starting at line {node.lc.line + 1}.") 

535 newEx.add_note(f"Available fields: {', '.join(key for key in node)}") 

536 raise newEx from ex 

537 

538 if not isinstance(value, str): 538 ↛ 539line 538 didn't jump to line 539 because the condition on line 538 was never true

539 raise AlertLogException(f"Field '{fieldName}' is not of type str.") # TODO: from TypeError?? 

540 

541 return value 

542 

543 @staticmethod 

544 def _ParseIntFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[int]: 

545 try: 

546 value = node[fieldName] 

547 except KeyError as ex: 

548 newEx = OSVVMException(f"Integer field '{fieldName}' not found in node starting at line {node.lc.line + 1}.") 

549 newEx.add_note(f"Available fields: {', '.join(key for key in node)}") 

550 raise newEx from ex 

551 

552 if not isinstance(value, int): 552 ↛ 553line 552 didn't jump to line 553 because the condition on line 552 was never true

553 raise AlertLogException(f"Field '{fieldName}' is not of type int.") # TODO: from TypeError?? 

554 

555 return value 

556 

557 def _ParseAlertLogItem(self, child: CommentedMap, parent: Nullable[AlertLogItem] = None) -> AlertLogItem: 

558 results = self._ParseMapFromYAML(child, "Results") 

559 yamlAlertCount = self._ParseMapFromYAML(results, "AlertCount") 

560 yamlDisabledAlertCount = self._ParseMapFromYAML(results, "DisabledAlertCount") 

561 alertLogItem = AlertLogItem( 

562 self._ParseStrFieldFromYAML(child, "Name"), 

563 AlertLogStatus.Parse(self._ParseStrFieldFromYAML(child, "Status")), 

564 self._ParseIntFieldFromYAML(results, "TotalErrors"), 

565 self._ParseIntFieldFromYAML(yamlAlertCount, "Warning"), 

566 self._ParseIntFieldFromYAML(yamlAlertCount, "Error"), 

567 self._ParseIntFieldFromYAML(yamlAlertCount, "Failure"), 

568 self._ParseIntFieldFromYAML(results, "PassedCount"), 

569 self._ParseIntFieldFromYAML(results, "AffirmCount"), 

570 self._ParseIntFieldFromYAML(results, "RequirementsPassed"), 

571 self._ParseIntFieldFromYAML(results, "RequirementsGoal"), 

572 self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Warning"), 

573 self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Error"), 

574 self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Failure"), 

575 children=(self._ParseAlertLogItem(ch) for ch in self._ParseSequenceFromYAML(child, "Children")), 

576 parent=parent 

577 ) 

578 

579 return alertLogItem