Compare commits

..

21 Commits

Author SHA1 Message Date
IluaAir
77f539254c Add Qtech model and template; clean up Mikrotik model
- Introduced a new `Qtech` model with a corresponding TTP template for parsing QTECH devices.
- Removed commented-out methods in the `Mikrotik` model to improve code clarity and maintainability.
2026-02-25 17:00:36 +03:00
IluaAir
8a126d8806 Update pyproject.toml
- Changed the `requires-python` field from ">=3.13" to ">=3.11" .
2026-02-25 14:16:00 +03:00
IluaAir
c9d171e2fa Update Keenetic TTP template
- Modified the `description` field in the Keenetic TTP template to strip surrounding quotes.
2026-02-25 13:47:06 +03:00
IluaAir
ac835d6b56 Enhance ModelView class with iterable and indexing support
- Added iterator, length, and item access methods to the `ModelView` class, allowing it to handle single models and lists more effectively.
- Refactored the `vlans` method in the `Keenetic` model to restore functionality for decoding VLAN descriptions, improving data processing consistency.
2026-02-24 23:19:57 +03:00
IluaAir
3159570e27 Add ModelView class for enhanced JSON serialization in NodeConfig
- Introduced a new `ModelView` class to facilitate JSON serialization of model data, supporting both single models and lists.
- Updated `NodeConfig` methods to utilize `ModelView` for `vlans`, `interfaces`, and `system` properties, improving data handling consistency.
- Refactored the `json` method to ensure proper serialization of parsed data.
2026-02-24 22:50:55 +03:00
IluaAir
16e97692fc Refactor NodeConfig methods for consistency in data access
- Renamed `l3interfaces` to `interfaces` and `vlaninterfaces` to `system` in the `NodeConfig` class to align with updated data structure.
- This change enhances clarity and consistency in method naming, reflecting the underlying data model more accurately.
2026-02-24 22:31:18 +03:00
IluaAir
3fdff33e2e Implement validation for optional VLANs in BaseDevice
- Added a check in the BaseDevice class to raise a ValueError if the 'vlans' section is declared in the template but not present in the raw data returned by TTP.
- This enhancement improves data integrity by ensuring that optional groups are properly validated before processing.
2026-02-24 22:28:36 +03:00
IluaAir
753268a381 Refactor BaseDevice and Interfaces models for improved validation and structure
- Introduced `_declared_sections` in `BaseDevice` to track declared template groups.
- Enhanced `_validate_contract` method to conditionally validate VLANs based on declared sections.
- Updated docstrings in `BaseDevice` and `Interfaces` models for clarity on expected structures.
- Refactored `Interfaces` and `Vlans` models to improve field definitions and aliases.
- Commented out unused `vlans` method in `Keenetic` model for future reference.
2026-02-22 15:52:45 +03:00
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
IluaAir
2394296f5b Enhance Keenetic model and update templates for improved data handling
- Implemented the `interfaces` and `vlans` methods in the `Keenetic` model to process and decode interface and VLAN data.
- Added a `_decode_utf` method to handle UTF-8 encoded descriptions.
- Updated the Keenetic TTP template to define structured groups for system, interfaces, and VLANs.
- Refactored file paths in the `Mikrotik` model for consistency and clarity.
2026-02-22 00:21:55 +03:00
IluaAir
a41cb1f7ec """
Implement Keenetic model and enhance BaseDevice documentation

- Added a new `Keenetic` model that registers a parser for KeeneticOS, extending the `BaseDevice` class.
- Updated docstrings in the `BaseDevice` class methods to provide clearer descriptions of their functionality and expected data structures.
- Introduced `model_config` in the `Vlans` model to enable name-based population.
- Removed unnecessary print statements from the `Mikrotik` model methods to streamline output.
"""
2026-02-19 00:49:10 +03:00
IluaAir
a938fe2d47 Update Interfaces model and Mikrotik template for improved data handling
- Made the `description` field in the `Interfaces` model optional to enhance flexibility.
- Renamed `id` to `vlan_id` in the `Vlans` model for clarity.
- Refactored the `Mikrotik` class methods to streamline raw data access and removed unnecessary print statements.
- Updated the Mikrotik TTP template to reflect changes in variable names and improve overall structure for interface and VLAN configurations.
2026-02-19 00:31:54 +03:00
IluaAir
685ff19d2f Enhance BaseDevice validation and update Mikrotik model
- Introduced a new method `_validate_template_groups` in `BaseDevice` to ensure TTP templates declare all required and optional sections.
- Updated `_REQUIRED_SECTIONS` and added `_OPTIONAL_SECTIONS` to improve template validation.
- Modified the `vlans` method in `Mikrotik` to streamline raw data handling and added debug print statements for clarity.
- Revised the Mikrotik TTP template to include structured variable definitions and improved group handling for interfaces and VLANs.
2026-02-19 00:16:37 +03:00
IluaAir
c434712309 Refactor Mikrotik model and update template structure
- Changed the parsing method in `NodeConfig` to use `parse()` instead of `parse_config()`.
- Updated the template loading path in `BaseDevice` to reflect the new directory structure.
- Enhanced the `Mikrotik` class with new methods for `system`, `interfaces`, and `vlans`, including debug print statements.
- Expanded the TTP template for Mikrotik to include structured variable definitions and groups for system, interfaces, and VLANs.
2026-02-18 15:45:17 +03:00
IluaAir
91b6606e3f Enhance BaseDevice methods with detailed parsing documentation
- Updated the `vlans`, `interfaces`, and `system` methods in the `BaseDevice` class to include comprehensive docstrings outlining expected raw data structures and error handling.
- Modified the `Interfaces` model in `contract.py` to allow optional fields for `ip_address` and `mask`, improving flexibility in interface definitions.
2026-02-18 00:55:43 +03:00
IluaAir
72cd796803 Refactor parse method in BaseDevice for improved readability
- Simplified the `parse` method in the `BaseDevice` class by directly returning a `Device` instance with keyword arguments, enhancing code clarity and maintainability.
2026-02-18 00:11:14 +03:00
IluaAir
e82bc25346 Refactor BaseDevice and update Device model structure
- Enhanced the `BaseDevice` class with methods for loading templates and parsing configuration data using TTP.
- Updated the `Device` model to use lists for `interfaces` and `vlans`, allowing for multiple entries.
- Introduced new TTP template files for structured data parsing.
2026-02-18 00:06:48 +03:00
IluaAir
2cfcc41e58 Enhance device interface structure and update dependencies
- Added `.idea` to `.gitignore` to exclude IDE configuration files.
- Introduced `pydantic` version `2.12.5` as a dependency in `pyproject.toml` and `uv.lock`.
- Added `annotated-types` as a new dependency in `uv.lock`.
- Refactored `BaseDevice` class to include abstract methods for device properties and loading templates.
- Created `Interfaces`, `System`, `Vlans`, and `Device` models in a new `contract.py` file for structured data handling.
- Updated `Mikrotik` class to implement the new `BaseDevice` structure and added a template loading method.
2026-02-16 00:57:09 +03:00
IluaAir
544688dae1 Update .gitignore and refactor Mikrotik model to inherit from BaseDevice
- Added *.txt files to .gitignore to exclude etc files.
- Refactored Mikrotik class to inherit from BaseDevice for improved structure and functionality.
2026-02-15 02:44:29 +03:00
IluaAir
b60182ef3c Add ttp dependency and refactor OxiAPI and NodeConfig classes
- Added `ttp` as a dependency in `pyproject.toml` and `uv.lock`.
- Updated `NodeConfig` to store model names in lowercase.
- Refactored `OxiAPI` to always create a new session and added a `close` method.
- Removed unnecessary logging in `Node` class.
- Introduced interfaces for device registration with a new `BaseDevice` class and a `register_parser` function.
- Created initial structure for device models, including a `Mikrotik` parser.
2026-02-14 21:31:02 +03:00
IluaAir
8e85086d98 base structure 2026-02-14 01:07:26 +03:00
20 changed files with 802 additions and 2 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
main.py
# Virtual environments
.venv
.idea
# etc files
*.txt

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

6
oxi/__init__.py Normal file
View File

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

86
oxi/conf.py Normal file
View File

@@ -0,0 +1,86 @@
from functools import cached_property
import json
from typing import TYPE_CHECKING, Generic, Iterator, Type, TypeVar
from pydantic import BaseModel
from .interfaces import BaseDevice, device_registry
if TYPE_CHECKING:
from requests import Session
TModel = TypeVar("TModel", bound=BaseModel)
class ModelView(Generic[TModel]):
def __init__(self, model: TModel | list[TModel]):
self._model = model
def json(self) -> str:
if isinstance(self._model, list):
return json.dumps(
[item.model_dump(by_alias=True) for item in self._model],
ensure_ascii=False,
)
return self._model.model_dump_json(by_alias=True)
def __iter__(self) -> Iterator[TModel]:
if isinstance(self._model, list):
return iter(self._model)
raise TypeError("This view wraps a single model, not a list")
def __len__(self) -> int:
if isinstance(self._model, list):
return len(self._model)
raise TypeError("This view wraps a single model, not a list")
def __getitem__(self, item):
if isinstance(self._model, list):
return self._model[item]
raise TypeError("This view wraps a single model, not a list")
def __getattr__(self, item):
return getattr(self._model, item)
def __repr__(self) -> str:
return repr(self._model)
class NodeConfig:
def __init__(self, session: "Session", full_name: str, model: str, base_url: str):
self._session = session
self._full_name = full_name
self._model = model.lower()
self._url = f"{base_url}/node/fetch/{full_name}"
self._device: type[BaseDevice] = device_registry.get(self._model.lower())
if self._device is None:
raise ValueError(f"Device model '{self._model}' not found in registry")
self._parsed_data = self._device(self.text).parse()
@cached_property
def _response(self):
response = self._session.get(self._url)
response.raise_for_status()
return response
@property
def text(self):
return self._response.text
def json(self):
return self._parsed_data.model_dump_json()
def __str__(self):
return self.text
@property
def vlans(self):
return ModelView(self._parsed_data.vlans)
@property
def interfaces(self):
return ModelView(self._parsed_data.interfaces)
@property
def system(self):
return ModelView(self._parsed_data.system)

28
oxi/core.py Normal file
View File

@@ -0,0 +1,28 @@
from typing import Optional
from requests import Session
from .node import Node
class OxiAPI:
def __init__(
self,
url: str,
username: Optional[str] = None,
password: Optional[str] = None,
verify: bool = True,
):
self.base_url = url.rstrip("/")
self._session = Session()
self._session.verify = verify
if username and password:
self._session.auth = (username, password)
self.node = Node(self._session, self.base_url)
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
return self._session.close()

View File

@@ -0,0 +1,26 @@
from typing import Callable, Type
from .base import BaseDevice
device_registry = {}
def register_parser(
name: list[str],
) -> Callable[[Type[BaseDevice]], Type[BaseDevice]]:
def wrapper(cls):
name_list = []
if isinstance(name, str):
name_list.append(name)
else:
name_list.extend(name)
for item in name_list:
device_registry[item.lower()] = cls
return cls
return wrapper
from . import models # noqa: E402, F401
__all__ = ["register_parser", "device_registry"]

130
oxi/interfaces/base.py Normal file
View File

@@ -0,0 +1,130 @@
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._declared_sections = None
self._validate_template_groups()
self.raw: dict = self._run_ttp()
@property
@abstractmethod
def template(self) -> str:
"""
Expected structure:
Название файла с парсером ttp
Returns:
None
"""
def vlans(self) -> list[dict]:
"""
Parse VLAN configuration from self.raw['vlans'].
Expected 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) -> dict:
system_data = self.system()
interfaces_data = self.interfaces() or []
result = {
"system": System(**system_data),
"interfaces": [Interfaces(**item) for item in interfaces_data],
"vlans": [],
}
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."
)
vlans_data = self.vlans() or []
result["vlans"] = [Vlans(**item) for item in vlans_data]
return result
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:
"""Проверяем только обязательные группы в template."""
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")}
self._declared_sections = declared
missing_required = self._REQUIRED_SECTIONS - declared
if missing_required:
raise ValueError(
f"{self.__class__.__name__}: template '{self.template}' "
f"missing required groups: {sorted(missing_required)}. "
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 groups: {sorted(missing)}. "
f"Return only: {(raw.keys())}"
)
return raw
def parse(self) -> Device:
return Device(**self._validate_contract())

View File

@@ -0,0 +1,42 @@
from ipaddress import IPv4Address
from pydantic import BaseModel, ConfigDict, Field
class Base(BaseModel):
model_config = ConfigDict(populate_by_name=True)
class System(BaseModel):
"""
Requred
"""
model: str
serial_number: str
version: str
class Interfaces(Base):
"""
Requred
"""
name: str = Field(alias="interface")
ip_address: IPv4Address | None = None
mask: int | None = None
description: str | None = None
class Vlans(Base):
"""
Optional
"""
vlan_id: int = Field(alias="id")
name: str | None = Field(default=None, alias="description")
class Device(BaseModel):
system: System
interfaces: list[Interfaces]
vlans: list[Vlans] = []

View File

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

View File

@@ -0,0 +1,49 @@
from ipaddress import ip_interface
from oxi.interfaces import register_parser
from oxi.interfaces.base import BaseDevice
@register_parser(["NDMS", "keenetic", "KeeneticOS"])
class Keenetic(BaseDevice):
template = "keenetic.ttp"
def _decode_utf(self, text: str):
if "\\x" in text:
desc = text.strip('"')
decoded = (
desc.encode("utf-8")
.decode("unicode_escape")
.encode("latin1")
.decode("utf-8")
)
return decoded
return text
def interfaces(self):
interfaces: list[dict] = self.raw["interfaces"]
for item in interfaces:
if item.get("ip_address") and item.get("netmask"):
ipaddress = ip_interface(
f"{item.get('ip_address')}/{item.get('netmask')}"
)
item["mask"] = ipaddress.network.prefixlen
item.pop("netmask", "Key not found")
if item.get("description"):
decoded = self._decode_utf(item.get("description", ""))
item["description"] = decoded
return interfaces
def vlans(self):
vlans = self.raw["vlans"]
for item in vlans:
if item.get("description"):
decoded = self._decode_utf(item.get("description", ""))
item["description"] = decoded
return vlans
if __name__ == "__main__":
with open("./test2.txt") as file:
data = file.read()
mikr = Keenetic(data)
print(mikr.parse().model_dump_json())

View File

@@ -0,0 +1,16 @@
import os
from oxi.interfaces import register_parser
from oxi.interfaces.base import BaseDevice
@register_parser(["routeros", "ros", "mikrotik"])
class Mikrotik(BaseDevice):
template = "mikrotik.ttp"
if __name__ == "__main__":
print(os.path.abspath(os.curdir))
with open("./test.txt") as file:
data = file.read()
mikr = Mikrotik(data)
print(mikr.parse().json())

View File

@@ -0,0 +1,7 @@
from oxi.interfaces import register_parser
from oxi.interfaces.base import BaseDevice
@register_parser(["QTECH"])
class Qtech(BaseDevice):
template = "qtech.ttp"

View File

@@ -0,0 +1,16 @@
<doc>
</doc>
<vars>
</vars>
<group name="system">
...
</group>
<group name="interfaces">
...
</group>
<group name="vlans">
...
</group>

View File

@@ -0,0 +1,29 @@
<doc>
</doc>
<vars>
default_system = {
"model": "",
"serial_number": ""
}
default_interfaces = {}
</vars>
<group name="system" default="default_system">
! release: {{ version }}
! model: {{ model | ORPHRASE }}
! hw_version: {{ serial_number }}
</group>
<group name="interfaces">
interface {{ name | _start_ | exclude("Vlan") }}
rename {{ rename }}
description {{ description | ORPHRASE | strip('"') }}
ip address {{ ip_address }} {{ netmask }}
{{ shutdown | re("up") | replace("up","False") | strip('"') }}
{{ shutdown | re("down") | replace("down","True") | strip('"') }}
</group>
<group name="vlans">
interface {{ ignore }}/Vlan{{ vlan_id }}
description {{ description | ORPHRASE | strip('"') }}
</group>

View File

@@ -0,0 +1,47 @@
<doc>
some templates
</doc>
<vars>
default_system = {
"model": "",
"serial_number": ""
}
default_interfaces = {
"disabled": "False"
}
default_vlans = {
"disabled": "False",
"mtu": None
}
</vars>
<group name="system" default="default_system">
# version: {{ version }}{{ ignore('.*') }}
# model = {{ model }}
# serial number = {{ serial_number }}
</group>
<group name="interfaces" default="default_interfaces">
/ip address
## not disabled and no comment
add address={{ ip_address | _start_ }}/{{ mask }} interface={{ name }} network={{ network }}
## not disabled and comment with/without quotes
add address={{ ip_address | _start_ }}/{{ mask }} comment={{ description | ORPHRASE | exclude("disabled=") | strip('"')}} interface={{ name }} network={{ network }}
## disabled no comment
add address={{ ip_address | _start_ }}/{{ mask }} disabled={{ disabled | replace("yes","True") | strip('"')}} interface={{ name }} network={{ network }}
## disabled with comment with/without quotes
add address={{ ip_address | _start_ }}/{{ mask }} comment={{ description | ORPHRASE | exclude("disabled=") | strip('"') }} disabled={{ disabled }} interface={{ name }} network={{ network }}
</group>
<group name="vlans">
/interface vlan
## not disabled and no comment
add interface={{ interface | _start_ }} name={{ name | ORPHRASE | strip('"') }} vlan-id={{ vlan_id }}
## not disabled and comment with/without quotes
add comment={{ comment | _start_ | ORPHRASE | exclude("disabled=") | strip('"')}} interface={{ interface }} name={{ name | ORPHRASE | strip('"') }} vlan-id={{ vlan_id }}
## disabled with comment with/without quotes
add comment={{ comment | _start_ | ORPHRASE | exclude("disabled=") | strip('"')}} disabled={{ disabled | replace("yes","True") | strip('"') }} interface={{ interface }} name={{ name | ORPHRASE | strip('"') }} vlan-id={{ vlan_id }}
## disabled no comment
add interface={{ interface | _start_ }} name={{ name | ORPHRASE | strip('"') }} vlan-id={{ vlan_id }} disabled={{ disabled | replace("yes","True") | strip('"') }}
</group>

View File

@@ -0,0 +1,24 @@
<doc>
</doc>
<vars>
default_system = {
"model": "",
"serial_number": ""
}
</vars>
<group name="system">
! {{ model }} Series Software, Version {{ bootrom_ver }} Build {{ version }}, {{ ignore }}
! Serial num:{{ serial_number }}, {{ ignore }}
</group>
<group name="interfaces">
interface {{ interface }}
description {{ description | ORPHRASE }}
ip address {{ ip_address }} {{ netmask }}
</group>
<group name="vlans">
vlan {{ vlan_id }}
name {{ name }}
</group>

25
oxi/node.py Normal file
View File

@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from .view import NodeView
if TYPE_CHECKING:
from requests import Session
class Node:
def __init__(self, session: "Session", base_url: str):
self._session = session
self._base_url = base_url
self._data = None
def __call__(self, name: str) -> NodeView:
url = f"{self._base_url}/node/show/{name}"
if not url.endswith(".json"):
url += ".json"
response = self._session.get(url)
if response.status_code == 500:
raise ValueError(f"page {url} not found")
return NodeView(
session=self._session, base_url=self._base_url, data=response.json()
)

35
oxi/view.py Normal file
View File

@@ -0,0 +1,35 @@
from functools import cached_property
from typing import TYPE_CHECKING
from .conf import NodeConfig
if TYPE_CHECKING:
from requests import Session
class NodeView:
def __init__(self, session: "Session", base_url: str, data: dict):
self._session = session
self._base_url = base_url
self._data = data
@property
def ip(self):
return self._data.get("ip")
@property
def full_name(self):
return self._data.get("full_name")
@property
def group(self):
return self._data.get("group")
@property
def model(self):
return self._data.get("model")
@cached_property
def config(self):
return NodeConfig(self._session, self.full_name, self.model, self._base_url)

View File

@@ -7,10 +7,12 @@ name = "oxipy"
version = "0.1.0"
description = "Oxi API client"
readme = "README.md"
requires-python = ">=3.13"
requires-python = ">=3.11"
dependencies = [
"pydantic>=2.12.5",
"requests>=2.32.5",
"ttp>=0.10.0",
]
[tool.setuptools.packages.find]
where = ["."]
include = ["oxi*"]
include = ["oxi*"]

210
uv.lock generated Normal file
View File

@@ -0,0 +1,210 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "certifi"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "oxipy"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "pydantic" },
{ name = "requests" },
{ name = "ttp" },
]
[package.metadata]
requires-dist = [
{ name = "pydantic", specifier = ">=2.12.5" },
{ name = "requests", specifier = ">=2.32.5" },
{ name = "ttp", specifier = ">=0.10.0" },
]
[[package]]
name = "pydantic"
version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "ttp"
version = "0.10.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/e6/9169d35574be82df2a0cdd2546f4f83d0d30964cf0043fc9784df855b024/ttp-0.10.0.tar.gz", hash = "sha256:40f1ca61ee1431f5b1ab5326fb55f852a04749e9574792d45455b62c5e7ac97b", size = 64665, upload-time = "2025-11-02T08:47:50.329Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/c3/60abb45bd8eb973997f133eb76949523478d35dfc551a0dbd8906b6a8075/ttp-0.10.0-py3-none-any.whl", hash = "sha256:9985e0ca414e85d41493a6291a924624b9a08c48c78d2d01477cc60ba2a347c1", size = 84287, upload-time = "2025-11-02T08:47:48.656Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
name = "urllib3"
version = "2.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]