Repository: aristanetworks/avd
Pull request #5119: Refactor(eos_designs): Refactor eos_designs structured_config code for spanning_tree
Author: login: MaheshGSLAB
Labels: name: rn: Refactor(eos_designs)
name: one approval
description: This PR has one approval and is only missing one more.
Created: 2025-03-03T06:15:15Z | Merged: 2025-03-06T15:17:02Z

## Change Summary

Refactor eos_designs structured_config code for spanning_tree

## Related Issue(s)

Fixes #<ISSUE ID>

## Component(s) name

`arista.avd.eos_designs`

## Proposed changes
Refactor eos_designs structured_config code for spanning_tree

## How to test
Run Molecule

## Checklist

### User Checklist

<!-- Add your own checklist using MD syntax and by replacing N/A -->
- N/A

### Repository Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code has been rebased from devel before I start
- [x] I have read the [**CONTRIBUTING**](https://avd.arista.com/stable/docs/contribution/overview.html) document.
- [ ] My change requires a change to the documentation and documentation have been updated accordingly.
- [x] I have updated [molecule CI](https://github.com/aristanetworks/avd/tree/devel/ansible_collections/arista/avd/molecule) testing accordingly. (check the box if not applicable)



Diff:
diff --git a/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
index a5bf085d9b1..b915ee0b490 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
@@ -385,31 +385,26 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
+            return
 
-        spanning_tree_root_super = self.shared_utils.node_config.spanning_tree_root_super
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
-        if spanning_tree_root_super is not True and spanning_tree_mode is None:
-            return None
 
-        spanning_tree = {}
-        if spanning_tree_root_super is True:
-            spanning_tree["root_super"] = True
+        if self.shared_utils.node_config.spanning_tree_root_super is True:
+            self.structured_config.spanning_tree.root_super = True
 
         if spanning_tree_mode is not None:
-            spanning_tree["mode"] = spanning_tree_mode
+            self.structured_config.spanning_tree.mode = spanning_tree_mode
             priority = self.shared_utils.node_config.spanning_tree_priority
             # "rapid-pvst" is not included below. Per vlan spanning-tree priorities are set under network-services.
             if spanning_tree_mode == "mstp":
-                spanning_tree["mst_instances"] = [{"id": "0", "priority": priority}]
+                self.structured_config.spanning_tree.mst_instances.append_new(id="0", priority=priority)
             elif spanning_tree_mode == "rstp":
-                spanning_tree["rstp_priority"] = priority
-
-        return spanning_tree
+                self.structured_config.spanning_tree.rstp_priority = priority
 
     @cached_property
     def service_unsupported_transceiver(self) -> dict | None:
diff --git a/python-avd/pyavd/_eos_designs/structured_config/mlag/__init__.py b/python-avd/pyavd/_eos_designs/structured_config/mlag/__init__.py
index e07515fb307..01a5d447f97 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/mlag/__init__.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/mlag/__init__.py
@@ -19,13 +19,13 @@ def render(self) -> None:
             return super().render()
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         vlans = [self.shared_utils.node_config.mlag_peer_vlan]
         if self.shared_utils.mlag_peer_l3_vlan is not None:
             vlans.append(self.shared_utils.mlag_peer_l3_vlan)
 
-        return {"no_spanning_tree_vlan": list_compress(vlans)}
+        self.structured_config.spanning_tree.no_spanning_tree_vlan = list_compress(vlans)
 
     @structured_config_contributor
     def vlans(self) -> None:
diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/spanning_tree.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/spanning_tree.py
index 8743160f076..0d2515a1d1a 100644
--- a/python-avd/pyavd/_eos_designs/structured_config/network_services/spanning_tree.py
+++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/spanning_tree.py
@@ -3,9 +3,9 @@
 # that can be found in the LICENSE file.
 from __future__ import annotations
 
-from functools import cached_property
 from typing import TYPE_CHECKING, Protocol
 
+from pyavd._eos_designs.structured_config.structured_config_generator import structured_config_contributor
 from pyavd.j2filters import list_compress
 
 if TYPE_CHECKING:
@@ -19,15 +19,15 @@ class SpanningTreeMixin(Protocol):
     Class should only be used as Mixin to a AvdStructuredConfig class.
     """
 
-    @cached_property
-    def spanning_tree(self: AvdStructuredConfigNetworkServicesProtocol) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self: AvdStructuredConfigNetworkServicesProtocol) -> None:
         """spanning_tree priorities set per VLAN if spanning_tree mode is "rapid-pvst"."""
         if not self.shared_utils.network_services_l2:
-            return None
+            return
 
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
         if spanning_tree_mode != "rapid-pvst":
-            return None
+            return
 
         default_priority = self.shared_utils.node_config.spanning_tree_priority
 
@@ -48,16 +48,10 @@ def spanning_tree(self: AvdStructuredConfigNetworkServicesProtocol) -> dict | No
 
         if not non_default_vlans:
             # Quick return with only default
-            return {"rapid_pvst_instances": [{"id": "1-4094", "priority": default_priority}]}
+            self.structured_config.spanning_tree.rapid_pvst_instances.append_new(id="1-4094", priority=default_priority)
+            return
 
         default_vlans = non_default_vlans.symmetric_difference(range(1, 4094))
         vlan_stp_priorities.setdefault(default_priority, set()).update(default_vlans)
-        return {
-            "rapid_pvst_instances": [
-                {
-                    "id": list_compress(list(vlans)),
-                    "priority": priority,
-                }
-                for priority, vlans in vlan_stp_priorities.items()
-            ],
-        }
+        for priority, vlans in vlan_stp_priorities.items():
+            self.structured_config.spanning_tree.rapid_pvst_instances.append_new(id=list_compress(list(vlans)), priority=priority)


Reviews:
user: login: Vibhu-gslab
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T10:09:37Z
user: login: gmuloc
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T13:56:44Z
user: login: ClausHolbechArista
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T08:31:53Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T10:39:51Z
user: login: MaheshGSLAB
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T10:44:36Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T11:15:41Z
user: login: gmuloc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T11:16:56Z
user: login: gmuloc
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T12:06:54Z

Review comments:
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
 
         spanning_tree_root_super = self.shared_utils.node_config.spanning_tree_root_super
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
         if spanning_tree_root_super is not True and spanning_tree_mode is None:
-            return None
+            return
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: gmuloc
body: I think you don't need this `if` anymore and so probably you can used directly the shared_utils in the two below if statements
created_at: 2025-03-05T13:53:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: ClausHolbechArista
body: the old code returned here, so you should add a return after setting the none. Please ensure we have testing of this case. I would expect all spines to hit this path.
created_at: 2025-03-06T08:29:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
 
         spanning_tree_root_super = self.shared_utils.node_config.spanning_tree_root_super
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
         if spanning_tree_root_super is not True and spanning_tree_mode is None:
-            return None
+            return
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: MaheshGSLAB
body: spanning_tree_mode used multiple times hence keeping and removed the spanning_tree_root_super variable
created_at: 2025-03-06T10:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: MaheshGSLAB
body: Updated the code.
created_at: 2025-03-06T10:44:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
 
         spanning_tree_root_super = self.shared_utils.node_config.spanning_tree_root_super
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
         if spanning_tree_root_super is not True and spanning_tree_mode is None:
-            return None
+            return
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: gmuloc
body: it is used inside a block that says " if spanning_tee_mode is not None" which is redundant with this check
created_at: 2025-03-06T11:15:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,31 +385,28 @@ def interface_defaults(self) -> dict | None:
             }
         return None
 
-    @cached_property
-    def spanning_tree(self) -> dict | None:
+    @structured_config_contributor
+    def spanning_tree(self) -> None:
         """spanning_tree set based on spanning_tree_root_super, spanning_tree_mode and spanning_tree_priority."""
         if not self.shared_utils.network_services_l2:
-            return {"mode": "none"}
+            self.structured_config.spanning_tree.mode = "none"
 
         spanning_tree_root_super = self.shared_utils.node_config.spanning_tree_root_super
         spanning_tree_mode = self.shared_utils.node_config.spanning_tree_mode
         if spanning_tree_root_super is not True and spanning_tree_mode is None:
-            return None
+            return
path: python-avd/pyavd/_eos_designs/structured_config/base/__init__.py
user: login: gmuloc
body: hmm actually I am not seeing the last change so I amy be commenting the wrong stuff :)
created_at: 2025-03-06T11:16:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-03T06:15:27Z
body: Review docs on [Read the Docs](https://ansible-avd--5119.org.readthedocs.build/en/5119/)

To test this pull request:

```sh
# Create virtual environment for this testing below the current directory
python -m venv test-avd-pr-5119
# Activate the virtual environment
source test-avd-pr-5119/bin/activate
# Install all requirements including PyAVD
pip install "pyavd[ansible] @ git+https://github.com/MaheshGSLAB/ansible-avd.git@stp-refactor#subdirectory=python-avd" --force
# Point Ansible collections path to the Python virtual environment
export ANSIBLE_COLLECTIONS_PATH=$VIRTUAL_ENV/ansible_collections
# Install Ansible collection
ansible-galaxy collection install git+https://github.com/MaheshGSLAB/ansible-avd.git#/ansible_collections/arista/avd/,stp-refactor --force
# Optional: Install AVD examples
cd test-avd-pr-5119
ansible-playbook arista.avd.install_examples
```

author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-06T13:58:30Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_avd&pullRequest=5119) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=aristanetworks_avd&pullRequest=5119&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_avd&pullRequest=5119&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_avd&pullRequest=5119&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [88.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_avd&pullRequest=5119&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_avd&pullRequest=5119&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_avd&pullRequest=5119)


author_association: NONE