Files
oxipy/oxi/interfaces/base.py
IluaAir 3635a07b27 Refactor BaseDevice methods for improved data handling and validation
- Updated the `BaseDevice` class to replace `_raw` with `raw` for consistency in data access.
- Enhanced the `vlans`, `interfaces`, and `system` methods to utilize the new `raw` attribute.
- Introduced a `_validate_contract` method to streamline the validation of parsed data into structured models.
- Adjusted the `Keenetic` and `Mikrotik` models to align with the updated data handling approach, ensuring proper parsing and decoding of interface and VLAN data.
2026-02-22 09:52:17 +03:00

118 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from abc import ABC, abstractmethod
from pathlib import Path
from ttp import ttp
from oxi.interfaces.contract import Device
import xml.etree.ElementTree as ET
from oxi.interfaces.contract import Interfaces, System, Vlans
class BaseDevice(ABC):
_REQUIRED_SECTIONS: frozenset[str] = frozenset({"system", "interfaces"})
_OPTIONAL_SECTIONS: frozenset[str] = frozenset({"vlans"})
def __init__(self, config: str):
self.config: str = config
self._loaded_template = self._load_template()
self._validate_template_groups()
self.raw: dict = self._run_ttp()
@property
@abstractmethod
def template(self) -> str:
"""
Returns:
Название файла с парсером ttp
"""
def vlans(self) -> list[dict]:
"""
Parse VLAN configuration from self.raw['vlans'].
Expected raw structure:
[{"id": 10, "description": "MGMT"}, {"id": 15, "name": "SSH"}, ...]
Returns:
list[Vlans]: список VLAN из секции vlans,
пустой список если секция отсутствует.
Raises:
ValueError: если raw содержит некорректные данные.
"""
return self.raw.get("vlans", [])
def interfaces(self) -> list[dict]:
"""
Parse Interface configuration from self.raw['interfaces'].
Expected raw structure:
[{"name": "GEthernet1/0/1", "ip_address": "192.168.1.1", "mask": "24", "description": "IPBB interface"}]
Raises:
ValueError: если raw содержит некорректные данные.
"""
return self.raw.get("interfaces", [])
def system(self) -> dict:
"""
Parse System configuration from self.raw['system'].
Expected raw structure:
{"model":"RB951Ui-2nD", serial_number: "B88C0B31117B", "version": "7.12.1"}
Raises:
ValueError: если raw содержит некорректные данные.
"""
return self.raw.get("system", None)
def _validate_contract(self):
optional_vlans = self.vlans()
if optional_vlans:
optional_vlans = [Vlans(**item) for item in optional_vlans]
return {
"system": System(**self.system()),
"interfaces": [Interfaces(**items) for items in self.interfaces()],
"vlans": optional_vlans,
}
def _load_template(self):
"""Подгрузка темплейтов из папки models/templates"""
path = Path(__file__).parent / "models" / "templates" / self.template
if not path.exists():
raise FileNotFoundError(f"Template {self.template} not found")
return path.read_text(encoding="utf-8")
def _validate_template_groups(self) -> None:
"""Проверка что TTP темлпейт имеет декларированные группы для всех требуемых и опциональных секций"""
try:
root = ET.fromstring(self._loaded_template)
except ET.ParseError:
root = ET.fromstring(f"<template>{self._loaded_template}</template>")
declared = {g.get("name") for g in root.iter("group") if g.get("name")}
expected = self._REQUIRED_SECTIONS | self._OPTIONAL_SECTIONS
missing = expected - declared
if missing:
raise ValueError(
f"{self.__class__.__name__}: template '{self.template}' "
f"missing group declarations: {sorted(missing)}. "
f"Declared groups: {sorted(declared)}"
)
def _run_ttp(self) -> dict:
"""Основной парсер"""
p = ttp(data=self.config, template=self._loaded_template)
p.parse()
raw: dict = p.result()[0][0]
missing = self._REQUIRED_SECTIONS - raw.keys()
if missing:
raise ValueError(
f"{self.__class__.__name__}: TTP template '{self.template}' "
f"did not produce required sections: {sorted(missing)}. "
f"Got: {(raw.keys())}"
)
return raw
def parse(self) -> Device:
return Device(**self._validate_contract())