Coverage for src/pyTRLCConverter/reqif_identifier_store.py: 85%

39 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-26 12:41 +0000

1""" 

2This module implements the persistent ReqIF identifier store. 

3 

4The store keeps the identifiers of ReqIF Identifiable elements immutable across 

5consecutive exports by persisting a mapping from a stable logical key to the 

6generated identifier in a JSON file. 

7 

8Author: Andreas Merkle (andreas.merkle@newtec.de) 

9""" 

10 

11# pyTRLCConverter - A tool to convert TRLC files to specific formats. 

12# Copyright (c) 2024 - 2026 NewTec GmbH 

13# 

14# This file is part of pyTRLCConverter program. 

15# 

16# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under 

17# the terms of the GNU General Public License as published by the Free Software Foundation, 

18# either version 3 of the License, or (at your option) any later version. 

19# 

20# The pyTRLCConverter program is distributed in the hope that it will be useful, but 

21# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

22# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

23# 

24# You should have received a copy of the GNU General Public License along with pyTRLCConverter. 

25# If not, see <https://www.gnu.org/licenses/>. 

26 

27# Imports ********************************************************************** 

28import json 

29from pyTRLCConverter.logger import log_error, log_verbose 

30 

31# Variables ******************************************************************** 

32 

33# Classes ********************************************************************** 

34 

35 

36class ReqifIdentifierStore(): 

37 """Persistent store mapping stable logical keys to immutable ReqIF identifiers. 

38 

39 On the initial conversion the store is empty and identifiers are generated on 

40 demand. The mapping can be persisted to a JSON file and loaded again on 

41 subsequent conversions so that already known elements keep their identifiers 

42 while new elements receive new identifiers. 

43 """ 

44 

45 SCHEMA_VERSION = 1 

46 

47 def __init__(self) -> None: 

48 # lobster-trace: SwRequirements.sw_req_reqif_identifier_immutable 

49 """Construct an empty identifier store.""" 

50 self._identifiers = {} 

51 self._next_id = 1 

52 

53 def load(self, file_name: str) -> bool: 

54 # lobster-trace: SwRequirements.sw_req_reqif_identifier_store_init 

55 # lobster-trace: SwRequirements.sw_req_reqif_identifier_store_reuse 

56 """Load the identifier store from a JSON file. 

57 

58 A missing file is treated as the initial conversion and leaves the store 

59 empty without reporting an error. 

60 

61 Args: 

62 file_name (str): The name of the JSON file to load. 

63 

64 Returns: 

65 bool: True if loading succeeded or the file does not exist yet, False on error. 

66 """ 

67 status = True 

68 

69 log_verbose(f"Loading ReqIF identifier store {file_name}.") 

70 

71 try: 

72 with open(file_name, "r", encoding="utf-8") as file: 

73 data = json.load(file) 

74 

75 self._identifiers = dict(data.get("identifiers", {})) 

76 self._next_id = int(data.get("next_id", len(self._identifiers) + 1)) 

77 

78 except FileNotFoundError: 

79 log_verbose(f"ReqIF identifier store {file_name} does not exist yet; starting empty.") 

80 

81 except (OSError, IOError, ValueError) as exc: 

82 log_error(f"Failed to load ReqIF identifier store {file_name}: {exc}") 

83 status = False 

84 

85 return status 

86 

87 def get_or_create(self, key: str, prefix: str) -> str: 

88 # lobster-trace: SwRequirements.sw_req_reqif_identifier_immutable 

89 # lobster-trace: SwRequirements.sw_req_reqif_identifier_store_reuse 

90 """Return the stored identifier for the given key or create a new immutable one. 

91 

92 A new identifier is built from the given prefix and a monotonic counter that 

93 never reuses a number, so identifiers stay unique and stable. 

94 

95 Args: 

96 key (str): Stable logical key identifying the ReqIF element. 

97 prefix (str): Identifier prefix (e.g. ``"spec-object"`` or ``"hierarchy"``). 

98 

99 Returns: 

100 str: The immutable identifier associated with the key. 

101 """ 

102 identifier = self._identifiers.get(key) 

103 

104 if identifier is None: 

105 identifier = f"{prefix}-{self._next_id}" 

106 self._next_id += 1 

107 self._identifiers[key] = identifier 

108 

109 return identifier 

110 

111 def save(self, file_name: str) -> bool: 

112 # lobster-trace: SwRequirements.sw_req_reqif_identifier_store_init 

113 # lobster-trace: SwRequirements.sw_req_reqif_identifier_store_reuse 

114 """Persist the identifier store to a JSON file. 

115 

116 Args: 

117 file_name (str): The name of the JSON file to write. 

118 

119 Returns: 

120 bool: True if the file was written successfully, False otherwise. 

121 """ 

122 status = True 

123 

124 log_verbose(f"Saving ReqIF identifier store {file_name}.") 

125 

126 data = { 

127 "version": ReqifIdentifierStore.SCHEMA_VERSION, 

128 "next_id": self._next_id, 

129 "identifiers": self._identifiers 

130 } 

131 

132 try: 

133 with open(file_name, "w", encoding="utf-8") as file: 

134 json.dump(data, file, indent=4, sort_keys=True) 

135 

136 except (OSError, IOError) as exc: 

137 log_error(f"Failed to save ReqIF identifier store {file_name}: {exc}") 

138 status = False 

139 

140 return status 

141 

142# Functions ******************************************************************** 

143 

144 

145# Main *************************************************************************