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:
IluaAir
2026-05-25 16:01:38 +03:00
parent e8c33b0e64
commit 41c4cc48e9
17 changed files with 524 additions and 642 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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]

View File

@@ -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)

View File

@@ -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

View File

@@ -1,41 +1,20 @@
<doc>
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
в &lt;vendor&gt;.ttp и заполните группы под формат конфигурации вашего устройства.
Base template for a new device parser. Copy this file, rename it to
&lt;vendor&gt;.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 = {

View File

@@ -1,41 +1,10 @@
<doc>
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
в &lt;vendor&gt;.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 = {

View File

@@ -1,41 +1,9 @@
<doc>
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
в &lt;vendor&gt;.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 = {

View File

@@ -1,40 +1,9 @@
<doc>
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
в &lt;vendor&gt;.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 = {

View File

@@ -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 = {

View File

@@ -1,41 +1,10 @@
<doc>
Базовый шаблон для нового устройства. Скопируйте этот файл, переименуйте
в &lt;vendor&gt;.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 = {