acex-devkit 1.1.1__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/PKG-INFO +1 -1
  2. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/pyproject.toml +1 -1
  3. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/configdiffer.py +7 -1
  4. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/diff.py +43 -5
  5. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/acl_model.py +4 -4
  6. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/composed_configuration.py +81 -76
  7. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/logging.py +21 -21
  8. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/spanning_tree.py +17 -17
  9. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/README.md +0 -0
  10. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/__init__.py +0 -0
  11. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/__init__.py +0 -0
  12. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/command.py +0 -0
  13. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/old_configdiffer.py +0 -0
  14. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/configdiffer/old_diff.py +0 -0
  15. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/drivers/__init__.py +0 -0
  16. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/drivers/base.py +0 -0
  17. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/drivers/base_driver.py +0 -0
  18. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/exceptions/__init__.py +0 -0
  19. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/__init__.py +0 -0
  20. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/attribute_value.py +0 -0
  21. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/external_value.py +0 -0
  22. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/ned.py +0 -0
  23. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/models/node_response.py +0 -0
  24. {acex_devkit-1.1.1 → acex_devkit-1.2.0}/src/acex_devkit/types/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acex-devkit
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: ACE-X DevKit - Development kit for building ACE-X drivers and plugins
5
5
  License: AGPL-3.0
6
6
  Keywords: automation,devkit,sdk,drivers,plugins
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "acex-devkit"
3
- version = "1.1.1"
3
+ version = "1.2.0"
4
4
  description = "ACE-X DevKit - Development kit for building ACE-X drivers and plugins"
5
5
  authors = ["Johan Lahti <johan.lahti@acebit.se>"]
6
6
  readme = "README.md"
@@ -167,6 +167,12 @@ class ConfigDiffer:
167
167
  changed_attributes=self._attribute_changes(observed_comp, desired_comp),
168
168
  ))
169
169
 
170
- return Diff(added=added, removed=removed, changed=changed)
170
+ return Diff(
171
+ added=added,
172
+ removed=removed,
173
+ changed=changed,
174
+ total_desired=len(desired_paths),
175
+ total_observed=len(observed_paths),
176
+ )
171
177
 
172
178
 
@@ -6,7 +6,7 @@ added, removed, or changed when comparing two configurations. This enables drive
6
6
  to render device-specific command patches based on the component changes.
7
7
  """
8
8
 
9
- from pydantic import BaseModel, Field
9
+ from pydantic import BaseModel, Field, field_serializer, computed_field
10
10
  from typing import Any, Dict, List, Optional, Type
11
11
  from enum import Enum
12
12
 
@@ -58,6 +58,15 @@ class ComponentChange(BaseModel):
58
58
 
59
59
  model_config = {"arbitrary_types_allowed": True}
60
60
 
61
+ @field_serializer("component_type")
62
+ def serialize_component_type(self, v: Type[Any]) -> str:
63
+ return v.__name__
64
+
65
+ @property
66
+ def component_type_name(self) -> str:
67
+ """Return the class name of the component type, e.g., 'FrontpanelPort'"""
68
+ return self.component_type.__name__
69
+
61
70
  def get_path_str(self, separator: str = "/") -> str:
62
71
  """Return the component path as a string, e.g., 'interfaces/GigabitEthernet0-0-1'"""
63
72
  return separator.join(self.path) if self.path else ""
@@ -74,17 +83,46 @@ class Diff(BaseModel):
74
83
  removed: List[ComponentChange] = Field(default_factory=list)
75
84
  changed: List[ComponentChange] = Field(default_factory=list)
76
85
 
86
+ # Total number of config points in desired/observed configs — populated by ConfigDiffer
87
+ total_desired: int = 0
88
+ total_observed: int = 0
89
+
90
+ @computed_field
91
+ @property
92
+ def compliant_count(self) -> int:
93
+ """
94
+ Number of desired config points that are fully compliant (present and
95
+ identical on the device). Equals total desired minus points that are
96
+ missing (added) or incorrect (changed).
97
+ """
98
+ return max(0, self.total_desired - len(self.added) - len(self.changed))
99
+
100
+ @computed_field
101
+ @property
102
+ def compliance_percentage(self) -> float:
103
+ """
104
+ Percentage of desired config points that are compliant.
105
+ Returns 100.0 when there are no desired config points.
106
+ """
107
+ if self.total_desired == 0:
108
+ return 100.0
109
+ return round(self.compliant_count / self.total_desired * 100, 2)
110
+
77
111
  def is_empty(self) -> bool:
78
112
  """Check if there are no changes"""
79
113
  return not (self.added or self.removed or self.changed)
80
114
 
81
- def summary(self) -> Dict[str, int]:
82
- """Get a summary of changes"""
115
+ def summary(self) -> Dict[str, Any]:
116
+ """Get a summary of changes and compliance metadata"""
83
117
  return {
84
118
  "added": len(self.added),
85
119
  "removed": len(self.removed),
86
120
  "changed": len(self.changed),
87
- "total": len(self.added) + len(self.removed) + len(self.changed)
121
+ "total_changes": len(self.added) + len(self.removed) + len(self.changed),
122
+ "total_desired": self.total_desired,
123
+ "total_observed": self.total_observed,
124
+ "compliant_count": self.compliant_count,
125
+ "compliance_percentage": self.compliance_percentage,
88
126
  }
89
127
 
90
128
  def get_all_changes(self) -> List[ComponentChange]:
@@ -95,7 +133,7 @@ class Diff(BaseModel):
95
133
  """Get all changes for a specific component type (e.g., 'Loopback')"""
96
134
  return [
97
135
  change for change in self.get_all_changes()
98
- if change.component_type == component_type
136
+ if change.component_type.__name__ == component_type
99
137
  ]
100
138
 
101
139
  def get_changes_by_path_prefix(self, path_prefix: List[str]) -> List[ComponentChange]:
@@ -67,13 +67,13 @@ class Ipv6AclEntryAttributes(IpAclOptions):
67
67
  class Ipv4AclAttributes(BaseModel):
68
68
  name: AttributeValue[str]
69
69
  type: AttributeValue[str] = "ipv4_acl"
70
- acl_entries: Optional[Dict[str, Ipv4AclEntryAttributes]] = {}
70
+ acl_entries: Optional[Dict[str, Ipv4AclEntryAttributes]] = None
71
71
 
72
72
  class Ipv6AclAttributes(BaseModel):
73
73
  name: AttributeValue[str]
74
74
  type: AttributeValue[str] = "ipv6_acl"
75
- acl_entries: Optional[Dict[str, Ipv6AclEntryAttributes]] = {}
75
+ acl_entries: Optional[Dict[str, Ipv6AclEntryAttributes]] = None
76
76
 
77
77
  class Acl(BaseModel):
78
- ipv4_acls: Optional[Dict[str, Ipv4AclAttributes]] = {}
79
- ipv6_acls: Optional[Dict[str, Ipv6AclAttributes]] = {}
78
+ ipv4_acls: Optional[Dict[str, Ipv4AclAttributes]] = None
79
+ ipv6_acls: Optional[Dict[str, Ipv6AclAttributes]] = None
@@ -55,10 +55,10 @@ class SystemConfig(BaseModel):
55
55
  class LoggingComponents(BaseModel):
56
56
  config: LoggingConfig = LoggingConfig()
57
57
  console: Optional[Console] = None
58
- remote_servers: Optional[Dict[str, RemoteServer]] = {}
58
+ remote_servers: Optional[Dict[str, RemoteServer]] = None
59
59
  events: Optional[LoggingEvents] = None
60
- vty: Optional[Dict[str, VtyLine]] = {}
61
- files: Optional[Dict[str, FileLogging]] = {}
60
+ vty: Optional[Dict[str, VtyLine]] = None
61
+ files: Optional[Dict[str, FileLogging]] = None
62
62
 
63
63
  class NtpConfig(BaseModel):
64
64
  enabled: AttributeValue[bool] = AttributeValue(value=False)
@@ -73,7 +73,7 @@ class NtpServer(BaseModel):
73
73
 
74
74
  class Ntp(BaseModel):
75
75
  config: Optional[NtpConfig] = None
76
- servers: Optional[Dict[str, NtpServer]] = {}
76
+ servers: Optional[Dict[str, NtpServer]] = None
77
77
 
78
78
  class SshServer(BaseModel):
79
79
  enable: Optional[AttributeValue[bool]] = None
@@ -100,7 +100,7 @@ class AuthorizedKey(BaseModel):
100
100
 
101
101
  class Ssh(BaseModel):
102
102
  config: Optional[SshServer] = None
103
- host_keys: Optional[Dict[str, AuthorizedKey]] = {}
103
+ host_keys: Optional[Dict[str, AuthorizedKey]] = None
104
104
 
105
105
  class Lldp(BaseModel): ...
106
106
 
@@ -164,23 +164,22 @@ class EthernetCsmacdInterface(Interface):
164
164
  lacp_interval: Optional[AttributeValue[Literal["fast", "slow"]]] = None
165
165
 
166
166
  # Spanning-tree relaterade attribut
167
- stp_port_priority: Optional[int] = None
168
- stp_cost: Optional[int] = None
169
- stp_edge_port: Optional[bool] = False # Disabled by default
170
- stp_bpdu_filter: Optional[bool] = False # Disabled by default
171
- stp_bpdu_guard: Optional[bool] = False # Disabled by default
172
- stp_loop_guard: Optional[bool] = False # Disabled by default
173
- stp_root_guard: Optional[bool] = False # Disabled by default
174
- stp_portfast: Optional[bool] = False # Disabled by default
175
- stp_link_type: Optional[Literal["point-to-point", "shared"]] = None # e.g., "point-to-point", "shared"
167
+ stp_port_priority: Optional[AttributeValue[int]] = None
168
+ stp_cost: Optional[AttributeValue[int]] = None
169
+ stp_edge_port: Optional[AttributeValue[bool]] = None
170
+ stp_bpdu_filter: Optional[AttributeValue[bool]] = None
171
+ stp_bpdu_guard: Optional[AttributeValue[bool]] = None
172
+ stp_loop_guard: Optional[AttributeValue[bool]] = None
173
+ stp_root_guard: Optional[AttributeValue[bool]] = None
174
+ stp_portfast: Optional[AttributeValue[bool]] = None
175
+ stp_link_type: Optional[AttributeValue[Literal["point-to-point", "shared"]]] = None
176
176
 
177
177
 
178
178
  class Ieee8023adLagInterface(Interface):
179
179
  "LAG Interface"
180
180
  type: Literal["ieee8023adLag"] = "ieee8023adLag"
181
- #aggregate_id: AttributeValue[int] = None
182
- aggregate_id: int = None
183
- members: list[str] = Field(default_factory=list)
181
+ aggregate_id: Optional[AttributeValue[int]] = None
182
+ members: Optional[AttributeValue[List[str]]] = None
184
183
  max_ports: Optional[AttributeValue[int]] = None
185
184
  switchport: Optional[AttributeValue[bool]] = None
186
185
  switchport_mode: Optional[AttributeValue[Literal["access", "trunk"]]] = None
@@ -226,15 +225,15 @@ class StaticRouteNextHop(BaseModel):
226
225
  class StaticRoute(BaseModel):
227
226
  route_name: Optional[AttributeValue[str]] = None
228
227
  prefix: AttributeValue[str]
229
- next_hops: Optional[Dict[str, StaticRouteNextHop]] = {}
228
+ next_hops: Optional[Dict[str, StaticRouteNextHop]] = None
230
229
  network_instance: Optional[AttributeValue[str]] = None
231
230
 
232
231
  class Protocols(BaseModel):
233
- static_routes: Optional[Dict[str, StaticRoute]] = {}
232
+ static_routes: Optional[Dict[str, StaticRoute]] = None
234
233
  # OSPF, BGP, etc. can be added here as needed
235
234
 
236
235
  class RouteTarget(BaseModel):
237
- value: str # TODO: Add constraints and validators...
236
+ value: Optional[AttributeValue[str]] = None
238
237
 
239
238
  class ImportExportPolicy(BaseModel):
240
239
  export_route_target: Optional[List[RouteTarget]] = None
@@ -246,9 +245,9 @@ class InterInstancePolicy(BaseModel):
246
245
  class NetworkInstance(BaseModel):
247
246
  name: AttributeValue[str]
248
247
  description: Optional[AttributeValue[str]] = None
249
- vlans: Optional[Dict[str, Vlan]] = {}
248
+ vlans: Optional[Dict[str, Vlan]] = None
250
249
  interfaces: Optional[Dict[str, Reference]] = {}
251
- inter_instance_policies: Optional[Dict[str, InterInstancePolicy]] = {}
250
+ inter_instance_policies: Optional[Dict[str, InterInstancePolicy]] = None
252
251
  protocols: Optional[Protocols] = Protocols()
253
252
 
254
253
  class LacpConfig(BaseModel):
@@ -258,7 +257,7 @@ class LacpConfig(BaseModel):
258
257
 
259
258
  class Lacp(BaseModel):
260
259
  config: Optional[LacpConfig] = LacpConfig()
261
- interfaces: Optional[Dict[str, Interface]] = {}
260
+ interfaces: Optional[Dict[str, Interface]] = None
262
261
 
263
262
  # SNMP
264
263
  class SnmpAccess(str, Enum):
@@ -323,7 +322,7 @@ class SnmpView(BaseModel):
323
322
 
324
323
 
325
324
  class SnmpServer(BaseModel):
326
- name: str
325
+ name: Optional[AttributeValue[str]] = None
327
326
  address: AttributeValue[str]
328
327
  port: Optional[AttributeValue[int]] = AttributeValue(value=162)
329
328
  enabled: Optional[AttributeValue[bool]] = AttributeValue(value=True)
@@ -450,41 +449,41 @@ class TrapEventOptions(str, Enum):
450
449
  BULKSTAT_TRANSFER = "bulkstat_transfer"
451
450
 
452
451
  class TrapEvent(BaseModel):
453
- name: str
454
- event_name: TrapEventOptions
452
+ name: Optional[AttributeValue[str]] = None
453
+ event_name: Optional[AttributeValue[TrapEventOptions]] = None
455
454
 
456
455
  #class SnmpTrap(BaseModel): ...
457
456
 
458
457
  class Snmp(BaseModel):
459
- config: Optional[Dict[str, SnmpConfig]] = {}
460
- communities: Optional[Dict[str, SnmpCommunity]] = {}
461
- users: Optional[Dict[str, SnmpUser]] = {}
462
- trap_servers: Optional[Dict[str, SnmpServer]] = {}
463
- trap_events: Optional[Dict[str, TrapEvent]] = {}
464
- views: Optional[Dict[str, SnmpView]] = {}
458
+ config: Optional[Dict[str, SnmpConfig]] = None
459
+ communities: Optional[Dict[str, SnmpCommunity]] = None
460
+ users: Optional[Dict[str, SnmpUser]] = None
461
+ trap_servers: Optional[Dict[str, SnmpServer]] = None
462
+ trap_events: Optional[Dict[str, TrapEvent]] = None
463
+ views: Optional[Dict[str, SnmpView]] = None
465
464
 
466
465
  # AAA
467
466
  class aaaBaseClass(BaseModel):
468
- name: str = None
467
+ name: Optional[AttributeValue[str]] = None
469
468
 
470
469
  class aaaTacacsAttributes(BaseModel):
471
- port: Optional[int] = 49
472
- secret_key: Optional[str] = None
473
- secret_key_hashed: Optional[str] = None
474
- address: Optional[str] = None
475
- timeout: Optional[int] = 30
476
- source_interface: Optional[Reference] = None #Optional[Reference] = None # should be reference
470
+ port: Optional[AttributeValue[int]] = None
471
+ secret_key: Optional[AttributeValue[str]] = None
472
+ secret_key_hashed: Optional[AttributeValue[str]] = None
473
+ address: Optional[AttributeValue[str]] = None
474
+ timeout: Optional[AttributeValue[int]] = None
475
+ source_interface: Optional[Reference] = None
477
476
  server_group: Optional[AttributeValue[str]] = None
478
477
 
479
478
  class aaaRadiusAttributes(BaseModel):
480
- auth_port: Optional[int] = 1812
481
- acct_port: Optional[int] = 1813
482
- secret_key: Optional[str] = None
483
- secret_key_hashed: Optional[str] = None
484
- address: Optional[str] = None
485
- timeout: Optional[int] = 30
486
- source_interface: Optional[Reference] = None #Optional[Reference] = None # should be reference
487
- retransmit_attempts: Optional[int] = 3
479
+ auth_port: Optional[AttributeValue[int]] = None
480
+ acct_port: Optional[AttributeValue[int]] = None
481
+ secret_key: Optional[AttributeValue[str]] = None
482
+ secret_key_hashed: Optional[AttributeValue[str]] = None
483
+ address: Optional[AttributeValue[str]] = None
484
+ timeout: Optional[AttributeValue[int]] = None
485
+ source_interface: Optional[Reference] = None
486
+ retransmit_attempts: Optional[AttributeValue[int]] = None
488
487
  server_group: Optional[AttributeValue[str]] = None
489
488
 
490
489
  class aaaServerGroupAttributes(BaseModel):
@@ -494,13 +493,13 @@ class aaaServerGroupAttributes(BaseModel):
494
493
  Type is used to tell future renderers what kind of server group this is.
495
494
  Example:
496
495
  type = 'tacacs' or type = 'radius'
497
-
496
+
498
497
  The tacacs and radius attributes expect a reference to the aaaTacacs and aaaRadius models respectively.
499
498
 
500
499
  Example in config map:
501
500
  enable = True
502
501
  type = 'tacacs'
503
- tacacs = [tacacs_server1, tacacs_server2]
502
+ tacacs = [tacacs_server1, tacacs_server2]
504
503
  radius = radius_server1
505
504
 
506
505
  Cisco example:
@@ -508,10 +507,10 @@ class aaaServerGroupAttributes(BaseModel):
508
507
  server name tacacs_server1
509
508
  server name tacacs_server2
510
509
  """
511
- enable: Optional[bool] = False
512
- type: Optional[Literal['tacacs','radius']] = None
513
- tacacs: Optional[Dict[str, aaaTacacsAttributes]] = {} #Optional[Dict[str, Reference]] = None
514
- radius: Optional[Dict[str, aaaRadiusAttributes]] = {} #Optional[Dict[str, Reference]] = None
510
+ enable: Optional[AttributeValue[bool]] = None
511
+ type: Optional[AttributeValue[Literal['tacacs','radius']]] = None
512
+ tacacs: Optional[Dict[str, aaaTacacsAttributes]] = None
513
+ radius: Optional[Dict[str, aaaRadiusAttributes]] = None
515
514
 
516
515
  # Authentication Models
517
516
  class aaaAuthenticationMethods(aaaBaseClass):
@@ -534,26 +533,26 @@ class aaaAuthenticationMethods(aaaBaseClass):
534
533
  # aaa authentication enable default group TACACS-GROUP-NEW enable
535
534
 
536
535
  class authenticationUser(aaaBaseClass):
537
- username: Optional[str] = None
538
- password: Optional[str] = None
539
- password_hahsed: Optional[str] = None
540
- ssh_key: Optional[str] = None
541
- role: Optional[str] = None
536
+ username: Optional[AttributeValue[str]] = None
537
+ password: Optional[AttributeValue[str]] = None
538
+ password_hahsed: Optional[AttributeValue[str]] = None
539
+ ssh_key: Optional[AttributeValue[str]] = None
540
+ role: Optional[AttributeValue[str]] = None
542
541
 
543
542
  class aaaAuthenticationUsers(aaaBaseClass):
544
- username: Optional[Dict[str, authenticationUser]] = {}
543
+ username: Optional[Dict[str, authenticationUser]] = None
545
544
 
546
- class adminUser(aaaBaseClass): # when to use this?
547
- admin_password: Optional[str] = None
548
- admin_password_hashed: Optional[str] = None
545
+ class adminUser(aaaBaseClass):
546
+ admin_password: Optional[AttributeValue[str]] = None
547
+ admin_password_hashed: Optional[AttributeValue[str]] = None
549
548
 
550
549
  class aaaAuthenticationAdminUsers(BaseModel):
551
- config: Optional[Dict[str, adminUser]] = {}
550
+ config: Optional[Dict[str, adminUser]] = None
552
551
 
553
552
  class aaaAuthentication(BaseModel):
554
- config: Optional[Dict[str, aaaAuthenticationMethods]] = {}
555
- admin_user: Optional[Dict[str, aaaAuthenticationAdminUsers]] = {}
556
- users: Optional[Dict[str, aaaAuthenticationUsers]] = {}
553
+ config: Optional[Dict[str, aaaAuthenticationMethods]] = None
554
+ admin_user: Optional[Dict[str, aaaAuthenticationAdminUsers]] = None
555
+ users: Optional[Dict[str, aaaAuthenticationUsers]] = None
557
556
 
558
557
  # Authorization Models
559
558
  class aaaAuthorizationMethods(aaaBaseClass):
@@ -583,10 +582,8 @@ class aaaAuthorizationEvents(aaaBaseClass):
583
582
  event: Optional[AttributeValue[str]] = None
584
583
 
585
584
  class aaaAuthorization(BaseModel):
586
- #config: Optional[Dict[str, aaaAuthorizationMethods]] = {}
587
- config: Optional[Dict[str, aaaAuthorizationMethods]] = {}
588
- #events: Optional[Dict[str, aaaAuthorizationEvents]] = {}
589
- events: Optional[Dict[str, aaaAuthorizationEvents]] = {}
585
+ config: Optional[Dict[str, aaaAuthorizationMethods]] = None
586
+ events: Optional[Dict[str, aaaAuthorizationEvents]] = None
590
587
 
591
588
  # Accounting Models
592
589
  class aaaAccountingMethods(BaseModel):
@@ -614,17 +611,15 @@ class aaaAccountingEvents(BaseModel):
614
611
  event: Optional[AttributeValue[str]] = None
615
612
 
616
613
  class aaaAccounting(BaseModel):
617
- #config: aaaAccountingMethods = aaaAccountingMethods()
618
- config: Optional[Dict[str, aaaAccountingMethods]] = {}
619
- #events: aaaAccountingEvents = aaaAccountingEvents()
620
- events: Optional[Dict[str, aaaAccountingEvents]] = {}
614
+ config: Optional[Dict[str, aaaAccountingMethods]] = None
615
+ events: Optional[Dict[str, aaaAccountingEvents]] = None
621
616
 
622
617
  class aaaGlobalAttributes(BaseModel):
623
618
  enabled: Optional[AttributeValue[bool]] = False # default False
624
619
 
625
620
  class TripleA(BaseModel):
626
621
  config: aaaGlobalAttributes = aaaGlobalAttributes()
627
- server_groups: Optional[Dict[str, aaaServerGroupAttributes]] = {}
622
+ server_groups: Optional[Dict[str, aaaServerGroupAttributes]] = None
628
623
  authentication: aaaAuthentication = aaaAuthentication()
629
624
  authorization: aaaAuthorization = aaaAuthorization()
630
625
  accounting: aaaAccounting = aaaAccounting()
@@ -654,4 +649,14 @@ class ComposedConfiguration(BaseModel):
654
649
  lacp: Optional[Lacp] = Lacp()
655
650
  interfaces: Dict[str, InterfaceType] = {}
656
651
  network_instances: Dict[str, NetworkInstance] = {"global": NetworkInstance(name="global")}
657
- stp: Optional[SpanningTree] = SpanningTree()
652
+ stp: Optional[SpanningTree] = SpanningTree()
653
+
654
+
655
+ """
656
+ GUIDELINES FOR COMPOSED CONFIGURATION:
657
+
658
+ 1. All values must always be typed as AttributeValue.
659
+ 2. Containers must always be defined as hierarchical pydantic types, no dicts as placeholders.
660
+ 3. Component collections are always Dict[str, BaseModel] — the key identifies the component
661
+ 4. Default values for Optional is always None.
662
+ """
@@ -42,36 +42,36 @@ class LoggingConfig(BaseModel):
42
42
  buffer_size: Optional[AttributeValue[int]] = None
43
43
 
44
44
  class Console(BaseModel):
45
- name: str = None
46
- line_number: int = None
47
- logging_synchronous: bool = True
45
+ name: Optional[AttributeValue[str]] = None
46
+ line_number: Optional[AttributeValue[int]] = None
47
+ logging_synchronous: Optional[AttributeValue[bool]] = None
48
48
 
49
49
  class RemoteServer(BaseModel):
50
- name: str = None
51
- host: str = None
52
- port: Optional[int] = 514
53
- transfer: Optional[str] = 'udp'
50
+ name: Optional[AttributeValue[str]] = None
51
+ host: Optional[AttributeValue[str]] = None
52
+ port: Optional[AttributeValue[int]] = None
53
+ transfer: Optional[AttributeValue[str]] = None
54
54
  source_address: Optional[AttributeValue[str]] = None # Can be an IP address or an interface reference
55
55
 
56
56
  class VtyLine(BaseModel):
57
- name: str = None
58
- line_number: int = None
59
- logging_synchronous: bool = True
60
- transport_input: Optional[str] = 'ssh' # default is SSH. Mostly used by Cisco.
57
+ name: Optional[AttributeValue[str]] = None
58
+ line_number: Optional[AttributeValue[int]] = None
59
+ logging_synchronous: Optional[AttributeValue[bool]] = None
60
+ transport_input: Optional[AttributeValue[str]] = None # default is SSH. Mostly used by Cisco.
61
61
 
62
62
  class FileLogging(BaseModel):
63
- name: str = None # object name
64
- filename: str = None # name of the file
65
- rotate: Optional[int] = None # How many versions to keep. Juniper specific.
66
- max_size: Optional[int] = None # Max size in bytes. Used both for Cisco and Juniper.
67
- min_size: Optional[int] = None # Min size in bytes. Only used for Cisco.
68
- facility: LoggingFacility # Type of log
69
- severity: LoggingSeverity # Severity level
63
+ name: Optional[AttributeValue[str]] = None # object name
64
+ filename: Optional[AttributeValue[str]] = None # name of the file
65
+ rotate: Optional[AttributeValue[int]] = None # How many versions to keep. Juniper specific.
66
+ max_size: Optional[AttributeValue[int]] = None # Max size in bytes. Used both for Cisco and Juniper.
67
+ min_size: Optional[AttributeValue[int]] = None # Min size in bytes. Only used for Cisco.
68
+ facility: Optional[AttributeValue[LoggingFacility]] = None # Type of log
69
+ severity: Optional[AttributeValue[LoggingSeverity]] = None # Severity level
70
70
 
71
71
  class LoggingEvent(BaseModel):
72
- enabled: bool
73
- severity: LoggingSeverity
72
+ enabled: Optional[AttributeValue[bool]] = None
73
+ severity: Optional[AttributeValue[LoggingSeverity]] = None
74
74
 
75
75
 
76
76
  class LoggingEvents(BaseModel):
77
- events: Optional[Dict[str, LoggingEvent]] = {}
77
+ events: Optional[Dict[str, LoggingEvent]] = None
@@ -4,27 +4,27 @@ from enum import Enum
4
4
  from typing import Optional, Dict
5
5
 
6
6
  class SpanningTreeGlobalAttributes(BaseModel):
7
- mode: Optional[str] = None # Needs to be defined by user. Default for Cisco is RAPID-PVST and for Juniper it's just RSTP
8
- bpdu_filter: Optional[bool] = False # Disabled by default
9
- bpdu_guard: Optional[bool] = False # Disabled by default
10
- loop_guard: Optional[bool] = False # Disabled by default
11
- portfast: Optional[bool] = False # Disabled by default. Global setting for access ports.
12
- bridge_assurance: Optional[bool] = False # Disabled by default. Only supported by MST and PVRST+
13
- #interfaces: Optional[Dict[str, Reference]] = {}
7
+ mode: Optional[AttributeValue[str]] = None # Needs to be defined by user. Default for Cisco is RAPID-PVST and for Juniper it's just RSTP
8
+ bpdu_filter: Optional[AttributeValue[bool]] = None # Disabled by default
9
+ bpdu_guard: Optional[AttributeValue[bool]] = None # Disabled by default
10
+ loop_guard: Optional[AttributeValue[bool]] = None # Disabled by default
11
+ portfast: Optional[AttributeValue[bool]] = None # Disabled by default. Global setting for access ports.
12
+ bridge_assurance: Optional[AttributeValue[bool]] = None # Disabled by default. Only supported by MST and PVRST+
13
+ #interfaces: Optional[Dict[str, Reference]] = None
14
14
 
15
15
  class SpanningTreeModeConfig(BaseModel):
16
- hello_time: Optional[int] = None
17
- max_age: Optional[int] = None
18
- forward_delay: Optional[int] = None
19
- bridge_priority: Optional[int] = None
20
- hold_count: Optional[int] = None # Range 1..10
16
+ hello_time: Optional[AttributeValue[int]] = None
17
+ max_age: Optional[AttributeValue[int]] = None
18
+ forward_delay: Optional[AttributeValue[int]] = None
19
+ bridge_priority: Optional[AttributeValue[int]] = None
20
+ hold_count: Optional[AttributeValue[int]] = None # Range 1..10
21
21
 
22
22
  ## RSTP
23
23
  class RstpAttributes(SpanningTreeModeConfig): ...
24
24
 
25
25
  class RSTPConfig(BaseModel):
26
26
  #config: RstpAttributes = RstpAttributes()
27
- config: Optional[Dict[str, RstpAttributes]] = {}
27
+ config: Optional[Dict[str, RstpAttributes]] = None
28
28
 
29
29
  ### MSTP
30
30
  class MstpInstanceAttributes(SpanningTreeModeConfig):
@@ -38,8 +38,8 @@ class MstpAttributes(SpanningTreeModeConfig):
38
38
 
39
39
  class MSTPConfig(BaseModel):
40
40
  #config: MstpAttributes = MstpAttributes()
41
- config: Optional[Dict[str, MstpAttributes]] = {}
42
- mst_instances: Optional[Dict[str, MstpInstanceAttributes]] = {}
41
+ config: Optional[Dict[str, MstpAttributes]] = None
42
+ mst_instances: Optional[Dict[str, MstpInstanceAttributes]] = None
43
43
 
44
44
  ### Rapid PVST
45
45
  class RapidPVSTAttributes(SpanningTreeModeConfig):
@@ -57,10 +57,10 @@ class RapidPVSTAttributes(SpanningTreeModeConfig):
57
57
  vlan: Optional[AttributeValue[int | list[int]]] = None # Single VLAN ID or list of VLANs using Rapid PVST+
58
58
 
59
59
  class RapidPVSTConfig(BaseModel):
60
- vlan: Optional[Dict[str, RapidPVSTAttributes]] = {}
60
+ vlan: Optional[Dict[str, RapidPVSTAttributes]] = None
61
61
 
62
62
  class SpanningTree(BaseModel):
63
- config: Optional[Dict[str, SpanningTreeGlobalAttributes]] = {}#SpanningTreeGlobalAttributes()
63
+ config: Optional[Dict[str, SpanningTreeGlobalAttributes]] = None#SpanningTreeGlobalAttributes()
64
64
  rstp: Optional[RSTPConfig] = RSTPConfig()
65
65
  mstp: Optional[MSTPConfig] = MSTPConfig()
66
66
  rapidpvst: Optional[RapidPVSTConfig] = RapidPVSTConfig()
File without changes