From 6f188649ff71a2f43f06a02f9abc890016b1db7e Mon Sep 17 00:00:00 2001 From: Simon Steuer Date: Sun, 9 Mar 2025 00:28:09 +0100 Subject: [PATCH] =?UTF-8?q?Gro=C3=9Fes=20Refactoring=20-=20Dekorator=20f?= =?UTF-8?q?=C3=BCr=20lazy-Initialisierung=20durch=20die=20in=20Python=20f?= =?UTF-8?q?=C3=BCr=20diese=20Aufgabe=20vorgesehenen=20Property-Dekoratoren?= =?UTF-8?q?=20ersetzt.=20Durch=20diese=20Dekoratoren=20k=C3=B6nnen=20zuk?= =?UTF-8?q?=C3=BCnftig=20auch=20einfach=20Setter-Methoden=20erg=C3=A4nzt?= =?UTF-8?q?=20werden.=20Das=20Holen/Laden=20der=20JSON-=20und=20CSV-Dateie?= =?UTF-8?q?n=20wurde=20zudem=20nun=20so=20umgebaut,=20dass=20dies=20nicht?= =?UTF-8?q?=20mehr=20in=20der=20Klasse=20'MigrationsStatus'=20erfolgt,=20s?= =?UTF-8?q?ondern=20in=20den=20Klassen=20'SivasAssemblyData'=20und=20'Rule?= =?UTF-8?q?DesignerDatabaseConfig',=20da=20dies=20thematisch=20dort=20auch?= =?UTF-8?q?=20hingeh=C3=B6rt.=20Die=20bisherige=20Vorgehensweise=20war=20e?= =?UTF-8?q?inerseits=20verwirrend=20und=20zweitens=20ineffizient,=20weil?= =?UTF-8?q?=20in=20der=20Klasse=20'MigrationsStatus'=20nochmal=20Objekte?= =?UTF-8?q?=20der=20Klassen=20'SivasAssemblyData'=20und=20'RuleDesignerDat?= =?UTF-8?q?abaseConfig'=20initialisiert=20werden=20mussten.=20Durch=20die?= =?UTF-8?q?=20Anpassung=20wird=20nun=20zudem=20die=20Datei=20'get=5Fteiles?= =?UTF-8?q?tamm.py'=20nicht=20l=C3=A4nger=20ben=C3=B6tigt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/manage_configs.py | 250 +++++++++++++++++++++++++----------------- 1 file changed, 147 insertions(+), 103 deletions(-) diff --git a/lib/manage_configs.py b/lib/manage_configs.py index d917501..6e5ff8c 100644 --- a/lib/manage_configs.py +++ b/lib/manage_configs.py @@ -1,6 +1,6 @@ import os import json -from configparser import ConfigParser, RawConfigParser +from configparser import ConfigParser import pandas as pd import update_database @@ -39,153 +39,197 @@ class CSVBasedConfig: class SivasAssemblyData(JsonBasedConfig): """Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer""" - def __init__(self, config_dir, config_file): + def __init__(self, config_dir, config_file, load_local_json=False): super().__init__(config_dir, config_file) - self.assembly_data = None + self._load_local_json = load_local_json + self._asm_data = None + self._child_ids = None + self._parent_children = None + self._longest_child_id = None + self._max_bom_depth = None + self._property_names = None + self._all_bom_properties = None - def _lazy_init_assembly_data(func): - """Sorgt dafür, dass json-Datei der Baugruppe nur 1x geladen wird""" + def create_asm_json(self): + """Erstellt JSON mit den aktuellen Baugruppeninformationen""" + try: + update_database.get_sivas_dbase(self._config_path, os.environ.get("CREMIG_DATA")) + except: + raise ValueError("JSON-Datei mit Baugruppeninformationen konnte nicht erstellt werden!") - def wrapper(self, *args, **kwargs): - if self.assembly_data is None: - self.assembly_data = self.load_json_from_file() - return func(self, *args, **kwargs) - return wrapper + @property + def asm_data(self) -> dict: + """Liest die Baugruppen-JSON ein und gibt deren Informationen zurück""" + if self._asm_data is None: - @_lazy_init_assembly_data - def get_assembly_ids(self): + if not os.path.isfile(self._config_path): + raise FileNotFoundError(f"{self._config_path} existiert nicht. Bitte prüfen, ob die Nummer richtig geschrieben wurde.") + + if not self._load_local_json: + self.create_asm_json() + + self._asm_data = self.load_json_from_file() + + return self._asm_data + + @property + def child_ids(self) -> set: """Gibt alle Teilenummern der Stückliste zurück (auch Teilenummern aus tieferen Baugruppenebenen)""" - assembly_ids = set() - for child_id, parent_properties in self.assembly_data.items(): - assembly_ids.add(child_id) - for parent_property in parent_properties: - parent_id = parent_property["Parent"] - assembly_ids.add(parent_id) - return assembly_ids + if self._child_ids is None: + self._child_ids = set() + for child_id, parent_properties in self.asm_data.items(): + self._child_ids.add(child_id) + for parent_property in parent_properties: + parent_id = parent_property["Parent"] + self._child_ids.add(parent_id) + return self._child_ids - @_lazy_init_assembly_data - def get_parent_children(self) -> dict: + @property + def parent_children(self) -> dict: """Ordnet Stücklistenpositionen (Kinder) den Baugruppennummern (Eltern) zu. Die Kinder sind entsprechend ihrer Positionsnummern in den Stücklisten sortiert.""" - unordered_parent_children = {} + if self._parent_children is None: + unordered_parent_children = {} - for child_id, parent_properties in self.assembly_data.items(): - for parent_property in parent_properties: - parent_id = parent_property["Parent"] - child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0]) + for child_id, parent_properties in self.asm_data.items(): + for parent_property in parent_properties: + parent_id = parent_property["Parent"] + child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0]) - if parent_id not in unordered_parent_children: - unordered_parent_children[parent_id] = {} + if parent_id not in unordered_parent_children: + unordered_parent_children[parent_id] = {} - unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id - - ordered_parent_children = {} - for parent, children in unordered_parent_children.items(): - for pos_nr, child in sorted(children.items()): - if parent not in ordered_parent_children: - ordered_parent_children[parent] = [] - ordered_parent_children[parent].append(child) - return ordered_parent_children + unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id + + ordered_parent_children = {} + for parent, children in unordered_parent_children.items(): + for pos_nr, child in sorted(children.items()): + if parent not in ordered_parent_children: + ordered_parent_children[parent] = [] + ordered_parent_children[parent].append(child) + self._parent_children = ordered_parent_children + return self._parent_children - def get_longest_assembly_id(self) -> str: + @property + def longest_child_id(self) -> str: """Gibt die längste Teilenummer von allen Stücklistenpositionen zurück""" - return max(self.get_assembly_ids()) + if self._longest_child_id is None: + self._longest_child_id = max(self.child_ids) + return max(self.child_ids) - @_lazy_init_assembly_data - def get_max_bom_depth(self) -> int: + @property + def max_bom_depth(self) -> int: """Gibt die tiefste Stücklistenebene zurück (0 = höchste Ebene)""" - bom_levels = [] - for parents in self.assembly_data.values(): - for parent in parents: - current_bom_level = parent["Position"].count(".") - bom_levels.append(current_bom_level) - return max(bom_levels) + if self._max_bom_depth is None: + bom_levels = [] + for parents in self.asm_data.values(): + for parent in parents: + current_bom_level = parent["Position"].count(".") + bom_levels.append(current_bom_level) + self._max_bom_depth = max(bom_levels) + return self._max_bom_depth - @_lazy_init_assembly_data - def get_property_names(self) -> list: + @property + def property_names(self) -> list: """Gibt alle Eigenschaftsnamen zurück, die in der Assembly-json-Datei enthalten sind""" - for parent_data in self.assembly_data.values(): - return list(parent_data[0]) + if self._property_names is None: + for parent_data in self.asm_data.values(): + self._property_names = list(parent_data[0]) + return self._property_names - @_lazy_init_assembly_data def get_bom_property_values(self, property) -> dict: """Gibt eine Eigenschaft zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)""" - if property not in self.get_property_names(): + if property not in self.property_names: raise ValueError("Angegebener Eigenschaftsname exisitert nicht!") id_property_mapping = {} - for child, parent_data in self.assembly_data.items(): + for child, parent_data in self.asm_data.items(): for properties in parent_data: if property in properties: id_property_mapping[child] = properties[property] return id_property_mapping - @_lazy_init_assembly_data - def get_all_bom_properties(self) -> dict: + @property + def all_bom_properties(self) -> dict: """Gibt alle Eigenschaften zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)""" - all_properties = {} - property_names = self.get_property_names() - for property_name in property_names: - all_properties[property_name] = self.get_bom_property_values(property_name) - return all_properties + if self._all_bom_properties is None: + self._all_bom_properties = {} + for property_name in self.property_names: + self._all_bom_properties[property_name] = self.get_bom_property_values(property_name) + return self._all_bom_properties class RuleDesignerDatabaseConfig(CSVBasedConfig): - """Auslesen der Angaben aus der RuleDesigner-Datenbank-CSV""" - def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv"): + """Auslesen der CSV-Datei, in der alle RuleDesigner-Teilenummern aufgelistet sind""" + def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv", load_local_csv=False): super().__init__(config_dir, config_file) + self._load_local_csv = load_local_csv + self._ids = None - def get_ruledesigner_ids(self): - return tuple(self.load_csv_from_file()['Teilenummern']) + def copy_csv_from_server(self): + """Kopiert CSV-Datei vom Server, in der alle Teilenummern, die sich in RuleDesigner befinden""" + try: + update_database.get_rd_dbase() + except: + raise ValueError("CSV mit RuleDesigner-Teilenummern konnte nicht ins lokale Verzeichnis kopiert werden!") + @property + def ids(self): + if self._ids is None: + if not os.path.isfile(self._config_path): + raise FileNotFoundError(f"{self._config_path} existiert nicht. Bitte Batchdatei unter 'bin/01_get_rd_dbase.bat' ausführen!") + + if not self._load_local_csv: + self.copy_csv_from_server() + + self._ids = tuple(self.load_csv_from_file()['Teilenummern']) + return self._ids + class MigrationStatus: - def __init__(self, file_dir, assembly_json): - self._file_dir = file_dir - self._assembly_json = assembly_json - self._uselocal = False + """Klasse gibt Informationen zum aktuellen Migrationsstand zurück, indem geprüft wird, ob die Nummern aus Sivas, auch in RuleDesigner vorliegen""" + def __init__(self, asm_child_ids: tuple, rd_ids: tuple): + self.asm_child_ids = asm_child_ids + self.rd_ids = rd_ids + self._overview = None + self._green_ids = None + self._red_ids = None - def use_local(self): - self._uselocal = True + @property + def id_status(self) -> dict: + """Gibt zurück, ob die Stücklistenpositionen der Baugruppe migriert sind, oder nicht""" + if self._overview is None: + self._overview = {} + for child_id in self.asm_child_ids: + self._overview[child_id] = True if child_id in self.rd_ids else False + return self._overview - def get_migration_status(self) -> dict: - """Gibt zurück, ob die Teilenummern der Baugruppe bereits migriert wurden, oder nicht""" - out_dir = self._file_dir - if self._uselocal == False: - ok = update_database.get_sivas_dbase(self._assembly_json, out_dir) - if not ok: - raise Exception("no json file could be generated") - update_database.get_rd_dbase() - else: - if not os.path.exists(os.path.join(out_dir, self._assembly_json)): - raise Exception("no local file exists "+ self._assembly_json) - self.sivas_assembly_data = SivasAssemblyData(out_dir, self._assembly_json) - sivas_assembly_ids = self.sivas_assembly_data.get_assembly_ids() - self.ruledesigner_data = RuleDesignerDatabaseConfig(out_dir) - ruledesigner_ids = self.ruledesigner_data.get_ruledesigner_ids() + @property + def green_ids(self) -> list: + """Gibt alle Stücklistenpositionen zurück, die migriert sind""" - migration_status = {} - for sivas_assembly_id in sivas_assembly_ids: - migration_status[sivas_assembly_id] = True if sivas_assembly_id in ruledesigner_ids else False - return migration_status + if self._green_ids is None: + self._green_ids = [id for id, status in self.id_status.items() if status is True] + return self._green_ids - def get_migrated_ids(self) -> list: - """Gibt alle Teilenummern aus der Baugruppenstückliste zurück, die bereits in RuleDesigner migriert wurden""" + @property + def yellow_ids(self) -> list: + """Gibt migrierte Stücklistenpositionen zurück, die aktuell eine Freigabe der Baugruppe verhindern, da sie sich derzeit in Arbeit befinden""" + pass - migrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is True] - return migrated_ids + @property + def red_ids(self) -> list: + """Gibt alle Stücklistenpositionen zurück, die noch nicht migriert sind""" - def get_unmigrated_ids(self) -> list: - """Gibt alle Teilenummern aus der Baugruppenstückliste zurück, deren Migration in RuleDesigner noch aussteht""" - - unmigrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is False] - return unmigrated_ids + if self._red_ids is None: + self._red_ids = [id for id, status in self.id_status.items() if status is False] + return self._red_ids class TOSGroupsConfig(JsonBasedConfig): @@ -525,7 +569,7 @@ class Configs: self.TOSGruppen = TOSGroupsConfig(cfg_dir) self.Materialien = MaterialTransConfig(cfg_dir) - self._TeileNummern = None + self._teilenummern = None self._blacklist = None def get_cfg_path(self): @@ -539,9 +583,9 @@ class Configs: def get_ruledesigner_ids(self): if not self._data_dir: raise Exception("set_data_dir not called. path not set") - if not self._TeileNummern: - self._TeileNummern = RuleDesignerDatabaseConfig(self._data_dir).get_ruledesigner_ids() - return self._TeileNummern + if not self._teilenummern: + self._teilenummern = RuleDesignerDatabaseConfig(self._data_dir, load_local_csv=True).ids + return self._teilenummern def get_blacklist(self): if not self._data_dir: