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
« 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.
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.
8Author: Andreas Merkle (andreas.merkle@newtec.de)
9"""
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/>.
27# Imports **********************************************************************
28import json
29from pyTRLCConverter.logger import log_error, log_verbose
31# Variables ********************************************************************
33# Classes **********************************************************************
36class ReqifIdentifierStore():
37 """Persistent store mapping stable logical keys to immutable ReqIF identifiers.
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 """
45 SCHEMA_VERSION = 1
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
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.
58 A missing file is treated as the initial conversion and leaves the store
59 empty without reporting an error.
61 Args:
62 file_name (str): The name of the JSON file to load.
64 Returns:
65 bool: True if loading succeeded or the file does not exist yet, False on error.
66 """
67 status = True
69 log_verbose(f"Loading ReqIF identifier store {file_name}.")
71 try:
72 with open(file_name, "r", encoding="utf-8") as file:
73 data = json.load(file)
75 self._identifiers = dict(data.get("identifiers", {}))
76 self._next_id = int(data.get("next_id", len(self._identifiers) + 1))
78 except FileNotFoundError:
79 log_verbose(f"ReqIF identifier store {file_name} does not exist yet; starting empty.")
81 except (OSError, IOError, ValueError) as exc:
82 log_error(f"Failed to load ReqIF identifier store {file_name}: {exc}")
83 status = False
85 return status
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.
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.
95 Args:
96 key (str): Stable logical key identifying the ReqIF element.
97 prefix (str): Identifier prefix (e.g. ``"spec-object"`` or ``"hierarchy"``).
99 Returns:
100 str: The immutable identifier associated with the key.
101 """
102 identifier = self._identifiers.get(key)
104 if identifier is None:
105 identifier = f"{prefix}-{self._next_id}"
106 self._next_id += 1
107 self._identifiers[key] = identifier
109 return identifier
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.
116 Args:
117 file_name (str): The name of the JSON file to write.
119 Returns:
120 bool: True if the file was written successfully, False otherwise.
121 """
122 status = True
124 log_verbose(f"Saving ReqIF identifier store {file_name}.")
126 data = {
127 "version": ReqifIdentifierStore.SCHEMA_VERSION,
128 "next_id": self._next_id,
129 "identifiers": self._identifiers
130 }
132 try:
133 with open(file_name, "w", encoding="utf-8") as file:
134 json.dump(data, file, indent=4, sort_keys=True)
136 except (OSError, IOError) as exc:
137 log_error(f"Failed to save ReqIF identifier store {file_name}: {exc}")
138 status = False
140 return status
142# Functions ********************************************************************
145# Main *************************************************************************