dev #3

Merged
gitark merged 84 commits from dev into main 2026-06-12 16:00:25 +03:00
19 changed files with 76 additions and 37 deletions
Showing only changes of commit 3c0e70b320 - Show all commits

2
.gitignore vendored
View File

@@ -10,6 +10,6 @@ main.py
.venv
.idea
.DS_Store
.vscode
# etc files
*.txt

View File

@@ -1,6 +1,5 @@
from .core import OxiAPI
__all__ = [
"OxiAPI",
]

View File

@@ -1,4 +1,3 @@
from typing import Optional
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
@@ -6,7 +5,7 @@ from urllib3.util import Retry
class OxiAdapter(HTTPAdapter):
def __init__(
self,
timeout: Optional[int] = None,
timeout: int | None = None,
max_retries: int = 3,
*args,
**kwargs,

View File

@@ -1,6 +1,7 @@
from functools import cached_property
import json
from typing import TYPE_CHECKING, Generic, Iterator, TypeVar
from collections.abc import Iterator
from functools import cached_property
from typing import TYPE_CHECKING, Generic, TypeVar
from pydantic import BaseModel

View File

@@ -1,8 +1,8 @@
from typing import Optional
from requests import HTTPError, Session
from oxi.adapter import OxiAdapter
from oxi.exception import OxiAPIError
from .node import Node
@@ -10,8 +10,8 @@ class OxiAPI:
def __init__(
self,
url: str,
username: Optional[str] = None,
password: Optional[str] = None,
username: str | None = None,
password: str | None = None,
verify: bool = True,
):
self.base_url = url.rstrip("/")
@@ -20,8 +20,8 @@ class OxiAPI:
def __create_session(
self,
username: Optional[str] = None,
password: Optional[str] = None,
username: str | None = None,
password: str | None = None,
verify: bool = True,
) -> Session:
session = Session()

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from requests import HTTPError
@@ -35,7 +35,7 @@ def _looks_like_node_not_found_html(e: "HTTPError") -> bool:
class OxiAPIError(Exception):
def __init__(self, message: str, status_code: Optional[int] = None):
def __init__(self, message: str, status_code: int | None = None):
super().__init__(message)
self.status_code = status_code
self.message = message

View File

@@ -1,4 +1,4 @@
from typing import Callable, Type
from collections.abc import Callable
from .base import BaseDevice
@@ -7,7 +7,7 @@ device_registry = {}
def register_parser(
name: list[str] | str,
) -> Callable[[Type[BaseDevice]], Type[BaseDevice]]:
) -> Callable[[type[BaseDevice]], type[BaseDevice]]:
def wrapper(cls):
name_list = []
if isinstance(name, str):

View File

@@ -1,10 +1,11 @@
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from pathlib import Path
from ttp import ttp
from oxi.exception import OxiAPIError
from oxi.interfaces.contract import Device
import xml.etree.ElementTree as ET
from oxi.interfaces.contract import Interfaces, System, Vlans
from oxi.interfaces.contract import Device, Interfaces, System, Vlans
class BaseDevice(ABC):
@@ -40,7 +41,7 @@ class BaseDevice(ABC):
Raises:
ValueError: if raw data cannot be validated by the contract.
"""
""" # noqa: E501
return self.raw.get("vlans", [])
def interfaces(self) -> list[dict]:
@@ -52,7 +53,7 @@ class BaseDevice(ABC):
Raises:
ValueError: if raw data cannot be validated by the contract.
"""
""" # noqa: E501
return self.raw.get("interfaces", [])
def system(self) -> dict:
@@ -82,11 +83,7 @@ class BaseDevice(ABC):
def _validate_contract(self) -> dict:
if self.raw is None:
msg = (
f"Node {self.name} not found"
if self.name
else "Node not found"
)
msg = f"Node {self.name} not found" if self.name else "Node not found"
raise OxiAPIError(msg, status_code=404)
system_data = self.system()
interfaces_data = self._as_list(self.interfaces())
@@ -99,8 +96,8 @@ class BaseDevice(ABC):
if "vlans" in self._declared_sections:
if "vlans" not in self.raw:
raise ValueError(
f"{self.__class__.__name__}: template '{self.template}' declares optional group "
f"'vlans', but TTP did not return it."
f"{self.__class__.__name__}: template '{self.template}' "
f"declares optional group 'vlans', but TTP did not return it."
)
vlans_data = self._as_list(self.vlans())
result["vlans"] = [Vlans(**item) for item in vlans_data]

View File

@@ -1,4 +1,5 @@
from ipaddress import IPv4Address
from pydantic import BaseModel, ConfigDict, Field

View File

@@ -3,5 +3,5 @@ import pkgutil
package = __package__
for loader, module_name, is_pkg in pkgutil.iter_modules(__path__):
for _, module_name, _ in pkgutil.iter_modules(__path__):
importlib.import_module(f"{package}.{module_name}")

View File

@@ -1,4 +1,5 @@
from ipaddress import ip_interface
from oxi.interfaces import register_parser
from oxi.interfaces.base import BaseDevice
from oxi.interfaces.utils import decode_utf

View File

@@ -6,7 +6,6 @@ from oxi.exception import OxiAPIError
from .view import NodeView
if TYPE_CHECKING:
from requests import Session

View File

@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
from .conf import NodeConfig
if TYPE_CHECKING:
from requests import Session

View File

@@ -47,4 +47,12 @@ include-package-data = true
dev = [
"pytest>=9.0.3",
"responses>=0.26.1",
"ruff>=0.15.17",
]
[tool.ruff]
target-version = "py310"
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

View File

@@ -1,8 +1,8 @@
import json
import pytest
from conftest import FIXTURES, load
from oxi.interfaces import device_registry
MODEL_CASES = [

View File

@@ -1,7 +1,7 @@
import pytest
import responses
from conftest import load
from oxi import OxiAPI
from oxi.exception import OxiAPIError
@@ -84,4 +84,4 @@ def test_unknown_model_raises_value_error():
api = OxiAPI(url=BASE)
with pytest.raises(ValueError, match="not found in registry"):
api.node("HQ").config
_ = api.node("HQ").config

View File

@@ -1,10 +1,9 @@
import pytest
from conftest import load
from oxi.exception import OxiAPIError
from oxi.interfaces import device_registry
from oxi.interfaces.base import BaseDevice
from oxi.interfaces.contract import Interfaces, System
from oxi.interfaces.utils import decode_utf, expand_vlan_range

38
uv.lock generated
View File

@@ -158,23 +158,34 @@ dependencies = [
{ name = "ttp" },
]
[package.dev-dependencies]
[package.optional-dependencies]
dev = [
{ name = "pytest" },
{ name = "responses" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
{ name = "responses" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "pydantic", specifier = ">=2.12.5" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" },
{ name = "requests", specifier = ">=2.32.5" },
{ name = "responses", marker = "extra == 'dev'", specifier = ">=0.26.1" },
{ name = "ttp", specifier = ">=0.10.0" },
]
provides-extras = ["dev"]
[package.metadata.requires-dev]
dev = [
{ name = "pytest", specifier = ">=9.0.3" },
{ name = "responses", specifier = ">=0.26.1" },
{ name = "ruff", specifier = ">=0.15.17" },
]
[[package]]
@@ -448,6 +459,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/31/6a620b4427d546b9e7cca8b3b8c5f0559d9cef2bb9eedcda7f73c1473c19/responses-0.26.1-py3-none-any.whl", hash = "sha256:8aacc4586eb08fb2208ef64a9eb4258d9b0c6e6f4260845f2f018ab847495345", size = 35502, upload-time = "2026-05-21T19:56:38.046Z" },
]
[[package]]
name = "ruff"
version = "0.15.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" },
{ url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" },
{ url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" },
{ url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" },
{ url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" },
{ url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" },
{ url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" },
{ url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" },
{ url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" },
{ url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" },
{ url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" },
{ url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" },
{ url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" },
{ url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" },
{ url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
]
[[package]]
name = "tomli"
version = "2.4.1"