From 05a2534f759259c98635d420722c7befa02665fc Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 10 Jun 2026 22:10:14 +0000 Subject: [PATCH 01/11] Add construct_sfp_list_for_topology hook to ChassisBase Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 24 +++++++++++++++ tests/chassis_base_test.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index ddfe71989..d7a4b9722 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -80,6 +80,15 @@ def __init__(self): # SED (Self-Encrypting Drive) password management self._sed_mgmt = None + # On platforms that provide an optical_devices.json file (e.g. + # platforms using co-packaged optics, where each port can be driven + # by multiple devices), populate the SFP list based on the optical + # device topology described in that file + from sonic_py_common import device_info + optical_device_data = device_info.get_optical_devices_data() + if optical_device_data: + self.construct_sfp_list_for_topology(optical_device_data) + def get_base_mac(self): """ Retrieves the base MAC address for the chassis @@ -701,6 +710,21 @@ def get_current_sensor(self, index): # SFP methods ############################################## + def construct_sfp_list_for_topology(self, optical_device_data): + """ + Construct objects representing the devices driving traffic through + a front panel port on the chassis based on topology data in + optical_devices.json and appends them to self._sfp_list + + Subclasses should implement this method on platforms that provide + an optical_devices.json file. + + Args: + optical_device_data: Optical device topology data parsed from + optical_devices.json + """ + raise NotImplementedError + def get_num_sfps(self): """ Retrieves the number of sfps available on this chassis diff --git a/tests/chassis_base_test.py b/tests/chassis_base_test.py index fccb3101c..a05fa62e4 100644 --- a/tests/chassis_base_test.py +++ b/tests/chassis_base_test.py @@ -1,7 +1,17 @@ +import pytest +from unittest import mock + from sonic_platform_base.chassis_base import ChassisBase class TestChassisBase: + @pytest.fixture(autouse=True) + def _mock_optical_devices_data(self): + with mock.patch("sonic_py_common.device_info.get_optical_devices_data", + return_value=None) as mock_get: + self.mock_get_optical_devices_data = mock_get + yield + def test_reboot_cause(self): chassis = ChassisBase() assert(chassis.REBOOT_CAUSE_POWER_LOSS == "Power Loss") @@ -161,3 +171,39 @@ def test_pdbs_multiple_and_negative_index(self, capsys): assert chassis.get_pdb(-4) is None err_neg = capsys.readouterr().err assert "PDB index -4 out of range (0-2)" in err_neg + + def test_no_optical_devices_data(self): + chassis = ChassisBase() + self.mock_get_optical_devices_data.assert_called_once() + assert chassis.get_num_sfps() == 0 + + def test_optical_devices_data_base_class_not_implemented(self): + self.mock_get_optical_devices_data.return_value = {"devices": {}, "interfaces": {}} + with pytest.raises(NotImplementedError): + ChassisBase() + + def test_construct_sfp_list_for_topology(self): + optical_device_data = { + "devices": { + "OE1": {"device_type": "optical_engine"}, + "ELS1": {"device_type": "external_laser_source"}, + }, + "interfaces": { + "Ethernet0": { + "associated_devices": [ + {"device_id": "OE1", "bank": 0}, + {"device_id": "ELS1", "bank": 0}, + ] + } + }, + } + self.mock_get_optical_devices_data.return_value = optical_device_data + + class CpoChassis(ChassisBase): + def construct_sfp_list_for_topology(self, optical_device_data): + for interface in optical_device_data["interfaces"]: + self._sfp_list.append(interface) + + chassis = CpoChassis() + assert chassis.get_num_sfps() == 1 + assert chassis.get_sfp(0) == "Ethernet0" From ff568f1dc7bc243634069dffc8f5576675f60285 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Thu, 25 Jun 2026 22:29:54 +0000 Subject: [PATCH 02/11] Add functions to store and retrieve CpoBase objects on a Chassis object Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index d7a4b9722..884b5932a 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -65,6 +65,10 @@ def __init__(self): # available on the chassis self._sfp_list = [] + # List of CpoBase-derived objects representing all CPO ports + # available on the chassis, indexed by physical port. + self._cpo_list = [] + # Object derived from WatchdogBase for interacting with hardware watchdog self._watchdog = None @@ -768,6 +772,41 @@ def get_sfp(self, index): return sfp + def get_num_cpos(self): + """ + Retrieves the number of CPO ports available on this chassis + + Returns: + An integer, the number of CPO ports available on this chassis + """ + return len(self._cpo_list) + + def get_all_cpos(self): + """ + Retrieves all CPO ports available on this chassis + + Returns: + A list of objects derived from CpoBase representing all CPO ports + available on this chassis + """ + return [cpo for cpo in self._cpo_list if cpo is not None] + + def get_cpo(self, index): + """ + Retrieves the CPO port corresponding to physical port , if + that port is driven by CPO devices. + + Args: + index: An integer (>=0), the physical port index (same indexing as + get_sfp()). + + Returns: + An object derived from CpoBase representing the specified CPO port, + or None if the port is not a CPO port. + """ + if 0 <= index < len(self._cpo_list): + return self._cpo_list[index] + return None def get_port_or_cage_type(self, index): """ From 29ee0ac0e9397cbfa2e8dc6dc129c334c6b9f87a Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 1 Jul 2026 00:44:56 +0000 Subject: [PATCH 03/11] Make CpoBase default to OE and CPO classes inherit from Device Signed-off-by: Brian Gallagher --- .../sonic_xcvr/cpo/cpo_base.py | 26 +++++++++++++------ tests/sonic_xcvr/test_cpo_base.py | 12 +++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/sonic_platform_base/sonic_xcvr/cpo/cpo_base.py b/sonic_platform_base/sonic_xcvr/cpo/cpo_base.py index e0661bec3..fa9d0e2dd 100644 --- a/sonic_platform_base/sonic_xcvr/cpo/cpo_base.py +++ b/sonic_platform_base/sonic_xcvr/cpo/cpo_base.py @@ -3,6 +3,7 @@ from enum import Enum from typing import Optional +from sonic_platform_base import device_base from sonic_platform_base.sonic_xcvr.xcvr_eeprom import XcvrEeprom from sonic_platform_base.sonic_xcvr.eeprom_rw import EepromReadWriteMixin @@ -36,7 +37,7 @@ def create_api(self): raise NotImplementedError -class CpoDeviceBase(EepromReadWriteMixin): +class CpoDeviceBase(device_base.DeviceBase, EepromReadWriteMixin): def __init__(self, hardware_id: CpoHardwareInfo, bank: int = 0): self.bank = bank self.hardware_id = hardware_id @@ -55,16 +56,25 @@ def get_api(self): self.refresh_api() return self._api + def remove_api(self): + self._api = None + -class CpoBase: +class CpoBase(device_base.DeviceBase): def __init__(self, hardware_id: CpoHardwareInfo, oe: "OeBase", elsfp: "ElsfpBase"): self.hardware_id = hardware_id self.oe = oe self.elsfp = elsfp -# TODO: Implement CPO-specific methods -# def do_fiber_check(self, lane): -# self.oe.get_api().do_fiber_check(lane) -# -# def tx_disable(self, lane): -# self.elsfp.get_api().tx_disable(lane) + def refresh_xcvr_api(self): + self.oe.refresh_api() + self.elsfp.refresh_api() + + def get_xcvr_api(self): + # We always default to the OE API for CPO. If the ELSFP API is required, + # then that can be accessed via self.elsfp.get_api() directly. + return self.oe.get_api() + + def remove_xcvr_api(self): + self.oe.remove_api() + self.elsfp.remove_api() diff --git a/tests/sonic_xcvr/test_cpo_base.py b/tests/sonic_xcvr/test_cpo_base.py index e01825493..118b043be 100644 --- a/tests/sonic_xcvr/test_cpo_base.py +++ b/tests/sonic_xcvr/test_cpo_base.py @@ -68,3 +68,15 @@ def test_init(self): assert cpo.hardware_id is hardware_id assert cpo.oe is oe assert cpo.elsfp is elsfp + + def test_get_xcvr_api_returns_oe_api(self): + hardware_id = CpoHardwareInfo(oe_id=SOME_OE_ID, elsfp_id=SOME_ELSFP_ID) + oe = OeBase(hardware_id) + elsfp = ElsfpBase(hardware_id) + oe_api = MagicMock() + oe.get_api = MagicMock(return_value=oe_api) + + cpo = CpoBase(hardware_id, oe, elsfp) + + assert cpo.get_xcvr_api() is oe_api + oe.get_api.assert_called_with() From 5d8929c3bca7def13b223883364cf33880aacaa3 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Thu, 9 Jul 2026 18:54:19 +0000 Subject: [PATCH 04/11] Rename construct_sfp_list_for_topology to construct_optical_devices Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index 884b5932a..ecd50d8de 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -91,7 +91,7 @@ def __init__(self): from sonic_py_common import device_info optical_device_data = device_info.get_optical_devices_data() if optical_device_data: - self.construct_sfp_list_for_topology(optical_device_data) + self.construct_optical_devices(optical_device_data) def get_base_mac(self): """ @@ -714,11 +714,11 @@ def get_current_sensor(self, index): # SFP methods ############################################## - def construct_sfp_list_for_topology(self, optical_device_data): + def construct_optical_devices(self, optical_device_data): """ Construct objects representing the devices driving traffic through a front panel port on the chassis based on topology data in - optical_devices.json and appends them to self._sfp_list + optical_devices.json Subclasses should implement this method on platforms that provide an optical_devices.json file. From 83d4b242f37d3ee46ca80747df032caf7e316c5c Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Mon, 13 Jul 2026 17:54:17 +0000 Subject: [PATCH 05/11] Rename optical_devices.json to cpo.json Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 21 +++++++++----------- tests/chassis_base_test.py | 30 ++++++++++++++--------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index ecd50d8de..72d19f2a9 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -84,14 +84,12 @@ def __init__(self): # SED (Self-Encrypting Drive) password management self._sed_mgmt = None - # On platforms that provide an optical_devices.json file (e.g. - # platforms using co-packaged optics, where each port can be driven - # by multiple devices), populate the SFP list based on the optical - # device topology described in that file + # On platforms that provide a cpo.json file, populate self._cpo_list + # based on the device topology described in that file from sonic_py_common import device_info - optical_device_data = device_info.get_optical_devices_data() - if optical_device_data: - self.construct_optical_devices(optical_device_data) + cpo_data = device_info.get_cpo_data() + if cpo_data: + self.construct_cpo_devices(cpo_data) def get_base_mac(self): """ @@ -714,18 +712,17 @@ def get_current_sensor(self, index): # SFP methods ############################################## - def construct_optical_devices(self, optical_device_data): + def construct_cpo_devices(self, cpo_data): """ Construct objects representing the devices driving traffic through a front panel port on the chassis based on topology data in - optical_devices.json + cpo.json Subclasses should implement this method on platforms that provide - an optical_devices.json file. + an cpo.json file. Args: - optical_device_data: Optical device topology data parsed from - optical_devices.json + cpo_data: device topology data parsed from cpo.json """ raise NotImplementedError diff --git a/tests/chassis_base_test.py b/tests/chassis_base_test.py index a05fa62e4..0c06bbdcf 100644 --- a/tests/chassis_base_test.py +++ b/tests/chassis_base_test.py @@ -6,10 +6,10 @@ class TestChassisBase: @pytest.fixture(autouse=True) - def _mock_optical_devices_data(self): - with mock.patch("sonic_py_common.device_info.get_optical_devices_data", + def _mock_cpo_data(self): + with mock.patch("sonic_py_common.device_info.get_cpo_data", return_value=None) as mock_get: - self.mock_get_optical_devices_data = mock_get + self.mock_get_cpo_data = mock_get yield def test_reboot_cause(self): @@ -172,18 +172,18 @@ def test_pdbs_multiple_and_negative_index(self, capsys): err_neg = capsys.readouterr().err assert "PDB index -4 out of range (0-2)" in err_neg - def test_no_optical_devices_data(self): + def test_no_cpo_data(self): chassis = ChassisBase() - self.mock_get_optical_devices_data.assert_called_once() - assert chassis.get_num_sfps() == 0 + self.mock_get_cpo_data.assert_called_once() + assert chassis.get_num_cpos() == 0 - def test_optical_devices_data_base_class_not_implemented(self): - self.mock_get_optical_devices_data.return_value = {"devices": {}, "interfaces": {}} + def test_cpo_data_base_class_not_implemented(self): + self.mock_get_cpo_data.return_value = {"devices": {}, "interfaces": {}} with pytest.raises(NotImplementedError): ChassisBase() def test_construct_sfp_list_for_topology(self): - optical_device_data = { + cpo_data = { "devices": { "OE1": {"device_type": "optical_engine"}, "ELS1": {"device_type": "external_laser_source"}, @@ -197,13 +197,13 @@ def test_construct_sfp_list_for_topology(self): } }, } - self.mock_get_optical_devices_data.return_value = optical_device_data + self.mock_get_cpo_data.return_value = cpo_data class CpoChassis(ChassisBase): - def construct_sfp_list_for_topology(self, optical_device_data): - for interface in optical_device_data["interfaces"]: - self._sfp_list.append(interface) + def construct_cpo_devices(self, cpo_data): + for interface in cpo_data["interfaces"]: + self._cpo_list.append(interface) chassis = CpoChassis() - assert chassis.get_num_sfps() == 1 - assert chassis.get_sfp(0) == "Ethernet0" + assert chassis.get_num_cpos() == 1 + assert chassis.get_cpo(0) == "Ethernet0" From 2f9a227923228899e8d7da06f7b3b87e04c45602 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Tue, 28 Jul 2026 16:55:58 +0000 Subject: [PATCH 06/11] Copilot review feedback Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 12 ++++++++---- tests/chassis_base_test.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index 72d19f2a9..1c3c8b486 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -719,7 +719,7 @@ def construct_cpo_devices(self, cpo_data): cpo.json Subclasses should implement this method on platforms that provide - an cpo.json file. + a cpo.json file. Args: cpo_data: device topology data parsed from cpo.json @@ -801,9 +801,13 @@ def get_cpo(self, index): An object derived from CpoBase representing the specified CPO port, or None if the port is not a CPO port. """ - if 0 <= index < len(self._cpo_list): - return self._cpo_list[index] - return None + cpo = None + try: + cpo = self._cpo_list[index] + except IndexError: + sys.stderr.write("CPO index {} out of range (0-{})\n".format( + index, len(self._cpo_list)-1)) + return cpo def get_port_or_cage_type(self, index): """ diff --git a/tests/chassis_base_test.py b/tests/chassis_base_test.py index 0c06bbdcf..15cc17340 100644 --- a/tests/chassis_base_test.py +++ b/tests/chassis_base_test.py @@ -182,7 +182,7 @@ def test_cpo_data_base_class_not_implemented(self): with pytest.raises(NotImplementedError): ChassisBase() - def test_construct_sfp_list_for_topology(self): + def test_construct_cpo_list_for_topology(self): cpo_data = { "devices": { "OE1": {"device_type": "optical_engine"}, From aaceb8306a4da4478e19522ad80c7b828aeaf55a Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 29 Jul 2026 17:09:27 +0000 Subject: [PATCH 07/11] Review feedback Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index 1c3c8b486..14956aa36 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -776,7 +776,7 @@ def get_num_cpos(self): Returns: An integer, the number of CPO ports available on this chassis """ - return len(self._cpo_list) + return sum(1 for cpo in self._cpo_list if cpo is not None) def get_all_cpos(self): """ From ab18bf2db0e2df99131ef3eb8613557f561cc0f5 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Thu, 30 Jul 2026 18:35:17 +0000 Subject: [PATCH 08/11] Add validation of _sfp_list and _cpo_list Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 50 +++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index 14956aa36..a0deedaf5 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -62,13 +62,18 @@ def __init__(self): self._current_sensor_list = [] # List of SfpBase-derived objects representing all sfps - # available on the chassis + # available on the chassis, indexed by physical port. self._sfp_list = [] # List of CpoBase-derived objects representing all CPO ports # available on the chassis, indexed by physical port. self._cpo_list = [] + # Set once the port lists have been checked for consistency. The check + # is deferred until the lists are first accessed, since subclasses + # populate them after ChassisBase.__init__ has returned. + self._port_lists_validated = False + # Object derived from WatchdogBase for interacting with hardware watchdog self._watchdog = None @@ -726,6 +731,38 @@ def construct_cpo_devices(self, cpo_data): """ raise NotImplementedError + def _validate_port_lists(self): + """ + Check that _sfp_list and _cpo_list follow the indexing convention: + both are indexed by physical front panel port, with None at the + indices driven by the other technology. A platform which has only one + kind of port need only populate the corresponding list. + """ + if self._port_lists_validated: + return + + if not self._sfp_list or not self._cpo_list: + # This is a platform that only has one type of transceiver technology. + # We do not need to perform validation against both lists, because only + # one will be used. + return + + if len(self._sfp_list) != len(self._cpo_list): + raise RuntimeError( + "_sfp_list (length {}) and _cpo_list (length {}) must be the " + "same length: both are indexed by physical front panel port, " + "with None at the indices driven by the other technology".format( + len(self._sfp_list), len(self._cpo_list))) + + for index, (sfp, cpo) in enumerate(zip(self._sfp_list, self._cpo_list)): + if sfp is not None and cpo is not None: + raise RuntimeError( + "Physical port {} has both an SFP and a CPO object; each " + "port must appear in exactly one of _sfp_list and " + "_cpo_list".format(index)) + + self._port_lists_validated = True + def get_num_sfps(self): """ Retrieves the number of sfps available on this chassis @@ -733,7 +770,8 @@ def get_num_sfps(self): Returns: An integer, the number of sfps available on this chassis """ - return len(self._sfp_list) + self._validate_port_lists() + return sum(1 for sfp in self._sfp_list if sfp is not None) def get_all_sfps(self): """ @@ -743,6 +781,7 @@ def get_all_sfps(self): A list of objects derived from SfpBase representing all sfps available on this chassis """ + self._validate_port_lists() return [ sfp for sfp in self._sfp_list if sfp is not None ] def get_sfp(self, index): @@ -757,10 +796,12 @@ def get_sfp(self, index): 0 for Ethernet0, 1 for Ethernet4 and so on for another platform. Returns: - An object dervied from SfpBase representing the specified sfp + An object dervied from SfpBase representing the specified sfp, + or None if the port is not an SFP port """ sfp = None + self._validate_port_lists() try: sfp = self._sfp_list[index] except IndexError: @@ -776,6 +817,7 @@ def get_num_cpos(self): Returns: An integer, the number of CPO ports available on this chassis """ + self._validate_port_lists() return sum(1 for cpo in self._cpo_list if cpo is not None) def get_all_cpos(self): @@ -786,6 +828,7 @@ def get_all_cpos(self): A list of objects derived from CpoBase representing all CPO ports available on this chassis """ + self._validate_port_lists() return [cpo for cpo in self._cpo_list if cpo is not None] def get_cpo(self, index): @@ -802,6 +845,7 @@ def get_cpo(self, index): or None if the port is not a CPO port. """ cpo = None + self._validate_port_lists() try: cpo = self._cpo_list[index] except IndexError: From 30f86279d293244ae16ee5e86d0c9511fec5216b Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Thu, 30 Jul 2026 18:39:08 +0000 Subject: [PATCH 09/11] Add tests exercising _sfp_list and _cpo_list validation Signed-off-by: Brian Gallagher --- tests/chassis_base_test.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/chassis_base_test.py b/tests/chassis_base_test.py index 15cc17340..64890b1e9 100644 --- a/tests/chassis_base_test.py +++ b/tests/chassis_base_test.py @@ -207,3 +207,49 @@ def construct_cpo_devices(self, cpo_data): chassis = CpoChassis() assert chassis.get_num_cpos() == 1 assert chassis.get_cpo(0) == "Ethernet0" + + def test_sfp_counts_only_valid_objects(self): + chassis = ChassisBase() + sfp2 = mock.MagicMock() + sfp3 = mock.MagicMock() + chassis._sfp_list = [None, None, sfp2, sfp3] + + assert chassis.get_num_sfps() == 2 + assert chassis.get_all_sfps() == [sfp2, sfp3] + assert chassis.get_sfp(0) is None + assert chassis.get_sfp(2) is sfp2 + + def test_port_lists_valid(self): + chassis = ChassisBase() + chassis._sfp_list = [None, None, mock.MagicMock(), mock.MagicMock()] + chassis._cpo_list = [mock.MagicMock(), mock.MagicMock(), None, None] + + assert chassis.get_num_sfps() == 2 + assert chassis.get_num_cpos() == 2 + + def test_port_lists_length_mismatch(self): + chassis = ChassisBase() + chassis._sfp_list = [None, None, mock.MagicMock(), mock.MagicMock()] + chassis._cpo_list = [mock.MagicMock(), mock.MagicMock()] + + with pytest.raises(RuntimeError, match="must be the same length"): + chassis.get_num_cpos() + + with pytest.raises(RuntimeError, match="must be the same length"): + chassis.get_num_sfps() + + def test_port_lists_double_claimed_port(self): + chassis = ChassisBase() + chassis._sfp_list = [None, mock.MagicMock(), mock.MagicMock()] + chassis._cpo_list = [mock.MagicMock(), mock.MagicMock(), None] + + with pytest.raises(RuntimeError, + match="Physical port 1 has both an SFP and a CPO object"): + chassis.get_num_sfps() + + def test_port_lists_single_technology_not_checked(self): + chassis = ChassisBase() + chassis._sfp_list = [None, mock.MagicMock(), mock.MagicMock()] + + assert chassis.get_num_sfps() == 2 + assert chassis.get_num_cpos() == 0 From 9b8f96102c50c6440665788a5c9dc5f047f1e779 Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 5 Aug 2026 10:00:06 +0000 Subject: [PATCH 10/11] Empty commit to trigger build Signed-off-by: Brian Gallagher From fc51057d8e31b2bc2c7c2d3ed13b70873d96462f Mon Sep 17 00:00:00 2001 From: Brian Gallagher Date: Wed, 5 Aug 2026 15:31:51 +0000 Subject: [PATCH 11/11] Move import to module level Signed-off-by: Brian Gallagher --- sonic_platform_base/chassis_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index a0deedaf5..9e5823ae9 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -6,6 +6,7 @@ """ import sys +from sonic_py_common import device_info from . import device_base from . import sfp_base @@ -91,7 +92,6 @@ def __init__(self): # On platforms that provide a cpo.json file, populate self._cpo_list # based on the device topology described in that file - from sonic_py_common import device_info cpo_data = device_info.get_cpo_data() if cpo_data: self.construct_cpo_devices(cpo_data)