From b19c458e84b59648a5dee033d98e9a967240da83 Mon Sep 17 00:00:00 2001 From: mpenning Date: Sun, 15 Oct 2023 21:32:32 -0500 Subject: [PATCH] Add typing to CiscoInterface().as_set() and CiscoInterface().as_list() --- ciscoconfparse/ccp_util.py | 47 +++++++++++++++++++++++++++++--------- tests/test_Ccp_Util.py | 34 +++++++++++++-------------- tests/test_Models_Cisco.py | 13 ++++++----- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/ciscoconfparse/ccp_util.py b/ciscoconfparse/ccp_util.py index fcf11e2c..c40a2f3c 100644 --- a/ciscoconfparse/ccp_util.py +++ b/ciscoconfparse/ccp_util.py @@ -1,6 +1,6 @@ r""" ccp_util.py - Parse, Query, Build, and Modify IOS-style configurations - Copyright (C) 2021-2023 David Michael Pennington + Copyright (C) 2023 David Michael Pennington Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems Copyright (C) 2019-2020 David Michael Pennington at ThousandEyes Copyright (C) 2014-2019 David Michael Pennington at Samsung Data Services @@ -2973,6 +2973,7 @@ def reverse_dns_lookup(input_str, timeout=3.0, server="4.2.2.2", proto="udp"): class CiscoInterface(object): interface_name = None interface_dict = None + debug = None _prefix = None _digit_separator = None _slot = None @@ -3014,6 +3015,7 @@ def __init__(self, interface_name=None, interface_dict=None, debug=False): self.interface_name = interface_name self.interface_dict = interface_dict + self.debug = debug if isinstance(interface_name, str): intf_dict = self.parse_single_interface(interface_name, debug=debug) @@ -3242,6 +3244,14 @@ def parse_intf_long(self, re_intf_long=None, debug=False): _slot = groupdict_slot_card_port["slot"] _card = groupdict_slot_card_port["card"] _port = groupdict_slot_card_port["port"] + + # Handle Ethernet1/48, where 48 is initially assigned to + # _card (should be port) + if isinstance(_slot, str) and isinstance(_card, str) and _port is None: + # Swap _card and _port + _port = _card + _card = None + if isinstance(_slot, str): _slot = int(_slot) if isinstance(_card, str): @@ -3406,15 +3416,15 @@ def number(self): logger.error(error) # Use sys.exit(1) here to avoid infinite recursion during # pathological errors such as a dash in an interface range - logger.critical("Exit on `CiscoInterface()` failure to avoid infinite recursion during raise ValueError()") + logger.critical(f"Exit on `CiscoInterface(interface_name='{self.interface_name}')`. Manually calling sys.exit(99) on this failure to avoid infinite recursion during raise ValueError(). This might be a bug in a third-party library.") sys.exit(99) # Fix regex port parsing problems... relocate _slot and _card, as-required - if self._slot is None and self._card is None and isinstance(self._port, int): + if self._slot is None and self._card is None and isinstance(self._port, (int, str)): self._number = f"{self._port}" - elif isinstance(self._slot, int) and self._card is None and isinstance(self._port, int): + elif isinstance(self._slot, (int, str)) and self._card is None and isinstance(self._port, (int, str)): self._number = f"{self._slot}{self.digit_separator}{self._port}" - elif isinstance(self._slot, int) and isinstance(self._card, int) and isinstance(self._port, int): + elif isinstance(self._slot, (int, str)) and isinstance(self._card, (int, str)) and isinstance(self._port, (int, str)): self._number = f"{self._slot}{self.digit_separator}{self._card}{self.digit_separator}{self._port}" else: error = f"Could not parse into _number: _slot: {self._slot} _card: {self._card} _port: {self._port} _digit_separator: {self.digit_separator}" @@ -3452,10 +3462,17 @@ def digit_separator(self): def digit_separator(self, value): if isinstance(value, str): self._digit_separator = value - else: + elif self.slot is not None or self.card is not None: error = f"Could not set _digit_separator: {value} {type(value)}" logger.critical(error) raise ValueError(error) + elif self.slot is None and self.card is None: + if self.debug is True: + logger.debug(f"Ignoring _digit_separator when there is no slot or card.") + else: + error = f"Found an unsupported value for `CiscoInterface().digit_separator`." + logger.error(error) + raise ValueError(error) @property @logger.catch(reraise=True) @@ -3879,7 +3896,6 @@ def remove(self, arg): return self # This method is on CiscoRange() - @property @logger.catch(reraise=True) def as_list(self, result_type=str): """Return a list of sorted components; an empty string is automatically rejected. This method is tricky to test due to the requirement for the `.sort_list` attribute on all elements; avoid using the ordered nature of `as_list` and use `as_set`.""" @@ -3903,7 +3919,7 @@ def as_list(self, result_type=str): elif result_type is int: return [int(ii) for ii in retval] else: - error f"CiscoRange().as_list(result_type={result_type}) is not valid. Choose from {[None, int, str]}. result_type: None will return CiscoInterface() objects." + error = f"CiscoRange().as_list(result_type={result_type}) is not valid. Choose from {[None, int, str]}. result_type: None will return CiscoInterface() objects." logger.critical(error) raise ValueError(error) except AttributeError as eee: @@ -3915,11 +3931,20 @@ def as_list(self, result_type=str): raise ValueError(eee) # This method is on CiscoRange() - @property @logger.catch(reraise=True) def as_set(self, result_type=str): """Return an unsorted set({}) components. Use this method instead of `.as_list` whenever possible to avoid the requirement for elements needing a `.sort_list` attribute.""" - return set(self._list) + retval = set(self._list) + if result_type is None: + return retval + elif result_type is str: + return set([str(ii) for ii in retval]) + elif result_type is int: + return set([int(ii) for ii in retval]) + else: + error = f"CiscoRange().as_list(result_type={result_type}) is not valid. Choose from {[None, int, str]}. result_type: None will return CiscoInterface() objects." + logger.critical(error) + raise ValueError(error) # This method is on CiscoRange() ## Github issue #125 @@ -3988,7 +4013,7 @@ def compressed_str(self): elif result_type is int: return [int(ii) for ii in retval] else: - error f"CiscoRange().as_set(result_type={result_type}) is not valid. Choose from {[None, int, str]}. result_type: None will return CiscoInterface() objects." + error = f"CiscoRange().as_set(result_type={result_type}) is not valid. Choose from {[None, int, str]}. result_type: None will return CiscoInterface() objects." logger.critical(error) raise ValueError(error) diff --git a/tests/test_Ccp_Util.py b/tests/test_Ccp_Util.py index 6e07821e..474a031c 100755 --- a/tests/test_Ccp_Util.py +++ b/tests/test_Ccp_Util.py @@ -725,7 +725,7 @@ def test_CiscoRange_01(): result_correct = {"1", "2", "3"} uut_str = "1-3" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -734,7 +734,7 @@ def test_CiscoRange_02(): result_correct = {"1", "3"} uut_str = "1,3" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -749,7 +749,7 @@ def test_CiscoRange_03(): } uut_str = "1,2-4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -758,7 +758,7 @@ def test_CiscoRange_04(): result_correct = {"1", "2", "3", "4", "5"} uut_str = "1-3,4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -767,7 +767,7 @@ def test_CiscoRange_05(): result_correct = {"1", "2", "3", "4", "5"} uut_str = "1,2,3-5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -776,7 +776,7 @@ def test_CiscoRange_06(): result_correct = {"1/1", "1/2", "1/3", "1/4", "1/5"} uut_str = "1/1-3,4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct assert CiscoRange(uut_str).iterate_attribute == "port" @@ -785,7 +785,7 @@ def test_CiscoRange_07(): result_correct = {"1/1", "1/2", "1/3", "1/4", "1/5"} uut_str = "1/1,2-4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_08(): @@ -793,7 +793,7 @@ def test_CiscoRange_08(): result_correct = {"1/1", "1/2", "1/3", "1/4", "1/5"} uut_str = "1/1,2,3-5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_09(): @@ -801,7 +801,7 @@ def test_CiscoRange_09(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "2/1/1-3,4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_10(): @@ -809,7 +809,7 @@ def test_CiscoRange_10(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "2/1/1,2-4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_11(): @@ -817,7 +817,7 @@ def test_CiscoRange_11(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "2/1/1,2,3-5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct if False: @@ -826,7 +826,7 @@ def test_CiscoRange_12(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "interface Eth2/1/1-3,4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_13(): @@ -834,7 +834,7 @@ def test_CiscoRange_13(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "interface Eth2/1/1,2-4,5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_14(): @@ -842,7 +842,7 @@ def test_CiscoRange_14(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "interface Eth2/1/1,2,3-5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_15(): @@ -850,7 +850,7 @@ def test_CiscoRange_15(): result_correct = {"2/1/1", "2/1/2", "2/1/3", "2/1/4", "2/1/5"} uut_str = "interface Eth 2/1/1,2,3-5" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_18(): @@ -862,7 +862,7 @@ def test_CiscoRange_18(): } uut_str = "Eth1/1,Eth1/12-20,Eth1/16,Eth1/10" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct if False: @@ -876,7 +876,7 @@ def test_CiscoRange_19(): } uut_str = "interface Eth1/1,interface Eth1/12-20,interface Eth1/16,interface Eth1/10" assert isinstance(uut_str, str) - assert CiscoRange(uut_str).as_set == result_correct + assert CiscoRange(uut_str).as_set(result_type=str) == result_correct def test_CiscoRange_compressed_str_01(): diff --git a/tests/test_Models_Cisco.py b/tests/test_Models_Cisco.py index 1a049f07..c0c9f036 100755 --- a/tests/test_Models_Cisco.py +++ b/tests/test_Models_Cisco.py @@ -14,6 +14,7 @@ r""" test_Models_Cisco.py - Parse, Query, Build, and Modify IOS-style configs + Copyright (C) 2023 David Michael Pennington Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems Copyright (C) 2019 David Michael Pennington at ThousandEyes Copyright (C) 2014-2019 David Michael Pennington at Samsung Data Services @@ -280,8 +281,8 @@ def testVal_IOSIntfLine_trunk_vlan_allowed_01(): ] cfg = CiscoConfParse(lines, factory=True) intf_obj = cfg.find_objects("^interface")[0] - assert len(intf_obj.trunk_vlans_allowed.as_set) == len(range(1, 4095)) - assert intf_obj.trunk_vlans_allowed.as_set == {str(ii) for ii in range(1, 4095)} + assert len(intf_obj.trunk_vlans_allowed.as_set(result_type=str)) == len(range(1, 4095)) + assert intf_obj.trunk_vlans_allowed.as_set(result_type=str) == {str(ii) for ii in range(1, 4095)} def testVal_IOSIntfLine_trunk_vlan_allowed_02(): @@ -295,7 +296,7 @@ def testVal_IOSIntfLine_trunk_vlan_allowed_02(): ] cfg = CiscoConfParse(lines, factory=True) intf_obj = cfg.find_objects("^interface")[0] - assert intf_obj.trunk_vlans_allowed.as_set == {"2", "4", "6", "911"} + assert intf_obj.trunk_vlans_allowed.as_set(result_type=str) == {"2", "4", "6", "911"} def testVal_IOSIntfLine_trunk_vlan_allowed_03(): @@ -310,7 +311,7 @@ def testVal_IOSIntfLine_trunk_vlan_allowed_03(): ] cfg = CiscoConfParse(lines, factory=True) intf_obj = cfg.find_objects("^interface")[0] - assert intf_obj.trunk_vlans_allowed.as_set == {"4", "6", "911"} + assert intf_obj.trunk_vlans_allowed.as_set(result_type=str) == {"4", "6", "911"} def testVal_IOSIntfLine_trunk_vlan_allowed_04(): @@ -325,7 +326,7 @@ def testVal_IOSIntfLine_trunk_vlan_allowed_04(): ] cfg = CiscoConfParse(lines, factory=True) intf_obj = cfg.find_objects("^interface")[0] - assert intf_obj.trunk_vlans_allowed.as_set == {"1"} + assert intf_obj.trunk_vlans_allowed.as_set(result_type=str) == {"1"} def testVal_IOSIntfLine_trunk_vlan_allowed_05(): @@ -340,7 +341,7 @@ def testVal_IOSIntfLine_trunk_vlan_allowed_05(): ] cfg = CiscoConfParse(lines, factory=True) intf_obj = cfg.find_objects("^interface")[0] - assert intf_obj.trunk_vlans_allowed.as_set == {"1",} + assert intf_obj.trunk_vlans_allowed.as_set(result_type=str) == {"1",} def testVal_IOSIntfLine_trunk_vlan_allowed_06():