Update project description and enhance documentation for clarity
- Revised the project description in `pyproject.toml` to better reflect the functionality of the `oxipy` client. - Improved the README.md by adding detailed explanations of the project structure, installation instructions, and usage examples. - Updated documentation files to enhance clarity and organization, including sections on extending models and writing TTP templates. - Adjusted various TTP templates to ensure consistency and accuracy in the parsing of device configurations.
This commit is contained in:
@@ -24,10 +24,7 @@ class BaseDevice(ABC):
|
||||
@abstractmethod
|
||||
def template(self) -> str:
|
||||
"""
|
||||
Expected structure:
|
||||
Название файла с парсером ttp
|
||||
Returns:
|
||||
None
|
||||
Name of the TTP template file used by this device parser.
|
||||
"""
|
||||
|
||||
def vlans(self) -> list[dict]:
|
||||
@@ -35,14 +32,14 @@ class BaseDevice(ABC):
|
||||
Parse VLAN configuration from self.raw['vlans'].
|
||||
|
||||
Expected structure:
|
||||
[{"id": 10, "description": "MGMT"}, {"id": 15, "name": "SSH"}, ...]
|
||||
[{"vlan_id": 10, "description": "MGMT"}, {"vlan_id": 15, "name": "SSH"}, ...]
|
||||
|
||||
Returns:
|
||||
list[Vlans]: список VLAN из секции vlans,
|
||||
пустой список если секция отсутствует.
|
||||
list[Vlans]: VLANs from the vlans section, or an empty list
|
||||
when the section is absent.
|
||||
|
||||
Raises:
|
||||
ValueError: если raw содержит некорректные данные.
|
||||
ValueError: if raw data cannot be validated by the contract.
|
||||
"""
|
||||
return self.raw.get("vlans", [])
|
||||
|
||||
@@ -51,10 +48,10 @@ class BaseDevice(ABC):
|
||||
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"}]
|
||||
[{"interface": "GEthernet1/0/1", "ip_address": "192.168.1.1", "mask": "24", "description": "IPBB interface"}]
|
||||
|
||||
Raises:
|
||||
ValueError: если raw содержит некорректные данные.
|
||||
ValueError: if raw data cannot be validated by the contract.
|
||||
"""
|
||||
return self.raw.get("interfaces", [])
|
||||
|
||||
@@ -66,7 +63,7 @@ class BaseDevice(ABC):
|
||||
{"model":"RB951Ui-2nD", serial_number: "B88C0B31117B", "version": "7.12.1"}
|
||||
|
||||
Raises:
|
||||
ValueError: если raw содержит некорректные данные.
|
||||
ValueError: if raw data cannot be validated by the contract.
|
||||
"""
|
||||
return self.raw.get("system", None)
|
||||
|
||||
@@ -97,14 +94,14 @@ class BaseDevice(ABC):
|
||||
return result
|
||||
|
||||
def _load_template(self):
|
||||
"""Подгрузка темплейтов из папки models/templates"""
|
||||
"""Load the device TTP template from 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:
|
||||
"""Проверяем только обязательные группы в template."""
|
||||
"""Validate that the template declares all required groups."""
|
||||
try:
|
||||
root = ET.fromstring(self._loaded_template)
|
||||
except ET.ParseError:
|
||||
@@ -122,7 +119,7 @@ class BaseDevice(ABC):
|
||||
)
|
||||
|
||||
def _run_ttp(self) -> dict:
|
||||
"""Основной парсер"""
|
||||
"""Run the node-not-found check and then parse the config with TTP."""
|
||||
pattern = """node not {{found}}"""
|
||||
parser = ttp(data=self.config, template=pattern)
|
||||
parser.parse()
|
||||
|
||||
@@ -2,6 +2,30 @@ from oxi.interfaces import register_parser
|
||||
from oxi.interfaces.base import BaseDevice
|
||||
|
||||
|
||||
def _expand_vlan_range(value: str | list[str]) -> list[str]:
|
||||
if isinstance(value, list):
|
||||
value = ",".join(str(item) for item in value)
|
||||
|
||||
result: list[str] = []
|
||||
for part in value.split(","):
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
if "-" not in part:
|
||||
result.append(part)
|
||||
continue
|
||||
start_s, end_s = part.split("-", 1)
|
||||
try:
|
||||
start, end = int(start_s), int(end_s)
|
||||
except ValueError:
|
||||
result.append(part)
|
||||
continue
|
||||
if start > end:
|
||||
start, end = end, start
|
||||
result.extend(str(vlan_id) for vlan_id in range(start, end + 1))
|
||||
return result
|
||||
|
||||
|
||||
@register_parser("eltex")
|
||||
class Eltex(BaseDevice):
|
||||
template = "eltex.ttp"
|
||||
@@ -17,22 +41,23 @@ class Eltex(BaseDevice):
|
||||
|
||||
def vlans(self) -> list[dict]:
|
||||
vlans_ttp = self.raw.get("vlans", [])
|
||||
vlans = []
|
||||
named_vlan = set()
|
||||
vlans: list[dict] = []
|
||||
named_vlan: set[str] = set()
|
||||
for item in vlans_ttp:
|
||||
if item.get("vlan_id"):
|
||||
named_vlan.add(item.get("vlan_id"))
|
||||
vlan_id = item.get("vlan_id")
|
||||
if vlan_id:
|
||||
named_vlan.add(str(vlan_id))
|
||||
vlans.append(item)
|
||||
else:
|
||||
ids = item.get("vlan_ids", "")
|
||||
tail = item.get("vlan_tail")
|
||||
if tail:
|
||||
ids = f"{ids},{tail}"
|
||||
for vid in ids:
|
||||
vid = vid.strip()
|
||||
if vid in named_vlan:
|
||||
continue
|
||||
vlans.append({"vlan_id": vid})
|
||||
continue
|
||||
|
||||
ids = item.get("vlan_ids", "")
|
||||
tail = item.get("vlan_tail")
|
||||
if tail:
|
||||
ids = [*ids, tail] if isinstance(ids, list) else f"{ids},{tail}"
|
||||
for vid in _expand_vlan_range(ids):
|
||||
if vid in named_vlan:
|
||||
continue
|
||||
vlans.append({"vlan_id": vid})
|
||||
return vlans
|
||||
|
||||
|
||||
|
||||
@@ -6,15 +6,17 @@ class H3C(BaseDevice):
|
||||
template = "h3c.ttp"
|
||||
|
||||
def vlans(self) -> list[dict]:
|
||||
vlan_list = self.raw["vlans"]
|
||||
vlans = []
|
||||
vlan_list = self.raw.get("vlans", [])
|
||||
vlans: list[dict] = []
|
||||
for item in vlan_list:
|
||||
if item.get("vlans_id"):
|
||||
vlans.extend([{'vlan_id': vln }for vln in item.get("vlans_id")])
|
||||
else:
|
||||
vlan_ids = item.get("vlans_id")
|
||||
if not vlan_ids:
|
||||
vlans.append(item)
|
||||
continue
|
||||
vlans.extend({"vlan_id": vlan_id} for vlan_id in vlan_ids)
|
||||
return vlans
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./test5.txt") as file:
|
||||
data = file.read()
|
||||
|
||||
@@ -6,8 +6,8 @@ from oxi.interfaces.base import BaseDevice
|
||||
class Huawei(BaseDevice):
|
||||
template = "huawei.ttp"
|
||||
|
||||
def vlans(self):
|
||||
vlan_ids = self.raw["vlans"].get("vlan_ids")
|
||||
def vlans(self) -> list[dict]:
|
||||
vlan_ids = self.raw.get("vlans", {}).get("vlan_ids", [])
|
||||
return [{"vlan_id": vlan} for vlan in vlan_ids]
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import os
|
||||
from oxi.interfaces import register_parser
|
||||
from oxi.interfaces.base import BaseDevice
|
||||
|
||||
|
||||
def _expand_vlan_range(value: str) -> list[str]:
|
||||
"""Разворачивает строку вида '1,7,14-15,200-205' в список ['1','7','14','15',...]."""
|
||||
def _expand_vlan_range(value: str | list[str]) -> list[str]:
|
||||
"""Expand values like '1,7,14-15' into individual VLAN IDs."""
|
||||
if isinstance(value, list):
|
||||
value = ",".join(str(item) for item in value)
|
||||
|
||||
result: list[str] = []
|
||||
if not value:
|
||||
return result
|
||||
@@ -31,12 +33,8 @@ def _expand_vlan_range(value: str) -> list[str]:
|
||||
class Qtech(BaseDevice):
|
||||
template = "qtech.ttp"
|
||||
|
||||
def system(self) -> dict:
|
||||
system = self.raw["system"]
|
||||
return system
|
||||
|
||||
def vlans(self) -> list[dict]:
|
||||
vlans_ttp = self.raw["vlans"]
|
||||
vlans_ttp = self.raw.get("vlans", [])
|
||||
vlans: list[dict] = []
|
||||
named_vlan: set[str] = set()
|
||||
for item in vlans_ttp:
|
||||
@@ -49,7 +47,7 @@ class Qtech(BaseDevice):
|
||||
ids = item.get("vlan_ids") or vlan_id or ""
|
||||
tail = item.get("vlan_tail")
|
||||
if tail:
|
||||
ids = f"{ids},{tail}"
|
||||
ids = [*ids, tail] if isinstance(ids, list) else f"{ids},{tail}"
|
||||
for vid in _expand_vlan_range(ids):
|
||||
if vid in named_vlan:
|
||||
continue
|
||||
@@ -58,7 +56,6 @@ class Qtech(BaseDevice):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(os.path.abspath(os.curdir))
|
||||
with open("./test3.txt") as file:
|
||||
data = file.read()
|
||||
qtech = Qtech(data)
|
||||
|
||||
@@ -6,9 +6,9 @@ class Quasar(BaseDevice):
|
||||
template = "quasar.ttp"
|
||||
|
||||
def interfaces(self) -> list[dict]:
|
||||
ether_interfaces: dict = self.raw["interfaces"]
|
||||
ether_interface: dict = self.raw.get("interfaces", {})
|
||||
interfaces: list[dict] = []
|
||||
bulk_interfaces: dict = self.raw["bulkinterfaces"]
|
||||
bulk_interfaces: dict = self.raw.get("bulkinterfaces", {})
|
||||
for key, value in bulk_interfaces.items():
|
||||
interfaces.append(
|
||||
{
|
||||
@@ -18,7 +18,8 @@ class Quasar(BaseDevice):
|
||||
"mask": value.get("mask"),
|
||||
}
|
||||
)
|
||||
interfaces.append(ether_interfaces)
|
||||
if ether_interface:
|
||||
interfaces.append(ether_interface)
|
||||
return interfaces
|
||||
|
||||
|
||||
|
||||
@@ -1,41 +1,20 @@
|
||||
<doc>
|
||||
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
|
||||
в <vendor>.ttp и заполните группы под формат конфигурации вашего устройства.
|
||||
Base template for a new device parser. Copy this file, rename it to
|
||||
<vendor>.ttp, and fill the groups for the target configuration format.
|
||||
|
||||
Обязательные группы: system, interfaces.
|
||||
Опциональная группа: vlans — добавляйте только если устройство поддерживает VLAN.
|
||||
Required groups: system, interfaces.
|
||||
Optional group: vlans. Add it only when VLAN parsing is implemented.
|
||||
|
||||
--- Группа system ---
|
||||
Должна возвращать одиночный словарь с полями:
|
||||
model (str) — модель устройства
|
||||
serial_number (str) — серийный номер
|
||||
version (str) — версия прошивки
|
||||
system must return one dictionary with: model, serial_number, version.
|
||||
interfaces must return a list of dictionaries with: interface, ip_address,
|
||||
mask, description. Use a prefix length for mask; convert dotted decimal masks
|
||||
with `to_cidr` or in the device class.
|
||||
vlans must return dictionaries with vlan_id and optional name/description.
|
||||
|
||||
--- Группа interfaces ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
interface (str) — имя интерфейса (alias поля name)
|
||||
ip_address (str|None) — IPv4-адрес
|
||||
mask (int|None) — длина префикса (напр. 24)
|
||||
description (str|None) — описание интерфейса
|
||||
Useful TTP modifiers: ORPHRASE, _start_, strip(), replace(), exclude(),
|
||||
ignore, ignore('.*'), to_cidr, unrange(), split().
|
||||
|
||||
Если устройство возвращает маску в виде 255.255.255.0, конвертируйте
|
||||
её в prefix length в методе interfaces() класса устройства.
|
||||
|
||||
--- Группа vlans ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
id (int) — номер VLAN (alias поля vlan_id)
|
||||
description (str|None) — название VLAN (alias поля name)
|
||||
|
||||
--- Полезные модификаторы TTP ---
|
||||
{{ field | ORPHRASE }} — одно слово или фраза до конца строки
|
||||
{{ field | _start_ }} — начало новой записи группы
|
||||
{{ field | strip('"') }} — убрать кавычки
|
||||
{{ field | replace("yes","True") }} — замена подстроки
|
||||
{{ field | exclude("pattern") }} — пропустить строку при совпадении
|
||||
{{ ignore }} — захватить и выбросить значение
|
||||
{{ ignore('.*') }} — выбросить всё до конца строки
|
||||
|
||||
Подробнее: docs/templates.md
|
||||
See docs/templates.md for details.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
<doc>
|
||||
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
|
||||
в <vendor>.ttp и заполните группы под формат конфигурации вашего устройства.
|
||||
Eltex configuration parser.
|
||||
|
||||
Обязательные группы: system, interfaces.
|
||||
Опциональная группа: vlans — добавляйте только если устройство поддерживает VLAN.
|
||||
|
||||
--- Группа system ---
|
||||
Должна возвращать одиночный словарь с полями:
|
||||
model (str) — модель устройства
|
||||
serial_number (str) — серийный номер
|
||||
version (str) — версия прошивки
|
||||
|
||||
--- Группа interfaces ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
interface (str) — имя интерфейса (alias поля name)
|
||||
ip_address (str|None) — IPv4-адрес
|
||||
mask (int|None) — длина префикса (напр. 24)
|
||||
description (str|None) — описание интерфейса
|
||||
|
||||
Если устройство возвращает маску в виде 255.255.255.0, конвертируйте
|
||||
её в prefix length в методе interfaces() класса устройства.
|
||||
|
||||
--- Группа vlans ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
id (int) — номер VLAN (alias поля vlan_id)
|
||||
description (str|None) — название VLAN (alias поля name)
|
||||
|
||||
--- Полезные модификаторы TTP ---
|
||||
{{ field | ORPHRASE }} — одно слово или фраза до конца строки
|
||||
{{ field | _start_ }} — начало новой записи группы
|
||||
{{ field | strip('"') }} — убрать кавычки
|
||||
{{ field | replace("yes","True") }} — замена подстроки
|
||||
{{ field | exclude("pattern") }} — пропустить строку при совпадении
|
||||
{{ ignore }} — захватить и выбросить значение
|
||||
{{ ignore('.*') }} — выбросить всё до конца строки
|
||||
|
||||
Подробнее: docs/templates.md
|
||||
The system group reads software version data and the serial group extracts
|
||||
serial numbers from the unit table. The interfaces group parses interface IP
|
||||
settings. The vlans group supports named VLAN interfaces and compressed VLAN
|
||||
lists.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
@@ -1,41 +1,9 @@
|
||||
<doc>
|
||||
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
|
||||
в <vendor>.ttp и заполните группы под формат конфигурации вашего устройства.
|
||||
H3C configuration parser.
|
||||
|
||||
Обязательные группы: system, interfaces.
|
||||
Опциональная группа: vlans — добавляйте только если устройство поддерживает VLAN.
|
||||
|
||||
--- Группа system ---
|
||||
Должна возвращать одиночный словарь с полями:
|
||||
model (str) — модель устройства
|
||||
serial_number (str) — серийный номер
|
||||
version (str) — версия прошивки
|
||||
|
||||
--- Группа interfaces ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
interface (str) — имя интерфейса (alias поля name)
|
||||
ip_address (str|None) — IPv4-адрес
|
||||
mask (int|None) — длина префикса (напр. 24)
|
||||
description (str|None) — описание интерфейса
|
||||
|
||||
Если устройство возвращает маску в виде 255.255.255.0, конвертируйте
|
||||
её в prefix length в методе interfaces() класса устройства.
|
||||
|
||||
--- Группа vlans ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
id (int) — номер VLAN (alias поля vlan_id)
|
||||
description (str|None) — название VLAN (alias поля name)
|
||||
|
||||
--- Полезные модификаторы TTP ---
|
||||
{{ field | ORPHRASE }} — одно слово или фраза до конца строки
|
||||
{{ field | _start_ }} — начало новой записи группы
|
||||
{{ field | strip('"') }} — убрать кавычки
|
||||
{{ field | replace("yes","True") }} — замена подстроки
|
||||
{{ field | exclude("pattern") }} — пропустить строку при совпадении
|
||||
{{ ignore }} — захватить и выбросить значение
|
||||
{{ ignore('.*') }} — выбросить всё до конца строки
|
||||
|
||||
Подробнее: docs/templates.md
|
||||
The system group reads boot image version and board model data. The interfaces
|
||||
group parses interface IP settings. The vlans groups parse both named VLANs and
|
||||
range-style VLAN declarations.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
@@ -1,40 +1,9 @@
|
||||
<doc>
|
||||
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
|
||||
в <vendor>.ttp и заполните группы под формат конфигурации вашего устройства.
|
||||
Huawei VRP configuration parser.
|
||||
|
||||
Обязательные группы: system, interfaces.
|
||||
Опциональная группа: vlans — добавляйте только если устройство поддерживает VLAN.
|
||||
|
||||
--- Группа system ---
|
||||
Должна возвращать одиночный словарь с полями:
|
||||
model (str) — модель устройства
|
||||
serial_number (str) — серийный номер
|
||||
version (str) — версия прошивки
|
||||
|
||||
--- Группа interfaces ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
interface (str) — имя интерфейса (alias поля name)
|
||||
ip_address (str|None) — IPv4-адрес
|
||||
mask (int|None) — длина префикса (напр. 24)
|
||||
description (str|None) — описание интерфейса
|
||||
|
||||
Если устройство возвращает маску в виде 255.255.255.0, конвертируйте
|
||||
её в prefix length в методе interfaces() класса устройства.
|
||||
|
||||
--- Группа vlans ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
id (int) — номер VLAN (alias поля vlan_id)
|
||||
description (str|None) — название VLAN (alias поля name)
|
||||
|
||||
--- Полезные модификаторы TTP ---
|
||||
{{ field | ORPHRASE }} — одно слово или фраза до конца строки
|
||||
{{ field | _start_ }} — начало новой записи группы
|
||||
{{ field | strip('"') }} — убрать кавычки
|
||||
{{ field | replace("yes","True") }} — замена подстроки
|
||||
{{ field | exclude("pattern") }} — пропустить строку при совпадении
|
||||
{{ ignore }} — захватить и выбросить значение
|
||||
{{ ignore('.*') }} — выбросить всё до конца строки
|
||||
Подробнее: docs/templates.md
|
||||
The system group reads VRP version and slot ESN data. The interfaces group
|
||||
parses interface blocks and converts dotted decimal masks to prefix lengths.
|
||||
The vlans group parses `vlan batch` declarations and emits VLAN IDs.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
<doc>
|
||||
Qtech switch configuration parser.
|
||||
|
||||
The system group reads the model, serial number, and build number. For Qtech,
|
||||
system.version intentionally stores the build number from lines like
|
||||
`Version 2.2.0C Build 96279`.
|
||||
|
||||
The interfaces group parses CLI interface blocks and converts dotted decimal
|
||||
masks to prefix lengths. The vlans group supports named VLANs, comma-separated
|
||||
VLAN lists, ranges, and continuation lines.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
<doc>
|
||||
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
|
||||
в <vendor>.ttp и заполните группы под формат конфигурации вашего устройства.
|
||||
Quasar configuration parser.
|
||||
|
||||
Обязательные группы: system, interfaces.
|
||||
Опциональная группа: vlans — добавляйте только если устройство поддерживает VLAN.
|
||||
|
||||
--- Группа system ---
|
||||
Должна возвращать одиночный словарь с полями:
|
||||
model (str) — модель устройства
|
||||
serial_number (str) — серийный номер
|
||||
version (str) — версия прошивки
|
||||
|
||||
--- Группа interfaces ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
interface (str) — имя интерфейса (alias поля name)
|
||||
ip_address (str|None) — IPv4-адрес
|
||||
mask (int|None) — длина префикса (напр. 24)
|
||||
description (str|None) — описание интерфейса
|
||||
|
||||
Если устройство возвращает маску в виде 255.255.255.0, конвертируйте
|
||||
её в prefix length в методе interfaces() класса устройства.
|
||||
|
||||
--- Группа vlans ---
|
||||
Должна возвращать список словарей. Каждый элемент:
|
||||
id (int) — номер VLAN (alias поля vlan_id)
|
||||
description (str|None) — название VLAN (alias поля name)
|
||||
|
||||
--- Полезные модификаторы TTP ---
|
||||
{{ field | ORPHRASE }} — одно слово или фраза до конца строки
|
||||
{{ field | _start_ }} — начало новой записи группы
|
||||
{{ field | strip('"') }} — убрать кавычки
|
||||
{{ field | replace("yes","True") }} — замена подстроки
|
||||
{{ field | exclude("pattern") }} — пропустить строку при совпадении
|
||||
{{ ignore }} — захватить и выбросить значение
|
||||
{{ ignore('.*') }} — выбросить всё до конца строки
|
||||
|
||||
Подробнее: docs/templates.md
|
||||
The system group supports Assembly-based and Engine-based firmware blocks. The
|
||||
interfaces group parses the management Ethernet address, while bulkinterfaces
|
||||
collects per-port descriptions that the Python model merges into interface
|
||||
records.
|
||||
</doc>
|
||||
<vars>
|
||||
default_system = {
|
||||
|
||||
Reference in New Issue
Block a user