fix: use loaded json instead of Path for extra configs

In order to be used by API.

Co-authored-by: Renato Ambrosone <renato.ambrosone@polito.it>

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I12111427c8a90b85b3158cdd95f4ee771cb39316
This commit is contained in:
EstherLerouzic
2025-07-07 17:46:49 +02:00
parent 78227e65da
commit f2039fbe1c
17 changed files with 213 additions and 78 deletions

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: BSD-3-Clause
# gnpy.tools.default_edfa_configs: loads JSON configuration files at module initialization time
# Copyright (C) 2025 Telecom Infra Project and GNPy contributors
# see AUTHORS.rst for a list of contributors
"""
gnpy.tools.default_edfa_config
==============================
Default configs for pre defined amplifiers:
- Juniper-BoosterHG.json,
- std_medium_gain_advanced_config.json
"""
from logging import getLogger
from typing import Dict, Optional
from json import JSONDecodeError, load
from pathlib import Path
from gnpy.core.exceptions import ConfigurationError
from gnpy.tools.convert_legacy_yang import yang_to_legacy
_logger = getLogger(__name__)
_examples_dir = Path(__file__).parent.parent / 'example-data'
def _load_json_file(file_path: Path) -> Optional[Dict]:
"""Load and parse a JSON file.
:param file_path: Path to the JSON file to load
:type file_path: Path
:return: Dict containing the parsed JSON data or None if loading fails
:rtype: Optional[Dict]
"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
return yang_to_legacy(load(file))
except FileNotFoundError:
msg = f"Configuration file not found: {file_path}"
_logger.error(msg)
return None
except JSONDecodeError as e:
msg = f"Invalid JSON in configuration file {file_path}: {e}"
_logger.error(msg)
return None
# Default files to load
_files_to_load = {
"std_medium_gain_advanced_config.json": _examples_dir / "std_medium_gain_advanced_config.json",
"Juniper-BoosterHG.json": _examples_dir / "Juniper-BoosterHG.json"
}
# Load configurations
_configs: Dict = {}
for key, filepath in _files_to_load.items():
config_data = _load_json_file(filepath)
if config_data is not None:
_configs[key] = config_data
else:
_msg = f"Failed to load configuration: {key}. Using empty dict as fallback."
_logger.error(_msg)
raise ConfigurationError
# Expose the constant
DEFAULT_EXTRA_CONFIG: Dict[str, Dict] = _configs
DEFAULT_EQPT_CONFIG: Path = _examples_dir / "eqpt_config.json"

View File

@@ -18,7 +18,7 @@ from pathlib import Path
import json import json
from collections import namedtuple from collections import namedtuple
from copy import deepcopy from copy import deepcopy
from typing import Union, Dict, List, Tuple from typing import Union, Dict, List, Tuple, Optional
from networkx import DiGraph from networkx import DiGraph
from numpy import arange from numpy import arange
@@ -35,6 +35,7 @@ from gnpy.topology.spectrum_assignment import mvalue_to_slots
from gnpy.tools.convert import xls_to_json_data from gnpy.tools.convert import xls_to_json_data
from gnpy.tools.service_sheet import read_service_sheet from gnpy.tools.service_sheet import read_service_sheet
from gnpy.tools.convert_legacy_yang import yang_to_legacy, legacy_to_yang from gnpy.tools.convert_legacy_yang import yang_to_legacy, legacy_to_yang
from gnpy.tools.default_edfa_config import DEFAULT_EXTRA_CONFIG, DEFAULT_EQPT_CONFIG
_logger = getLogger(__name__) _logger = getLogger(__name__)
@@ -45,10 +46,6 @@ Model_fg = namedtuple('Model_fg', 'nf0')
Model_openroadm_ila = namedtuple('Model_openroadm_ila', 'nf_coef') Model_openroadm_ila = namedtuple('Model_openroadm_ila', 'nf_coef')
Model_hybrid = namedtuple('Model_hybrid', 'nf_ram gain_ram edfa_variety') Model_hybrid = namedtuple('Model_hybrid', 'nf_ram gain_ram edfa_variety')
Model_dual_stage = namedtuple('Model_dual_stage', 'preamp_variety booster_variety') Model_dual_stage = namedtuple('Model_dual_stage', 'preamp_variety booster_variety')
_examples_dir = Path(__file__).parent.parent / 'example-data'
DEFAULT_EXTRA_CONFIG = {"std_medium_gain_advanced_config.json": _examples_dir / "std_medium_gain_advanced_config.json",
"Juniper-BoosterHG.json": _examples_dir / "Juniper-BoosterHG.json"}
DEFAULT_EQPT_CONFIG = _examples_dir / "eqpt_config.json"
class Model_openroadm_preamp: class Model_openroadm_preamp:
@@ -245,8 +242,8 @@ class Amp(_JsonThing):
if type_def == 'fixed_gain': if type_def == 'fixed_gain':
if 'default_config_from_json' in kwargs: if 'default_config_from_json' in kwargs:
# use user defined default instead of DEFAULT_EDFA_CONFIG # use user defined default instead of DEFAULT_EDFA_CONFIG
config_filename = extra_configs[kwargs.pop('default_config_from_json')] config_filename = kwargs.pop('default_config_from_json')
config = load_json(config_filename) config = deepcopy(extra_configs[config_filename])
try: try:
nf0 = kwargs.pop('nf0') nf0 = kwargs.pop('nf0')
except KeyError as exc: # nf0 is expected for a fixed gain amp except KeyError as exc: # nf0 is expected for a fixed gain amp
@@ -260,13 +257,13 @@ class Amp(_JsonThing):
nf_def = Model_fg(nf0) nf_def = Model_fg(nf0)
elif type_def == 'advanced_model': elif type_def == 'advanced_model':
# use the user file name define in library instead of default config # use the user file name define in library instead of default config
config_filename = extra_configs[kwargs.pop('advanced_config_from_json')] config_filename = kwargs.pop('advanced_config_from_json')
config = load_json(config_filename) config = deepcopy(extra_configs[config_filename])
elif type_def == 'variable_gain': elif type_def == 'variable_gain':
if 'default_config_from_json' in kwargs: if 'default_config_from_json' in kwargs:
# use user defined default instead of DEFAULT_EDFA_CONFIG # use user defined default instead of DEFAULT_EDFA_CONFIG
config_filename = extra_configs[kwargs.pop('default_config_from_json')] config_filename = kwargs.pop('default_config_from_json')
config = load_json(config_filename) config = deepcopy(extra_configs[config_filename])
gain_min, gain_max = kwargs['gain_min'], kwargs['gain_flatmax'] gain_min, gain_max = kwargs['gain_min'], kwargs['gain_flatmax']
try: # nf_min and nf_max are expected for a variable gain amp try: # nf_min and nf_max are expected for a variable gain amp
nf_min = kwargs.pop('nf_min') nf_min = kwargs.pop('nf_min')
@@ -389,36 +386,37 @@ def _spectrum_from_json(json_data: dict):
return spectrum return spectrum
def merge_equipment(equipment: dict, additional_filenames: List[Path], extra_configs: Dict[str, Path]): def merge_equipment(equipment: Dict, extra_equipments: Dict[str, Dict], extra_configs: Dict[str, Dict]):
"""Merge additional equipment libraries into the base equipment dictionary. """Merge additional equipment libraries into the base equipment dictionary.
Typical case is the use of third party transceivers which are not part of a the supplier library. Typical case is the use of third party transceivers which are not part of a the supplier library.
raise warnings if the same reference is used on two different libraries raise warnings if the same reference is used on two different libraries
""" """
for filename in additional_filenames: for filename, json_data in extra_equipments.items():
extra_eqpt = load_equipment(filename, extra_configs) extra_eqpt = _equipment_from_json(json_data, extra_configs)
# populate with default eqpt to streamline loading # populate with default eqpt to streamline loading
for eqpt_type, extra_items in extra_eqpt.items(): for eqpt_type, extra_items in extra_eqpt.items():
for type_variety, item in extra_items.items(): for type_variety, item in extra_items.items():
if type_variety not in equipment[eqpt_type]: if type_variety not in equipment[eqpt_type]:
equipment[eqpt_type][type_variety] = item equipment[eqpt_type][type_variety] = item
else: else:
msg = f'\n\tEquipment file {filename.name}: duplicate equipment entry found: {eqpt_type}-{type_variety}\n' msg = f'\n\tEquipment file {filename}: duplicate equipment entry found: {eqpt_type}-{type_variety}\n'
_logger.warning(msg) _logger.warning(msg)
def load_equipments_and_configs(equipment_filename: Path, def load_equipments_and_configs(equipment_filename: Path,
extra_equipment_filenames: List[Path], extra_equipment_filenames: List[Path],
extra_config_filenames: List[Path]) -> dict: extra_config_filenames: List[Path]) -> Dict:
"""Loads equipment configurations and merge with additional equipment and configuration files. """Loads equipment configurations and merge with additional equipment and configuration files.
Args: :param equipment_filename: The path to the primary equipment configuration file.
equipment_filename (Path): The path to the primary equipment configuration file. :type equipment_filename: Path
extra_equipment_filenames (List[Path]): A list of paths to additional equipment configuration files to merge. :param extra_equipment_filenames: A list of paths to additional equipment configuration files to merge.
extra_config_filenames (List[Path]): A list of paths to additional configuration files to include. :type extra_equipment_filenames: List[Path]
:param extra_config_filenames: A list of paths to additional configuration files to include.
Returns: :type extra_config_filenames: List[Path]
dict: A dictionary containing the loaded equipment configurations. :return: A dictionary containing the loaded equipment configurations.
:rtype: Dict
Notes: Notes:
If no equipment filename is provided, a default equipment configuration will be used. If no equipment filename is provided, a default equipment configuration will be used.
@@ -429,16 +427,24 @@ def load_equipments_and_configs(equipment_filename: Path,
if not equipment_filename: if not equipment_filename:
equipment_filename = DEFAULT_EQPT_CONFIG equipment_filename = DEFAULT_EQPT_CONFIG
if extra_config_filenames: if extra_config_filenames:
extra_configs = {f.name: f for f in extra_config_filenames} # All files must have different filenames (as filename is used as the key in the library)
filename_list = [f.name for f in extra_config_filenames]
if len(set(filename_list)) != len(extra_config_filenames):
msg = f'Identical filenames for extra-config {filename_list}'
_logger.error(msg)
raise ConfigurationError(msg)
extra_configs = {f.name: load_json(f) for f in extra_config_filenames}
for k, v in DEFAULT_EXTRA_CONFIG.items(): for k, v in DEFAULT_EXTRA_CONFIG.items():
extra_configs[k] = v extra_configs[k] = v
equipment = load_equipment(equipment_filename, extra_configs) equipment = load_equipment(equipment_filename, extra_configs)
if extra_equipment_filenames: if extra_equipment_filenames:
merge_equipment(equipment, extra_equipment_filenames, extra_configs) # use the string representation of the path to support identical filenames but placed in different folders.
extra_equipments = {f.as_posix(): load_json(f) for f in extra_equipment_filenames}
merge_equipment(equipment, extra_equipments, extra_configs)
return equipment return equipment
def load_equipment(filename: Path, extra_configs: Dict[str, Path] = DEFAULT_EXTRA_CONFIG) -> dict: def load_equipment(filename: Path, extra_configs: Dict[str, Dict] = DEFAULT_EXTRA_CONFIG) -> Dict:
"""Load equipment, returns equipment dict """Load equipment, returns equipment dict
""" """
json_data = load_gnpy_json(filename) json_data = load_gnpy_json(filename)
@@ -544,7 +550,7 @@ def _si_sanity_check(equipment):
del equipment['SI'][possible_SI[0]] del equipment['SI'][possible_SI[0]]
def _equipment_from_json(json_data: dict, extra_configs: Dict[str, Path]) -> dict: def _equipment_from_json(json_data: dict, extra_configs: Dict[str, Dict]) -> Dict:
"""build global dictionnary eqpt_library that stores all eqpt characteristics: """build global dictionnary eqpt_library that stores all eqpt characteristics:
edfa type type_variety, fiber type_variety edfa type type_variety, fiber type_variety
from the eqpt_config.json (filename parameter) from the eqpt_config.json (filename parameter)
@@ -1022,13 +1028,21 @@ def results_to_json(pathresults: List[ResultElement]):
return {'response': [n.json for n in pathresults]} return {'response': [n.json for n in pathresults]}
def load_eqpt_topo_from_json(eqpt: dict, topology: dict) -> Tuple[dict, DiGraph]: def load_eqpt_topo_from_json(eqpt: dict, topology: dict, extra_equipments: Optional[Dict[str, Dict]] = None,
extra_configs: Dict[str, Dict] = DEFAULT_EXTRA_CONFIG) -> Tuple[dict, DiGraph]:
"""Loads equipment configuration and network topology from JSON data. """Loads equipment configuration and network topology from JSON data.
:param eqpt: Dictionary containing the equipment configuration in JSON format. :param eqpt: Dictionary containing the equipment configuration in JSON format.
It includes details about the devices to be processed and structured. It includes details about the devices to be processed and structured.
:type eqpt: dict
:param topology: Dictionary representing the network topology in JSON format, :param topology: Dictionary representing the network topology in JSON format,
defining the structure of the network and its connections. defining the structure of the network and its connections.
:type topology: dict
:param extra_equipments: dictionary containing additional libraries (eg for pluggables). Key can be
the file Path or any other string.
:type extra_equipments: Optional[Dict[str, Dict]]
:param extra_configs: Additional configurations for amplifiers in the library
:type extra_configs: Dict[str, Dict]
:return: A tuple containing: :return: A tuple containing:
@@ -1036,7 +1050,8 @@ def load_eqpt_topo_from_json(eqpt: dict, topology: dict) -> Tuple[dict, DiGraph]
- A directed graph (DiGraph) representing the network topology, where nodes - A directed graph (DiGraph) representing the network topology, where nodes
correspond to equipment and edges define their connections. correspond to equipment and edges define their connections.
""" """
equipment = _equipment_from_json(eqpt, DEFAULT_EXTRA_CONFIG) equipment = _equipment_from_json(eqpt, extra_configs)
if extra_equipments:
merge_equipment(equipment, extra_equipments, extra_configs)
network = network_from_json(topology, equipment) network = network_from_json(topology, equipment)
return equipment, network return equipment, network

View File

@@ -4,7 +4,7 @@ WARNING gnpy.tools.json_io:json_io.py
default value is type_variety = default default value is type_variety = default
WARNING gnpy.tools.json_io:json_io.py WARNING gnpy.tools.json_io:json_io.py
Equipment file extra_eqpt_config.json: duplicate equipment entry found: Transceiver-ZR400G Equipment file tests/data/extra_eqpt_config.json: duplicate equipment entry found: Transceiver-ZR400G
INFO gnpy.core.network:network.py INFO gnpy.core.network:network.py
Reference used for design: (Input optical power reference in span = 0.00dBm Reference used for design: (Input optical power reference in span = 0.00dBm

View File

@@ -6,23 +6,25 @@
# Copyright (C) 2025 Telecom Infra Project and GNPy contributors # Copyright (C) 2025 Telecom Infra Project and GNPy contributors
# see AUTHORS.rst for a list of contributors # see AUTHORS.rst for a list of contributors
from pathlib import Path
import pytest
from numpy import zeros, array from numpy import zeros, array
from numpy.testing import assert_allclose from numpy.testing import assert_allclose
from gnpy.core.elements import Transceiver, Edfa, Fiber from gnpy.core.elements import Transceiver, Edfa, Fiber
from gnpy.core.utils import automatic_fmax, lin2db, db2lin, merge_amplifier_restrictions, dbm2watt, watt2dbm from gnpy.core.utils import automatic_fmax, lin2db, db2lin, merge_amplifier_restrictions, dbm2watt, watt2dbm
from gnpy.core.info import create_input_spectral_information, create_arbitrary_spectral_information from gnpy.core.info import create_input_spectral_information, create_arbitrary_spectral_information
from gnpy.core.network import build_network, set_amplifier_voa from gnpy.core.network import build_network, set_amplifier_voa
from gnpy.tools.json_io import load_network, load_equipment, load_json, _equipment_from_json, network_from_json from gnpy.tools.json_io import load_network, load_equipment, load_json, _equipment_from_json, network_from_json
from gnpy.topology.request import PathRequest from gnpy.topology.request import PathRequest
from pathlib import Path
import pytest
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
test_network = DATA_DIR / 'test_network.json' test_network = DATA_DIR / 'test_network.json'
eqpt_library = DATA_DIR / 'eqpt_config.json' eqpt_library = DATA_DIR / 'eqpt_config.json'
extra_configs = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", extra_configs = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
# TODO in elements.py code: pytests doesn't pass with 1 channel: interpolate fail # TODO in elements.py code: pytests doesn't pass with 1 channel: interpolate fail
@@ -551,7 +553,7 @@ def test_multiband():
def test_user_defined_config(): def test_user_defined_config():
"""Checks that a user defined config is correctly used instead of DEFAULT_EDFA_CONFIG """Checks that a user defined config is correctly used instead of DEFAULT_EDFA_CONFIG
""" """
extra_configs['user_edfa_config.json'] = DATA_DIR / 'user_edfa_config.json' extra_configs['user_edfa_config.json'] = load_json(DATA_DIR / 'user_edfa_config.json')
user_edfa = { user_edfa = {
"type_variety": "user_defined", "type_variety": "user_defined",
"type_def": "variable_gain", "type_def": "variable_gain",
@@ -596,7 +598,7 @@ def test_default_config():
"""Checks that a config using a file gives the exact same result as the default config if values are identical """Checks that a config using a file gives the exact same result as the default config if values are identical
to DEFAULT_EDFA_CONFIG to DEFAULT_EDFA_CONFIG
""" """
extra_configs['copy_default_edfa_config.json'] = DATA_DIR / 'copy_default_edfa_config.json' extra_configs['copy_default_edfa_config.json'] = load_json(DATA_DIR / 'copy_default_edfa_config.json')
user_edfa = { user_edfa = {
"type_variety": "user_defined", "type_variety": "user_defined",
"type_def": "variable_gain", "type_def": "variable_gain",
@@ -680,7 +682,7 @@ def test_frequency_range(file):
} }
if file: if file:
user_edfa["default_config_from_json"] = file['name'] user_edfa["default_config_from_json"] = file['name']
extra_configs[file['name']] = file['path'] extra_configs[file['name']] = load_json(file['path'])
# add the reference to # add the reference to
json_data = load_json(eqpt_library) json_data = load_json(eqpt_library)
json_data['Edfa'].append(user_edfa) json_data['Edfa'].append(user_edfa)

View File

@@ -33,8 +33,7 @@ network_file_name = data_dir / 'testTopology_expected.json'
service_file_name = data_dir / 'testTopology_testservices.json' service_file_name = data_dir / 'testTopology_testservices.json'
result_file_name = data_dir / 'testTopology_testresults.json' result_file_name = data_dir / 'testTopology_testresults.json'
eqpt_library_name = data_dir / 'eqpt_config.json' eqpt_library_name = data_dir / 'eqpt_config.json'
extra_configs = {"std_medium_gain_advanced_config.json": data_dir / "std_medium_gain_advanced_config.json", extra_configs = {"std_medium_gain_advanced_config.json": load_json(data_dir / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": data_dir / "Juniper-BoosterHG.json"}
def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None): def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None):

View File

@@ -22,7 +22,8 @@ from gnpy.core.elements import Roadm
from gnpy.topology.request import (compute_path_dsjctn, isdisjoint, find_reversed_path, PathRequest, from gnpy.topology.request import (compute_path_dsjctn, isdisjoint, find_reversed_path, PathRequest,
correct_json_route_list, requests_aggregation, Disjunction) correct_json_route_list, requests_aggregation, Disjunction)
from gnpy.topology.spectrum_assignment import build_oms_list from gnpy.topology.spectrum_assignment import build_oms_list
from gnpy.tools.json_io import requests_from_json, load_requests, load_network, load_equipment, disjunctions_from_json from gnpy.tools.json_io import requests_from_json, load_requests, load_network, load_equipment, \
disjunctions_from_json, load_json
DATA_DIR = Path(__file__).parent.parent / 'tests/data' DATA_DIR = Path(__file__).parent.parent / 'tests/data'
@@ -30,8 +31,7 @@ NETWORK_FILE_NAME = DATA_DIR / 'testTopology_expected.json'
SERVICE_FILE_NAME = DATA_DIR / 'testTopology_testservices.json' SERVICE_FILE_NAME = DATA_DIR / 'testTopology_testservices.json'
RESULT_FILE_NAME = DATA_DIR / 'testTopology_testresults.json' RESULT_FILE_NAME = DATA_DIR / 'testTopology_testresults.json'
EQPT_LIBRARY_NAME = DATA_DIR / 'eqpt_config.json' EQPT_LIBRARY_NAME = DATA_DIR / 'eqpt_config.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
@pytest.fixture() @pytest.fixture()

View File

@@ -34,8 +34,7 @@ TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILENAME = DATA_DIR / 'testTopology_expected.json' NETWORK_FILENAME = DATA_DIR / 'testTopology_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
@pytest.mark.parametrize('degree, equalization_type, target, expected_pch_out_dbm, expected_si', @pytest.mark.parametrize('degree, equalization_type, target, expected_pch_out_dbm, expected_si',

View File

@@ -19,7 +19,7 @@ from numpy.testing import assert_array_equal, assert_allclose
import pytest import pytest
from gnpy.core.utils import automatic_nch, dbm2watt from gnpy.core.utils import automatic_nch, dbm2watt
from gnpy.core.network import build_network from gnpy.core.network import build_network
from gnpy.tools.json_io import load_equipment, load_network from gnpy.tools.json_io import load_equipment, load_network, load_json
from gnpy.core.equipment import trx_mode_params from gnpy.core.equipment import trx_mode_params
from gnpy.topology.request import PathRequest, compute_constrained_path, propagate from gnpy.topology.request import PathRequest, compute_constrained_path, propagate
@@ -28,8 +28,7 @@ TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILENAME = DATA_DIR / 'perdegreemeshTopologyExampleV2_auto_design_expected.json' NETWORK_FILENAME = DATA_DIR / 'perdegreemeshTopologyExampleV2_auto_design_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
def pathrequest(pch_dbm, nb_channels): def pathrequest(pch_dbm, nb_channels):

56
tests/test_json_io.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: BSD-3-Clause
# test_gain_mode
# Copyright (C) 2025 Telecom Infra Project and GNPy contributors
# see AUTHORS.rst for a list of contributors
"""
checks behaviour of gain mode
- if all amps have their gains set, check that these gains are used, even if power_dbm or req_power change
- check that saturation is correct in gain mode
"""
from pathlib import Path
from gnpy.tools.json_io import load_json, load_eqpt_topo_from_json
import networkx as nx
TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILENAME = DATA_DIR / 'LinkforTest.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
def test_load_eqpt_topo_from_json():
eqpt = load_json(EQPT_FILENAME)
topology = load_json(NETWORK_FILENAME)
extra_equipments = None
extra_configs = EXTRA_CONFIGS
equipment, network = load_eqpt_topo_from_json(eqpt, topology, extra_equipments, extra_configs)
assert isinstance(equipment, dict)
assert isinstance(network, nx.DiGraph)
assert len(network.nodes) > 0
assert len(network.edges) > 0
def test_load_eqpt_topo_from_json_default_extra():
eqpt = load_json(EQPT_FILENAME)
topology = load_json(NETWORK_FILENAME)
equipment, network = load_eqpt_topo_from_json(eqpt, topology)
assert isinstance(equipment, dict)
assert isinstance(network, nx.DiGraph)
# Check that some expected nodes exist (example: 'A', 'B')
assert len(network.nodes) > 0
assert len(network.edges) > 0

View File

@@ -17,15 +17,14 @@ import pytest
from gnpy.core.exceptions import ConfigurationError, ServiceError, EquipmentConfigError, ParametersError, \ from gnpy.core.exceptions import ConfigurationError, ServiceError, EquipmentConfigError, ParametersError, \
NetworkTopologyError NetworkTopologyError
from gnpy.tools.json_io import SI, Roadm, Amp, load_equipment, requests_from_json, network_from_json, \ from gnpy.tools.json_io import SI, Roadm, Amp, load_equipment, requests_from_json, network_from_json, \
load_network, load_requests load_network, load_requests, load_json
from gnpy.tools.convert import xls_to_json_data from gnpy.tools.convert import xls_to_json_data
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
EQPT_FILENAME = TEST_DIR / 'data/eqpt_config.json' EQPT_FILENAME = TEST_DIR / 'data/eqpt_config.json'
MULTIBAND_EQPT_FILENAME = TEST_DIR / 'data/eqpt_config_multiband.json' MULTIBAND_EQPT_FILENAME = TEST_DIR / 'data/eqpt_config_multiband.json'
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
def test_jsonthing(caplog): def test_jsonthing(caplog):

View File

@@ -31,8 +31,7 @@ DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
EQPT_MULTBAND_FILENAME = DATA_DIR / 'eqpt_config_multiband.json' EQPT_MULTBAND_FILENAME = DATA_DIR / 'eqpt_config_multiband.json'
NETWORK_FILENAME = DATA_DIR / 'bugfixiteratortopo.json' NETWORK_FILENAME = DATA_DIR / 'bugfixiteratortopo.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
@pytest.mark.parametrize("node, attenuation", [ @pytest.mark.parametrize("node, attenuation", [

View File

@@ -36,13 +36,12 @@ from gnpy.tools.convert import convert_file
from gnpy.tools.json_io import (load_json, load_network, save_network, load_equipment, requests_from_json, from gnpy.tools.json_io import (load_json, load_network, save_network, load_equipment, requests_from_json,
disjunctions_from_json, network_to_json, network_from_json) disjunctions_from_json, network_to_json, network_from_json)
from gnpy.tools.service_sheet import read_service_sheet, correct_xls_route_list, Request_element, Request from gnpy.tools.service_sheet import read_service_sheet, correct_xls_route_list, Request_element, Request
from gnpy.tools.default_edfa_config import DEFAULT_EXTRA_CONFIG
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS)
def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None): def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None):
@@ -108,7 +107,7 @@ def test_excel_json_generation(tmpdir, xls_input, expected_json_output):
}.items()) }.items())
def test_auto_design_generation_fromxlsgainmode(tmpdir, xls_input, expected_json_output): def test_auto_design_generation_fromxlsgainmode(tmpdir, xls_input, expected_json_output):
"""tests generation of topology json and that the build network gives correct results in gain mode""" """tests generation of topology json and that the build network gives correct results in gain mode"""
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
network = load_network(xls_input, equipment) network = load_network(xls_input, equipment)
add_missing_elements_in_network(network, equipment) add_missing_elements_in_network(network, equipment)
# in order to test the Eqpt sheet and load gain target, # in order to test the Eqpt sheet and load gain target,
@@ -139,7 +138,7 @@ def test_auto_design_generation_fromxlsgainmode(tmpdir, xls_input, expected_json
}.items()) }.items())
def test_auto_design_generation_fromjson(tmpdir, json_input, power_mode): def test_auto_design_generation_fromjson(tmpdir, json_input, power_mode):
"""test that autodesign creates same file as an input file already autodesigned""" """test that autodesign creates same file as an input file already autodesigned"""
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
network = load_network(json_input, equipment) network = load_network(json_input, equipment)
# in order to test the Eqpt sheet and load gain target, # in order to test the Eqpt sheet and load gain target,
# change the power-mode to False (to be in gain mode) # change the power-mode to False (to be in gain mode)
@@ -166,7 +165,7 @@ def test_auto_design_generation_fromjson(tmpdir, json_input, power_mode):
}.items()) }.items())
def test_excel_service_json_generation(xls_input, expected_json_output): def test_excel_service_json_generation(xls_input, expected_json_output):
"""test services creation""" """test services creation"""
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
network = load_network(DATA_DIR / 'testTopology.xls', equipment) network = load_network(DATA_DIR / 'testTopology.xls', equipment)
# Build the network once using the default power defined in SI in eqpt config # Build the network once using the default power defined in SI in eqpt config
p_db = equipment['SI']['default'].power_dbm p_db = equipment['SI']['default'].power_dbm
@@ -187,7 +186,7 @@ def test_excel_service_json_generation(xls_input, expected_json_output):
def test_csv_response_generation(tmpdir, json_input): def test_csv_response_generation(tmpdir, json_input):
"""tests if generated csv is consistant with expected generation same columns (order not important)""" """tests if generated csv is consistant with expected generation same columns (order not important)"""
json_data = load_json(json_input) json_data = load_json(json_input)
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
csv_filename = Path(tmpdir / json_input.name).with_suffix('.csv') csv_filename = Path(tmpdir / json_input.name).with_suffix('.csv')
with open(csv_filename, 'w', encoding='utf-8') as fcsv: with open(csv_filename, 'w', encoding='utf-8') as fcsv:
jsontocsv(json_data, equipment, fcsv) jsontocsv(json_data, equipment, fcsv)
@@ -252,7 +251,7 @@ def test_csv_response_generation(tmpdir, json_input):
def test_json_response_generation(xls_input, expected_response_file): def test_json_response_generation(xls_input, expected_response_file):
"""tests if json response is correctly generated for all combinations of requests""" """tests if json response is correctly generated for all combinations of requests"""
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
network = load_network(xls_input, equipment) network = load_network(xls_input, equipment)
p_db = equipment['SI']['default'].power_dbm p_db = equipment['SI']['default'].power_dbm
@@ -669,7 +668,7 @@ def test_roadm_type_variety(type_variety, target_pch_out_db, correct_variety):
# Do not add type variety in json_data to test that it creates a 'default' type_variety # Do not add type variety in json_data to test that it creates a 'default' type_variety
expected_roadm['type_variety'] = 'default' expected_roadm['type_variety'] = 'default'
expected_roadm['params']['target_pch_out_db'] = target_pch_out_db expected_roadm['params']['target_pch_out_db'] = target_pch_out_db
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
if correct_variety: if correct_variety:
network = network_from_json(json_data, equipment) network = network_from_json(json_data, equipment)
roadm = [n for n in network.nodes()][0] roadm = [n for n in network.nodes()][0]

View File

@@ -20,6 +20,7 @@ from gnpy.core.utils import automatic_nch, dbm2watt
from gnpy.topology.request import explicit_path, PathRequest from gnpy.topology.request import explicit_path, PathRequest
from gnpy.topology.spectrum_assignment import build_oms_list from gnpy.topology.spectrum_assignment import build_oms_list
from gnpy.tools.json_io import load_equipment, load_network, requests_from_json from gnpy.tools.json_io import load_equipment, load_network, requests_from_json
from gnpy.tools.default_edfa_config import DEFAULT_EXTRA_CONFIG
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
@@ -27,9 +28,8 @@ DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILENAME = DATA_DIR / 'testTopology_auto_design_expected.json' NETWORK_FILENAME = DATA_DIR / 'testTopology_auto_design_expected.json'
SERVICE_FILENAME = DATA_DIR / 'testTopology_services_expected.json' SERVICE_FILENAME = DATA_DIR / 'testTopology_services_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json",
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"} equipment = load_equipment(EQPT_FILENAME, DEFAULT_EXTRA_CONFIG)
equipment = load_equipment(EQPT_FILENAME, EXTRA_CONFIGS)
def pathrequest(pch_dbm, nb_channels): def pathrequest(pch_dbm, nb_channels):

View File

@@ -22,15 +22,14 @@ from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm
from gnpy.core.utils import db2lin, dbm2watt from gnpy.core.utils import db2lin, dbm2watt
from gnpy.core.info import create_input_spectral_information from gnpy.core.info import create_input_spectral_information
from gnpy.core.network import build_network from gnpy.core.network import build_network
from gnpy.tools.json_io import load_network, load_equipment, network_from_json from gnpy.tools.json_io import load_network, load_equipment, network_from_json, load_json
from gnpy.topology.request import PathRequest from gnpy.topology.request import PathRequest
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
network_file_name = DATA_DIR / 'LinkforTest.json' network_file_name = DATA_DIR / 'LinkforTest.json'
eqpt_library_name = DATA_DIR / 'eqpt_config.json' eqpt_library_name = DATA_DIR / 'eqpt_config.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
@pytest.fixture(params=[(96, 0.05e12), (60, 0.075e12), (45, 0.1e12), (2, 0.1e12)], @pytest.fixture(params=[(96, 0.05e12), (60, 0.075e12), (45, 0.1e12), (2, 0.1e12)],

View File

@@ -35,8 +35,7 @@ TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILE_NAME = DATA_DIR / 'testTopology_expected.json' NETWORK_FILE_NAME = DATA_DIR / 'testTopology_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None): def pathrequest(pch_dbm: float, p_tot_dbm: float = None, nb_channels: int = None):

View File

@@ -24,17 +24,16 @@ from gnpy.topology.spectrum_assignment import (build_oms_list, align_grids, nval
bitmap_sum, Bitmap, spectrum_selection, pth_assign_spectrum, bitmap_sum, Bitmap, spectrum_selection, pth_assign_spectrum,
build_path_oms_id_list, aggregate_oms_bitmap) build_path_oms_id_list, aggregate_oms_bitmap)
from gnpy.tools.json_io import (load_equipment, load_network, requests_from_json, disjunctions_from_json, from gnpy.tools.json_io import (load_equipment, load_network, requests_from_json, disjunctions_from_json,
_check_one_request) _check_one_request, load_json)
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_FILENAME = DATA_DIR / 'eqpt_config.json' EQPT_FILENAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILENAME = DATA_DIR / 'testTopology_auto_design_expected.json' NETWORK_FILENAME = DATA_DIR / 'testTopology_auto_design_expected.json'
SERVICE_FILENAME = DATA_DIR / 'testTopology_services_expected.json' SERVICE_FILENAME = DATA_DIR / 'testTopology_services_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json", EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": load_json(DATA_DIR / "std_medium_gain_advanced_config.json")}
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
grid = 0.00625e12 grid = 0.00625e12
slot = 0.0125e12 slot = 0.0125e12
guardband = 25.0e9 guardband = 25.0e9
cband_freq_min = 191.3e12 cband_freq_min = 191.3e12

View File

@@ -16,14 +16,13 @@ import pytest
from gnpy.core.equipment import trx_mode_params from gnpy.core.equipment import trx_mode_params
from gnpy.core.exceptions import EquipmentConfigError from gnpy.core.exceptions import EquipmentConfigError
from gnpy.tools.json_io import load_equipment, load_json, _equipment_from_json from gnpy.tools.json_io import load_equipment, load_json, _equipment_from_json
from gnpy.tools.default_edfa_config import DEFAULT_EXTRA_CONFIG
TEST_DIR = Path(__file__).parent TEST_DIR = Path(__file__).parent
DATA_DIR = TEST_DIR / 'data' DATA_DIR = TEST_DIR / 'data'
EQPT_LIBRARY_NAME = DATA_DIR / 'eqpt_config.json' EQPT_LIBRARY_NAME = DATA_DIR / 'eqpt_config.json'
NETWORK_FILE_NAME = DATA_DIR / 'testTopology_expected.json' NETWORK_FILE_NAME = DATA_DIR / 'testTopology_expected.json'
EXTRA_CONFIGS = {"std_medium_gain_advanced_config.json": DATA_DIR / "std_medium_gain_advanced_config.json",
"Juniper-BoosterHG.json": DATA_DIR / "Juniper-BoosterHG.json"}
@pytest.mark.parametrize('trx_type, trx_mode, error_message, no_error, expected_result', @pytest.mark.parametrize('trx_type, trx_mode, error_message, no_error, expected_result',
@@ -96,7 +95,7 @@ def test_trx_mode_params(trx_type, trx_mode, error_message, no_error, expected_r
'penalties': None, 'penalties': None,
'cost': None 'cost': None
} }
equipment = load_equipment(EQPT_LIBRARY_NAME, EXTRA_CONFIGS) equipment = load_equipment(EQPT_LIBRARY_NAME, DEFAULT_EXTRA_CONFIG)
if no_error: if no_error:
trx_params = trx_mode_params(equipment, trx_type, trx_mode, error_message) trx_params = trx_mode_params(equipment, trx_type, trx_mode, error_message)
print(trx_params) print(trx_params)
@@ -155,7 +154,7 @@ def test_wrong_baudrate_spacing(baudrate, spacing, error_message):
'equalization_offset_db': 0}] 'equalization_offset_db': 0}]
} }
json_data['Transceiver'].append(wrong_transceiver) json_data['Transceiver'].append(wrong_transceiver)
equipment = _equipment_from_json(json_data, EXTRA_CONFIGS) equipment = _equipment_from_json(json_data, DEFAULT_EXTRA_CONFIG)
with pytest.raises(EquipmentConfigError, match=error_message): with pytest.raises(EquipmentConfigError, match=error_message):
_ = trx_mode_params(equipment, 'vendorB_trx-type1', 'wrong mode', error_message=False) _ = trx_mode_params(equipment, 'vendorB_trx-type1', 'wrong mode', error_message=False)