From a0758d0da5bafbdac9413f764073197b69d65d30 Mon Sep 17 00:00:00 2001 From: EstherLerouzic Date: Thu, 4 Nov 2021 15:56:01 +0100 Subject: [PATCH] Move and refactor create_eqpt_sheet.py and add tests on it Co-authored-by: Rodrigo Sasse David Signed-off-by: EstherLerouzic Change-Id: Ib961c5c0e203f2225a0f1e2e7a091485567189c3 --- AUTHORS.rst | 1 + gnpy/tools/create_eqpt_sheet.py | 149 ++++++++++++++++++ .../write_path_jsontocsv.py | 0 tests/data/create_eqpt_sheet/testTopology.xls | Bin 0 -> 30720 bytes .../testTopology_eqpt_sheet.csv | 34 ++++ .../create_eqpt_sheet/test_ces_key_err.xls | Bin 0 -> 6656 bytes .../create_eqpt_sheet/test_ces_no_err.xls | Bin 0 -> 6656 bytes .../test_ces_node_degree_err.xls | Bin 0 -> 6656 bytes .../test_create_eqpt_sheet.csv | 9 ++ tests/test_create_eqpt_sheet.py | 95 +++++++++++ 10 files changed, 288 insertions(+) create mode 100644 gnpy/tools/create_eqpt_sheet.py rename gnpy/{example-data => tools}/write_path_jsontocsv.py (100%) create mode 100644 tests/data/create_eqpt_sheet/testTopology.xls create mode 100644 tests/data/create_eqpt_sheet/testTopology_eqpt_sheet.csv create mode 100644 tests/data/create_eqpt_sheet/test_ces_key_err.xls create mode 100644 tests/data/create_eqpt_sheet/test_ces_no_err.xls create mode 100644 tests/data/create_eqpt_sheet/test_ces_node_degree_err.xls create mode 100644 tests/data/create_eqpt_sheet/test_create_eqpt_sheet.csv create mode 100644 tests/test_create_eqpt_sheet.py diff --git a/AUTHORS.rst b/AUTHORS.rst index e9520213..c9c56e65 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -29,6 +29,7 @@ To learn how to contribute, please see CONTRIBUTING.md - Raj Nagarajan (Lumentum) - Renato Ambrosone (Politecnico di Torino) - Roberts Miculens (Lattelecom) +- Rodrigo Sasse David (Orange) - Sami Alavi (NUST) - Shengxiang Zhu (University of Arizona) - Stefan Melin (Telia Company) diff --git a/gnpy/tools/create_eqpt_sheet.py b/gnpy/tools/create_eqpt_sheet.py new file mode 100644 index 00000000..1d79247c --- /dev/null +++ b/gnpy/tools/create_eqpt_sheet.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# SPDX-License-Identifier: BSD-3-Clause +# Utility functions that creates an Eqpt sheet template +# Copyright (C) 2025 Telecom Infra Project and GNPy contributors +# see AUTHORS.rst for a list of contributors + +""" +create_eqpt_sheet.py +==================== + +XLS parser that can be called to create a "City" column in the "Eqpt" sheet. + +If not present in the "Nodes" sheet, the "Type" column will be implicitly +determined based on the topology. +""" + +from argparse import ArgumentParser +from pathlib import Path +import csv +from typing import List, Dict, Optional +from logging import getLogger +import dataclasses + +from gnpy.core.exceptions import NetworkTopologyError +from gnpy.tools.xls_utils import generic_open_workbook, get_sheet, XLS_EXCEPTIONS, all_rows, fast_get_sheet_rows, \ + WorkbookType, SheetType + + +logger = getLogger(__name__) +EXAMPLE_DATA_DIR = Path(__file__).parent.parent / 'example-data' + +PARSER = ArgumentParser() +PARSER.add_argument('workbook', type=Path, nargs='?', default=f'{EXAMPLE_DATA_DIR}/meshTopologyExampleV2.xls', + help='create the mandatory columns in Eqpt sheet') +PARSER.add_argument('-o', '--output', type=Path, help='Store CSV file') + + +@dataclasses.dataclass +class Node: + """Represents a network node with a unique identifier, connected nodes, and equipment type. + + :param uid: Unique identifier of the node. + :type uid: str + :param to_node: List of connected node identifiers. + :type to_node: List[str.] + :param eqpt: Equipment type associated with the node (ROADM, ILA, FUSED). + :type eqpt: str + """ + def __init__(self, uid: str, to_node: List[str], eqpt: str = None): + self.uid = uid + self.to_node = to_node + self.eqpt = eqpt + + +def open_sheet_with_error_handling(wb: WorkbookType, sheet_name: str, is_xlsx: bool) -> SheetType: + """Opens a sheet from the workbook with error handling. + + :param wb: The opened workbook. + :type wb: WorkbookType + :param sheet_name: Name of the sheet to open. + :type sheet_name: str + :param is_xlsx: Boolean indicating if the file is XLSX format. + :type is_xlsx: bool + :return: The worksheet object. + :rtype: SheetType + :raises NetworkTopologyError: If the sheet is not found. + """ + try: + sheet = get_sheet(wb, sheet_name, is_xlsx) + return sheet + except XLS_EXCEPTIONS as exc: + msg = f'Error: no {sheet_name} sheet in the file.' + raise NetworkTopologyError(msg) from exc + + +def read_excel(input_filename: Path) -> Dict[str, Node]: + """Reads the 'Nodes' and 'Links' sheets from an Excel file to build a network graph. + + :param input_filename: Path to the Excel file. + :type input_filename: Path + :return: Dictionary of nodes with their connectivity and equipment type. + :rtype: Dict[str, Node] + """ + wobo, is_xlsx = generic_open_workbook(input_filename) + links_sheet = open_sheet_with_error_handling(wobo, 'Links', is_xlsx) + get_rows_links = fast_get_sheet_rows(links_sheet) if is_xlsx else None + + nodes = {} + for row in all_rows(links_sheet, is_xlsx, start=5, get_rows=get_rows_links): + node_a, node_z = row[0].value, row[1].value + # Add connection in both directions + for node1, node2 in [(node_a, node_z), (node_z, node_a)]: + if node1 in nodes: + nodes[node1].to_node.append(node2) + else: + nodes[node1] = Node(node1, [node2]) + + nodes_sheet = open_sheet_with_error_handling(wobo, 'Nodes', is_xlsx) + get_rows_nodes = fast_get_sheet_rows(nodes_sheet) if is_xlsx else None + + for row in all_rows(nodes_sheet, is_xlsx, start=5, get_rows=get_rows_nodes): + node = row[0].value + eqpt = row[6].value + if node not in nodes: + raise NetworkTopologyError(f'Error: node {node} is not listed on the links sheet.') + if eqpt == 'ILA' and len(nodes[node].to_node) != 2: + degree = len(nodes[node].to_node) + raise NetworkTopologyError(f'Error: node {node} has an incompatible node degree ({degree}) ' + + 'for its equipment type (ILA).') + if eqpt == '' and len(nodes[node].to_node) == 2: + nodes[node].eqpt = 'ILA' + elif eqpt == '' and len(nodes[node].to_node) != 2: + nodes[node].eqpt = 'ROADM' + else: + nodes[node].eqpt = eqpt + return nodes + + +def create_eqpt_template(nodes: Dict[str, Node], input_filename: Path, output_filename: Optional[Path] = None): + """Creates a CSV template to help users populate equipment types for nodes. + + :param nodes: Dictionary of nodes. + :type nodes: Dict[str, Node] + :param input_filename: Path to the original Excel file. + :type input_filename: Path + :param output_filename: Path to save the CSV file; generated if None. + :type output_filename: Optional(Path) + """ + if output_filename is None: + output_filename = input_filename.parent / (input_filename.with_suffix('').stem + '_eqpt_sheet.csv') + with open(output_filename, mode='w', encoding='utf-8', newline='') as output_file: + output_writer = csv.writer(output_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + amp_header = ['amp_type', 'att_in', 'amp_gain', 'tilt', 'att_out', 'delta_p'] + output_writer.writerow(['node_a', 'node_z'] + amp_header + amp_header) + for node in nodes.values(): + if node.eqpt == 'ILA': + output_writer.writerow([node.uid, node.to_node[0]]) + if node.eqpt == 'ROADM': + for to_node in node.to_node: + output_writer.writerow([node.uid, to_node]) + msg = f'File {output_filename} successfully created.' + logger.info(msg) + + +if __name__ == '__main__': + ARGS = PARSER.parse_args() + create_eqpt_template(read_excel(ARGS.workbook), ARGS.workbook, ARGS.output) diff --git a/gnpy/example-data/write_path_jsontocsv.py b/gnpy/tools/write_path_jsontocsv.py similarity index 100% rename from gnpy/example-data/write_path_jsontocsv.py rename to gnpy/tools/write_path_jsontocsv.py diff --git a/tests/data/create_eqpt_sheet/testTopology.xls b/tests/data/create_eqpt_sheet/testTopology.xls new file mode 100644 index 0000000000000000000000000000000000000000..1c5161342b810c84c6d44edde65a66b58be8c670 GIT binary patch literal 30720 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 M+m9(E6h7~DX8Kx6J1q<BxU&&+p! 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 CLOG5A literal 0 HcmV?d00001 diff --git a/tests/data/create_eqpt_sheet/test_ces_node_degree_err.xls b/tests/data/create_eqpt_sheet/test_ces_node_degree_err.xls new file mode 100644 index 0000000000000000000000000000000000000000..ac177b301fda4f731332404882f6d0ad311348ce GIT binary patch literal 6656 zcmeHLO>9(E6h7~DX8PJfJ1q<<2tHAu&;p_hFp8Z*3)B=!Xwk%wz;rsTBSQyEHDF>K z1YAMHuyE7Fkg(90_!ARcNMeZ_7aENTM2(B0iP7j{Xi!5!JAU7JZ|LjGOgf`PgZ9jP z_vhSm?>YD0bN}9#U(}DCeShH<@zEnvA(Oc(33>Pi($)EVSj0q{yvbZHmuDk6OSjPo zOt`jk4g;9PAiz1V1T3HmU|EeD-$q@VHMN8h*d=NFhQ*e48ALoJM=;Z+(*k&mS4}B3 zz57Ke@-xZI>Yo~cnZ}Cc-tp)D&3(KAD7OB&=X3qf0p-0ZhyV+K zJAgZZyMTqjBH(UdF|Y)<2e5&Afu+EG!2Q57U^%b?XarUQw71EPnO=kRTHpbf_n;e3 zjlyiI)Dft%FpHs}d^GnnZuz?RUx7!w7r3Z?@jZBZ)?ssLCTWT z?lPz}HcL!Wl9XQTgh^Qk-~AL4xtCM|si4uSC6xL*rAq>H__9@jJ%G!)!3(o}l3_%|x^67`9 zCd+;QV+dMu)WAK_8pWy;D=JH_@?^kCVd2EEa1vO5;IOzMkL!uRlLRKllfy?4C7wMv zjCNzt)zR6CR4;CzTXuK1w;>$|`hWy(q=V?r-dH~ILJlvH8hIWq@KlOh5daC7cnXnB z9$n5B7oN7o%|S={&}nW7_1dX0x^d&Jt6y#J-Py8N`5BIZOUpPN(rg?>$;H4btTV_}}m1u_oV$>;*7yL?jbQ;gO@K2Xx8-6*Chrc~O z5Dbrw3D`hOoGEZ}A>^o#IqHw-_&7P1(b1Tr9TBbV|=4ekuB@8o!9Lvb3O%W}1 za^Ci6TSO&(m7SdJ>^5*P(^=NiA%LlMQjz6VqD}(YM&(y!>A-$6rCRIJKw9;f*&yd> zvP2oUw3zJ$9YZ08RA>NeV1OE3g46MMX72yypIrRTCX)A0`nfVa{^{p&ko`RRgP+GS z_VefiejZ2N^sE7Tj^neGc5((hhjPsMeU@dEXPMt;SwfH2j=#xSoPV)@)aMPT@!?lURfq~n71^UDV z5w_T-$f$<0g>Q!K$@ozEP`ZEEZa*APr0j>+ukMNUCQ_+{j`F5tf4_6-bLx~G!uK4d zW!H2nOkx>xv-T5xdhEm-*)+U#BFzMElK*jn`z2>FQFO95O6h8Ah`rmy0 zoi^}-KHeVi_)erkf*UXEvX901D4wNTGy=Es`;X3_Zw(hRlgIri9UJr{zCI1fe%wI$ z9csVBEF)}yx6og>j`D|UDb*8PXdpv2L+=5+aJyi`GY+$ikowsl>L!&^brpm4uvl)3_SV=+Ymgms_19H^6c607y7>rx`$o;w<0Qc{Jr1b{%!q#0z+9h8~^|S literal 0 HcmV?d00001 diff --git a/tests/data/create_eqpt_sheet/test_create_eqpt_sheet.csv b/tests/data/create_eqpt_sheet/test_create_eqpt_sheet.csv new file mode 100644 index 00000000..f65675fb --- /dev/null +++ b/tests/data/create_eqpt_sheet/test_create_eqpt_sheet.csv @@ -0,0 +1,9 @@ +node_a,node_z,amp_type,att_in,amp_gain,tilt,att_out,delta_p,amp_type,att_in,amp_gain,tilt,att_out,delta_p +a,b +a,d +a,e +c,b +c,d +c,e +d,c +e,a diff --git a/tests/test_create_eqpt_sheet.py b/tests/test_create_eqpt_sheet.py new file mode 100644 index 00000000..26727067 --- /dev/null +++ b/tests/test_create_eqpt_sheet.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# SPDX-License-Identifier: BSD-3-Clause +# test_create_eqpt_sheet +# Copyright (C) 2025 Telecom Infra Project and GNPy contributors +# see AUTHORS.rst for a list of contributors + +""" +Checks create_eqpt_sheet.py: verify that output is as expected +""" +from pathlib import Path +from os import symlink, unlink + +import pytest +from gnpy.tools.create_eqpt_sheet import Node, read_excel, create_eqpt_template +from gnpy.core.exceptions import NetworkTopologyError + + +TEST_DIR = Path(__file__).parent +DATA_DIR = TEST_DIR / 'data' / 'create_eqpt_sheet' + +TEST_FILE_NO_ERR = DATA_DIR / 'test_ces_no_err.xls' + +TEST_FILE = 'testTopology.xls' +EXPECTED_OUTPUT = DATA_DIR / 'testTopology_eqpt_sheet.csv' + +TEST_FILE_NODE_DEGREE_ERR = DATA_DIR / 'test_ces_node_degree_err.xls' +TEST_FILE_KEY_ERR = DATA_DIR / 'test_ces_key_err.xls' +TEST_OUTPUT_FILE_CSV = DATA_DIR / 'test_create_eqpt_sheet.csv' +PYTEST_OUTPUT_FILE_NAME = 'test_ces_pytest_output.csv' +EXPECTED_OUTPUT_CSV_NAME = 'testTopology_eqpt_sheet.csv' + + +@pytest.fixture() +def test_node(): + """Fixture of simple Node.""" + return Node(1, ['A', 'B'], 'ROADM') + + +@pytest.fixture() +def test_nodes_list(): + """Fixture of nodes list parsing.""" + return read_excel(TEST_FILE_NO_ERR) + + +def test_node_append(test_node): + """Test Node's append method.""" + expected = {'uid': 1, 'to_node': ['A', 'B', 'C'], 'eqpt': 'ROADM'} + test_node.to_node.append('C') + assert test_node.__dict__ == expected + + +def test_read_excel(test_nodes_list): + """Test method read_excel().""" + expected = {} + expected['a'] = Node('a', ['b', 'd', 'e'], 'ROADM') + expected['b'] = Node('b', ['a', 'c'], 'FUSED') + expected['c'] = Node('c', ['b', 'd', 'e'], 'ROADM') + expected['d'] = Node('d', ['c', 'a'], 'ILA') + expected['e'] = Node('e', ['a', 'c'], 'ILA') + assert set(test_nodes_list) == set(expected) + + +def test_read_excel_node_degree_err(): + """Test node degree error (eqpt == 'ILA' and len(nodes[node].to_node) != 2).""" + with pytest.raises(NetworkTopologyError): + _ = read_excel(TEST_FILE_NODE_DEGREE_ERR) + + +def test_read_excel_key_err(): + """Test node not listed on the links sheets.""" + with pytest.raises(NetworkTopologyError): + _ = read_excel(TEST_FILE_KEY_ERR) + + +def test_create_eqpt_template(tmpdir, test_nodes_list): + """Test method create_eqt_template().""" + create_eqpt_template(test_nodes_list, DATA_DIR / TEST_FILE_NO_ERR, + tmpdir / PYTEST_OUTPUT_FILE_NAME) + with open((tmpdir / PYTEST_OUTPUT_FILE_NAME).strpath, 'r') as actual, \ + open(TEST_OUTPUT_FILE_CSV, 'r') as expected: + assert set(actual.readlines()) == set(expected.readlines()) + unlink(tmpdir / PYTEST_OUTPUT_FILE_NAME) + + +def test_create_eqpt(tmpdir): + """Test method create_eqt_template().""" + # create a fake file in tempdir in order to test the automatic output filename generation + symlink(DATA_DIR / TEST_FILE, tmpdir / TEST_FILE) + create_eqpt_template(read_excel(DATA_DIR / TEST_FILE), Path((tmpdir / TEST_FILE).strpath)) + with open(DATA_DIR / EXPECTED_OUTPUT_CSV_NAME, 'r') as expected, \ + open(tmpdir / EXPECTED_OUTPUT_CSV_NAME, 'r') as actual: + assert set(actual.readlines()) == set(expected.readlines()) + unlink(tmpdir / EXPECTED_OUTPUT_CSV_NAME)