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

838 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-21 23:49 +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 

53from typing import 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 TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

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

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

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

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

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

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

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 TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

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

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

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

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

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 TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

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

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

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

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

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

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

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

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

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

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 TypeError: If parameter ``keyValuePairs`` isn't a mapping. 

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

990 :attr:`_PARENT_TYPE`. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1006 :raises TypeError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1007 ``attachments`` 

1008 isn't iterable, or is a single element. 

1009 :raises TypeError: If parameter ``authors`` or ``owners`` contains an element not of type 

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

1011 :raises TypeError: If parameter ``personas``, ``labels`` or ``attachments`` contains an element 

1012 not of 

1013 type :class:`Persona`, :class:`Label` or :class:`Document`. 

1014 :raises NotInSameSpecificationError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1015 ``attachments`` 

1016 contains an element, which isn't part of the item's specification. 

1017 :raises DuplicatePersonError: If parameter ``authors`` or ``owners`` contains a person twice. 

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

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

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

1021 """ 

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

1023 

1024 if title is None: 

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

1026 elif not isinstance(title, str): 

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

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

1029 raise ex 

1030 elif title == "": 

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

1032 

1033 if rationale is None: 

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

1035 elif not isinstance(rationale, str): 

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

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

1038 raise ex 

1039 

1040 if created is None: 

1041 created = datetime.now() 

1042 elif not isinstance(created, datetime): 

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

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

1045 raise ex 

1046 

1047 if state is None: 

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

1049 elif not isinstance(state, ItemState): 

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

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

1052 raise ex 

1053 

1054 if layer is not None: 

1055 if not isinstance(layer, Layer): 

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

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

1058 raise ex 

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

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

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

1062 raise ex 

1063 

1064 self._title = title 

1065 self._rationale = rationale 

1066 self._created = created 

1067 self._state = state 

1068 self._layer = layer 

1069 self._authors = {} 

1070 self._owners = {} 

1071 self._personas = {} 

1072 self._labels = {} 

1073 self._attachments = {} 

1074 self._children = {} 

1075 self._outboundLinks = [] 

1076 self._inboundLinks = [] 

1077 self._history = [] 

1078 

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

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

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

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

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

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

1085 ): 

1086 if references is None: 

1087 continue 

1088 elif not isinstance(references, Iterable): 

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

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

1091 raise ex 

1092 elif isinstance(references, KeyValuePairsMixin): 

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

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

1095 raise ex 

1096 

1097 for reference in references: 

1098 if not isinstance(reference, referenceType): 

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

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

1101 raise ex 

1102 

1103 if referenceType is Person: 

1104 key = reference._userID 

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

1106 else: 

1107 key = reference._id 

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

1109 

1110 if not registered: 

1111 ex = NotInSameSpecificationError( 

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

1113 ) 

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

1115 raise ex 

1116 elif key in container: 

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

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

1119 raise ex 

1120 

1121 container[key] = reference 

1122 

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

1124 """ 

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

1126 

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

1128 

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

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

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

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

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

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

1135 it has a child of this ID already. 

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

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

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

1139 """ 

1140 if isinstance(element, HistoryEvent): 

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

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

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

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

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

1146 

1147 self._history.append(element) 

1148 else: 

1149 if self._root is not None: 

1150 self._root._AddItems(element) 

1151 elif element._id in self._children: 

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

1153 

1154 self._children[element._id] = element 

1155 

1156 @readonly 

1157 def Title(self) -> str: 

1158 """ 

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

1160 

1161 :returns: The title. 

1162 """ 

1163 return self._title 

1164 

1165 @readonly 

1166 def Rationale(self) -> str: 

1167 """ 

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

1169 

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

1171 """ 

1172 return self._rationale 

1173 

1174 @readonly 

1175 def Created(self) -> datetime: 

1176 """ 

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

1178 

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

1180 """ 

1181 return self._created 

1182 

1183 @property 

1184 def State(self) -> ItemState: 

1185 """ 

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

1187 

1188 :returns: The state. 

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

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

1191 """ 

1192 return self._state 

1193 

1194 @State.setter 

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

1196 if state is None: 

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

1198 elif not isinstance(state, ItemState): 

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

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

1201 raise ex 

1202 

1203 self._state = state 

1204 

1205 @readonly 

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

1207 """ 

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

1209 

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

1211 """ 

1212 return self._layer 

1213 

1214 @readonly 

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

1216 """ 

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

1218 

1219 :returns: The authors, by user ID. 

1220 """ 

1221 return self._authors 

1222 

1223 @readonly 

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

1225 """ 

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

1227 

1228 :returns: The owners, by user ID. 

1229 """ 

1230 return self._owners 

1231 

1232 @readonly 

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

1234 """ 

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

1236 

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

1238 

1239 :returns: The personas, by ID. 

1240 """ 

1241 return self._personas 

1242 

1243 @readonly 

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

1245 """ 

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

1247 

1248 :returns: The labels, by ID. 

1249 """ 

1250 return self._labels 

1251 

1252 @readonly 

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

1254 """ 

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

1256 

1257 :returns: The documents, by ID. 

1258 """ 

1259 return self._attachments 

1260 

1261 @readonly 

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

1263 """ 

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

1265 

1266 :returns: The child items, by ID. 

1267 """ 

1268 return self._children 

1269 

1270 @readonly 

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

1272 """ 

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

1274 

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

1276 """ 

1277 return self._outboundLinks 

1278 

1279 @readonly 

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

1281 """ 

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

1283 

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

1285 """ 

1286 return self._inboundLinks 

1287 

1288 @readonly 

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

1290 """ 

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

1292 

1293 :returns: The events, in chronological order. 

1294 """ 

1295 return self._history 

1296 

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

1298 """ 

1299 Iterate all or selected child items. 

1300 

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

1302 

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

1304 :returns: A generator of the child items. 

1305 """ 

1306 if predicate is None: 

1307 yield from self._children.values() 

1308 else: 

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

1310 if predicate(child): 

1311 yield child 

1312 

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

1314 """ 

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

1316 followed by the items below it. 

1317 

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

1319 item's children are still iterated. 

1320 

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

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

1323 """ 

1324 if predicate is None: 

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

1326 yield child 

1327 yield from child.IterateDescendants() 

1328 else: 

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

1330 if predicate(child): 

1331 yield child 

1332 

1333 yield from child.IterateDescendants(predicate) 

1334 

1335 def LinkTo( 

1336 self, 

1337 destination: BaseItem, 

1338 kind: LinkKind, 

1339 *, 

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

1341 ) -> Link: 

1342 """ 

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

1344 

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

1346 :param kind: The relationship the link states. 

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

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

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

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

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

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

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

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

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

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

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

1358 

1359 .. seealso:: 

1360 

1361 :meth:`LinkFrom` 

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

1363 """ 

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

1365 

1366 def LinkFrom( 

1367 self, 

1368 source: BaseItem, 

1369 kind: LinkKind, 

1370 *, 

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

1372 ) -> Link: 

1373 """ 

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

1375 

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

1377 :param kind: The relationship the link states. 

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

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

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

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

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

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

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

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

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

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

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

1389 

1390 .. seealso:: 

1391 

1392 :meth:`LinkTo` 

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

1394 """ 

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

1396 

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

1398 """ 

1399 Iterate all or selected links from this item. 

1400 

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

1402 

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

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

1405 """ 

1406 if predicate is None: 

1407 yield from self._outboundLinks 

1408 else: 

1409 for link in self._outboundLinks: 

1410 if predicate(link): 

1411 yield link 

1412 

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

1414 """ 

1415 Iterate all or selected links to this item. 

1416 

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

1418 

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

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

1421 """ 

1422 if predicate is None: 

1423 yield from self._inboundLinks 

1424 else: 

1425 for link in self._inboundLinks: 

1426 if predicate(link): 

1427 yield link 

1428 

1429 def _IterateTrace( 

1430 self, 

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

1432 inbound: bool 

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

1434 """ 

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

1436 

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

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

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

1440 """ 

1441 visited = {self} 

1442 

1443 if predicate is None: 

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

1445 """ 

1446 Nested function for recursion, following every link. 

1447 

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

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

1450 """ 

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

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

1453 if reached not in visited: 

1454 visited.add(reached) 

1455 yield reached 

1456 yield from follow(reached) 

1457 else: 

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

1459 """ 

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

1461 

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

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

1464 """ 

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

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

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

1468 visited.add(reached) 

1469 yield reached 

1470 yield from follow(reached) 

1471 

1472 yield from follow(self) 

1473 

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

1475 """ 

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

1477 item once. 

1478 

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

1480 

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

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

1483 """ 

1484 yield from self._IterateTrace(predicate, False) 

1485 

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

1487 """ 

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

1489 depth-first, each 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, True) 

1497 

1498 

1499@export 

1500class Requirement(BaseItem): 

1501 """ 

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

1503 

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

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

1506 """ 

1507 

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

1509 

1510 _kind: RequirementKind #: What the requirement constrains. 

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

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

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

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

1515 

1516 def __init__( 

1517 self, 

1518 identifier: str, 

1519 title: str, 

1520 kind: RequirementKind, 

1521 description: str = "", 

1522 created: Nullable[datetime] = None, 

1523 *, 

1524 obligation: Obligation = Obligation.Mandatory, 

1525 verificationMethods: VerificationMethod = VerificationMethod.Unknown, 

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

1527 rationale: str = "", 

1528 state: ItemState = ItemState.Draft, 

1529 layer: Nullable[Layer] = None, 

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

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

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

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

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

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

1536 parent: Nullable[Requirement | RequirementSpecification] = None 

1537 ) -> None: 

1538 """ 

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

1540 

1541 :param identifier: The requirement's ID. 

1542 :param title: The requirement's title. 

1543 :param kind: What the requirement constrains. 

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

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

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

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

1548 :attr:`Obligation.Mandatory`. 

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

1550 :attr:`VerificationMethod.Unknown`. 

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

1552 ``None``. 

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

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

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

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

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

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

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

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

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

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

1563 ``None``. 

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

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

1566 :class:`RequirementSpecification`. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1582 :raises TypeError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1583 ``attachments`` 

1584 isn't iterable, or is a single element. 

1585 :raises TypeError: If parameter ``authors`` or ``owners`` contains an element not of type 

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

1587 :raises TypeError: If parameter ``personas``, ``labels`` or ``attachments`` contains an element 

1588 not 

1589 of type :class:`Persona`, :class:`Label` or :class:`Document`. 

1590 :raises NotInSameSpecificationError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1591 ``attachments`` 

1592 contains an element, which isn't part of the requirement's specification. 

1593 :raises DuplicatePersonError: If parameter ``authors`` or ``owners`` contains a person twice. 

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

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

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

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

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

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

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

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

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

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

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

1605 :class:`str`. 

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

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

1608 """ 

1609 super().__init__( 

1610 identifier, 

1611 title, 

1612 description, 

1613 created, 

1614 rationale=rationale, 

1615 state=state, 

1616 layer=layer, 

1617 authors=authors, 

1618 owners=owners, 

1619 personas=personas, 

1620 labels=labels, 

1621 attachments=attachments, 

1622 keyValuePairs=keyValuePairs, 

1623 parent=parent 

1624 ) 

1625 

1626 if kind is None: 

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

1628 elif not isinstance(kind, RequirementKind): 

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

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

1631 raise ex 

1632 

1633 if obligation is None: 

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

1635 elif not isinstance(obligation, Obligation): 

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

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

1638 raise ex 

1639 

1640 if verificationMethods is None: 

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

1642 elif not isinstance(verificationMethods, VerificationMethod): 

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

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

1645 raise ex 

1646 

1647 self._acceptanceCriteria = [] 

1648 if acceptanceCriteria is not None: 

1649 if not isinstance(acceptanceCriteria, Iterable): 

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

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

1652 raise ex 

1653 elif isinstance(acceptanceCriteria, str): 

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

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

1656 raise ex 

1657 

1658 for criterion in acceptanceCriteria: 

1659 if not isinstance(criterion, str): 

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

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

1662 raise ex 

1663 elif criterion == "": 

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

1665 

1666 self._acceptanceCriteria.append(criterion) 

1667 

1668 self._kind = kind 

1669 self._obligation = obligation 

1670 self._verificationMethods = verificationMethods 

1671 self._specificationItems = {} 

1672 

1673 if parent is not None: 

1674 parent._AddElement(self) 

1675 

1676 @readonly 

1677 def Kind(self) -> RequirementKind: 

1678 """ 

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

1680 

1681 :returns: The requirement's kind. 

1682 """ 

1683 return self._kind 

1684 

1685 @readonly 

1686 def Obligation(self) -> Obligation: 

1687 """ 

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

1689 

1690 :returns: The requirement's obligation. 

1691 """ 

1692 return self._obligation 

1693 

1694 @readonly 

1695 def VerificationMethods(self) -> VerificationMethod: 

1696 """ 

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

1698 

1699 :returns: The verification methods. 

1700 """ 

1701 return self._verificationMethods 

1702 

1703 @readonly 

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

1705 """ 

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

1707 

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

1709 """ 

1710 return self._acceptanceCriteria 

1711 

1712 @readonly 

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

1714 """ 

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

1716 

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

1718 

1719 :returns: The specification items, by ID. 

1720 """ 

1721 return self._specificationItems 

1722 

1723 

1724Requirement._PARENT_TYPE = (Requirement, RequirementSpecification) 

1725Requirement._DUPLICATE_ERROR = DuplicateRequirementError 

1726 

1727 

1728@export 

1729class SpecificationItem(BaseItem): 

1730 """ 

1731 A specification item: how the system meets requirements. 

1732 

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

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

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

1736 

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

1738 details. The specification registers either by its ID. 

1739 """ 

1740 

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

1742 

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

1744 

1745 def __init__( 

1746 self, 

1747 identifier: str, 

1748 title: str, 

1749 description: str = "", 

1750 created: Nullable[datetime] = None, 

1751 *, 

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

1753 rationale: str = "", 

1754 state: ItemState = ItemState.Draft, 

1755 layer: Nullable[Layer] = None, 

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

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

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

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

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

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

1762 parent: Nullable[SpecificationItem | RequirementSpecification] = None 

1763 ) -> None: 

1764 """ 

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

1766 requirements it specifies. 

1767 

1768 :param identifier: The item's ID. 

1769 :param title: The item's title. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1784 Default: ``None``. 

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

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

1787 :class:`RequirementSpecification`. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1803 :raises TypeError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1804 ``attachments`` isn't iterable, or is a single element. 

1805 :raises TypeError: If parameter ``authors`` or ``owners`` contains an element not of type 

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

1807 :raises TypeError: If parameter ``personas``, ``labels`` or ``attachments`` contains an 

1808 element not of type :class:`Persona`, :class:`Label` or :class:`Document`. 

1809 :raises NotInSameSpecificationError: If parameter ``authors``, ``owners``, ``personas``, ``labels`` or 

1810 ``attachments`` contains an element, which isn't part of the item's 

1811 specification. 

1812 :raises DuplicatePersonError: If parameter ``authors`` or ``owners`` contains a person twice. 

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

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

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

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

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

1818 :class:`Requirement`. 

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

1820 the item's specification. 

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

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

1823 """ 

1824 super().__init__( 

1825 identifier, 

1826 title, 

1827 description, 

1828 created, 

1829 rationale=rationale, 

1830 state=state, 

1831 layer=layer, 

1832 authors=authors, 

1833 owners=owners, 

1834 personas=personas, 

1835 labels=labels, 

1836 attachments=attachments, 

1837 keyValuePairs=keyValuePairs, 

1838 parent=parent 

1839 ) 

1840 

1841 self._requirements = {} 

1842 

1843 if requirements is not None: 

1844 if not isinstance(requirements, Iterable): 

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

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

1847 raise ex 

1848 elif isinstance(requirements, KeyValuePairsMixin): 

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

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

1851 raise ex 

1852 

1853 for requirement in requirements: 

1854 if not isinstance(requirement, Requirement): 

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

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

1857 raise ex 

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

1859 ex = NotInSameSpecificationError( 

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

1861 ) 

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

1863 raise ex 

1864 elif requirement._id in self._requirements: 

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

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

1867 raise ex 

1868 

1869 self._requirements[requirement._id] = requirement 

1870 

1871 if parent is not None: 

1872 parent._AddElement(self) 

1873 

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

1875 requirement._specificationItems[identifier] = self 

1876 

1877 @readonly 

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

1879 """ 

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

1881 

1882 :returns: The requirements, by ID. 

1883 """ 

1884 return self._requirements 

1885 

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

1887 """ 

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

1889 

1890 :param requirement: The requirement. 

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

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

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

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

1895 """ 

1896 if requirement is None: 

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

1898 elif not isinstance(requirement, Requirement): 

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

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

1901 raise ex 

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

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

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

1905 raise ex 

1906 elif requirement._id in self._requirements: 

1907 raise DuplicateRequirementError( 

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

1909 ) 

1910 

1911 self._requirements[requirement._id] = requirement 

1912 requirement._specificationItems[self._id] = self 

1913 

1914 

1915SpecificationItem._PARENT_TYPE = (SpecificationItem, RequirementSpecification) 

1916SpecificationItem._DUPLICATE_ERROR = DuplicateSpecificationItemError 

1917 

1918 

1919@export 

1920class LinkKind(Enum): 

1921 """ 

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

1923 

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

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

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

1927 """ 

1928 

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

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

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

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

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

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

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

1936 RelatesTo = 8 #: Any other relationship. 

1937 

1938 

1939@export 

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

1941 """ 

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

1943 relationship it states. 

1944 

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

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

1947 """ 

1948 

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

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

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

1952 

1953 def __init__( 

1954 self, 

1955 source: BaseItem, 

1956 destination: BaseItem, 

1957 kind: LinkKind, 

1958 *, 

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

1960 ) -> None: 

1961 """ 

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

1963 

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

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

1966 :param kind: The relationship the link states. 

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

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

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

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

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

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

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

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

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

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

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

1978 :raises DuplicateLinkError: If the source has a link of this kind to this destination already. 

1979 """ 

1980 super().__init__(keyValuePairs) 

1981 

1982 if source is None: 

1983 raise ValueError("Parameter 'source' is None.") 

1984 elif not isinstance(source, BaseItem): 

1985 ex = TypeError("Parameter 'source' is not of type 'BaseItem'.") 

1986 ex.add_note(f"Got type '{getFullyQualifiedName(source)}'.") 

1987 raise ex 

1988 elif source._root is None: 

1989 ex = NotInSameSpecificationError("Parameter 'source' isn't part of a specification.") 

1990 ex.add_note(f"Got item '{source._id}'.") 

1991 raise ex 

1992 

1993 if destination is None: 

1994 raise ValueError("Parameter 'destination' is None.") 

1995 elif not isinstance(destination, BaseItem): 

1996 ex = TypeError("Parameter 'destination' is not of type 'BaseItem'.") 

1997 ex.add_note(f"Got type '{getFullyQualifiedName(destination)}'.") 

1998 raise ex 

1999 elif destination is source: 

2000 ex = ValueError("Parameter 'destination' is the source.") 

2001 ex.add_note(f"Got item '{destination._id}'.") 

2002 raise ex 

2003 elif destination._root is not source._root: 

2004 ex = NotInSameSpecificationError("Parameter 'destination' isn't part of the source's specification.") 

2005 ex.add_note(f"Got item '{destination._id}'.") 

2006 raise ex 

2007 

2008 if kind is None: 

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

2010 elif not isinstance(kind, LinkKind): 

2011 ex = TypeError("Parameter 'kind' is not of type 'LinkKind'.") 

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

2013 raise ex 

2014 

2015 for link in source._outboundLinks: 

2016 if link._destination is destination and link._kind is kind: 

2017 ex = DuplicateLinkError(f"{getTypeName(source)} '{source._id}' has a '{kind.name}' link already.") 

2018 ex.add_note(f"Got destination '{destination._id}'.") 

2019 raise ex 

2020 

2021 self._source = source 

2022 self._destination = destination 

2023 self._kind = kind 

2024 

2025 source._outboundLinks.append(self) 

2026 destination._inboundLinks.append(self) 

2027 

2028 @readonly 

2029 def Source(self) -> BaseItem: 

2030 """ 

2031 Read-only property to access the item the link starts at (:attr:`_source`). 

2032 

2033 :returns: The source item. 

2034 """ 

2035 return self._source 

2036 

2037 @readonly 

2038 def Destination(self) -> BaseItem: 

2039 """ 

2040 Read-only property to access the item the link points to (:attr:`_destination`). 

2041 

2042 :returns: The destination item. 

2043 """ 

2044 return self._destination 

2045 

2046 @readonly 

2047 def Kind(self) -> LinkKind: 

2048 """ 

2049 Read-only property to access the relationship the link states (:attr:`_kind`). 

2050 

2051 :returns: The link's kind. 

2052 """ 

2053 return self._kind 

2054 

2055 

2056@export 

2057class EventKind(Enum): 

2058 """ 

2059 The kind of an event in the life cycle of an item. 

2060 """ 

2061 

2062 Opened = 1 #: The item was opened, e.g. written as a draft. 

2063 Modified = 2 #: The item's text or fields were changed. 

2064 Reviewed = 3 #: The item was reviewed. 

2065 Approved = 4 #: The item was approved: it became final. 

2066 Rejected = 5 #: The item was rejected. 

2067 Split = 6 #: The item was split; the event names the other items involved. 

2068 Merged = 7 #: The item was merged; the event names the other items involved. 

2069 Closed = 8 #: The item was closed after its verification, e.g. released. 

2070 Reopened = 9 #: The closed item was opened again. 

2071 Obsoleted = 10 #: The item became obsolete, e.g. replaced by the items the event names. 

2072 

2073 

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

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

2076_Person = Person 

2077 

2078 

2079@export 

2080class HistoryEvent(Base): 

2081 """ 

2082 An event in the life cycle of an item: its kind, its time, the person causing it, and the other items involved. 

2083 

2084 Its parent is the item, which lists it in :attr:`~BaseItem.History`, in chronological order. A split or a merge names 

2085 the other items involved, e.g. the requirements a requirement was split into, or merged with. 

2086 """ 

2087 

2088 _PARENT_TYPE: ClassVar[tuple[type, ...]] = (BaseItem, ) #: An event is in the history of an item. 

2089 

2090 _kind: EventKind #: The kind of the event. 

2091 _timestamp: datetime #: The time of the event. 

2092 _person: Nullable[_Person] #: The person causing the event, or ``None``. 

2093 _involved: list[BaseItem] #: The other items involved, in the order given. 

2094 _description: str #: The event's description, or ``""``. 

2095 

2096 def __init__( 

2097 self, 

2098 kind: EventKind, 

2099 timestamp: datetime, 

2100 *, 

2101 person: Nullable[Person] = None, 

2102 involved: Nullable[Iterable[BaseItem]] = None, 

2103 description: str = "", 

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

2105 parent: Nullable[BaseItem] = None 

2106 ) -> None: 

2107 """ 

2108 Initializes a history event, and adds it to the history of its item. 

2109 

2110 :param kind: The kind of the event. 

2111 :param timestamp: The time of the event; without a time zone, it is a local time. 

2112 :param person: Optional, the person causing the event. Default: ``None``. 

2113 :param involved: Optional, the other items involved. Default: ``None``. 

2114 :param description: Optional, the event's description. Default: ``""``. 

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

2116 :param parent: Optional, the item the event happened to. Default: ``None``. 

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

2118 :raises TypeError: If parameter ``parent`` isn't of type :class:`BaseItem`. 

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

2120 :raises TypeError: If parameter ``kind`` isn't of type :class:`EventKind`. 

2121 :raises ValueError: If parameter ``timestamp`` is ``None``. 

2122 :raises TypeError: If parameter ``timestamp`` isn't of type :class:`~datetime.datetime`. 

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

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

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

2126 :raises TypeError: If parameter ``involved`` contains an element not of type 

2127 :class:`BaseItem`. 

2128 :raises NotInSameSpecificationError: If parameter ``involved`` contains an item, which isn't part of the item's 

2129 specification. 

2130 :raises DuplicateRequirementError: If parameter ``involved`` contains a requirement twice. 

2131 :raises DuplicateSpecificationItemError: If parameter ``involved`` contains a specification item twice. 

2132 :raises ValueError: If parameter ``involved`` names no item for a split or a merge. 

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

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

2135 :raises RequirementsError: If parameter ``timestamp`` is earlier than the item's creation or its last 

2136 event. 

2137 """ 

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

2139 

2140 if kind is None: 

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

2142 elif not isinstance(kind, EventKind): 

2143 ex = TypeError("Parameter 'kind' is not of type 'EventKind'.") 

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

2145 raise ex 

2146 

2147 if timestamp is None: 

2148 raise ValueError("Parameter 'timestamp' is None.") 

2149 elif not isinstance(timestamp, datetime): 

2150 ex = TypeError("Parameter 'timestamp' is not of type 'datetime'.") 

2151 ex.add_note(f"Got type '{getFullyQualifiedName(timestamp)}'.") 

2152 raise ex 

2153 

2154 if person is not None: 

2155 if not isinstance(person, Person): 

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

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

2158 raise ex 

2159 elif self._root is None or self._root._persons.get(person._userID) is not person: 

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

2161 ex.add_note(f"Got person '{person._userID}'.") 

2162 raise ex 

2163 

2164 self._involved = [] 

2165 if involved is not None: 

2166 if not isinstance(involved, Iterable): 

2167 ex = TypeError("Parameter 'involved' is not iterable.") 

2168 ex.add_note(f"Got type '{getFullyQualifiedName(involved)}'.") 

2169 raise ex 

2170 elif isinstance(involved, KeyValuePairsMixin): 

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

2172 ex.add_note(f"Got type '{getFullyQualifiedName(involved)}'.") 

2173 raise ex 

2174 

2175 for item in involved: 

2176 if not isinstance(item, BaseItem): 

2177 ex = TypeError("Parameter 'involved' contains an element not of type 'BaseItem'.") 

2178 ex.add_note(f"Got type '{getFullyQualifiedName(item)}'.") 

2179 raise ex 

2180 elif self._root is None or item._root is not self._root: 

2181 ex = NotInSameSpecificationError( 

2182 "Parameter 'involved' contains an item not part of the item's specification." 

2183 ) 

2184 ex.add_note(f"Got item '{item._id}'.") 

2185 raise ex 

2186 elif item in self._involved: 

2187 ex = item._DUPLICATE_ERROR("Parameter 'involved' contains an item twice.") 

2188 ex.add_note(f"Got item '{item._id}'.") 

2189 raise ex 

2190 

2191 self._involved.append(item) 

2192 

2193 if len(self._involved) == 0 and kind in (EventKind.Split, EventKind.Merged): 

2194 raise ValueError(f"Parameter 'involved' names no item for a '{kind.name}' event.") 

2195 

2196 if description is None: 

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

2198 elif not isinstance(description, str): 

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

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

2201 raise ex 

2202 

2203 self._kind = kind 

2204 self._timestamp = timestamp 

2205 self._person = person 

2206 self._description = description 

2207 

2208 if parent is not None: 

2209 parent._AddElement(self) 

2210 

2211 @readonly 

2212 def Kind(self) -> EventKind: 

2213 """ 

2214 Read-only property to access the kind of the event (:attr:`_kind`). 

2215 

2216 :returns: The event's kind. 

2217 """ 

2218 return self._kind 

2219 

2220 @readonly 

2221 def Timestamp(self) -> datetime: 

2222 """ 

2223 Read-only property to access the time of the event (:attr:`_timestamp`). 

2224 

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

2226 """ 

2227 return self._timestamp 

2228 

2229 @readonly 

2230 def Person(self) -> Nullable[Person]: 

2231 """ 

2232 Read-only property to access the person causing the event (:attr:`_person`). 

2233 

2234 :returns: The person, or ``None``. 

2235 """ 

2236 return self._person 

2237 

2238 @readonly 

2239 def Involved(self) -> list[BaseItem]: 

2240 """ 

2241 Read-only property to access the other items involved in the event (:attr:`_involved`). 

2242 

2243 :returns: The items, in the order given. 

2244 """ 

2245 return self._involved 

2246 

2247 @readonly 

2248 def Description(self) -> str: 

2249 """ 

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

2251 

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

2253 """ 

2254 return self._description