- Added a repository URL section in `pyproject.toml` to link to the GitHub repository. - Updated installation instructions in `README.md` to reflect the change from Gitea to GitHub as the source for package installation, removing references to the private Gitea Package Registry.
263 lines
6.6 KiB
Markdown
263 lines
6.6 KiB
Markdown
# oxipy
|
|
|
|
`oxipy` is a Python client for the [Oxidized](https://github.com/ytti/oxidized) API.
|
|
It fetches device configurations from Oxidized and parses them into structured
|
|
Pydantic models using bundled TTP templates.
|
|
|
|
Oxidized remains responsible for collecting and storing configuration backups.
|
|
`oxipy` focuses on consuming those backups from Python code and exposing common
|
|
configuration sections such as system data, interfaces, and VLANs.
|
|
|
|
## Contents
|
|
|
|
- [Installation](#installation)
|
|
- [Quick Start](#quick-start)
|
|
- [API Reference](#api-reference)
|
|
- [OxiAPI](#oxiapi)
|
|
- [NodeView](#nodeview)
|
|
- [NodeConfig](#nodeconfig)
|
|
- [ModelView](#modelview)
|
|
- [Supported Devices](#supported-devices)
|
|
- [Additional Documentation](#additional-documentation)
|
|
|
|
## Installation
|
|
|
|
The package is distributed from the source repository. It is not published to
|
|
PyPI yet.
|
|
|
|
**Requirements:** Python 3.10+
|
|
|
|
### From GitHub Source
|
|
|
|
Install directly from the repository:
|
|
|
|
```bash
|
|
pip install git+https://github.com/sttarsky/oxipy.git
|
|
```
|
|
|
|
Install a specific tag or branch:
|
|
|
|
```bash
|
|
pip install git+https://github.com/sttarsky/oxipy.git@v0.1.0
|
|
pip install git+https://github.com/sttarsky/oxipy.git@dev
|
|
```
|
|
|
|
For local development:
|
|
|
|
```bash
|
|
git clone https://github.com/sttarsky/oxipy
|
|
cd oxipy
|
|
pip install -e .
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```python
|
|
from oxi import OxiAPI
|
|
|
|
api = OxiAPI(url="https://oxi.example.com", verify=False)
|
|
|
|
node = api.node("Router_HOME")
|
|
|
|
print(node.ip)
|
|
print(node.model)
|
|
print(node.full_name)
|
|
|
|
print(node.config.system.model)
|
|
print(node.config.interfaces.dump_json())
|
|
print(node.config.vlans.dump_json())
|
|
```
|
|
|
|
Example output:
|
|
|
|
```text
|
|
192.168.1.1
|
|
keenetic
|
|
router/HQ
|
|
Sprinter (KN-3710)
|
|
[
|
|
{"interface": "Bridge1", "ip_address": "192.168.1.1", "mask": 24, "description": "Guest network"},
|
|
{"interface": "Bridge0", "ip_address": "172.16.1.1", "mask": 24, "description": "Home network"}
|
|
]
|
|
[
|
|
{"vlan_id": 1, "description": "Home VLAN"},
|
|
{"vlan_id": 2, "description": "Ethernet uplink"},
|
|
{"vlan_id": 3, "description": "Home network"}
|
|
]
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### OxiAPI
|
|
|
|
`OxiAPI` is the entry point. It manages the HTTP session and provides access to
|
|
Oxidized nodes.
|
|
|
|
```python
|
|
OxiAPI(
|
|
url: str,
|
|
username: str | None = None,
|
|
password: str | None = None,
|
|
verify: bool = True,
|
|
)
|
|
```
|
|
|
|
| Parameter | Type | Description |
|
|
| --- | --- | --- |
|
|
| `url` | `str` | Base URL of the Oxidized API, for example `https://oxi.example.com`. |
|
|
| `username` | `str | None` | Optional username for HTTP basic authentication. |
|
|
| `password` | `str | None` | Optional password for HTTP basic authentication. |
|
|
| `verify` | `bool` | Whether to verify TLS certificates. Defaults to `True`. |
|
|
|
|
Example:
|
|
|
|
```python
|
|
# Without authentication
|
|
api = OxiAPI(url="https://oxi.example.com")
|
|
|
|
# With HTTP basic authentication
|
|
api = OxiAPI(
|
|
url="https://oxi.example.com",
|
|
username="admin",
|
|
password="secret",
|
|
)
|
|
|
|
# As a context manager. The HTTP session is closed automatically.
|
|
with OxiAPI(url="https://oxi.example.com") as api:
|
|
node = api.node("HQ")
|
|
print(node.ip)
|
|
```
|
|
|
|
#### `api.node(name)`
|
|
|
|
Returns a `NodeView` for the requested Oxidized node.
|
|
|
|
```python
|
|
node = api.node("HQ")
|
|
```
|
|
|
|
### NodeView
|
|
|
|
`NodeView` represents one network device. It contains metadata returned by
|
|
Oxidized and lazy access to the fetched configuration.
|
|
|
|
| Property | Type | Description |
|
|
| --- | --- | --- |
|
|
| `ip` | `str` | Node IP address. |
|
|
| `full_name` | `str` | Full node name in Oxidized. |
|
|
| `group` | `str` | Oxidized group the node belongs to. |
|
|
| `model` | `str` | Device model key used to select a parser. |
|
|
| `config` | `NodeConfig` | Device configuration, fetched and parsed on first access. |
|
|
|
|
Example:
|
|
|
|
```python
|
|
node = api.node("HQ")
|
|
|
|
print(node.ip)
|
|
print(node.group)
|
|
print(node.model)
|
|
```
|
|
|
|
### NodeConfig
|
|
|
|
`NodeConfig` fetches and parses a device configuration. The parser is selected
|
|
from the device registry by the node `model` value returned by Oxidized.
|
|
|
|
Configuration sections are exposed through properties that return `ModelView`
|
|
objects.
|
|
|
|
| Property | Returns | Description |
|
|
| --- | --- | --- |
|
|
| `system` | `ModelView[System]` | System information. |
|
|
| `interfaces` | `ModelView[list[Interfaces]]` | Parsed interface list. |
|
|
| `vlans` | `ModelView[list[Vlans]]` | Parsed VLAN list, if the template provides VLAN data. |
|
|
| `text` | `str` | Raw configuration text fetched from Oxidized. |
|
|
|
|
Example:
|
|
|
|
```python
|
|
cfg = node.config
|
|
|
|
print(cfg.system.model)
|
|
print(cfg.system.serial_number)
|
|
print(cfg.system.version)
|
|
|
|
for iface in cfg.interfaces:
|
|
print(iface.name, iface.ip_address, iface.mask)
|
|
|
|
first_iface = cfg.interfaces[0]
|
|
print(first_iface.name)
|
|
print(len(cfg.interfaces))
|
|
|
|
print(cfg.interfaces.dump_json())
|
|
print(cfg.vlans.dump_json())
|
|
print(cfg.system.dump_json())
|
|
|
|
print(cfg.text)
|
|
```
|
|
|
|
`NodeConfig` also provides `dump()` and `dump_json()` methods for the whole
|
|
parsed device object.
|
|
|
|
### ModelView
|
|
|
|
`ModelView` wraps either a single Pydantic model or a list of Pydantic models.
|
|
It provides serialization, iteration for list sections, and transparent access
|
|
to model attributes.
|
|
|
|
| Method / operation | Applies to | Description |
|
|
| --- | --- | --- |
|
|
| `.dump()` | single model and list | Returns a Python `dict` or `list` using aliases. |
|
|
| `.dump_json()` | single model and list | Returns a JSON string using aliases. |
|
|
| `.<attr>` | single model and list | Proxies attribute access to the wrapped model. |
|
|
| `iter(view)` | list only | Iterates over wrapped models. |
|
|
| `len(view)` | list only | Returns the number of wrapped models. |
|
|
| `view[i]` | list only | Returns an item or slice. |
|
|
|
|
`__iter__`, `__len__`, and `__getitem__` are available only for list-backed
|
|
sections such as `interfaces` and `vlans`. Calling them on `system` raises
|
|
`TypeError`.
|
|
|
|
Examples:
|
|
|
|
```python
|
|
system = node.config.system
|
|
print(system.dump_json())
|
|
print(system.model)
|
|
print(system.serial_number)
|
|
|
|
interfaces = node.config.interfaces
|
|
|
|
for iface in interfaces:
|
|
print(iface.name, iface.ip_address)
|
|
|
|
print(len(interfaces))
|
|
print(interfaces[0])
|
|
print(interfaces[:3])
|
|
print(interfaces.dump())
|
|
```
|
|
|
|
## Supported Devices
|
|
|
|
Registry keys are compared with the Oxidized node `model` value
|
|
case-insensitively.
|
|
|
|
| Device | Registry keys |
|
|
| --- | --- |
|
|
| Keenetic | `ndms`, `keenetic`, `keeneticos` |
|
|
| MikroTik | `routeros`, `ros`, `mikrotik` |
|
|
| Qtech | `qtech` |
|
|
| Huawei | `huawei`, `vrp` |
|
|
| Eltex | `eltex` |
|
|
| H3C | `h3c` |
|
|
| Quasar | `qos`, `quasar` |
|
|
|
|
You can add support for another device family by creating a new device model
|
|
and TTP template. See [Extending Device Models](docs/extending-models.md).
|
|
|
|
## Additional Documentation
|
|
|
|
- [Writing TTP Templates](docs/templates.md)
|
|
- [Extending Device Models](docs/extending-models.md)
|