Coverage for pyEDAA/Reports/Requirements/__init__.py: 98%

837 statements  

« prev     ^ index     » next       coverage.py v7.17.3, created at 2026-11-14 02:26 +0000

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

2# _____ ____ _ _ ____ _ # 

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

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

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ # 

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

7# |_| |___/ |_| # 

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2026-2026 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""" 

32Abstract data model of requirements management. 

33 

34A :class:`RequirementSpecification` is the root: it registers the persons, personas, labels, documents and layers its 

35items refer to, and every requirement and specification item by its ID. 

36 

37A :class:`Requirement` has a title, a description, a creation time, a state, a kind and an obligation. It names its 

38authors and owners (:class:`~pyEDAA.Reports.Person`), the personas it is about (:class:`Persona`), its labels 

39(:class:`Label`), the documents attached to it (:class:`Document`), and the layer it belongs to (:class:`Layer`) - all 

40registered in the same specification. A requirement contains child requirements, which refine it. 

41 

42A :class:`SpecificationItem` mirrors a requirement: it states how the system meets requirements. Specification items and 

43requirements are in an N:M relation. 

44 

45An item's :class:`Link` states a relationship to another item, e.g. to its counterpart in another layer. An item's 

46history lists the events of its life cycle (:class:`HistoryEvent`), e.g. a split into other requirements. 

47""" 

48from __future__ import annotations 

49 

50from datetime import datetime 

51from enum import Enum, Flag 

52from typing import Any, Callable, ClassVar, Generator, Hashable, Iterable, Mapping, \ 

53 Optional as Nullable, Self 

54 

55from pyTooling.Common import getFullyQualifiedName, getTypeName 

56from pyTooling.DataModel import KeyValuePairsMixin 

57from pyTooling.Decorators import export, readonly 

58from pyTooling.Exceptions import AlreadyInHierarchyError, DuplicateElementError, NotInSameHierarchyError 

59from pyTooling.MetaClasses import ExtendedType 

60from pyTooling.Versioning import SemanticVersion 

61 

62from pyEDAA.Reports import Person, ReportException 

63 

64 

65@export 

66class RequirementsError(ReportException): 

67 """Base-exception of the requirements model.""" 

68 

69 

70@export 

71class NotInSameSpecificationError(RequirementsError, NotInSameHierarchyError): 

72 """ 

73 The exception is raised when an element is used together with an element or a person of another specification. 

74 

75 The element or person itself is valid, only its ownership is wrong: add it to the specification first. E.g. a link 

76 from a requirement to a requirement of another specification. 

77 """ 

78 

79 

80@export 

81class AlreadyInSpecificationError(RequirementsError, AlreadyInHierarchyError): 

82 """The exception is raised when an element is added to a specification or an item, but it already has a parent.""" 

83 

84 

85@export 

86class DuplicatePersonaError(RequirementsError, DuplicateElementError): 

87 """The exception is raised when a specification or an item already contains a persona of the same ID.""" 

88 

89 

90@export 

91class DuplicateLabelError(RequirementsError, DuplicateElementError): 

92 """The exception is raised when a specification or an item already contains a label of the same ID.""" 

93 

94 

95@export 

96class DuplicateDocumentError(RequirementsError, DuplicateElementError): 

97 """The exception is raised when a specification or an item already contains a document of the same ID.""" 

98 

99 

100@export 

101class DuplicateLayerError(RequirementsError, DuplicateElementError): 

102 """The exception is raised when a specification already contains a layer of the same ID.""" 

103 

104 

105@export 

106class DuplicatePersonError(RequirementsError, DuplicateElementError): 

107 """The exception is raised when a specification or an item already contains a person of the same user ID.""" 

108 

109 

110@export 

111class DuplicateRequirementError(RequirementsError, DuplicateElementError): 

112 """The exception is raised when a specification or an item already contains a requirement of the same ID.""" 

113 

114 

115@export 

116class DuplicateSpecificationItemError(RequirementsError, DuplicateElementError): 

117 """The exception is raised when a specification or an item already contains a specification item of the same ID.""" 

118 

119 

120@export 

121class DuplicateLinkError(RequirementsError, DuplicateElementError): 

122 """The exception is raised when an item already has a link of the same kind to the same destination.""" 

123 

124 

125@export 

126class ItemState(Enum): 

127 """ 

128 The state of an item in its life cycle. 

129 

130 An item is open from :attr:`Placeholder` over :attr:`Draft`, :attr:`InReview`, :attr:`Final` and :attr:`Implemented` 

131 to :attr:`Verified`. It ends :attr:`Closed` after its verification, or :attr:`Rejected` or :attr:`Obsolete` at any 

132 time. 

133 """ 

134 

135 Placeholder = 1 #: Reserved, e.g. an ID and a title, but not yet written. 

136 Draft = 2 #: Written, but not yet reviewed. 

137 InReview = 3 #: Under review by its stakeholders. 

138 Final = 4 #: Reviewed and agreed by its stakeholders; it binds the design. 

139 Implemented = 5 #: Implemented, but not yet verified. 

140 Verified = 6 #: Implemented and verified; open for a regression. 

141 Closed = 7 #: Verified and released, e.g. in a baseline; it changes no more. 

142 Rejected = 8 #: Not accepted by its stakeholders. 

143 Obsolete = 9 #: No longer valid, e.g. replaced by another one, split or merged. 

144 

145 

146@export 

147class RequirementKind(Enum): 

148 """ 

149 The kind of a requirement: what it constrains. 

150 

151 The kinds follow the requirement types of ISO/IEC/IEEE 29148, with safety and security as kinds of their own, as 

152 functional safety and security standards treat them separately. 

153 """ 

154 

155 Functional = 1 #: A function the system performs, e.g. a register map or a protocol's behavior. 

156 Performance = 2 #: How well a function is performed, e.g. a clock frequency, latency or throughput. 

157 Quality = 3 #: A quality attribute, e.g. reliability, maintainability or usability. 

158 Safety = 4 #: A measure against hazards, e.g. a safety mechanism of ISO 26262 or IEC 61508. 

159 Security = 5 #: A measure against threats, e.g. encrypted bitstreams or secure boot. 

160 Interface = 6 #: An interface to another system, e.g. a pin-out, a bus or a signal level. 

161 Environmental = 7 #: An operating condition, e.g. a temperature range, vibration or EMC. 

162 Constraint = 8 #: A design or implementation constraint, e.g. an FPGA family or a language. 

163 Regulatory = 9 #: A standard, law or certification the system complies with. 

164 Other = 10 #: Any other requirement. 

165 

166 

167@export 

168class Obligation(Enum): 

169 """ 

170 The obligation of a requirement: whether it must, should or may be met. 

171 

172 The levels are those of the keywords of ISO/IEC/IEEE 29148 (*shall*, *should*, *may*) and of RFC 2119 (*MUST*, 

173 *SHOULD*, *MAY*). A negated requirement (*shall not*) has the obligation of its positive keyword; the negation is part 

174 of its text. 

175 """ 

176 

177 Mandatory = 1 #: Binding: the requirement must be met (*shall*, *must*). 

178 Recommended = 2 #: A goal: the requirement should be met, deviations are justified (*should*). 

179 Optional = 3 #: A permission: the requirement may be met (*may*). 

180 

181 

182# A class with a property named like a class - ``Obligation`` - can't name that class in the annotation of a field: the 

183# class body's namespace, where annotations are evaluated, binds the name to the property. 

184_Obligation = Obligation 

185 

186 

187@export 

188class VerificationMethod(Flag): 

189 """ 

190 The methods verifying a requirement, as flags. 

191 

192 The methods follow the verification methods of ISO/IEC/IEEE 29148 - inspection, analysis, demonstration and test -, 

193 with simulation and formal verification as methods of their own, as hardware designs are verified by them. 

194 """ 

195 

196 Unknown = 0 #: Not decided yet. 

197 Inspection = 1 #: Examining the item, e.g. reviewing a schematic or source code. 

198 Analysis = 2 #: Calculating or modeling, e.g. a timing analysis or a power budget. 

199 Demonstration = 4 #: Operating the system without measurements, e.g. showing a feature works. 

200 Test = 8 #: Operating the system with measurements under defined conditions, e.g. on a test bench. 

201 Simulation = 16 #: Simulating a model of the design, e.g. a VHDL testbench. 

202 Formal = 32 #: Proving properties of the design, e.g. by formal property checking. 

203 

204 

205@export 

206class Base(KeyValuePairsMixin, metaclass=ExtendedType, slots=True): 

207 """ 

208 Base-class of every element of the requirements model: key-value pairs, a reference to the element containing it, and 

209 a reference to the specification it is part of. 

210 """ 

211 

212 _PARENT_TYPE: ClassVar[tuple[type, ...]] #: The types a parent may have; set by each class with a parent. 

213 

214 _parent: Nullable[Base] #: The element containing this one, or ``None``. 

215 _root: Nullable[RequirementSpecification] #: The specification, or ``None`` while the element isn't in one. 

216 

217 def __init__(self, *, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, parent: Nullable[Base] = None) -> None: 

218 """ 

219 Initializes an element with its key-value pairs, its parent, and the parent's specification. 

220 

221 The parent doesn't list the element yet: the class setting the field the parent stores it by - an ID - adds it with 

222 ``parent._AddElement(self)``. 

223 

224 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

225 :param parent: Optional, the element containing this one. Default: ``None``. 

226 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

227 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

228 """ 

229 super().__init__(keyValuePairs) 

230 

231 if parent is None: 

232 self._parent = None 

233 self._root = None 

234 elif not isinstance(parent, self._PARENT_TYPE): 

235 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE) 

236 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.") 

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

238 raise ex 

239 else: 

240 self._parent = parent 

241 self._root = parent._root 

242 

243 @property 

244 def Parent(self) -> Nullable[Base]: 

245 """ 

246 Property to access the element containing this one (:attr:`_parent`). 

247 

248 Assigning a parent to an element without one adds the element to it, and sets the parent's specification as the 

249 specification of this element and of every element below it. 

250 

251 :returns: The parent, or ``None``. 

252 :raises ValueError: If ``None`` is assigned. 

253 :raises TypeError: If the assigned parent isn't of a type this class declares in 

254 :attr:`_PARENT_TYPE`. 

255 :raises TypeError: If a parent is assigned to a :class:`RequirementSpecification`, which has 

256 no 

257 parent. 

258 :raises AlreadyInSpecificationError: If the element already has a parent. 

259 :raises DuplicatePersonaError: If the element is a persona, and the specification already contains a 

260 persona of its ID. 

261 :raises DuplicateLabelError: If the element is a label, and the specification already contains a label 

262 of its ID. 

263 :raises DuplicateDocumentError: If the element is a document, and the specification already contains a 

264 document of its ID. 

265 :raises DuplicateLayerError: If the element is a layer, and the specification already contains a layer 

266 of its ID. 

267 :raises DuplicateRequirementError: If the specification already contains a requirement of an ID of this 

268 requirement or of a requirement below it. 

269 :raises DuplicateSpecificationItemError: If the specification already contains a specification item of an ID of this 

270 item or of an item below it. 

271 """ 

272 return self._parent 

273 

274 @Parent.setter 

275 def Parent(self, parent: Base) -> None: 

276 if parent is None: 

277 raise ValueError("Parameter 'parent' is None.") 

278 elif not isinstance(parent, self._PARENT_TYPE): 

279 typeNames = " or ".join(f"'{parentType.__name__}'" for parentType in self._PARENT_TYPE) 

280 ex = TypeError(f"Parameter 'parent' is not of type {typeNames}.") 

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

282 raise ex 

283 

284 if self._parent is not None: 

285 raise AlreadyInSpecificationError(f"{getTypeName(self)} already has a parent.") 

286 

287 parent._AddElement(self) 

288 self._parent = parent 

289 self._root = parent._root 

290 

291 @readonly 

292 def Root(self) -> Nullable[RequirementSpecification]: 

293 """ 

294 Read-only property to access the specification this element is part of (:attr:`_root`). 

295 

296 The specification is maintained by :attr:`Parent`: assigning a parent sets it for this element and every element 

297 below it. 

298 

299 :returns: The specification, or ``None`` while the element isn't part of one. 

300 """ 

301 return self._root 

302 

303 

304@export 

305class BaseWithID(Base): 

306 """ 

307 Base-class of the elements of the requirements model, which have an ID and a description. 

308 

309 The ID is unique among the elements of the same class in a specification. 

310 """ 

311 

312 _DUPLICATE_ERROR: ClassVar[type[RequirementsError]] #: The error a second element of an ID raises; set by each class. 

313 

314 _id: str #: The element's ID. 

315 _description: str #: The element's description, or ``""``. 

316 

317 def __init__( 

318 self, 

319 identifier: str, 

320 *, 

321 description: str = "", 

322 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

323 parent: Nullable[Base] = None 

324 ) -> None: 

325 """ 

326 Initializes an element with its ID, its description, its key-value pairs and its parent. 

327 

328 :param identifier: The element's ID. 

329 :param description: Optional, the element's description. Default: ``""``. 

330 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

331 :param parent: Optional, the element containing this one. Default: ``None``. 

332 :raises ValueError: If parameter ``identifier`` is ``None``. 

333 :raises TypeError: If parameter ``identifier`` isn't of type :class:`str`. 

334 :raises ValueError: If parameter ``identifier`` is empty. 

335 :raises ValueError: If parameter ``description`` is ``None``. 

336 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

337 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

338 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in :attr:`_PARENT_TYPE`. 

339 """ 

340 super().__init__(keyValuePairs=keyValuePairs, parent=parent) 

341 

342 if identifier is None: 

343 raise ValueError("Parameter 'identifier' is None.") 

344 elif not isinstance(identifier, str): 

345 ex = TypeError("Parameter 'identifier' is not of type 'str'.") 

346 ex.add_note(f"Got type '{getFullyQualifiedName(identifier)}'.") 

347 raise ex 

348 elif identifier == "": 

349 raise ValueError("Parameter 'identifier' is empty.") 

350 

351 if description is None: 

352 raise ValueError("Parameter 'description' is None.") 

353 elif not isinstance(description, str): 

354 ex = TypeError("Parameter 'description' is not of type 'str'.") 

355 ex.add_note(f"Got type '{getFullyQualifiedName(description)}'.") 

356 raise ex 

357 

358 self._id = identifier 

359 self._description = description 

360 

361 @readonly 

362 def ID(self) -> str: 

363 """ 

364 Read-only property to access the element's ID (:attr:`_id`). 

365 

366 :returns: The ID. 

367 """ 

368 return self._id 

369 

370 @readonly 

371 def Description(self) -> str: 

372 """ 

373 Read-only property to access the element's description (:attr:`_description`). 

374 

375 :returns: The description, or ``""``. 

376 """ 

377 return self._description 

378 

379 

380@export 

381class RequirementSpecification(Base): 

382 """ 

383 The root of the requirements model: the requirements and specification items of a system, and the personas, labels, 

384 documents and layers they refer to. 

385 

386 It registers every requirement and every specification item by its ID - also a child item -, and is its own 

387 specification. 

388 

389 Persons have no parent: the specification registers the persons its items refer to by their user IDs, and a person may 

390 be registered in several specifications. 

391 """ 

392 

393 _name: Nullable[str] #: The specification's name, or ``None``. 

394 _version: SemanticVersion #: The version of the specification. 

395 _persons: dict[str, Person] #: The persons, by user ID. 

396 _personas: dict[str, Persona] #: The personas, by ID. 

397 _labels: dict[str, Label] #: The labels, by ID. 

398 _documents: dict[str, Document] #: The documents, by ID. 

399 _layers: dict[str, Layer] #: The layers, by ID. 

400 _requirements: dict[str, Requirement] #: Every requirement, by ID, in the order they were added. 

401 _specificationItems: dict[str, SpecificationItem] #: Every specification item, by ID, in the order they were added. 

402 

403 def __init__( 

404 self, 

405 version: SemanticVersion, 

406 name: Nullable[str] = None, 

407 *, 

408 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

409 ) -> None: 

410 """ 

411 Initializes an empty requirement specification. 

412 

413 :param version: The version of the specification. 

414 :param name: Optional, the specification's name. Default: ``None``. 

415 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

416 :raises ValueError: If parameter ``version`` is ``None``. 

417 :raises TypeError: If parameter ``version`` isn't of type :class:`~pyTooling.Versioning.SemanticVersion`. 

418 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

419 :raises ValueError: If parameter ``name`` is empty. 

420 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

421 """ 

422 super().__init__(keyValuePairs=keyValuePairs) 

423 

424 if version is None: 

425 raise ValueError("Parameter 'version' is None.") 

426 elif not isinstance(version, SemanticVersion): 

427 ex = TypeError("Parameter 'version' is not of type 'SemanticVersion'.") 

428 ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.") 

429 raise ex 

430 

431 if name is not None: 

432 if not isinstance(name, str): 

433 ex = TypeError("Parameter 'name' is not of type 'str'.") 

434 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

435 raise ex 

436 elif name == "": 

437 raise ValueError("Parameter 'name' is empty.") 

438 

439 self._root = self 

440 self._name = name 

441 self._version = version 

442 self._persons = {} 

443 self._personas = {} 

444 self._labels = {} 

445 self._documents = {} 

446 self._layers = {} 

447 self._requirements = {} 

448 self._specificationItems = {} 

449 

450 @Base.Parent.setter 

451 def Parent(self, parent: None) -> None: 

452 ex = TypeError(f"A '{getFullyQualifiedName(self)}' has no parent.") 

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

454 raise ex 

455 

456 def _AddElement(self, element: BaseWithID) -> None: 

457 """ 

458 Register a persona, label, document, layer, top-level requirement or top-level specification item, which names the 

459 specification as its parent. 

460 

461 :param element: The element. 

462 :raises DuplicatePersonaError: If the specification already contains a persona of the element's ID. 

463 :raises DuplicateLabelError: If the specification already contains a label of the element's ID. 

464 :raises DuplicateDocumentError: If the specification already contains a document of the element's ID. 

465 :raises DuplicateLayerError: If the specification already contains a layer of the element's ID. 

466 :raises DuplicateRequirementError: If the specification already contains a requirement of the ID of the 

467 requirement or of a requirement below it. 

468 :raises DuplicateSpecificationItemError: If the specification already contains a specification item of the ID of the 

469 item or of an item below it. 

470 """ 

471 if isinstance(element, BaseItem): 

472 self._AddItems(element) 

473 return 

474 

475 if isinstance(element, Persona): 

476 elements = self._personas 

477 elif isinstance(element, Label): 

478 elements = self._labels 

479 elif isinstance(element, Document): 

480 elements = self._documents 

481 else: 

482 elements = self._layers 

483 

484 if element._id in elements: 

485 raise element._DUPLICATE_ERROR( 

486 f"{getTypeName(element)} '{element._id}' already exists in the specification." 

487 ) 

488 

489 elements[element._id] = element 

490 

491 def _AddItems(self, item: BaseItem) -> None: 

492 """ 

493 Register an item and every item below it by their IDs, and set the specification as their specification and the 

494 specification of their history events. 

495 

496 Either all of them are registered, or none. 

497 

498 :param item: The item joining the specification. 

499 :raises DuplicateRequirementError: If the item is a requirement, and an ID of these requirements is registered 

500 already, or used twice by them. 

501 :raises DuplicateSpecificationItemError: If the item is a specification item, and an ID of these items is registered 

502 already, or used twice by them. 

503 """ 

504 items = self._requirements if isinstance(item, Requirement) else self._specificationItems 

505 ids = set() 

506 subtree = (item, *item.IterateDescendants()) 

507 for element in subtree: 

508 if element._id in items or element._id in ids: 

509 raise element._DUPLICATE_ERROR( 

510 f"{getTypeName(element)} '{element._id}' already exists in the specification." 

511 ) 

512 

513 ids.add(element._id) 

514 

515 for element in subtree: 

516 element._root = self 

517 items[element._id] = element 

518 for event in element._history: 

519 event._root = self 

520 

521 @readonly 

522 def Name(self) -> Nullable[str]: 

523 """ 

524 Read-only property to access the specification's name (:attr:`_name`). 

525 

526 :returns: The name, or ``None``. 

527 """ 

528 return self._name 

529 

530 @readonly 

531 def Version(self) -> SemanticVersion: 

532 """ 

533 Read-only property to access the version of the specification (:attr:`_version`). 

534 

535 :returns: The version. 

536 """ 

537 return self._version 

538 

539 @readonly 

540 def Persons(self) -> dict[str, Person]: 

541 """ 

542 Read-only property to access the persons (:attr:`_persons`). 

543 

544 :returns: The persons, by user ID. 

545 """ 

546 return self._persons 

547 

548 def IteratePersons(self, predicate: Nullable[Callable[[Person], bool]] = None) -> Generator[Person, None, None]: 

549 """ 

550 Iterate all or selected persons. 

551 

552 If parameter ``predicate`` is not None, the given filter function is used to skip persons in the generator. 

553 

554 :param predicate: Optional, filter function accepting a person and returning a boolean. Default: ``None``. 

555 :returns: A generator of the persons, in the order they were added. 

556 """ 

557 if predicate is None: 

558 yield from self._persons.values() 

559 else: 

560 for person in self._persons.values(): 

561 if predicate(person): 561 ↛ 562line 561 didn't jump to line 562 because the condition on line 561 was never true

562 yield person 

563 

564 def AddPerson(self, person: Person) -> None: 

565 """ 

566 Register a person, whom the specification's items can refer to. 

567 

568 E.g. an author or an owner of requirements. A person has no parent: the same person may be registered in several 

569 specifications. 

570 

571 :param person: The person. 

572 :raises ValueError: If parameter ``person`` is ``None``. 

573 :raises TypeError: If parameter ``person`` isn't of type :class:`~pyEDAA.Reports.Person`. 

574 :raises DuplicatePersonError: If the specification already contains a person of this user ID. 

575 """ 

576 if person is None: 

577 raise ValueError("Parameter 'person' is None.") 

578 elif not isinstance(person, Person): 

579 ex = TypeError("Parameter 'person' is not of type 'Person'.") 

580 ex.add_note(f"Got type '{getFullyQualifiedName(person)}'.") 

581 raise ex 

582 elif person._userID in self._persons: 

583 raise DuplicatePersonError(f"Person '{person._userID}' already exists in the specification.") 

584 

585 self._persons[person._userID] = person 

586 

587 @readonly 

588 def Personas(self) -> dict[str, Persona]: 

589 """ 

590 Read-only property to access the personas (:attr:`_personas`). 

591 

592 :returns: The personas, by ID. 

593 """ 

594 return self._personas 

595 

596 def IteratePersonas(self, predicate: Nullable[Callable[[Persona], bool]] = None) -> Generator[Persona, None, None]: 

597 """ 

598 Iterate all or selected personas. 

599 

600 If parameter ``predicate`` is not None, the given filter function is used to skip personas in the generator. 

601 

602 :param predicate: Optional, filter function accepting a persona and returning a boolean. Default: ``None``. 

603 :returns: A generator of the personas, in the order they were added. 

604 """ 

605 if predicate is None: 

606 yield from self._personas.values() 

607 else: 

608 for persona in self._personas.values(): 

609 if predicate(persona): 

610 yield persona 

611 

612 @readonly 

613 def Labels(self) -> dict[str, Label]: 

614 """ 

615 Read-only property to access the labels (:attr:`_labels`). 

616 

617 :returns: The labels, by ID. 

618 """ 

619 return self._labels 

620 

621 def IterateLabels(self, predicate: Nullable[Callable[[Label], bool]] = None) -> Generator[Label, None, None]: 

622 """ 

623 Iterate all or selected labels. 

624 

625 If parameter ``predicate`` is not None, the given filter function is used to skip labels in the generator. 

626 

627 :param predicate: Optional, filter function accepting a label and returning a boolean. Default: ``None``. 

628 :returns: A generator of the labels, in the order they were added. 

629 """ 

630 if predicate is None: 

631 yield from self._labels.values() 

632 else: 

633 for label in self._labels.values(): 

634 if predicate(label): 

635 yield label 

636 

637 @readonly 

638 def Documents(self) -> dict[str, Document]: 

639 """ 

640 Read-only property to access the documents (:attr:`_documents`). 

641 

642 :returns: The documents, by ID. 

643 """ 

644 return self._documents 

645 

646 def IterateDocuments(self, predicate: Nullable[Callable[[Document], bool]] = None) -> Generator[Document, None, None]: 

647 """ 

648 Iterate all or selected documents. 

649 

650 If parameter ``predicate`` is not None, the given filter function is used to skip documents in the generator. 

651 

652 :param predicate: Optional, filter function accepting a document and returning a boolean. Default: ``None``. 

653 :returns: A generator of the documents, in the order they were added. 

654 """ 

655 if predicate is None: 

656 yield from self._documents.values() 

657 else: 

658 for document in self._documents.values(): 

659 if predicate(document): 

660 yield document 

661 

662 @readonly 

663 def Layers(self) -> dict[str, Layer]: 

664 """ 

665 Read-only property to access the layers (:attr:`_layers`). 

666 

667 :returns: The layers, by ID. 

668 """ 

669 return self._layers 

670 

671 def IterateLayers(self, predicate: Nullable[Callable[[Layer], bool]] = None) -> Generator[Layer, None, None]: 

672 """ 

673 Iterate all or selected layers. 

674 

675 If parameter ``predicate`` is not None, the given filter function is used to skip layers in the generator. 

676 

677 :param predicate: Optional, filter function accepting a layer and returning a boolean. Default: ``None``. 

678 :returns: A generator of the layers, in the order they were added. 

679 """ 

680 if predicate is None: 

681 yield from self._layers.values() 

682 else: 

683 for layer in self._layers.values(): 

684 if predicate(layer): 

685 yield layer 

686 

687 @readonly 

688 def Requirements(self) -> dict[str, Requirement]: 

689 """ 

690 Read-only property to access every requirement (:attr:`_requirements`), also the child requirements. 

691 

692 :returns: The requirements, by ID, in the order they were added. 

693 """ 

694 return self._requirements 

695 

696 @readonly 

697 def SpecificationItems(self) -> dict[str, SpecificationItem]: 

698 """ 

699 Read-only property to access every specification item (:attr:`_specificationItems`), also the child items. 

700 

701 :returns: The specification items, by ID, in the order they were added. 

702 """ 

703 return self._specificationItems 

704 

705 def IterateRequirements( 

706 self, 

707 predicate: Nullable[Callable[[Requirement], bool]] = None 

708 ) -> Generator[Requirement, None, None]: 

709 """ 

710 Iterate all or selected requirements, also the child requirements, in the order they were added. 

711 

712 If parameter ``predicate`` is not None, the given filter function is used to skip requirements in the generator. 

713 

714 :param predicate: Optional, filter function accepting a requirement and returning a boolean. Default: ``None``. 

715 :returns: A generator of the requirements. 

716 """ 

717 if predicate is None: 

718 yield from self._requirements.values() 

719 else: 

720 for requirement in self._requirements.values(): 

721 if predicate(requirement): 

722 yield requirement 

723 

724 def IterateSpecificationItems( 

725 self, 

726 predicate: Nullable[Callable[[SpecificationItem], bool]] = None 

727 ) -> Generator[SpecificationItem, None, None]: 

728 """ 

729 Iterate all or selected specification items, also the child items, in the order they were added. 

730 

731 If parameter ``predicate`` is not None, the given filter function is used to skip items in the generator. 

732 

733 :param predicate: Optional, filter function accepting a specification item and returning a boolean. Default: 

734 ``None``. 

735 :returns: A generator of the specification items. 

736 """ 

737 if predicate is None: 

738 yield from self._specificationItems.values() 

739 else: 

740 for specification in self._specificationItems.values(): 

741 if predicate(specification): 

742 yield specification 

743 

744 

745@export 

746class BaseWithName(BaseWithID): 

747 """ 

748 Base-class of the elements a specification registers for its requirements to refer to: an ID, a name and a 

749 description. 

750 

751 Its parent is the :class:`RequirementSpecification`. 

752 """ 

753 

754 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (RequirementSpecification, ) #: The element is in a specification. 

755 

756 _name: str #: The element's name. 

757 

758 def __init__( 

759 self, 

760 identifier: str, 

761 name: str, 

762 *, 

763 description: str = "", 

764 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

765 parent: Nullable[RequirementSpecification] = None 

766 ) -> None: 

767 """ 

768 Initializes an element with its ID, name and description, and registers it in its specification. 

769 

770 :param identifier: The element's ID. 

771 :param name: The element's name. 

772 :param description: Optional, the element's description. Default: ``""``. 

773 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

774 :param parent: Optional, the specification. Default: ``None``. 

775 :raises ValueError: If parameter ``identifier`` is ``None``. 

776 :raises TypeError: If parameter ``identifier`` isn't of type :class:`str`. 

777 :raises ValueError: If parameter ``identifier`` is empty. 

778 :raises ValueError: If parameter ``name`` is ``None``. 

779 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

780 :raises ValueError: If parameter ``name`` is empty. 

781 :raises ValueError: If parameter ``description`` is ``None``. 

782 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

783 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

784 :raises TypeError: If parameter ``parent`` isn't of type :class:`RequirementSpecification`. 

785 :raises DuplicatePersonaError: If the element is a persona, and the specification already contains a persona of 

786 this ID. 

787 :raises DuplicateLabelError: If the element is a label, and the specification already contains a label of this 

788 ID. 

789 :raises DuplicateDocumentError: If the element is a document, and the specification already contains a document of 

790 this ID. 

791 :raises DuplicateLayerError: If the element is a layer, and the specification already contains a layer of this 

792 ID. 

793 """ 

794 super().__init__(identifier, description=description, keyValuePairs=keyValuePairs, parent=parent) 

795 

796 if name is None: 

797 raise ValueError("Parameter 'name' is None.") 

798 elif not isinstance(name, str): 

799 ex = TypeError("Parameter 'name' is not of type 'str'.") 

800 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

801 raise ex 

802 elif name == "": 

803 raise ValueError("Parameter 'name' is empty.") 

804 

805 self._name = name 

806 

807 if parent is not None: 

808 parent._AddElement(self) 

809 

810 @readonly 

811 def Name(self) -> str: 

812 """ 

813 Read-only property to access the element's name (:attr:`_name`). 

814 

815 :returns: The name. 

816 """ 

817 return self._name 

818 

819 

820@export 

821class Persona(BaseWithName): 

822 """ 

823 A persona: a role a user of the system acts in, like the role of a use case or a user story. 

824 

825 E.g. an administrator, a reviewer or a quality inspector. A real person, e.g. an author, is a 

826 :class:`~pyEDAA.Reports.Person`. 

827 """ 

828 

829 _DUPLICATE_ERROR = DuplicatePersonaError #: The error a second persona of an ID raises. 

830 

831 

832@export 

833class Label(BaseWithName): 

834 """ 

835 A label to classify items across layers and kinds. 

836 

837 E.g. a product variant, a release or a customer. 

838 """ 

839 

840 _DUPLICATE_ERROR = DuplicateLabelError #: The error a second label of an ID raises. 

841 

842 

843@export 

844class Document(BaseWithName): 

845 """ 

846 A document attached to items. 

847 

848 E.g. a datasheet, a schematic or a standard. 

849 """ 

850 

851 _DUPLICATE_ERROR = DuplicateDocumentError #: The error a second document of an ID raises. 

852 

853 

854@export 

855class Layer(BaseWithName): 

856 """ 

857 A layer of the system: the items of one engineering discipline. 

858 

859 E.g. electrical, mechanical, FPGA firmware and software requirements are four layers. Each item belongs to at most one 

860 layer. A requirement of one layer has its counterparts in other layers, e.g. an electrical requirement in the FPGA 

861 firmware, and this one in the software; links of kind :attr:`LinkKind.CounterpartOf` or :attr:`LinkKind.DerivesFrom` 

862 state it. 

863 """ 

864 

865 _DUPLICATE_ERROR = DuplicateLayerError #: The error a second layer of an ID raises. 

866 

867 def IterateRequirements( 

868 self, 

869 predicate: Nullable[Callable[[Requirement], bool]] = None 

870 ) -> Generator[Requirement, None, None]: 

871 """ 

872 Iterate all or selected requirements of this layer, in the order the specification registered them. 

873 

874 If parameter ``predicate`` is not None, the given filter function is used to skip requirements in the generator. 

875 

876 :param predicate: Optional, filter function accepting a requirement and returning a boolean. Default: ``None``. 

877 :returns: A generator of the layer's requirements; an empty one, while the layer isn't part of a 

878 specification. 

879 """ 

880 if self._root is None: 

881 return 

882 

883 if predicate is None: 883 ↛ 888line 883 didn't jump to line 888 because the condition on line 883 was always true

884 for requirement in self._root._requirements.values(): 

885 if requirement._layer is self: 

886 yield requirement 

887 else: 

888 for requirement in self._root._requirements.values(): 

889 if requirement._layer is self and predicate(requirement): 

890 yield requirement 

891 

892 def IterateSpecificationItems( 

893 self, 

894 predicate: Nullable[Callable[[SpecificationItem], bool]] = None 

895 ) -> Generator[SpecificationItem, None, None]: 

896 """ 

897 Iterate all or selected specification items of this layer, in the order the specification registered them. 

898 

899 If parameter ``predicate`` is not None, the given filter function is used to skip items in the generator. 

900 

901 :param predicate: Optional, filter function accepting a specification item and returning a boolean. Default: 

902 ``None``. 

903 :returns: A generator of the layer's specification items; an empty one, while the layer isn't part of a 

904 specification. 

905 """ 

906 if self._root is None: 

907 return 

908 

909 if predicate is None: 909 ↛ 914line 909 didn't jump to line 914 because the condition on line 909 was always true

910 for specification in self._root._specificationItems.values(): 

911 if specification._layer is self: 

912 yield specification 

913 else: 

914 for specification in self._root._specificationItems.values(): 

915 if specification._layer is self and predicate(specification): 

916 yield specification 

917 

918 

919# A class with a property named like a class - ``Layer`` - can't name that class in the annotation of a field: the class 

920# body's namespace, where annotations are evaluated, binds the name to the property. 

921_Layer = Layer 

922 

923 

924@export 

925class BaseItem(BaseWithID): 

926 """ 

927 Base-class of the items of a specification - requirements and specification items -: a title, a creation time, a 

928 state, the layer, the persons, personas, labels and documents it refers to, its child items, its links and its 

929 history. 

930 

931 An item refers only to persons, personas, labels, documents and layers of its own specification, so it must be part of 

932 one to refer to any. A child item has the class of its parent; its ID is unique in the specification, too. 

933 """ 

934 

935 _title: str #: The item's title. 

936 _rationale: str #: Why the item exists or is as it is, or ``""``. 

937 _created: datetime #: The time the item was created. 

938 _state: ItemState #: The item's state in its life cycle. 

939 _layer: Nullable[_Layer] #: The layer the item belongs to, or ``None``. 

940 _authors: dict[str, Person] #: The authors of the item, by user ID. 

941 _owners: dict[str, Person] #: The owners of the item, by user ID. 

942 _personas: dict[str, Persona] #: The personas the item is about, by ID. 

943 _labels: dict[str, Label] #: The labels of the item, by ID. 

944 _attachments: dict[str, Document] #: The documents attached to the item, by ID. 

945 _children: dict[str, Self] #: The child items, by ID. 

946 _outboundLinks: list[Link] #: The links from this item, in the order they were added. 

947 _inboundLinks: list[Link] #: The links to this item, in the order they were added. 

948 _history: list[HistoryEvent] #: The events in the item's life cycle, in chronological order. 

949 

950 def __init__( 

951 self, 

952 identifier: str, 

953 title: str, 

954 description: str = "", 

955 created: Nullable[datetime] = None, 

956 *, 

957 rationale: str = "", 

958 state: ItemState = ItemState.Draft, 

959 layer: Nullable[Layer] = None, 

960 authors: Nullable[Iterable[Person]] = None, 

961 owners: Nullable[Iterable[Person]] = None, 

962 personas: Nullable[Iterable[Persona]] = None, 

963 labels: Nullable[Iterable[Label]] = None, 

964 attachments: Nullable[Iterable[Document]] = None, 

965 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

966 parent: Nullable[BaseItem | RequirementSpecification] = None 

967 ) -> None: 

968 """ 

969 Initializes an item with its title, creation time, state, layer, and the persons, personas, labels and documents it 

970 refers to. 

971 

972 :param identifier: The item's ID. 

973 :param title: The item's title. 

974 :param description: Optional, the item's description. Default: ``""``. 

975 :param created: Optional, the time the item was created; without a time zone, it is a local 

976 time. 

977 Default: ``None``, the current time. 

978 :param rationale: Optional, why the item exists or is as it is. Default: ``""``. 

979 :param state: Optional, the item's state. Default: :attr:`ItemState.Draft`. 

980 :param layer: Optional, the layer the item belongs to. Default: ``None``. 

981 :param authors: Optional, the authors of the item. Default: ``None``. 

982 :param owners: Optional, the owners of the item. Default: ``None``. 

983 :param personas: Optional, the personas the item is about. Default: ``None``. 

984 :param labels: Optional, the labels of the item. Default: ``None``. 

985 :param attachments: Optional, the documents attached to the item. Default: ``None``. 

986 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

987 :param parent: Optional, the parent item or the specification. Default: ``None``. 

988 :raises ValueError: If parameter ``identifier`` is ``None``. 

989 :raises TypeError: If parameter ``identifier`` isn't of type :class:`str`. 

990 :raises ValueError: If parameter ``identifier`` is empty. 

991 :raises ValueError: If parameter ``title`` is ``None``. 

992 :raises TypeError: If parameter ``title`` isn't of type :class:`str`. 

993 :raises ValueError: If parameter ``title`` is empty. 

994 :raises ValueError: If parameter ``description`` is ``None``. 

995 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

996 :raises TypeError: If parameter ``created`` isn't of type :class:`~datetime.datetime`. 

997 :raises ValueError: If parameter ``rationale`` is ``None``. 

998 :raises TypeError: If parameter ``rationale`` isn't of type :class:`str`. 

999 :raises ValueError: If parameter ``state`` is ``None``. 

1000 :raises TypeError: If parameter ``state`` isn't of type :class:`ItemState`. 

1001 :raises TypeError: If parameter ``layer`` isn't of type :class:`Layer`. 

1002 :raises NotInSameSpecificationError: If parameter ``layer`` isn't part of the item's specification. 

1003 :raises TypeError: If parameter ``authors`` isn't iterable, or is a single element. 

1004 :raises TypeError: If parameter ``authors`` contains an element not of type 

1005 :class:`~pyEDAA.Reports.Person`. 

1006 :raises NotInSameSpecificationError: If parameter ``authors`` contains an element, which isn't part of the item's 

1007 specification. 

1008 :raises DuplicatePersonError: If parameter ``authors`` contains a person twice. 

1009 :raises TypeError: If parameter ``owners`` isn't iterable, or is a single element. 

1010 :raises TypeError: If parameter ``owners`` contains an element not of type 

1011 :class:`~pyEDAA.Reports.Person`. 

1012 :raises NotInSameSpecificationError: If parameter ``owners`` contains an element, which isn't part of the item's 

1013 specification. 

1014 :raises DuplicatePersonError: If parameter ``owners`` contains a person twice. 

1015 :raises TypeError: If parameter ``personas`` isn't iterable, or is a single element. 

1016 :raises TypeError: If parameter ``personas`` contains an element not of type :class:`Persona`. 

1017 :raises NotInSameSpecificationError: If parameter ``personas`` contains an element, which isn't part of the item's 

1018 specification. 

1019 :raises DuplicatePersonaError: If parameter ``personas`` contains a persona twice. 

1020 :raises TypeError: If parameter ``labels`` isn't iterable, or is a single element. 

1021 :raises TypeError: If parameter ``labels`` contains an element not of type :class:`Label`. 

1022 :raises NotInSameSpecificationError: If parameter ``labels`` contains an element, which isn't part of the item's 

1023 specification. 

1024 :raises DuplicateLabelError: If parameter ``labels`` contains a label twice. 

1025 :raises TypeError: If parameter ``attachments`` isn't iterable, or is a single element. 

1026 :raises TypeError: If parameter ``attachments`` contains an element not of type :class:`Document`. 

1027 :raises NotInSameSpecificationError: If parameter ``attachments`` contains an element, which isn't part of the 

1028 item's specification. 

1029 :raises DuplicateDocumentError: If parameter ``attachments`` contains a document twice. 

1030 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1031 :raises TypeError: If parameter ``parent`` isn't of a type this class declares in 

1032 :attr:`_PARENT_TYPE`. 

1033 """ 

1034 super().__init__(identifier, description=description, keyValuePairs=keyValuePairs, parent=parent) 

1035 

1036 if title is None: 

1037 raise ValueError("Parameter 'title' is None.") 

1038 elif not isinstance(title, str): 

1039 ex = TypeError("Parameter 'title' is not of type 'str'.") 

1040 ex.add_note(f"Got type '{getFullyQualifiedName(title)}'.") 

1041 raise ex 

1042 elif title == "": 

1043 raise ValueError("Parameter 'title' is empty.") 

1044 

1045 if rationale is None: 

1046 raise ValueError("Parameter 'rationale' is None.") 

1047 elif not isinstance(rationale, str): 

1048 ex = TypeError("Parameter 'rationale' is not of type 'str'.") 

1049 ex.add_note(f"Got type '{getFullyQualifiedName(rationale)}'.") 

1050 raise ex 

1051 

1052 if created is None: 

1053 created = datetime.now() 

1054 elif not isinstance(created, datetime): 

1055 ex = TypeError("Parameter 'created' is not of type 'datetime'.") 

1056 ex.add_note(f"Got type '{getFullyQualifiedName(created)}'.") 

1057 raise ex 

1058 

1059 if state is None: 

1060 raise ValueError("Parameter 'state' is None.") 

1061 elif not isinstance(state, ItemState): 

1062 ex = TypeError("Parameter 'state' is not of type 'ItemState'.") 

1063 ex.add_note(f"Got type '{getFullyQualifiedName(state)}'.") 

1064 raise ex 

1065 

1066 if layer is not None: 

1067 if not isinstance(layer, Layer): 

1068 ex = TypeError("Parameter 'layer' is not of type 'Layer'.") 

1069 ex.add_note(f"Got type '{getFullyQualifiedName(layer)}'.") 

1070 raise ex 

1071 elif self._root is None or layer._root is not self._root: 

1072 ex = NotInSameSpecificationError("Parameter 'layer' isn't part of the item's specification.") 

1073 ex.add_note(f"Got layer '{layer._id}'.") 

1074 raise ex 

1075 

1076 self._title = title 

1077 self._rationale = rationale 

1078 self._created = created 

1079 self._state = state 

1080 self._layer = layer 

1081 self._authors = {} 

1082 self._owners = {} 

1083 self._personas = {} 

1084 self._labels = {} 

1085 self._attachments = {} 

1086 self._children = {} 

1087 self._outboundLinks = [] 

1088 self._inboundLinks = [] 

1089 self._history = [] 

1090 

1091 for parameterName, references, referenceType, container, duplicateError in ( 

1092 ("authors", authors, Person, self._authors, DuplicatePersonError), 

1093 ("owners", owners, Person, self._owners, DuplicatePersonError), 

1094 ("personas", personas, Persona, self._personas, DuplicatePersonaError), 

1095 ("labels", labels, Label, self._labels, DuplicateLabelError), 

1096 ("attachments", attachments, Document, self._attachments, DuplicateDocumentError), 

1097 ): 

1098 if references is None: 

1099 continue 

1100 elif not isinstance(references, Iterable): 

1101 ex = TypeError(f"Parameter '{parameterName}' is not iterable.") 

1102 ex.add_note(f"Got type '{getFullyQualifiedName(references)}'.") 

1103 raise ex 

1104 elif isinstance(references, KeyValuePairsMixin): 

1105 ex = TypeError(f"Parameter '{parameterName}' is a single element, not an iterable of elements.") 

1106 ex.add_note(f"Got type '{getFullyQualifiedName(references)}'.") 

1107 raise ex 

1108 

1109 for reference in references: 

1110 if not isinstance(reference, referenceType): 

1111 ex = TypeError(f"Parameter '{parameterName}' contains an element not of type '{referenceType.__name__}'.") 

1112 ex.add_note(f"Got type '{getFullyQualifiedName(reference)}'.") 

1113 raise ex 

1114 

1115 if referenceType is Person: 

1116 key = reference._userID 

1117 registered = self._root is not None and self._root._persons.get(key) is reference 

1118 else: 

1119 key = reference._id 

1120 registered = self._root is not None and reference._root is self._root 

1121 

1122 if not registered: 

1123 ex = NotInSameSpecificationError( 

1124 f"Parameter '{parameterName}' contains an element not part of the item's specification." 

1125 ) 

1126 ex.add_note(f"Got {referenceType.__name__.lower()} '{key}'.") 

1127 raise ex 

1128 elif key in container: 

1129 ex = duplicateError(f"Parameter '{parameterName}' contains an element twice.") 

1130 ex.add_note(f"Got {referenceType.__name__.lower()} '{key}'.") 

1131 raise ex 

1132 

1133 container[key] = reference 

1134 

1135 def _AddElement(self, element: Self | HistoryEvent) -> None: 

1136 """ 

1137 Add a child item or a history event, which names this item as its parent. 

1138 

1139 A child item is registered with every item below it in the specification. 

1140 

1141 :param element: The child item or history event. 

1142 :raises DuplicateRequirementError: If the child is a requirement, and the specification already contains a 

1143 requirement of an ID of the child or a requirement below it. 

1144 :raises DuplicateSpecificationItemError: If the child is a specification item, and the specification already 

1145 contains a specification item of an ID of the child or an item below it. 

1146 :raises DuplicateRequirementError: If the child is a requirement, this item isn't part of a specification, and 

1147 it has a child of this ID already. 

1148 :raises DuplicateSpecificationItemError: If the child is a specification item, this item isn't part of a 

1149 specification, and it has a child of this ID already. 

1150 :raises RequirementsError: If the history event is earlier than the item's creation or its last event. 

1151 """ 

1152 if isinstance(element, HistoryEvent): 

1153 previous = self._history[-1]._timestamp if len(self._history) > 0 else self._created 

1154 if element._timestamp.astimezone() < previous.astimezone(): 

1155 ex = RequirementsError("History event is earlier than the item's creation or its last event.") 

1156 ex.add_note(f"Got '{element._timestamp.isoformat()}' after '{previous.isoformat()}'.") 

1157 raise ex from ValueError(f"Parameter 'timestamp' is earlier than '{previous.isoformat()}'.") 

1158 

1159 self._history.append(element) 

1160 else: 

1161 if self._root is not None: 

1162 self._root._AddItems(element) 

1163 elif element._id in self._children: 

1164 raise element._DUPLICATE_ERROR(f"{getTypeName(self)} '{self._id}' already has a child '{element._id}'.") 

1165 

1166 self._children[element._id] = element 

1167 

1168 @readonly 

1169 def Title(self) -> str: 

1170 """ 

1171 Read-only property to access the item's title (:attr:`_title`). 

1172 

1173 :returns: The title. 

1174 """ 

1175 return self._title 

1176 

1177 @readonly 

1178 def Rationale(self) -> str: 

1179 """ 

1180 Read-only property to access why the item exists or is as it is (:attr:`_rationale`). 

1181 

1182 :returns: The rationale, or ``""``. 

1183 """ 

1184 return self._rationale 

1185 

1186 @readonly 

1187 def Created(self) -> datetime: 

1188 """ 

1189 Read-only property to access the time the item was created (:attr:`_created`). 

1190 

1191 :returns: The time; without a time zone, it is a local time. 

1192 """ 

1193 return self._created 

1194 

1195 @property 

1196 def State(self) -> ItemState: 

1197 """ 

1198 Property to access the item's state in its life cycle (:attr:`_state`). 

1199 

1200 :returns: The state. 

1201 :raises ValueError: If ``None`` is assigned. 

1202 :raises TypeError: If the assigned state isn't of type :class:`ItemState`. 

1203 """ 

1204 return self._state 

1205 

1206 @State.setter 

1207 def State(self, state: ItemState) -> None: 

1208 if state is None: 

1209 raise ValueError("Parameter 'state' is None.") 

1210 elif not isinstance(state, ItemState): 

1211 ex = TypeError("Parameter 'state' is not of type 'ItemState'.") 

1212 ex.add_note(f"Got type '{getFullyQualifiedName(state)}'.") 

1213 raise ex 

1214 

1215 self._state = state 

1216 

1217 @readonly 

1218 def Layer(self) -> Nullable[Layer]: 

1219 """ 

1220 Read-only property to access the layer the item belongs to (:attr:`_layer`). 

1221 

1222 :returns: The layer, or ``None``. 

1223 """ 

1224 return self._layer 

1225 

1226 @readonly 

1227 def Authors(self) -> dict[str, Person]: 

1228 """ 

1229 Read-only property to access the authors of the item (:attr:`_authors`). 

1230 

1231 :returns: The authors, by user ID. 

1232 """ 

1233 return self._authors 

1234 

1235 @readonly 

1236 def Owners(self) -> dict[str, Person]: 

1237 """ 

1238 Read-only property to access the owners of the item (:attr:`_owners`). 

1239 

1240 :returns: The owners, by user ID. 

1241 """ 

1242 return self._owners 

1243 

1244 @readonly 

1245 def Personas(self) -> dict[str, Persona]: 

1246 """ 

1247 Read-only property to access the personas the item is about (:attr:`_personas`). 

1248 

1249 E.g. the role of a user story: "As a reviewer, I want ...". 

1250 

1251 :returns: The personas, by ID. 

1252 """ 

1253 return self._personas 

1254 

1255 @readonly 

1256 def Labels(self) -> dict[str, Label]: 

1257 """ 

1258 Read-only property to access the labels of the item (:attr:`_labels`). 

1259 

1260 :returns: The labels, by ID. 

1261 """ 

1262 return self._labels 

1263 

1264 @readonly 

1265 def Attachments(self) -> dict[str, Document]: 

1266 """ 

1267 Read-only property to access the documents attached to the item (:attr:`_attachments`). 

1268 

1269 :returns: The documents, by ID. 

1270 """ 

1271 return self._attachments 

1272 

1273 @readonly 

1274 def Children(self) -> dict[str, Self]: 

1275 """ 

1276 Read-only property to access the child items (:attr:`_children`). 

1277 

1278 :returns: The child items, by ID. 

1279 """ 

1280 return self._children 

1281 

1282 @readonly 

1283 def OutboundLinks(self) -> list[Link]: 

1284 """ 

1285 Read-only property to access the links from this item (:attr:`_outboundLinks`). 

1286 

1287 :returns: The links, in the order they were added. 

1288 """ 

1289 return self._outboundLinks 

1290 

1291 @readonly 

1292 def InboundLinks(self) -> list[Link]: 

1293 """ 

1294 Read-only property to access the links to this item (:attr:`_inboundLinks`). 

1295 

1296 :returns: The links, in the order they were added. 

1297 """ 

1298 return self._inboundLinks 

1299 

1300 @readonly 

1301 def History(self) -> list[HistoryEvent]: 

1302 """ 

1303 Read-only property to access the events in the item's life cycle (:attr:`_history`). 

1304 

1305 :returns: The events, in chronological order. 

1306 """ 

1307 return self._history 

1308 

1309 def IterateChildren(self, predicate: Nullable[Callable[[Self], bool]] = None) -> Generator[Self, None, None]: 

1310 """ 

1311 Iterate all or selected child items. 

1312 

1313 If parameter ``predicate`` is not None, the given filter function is used to skip items in the generator. 

1314 

1315 :param predicate: Optional, filter function accepting an item and returning a boolean. Default: ``None``. 

1316 :returns: A generator of the child items. 

1317 """ 

1318 if predicate is None: 

1319 yield from self._children.values() 

1320 else: 

1321 for child in self._children.values(): 

1322 if predicate(child): 

1323 yield child 

1324 

1325 def IterateDescendants(self, predicate: Nullable[Callable[[Self], bool]] = None) -> Generator[Self, None, None]: 

1326 """ 

1327 Iterate all or selected items below this one - its children, grandchildren, ... -, depth-first: each child 

1328 followed by the items below it. 

1329 

1330 If parameter ``predicate`` is not None, the given filter function is used to skip items in the generator. A skipped 

1331 item's children are still iterated. 

1332 

1333 :param predicate: Optional, filter function accepting an item and returning a boolean. Default: ``None``. 

1334 :returns: A generator of the items below this one. 

1335 """ 

1336 if predicate is None: 

1337 for child in self._children.values(): 

1338 yield child 

1339 yield from child.IterateDescendants() 

1340 else: 

1341 for child in self._children.values(): 

1342 if predicate(child): 

1343 yield child 

1344 

1345 yield from child.IterateDescendants(predicate) 

1346 

1347 def LinkTo( 

1348 self, 

1349 destination: BaseItem, 

1350 kind: LinkKind, 

1351 *, 

1352 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

1353 ) -> Link: 

1354 """ 

1355 Create an outbound link from this item to the given item. 

1356 

1357 :param destination: The item the link points to. 

1358 :param kind: The relationship the link states. 

1359 :param keyValuePairs: Optional, the key-value pairs of the link. Default: ``None``. 

1360 :returns: The link from this item to the destination. 

1361 :raises ValueError: If parameter ``destination`` is ``None``. 

1362 :raises TypeError: If parameter ``destination`` isn't of type :class:`BaseItem`. 

1363 :raises ValueError: If parameter ``destination`` is this item. 

1364 :raises NotInSameSpecificationError: If parameter ``destination`` isn't part of this item's specification. 

1365 :raises ValueError: If parameter ``kind`` is ``None``. 

1366 :raises TypeError: If parameter ``kind`` isn't of type :class:`LinkKind`. 

1367 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1368 :raises NotInSameSpecificationError: If this item isn't part of a specification. 

1369 :raises DuplicateLinkError: If this item has a link of this kind to the destination already. 

1370 

1371 .. seealso:: 

1372 

1373 :meth:`LinkFrom` 

1374 |rarr| Create an inbound link from the given item to this item. 

1375 """ 

1376 return Link(self, destination, kind, keyValuePairs=keyValuePairs) 

1377 

1378 def LinkFrom( 

1379 self, 

1380 source: BaseItem, 

1381 kind: LinkKind, 

1382 *, 

1383 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

1384 ) -> Link: 

1385 """ 

1386 Create an inbound link from the given item to this item. 

1387 

1388 :param source: The item the link starts at. 

1389 :param kind: The relationship the link states. 

1390 :param keyValuePairs: Optional, the key-value pairs of the link. Default: ``None``. 

1391 :returns: The link from the source to this item. 

1392 :raises ValueError: If parameter ``source`` is ``None``. 

1393 :raises TypeError: If parameter ``source`` isn't of type :class:`BaseItem`. 

1394 :raises NotInSameSpecificationError: If parameter ``source`` isn't part of a specification. 

1395 :raises ValueError: If parameter ``source`` is this item. 

1396 :raises ValueError: If parameter ``kind`` is ``None``. 

1397 :raises TypeError: If parameter ``kind`` isn't of type :class:`LinkKind`. 

1398 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1399 :raises NotInSameSpecificationError: If this item isn't part of the source's specification. 

1400 :raises DuplicateLinkError: If the source has a link of this kind to this item already. 

1401 

1402 .. seealso:: 

1403 

1404 :meth:`LinkTo` 

1405 |rarr| Create an outbound link from this item to the given item. 

1406 """ 

1407 return Link(source, self, kind, keyValuePairs=keyValuePairs) 

1408 

1409 def IterateOutboundLinks(self, predicate: Nullable[Callable[[Link], bool]] = None) -> Generator[Link, None, None]: 

1410 """ 

1411 Iterate all or selected links from this item. 

1412 

1413 If parameter ``predicate`` is not None, the given filter function is used to skip links in the generator. 

1414 

1415 :param predicate: Optional, filter function accepting a link and returning a boolean. Default: ``None``. 

1416 :returns: A generator of the links from this item. 

1417 """ 

1418 if predicate is None: 

1419 yield from self._outboundLinks 

1420 else: 

1421 for link in self._outboundLinks: 

1422 if predicate(link): 

1423 yield link 

1424 

1425 def IterateInboundLinks(self, predicate: Nullable[Callable[[Link], bool]] = None) -> Generator[Link, None, None]: 

1426 """ 

1427 Iterate all or selected links to this item. 

1428 

1429 If parameter ``predicate`` is not None, the given filter function is used to skip links in the generator. 

1430 

1431 :param predicate: Optional, filter function accepting a link and returning a boolean. Default: ``None``. 

1432 :returns: A generator of the links to this item. 

1433 """ 

1434 if predicate is None: 

1435 yield from self._inboundLinks 

1436 else: 

1437 for link in self._inboundLinks: 

1438 if predicate(link): 

1439 yield link 

1440 

1441 def _IterateTrace( 

1442 self, 

1443 predicate: Nullable[Callable[[Link], bool]], 

1444 inbound: bool 

1445 ) -> Generator[BaseItem, None, None]: 

1446 """ 

1447 Iterate the items reached by following links transitively, depth-first, each item once. 

1448 

1449 :param predicate: Filter function accepting a link and returning a boolean, or ``None`` to follow every link. 

1450 :param inbound: ``True`` to follow links to their sources, ``False`` to their destinations. 

1451 :returns: A generator of the items reached, without this item. 

1452 """ 

1453 visited = {self} 

1454 

1455 if predicate is None: 

1456 def follow(item: BaseItem) -> Generator[BaseItem, None, None]: 

1457 """ 

1458 Nested function for recursion, following every link. 

1459 

1460 :param item: The item, whose links are followed. 

1461 :returns: A generator of the items reached from the item, not visited before. 

1462 """ 

1463 for link in (item._inboundLinks if inbound else item._outboundLinks): 

1464 reached = link._source if inbound else link._destination 

1465 if reached not in visited: 

1466 visited.add(reached) 

1467 yield reached 

1468 yield from follow(reached) 

1469 else: 

1470 def follow(item: BaseItem) -> Generator[BaseItem, None, None]: 

1471 """ 

1472 Nested function for recursion, following the links the predicate accepts. 

1473 

1474 :param item: The item, whose links are followed. 

1475 :returns: A generator of the items reached from the item, not visited before. 

1476 """ 

1477 for link in (item._inboundLinks if inbound else item._outboundLinks): 

1478 reached = link._source if inbound else link._destination 

1479 if reached not in visited and predicate(link): 

1480 visited.add(reached) 

1481 yield reached 

1482 yield from follow(reached) 

1483 

1484 yield from follow(self) 

1485 

1486 def IterateTrace(self, predicate: Nullable[Callable[[Link], bool]] = None) -> Generator[BaseItem, None, None]: 

1487 """ 

1488 Iterate the items this item traces to: the destinations of its links, and of their links, ..., depth-first, each 

1489 item once. 

1490 

1491 If parameter ``predicate`` is not None, only links accepted by the given filter function are followed. 

1492 

1493 :param predicate: Optional, filter function accepting a link and returning a boolean. Default: ``None``. 

1494 :returns: A generator of the items reached, without this item. 

1495 """ 

1496 yield from self._IterateTrace(predicate, False) 

1497 

1498 def IterateInboundTrace(self, predicate: Nullable[Callable[[Link], bool]] = None) -> Generator[BaseItem, None, None]: 

1499 """ 

1500 Iterate the items tracing to this item: the sources of its inbound links, and of their inbound links, ..., 

1501 depth-first, each item once. 

1502 

1503 If parameter ``predicate`` is not None, only links accepted by the given filter function are followed. 

1504 

1505 :param predicate: Optional, filter function accepting a link and returning a boolean. Default: ``None``. 

1506 :returns: A generator of the items reached, without this item. 

1507 """ 

1508 yield from self._IterateTrace(predicate, True) 

1509 

1510 

1511@export 

1512class Requirement(BaseItem): 

1513 """ 

1514 A requirement: a condition or capability the system must, should or may meet, its kind and its obligation. 

1515 

1516 Its parent is the :class:`RequirementSpecification` for a top-level requirement, or the :class:`Requirement` it 

1517 refines - a child requirement. The specification registers either by its ID. 

1518 """ 

1519 

1520 _PARENT_TYPE: ClassVar[tuple[type, ...]] #: In a requirement or the specification; assigned below. 

1521 

1522 _kind: RequirementKind #: What the requirement constrains. 

1523 _obligation: _Obligation #: Whether the requirement must, should or may be met. 

1524 _verificationMethods: VerificationMethod #: The methods verifying the requirement. 

1525 _acceptanceCriteria: list[str] #: The criteria the verification accepts the requirement by. 

1526 _specificationItems: dict[str, SpecificationItem] #: The specification items specifying this requirement, by ID. 

1527 

1528 def __init__( 

1529 self, 

1530 identifier: str, 

1531 title: str, 

1532 kind: RequirementKind, 

1533 description: str = "", 

1534 created: Nullable[datetime] = None, 

1535 *, 

1536 obligation: Obligation = Obligation.Mandatory, 

1537 verificationMethods: VerificationMethod = VerificationMethod.Unknown, 

1538 acceptanceCriteria: Nullable[Iterable[str]] = None, 

1539 rationale: str = "", 

1540 state: ItemState = ItemState.Draft, 

1541 layer: Nullable[Layer] = None, 

1542 authors: Nullable[Iterable[Person]] = None, 

1543 owners: Nullable[Iterable[Person]] = None, 

1544 personas: Nullable[Iterable[Persona]] = None, 

1545 labels: Nullable[Iterable[Label]] = None, 

1546 attachments: Nullable[Iterable[Document]] = None, 

1547 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1548 parent: Nullable[Requirement | RequirementSpecification] = None 

1549 ) -> None: 

1550 """ 

1551 Initializes a requirement, adds it to its parent, and registers it in its specification. 

1552 

1553 :param identifier: The requirement's ID. 

1554 :param title: The requirement's title. 

1555 :param kind: What the requirement constrains. 

1556 :param description: Optional, the requirement's description. Default: ``""``. 

1557 :param created: Optional, the time the requirement was created; without a time zone, it is a 

1558 local time. Default: ``None``, the current time. 

1559 :param obligation: Optional, whether the requirement must, should or may be met. Default: 

1560 :attr:`Obligation.Mandatory`. 

1561 :param verificationMethods: Optional, the methods verifying the requirement. Default: 

1562 :attr:`VerificationMethod.Unknown`. 

1563 :param acceptanceCriteria: Optional, the criteria the verification accepts the requirement by. Default: 

1564 ``None``. 

1565 :param rationale: Optional, why the requirement exists or is as it is. Default: ``""``. 

1566 :param state: Optional, the requirement's state. Default: :attr:`ItemState.Draft`. 

1567 :param layer: Optional, the layer the requirement belongs to. Default: ``None``. 

1568 :param authors: Optional, the authors of the requirement. Default: ``None``. 

1569 :param owners: Optional, the owners of the requirement. Default: ``None``. 

1570 :param personas: Optional, the personas the requirement is about. Default: ``None``. 

1571 :param labels: Optional, the labels of the requirement. Default: ``None``. 

1572 :param attachments: Optional, the documents attached to the requirement. Default: ``None``. 

1573 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1574 :param parent: Optional, the requirement this one refines, or the specification. Default: 

1575 ``None``. 

1576 :raises ValueError: If parameter ``identifier`` is ``None``. 

1577 :raises TypeError: If parameter ``identifier`` isn't of type :class:`str`. 

1578 :raises ValueError: If parameter ``identifier`` is empty. 

1579 :raises ValueError: If parameter ``title`` is ``None``. 

1580 :raises TypeError: If parameter ``title`` isn't of type :class:`str`. 

1581 :raises ValueError: If parameter ``title`` is empty. 

1582 :raises ValueError: If parameter ``kind`` is ``None``. 

1583 :raises TypeError: If parameter ``kind`` isn't of type :class:`RequirementKind`. 

1584 :raises ValueError: If parameter ``description`` is ``None``. 

1585 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

1586 :raises TypeError: If parameter ``created`` isn't of type :class:`~datetime.datetime`. 

1587 :raises ValueError: If parameter ``obligation`` is ``None``. 

1588 :raises TypeError: If parameter ``obligation`` isn't of type :class:`Obligation`. 

1589 :raises ValueError: If parameter ``verificationMethods`` is ``None``. 

1590 :raises TypeError: If parameter ``verificationMethods`` isn't of type :class:`VerificationMethod`. 

1591 :raises TypeError: If parameter ``acceptanceCriteria`` isn't iterable, or is a single string. 

1592 :raises TypeError: If parameter ``acceptanceCriteria`` contains an element not of type 

1593 :class:`str`. 

1594 :raises ValueError: If parameter ``acceptanceCriteria`` contains an empty criterion. 

1595 :raises ValueError: If parameter ``rationale`` is ``None``. 

1596 :raises TypeError: If parameter ``rationale`` isn't of type :class:`str`. 

1597 :raises ValueError: If parameter ``state`` is ``None``. 

1598 :raises TypeError: If parameter ``state`` isn't of type :class:`ItemState`. 

1599 :raises TypeError: If parameter ``layer`` isn't of type :class:`Layer`. 

1600 :raises NotInSameSpecificationError: If parameter ``layer`` isn't part of the requirement's specification. 

1601 :raises TypeError: If parameter ``authors`` isn't iterable, or is a single element. 

1602 :raises TypeError: If parameter ``authors`` contains an element not of type 

1603 :class:`~pyEDAA.Reports.Person`. 

1604 :raises NotInSameSpecificationError: If parameter ``authors`` contains an element, which isn't part of the 

1605 requirement's specification. 

1606 :raises DuplicatePersonError: If parameter ``authors`` contains a person twice. 

1607 :raises TypeError: If parameter ``owners`` isn't iterable, or is a single element. 

1608 :raises TypeError: If parameter ``owners`` contains an element not of type 

1609 :class:`~pyEDAA.Reports.Person`. 

1610 :raises NotInSameSpecificationError: If parameter ``owners`` contains an element, which isn't part of the 

1611 requirement's specification. 

1612 :raises DuplicatePersonError: If parameter ``owners`` contains a person twice. 

1613 :raises TypeError: If parameter ``personas`` isn't iterable, or is a single element. 

1614 :raises TypeError: If parameter ``personas`` contains an element not of type :class:`Persona`. 

1615 :raises NotInSameSpecificationError: If parameter ``personas`` contains an element, which isn't part of the 

1616 requirement's specification. 

1617 :raises DuplicatePersonaError: If parameter ``personas`` contains a persona twice. 

1618 :raises TypeError: If parameter ``labels`` isn't iterable, or is a single element. 

1619 :raises TypeError: If parameter ``labels`` contains an element not of type :class:`Label`. 

1620 :raises NotInSameSpecificationError: If parameter ``labels`` contains an element, which isn't part of the 

1621 requirement's specification. 

1622 :raises DuplicateLabelError: If parameter ``labels`` contains a label twice. 

1623 :raises TypeError: If parameter ``attachments`` isn't iterable, or is a single element. 

1624 :raises TypeError: If parameter ``attachments`` contains an element not of type :class:`Document`. 

1625 :raises NotInSameSpecificationError: If parameter ``attachments`` contains an element, which isn't part of the 

1626 requirement's specification. 

1627 :raises DuplicateDocumentError: If parameter ``attachments`` contains a document twice. 

1628 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1629 :raises TypeError: If parameter ``parent`` isn't of type :class:`Requirement` or 

1630 :class:`RequirementSpecification`. 

1631 :raises DuplicateRequirementError: If the specification already contains a requirement of this ID. 

1632 """ 

1633 super().__init__( 

1634 identifier, 

1635 title, 

1636 description, 

1637 created, 

1638 rationale=rationale, 

1639 state=state, 

1640 layer=layer, 

1641 authors=authors, 

1642 owners=owners, 

1643 personas=personas, 

1644 labels=labels, 

1645 attachments=attachments, 

1646 keyValuePairs=keyValuePairs, 

1647 parent=parent 

1648 ) 

1649 

1650 if kind is None: 

1651 raise ValueError("Parameter 'kind' is None.") 

1652 elif not isinstance(kind, RequirementKind): 

1653 ex = TypeError("Parameter 'kind' is not of type 'RequirementKind'.") 

1654 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

1655 raise ex 

1656 

1657 if obligation is None: 

1658 raise ValueError("Parameter 'obligation' is None.") 

1659 elif not isinstance(obligation, Obligation): 

1660 ex = TypeError("Parameter 'obligation' is not of type 'Obligation'.") 

1661 ex.add_note(f"Got type '{getFullyQualifiedName(obligation)}'.") 

1662 raise ex 

1663 

1664 if verificationMethods is None: 

1665 raise ValueError("Parameter 'verificationMethods' is None.") 

1666 elif not isinstance(verificationMethods, VerificationMethod): 

1667 ex = TypeError("Parameter 'verificationMethods' is not of type 'VerificationMethod'.") 

1668 ex.add_note(f"Got type '{getFullyQualifiedName(verificationMethods)}'.") 

1669 raise ex 

1670 

1671 self._acceptanceCriteria = acceptanceCriterionList = [] 

1672 if acceptanceCriteria is not None: 

1673 if not isinstance(acceptanceCriteria, Iterable): 

1674 ex = TypeError("Parameter 'acceptanceCriteria' is not iterable.") 

1675 ex.add_note(f"Got type '{getFullyQualifiedName(acceptanceCriteria)}'.") 

1676 raise ex 

1677 elif isinstance(acceptanceCriteria, str): 

1678 ex = TypeError("Parameter 'acceptanceCriteria' is a single string, not an iterable of strings.") 

1679 ex.add_note(f"Got value '{acceptanceCriteria}'.") 

1680 raise ex 

1681 

1682 for criterion in acceptanceCriteria: 

1683 if not isinstance(criterion, str): 

1684 ex = TypeError("Parameter 'acceptanceCriteria' contains an element not of type 'str'.") 

1685 ex.add_note(f"Got type '{getFullyQualifiedName(criterion)}'.") 

1686 raise ex 

1687 elif criterion == "": 

1688 raise ValueError("Parameter 'acceptanceCriteria' contains an empty criterion.") 

1689 

1690 acceptanceCriterionList.append(criterion) 

1691 

1692 self._kind = kind 

1693 self._obligation = obligation 

1694 self._verificationMethods = verificationMethods 

1695 self._specificationItems = {} 

1696 

1697 if parent is not None: 

1698 parent._AddElement(self) 

1699 

1700 @readonly 

1701 def Kind(self) -> RequirementKind: 

1702 """ 

1703 Read-only property to access what the requirement constrains (:attr:`_kind`). 

1704 

1705 :returns: The requirement's kind. 

1706 """ 

1707 return self._kind 

1708 

1709 @readonly 

1710 def Obligation(self) -> Obligation: 

1711 """ 

1712 Read-only property to access whether the requirement must, should or may be met (:attr:`_obligation`). 

1713 

1714 :returns: The requirement's obligation. 

1715 """ 

1716 return self._obligation 

1717 

1718 @readonly 

1719 def VerificationMethods(self) -> VerificationMethod: 

1720 """ 

1721 Read-only property to access the methods verifying the requirement (:attr:`_verificationMethods`). 

1722 

1723 :returns: The verification methods. 

1724 """ 

1725 return self._verificationMethods 

1726 

1727 @readonly 

1728 def AcceptanceCriteria(self) -> list[str]: 

1729 """ 

1730 Read-only property to access the criteria the verification accepts the requirement by (:attr:`_acceptanceCriteria`). 

1731 

1732 :returns: The acceptance criteria, in the order given. 

1733 """ 

1734 return self._acceptanceCriteria 

1735 

1736 @readonly 

1737 def SpecificationItems(self) -> dict[str, SpecificationItem]: 

1738 """ 

1739 Read-only property to access the specification items specifying this requirement (:attr:`_specificationItems`). 

1740 

1741 The relation is maintained by :meth:`SpecificationItem.AddRequirement`. 

1742 

1743 :returns: The specification items, by ID. 

1744 """ 

1745 return self._specificationItems 

1746 

1747 

1748Requirement._PARENT_TYPE = (Requirement, RequirementSpecification) 

1749Requirement._DUPLICATE_ERROR = DuplicateRequirementError 

1750 

1751 

1752@export 

1753class SpecificationItem(BaseItem): 

1754 """ 

1755 A specification item: how the system meets requirements. 

1756 

1757 E.g. a design decision, an architecture or an interface description. It mirrors a requirement - with an ID, a title, a 

1758 state, a layer, persons, personas, labels, documents, child items, links and a history -, and specifies any number of 

1759 requirements; a requirement is specified by any number of specification items. 

1760 

1761 Its parent is the :class:`RequirementSpecification` for a top-level item, or the :class:`SpecificationItem` it 

1762 details. The specification registers either by its ID. 

1763 """ 

1764 

1765 _PARENT_TYPE: ClassVar[tuple[type, ...]] #: An item is in an item or the specification; assigned below the class. 

1766 

1767 _requirements: dict[str, Requirement] #: The requirements this item specifies, by ID. 

1768 

1769 def __init__( 

1770 self, 

1771 identifier: str, 

1772 title: str, 

1773 description: str = "", 

1774 created: Nullable[datetime] = None, 

1775 *, 

1776 requirements: Nullable[Iterable[Requirement]] = None, 

1777 rationale: str = "", 

1778 state: ItemState = ItemState.Draft, 

1779 layer: Nullable[Layer] = None, 

1780 authors: Nullable[Iterable[Person]] = None, 

1781 owners: Nullable[Iterable[Person]] = None, 

1782 personas: Nullable[Iterable[Persona]] = None, 

1783 labels: Nullable[Iterable[Label]] = None, 

1784 attachments: Nullable[Iterable[Document]] = None, 

1785 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

1786 parent: Nullable[SpecificationItem | RequirementSpecification] = None 

1787 ) -> None: 

1788 """ 

1789 Initializes a specification item, adds it to its parent, registers it in its specification, and relates it to the 

1790 requirements it specifies. 

1791 

1792 :param identifier: The item's ID. 

1793 :param title: The item's title. 

1794 :param description: Optional, the item's description. Default: ``""``. 

1795 :param created: Optional, the time the item was created; without a time zone, it is a local 

1796 time. Default: ``None``, the current time. 

1797 :param requirements: Optional, the requirements the item specifies. Default: ``None``. 

1798 :param rationale: Optional, why the item exists or is as it is. Default: ``""``. 

1799 :param state: Optional, the item's state. Default: :attr:`ItemState.Draft`. 

1800 :param layer: Optional, the layer the item belongs to. Default: ``None``. 

1801 :param authors: Optional, the authors of the item. Default: ``None``. 

1802 :param owners: Optional, the owners of the item. Default: ``None``. 

1803 :param personas: Optional, the personas the item is about. Default: ``None``. 

1804 :param labels: Optional, the labels of the item. Default: ``None``. 

1805 :param attachments: Optional, the documents attached to the item. Default: ``None``. 

1806 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

1807 :param parent: Optional, the specification item this one details, or the specification. 

1808 Default: ``None``. 

1809 :raises ValueError: If parameter ``identifier`` is ``None``. 

1810 :raises TypeError: If parameter ``identifier`` isn't of type :class:`str`. 

1811 :raises ValueError: If parameter ``identifier`` is empty. 

1812 :raises ValueError: If parameter ``title`` is ``None``. 

1813 :raises TypeError: If parameter ``title`` isn't of type :class:`str`. 

1814 :raises ValueError: If parameter ``title`` is empty. 

1815 :raises ValueError: If parameter ``description`` is ``None``. 

1816 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

1817 :raises TypeError: If parameter ``created`` isn't of type :class:`~datetime.datetime`. 

1818 :raises TypeError: If parameter ``requirements`` isn't iterable, or is a single element. 

1819 :raises TypeError: If parameter ``requirements`` contains an element not of type 

1820 :class:`Requirement`. 

1821 :raises NotInSameSpecificationError: If parameter ``requirements`` contains a requirement, which isn't part of 

1822 the item's specification. 

1823 :raises DuplicateRequirementError: If parameter ``requirements`` contains a requirement twice. 

1824 :raises ValueError: If parameter ``rationale`` is ``None``. 

1825 :raises TypeError: If parameter ``rationale`` isn't of type :class:`str`. 

1826 :raises ValueError: If parameter ``state`` is ``None``. 

1827 :raises TypeError: If parameter ``state`` isn't of type :class:`ItemState`. 

1828 :raises TypeError: If parameter ``layer`` isn't of type :class:`Layer`. 

1829 :raises NotInSameSpecificationError: If parameter ``layer`` isn't part of the item's specification. 

1830 :raises TypeError: If parameter ``authors`` isn't iterable, or is a single element. 

1831 :raises TypeError: If parameter ``authors`` contains an element not of type 

1832 :class:`~pyEDAA.Reports.Person`. 

1833 :raises NotInSameSpecificationError: If parameter ``authors`` contains an element, which isn't part of the 

1834 item's specification. 

1835 :raises DuplicatePersonError: If parameter ``authors`` contains a person twice. 

1836 :raises TypeError: If parameter ``owners`` isn't iterable, or is a single element. 

1837 :raises TypeError: If parameter ``owners`` contains an element not of type 

1838 :class:`~pyEDAA.Reports.Person`. 

1839 :raises NotInSameSpecificationError: If parameter ``owners`` contains an element, which isn't part of the item's 

1840 specification. 

1841 :raises DuplicatePersonError: If parameter ``owners`` contains a person twice. 

1842 :raises TypeError: If parameter ``personas`` isn't iterable, or is a single element. 

1843 :raises TypeError: If parameter ``personas`` contains an element not of type :class:`Persona`. 

1844 :raises NotInSameSpecificationError: If parameter ``personas`` contains an element, which isn't part of the 

1845 item's specification. 

1846 :raises DuplicatePersonaError: If parameter ``personas`` contains a persona twice. 

1847 :raises TypeError: If parameter ``labels`` isn't iterable, or is a single element. 

1848 :raises TypeError: If parameter ``labels`` contains an element not of type :class:`Label`. 

1849 :raises NotInSameSpecificationError: If parameter ``labels`` contains an element, which isn't part of the item's 

1850 specification. 

1851 :raises DuplicateLabelError: If parameter ``labels`` contains a label twice. 

1852 :raises TypeError: If parameter ``attachments`` isn't iterable, or is a single element. 

1853 :raises TypeError: If parameter ``attachments`` contains an element not of type 

1854 :class:`Document`. 

1855 :raises NotInSameSpecificationError: If parameter ``attachments`` contains an element, which isn't part of the 

1856 item's specification. 

1857 :raises DuplicateDocumentError: If parameter ``attachments`` contains a document twice. 

1858 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

1859 :raises TypeError: If parameter ``parent`` isn't of type :class:`SpecificationItem` or 

1860 :class:`RequirementSpecification`. 

1861 :raises DuplicateSpecificationItemError: If the specification already contains a specification item of this ID. 

1862 """ 

1863 super().__init__( 

1864 identifier, 

1865 title, 

1866 description, 

1867 created, 

1868 rationale=rationale, 

1869 state=state, 

1870 layer=layer, 

1871 authors=authors, 

1872 owners=owners, 

1873 personas=personas, 

1874 labels=labels, 

1875 attachments=attachments, 

1876 keyValuePairs=keyValuePairs, 

1877 parent=parent 

1878 ) 

1879 

1880 self._requirements = {} 

1881 

1882 if requirements is not None: 

1883 if not isinstance(requirements, Iterable): 

1884 ex = TypeError("Parameter 'requirements' is not iterable.") 

1885 ex.add_note(f"Got type '{getFullyQualifiedName(requirements)}'.") 

1886 raise ex 

1887 elif isinstance(requirements, KeyValuePairsMixin): 

1888 ex = TypeError("Parameter 'requirements' is a single element, not an iterable of elements.") 

1889 ex.add_note(f"Got type '{getFullyQualifiedName(requirements)}'.") 

1890 raise ex 

1891 

1892 for requirement in requirements: 

1893 if not isinstance(requirement, Requirement): 

1894 ex = TypeError("Parameter 'requirements' contains an element not of type 'Requirement'.") 

1895 ex.add_note(f"Got type '{getFullyQualifiedName(requirement)}'.") 

1896 raise ex 

1897 elif self._root is None or requirement._root is not self._root: 

1898 ex = NotInSameSpecificationError( 

1899 "Parameter 'requirements' contains a requirement not part of the item's specification." 

1900 ) 

1901 ex.add_note(f"Got requirement '{requirement._id}'.") 

1902 raise ex 

1903 elif requirement._id in self._requirements: 

1904 ex = DuplicateRequirementError("Parameter 'requirements' contains a requirement twice.") 

1905 ex.add_note(f"Got requirement '{requirement._id}'.") 

1906 raise ex 

1907 

1908 self._requirements[requirement._id] = requirement 

1909 

1910 if parent is not None: 

1911 parent._AddElement(self) 

1912 

1913 for requirement in self._requirements.values(): 

1914 requirement._specificationItems[identifier] = self 

1915 

1916 @readonly 

1917 def Requirements(self) -> dict[str, Requirement]: 

1918 """ 

1919 Read-only property to access the requirements this item specifies (:attr:`_requirements`). 

1920 

1921 :returns: The requirements, by ID. 

1922 """ 

1923 return self._requirements 

1924 

1925 def AddRequirement(self, requirement: Requirement) -> None: 

1926 """ 

1927 Relate this item to a requirement it specifies: the item lists the requirement, and the requirement lists the item. 

1928 

1929 :param requirement: The requirement. 

1930 :raises ValueError: If parameter ``requirement`` is ``None``. 

1931 :raises TypeError: If parameter ``requirement`` isn't of type :class:`Requirement`. 

1932 :raises NotInSameSpecificationError: If parameter ``requirement`` isn't part of the item's specification. 

1933 :raises DuplicateRequirementError: If the item specifies the requirement already. 

1934 """ 

1935 if requirement is None: 

1936 raise ValueError("Parameter 'requirement' is None.") 

1937 elif not isinstance(requirement, Requirement): 

1938 ex = TypeError("Parameter 'requirement' is not of type 'Requirement'.") 

1939 ex.add_note(f"Got type '{getFullyQualifiedName(requirement)}'.") 

1940 raise ex 

1941 elif self._root is None or requirement._root is not self._root: 

1942 ex = NotInSameSpecificationError("Parameter 'requirement' isn't part of the item's specification.") 

1943 ex.add_note(f"Got requirement '{requirement._id}'.") 

1944 raise ex 

1945 elif requirement._id in self._requirements: 

1946 raise DuplicateRequirementError( 

1947 f"Specification item '{self._id}' specifies requirement '{requirement._id}' already." 

1948 ) 

1949 

1950 self._requirements[requirement._id] = requirement 

1951 requirement._specificationItems[self._id] = self 

1952 

1953 

1954SpecificationItem._PARENT_TYPE = (SpecificationItem, RequirementSpecification) 

1955SpecificationItem._DUPLICATE_ERROR = DuplicateSpecificationItemError 

1956 

1957 

1958@export 

1959class LinkKind(Enum): 

1960 """ 

1961 The relationship a link states: the source item *<kind>* the destination item. 

1962 

1963 A link connects items of the same or of different layers, e.g. an FPGA firmware requirement derived from an electrical 

1964 one. Which specification item specifies which requirement, isn't a link, but the relation of 

1965 :attr:`SpecificationItem.Requirements` and :attr:`Requirement.SpecificationItems`. 

1966 """ 

1967 

1968 Refines = 1 #: The source states the destination in more detail, outside the item hierarchy. 

1969 DerivesFrom = 2 #: The source is derived from the destination, e.g. from a higher layer. 

1970 CounterpartOf = 3 #: The source is the destination's counterpart in another layer. 

1971 DependsOn = 4 #: The source can only be met, if the destination is met. 

1972 ConflictsWith = 5 #: The source and the destination can't both be met. 

1973 Duplicates = 6 #: The source states the same as the destination. 

1974 Replaces = 7 #: The source replaces the destination, which is obsolete. 

1975 RelatesTo = 8 #: Any other relationship. 

1976 

1977 

1978@export 

1979class Link(KeyValuePairsMixin, metaclass=ExtendedType, slots=True): 

1980 """ 

1981 A link from an item - its source - to another item of the same specification - its destination -, and the 

1982 relationship it states. 

1983 

1984 A link connects two items of the item hierarchy; it is no element of the hierarchy and has no parent. The source 

1985 lists it in :attr:`~BaseItem.OutboundLinks`, the destination in :attr:`~BaseItem.InboundLinks`. 

1986 """ 

1987 

1988 _source: BaseItem #: The item the link starts at. 

1989 _destination: BaseItem #: The item the link points to. 

1990 _kind: LinkKind #: The relationship the link states. 

1991 

1992 def __init__( 

1993 self, 

1994 source: BaseItem, 

1995 destination: BaseItem, 

1996 kind: LinkKind, 

1997 *, 

1998 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

1999 ) -> None: 

2000 """ 

2001 Initializes a link, and adds it to its source and its destination. 

2002 

2003 :param source: The item the link starts at. 

2004 :param destination: The item the link points to. 

2005 :param kind: The relationship the link states. 

2006 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

2007 :raises ValueError: If parameter ``source`` is ``None``. 

2008 :raises TypeError: If parameter ``source`` isn't of type :class:`BaseItem`. 

2009 :raises NotInSameSpecificationError: If parameter ``source`` isn't part of a specification. 

2010 :raises ValueError: If parameter ``destination`` is ``None``. 

2011 :raises TypeError: If parameter ``destination`` isn't of type :class:`BaseItem`. 

2012 :raises ValueError: If parameter ``destination`` is the source. 

2013 :raises NotInSameSpecificationError: If parameter ``destination`` isn't part of the source's specification. 

2014 :raises ValueError: If parameter ``kind`` is ``None``. 

2015 :raises TypeError: If parameter ``kind`` isn't of type :class:`LinkKind`. 

2016 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

2017 :raises DuplicateLinkError: If the source has a link of this kind to this destination already. 

2018 """ 

2019 super().__init__(keyValuePairs) 

2020 

2021 if source is None: 

2022 raise ValueError("Parameter 'source' is None.") 

2023 elif not isinstance(source, BaseItem): 

2024 ex = TypeError("Parameter 'source' is not of type 'BaseItem'.") 

2025 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

2026 raise ex 

2027 elif source._root is None: 

2028 ex = NotInSameSpecificationError("Parameter 'source' isn't part of a specification.") 

2029 ex.add_note(f"Got item '{source._id}'.") 

2030 raise ex 

2031 

2032 if destination is None: 

2033 raise ValueError("Parameter 'destination' is None.") 

2034 elif not isinstance(destination, BaseItem): 

2035 ex = TypeError("Parameter 'destination' is not of type 'BaseItem'.") 

2036 ex.add_note(f"Got type '{getFullyQualifiedName(destination)}'.") 

2037 raise ex 

2038 elif destination is source: 

2039 ex = ValueError("Parameter 'destination' is the source.") 

2040 ex.add_note(f"Got item '{destination._id}'.") 

2041 raise ex 

2042 elif destination._root is not source._root: 

2043 ex = NotInSameSpecificationError("Parameter 'destination' isn't part of the source's specification.") 

2044 ex.add_note(f"Got item '{destination._id}'.") 

2045 raise ex 

2046 

2047 if kind is None: 

2048 raise ValueError("Parameter 'kind' is None.") 

2049 elif not isinstance(kind, LinkKind): 

2050 ex = TypeError("Parameter 'kind' is not of type 'LinkKind'.") 

2051 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

2052 raise ex 

2053 

2054 for link in source._outboundLinks: 

2055 if link._destination is destination and link._kind is kind: 

2056 ex = DuplicateLinkError(f"{getTypeName(source)} '{source._id}' has a '{kind.name}' link already.") 

2057 ex.add_note(f"Got destination '{destination._id}'.") 

2058 raise ex 

2059 

2060 self._source = source 

2061 self._destination = destination 

2062 self._kind = kind 

2063 

2064 source._outboundLinks.append(self) 

2065 destination._inboundLinks.append(self) 

2066 

2067 @readonly 

2068 def Source(self) -> BaseItem: 

2069 """ 

2070 Read-only property to access the item the link starts at (:attr:`_source`). 

2071 

2072 :returns: The source item. 

2073 """ 

2074 return self._source 

2075 

2076 @readonly 

2077 def Destination(self) -> BaseItem: 

2078 """ 

2079 Read-only property to access the item the link points to (:attr:`_destination`). 

2080 

2081 :returns: The destination item. 

2082 """ 

2083 return self._destination 

2084 

2085 @readonly 

2086 def Kind(self) -> LinkKind: 

2087 """ 

2088 Read-only property to access the relationship the link states (:attr:`_kind`). 

2089 

2090 :returns: The link's kind. 

2091 """ 

2092 return self._kind 

2093 

2094 

2095@export 

2096class EventKind(Enum): 

2097 """ 

2098 The kind of an event in the life cycle of an item. 

2099 """ 

2100 

2101 Opened = 1 #: The item was opened, e.g. written as a draft. 

2102 Modified = 2 #: The item's text or fields were changed. 

2103 Reviewed = 3 #: The item was reviewed. 

2104 Approved = 4 #: The item was approved: it became final. 

2105 Rejected = 5 #: The item was rejected. 

2106 Split = 6 #: The item was split; the event names the other items involved. 

2107 Merged = 7 #: The item was merged; the event names the other items involved. 

2108 Closed = 8 #: The item was closed after its verification, e.g. released. 

2109 Reopened = 9 #: The closed item was opened again. 

2110 Obsoleted = 10 #: The item became obsolete, e.g. replaced by the items the event names. 

2111 

2112 

2113# A class with a property named like a class - ``Person`` - can't name that class in the annotation of a field: the 

2114# class body's namespace, where annotations are evaluated, binds the name to the property. 

2115_Person = Person 

2116 

2117 

2118@export 

2119class HistoryEvent(Base): 

2120 """ 

2121 An event in the life cycle of an item: its kind, its time, the person causing it, and the other items involved. 

2122 

2123 Its parent is the item, which lists it in :attr:`~BaseItem.History`, in chronological order. A split or a merge names 

2124 the other items involved, e.g. the requirements a requirement was split into, or merged with. 

2125 """ 

2126 

2127 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (BaseItem, ) #: An event is in the history of an item. 

2128 

2129 _kind: EventKind #: The kind of the event. 

2130 _timestamp: datetime #: The time of the event. 

2131 _person: Nullable[_Person] #: The person causing the event, or ``None``. 

2132 _involved: list[BaseItem] #: The other items involved, in the order given. 

2133 _description: str #: The event's description, or ``""``. 

2134 

2135 def __init__( 

2136 self, 

2137 kind: EventKind, 

2138 timestamp: datetime, 

2139 *, 

2140 person: Nullable[Person] = None, 

2141 involved: Nullable[Iterable[BaseItem]] = None, 

2142 description: str = "", 

2143 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None, 

2144 parent: Nullable[BaseItem] = None 

2145 ) -> None: 

2146 """ 

2147 Initializes a history event, and adds it to the history of its item. 

2148 

2149 :param kind: The kind of the event. 

2150 :param timestamp: The time of the event; without a time zone, it is a local time. 

2151 :param person: Optional, the person causing the event. Default: ``None``. 

2152 :param involved: Optional, the other items involved. Default: ``None``. 

2153 :param description: Optional, the event's description. Default: ``""``. 

2154 :param keyValuePairs: Optional, the key-value pairs. Default: ``None``. 

2155 :param parent: Optional, the item the event happened to. Default: ``None``. 

2156 :raises ValueError: If parameter ``kind`` is ``None``. 

2157 :raises TypeError: If parameter ``kind`` isn't of type :class:`EventKind`. 

2158 :raises ValueError: If parameter ``timestamp`` is ``None``. 

2159 :raises TypeError: If parameter ``timestamp`` isn't of type :class:`~datetime.datetime`. 

2160 :raises RequirementsError: If parameter ``timestamp`` is earlier than the item's creation or its last 

2161 event. 

2162 :raises TypeError: If parameter ``person`` isn't of type :class:`~pyEDAA.Reports.Person`. 

2163 :raises NotInSameSpecificationError: If parameter ``person`` isn't part of the item's specification. 

2164 :raises TypeError: If parameter ``involved`` isn't iterable, or is a single element. 

2165 :raises TypeError: If parameter ``involved`` contains an element not of type 

2166 :class:`BaseItem`. 

2167 :raises NotInSameSpecificationError: If parameter ``involved`` contains an item, which isn't part of the item's 

2168 specification. 

2169 :raises DuplicateRequirementError: If parameter ``involved`` contains a requirement twice. 

2170 :raises DuplicateSpecificationItemError: If parameter ``involved`` contains a specification item twice. 

2171 :raises ValueError: If parameter ``involved`` names no item for a split or a merge. 

2172 :raises ValueError: If parameter ``description`` is ``None``. 

2173 :raises TypeError: If parameter ``description`` isn't of type :class:`str`. 

2174 :raises TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

2175 :raises TypeError: If parameter ``parent`` isn't of type :class:`BaseItem`. 

2176 """ 

2177 super().__init__(keyValuePairs=keyValuePairs, parent=parent) 

2178 

2179 if kind is None: 

2180 raise ValueError("Parameter 'kind' is None.") 

2181 elif not isinstance(kind, EventKind): 

2182 ex = TypeError("Parameter 'kind' is not of type 'EventKind'.") 

2183 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

2184 raise ex 

2185 

2186 if timestamp is None: 

2187 raise ValueError("Parameter 'timestamp' is None.") 

2188 elif not isinstance(timestamp, datetime): 

2189 ex = TypeError("Parameter 'timestamp' is not of type 'datetime'.") 

2190 ex.add_note(f"Got type '{getFullyQualifiedName(timestamp)}'.") 

2191 raise ex 

2192 

2193 if person is not None: 

2194 if not isinstance(person, Person): 

2195 ex = TypeError("Parameter 'person' is not of type 'Person'.") 

2196 ex.add_note(f"Got type '{getFullyQualifiedName(person)}'.") 

2197 raise ex 

2198 elif self._root is None or self._root._persons.get(person._userID) is not person: 

2199 ex = NotInSameSpecificationError("Parameter 'person' isn't part of the item's specification.") 

2200 ex.add_note(f"Got person '{person._userID}'.") 

2201 raise ex 

2202 

2203 self._involved = involvedList = [] 

2204 if involved is not None: 

2205 if not isinstance(involved, Iterable): 

2206 ex = TypeError("Parameter 'involved' is not iterable.") 

2207 ex.add_note(f"Got type '{getFullyQualifiedName(involved)}'.") 

2208 raise ex 

2209 elif isinstance(involved, KeyValuePairsMixin): 

2210 ex = TypeError("Parameter 'involved' is a single element, not an iterable of elements.") 

2211 ex.add_note(f"Got type '{getFullyQualifiedName(involved)}'.") 

2212 raise ex 

2213 

2214 for item in involved: 

2215 if not isinstance(item, BaseItem): 

2216 ex = TypeError("Parameter 'involved' contains an element not of type 'BaseItem'.") 

2217 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") 

2218 raise ex 

2219 elif self._root is None or item._root is not self._root: 

2220 ex = NotInSameSpecificationError( 

2221 "Parameter 'involved' contains an item not part of the item's specification." 

2222 ) 

2223 ex.add_note(f"Got item '{item._id}'.") 

2224 raise ex 

2225 elif item in self._involved: 

2226 ex = item._DUPLICATE_ERROR("Parameter 'involved' contains an item twice.") 

2227 ex.add_note(f"Got item '{item._id}'.") 

2228 raise ex 

2229 

2230 involvedList.append(item) 

2231 

2232 if len(self._involved) == 0 and kind in (EventKind.Split, EventKind.Merged): 

2233 raise ValueError(f"Parameter 'involved' names no item for a '{kind.name}' event.") 

2234 

2235 if description is None: 

2236 raise ValueError("Parameter 'description' is None.") 

2237 elif not isinstance(description, str): 

2238 ex = TypeError("Parameter 'description' is not of type 'str'.") 

2239 ex.add_note(f"Got type '{getFullyQualifiedName(description)}'.") 

2240 raise ex 

2241 

2242 self._kind = kind 

2243 self._timestamp = timestamp 

2244 self._person = person 

2245 self._description = description 

2246 

2247 if parent is not None: 

2248 parent._AddElement(self) 

2249 

2250 @readonly 

2251 def Kind(self) -> EventKind: 

2252 """ 

2253 Read-only property to access the kind of the event (:attr:`_kind`). 

2254 

2255 :returns: The event's kind. 

2256 """ 

2257 return self._kind 

2258 

2259 @readonly 

2260 def Timestamp(self) -> datetime: 

2261 """ 

2262 Read-only property to access the time of the event (:attr:`_timestamp`). 

2263 

2264 :returns: The time; without a time zone, it is a local time. 

2265 """ 

2266 return self._timestamp 

2267 

2268 @readonly 

2269 def Person(self) -> Nullable[Person]: 

2270 """ 

2271 Read-only property to access the person causing the event (:attr:`_person`). 

2272 

2273 :returns: The person, or ``None``. 

2274 """ 

2275 return self._person 

2276 

2277 @readonly 

2278 def Involved(self) -> list[BaseItem]: 

2279 """ 

2280 Read-only property to access the other items involved in the event (:attr:`_involved`). 

2281 

2282 :returns: The items, in the order given. 

2283 """ 

2284 return self._involved 

2285 

2286 @readonly 

2287 def Description(self) -> str: 

2288 """ 

2289 Read-only property to access the event's description (:attr:`_description`). 

2290 

2291 :returns: The description, or ``""``. 

2292 """ 

2293 return self._description