Skip to content

Commit 338b08f

Browse files
committed
introducing network explorer
1 parent 25723f2 commit 338b08f

14 files changed

Lines changed: 2453 additions & 1099 deletions

ngraph/blueprints.py

Lines changed: 93 additions & 131 deletions
Large diffs are not rendered by default.

ngraph/components.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
from __future__ import annotations
2+
3+
import yaml
4+
from copy import deepcopy
5+
from dataclasses import dataclass, field
6+
from typing import Dict, Any, Optional
7+
8+
9+
@dataclass(slots=True)
10+
class Component:
11+
"""
12+
A generic component with a name, cost, power, and optional children.
13+
14+
This can represent items such as chassis, line cards, optic modules, etc.
15+
16+
Attributes:
17+
name (str): Name of the component (e.g., "SpineChassis" or "400G-LR4").
18+
component_type (str): An optional string label ("chassis", "linecard", "optic", etc.).
19+
cost (float): Base capex cost for the component.
20+
power_watts (float): Power consumption of the component in watts.
21+
ports (int): Number of ports if relevant for this component.
22+
children (Dict[str, Component]): Child components keyed by their names.
23+
"""
24+
25+
name: str
26+
component_type: str = "generic"
27+
cost: float = 0.0
28+
power_watts: float = 0.0
29+
ports: int = 0
30+
children: Dict[str, Component] = field(default_factory=dict)
31+
32+
def total_cost(self) -> float:
33+
"""
34+
Calculates the total cost including all nested children.
35+
36+
Returns:
37+
float: Recursive sum of costs for this component and its children.
38+
"""
39+
total = self.cost
40+
for child in self.children.values():
41+
total += child.total_cost()
42+
return total
43+
44+
def total_power(self) -> float:
45+
"""
46+
Calculates the total power consumption including all nested children.
47+
48+
Returns:
49+
float: Recursive sum of power for this component and its children.
50+
"""
51+
total = self.power_watts
52+
for child in self.children.values():
53+
total += child.total_power()
54+
return total
55+
56+
57+
@dataclass(slots=True)
58+
class ComponentsLibrary:
59+
"""
60+
Holds a collection of named Components. Each entry is a top-level "template"
61+
that can be referenced for cost/power lookups, possibly with nested children.
62+
63+
Example:
64+
components:
65+
BigSwitch:
66+
component_type: chassis
67+
cost: 20000
68+
power_watts: 1000
69+
children:
70+
LC-48x10G:
71+
component_type: linecard
72+
cost: 5000
73+
power_watts: 300
74+
ports: 48
75+
400G-LR4:
76+
component_type: optic
77+
cost: 2000
78+
power_watts: 15
79+
80+
Attributes:
81+
components (Dict[str, Component]): A dictionary of component name -> Component.
82+
"""
83+
84+
components: Dict[str, Component] = field(default_factory=dict)
85+
86+
def get(self, name: str) -> Optional[Component]:
87+
"""
88+
Retrieves a Component by its name.
89+
90+
Args:
91+
name (str): Name of the component.
92+
93+
Returns:
94+
Optional[Component]: The requested Component or None if not found.
95+
"""
96+
return self.components.get(name)
97+
98+
def merge(
99+
self, other: ComponentsLibrary, override: bool = True
100+
) -> ComponentsLibrary:
101+
"""
102+
Merges another ComponentsLibrary into this one. By default (override=True),
103+
duplicate components in `other` overwrite those in the current library.
104+
105+
Args:
106+
other (ComponentsLibrary): Another library to merge into this one.
107+
override (bool): Whether components in `other` override existing ones.
108+
109+
Returns:
110+
ComponentsLibrary: This instance, updated in place.
111+
"""
112+
for comp_name, comp_obj in other.components.items():
113+
if override or comp_name not in self.components:
114+
self.components[comp_name] = comp_obj
115+
return self
116+
117+
def clone(self) -> ComponentsLibrary:
118+
"""
119+
Creates a deep copy of this ComponentsLibrary.
120+
121+
Returns:
122+
ComponentsLibrary: A new, cloned library instance.
123+
"""
124+
return ComponentsLibrary(components=deepcopy(self.components))
125+
126+
@classmethod
127+
def from_dict(cls, data: Dict[str, Any]) -> ComponentsLibrary:
128+
"""
129+
Constructs a ComponentsLibrary from a dictionary of raw component definitions.
130+
131+
Args:
132+
data (Dict[str, Any]): Raw component definitions.
133+
134+
Returns:
135+
ComponentsLibrary: A newly constructed library.
136+
"""
137+
components_map: Dict[str, Component] = {}
138+
for comp_name, comp_def in data.items():
139+
components_map[comp_name] = cls._build_component(comp_name, comp_def)
140+
return cls(components=components_map)
141+
142+
@classmethod
143+
def _build_component(cls, name: str, definition_data: Dict[str, Any]) -> Component:
144+
"""
145+
Recursively constructs a single Component from a dictionary definition.
146+
147+
Args:
148+
name (str): Name of the component.
149+
definition_data (Dict[str, Any]): Dictionary data for the component definition.
150+
151+
Returns:
152+
Component: The constructed Component instance.
153+
"""
154+
comp_type = definition_data.get("component_type", "generic")
155+
cost = float(definition_data.get("cost", 0.0))
156+
power = float(definition_data.get("power_watts", 0.0))
157+
ports = int(definition_data.get("ports", 0))
158+
159+
children_map: Dict[str, Component] = {}
160+
child_definitions = definition_data.get("children", {})
161+
for child_name, child_data in child_definitions.items():
162+
children_map[child_name] = cls._build_component(child_name, child_data)
163+
164+
return Component(
165+
name=name,
166+
component_type=comp_type,
167+
cost=cost,
168+
power_watts=power,
169+
ports=ports,
170+
children=children_map,
171+
)
172+
173+
@classmethod
174+
def from_yaml(cls, yaml_str: str) -> ComponentsLibrary:
175+
"""
176+
Constructs a ComponentsLibrary from a YAML string. If the YAML contains
177+
a top-level 'components' key, that key is used; otherwise the entire
178+
top-level of the YAML is treated as component definitions.
179+
180+
Args:
181+
yaml_str (str): A YAML-formatted string of component definitions.
182+
183+
Returns:
184+
ComponentsLibrary: A newly built components library.
185+
186+
Raises:
187+
ValueError: If the top-level is not a dictionary or if the 'components'
188+
key is present but not a dictionary.
189+
"""
190+
data = yaml.safe_load(yaml_str)
191+
if not isinstance(data, dict):
192+
raise ValueError("Top-level must be a dict in Components YAML.")
193+
194+
components_data = data.get("components") or data
195+
if not isinstance(components_data, dict):
196+
raise ValueError("'components' must be a dict if present.")
197+
198+
return cls.from_dict(components_data)

0 commit comments

Comments
 (0)